1# /=====================================================================\ #
2# |  LaTeXML::Core::Parameter                                           | #
3# | Representation of a single Parameter for Control Sequences          | #
4# |=====================================================================| #
5# | Part of LaTeXML:                                                    | #
6# |  Public domain software, produced as part of work done by the       | #
7# |  United States Government & not subject to copyright in the US.     | #
8# |---------------------------------------------------------------------| #
9# | Bruce Miller <bruce.miller@nist.gov>                        #_#     | #
10# | http://dlmf.nist.gov/LaTeXML/                              (o o)    | #
11# \=========================================================ooo==U==ooo=/ #
12
13package LaTeXML::Core::Parameter;
14use strict;
15use warnings;
16use LaTeXML::Global;
17use LaTeXML::Common::Object;
18use LaTeXML::Common::Error;
19use LaTeXML::Core::Token;
20use LaTeXML::Core::Tokens;
21use base qw(LaTeXML::Common::Object);
22
23# sub new {
24#   my ($class, $spec, %options) = @_;
25#   return bless { spec => $spec, %options }, $class; }
26
27# Create a parameter reading object for a specific type.
28# If either a declared entry or a function Read<Type> accessible from LaTeXML::Package::Pool
29# is defined.
30sub new {
31  my ($class, $type, $spec, %options) = @_;
32  my $descriptor = $STATE->lookupMapping('PARAMETER_TYPES', $type);
33  if (!defined $descriptor) {
34    if ($type =~ /^Optional(.+)$/) {
35      my $basetype = $1;
36      if    ($descriptor = $STATE->lookupMapping('PARAMETER_TYPES', $basetype)) { }
37      elsif (my $reader = checkReaderFunction("Read$type") || checkReaderFunction("Read$basetype")) {
38        $descriptor = { reader => $reader }; }
39      $descriptor = { %$descriptor, optional => 1 } if $descriptor; }
40    elsif ($type =~ /^Skip(.+)$/) {
41      my $basetype = $1;
42      if    ($descriptor = $STATE->lookupMapping('PARAMETER_TYPES', $basetype)) { }
43      elsif (my $reader = checkReaderFunction($type) || checkReaderFunction("Read$basetype")) {
44        $descriptor = { reader => $reader }; }
45      $descriptor = { %$descriptor, novalue => 1, optional => 1 } if $descriptor; }
46    else {
47      my $reader = checkReaderFunction("Read$type");
48      $descriptor = { reader => $reader } if $reader; } }
49  Fatal('misdefined', $type || 'no_type', undef, "Unrecognized parameter type in \"$spec\"") unless $descriptor;
50  # Convert semiverbatim to list of extra SPECIALS.
51  my %data = (%{$descriptor}, %options);
52  $data{semiverbatim} = [] if $data{semiverbatim} && (ref $data{semiverbatim} ne 'ARRAY');
53  return bless { spec => $spec, type => $type, %data }, $class; }
54
55# Check whether a reader function is accessible within LaTeXML::Package::Pool
56sub checkReaderFunction {
57  my ($function) = @_;
58  if (defined $LaTeXML::Package::Pool::{$function}) {
59    local *reader = $LaTeXML::Package::Pool::{$function};
60    if (defined &reader) {
61      return \&reader; } } }
62
63sub stringify {
64  my ($self) = @_;
65  return $$self{spec}; }
66
67sub setupCatcodes {
68  my ($self) = @_;
69  if ($$self{semiverbatim}) {
70    $STATE->beginSemiverbatim(@{ $$self{semiverbatim} }); }
71  return; }
72
73sub revertCatcodes {
74  my ($self) = @_;
75  if ($$self{semiverbatim}) {
76    $STATE->endSemiverbatim(); }
77  return; }
78
79sub read {
80  my ($self, $gullet, $fordefn) = @_;
81  # For semiverbatim, I had messed with catcodes, but there are cases
82  # (eg. \caption(...\label{badchars}}) where you really need to
83  # cleanup after the fact!
84  # Hmmm, seem to still need it...
85  if ($$self{semiverbatim}) {    # Open coded setupCatcodes
86    $STATE->beginSemiverbatim(@{ $$self{semiverbatim} }); }
87
88  my $value = &{ $$self{reader} }($gullet, @{ $$self{extra} || [] });
89  $value = $value->neutralize(@{ $$self{semiverbatim} }) if $$self{semiverbatim} && (ref $value)
90    && $value->can('neutralize');
91  $value = $value->packParameters if $value && $$self{packParameters};
92  if ($$self{semiverbatim}) {    # Open coded revertCatcodes
93    $STATE->endSemiverbatim(); }
94  if ((!defined $value) && !$$self{optional}) {
95    Error('expected', $self, $gullet,
96      "Missing argument " . Stringify($self) . " for " . Stringify($fordefn),
97      "Ended at " . ToString($gullet->getLocator));
98    $value = T_OTHER('missing'); }
99  return $value; }
100
101# This is needed by structured parameter types like KeyVals
102# where the argument may already have been tokenized before the KeyVals
103# (and the parameter types for the keys) had a chance to properly parse.
104# Yuck!
105sub reparse {
106  my ($self, $gullet, $tokens) = @_;
107  # Needs neutralization, since the keyvals may have been tokenized already???
108  # perhaps a better test would involve whether $tokens is, in fact, Tokens?
109  $tokens = $tokens->packParameters if $tokens && $$self{packParameters};
110  if (($$self{type} eq 'Plain') || $$self{undigested}) {    # Gack!
111    return $tokens; }
112  elsif ($$self{semiverbatim}) {                            # Needs neutralization
113    return $tokens->neutralize(@{ $$self{semiverbatim} }); }    # but maybe specific to catcodes
114  else {
115    return $gullet->readingFromMouth(LaTeXML::Core::Mouth->new(), sub {    # start with empty mouth
116        my ($gulletx) = @_;
117        my @tokens = $tokens->unlist;
118        if (@tokens    # Strip outer braces from dimensions & friends
119          && ($$self{type} =~ /^(?:Number|Dimension|Glue|MuDimension|MuGlue)$/)
120          && $tokens[0]->equals(T_BEGIN) && $tokens[-1]->equals(T_END)) {
121          shift(@tokens); pop(@tokens); }
122        $gulletx->unread(@tokens);    # but put back tokens to be read
123        my $value = $self->read($gulletx);
124        $gulletx->skipSpaces;
125        return $value; }); } }
126
127sub digest {
128  my ($self, $stomach, $value, $fordefn) = @_;
129  # If semiverbatim, Expand (before digest), so tokens can be neutralized; BLECH!!!!
130  if ($$self{semiverbatim}) {
131    $STATE->beginSemiverbatim(@{ $$self{semiverbatim} });
132    if ((ref $value eq 'LaTeXML::Core::Token') || (ref $value eq 'LaTeXML::Core::Tokens')) {
133      $stomach->getGullet->readingFromMouth(LaTeXML::Core::Mouth->new(), sub {
134          my ($igullet) = @_;
135          $igullet->unread($value);
136          my @tokens = ();
137          while (defined(my $token = $igullet->readXToken(1, 1))) {
138            push(@tokens, $token); }
139          $value = Tokens(@tokens);
140          $value = $value->neutralize; }); } }
141  if (my $pre = $$self{beforeDigest}) {    # Done for effect only.
142    &$pre($stomach); }                     # maybe pass extras?
143  $value = $value->beDigested($stomach) if (ref $value) && !$$self{undigested};
144  if (my $post = $$self{afterDigest}) {    # Done for effect only.
145    &$post($stomach); }                    # maybe pass extras?
146  $STATE->endSemiverbatim() if $$self{semiverbatim};    # Corner case?
147  return $value; }
148
149sub revert {
150  my ($self, $value) = @_;
151  if (my $reverter = $$self{reversion}) {
152    return &$reverter($value, @{ $$self{extra} || [] }); }
153  else {
154    return Revert($value); } }
155
156#======================================================================
1571;
158
159__END__
160
161=pod
162
163=head1 NAME
164
165C<LaTeXML::Core::Parameter> - a formal parameter
166
167=head1 DESCRIPTION
168
169Provides a representation for a single formal parameter of L<LaTeXML::Core::Definition>s:
170It extends L<LaTeXML::Common::Object>.
171
172=head1 SEE ALSO
173
174L<LaTeXML::Core::Parameters>.
175
176=head1 AUTHOR
177
178Bruce Miller <bruce.miller@nist.gov>
179
180=head1 COPYRIGHT
181
182Public domain software, produced as part of work done by the
183United States Government & not subject to copyright in the US.
184
185=cut
186