# -*- mode: Perl -*-
# /=====================================================================\ #
# |  pgfmath.code.tex                                                   | #
# | Implementation for LaTeXML                                          | #
# |---------------------------------------------------------------------| #
# | 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::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
use LaTeXML::Util::Geometry;
use List::Util qw(min max);

# Math functions needed here
# Native: sin cos atan2 log exp sqrt rand abs
use Math::Trig qw(
  deg2rad rad2deg
  tan atan asin acos
  cot sec cosec
  cosh sinh tanh);
use POSIX qw(floor ceil);
# round from Geometry
# Defined below: factorial

#======================================================================
# Load pgf's TeX code for math, first
InputDefinitions('pgfmath.code', type => 'tex', noltxml => 1)
  || Warn(":missing:pgfmath.code.tex Couldn't find pgfmath.code.tex");

#======================================================================
# Then redefine math operations to be done directly in Perl.
# Using pgflibraryluamath.code.tex as a guide for what needs doing.
#======================================================================
# Note that these macros typically get a CS passed as argument whose expansion is the number
# and that they assign the result, as a token list to \pgfmathresult.
# Hopefully the savings in doing the math in Perl isn't overwhelmed by string conversion?
our $PI    = Math::Trig::pi;
our $LOG2  = log(2);
our $LOG10 = log(10);
our $E     = exp(1);

# Note: We need to lookup /pgf/trig format/deg/ or /rad/ !!!  (default is deg?)
sub pgfmathargradians {
  my ($arg) = @_;
  if ($arg =~ s/\s*r$//) {
    return $arg; }
  elsif ($arg =~ s/\s*d$//) {    # ? is this also valid?
    return deg2rad($arg); }
  else {
    return deg2rad($arg); } }

sub pgfmathfactorial {
  my ($arg) = @_;
  my $f = 1;
  while ($arg > 0) { $f *= $arg; $arg--; }
  return $f; }

# I'll bet the deired precision is a parameter somewhere?
# Actually, the library uses exponents, but I generate them, for some reason I'm getting
# "Could not parse \dddd e-06e0', ie, there's an EXTRA e0 added to my number
# Maybe w/exponential, it' expecting "TeX FPU format"???;
# there's some alternative formats with a FLAGS prefix
#   <flag>Y<mantissa>e<exponent>] !!!
# with flag for signs, nans, etc.
# For the moment: fixed precision!
sub pgfmathresult {
  my ($value) = @_;
  $value = sprintf("%06f", $value);
  #print STDERR "ASSIGNING result as $value\n";
  #  my @v = ExplodeText($value);
  #print STDERR " that is ".join(' ',map { Stringify($_) } @v)."\n";
  DefMacroI(T_CS('\pgfmathresult'), undef, Tokens(ExplodeText($value)));
  return; }

DefMacro('\@@@show@mathresult{}', sub {
    my ($gulet, $result) = @_;
    $result = ToString(Expand($result));
    print STDERR "RESULT $result\n";
    return; });

#======================================================================

DefMacro('\@@@show@pgfmatharg@{}', sub {
    my ($gullet, $arg) = @_;
    $arg = ToString(Expand($arg));
    print STDERR "MATH ARG $arg\n";
    return; });

# A variant on Expanded that omits the outer {}
DefParameterType('pgfNumber', sub {
    my ($gullet) = @_;
    #    my $token    = $gullet->readXToken(0);
    my $token;
    do { $token = $gullet->readXToken(0);
    } while (defined $token && $$token[1] == CC_SPACE);    # Inline ->getCatcode!

    if ($token->getCatcode == CC_BEGIN) {
      my @tokens = ();
      my $result = '';
      my $level  = 1;
      while ($token = $gullet->readXToken(0)) {
        my $cc = $$token[1];
        if ($cc == CC_END) {
          $level--;
          last unless $level; }
        elsif ($cc == CC_BEGIN) {
          $level++; }
        #        push(@tokens, $token); }
        #      return Tokens(@tokens); }
        #        $result .= $token->getString; }
        $result .= $$token[0]; }
      return $result; }
    else {
##      return Tokens($token); } });
      return $token->getString; } });

