# -*- CPERL -*-
package LaTeXML::Package::Pool;
use strict;
use LaTeXML::Global;
use LaTeXML::Package;
DefKeyVal('Module','id','Semiverbatim');
DefKeyVal('Module','cd','Semiverbatim');
DefEnvironment('{module} OptionalKeyVals:Module',
"?#excluded()(#body)",
# beforeDigest=>\&useTheoryItemizations,
afterDigestBegin=>sub {
my($stomach, $whatsit)=@_;
$whatsit->setProperty(excluded=>LookupValue('excluding_modules'));
my $keys = $whatsit->getArg(1);
my($id, $cd)=$keys
&& map(ToString($keys->getValue($_)),qw(id cd));
#make sure we have an id or give a stub one otherwise:
if (not $id) {
#do magic to get a unique id for this theory
#$whatsit->setProperties(beginItemize('theory'));
#$id = ToString($whatsit->getProperty('id'));
# changed: beginItemize returns the hash returned by RefStepCounter.
# RefStepCounter deactivates any scopes for the current value of the
# counter which causes the stored prop. of the env. not to be
# visible anymore.
$id = LookupValue('stex:theory:id') || 0;
AssignValue('stex:theory:id', $id+1);
$id = "I$id";
}
$cd = $id unless $cd;
# update the catalog with paths for modules
my $module_paths = LookupValue('module_paths') || {};
$module_paths->{$id} = LookupValue('last_module_path');
AssignValue('module_paths', $module_paths, 'global');
#Update the current module position
AssignValue(current_module => $id);
AssignValue(module_cd => $cd) if $cd;
#activate the module in our current scope
$STATE->activateScope("module:".$id);
#Activate parent scope, if present
my $parentmod = LookupValue('parent_module');
use_module($parentmod) if $parentmod;
#Update the current parent module
AssignValue("parent_of_$id"=>$parentmod,'global');
AssignValue("parent_module" => $id);
return; },
afterDigest => sub {
#Move a step up on the module ancestry
AssignValue("parent_module" => LookupValue("parent_of_".LookupValue("parent_module")));
return;
});
sub use_module {
my($module,%ancestors)=@_;
$module = ToString($module);
if (defined $ancestors{$module}) {
Fatal(":module \"$module\" leads to import cycle!");
}
$ancestors{$module}=1;
# Depth-first load definitions from used modules, disregarding cycles
foreach my $used_module (@{ LookupValue("module_${module}_uses") || []}){
use_module($used_module,%ancestors);
}
# then load definitions for this module
$STATE->activateScope("module:$module"); }#$
DefMacro('\coolurion',sub {AssignValue('cooluri'=>1);});
DefMacro('\coolurioff',sub {AssignValue('cooluri'=>0);});
sub omext {
my ($mod)=@_; my $dest='';
$mod = ToString($mod);
if ($mod) {
#We need a constellation of abs_path invocations
# to make sure that all symbolic links get resolved
if ($mod=~/^(\w)+:\/\//) { $dest=$mod; } else {
my ($d,$f,$t) = pathname_split(abs_path($mod));
$d = pathname_relative(abs_path($d),abs_path(cwd()));
$dest=$d."/".$f;
}
}
$dest.=".omdoc" if (ToString($mod) && !LookupValue('cooluri'));
return Tokenize($dest);}
sub importmoduleI {
my($stomach,$whatsit)=@_;
my $file = ToString($whatsit->getArg(1));
my $omdocmod = $file.".omdoc" if $file;
my $module = ToString($whatsit->getArg(2));
my $containing_module = LookupValue('current_module');
AssignValue('last_import_module',$module);
#set the relation between the current module and the one to be imported
PushValue("module_".$containing_module."_uses"=>$module) if $containing_module;
#check if we've already loaded this module file or no file path given
if((!$file) || (LookupValue('file_'.$module.'_loaded'))) {use_module($module);} #if so activate it!
else {
#if not:
my $gullet = $stomach->getGullet;
#1) mark as loaded
AssignValue('file_'.$module.'_loaded' => 1, 'global');
#open a group for its definitions so that they are localized
$stomach->bgroup;
#update the last module path
AssignValue('last_module_path', $file);
#queue the closing tag for this module in the gullet where it will be executed
#after all other definitions of the imported module have been taken care of
$gullet->unread(Invocation(T_CS('\end@requiredmodule'), Tokens(Explode($module)))->unlist);
#we only need to load the sms definitions without generating any xml output, so we set the flag to 1
AssignValue('excluding_modules' => 1);
#queue this module's sms file in the gullet so that its definitions are imported
$gullet->input($file,['sms']);
}
return;}
DefConstructor('\importmodule OptionalSemiverbatim {}',
"",
afterDigest=>sub{ importmoduleI(@_)});
DefMacro('\importmodulevia OptionalSemiverbatim {}','\endgroup\importmoduleI[#1]{#2}\begin{importmoduleenv}[#1]{#2}');
DefMacroI('\end{importmodulevia}',undef,'\end{importmoduleenv}');
DefEnvironment('{importmoduleenv} OptionalSemiverbatim {}',
""
. "#body"
."");
DefConstructor('\importmoduleI OptionalSemiverbatim {}', '',
afterDigest=>sub{ importmoduleI(@_)});
DefConstructor('\vassign{}{}',
""
. "#1"
. "#2"
."");
DefConstructor('\tassign[]{}{}',
""
. ""
. ""
."",
afterDigest=> sub {
my ($stomach,$whatsit) = @_;
$whatsit->setProperty('currentModule',LookupValue("current_module"));
$whatsit->setProperty('lastImportModule',LookupValue("last_import_module"));
});
DefConstructor('\ttassign{}{}',
""
. "#1"
. "#2"
."");
DefConstructor('\importOMDocmodule OptionalSemiverbatim {}{}',"",
afterDigest=>sub{
#Same as \importmodule, just switch second and third argument.
my ($stomach,$whatsit) = @_;
my $path = $whatsit->getArg(1);
my $ouri = $whatsit->getArg(2);
my $module = $whatsit->getArg(3);
$whatsit->setArgs(($path, $module,$ouri));
importmoduleI($stomach,$whatsit);
return;
});
DefConstructor('\metalanguage OptionalSemiverbatim {}',
"",
afterDigest=>sub{ importmoduleI(@_)});
DefMacro('\DefMathOp OptionalKeyVals:symdef {}',
sub {
my($self,$keyval,$pres)=@_;
my $name = KeyVal($keyval,'name') if $keyval;
#Rewrite this token
my $scopes = $STATE->getActiveScopes;
DefMathRewrite(xpath=>'descendant-or-self::ltx:XMath',match=>ToString($pres),
replace=>sub{
map {$STATE->activateScope($_);} @$scopes;
$_[0]->absorb(Digest("\\".ToString($name)));
});
#Invoke symdef
(Invocation(T_CS('\symdef'),$keyval,$name,undef,$pres)->unlist);
});
DefMacro('\symdef OptionalKeyVals:symdef {}[]{}',
sub {
my($self,@args)=@_;
((Invocation(T_CS('\@symdef'),@args)->unlist),
(LookupValue('excluding_modules') ? ()
: (Invocation(T_CS('\@symdef@pres'), @args)->unlist))); });
#Current list of recognized formatter command sequences:
our @PresFormatters = qw (infix prefix postfix assoc mixfixi mixfixa mixfixii mixfixia mixfixai mixfixaii mixfixiii);
DefPrimitive('\@symdef OptionalKeyVals:symdef {}[]{}', sub {
my($stomach,$keys,$cs,$nargs,$presentation)=@_;
my($name,$cd,$role,$bvars,$bvar)=$keys
&& map($_ && $_->toString,map($keys->getValue($_), qw(name cd role
bvars bvar)));
$cd = LookupValue('module_cd') unless $cd;
$name = $cs unless $name;
#Store for later lookup
AssignValue("symdef.".ToString($cs).".cd"=>ToString($cd),'global');
AssignValue("symdef.".ToString($cs).".name"=>ToString($name),'global');
$nargs = (ref $nargs ? $nargs->toString : $nargs || 0);
my $module = LookupValue('current_module');
my $scope = (($keys && ($keys->getValue('local') || '' eq 'true')) ? 'module_local' : 'module').":".$module;
#The DefConstructorI Factory is responsible for creating the \symbol command sequences as dictated by the \symdef
DefConstructorI("\\".$cs->toString,convertLaTeXArgs($nargs+1,'default'), sub {
my ($document,@args) = @_;
my $icvariant = shift @args;
my @props = @args;
#Lookup the presentation from the State, if a variant:
@args = splice(@props,0,$nargs);
my %prs = @props;
my $localpres = $prs{presentation};
$prs{isbound} = "BINDER" if ($bvars || $bvar);
my $wrapped;
my $parent=$document->getNode;
if(! defined $parent->lookupNamespacePrefix("http://omdoc.org/ns")){ # namespace not already declared?
$document->getDocument->documentElement->setNamespace("http://omdoc.org/ns","omdoc",0); }
my $symdef_scope=$parent->exists('ancestor::omdoc:rendering'); #Are we in a \symdef rendering?
if (($localpres =~/^LaTeXML::Token/) && $symdef_scope) {
#Note: We should probably ask Bruce whether this maneuver makes sense
# We jump back to digestion, at a processing stage where it has been already completed
# Hence need to reinitialize all scopes and make a new group. This is probably expensive to do.
my @toks = $localpres->unlist;
while(@toks && $toks[0]->equals(T_SPACE)){ shift(@toks); } # Remove leading space
my $formatters = join("|",@PresFormatters);
$formatters = qr/$formatters/;
$wrapped = (@toks && ($toks[0]->toString =~ /^\\($formatters)$/));
$localpres = Invocation(T_CS('\@use'),$localpres) unless $wrapped;
# Plug in the provided arguments, doing a nasty reversion:
my @sargs = map (Tokens($_->revert), @args);
$localpres = Tokens(LaTeXML::Expandable::substituteTokens($localpres,@sargs)) if $nargs>0;
#Digest:
my $stomach = $STATE->getStomach;
$stomach->beginMode('inline-math');
$STATE->activateScope($scope);
use_module($module);
use_module(LookupValue("parent_of_".$module)) if LookupValue("parent_of_".$module);
$localpres=$stomach->digest($localpres);
$stomach->endMode('inline-math');
}
else { #Some are already digested to Whatsit, usually when dropped from a wrapping constructor
}
if ($nargs == 0) {
if (!$symdef_scope) { #Simple case - discourse flow, only a single XMTok
#Referencing XMTok when not in \symdefs:
$document->insertElement('ltx:XMTok',undef,(name=>$cs->toString, meaning=>$name,omcd=>$cd,role => $role,scriptpos=>$prs{'scriptpos'}));
}
else {
if ($symdef_scope && ($localpres =~/^LaTeXML::Whatsit/) && (!$wrapped)) {#1. Simple case: converts to a single token
$localpres->setProperties((name=>$cs->toString, meaning=>$name,omcd=>$cd,role => $role,scriptpos=>$prs{'scriptpos'}));
}
else {
#Experimental treatment - COMPLEXTOKEN
#$role=$role||'COMPLEXTOKEN';
#$document->openElement('ltx:XMApp',role=>'COMPLEXTOKEN');
#$document->insertElement('ltx:XMTok',undef,(name=>$cs->toString, meaning=>$name, omcd=>$cd, role=>$role, scriptpos=>$prs{'scriptpos'}));
#$document->openElement('ltx:XMWrap');
#$document->absorb($localpres);
#$document->closeElement('ltx:XMWrap');
#$document->closeElement('ltx:XMApp');
}
#We need expanded presentation when invoked in \symdef scope:
#Suppress errors from rendering attributes when absorbing.
#This is bad style, but we have no way around it due to the digestion acrobatics.
my $verbosity = $LaTeXML::Global::STATE->lookupValue('VERBOSITY');
my $errors = $LaTeXML::Global::STATE->getStatus('error');
$LaTeXML::Global::STATE->assignValue('VERBOSITY',-5);
#Absorb presentation:
$document->absorb($localpres);
#Return to original verbosity and error state:
$LaTeXML::Global::STATE->assignValue('VERBOSITY',$verbosity);
$LaTeXML::Global::STATE->setStatus('error',$errors);
#Strip all/any