# -*- 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;