# This one expects {{number}{number}....} and returns an array of them
DefParameterType('pgfNumbers', sub {
    my ($gullet) = @_;
    my $token;
    do { $token = $gullet->readXToken(0);
    } while (defined $token && $$token[1] == CC_SPACE);    # Inline ->getCatcode!

    if ($token->getCatcode == CC_BEGIN) {
      my @results = ();
      my $result  = '';
      my $level   = 1;
      while ($token = $gullet->readXToken(0)) {
        my $cc   = $$token[1];
        my $char = $$token[0];
        if ($cc == CC_END) {
          $level--;
          last unless $level;
          if ($level == 1) {                               # Next number
            push(@results, $result); $result = ''; $char = ''; } }
        elsif ($cc == CC_BEGIN) {
          if ($level == 1) { $char = ''; }
          $level++; }
        $result .= $char; }
      return [@results]; }
    else {
##      return Tokens($token); } });
      return [$token->getString]; } });

DefMacro('\pgfmathpi@', sub {
    pgfmathresult($PI); return; });
DefMacro('\pgfmathe@', sub {
    pgfmathresult($E); return; });
DefMacro('\pgfmathadd@ pgfNumber pgfNumber', sub {
    pgfmathresult($_[1] + $_[2]); return });
DefMacro('\pgfmathsubtract@ pgfNumber pgfNumber', sub {
    pgfmathresult($_[1] - $_[2]); return });
DefMacro('\pgfmathneg@ pgfNumber', sub {
    pgfmathresult(-$_[1]); return });
DefMacro('\pgfmathmultiply@ pgfNumber pgfNumber', sub {
    pgfmathresult($_[1] * $_[2]); return });
DefMacro('\pgfmathdivide@ pgfNumber pgfNumber', sub {
    pgfmathresult($_[1] / $_[2]); return });
DefMacro('\pgfmathpow@ pgfNumber pgfNumber', sub {
    pgfmathresult($_[1]**$_[2]); return });
DefMacro('\pgfmathabs@ pgfNumber', sub {
    pgfmathresult(abs($_[1])); return });
DefMacro('\pgfmathround@ pgfNumber', sub {
    pgfmathresult(round($_[1])); return });
DefMacro('\pgfmathfloor@ pgfNumber', sub {
    pgfmathresult(floor($_[1])); return });
DefMacro('\pgfmathceil@ pgfNumber', sub {
    pgfmathresult(ceil($_[1])); return });
#DefMacro('\pgfmathgcd@ pgfNumber pgfNumber', sub {

# DefMacro('\pgfmathisprime@ pgfNumber pgfNumber', sub {
# Seems these accept comma separated values?
# Or is it {{num}{num}...} ????
DefMacro('\pgfmathmax@ pgfNumbers', sub {
    my ($gullet, $args) = @_;
    #    my @args = split(/,/, $args);
    my @args = @$args;
    return pgfmathresult(max(@args)); });
DefMacro('\pgfmathmin@ pgfNumbers', sub {
    my ($gullet, $args) = @_;
    #    my @args = split(/,/, $args);
    my @args = @$args;
    return pgfmathresult(min(@args)); });
DefMacro('\pgfmathsin@ pgfNumber', sub {
    pgfmathresult(sin(pgfmathargradians($_[1]))); return });
DefMacro('\pgfmathcos@ pgfNumber', sub {
    pgfmathresult(cos(pgfmathargradians($_[1]))); return });
DefMacro('\pgfmathtan@ pgfNumber', sub {
    pgfmathresult(tan(pgfmathargradians($_[1]))); return });
# One mod is truncated (can be neg) other is floored, the latter should be capitalized?
# Apparently mod towards 0
sub pgfmath_mod_trunc {
  my ($arg1, $arg2) = @_;
  return ($arg1 / $arg2 < 0
    ? -(abs($arg1) % abs($arg2))
    : abs($arg1) % abs($arg2)); }

sub pgfmath_mod_floor {
  my ($arg1, $arg2) = @_;
  return ($arg1 / $arg2 < 0
    ? -(abs($arg1) % abs($arg2)) + abs($arg2)
    : abs($arg1) % abs($arg2)); }
