1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
# /=====================================================================\ #
# | LaTeXML::Core::Definition::Conditional | #
# | Representation of definitions of Control Sequences | #
# |=====================================================================| #
# | Part of LaTeXML: | #
# | Public domain software, produced as part of work done by the | #
# | United States Government & not subject to copyright in the US. | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Core::Definition::Conditional;
use strict;
use warnings;
use LaTeXML::Global;
use LaTeXML::Common::Object;
use LaTeXML::Common::Error;
use LaTeXML::Core::Token;
use LaTeXML::Core::Tokens;
use base qw(LaTeXML::Core::Definition::Expandable);
# Conditional control sequences; Expandable
# Expand enough to determine true/false, then maybe skip
# record a flag somewhere so that \else or \fi is recognized
# (otherwise, they should signal an error)
sub new {
my ($class, $cs, $parameters, $test, %traits) = @_;
my $source = $STATE->getStomach->getGullet->getMouth;
return bless { cs => $cs, parameters => $parameters, test => $test,
locator => $source->getLocator,
isExpandable => 1,
%traits }, $class; }
sub getTest {
my ($self) = @_;
return $$self{test}; }
sub invoke {
my ($self, $gullet) = @_;
# A real conditional must have condition_type set
if (my $cond_type = $$self{conditional_type}) {
if (($cond_type eq 'if') || ($cond_type eq 'unless')) {
return $self->invoke_conditional($gullet); }
elsif ($cond_type eq 'else') {
return $self->invoke_else($gullet); }
elsif ($cond_type eq 'or') {
return $self->invoke_else($gullet); }
elsif ($cond_type eq 'fi') {
return $self->invoke_fi($gullet); } }
Error('unexpected', $$self{cs}, $gullet,
"Unknown conditional control sequence " . Stringify($LaTeXML::CURRENT_TOKEN));
return; }
sub invoke_conditional {
my ($self, $gullet) = @_;
# Keep a stack of the conditionals we are processing.
my $ifid = $STATE->lookupValue('if_count') || 0;
$STATE->assignValue(if_count => ++$ifid, 'global');
if ($LaTeXML::IF_LIMIT and $ifid > $LaTeXML::IF_LIMIT) {
Fatal('timeout', 'if_limit', $self,
"Conditional limit of $LaTeXML::IF_LIMIT exceeded, infinite loop?"); }
local $LaTeXML::IFFRAME = { token => $LaTeXML::CURRENT_TOKEN, start => $gullet->getLocator,
parsing => 1, elses => 0, ifid => $ifid };
$STATE->unshiftValue(if_stack => $LaTeXML::IFFRAME);
my $parms = $$self{parameters};
my @args = ($parms ? $parms->readArguments($gullet) : ());
$$LaTeXML::IFFRAME{parsing} = 0; # Now, we're done parsing the Test clause.
my $tracing = $STATE->lookupValue('TRACINGCOMMANDS') || $LaTeXML::DEBUG{tracing};
if ($tracing) {
Debug('{' . $self->tracingCSName . "} [#$ifid]");
Debug($self->tracingArgs(@args)) if @args; }
if (my $test = $self->getTest) {
my $result = &$test($gullet, @args);
if ($result) {
Debug("{true}") if $tracing; }
else {
my $to = skipConditionalBody($gullet, -1);
Debug("{false} [skipped to " . ToString($to) . "]") if $tracing; } }
# If there's no test, it must be the Special Case, \ifcase
else {
my $num = $args[0]->valueOf;
if ($num > 0) {
my $to = skipConditionalBody($gullet, $num);
Debug("{$num} [skipped to " . ToString($to) . "]") if $tracing; } }
return; }
#======================================================================
# Support for conditionals:
# Skipping for conditionals
# 0 : skip to \fi
# -1 : skip to \else, if any, or \fi
# n : skip to n-th \or, if any, or \else, if any, or \fi.
# NOTE that there are 2 kinds of "nested" ifs.
# \if's inside the body of either the true or false branch
# are easily skipped by tracking a level of if nesting and skipping over the
# same number of \fi as you find \if.
# \if's that get expanded while evaluating the test clause itself
# are considerably trickier. There's a frame on the if-stack for this \if
# that's above the one we're currently processing; typically the \else & \fi
# may still remain, but we need to either evaluate them a normal
# if we're continuing to follow the true branch, or skip oever them if
# we're trying to find the \else for the false branch.
# The danger is mistaking the \else that's associated with the test clause's \if
# and taking it for the \else that we're skipping to!
# Canonical example:
# \if\ifx AA XY junk \else blah \fi True \else False \fi
# The inner \ifx should expand to "XY junk", since A==A
# Return the token we've skipped to, and the frame that this applies to.
sub skipConditionalBody {
my ($gullet, $nskips) = @_;
my $level = 1;
my $n_ors = 0;
my $start = $gullet->getLocator;
# NOTE: Open-coded manipulation of if_stack!, Gullet and Token's
# [we're only reading tokens & looking up, so State shouldn't change behind our backs]
my $stack = $STATE->lookupValue('if_stack');
while (1) {
my ($t, $cond_type);
while ($t = shift(@{ $$gullet{pushback} }) || $$gullet{mouth}->readToken()) {
$t = $$t[2] if $$t[1] == CC_SMUGGLE_THE;
if ($LaTeXML::Core::State::CATCODE_ACTIVE_OR_CS[$$t[1]]
&& ($cond_type = $STATE->lookupConditional($t))) {
last; } }
last unless $cond_type;
if ($cond_type eq 'if') { # Found a \ifxx of some sort
$level++; }
elsif ($cond_type eq 'fi') { # Found a \fi
if ($$stack[0] ne $LaTeXML::IFFRAME) {
# But is it for a condition nested in the test clause?
shift(@$stack); } # then DO pop that conditional's frame; it's DONE!
elsif (!--$level) { # If no more nesting, we're done.
shift(@$stack); # Done with this frame
return $t; } } # AND Return the finishing token.
elsif ($level > 1) { } # Ignore \else,\or nested in the body.
elsif (($cond_type eq 'or') && (++$n_ors == $nskips)) {
return $t; }
elsif (($cond_type eq 'else') && $nskips
# Found \else and we're looking for one?
# Make sure this \else is NOT for a nested \if that is part of the test clause!
&& ($$stack[0] eq $LaTeXML::IFFRAME)) {
# No need to actually call elseHandler, but note that we've seen an \else!
$$stack[0]{elses} = 1;
return $t; } } # } #}
Error('expected', '\fi', $gullet, "Missing \\fi or \\else, conditional fell off end",
"Conditional started at " . ToString($start));
return; }
sub invoke_else {
my ($self, $gullet) = @_;
my $stack = $STATE->lookupValue('if_stack');
if (!($stack && $$stack[0])) { # No if stack entry ?
Error('unexpected', $LaTeXML::CURRENT_TOKEN, $gullet,
"Didn't expect a " . Stringify($LaTeXML::CURRENT_TOKEN)
. " since we seem not to be in a conditional");
return; }
elsif ($$stack[0]{parsing}) { # Defer expanding the \else if we're still parsing the test
return Tokens(T_CS('\relax'), $LaTeXML::CURRENT_TOKEN); }
elsif ($$stack[0]{elses}) { # Already seen an \else's at this level?
Error('unexpected', $LaTeXML::CURRENT_TOKEN, $gullet,
"Extra " . Stringify($LaTeXML::CURRENT_TOKEN),
"already saw \\else for " . Stringify($$stack[0]{token}) . " [" . $$stack[0]{ifid} . "] at " . ToString($$stack[0]{start}));
return; }
else {
local $LaTeXML::IFFRAME = $$stack[0];
my $t = skipConditionalBody($gullet, 0);
Debug('{' . ToString($LaTeXML::CURRENT_TOKEN) . '}'
. " [for " . ToString($$LaTeXML::IFFRAME{token}) . " #" . $$LaTeXML::IFFRAME{ifid}
. " skipping to " . ToString($t) . "]")
if $STATE->lookupValue('TRACINGCOMMANDS') || $LaTeXML::DEBUG{tracing};
return; } }
sub invoke_fi {
my ($self, $gullet) = @_;
my $stack = $STATE->lookupValue('if_stack');
if (!($stack && $$stack[0])) { # No if stack entry ?
Error('unexpected', $LaTeXML::CURRENT_TOKEN, $gullet,
"Didn't expect a " . Stringify($LaTeXML::CURRENT_TOKEN)
. " since we seem not to be in a conditional");
return; }
elsif ($$stack[0]{parsing}) { # Defer expanding the \else if we're still parsing the test
return Tokens(T_CS('\relax'), $LaTeXML::CURRENT_TOKEN); }
else { # "expand" by removing the stack entry for this level
local $LaTeXML::IFFRAME = $$stack[0];
$STATE->shiftValue('if_stack'); # Done with this frame
Debug('{' . ToString($LaTeXML::CURRENT_TOKEN) . '}'
. " [for " . Stringify($$LaTeXML::IFFRAME{token}) . " #" . $$LaTeXML::IFFRAME{ifid} . "]")
if $STATE->lookupValue('TRACINGCOMMANDS') || $LaTeXML::DEBUG{tracing};
return; } }
sub equals {
my ($self, $other) = @_;
return (defined $other && (ref $self) eq (ref $other))
&& Equals($self->getParameters, $other->getParameters)
&& Equals($self->getTest, $other->getTest); }
#===============================================================================
1;
__END__
=pod
=head1 NAME
C<LaTeXML::Core::Definition::Conditional> - Conditionals Control sequence definitions.
=head1 DESCRIPTION
These represent the control sequences for conditionals, as well as
C<\else>, C<\or> and C<\fi>.
See L<LaTeXML::Package> for the most convenient means to create them.
It extends L<LaTeXML::Core::Definition::Expandable>.
=head1 AUTHOR
Bruce Miller <bruce.miller@nist.gov>
=head1 COPYRIGHT
Public domain software, produced as part of work done by the
United States Government & not subject to copyright in the US.
=cut
|