File: Conditional.pm

package info (click to toggle)
latexml 0.8.7-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 29,128 kB
  • sloc: xml: 98,982; perl: 29,706; sh: 179; javascript: 28; makefile: 15
file content (227 lines) | stat: -rw-r--r-- 9,884 bytes parent folder | download
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