DefMacro('\pgfmathmod@ pgfNumber pgfNumber', sub {
    my ($gullet, $arg1, $arg2) = @_;
    return pgfmathresult(pgfmath_mod_trunc($arg1, $arg2)); });
# Apparently mod twoards - infty
# (but lua version is incorrect if x > 0, y < 0)
DefMacro('\pgfmathmod@ pgfNumber pgfNumber', sub {
    my ($gullet, $arg1, $arg2) = @_;
    return pgfmathresult(pgfmath_mod_floor($arg1, $arg2)); });
DefMacro('\pgfmathrad@ pgfNumber', sub {
    pgfmathresult(deg2rad($_[1])); return });
DefMacro('\pgfmathdeg@ pgfNumber', sub {
    pgfmathresult(rad2deg($_[1])); return });
DefMacro('\pgfmathatan@ pgfNumber', sub {
    pgfmathresult(atan($_[1])); return });
DefMacro('\pgfmathatantwo@ pgfNumber', sub {
    pgfmathresult(atan2($_[1], $_[2])); return });
DefMacro('\pgfmathasin@ pgfNumber', sub {
    pgfmathresult(asin($_[1])); return });
DefMacro('\pgfmathacos@ pgfNumber', sub {
    pgfmathresult(acos($_[1])); return });
DefMacro('\pgfmathcot@ pgfNumber', sub {
    pgfmathresult(cot(pgfmathargradians($_[1]))); return });
DefMacro('\pgfmathsec@ pgfNumber', sub {
    pgfmathresult(sec(pgfmathargradians($_[1]))); return });
DefMacro('\pgfmathcosec@ pgfNumber', sub {
    pgfmathresult(cosec(pgfmathargradians($_[1]))); return });
DefMacro('\pgfmathexp@ pgfNumber', sub {
    pgfmathresult(exp($_[1])); return });
DefMacro('\pgfmathln@ pgfNumber', sub {
    pgfmathresult(log($_[1])); return });
DefMacro('\pgfmathlogten@ pgfNumber', sub {
    pgfmathresult(log($_[1]) / $LOG10); return });
DefMacro('\pgfmathsqrt@ pgfNumber', sub {
    pgfmathresult(sqrt($_[1])); return });
DefMacro('\pgfmathrnd@', sub {
    pgfmathresult(rand()); return });
DefMacro('\pgfmathrand@', sub {
    pgfmathresult(1 + rand(2)); return });
DefMacro('\pgfmathfactorial@', sub {
    pgfmathresult(pgfmathfactorial($_[1])); return });
DefMacro('\pgfmathreciprocal@ pgfNumber', sub {
    pgfmathresult(1 / $_[1]); return });

#======================================================================
DefMacro('\@@@test@mathresult{}{}{}', sub {
    my ($gullet, $input, $pgfresult, $lxresult) = @_;
    $input     = ToString($input);
    $lxresult  = ToString(Expand($lxresult));
    $pgfresult = ToString(Expand($pgfresult));
    # Try to figure out if the results are "Close Enough"
    # pgf seems to keep things as integer, when they've got no decimal,
    # but perl doesn't distinguish, and typically prints 0.0 as 0 and such
    my $d;
    if (($lxresult ne $pgfresult)
      && (($d = abs($lxresult - $pgfresult)) != 0.0)
      && ($d > 0.05 * max(abs($lxresult), abs($pgfresult)))) {
      Warn('mismatch', 'pgfparse', $gullet,
        "Parse of '$input'",
        "PGF: '$pgfresult'",
        "LTX: '$lxresult'"); }
    else {
      print STDERR "PGFParse OK '$input' => '$pgfresult' or '$lxresult'\n"; }
    return; });

our $PGFMATHGrammarSpec;
our $PGFMATHGrammar;
our $PGFMathFunctions;

# NOTE: haven't done \pgfmathpostparse
# NOTE: need to handle \ifpgfmathunitsdeclared
# Version issue?
RawTeX(<<'EoTeX');
\@ifundefined{pgfmathunitsdeclaredtrue}{\newif\ifpgfmathunitsdeclared}{}
\@ifundefined{pgfmathmathunitsdeclaredtrue}{\newif\ifpgfmathmathunitsdeclared}{}
EoTeX

sub pgfmathparse {
  my ($gullet, $tokens) = @_;
  SetCondition(T_CS('\ifpgfmathunitsdeclared'),     0, 'global');
  SetCondition(T_CS('\ifpgfmathmathunitsdeclared'), 0, 'global');
  my $string = (ref $tokens ? ToString(stripBraces(Expand($tokens))) : $tokens);
  $string =~ s/^\s+//; $string =~ s/\s+$//;
  my $input = $string;
###    if ($string =~/^+/){        # Don't parse as expression, just as glue. ???
  #    print STDERR "\nPGF Math Parse: $string\n";
  $PGFMATHGrammar = Parse::RecDescent->new($PGFMATHGrammarSpec) unless $PGFMATHGrammar;
  my $result = $PGFMATHGrammar->expr(\$string) || 0.0;
  #  $result = "0.0" if $result eq "0";
  #    print STDERR "  GOT " . (defined $result ? $result : '<fail>') . "\n";
  if ($string) {
    Error('pgfparse', 'pgfparse', $gullet,
      "Parse of '$input' failed",
      "LTX: '$result'",
      "Left: $string"); }
  #     print STDERR "  REMAINDER: $string\n" if $string;
  # There seem to be some pesky expectations for the results
  if ($result == 0.0) {
    $result = "0"; }
  else {    # We don't want scientific notation output!!!
    $result = sprintf("%f", $result); }
####  print STDERR "PGFPARSE: '$input' => '$result'\n";
  return $result; }

DefMacro('\lx@pgfmath@parseX{}', sub {
    my ($gullet, $tokens) = @_;
    DefMacroI('\lx@pgfmathresult', undef, Tokens(Explode(pgfmathparse($gullet, $tokens))));
    return; });

DefMacro('\lx@pgfmath@parse{}', sub {
    my ($gullet, $tokens) = @_;
    DefMacroI('\pgfmathresult', undef, Tokens(Explode(pgfmathparse($gullet, $tokens))));
    return; });

DefPrimitive('\pgfmathsetlength DefToken {}', sub {
    my ($stomach, $register, $tokens) = @_;
    my $gullet = $stomach->getGullet;
    my $length;
    my @tokens = $tokens->unlist;
    while (@tokens && ($tokens[0]->equals(T_SPACE))) {
      shift(@tokens); }
    if (@tokens && ($tokens[0]->equals(T_OTHER('+')))) {
      # pgf does this, but probably only size is relevant to LaTeXML's sloppy sizing?
      # \begingroup \pgfmath@selectfont \endgroup !!!
      $gullet->unread(@tokens);
      $length = $gullet->readGlue; }
    else {
      $length = pgfmathparse($gullet, $tokens);
      if (IfCondition(T_CS('\ifpgfmathmathunitsdeclared'))) {
        $length = MuDimension($length * $STATE->convertUnit('mu')); }
      else {
        $length = Dimension($length * 65536); } }
    AssignRegister($register, $length); });

Let('\@orig@pgfmathparse', '\pgfmathparse');
### This seems to indicate that \pgfmathparse is called quite a bit....

DefRegister('\lx@save@tracingmacros'   => Number(0));
DefRegister('\lx@save@tracingcommands' => Number(0));
DefMacro('\lx@test@pgfmath@parse{}',
  '\lx@pgfmath@parseX{#1}'
    . '\lx@save@tracingmacros=\tracingmacros\relax\tracingmacros=0\relax'
    . '\lx@save@tracingcommands=\tracingcommands\relax\tracingcommands=0\relax'
    . '\@orig@pgfmathparse{#1}'
    . '\tracingmacros=\lx@save@tracingmacros\relax'
    . '\tracingcommands=\lx@save@tracingcommands\relax'
    . '\@@@test@mathresult{#1}{\pgfmathresult}{\lx@pgfmathresult}');

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Leave BOTH of the following commented out, to use pgfmath's own parser.
# Use this to use our version of the pgfmath parser
Let('\pgfmathparse', '\lx@pgfmath@parse');
# Use this to run both and compare the results.
#Let('\pgfmathparse', '\lx@test@pgfmath@parse');
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

sub pgfmath_apply {
  my ($op, @args) = @_;
  if (my $fcn = $$PGFMathFunctions{$op}) {
    return &$fcn(@args); }
  else {
    Error('unexpected', $op, undef, "Unimplemented pgfmath operator '$op'");
    return 0; } }

sub pgfmath_leftrecapply {
  my (@stuff) = @_;
  my $result = shift(@stuff);
  while (@stuff) {
    my $op  = shift(@stuff);
    my $arg = shift(@stuff);
    if (my $fcn = $$PGFMathFunctions{$op}) {
      $result = &$fcn($result, $arg); }
    else {
      Error('unexpected', $op, undef, "Unimplemented pgfmath operator '$op'");
      return 0; } }
  return $result; }

# NOTE: Do NOT use ->ptValue here, since it rounds to 2 decimals
# (which is sensible for the ultimate output, but wreaks havoc w/ accuracy here!)
sub pgfmath_convert {
  my ($number, $unit) = @_;
  SetCondition(T_CS('\ifpgfmathunitsdeclared'), 1, 'global');                        # Saw units!
  SetCondition(T_CS('\ifpgfmathmathunitsdeclared'), 1, 'global') if $unit eq 'mu';
  return $number * $STATE->convertUnit($unit) / 65536; }    # return value in pts!

sub pgfmath_register {
  my ($cs) = @_;
  my $reg = LookupRegister($cs);
  SetCondition(T_CS('\ifpgfmathunitsdeclared'), 1, 'global');
  if (!$reg) {
    return 0.0; }
  return (ref $reg eq 'LaTeXML::Common::Number' ? $reg->valueOf : $reg->valueOf / 65536); }

sub pgfmath_setunitsdeclared {
  SetCondition(T_CS('\ifpgfmathunitsdeclared'), 1, 'global');
  return; }

sub pgfmath_getwidth {
  my ($cs) = @_;
  # Or could be an explicit Number?
  my $reg   = $cs  && LookupRegister($cs);
  my $box   = $reg && 'box' . $reg->valueOf;
  my $stuff = $box && LookupValue($box);
  return ($stuff ? $stuff->getWidth->valueOf / 65536 : Dimension(0)); }

sub pgfmath_getheight {
  my ($cs) = @_;
  # Or could be an explicit Number?
  my $reg   = $cs  && LookupRegister($cs);
  my $box   = $reg && 'box' . $reg->valueOf;
  my $stuff = $box && LookupValue($box);
  return ($stuff ? $stuff->getHeight->valueOf / 65536 : Dimension(0)); }

sub pgfmath_getdepth {
  my ($cs) = @_;
  # Or could be an explicit Number?
  my $reg   = $cs  && LookupRegister($cs);
  my $box   = $reg && 'box' . $reg->valueOf;
  my $stuff = $box && LookupValue($box);
  return ($stuff ? $stuff->getDepth->valueOf / 65536 : Dimension(0)); }

BEGIN {
  $PGFMathFunctions = {
    '=='       => sub { $_[0] == $_[1]; },
    equal      => sub { $_[0] == $_[1]; },
    '>'        => sub { $_[0] > $_[1]; },
    greater    => sub { $_[0] > $_[1]; },
    '<'        => sub { $_[0] < $_[1]; },
    less       => sub { $_[0] < $_[1]; },
    '!='       => sub { $_[0] != $_[1]; },
    notequal   => sub { $_[0] != $_[1]; },
    '>='       => sub { $_[0] >= $_[1]; },
    notless    => sub { $_[0] >= $_[1]; },
    '<='       => sub { $_[0] <= $_[1]; },
    notgreater => sub { $_[0] <= $_[1]; },
    '&&'       => sub { $_[0] && $_[1]; },
    'and'      => sub { $_[0] && $_[1]; },
    '||'       => sub { $_[0] || $_[1]; },
    or         => sub { $_[0] || $_[1]; },
    '+'        => sub { (defined $_[1] ? $_[0] + $_[1] : $_[0]); },
    'add'      => sub { (defined $_[1] ? $_[0] + $_[1] : $_[0]); },
    '-'        => sub { (defined $_[1] ? $_[0] - $_[1] : -$_[0]); },    # prefix or infix
    neg        => sub { -$_[0]; },
    '*'        => sub { $_[0] * $_[1]; },
    multiply   => sub { $_[0] * $_[1]; },
    '/'        => sub { $_[0] / $_[1]; },
    div        => sub { int($_[0] / $_[1]); },
    divide     => sub { $_[0] / $_[1]; },
    '!'        => sub { pgfmathfactorial($_[0]); },
    'r'        => sub { rad2deg($_[0]); },
    e          => sub { $E; },
    pi         => sub { $PI; },
    abs        => sub { abs($_[0]); },
    acos       => sub { acos($_[0]); },
    array      => sub { },
    asin       => sub { asin($_[0]); },
    atan       => sub { atan($_[0]); },
    atan2      => sub { atan2($_[0], $_[1]); },
    #    bin   => sub { },
    ceil  => sub { ceil($_[0]); },
    cos   => sub { cos(pgfmathargradians($_[0])); },
    cosec => sub { cosec(pgfmathargradians($_[0])); },
    cosh  => sub { cosh($_[0]); },
    cot   => sub { cot(pgfmathargradians($_[0])); },
    deg   => sub { rad2deg($_[0]); },
    #    depth => sub { },
    exp       => sub { exp($_[0]); },
    factorial => sub { pgfmathfactorial($_[0]); },
    false     => sub { 0; },
    floor     => sub { floor($_[0]); },
    #    frac       => sub { },
    #    gcd        => sub { },
    #    height     => sub { },
    hex => sub { sprintf("%x", $_[0]); },
    Hex => sub { sprintf("%X", $_[0]); },
    int => sub { int($_[0]); },
    ifthenelse => sub { ($_[0] ? $_[1] : $_[2]); },
    iseven     => sub { (int($_[0]) % 2) == 0 },
    isodd      => sub { (int($_[0]) % 2) == 1 },
    #    isprime    => sub { },
    ln    => sub { log($_[0]); },
    log10 => sub { log($_[0]) / $LOG10; },
    log2  => sub { log($_[0]) / $LOG2; },
    max   => sub { max(@_); },
    min   => sub { min(@_); },
    mod   => sub { pgfmath_mod_trunc($_[0], $_[1]); },
    Mod   => sub { pgfmath_mod_floor($_[0], $_[1]); },
    not   => sub { !$_[0]; },
    oct   => sub { sprintf("%o", $_[0]); },
    pow   => sub { $_[0]**$_[1]; },
    rad   => sub { deg2rad($_[0]); },
    #    rand     => sub { },
    #    random   => sub { },
    real => sub { $_[0] + 0.0; },
    #    rnd      => sub { },
    round    => sub { round($_[0]); },
    scalar   => sub { SetCondition(T_CS('\ifpgfmathunitsdeclared'), 0, 'global'); $_[0]; },
    sec      => sub { sec(pgfmathargradians($_[0])); },
    sign     => sub { ($_[0] > 0 ? 1 : ($_[0] < 0 ? -1 : 0)); },
    sin      => sub { sin(pgfmathargradians($_[0])); },
    sinh     => sub { sinh($_[0]); },
    sqrt     => sub { sqrt($_[0]); },
    subtract => sub { $_[0] - $_[1]; },
    tan      => sub { tan(pgfmathargradians($_[0])); },
    tanh     => sub { tanh($_[0]); },
    true     => sub { 1; },
    veclen   => sub { sqrt($_[0] * $_[0] + $_[1] * $_[1]); },
    #    width    => sub { },
  };

  $::RD_HINT = 1;

  # Why can't I manage to import a few functions to be visible to the grammar actions?
  # NOTE Not yet done: quoted strings, extensible functions
  $PGFMATHGrammarSpec = << 'EoGrammar';
#  {BEGIN { use LaTeXML::Package::Pool; }}
#  { use LaTeXML::Package::Pool; }
#  { LaTeXML::Package::Pool->import(qw(pgfmath_apply)); }
  formula :
    expr /\?/ expr /:/ expr { ($item[1] ? $item[3] : $item[5]); }
  | expr CMP expr           { LaTeXML::Package::Pool::pgfmath_apply($item[2], $item[1], $item[3]); }
  | expr

expr :
     term (ADDOP term { [$item[1],$item[2]]; })(s?)
          { LaTeXML::Package::Pool::pgfmath_leftrecapply($item[1],map(@$_,@{$item[2]})); }

  term :
     factor (MULOP factor { [$item[1],$item[2]]; })(s?)
          { LaTeXML::Package::Pool::pgfmath_leftrecapply($item[1],map(@$_,@{$item[2]})); }

   # addPostfix[$base] ; adds any following sub/super scripts to $base.
   addPostfix :
          /^\Z/ { $arg[0];}   # short circuit!
        | POSTFIX          addPostfix[LaTeXML::Package::Pool::pgfmath_apply($item[1],$arg[0])]
        | { $arg[0]; }
  factor : simplefactor /\^/ simplefactor { $item[1] ** $item[3]; }
    | simplefactor addPostfix[$item[1]]

  simplefactor :
      /\(/ formula /\)/     { $item[2]; }
    | PREFIX simplefactor   { LaTeXML::Package::Pool::pgfmath_apply($item[1],$item[2]); }
    | FUNCTION /\(/ formula (/,/ formula { $item[2]; })(s?) /\)/
           { LaTeXML::Package::Pool::pgfmath_apply($item[1], $item[3], @{$item[4]}); }
    | FUNCTION0                   { LaTeXML::Package::Pool::pgfmath_apply($item[1]); }
    | NUMBER UNIT          { LaTeXML::Package::Pool::pgfmath_convert($item[1],$item[2]); }
    | NUMBER REGISTER      { LaTeXML::Package::Pool::pgfmath_apply('*', $item[1], $item[2]); }
      # really count_register dimension_register!
    | REGISTER REGISTER    { LaTeXML::Package::Pool::pgfmath_apply('*', $item[1], $item[2]); }
    | NUMBER
    | REGISTER

    REGISTER :  # these need to set dimension flag!!!
      /\\wd/ CS            { LaTeXML::Package::Pool::pgfmath_setunitsdeclared();
                             LaTeXML::Package::Pool::pgfmath_getwidth($item[2]); }
    | /\\ht/ CS            { LaTeXML::Package::Pool::pgfmath_setunitsdeclared();
                             LaTeXML::Package::Pool::pgfmath_getheight($item[2]); }
    | /\\dp/ CS            { LaTeXML::Package::Pool::pgfmath_setunitsdeclared();
                             LaTeXML::Package::Pool::pgfmath_getdepth($item[2]); }
    | CS                   { LaTeXML::Package::Pool::pgfmath_register($item[1]); }

    CS : /\\[a-zA-Z@]*/

    # NOTE: Need to recognize octal, binary and hex!  AND scientific notation!
    NUMBER :
      /(?:\d+\.?\d*|\d*\.?\d+)(:?[eE][+-]?\d+)?/ { $item[1]+0.0; }
    | /0b[01]+/                  { oct($item[1]); } # !!!
    | /0x[0-9a-fA-F]+/           { hex($item[1]); }
    | /0[0-9]+/                  { oct($item[1]); }

    UNIT :
      /(?:ex|em|pt|pc|in|bp|cm|mm|dd|cc|sp)/

    FUNCTION0 : /(?:e|pi|false|rand|rnd|true)/
    FUNCTION : /(?:abs|acos|asin|atan|bin|ceil|cos|cosec|cosh|cot|deg|exp|factorial|floor|frac|hex|Hex|
int|iseven|isodd|isprime|ln|log10|log2|neg|not|oct|rad|real|round|sec|sign|sin|sinh|sqrt|tan|tanh|add|and|atan2|divide|div|equal|gcd|greater|less|max|min|mod|Mod|multiply|notequal|notgreater|notless|or|pow|random|subtract|ifthenelse)/

# ? array|veclen | scalar
# These take boxes!  depth|height|width

    CMP     : /==/ | /\>/ | /\</ | /!=/ | /\>=/ | /\<=/ | /&&/ | /||/
    PREFIX  : /\-/ | /!/ | /\+/
    POSTFIX : /!/ | /r/
    ADDOP   : /\+/ | /=/ | /\-/
    MULOP   : /\*/ | /\//

EoGrammar

}

#======================================================================
1;