diff --git a/MANIFEST b/MANIFEST index 75f864ffb7..7468ff73e3 100644 --- a/MANIFEST +++ b/MANIFEST @@ -831,6 +831,7 @@ t/55_theorem.t t/56_ams.t t/65_graphics.t t/66_pgf.tt +t/661_pgfmathparse.t t/67_tikz.tt t/70_parse.t t/80_complex.t @@ -1638,6 +1639,9 @@ t/pgf/stress_pgfmath.xml t/pgf/stress_pgfplots.pdf t/pgf/stress_pgfplots.tex t/pgf/stress_pgfplots.xml +t/pgfmathparse/pgfmathparse.pdf +t/pgfmathparse/pgfmathparse.tex +t/pgfmathparse/pgfmathparse.xml t/post/hyperref-post.xml t/post/hyperref.xml t/post/simplemath-post.xml diff --git a/lib/LaTeXML/Package/pgfmath.code.tex.ltxml b/lib/LaTeXML/Package/pgfmath.code.tex.ltxml index d820dc158c..95549095c3 100644 --- a/lib/LaTeXML/Package/pgfmath.code.tex.ltxml +++ b/lib/LaTeXML/Package/pgfmath.code.tex.ltxml @@ -15,7 +15,8 @@ use strict; use warnings; use LaTeXML::Package; use LaTeXML::Util::Geometry; -use List::Util qw(min max); +use List::Util qw(min max); +use Scalar::Util qw(looks_like_number); # NOTE: since *.code.tex is read with \input, the .ltxml may be loaded more than once. no warnings 'redefine'; @@ -87,7 +88,8 @@ sub pgfmathresult { if (!$presanitized && $value =~ /[.e]/) { $value = sprintf("%.5f", $value); $value =~ s/([^.])0+$/$1/; } - return Tokens(T_CS('\def'), T_CS('\pgfmathresult'), T_BEGIN, ExplodeText($value), T_END); } + # the result may contain TeX macros, retokenize them with the current cattable + return Tokens(T_CS('\def'), T_CS('\pgfmathresult'), T_BEGIN, LaTeXML::Core::Mouth->new($value)->readTokens, T_END); } DefMacro('\@@@show@mathresult{}', sub { my ($gulet, $result) = @_; @@ -324,7 +326,7 @@ sub pgfmathparse { Let('\widthof', '\pgfmath@calc@widthof'); Let('\heightof', '\pgfmath@calc@heightof'); Let('\depthof', '\pgfmath@calc@depthof'); - my $string = (ref $tokens ? ToString(Expand($tokens)->stripBraces) : $tokens); + my $string = (ref $tokens ? UnTeX(Expand($tokens)->stripBraces) : $tokens); $string =~ s/^\s+//; $string =~ s/\s+$//; $string =~ s/\s+/ /gs; my $input = $string; # simple number-like thing? return as-is without any changes. @@ -357,29 +359,33 @@ sub pgfmathparse { # note: trig functions may be tempting, but their precedence differs # e.g. perl's eval of 'cos 1260/5' is 0.7822121... , while PGF computes # \def\e{1260}\pgfmathparse{cos \e/5} to -0.2 - i.e. "cos" binds stronger than "/" + # also ^ associates to the left rather than the right && ($string !~ /\b(?:a?(sin|cos|tan2?)h?)|bin|add|array|tan|cot|sec|(?:co)?sec| deg|depth|dim|div|divide|(?:(?:not)?(?:equal|greater|less))|oct|pi|pow|multiply| - rand|scalar|sign|vec|width/x)) { { + rand|scalar|sign|vec|width|hex|frac|ifthenelse|\\|\^/x)) { { # special case! the ^ in tikz is used for power, but NOT so in perl. - $string =~ s/\^/**/g; + my $perlstring = $string =~ s/\^/**/gr; local $LaTeXML::IGNORE_ERRORS = 1; local $@; no warnings; - $result = eval $string; + $result = eval $perlstring; if (!$@) { # need to erase string when perl eval works, to keep it consistent with recdescent $string = ''; } } } + $result = $result || 0.0; if ($string && !$result) { $PGFMATHGrammar = Parse::RecDescent->new($PGFMATHGrammarSpec) unless $PGFMATHGrammar; - $result = $PGFMATHGrammar->expr(\$string); } - $result = $result || 0.0; + $result = $PGFMATHGrammar->formula(\$string); } $string =~ s/^[\)\]]+$//; # Forgive excess trailing ) !?!?!?!?! if ($string) { Error('pgfparse', 'pgfparse', $gullet, "Parse of '$input' failed", "LTX: '$result'", "Left: $string"); } + elsif (!looks_like_number($result)) { + # result is a string, leave untouched + return $result; } # NOT really correct, but would like to use an internal to distinguish 1.0 from int(1.0)!!! elsif ($result == int($result)) { $result = int($result); @@ -397,7 +403,8 @@ DefMacro('\lx@pgfmath@parseX{}', sub { return Tokens(T_CS('\def'), T_CS('\lx@pgfmathresult'), T_BEGIN, pgfmathparse($gullet, $tokens), T_END); }); -DefMacro('\lx@pgfmath@parse{}', sub { +# TODO argument should actually go through \protected@edef if that is defined (i.e. with the LaTeX engine) +DefMacro('\lx@pgfmath@parse ExpandedPartially', sub { my ($gullet, $tokens) = @_; return pgfmathresult(pgfmathparse($gullet, $tokens), 1); }); @@ -521,8 +528,6 @@ sub pgfmath_getdepth { sub pgfmath_sizer { my ($dimension, $rawtex) = @_; - $rawtex =~ s/^"//; - $rawtex =~ s/"$//; my $result; if (my $boxed = Digest($rawtex)) { if ($dimension eq 'height') { @@ -554,75 +559,81 @@ sub pgfmath_checkuserfunction { 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] / pgfmath_divisor($_[1]); }, - divide => sub { $_[0] / pgfmath_divisor($_[1]); }, - div => sub { int($_[0] / pgfmath_divisor($_[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 { rad2deg(asin($_[0])); }, - atan => sub { rad2deg(atan($_[0])); }, - atan2 => sub { rad2deg(atan2($_[0], $_[1])); }, - angle => sub { rad2deg(atan2($_[0], $_[1])); }, # Assume same? Where's documentation? - # 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]); }, + '!pref' => sub { int(!$_[0]); }, + not => sub { int(!$_[0]) }, + '==' => sub { int($_[0] == $_[1]) }, + equal => sub { int($_[0] == $_[1]) }, + '>' => sub { int($_[0] > $_[1]) }, + greater => sub { int($_[0] > $_[1]) }, + '<' => sub { int($_[0] < $_[1]) }, + less => sub { int($_[0] < $_[1]) }, + '!=' => sub { int($_[0] != $_[1]) }, + notequal => sub { int($_[0] != $_[1]) }, + '>=' => sub { int($_[0] >= $_[1]) }, + notless => sub { int($_[0] >= $_[1]) }, + '<=' => sub { int($_[0] <= $_[1]) }, + notgreater => sub { int($_[0] <= $_[1]) }, + '&&' => sub { int($_[0] && $_[1]) }, + 'and' => sub { int($_[0] && $_[1]) }, + '||' => sub { int($_[0] || $_[1]) }, + or => sub { int($_[0] || $_[1]) }, + iseven => sub { int((int($_[0]) % 2) == 0) }, + isodd => sub { int((int($_[0]) % 2) == 1) }, + # isprime => sub { }, + false => sub { 0; }, + true => sub { 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] / pgfmath_divisor($_[1]); }, + divide => sub { $_[0] / pgfmath_divisor($_[1]); }, + div => sub { int($_[0] / pgfmath_divisor($_[1])); }, + '^' => sub { $_[0]**$_[1]; }, + e => sub { $E; }, + pi => sub { $PI; }, + abs => sub { abs($_[0]); }, + acos => sub { acos($_[0]); }, + array => sub { }, + asin => sub { rad2deg(asin($_[0])); }, + atan => sub { rad2deg(atan($_[0])); }, + atan2 => sub { rad2deg(atan2($_[0], $_[1])); }, + angle => sub { rad2deg(atan2($_[0], $_[1])); }, # Assume same? Where's documentation? + bin => sub { sprintf("%b", $_[0]); }, + 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 { }, + frac => sub { $_[0] < 0 ? ceil($_[0]) - $_[0] : $_[0] - floor($_[0]); }, + gcd => sub { + # PGF computes the gcd of the underlying TeX integers! + my ($a, $b) = (abs($_[0]) * 65536, abs($_[1]) * 65536); + ($a, $b) = ($b, $a) if $b > $a; + while ($b > 0) { ($a, $b) = ($b, $a % $b); } + int($a / 65536); }, # height => sub { }, hex => sub { sprintf("%x", $_[0]); }, Hex => sub { sprintf("%X", $_[0]); }, - int => sub { int($_[0]); }, + int => sub { $_[0] < 0 ? ceil($_[0]) : floor($_[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]); }, + 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]); }, + oct => sub { sprintf("%o", $_[0]); }, + pow => sub { $_[0]**$_[1]; }, + rad => sub { deg2rad($_[0]); }, # rand => sub { }, # random => sub { }, real => sub { $_[0] + 0.0; }, @@ -637,7 +648,6 @@ BEGIN { 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 { }, # Additional functions from tikz-cd; these need to get parameters from the current math font! @@ -646,6 +656,17 @@ BEGIN { }; $::RD_HINT = 1; + # $::RD_TRACE = 1; + + # The PGF grammar is simple enough that can be parsed from left to right: + # once a part of the text has been parsed successfully, its meaning will not + # change. To ensure we do not waste energy on reparsing the same text, follow + # + # REQUIREMENT: alternations MUST be disambiguated by the first character + # + # In other words, the parser must never backtrack. This guarantees that + # successful matches and effectful computations such as 'rand' are not + # repeated multiple times. # 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 @@ -653,80 +674,123 @@ BEGIN { # {BEGIN { use LaTeXML::Package::Pool; }} # { use LaTeXML::Package::Pool; } # { LaTeXML::Package::Pool->import(qw(pgfmath_apply)); } - # braces ignored during parse... - 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 /(?:\)|^\Z)/ { $item[2]; } # Let unclosed () succeed at end? - | PREFIX simplefactor { LaTeXML::Package::Pool::pgfmath_apply($item[1],$item[2]); } - | SIZER /\(/ QTEX /\)/ { LaTeXML::Package::Pool::pgfmath_sizer($item[1], $item[3]); } - | FUNCTION /\(/ formula (/,/ formula { $item[2]; })(s?) /\)/ - { LaTeXML::Package::Pool::pgfmath_apply($item[1], $item[3], @{$item[4]}); } - | FUNCTION simplefactor - { LaTeXML::Package::Pool::pgfmath_apply($item[1], $item[2]); } - | 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]); } + + # braces ignored during parse... + +formula : predicate conditional[$item{predicate}](?) + { defined $item[2][0] ? $item[2][0] : $item{predicate}; } + +# conditional[$if] +# '?' precedence 100, ':' precendence 101 +conditional : '?' then ':' else + { $arg[0] ? $item{then} : $item{else}; } + then: formula + else: formula + +# BOOLOP precedence 200 +predicate : atomic (BOOLOP atomic { [$item{BOOLOP},$item{atomic}]; })(s?) + { LaTeXML::Package::Pool::pgfmath_leftrecapply($item{atomic}, map(@$_,@{$item[2]})); } + +# CMP precedence 250 +atomic : expr comparison[$item{expr}](?) + { defined $item[2][0] ? $item[2][0] : $item{expr}; } + +# comparison[$left] +comparison : CMP expr + { LaTeXML::Package::Pool::pgfmath_apply($item{CMP}, $arg[0], $item{expr}); } + +# ADDOP precedence 500 +expr : QTEX | term (ADDOP term { [$item{ADDOP},$item{term}]; })(s?) + { LaTeXML::Package::Pool::pgfmath_leftrecapply($item{term}, map(@$_,@{$item[2]})); } + +# MULOP precedence 700, 'r' precedence 600 +# the postfix 'r' is equivalent to multiplying by 180/pi, even accounting for predecence +term : factor ('r' { ['*',180/Math::Trig::pi]; } | MULOP factor { [$item{MULOP},$item{factor}]; })(s?) + { LaTeXML::Package::Pool::pgfmath_leftrecapply($item{factor},map(@$_,@{$item[2]})); } + +# pgfmathparser has a hardcoded notion of 'double character' operator +# '!' could be part of '!=', so we forbid that sequence +# '!' postfix precedence 800 +factor : power ('!' ...!'=')(s?) + { $return = $item{power}; + for my $i (@{$item[2]}) { $return = LaTeXML::Package::Pool::pgfmathfactorial($return); } } + +# '^' precedence 900 +power : simplefactor ('^' simplefactor { ['^',$item{simplefactor}]; })(s?) + { LaTeXML::Package::Pool::pgfmath_leftrecapply($item{simplefactor}, map(@$_,@{$item[2]})); } + +function : FUNCTION (arguments | simpleargument)[$item{FUNCTION}] { $item[2]; } + arguments : '(' formula (',' formula { $item[2]; })(s?) ')' + { LaTeXML::Package::Pool::pgfmath_apply($arg[0], $item{formula}, @{$item[3]}); } + # to respect the REQUIREMENT, forbid simplefactor starting with '(' + simpleargument : ...!'(' simplefactor + { LaTeXML::Package::Pool::pgfmath_apply($arg[0], $item{simplefactor}); } + +# '!' precedence 975, rest of PREFIX precedence infinity +# '(' precedence 5, ')' precedence 4 +simplefactor : + '(' formula /(?:\)|^\Z)/ { $item[2]; } # Let unclosed () succeed at end? + | PREFIX simplefactor { LaTeXML::Package::Pool::pgfmath_apply($item[1],$item[2]); } + | SIZER '(' QTEX ')' { LaTeXML::Package::Pool::pgfmath_sizer($item[1], $item[3]); } + | function + | FUNCTION0 { LaTeXML::Package::Pool::pgfmath_apply($item[1]); } + | number + | register + +number : NUMBER postNumber[$item[1]] + postNumber : + /^\Z/ { $arg[0]; } # short circuit! + | REGISTER { LaTeXML::Package::Pool::pgfmath_apply('*', $arg[0], $item[1]); } + | UNIT { LaTeXML::Package::Pool::pgfmath_convert($arg[0],$item[1]); } + | { $arg[0]; } + +register : REGISTER postRegister[$item[1]] + postRegister : + /^\Z/ { $arg[0]; } # short circuit! # 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]); } - | /\./ { 0.0; } # pgf treats a single dot as a zero - - UNIT : - /(?:ex|em|pt|pc|in|bp|cm|mm|dd|cc|sp)/ - - FUNCTION0 : /(?:e|pi|false|rand|rnd|true|axis_height|rule_thickness)/ - | /([a-zA-Z][a-zA-Z0-9]*)/ { LaTeXML::Package::Pool::pgfmath_checkuserconstant($item[1]); } - - FUNCTION : /(?:abs|acos|asin|atan2|atan|angle|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|divide|div|equal|gcd|greater|less|max|min|mod|Mod|multiply|notequal|notgreater|notless|or|pow|random|subtract|ifthenelse|veclen)/ - | /([a-zA-Z][a-zA-Z0-9]*)/ { LaTeXML::Package::Pool::pgfmath_checkuserfunction($item[1]); } - # ? array|scalar - # These take boxes! - SIZER : /(?:depth|height|width)/ - QTEX : /"[^"]*"/ - CMP : /==/ | /\>/ | /\=/ | /\<=/ | /&&/ | /||/ - PREFIX : /\-/ | /!/ | /\+/ - POSTFIX : /!/ | /r/ - ADDOP : /\+/ | /=/ | /\-/ - MULOP : /\*/ | /\// + | REGISTER { LaTeXML::Package::Pool::pgfmath_apply('*', $item[1], $item[2]); } + +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 : + /0(?:b|B)([01]+)/ { oct(lc($item[1])); } + | /0(?:x|X)[0-9a-fA-F]+/ { oct(lc($item[1])); } + | /0[0-7]+/ { oct($item[1]); } + | /(?:\d+\.?\d*|\d*\.?\d+)(:?[eE][+-]?\d+)?/ + { $item[1]+0.0; } + | '.' { 0.0; } # pgf treats a single dot as a zero + +UNIT : + /(?:ex|em|pt|pc|in|bp|cm|mm|dd|cc|sp)/ + +FUNCTION0 : /([a-zA-Z][a-zA-Z0-9]*)/ { LaTeXML::Package::Pool::pgfmath_checkuserconstant($item[1]); } + +FUNCTION : /([a-zA-Z][a-zA-Z0-9]*)/ { LaTeXML::Package::Pool::pgfmath_checkuserfunction($item[1]); } + +# ? array|scalar +# These take boxes! +SIZER : /(?:depth|height|width)/ +QTEX : '"' /[^"]*/ '"' { $item[2]; } +CMP : /==|>=?|<=?|!=/ +BOOLOP : /&&|\|\|/ + +# the meaning of ! depends on its position +# pgfmathparser has a hardcoded notion of 'double character' operator +# '!' could be part of '!=', so we forbid that sequence +PREFIX : /-|\+/ | '!' ...!'=' { '!pref'; } + +ADDOP : /\+|=|-/ +MULOP : /\*|\// EoGrammar diff --git a/t/661_pgfmathparse.t b/t/661_pgfmathparse.t new file mode 100644 index 0000000000..5e5db10680 --- /dev/null +++ b/t/661_pgfmathparse.t @@ -0,0 +1,10 @@ +# -*- CPERL -*- +#********************************************************************** +# Test cases for LaTeXML +#********************************************************************** +use LaTeXML::Util::Test; + +latexml_tests("t/pgfmathparse", + requires=>{ + pgfmathparse=>{ + packages=>'pgf.sty'} }); diff --git a/t/pgfmathparse/pgfmathparse.pdf b/t/pgfmathparse/pgfmathparse.pdf new file mode 100644 index 0000000000..fddbc3d816 Binary files /dev/null and b/t/pgfmathparse/pgfmathparse.pdf differ diff --git a/t/pgfmathparse/pgfmathparse.tex b/t/pgfmathparse/pgfmathparse.tex new file mode 100644 index 0000000000..2bf3bb9410 --- /dev/null +++ b/t/pgfmathparse/pgfmathparse.tex @@ -0,0 +1,188 @@ +\documentclass{article} +\usepackage{latexml} +\usepackage{pgf} +% some versions of PGF add an extra \clearpage at the end of the document, which breaks the test +\iflatexml\let\clearpage=\relax\fi +% ifthenelse will force LaTeXML to use the grammar instead of eval, because it is blacklisted +\makeatletter +\def\test{\begingroup\pgfmath@catcodes\@ifnextchar[\test@\test@@} +\def\test@[#1]#2{\texttt{#1 = \pgfmathprint{#2} = \pgfmathprint{ifthenelse(1,#2,"never")}}\endgroup\par} +\def\test@@#1{\test@[#1]{#1}} +\makeatother +\begin{document} + +\section{94.1.1 Commands} +\test{2pt+3.5pt} +\test{sin(.5*pi r)*60} +\test{1.234e+4} +\test{010} +\test{0xA} +\test{0XB} +\test{0b10} +\test{0B101} +% partially implemented: quotes + +\section{92.2 Operators} +\test{1+2} +\test{1-2} +\test{-2} +\test{1*2} +\test{1/2} +% correct, but generates 'Script ^ can only appear in math mode' error +\test[2\textasciicircum{}3]{2^3} +\test{4!} +\test{1r} +\test{pi r} +\test{1 ? 2 : 3} +\test{2 == 2} +\test{2 == 1} +\test{(1+2)*3} +\test{sin(30*10)} +\test{mod(72,3)} +\test{sin 30} +\test{sin(30)} +\test{sin 30*10} +\test{sin(30)*10} +% unimplemented: array operator {X} +% unimplemented: array access operator [X] +5 is {\pgfmathprint{ifthenelse(5 > 7,"\noexpand\Large Bigger","\noexpand\tiny smaller")} than 7.} + +5 is {\pgfmathprint{5 > 7 ? "\noexpand\Large Bigger" : "\noexpand\tiny smaller"} than 7.} + +7 is {\pgfmathprint{ifthenelse(7 > 5,"\noexpand\Large Bigger","\noexpand\tiny smaller")} than 5.} + +7 is {\pgfmathprint{7 > 5 ? "\noexpand\Large Bigger" : "\noexpand\tiny smaller"} than 5} + +\section{92.3 Functions} + +\subsection{92.3.1 Basic arithmetic functions} +\test{add(75,6)} +\test{subtract(75,6)} +\test{neg(50)} +\test{multiply(75,6)} +\test{divide(75,6)} +\test{div(75,9)} +\test{factorial(5)} +\test{sqrt(10)} +\test{sqrt(8765.432)} +\test{pow(2,7)} +\test[e\textasciicircum{}2 - e\textasciicircum{}-2]{(e^2 - e^-2)/2} +\test{exp(1)} +\test{exp(2.34)} +\test{ln(10)} +\test{ln(exp(5))} +\test{log10(100)} +\test{log2(128)} +\test{abs(-5)} +\test{-abs(4*-3)} +\test{mod(20,6)} +\test{mod(-100,30)} +\test{Mod(-100,30)} +\test{sign(-5)} +\test{sign(0)} +\test{sign(5)} + +\subsection{94.3.2 Rounding functions} +\test{round(32.5/17)} +\test{round(398/12)} +\test{floor(32.5/17)} +\test{floor(398/12)} +\test{floor(-398/12)} +\test{ceil(32.5/17)} +\test{ceil(398/12)} +\test{ceil(-398/12)} +\test{int(32.5/17)} +\test{frac(32.5/17)} +\test{int(-32.5/17)} % added in LaTeXML to check behavior on negative numbers +\test{frac(-32.5/17)} % added in LaTeXML to check behavior on negative numbers +\test{real(4)} + +\subsection{94.3.3 Integer arithmetic functions} +\test{gcd(42,56)} +\test{gcd(24,34)} % added in LaTeXML to check behavior when iteration is required +\test{gcd(42.1,56.1)} % added in LaTeXML to check behavior on non-integers +\test{gcd(-42,-56)} % added in LaTeXML to check behavior on negative numbers +\test{gcd(-42,0)} % added in LaTeXML to check behavior at zero +\test{isodd(2)} +\test{isodd(3)} +\test{iseven(2)} +\test{iseven(3)} +\test{isprime(1)} +\test{isprime(2)} +\test{isprime(31)} +\test{isprime(64)} + +\subsection{94.3.4 Trigonometric functions} +\test{pi} +\test{pi r} +\test{rad(90)} +\test{deg(3*pi/2)} +\test{sin(60)} +\test{sin(pi/3 r)} +\test{cos(60)} +\test{cos(pi/3 r)} +\test{tan(45)} +\test{tan(2*pi/8 r)} +\test{sec(45)} +\test{cosec(30)} +\test{cot(15)} + +\subsection{94.3.5 Comparison and logical functions} +% infix versions added by LaTeXML to check parsing +\test{equal(20,20)} +\test{20 == 20} +\test{greater(20,25)} +\test{20 > 25} +\test{less(20,25)} +\test{20 < 25} +\test{notequal(20,25)} +\test{20 != 25} +\test{notgreater(20,25)} +\test{20 <= 25} +\test{notless(20,25)} +\test{20 >= 25} +\test{and(5>4,6>7)} +\test{5>4 && 6>7} +\test{or(5>4,6>7)} +\test{5>4 || 6>7} +\test{not(true)} +\test{! true} +\test{not(false)} % added in LaTeXML to check parsing +\test{! false} % added in LaTeXML to check parsing +\test{ifthenelse(5==4,"yes","no")} +\test{5==4 ? "yes" : "no"} +\test{true ? "yes" : "no"} +\test{false ? "yes" : "no"} + +\subsection{94.3.6 Pseudo-random functions} +% unimplemented: determinism (yet) + +\subsection{94.3.7 Base conversion functions} +\test{hex(65535)} +\test{Hex(65535)} +\test{oct(63)} +% perl thinks this is a number and yields an overflow error +% FAILS \test{bin(185)} + +\subsection{94.3.8 Miscellaneous functions} +\test{min(3,4,-2,250,-8,100)} +\test{max(3,4,-2,250,-8,100)} +\test{veclen(12,5)} +% unimplemented: arrays +\test{sinh(0.5)} +\test{cosh(0.5)} +\test{tanh(0.5)} +\test{width("Some Lovely Text")} +\test{height("Some Lovely Text")} +\test{depth("Some Lovely Text")} + +\subsection{Additional predecence tests} +\test[2\textasciicircum{}2!]{2^2!} +\test[2\textasciicircum{}(2!)]{2^(2!)} +\test{2*2!} +\test{3!!} +\test[2\textasciicircum{}2\textasciicircum{}3]{2^2^3} +\test[2\textasciicircum{}3 r * 2]{2^3 r * 2} +\test{2 + !1 + 2} + +\end{document} diff --git a/t/pgfmathparse/pgfmathparse.xml b/t/pgfmathparse/pgfmathparse.xml new file mode 100644 index 0000000000..30184d0eca --- /dev/null +++ b/t/pgfmathparse/pgfmathparse.xml @@ -0,0 +1,519 @@ + + + + + + + + +
+ + 1 + 1 + §1 + + <tag close=" ">1</tag>94.1.1 Commands + +

2pt+3.5pt = 5.5 = 5.5

+
+ +

sin(.5*pi r)*60 = 60.0 = 60.0

+
+ +

1.234e+4 = 12340.0 = 12340.0

+
+ +

010 = 010 = 8.0

+
+ +

0xA = 10.0 = 10.0

+
+ +

0XB = 11.0 = 11.0

+
+ +

0b10 = 2.0 = 2.0

+
+ +

0B101 = 5.0 = 5.0

+
+
+
+ + 2 + 2 + §2 + + <tag close=" ">2</tag>92.2 Operators + +

1+2 = 3.0 = 3.0

+
+ +

1-2 = -1.0 = -1.0

+
+ +

-2 = -2.0 = -2.0

+
+ +

1*2 = 2.0 = 2.0

+
+ +

1/2 = 0.5 = 0.5

+
+ +

2^3 = 8.0 = 8.0

+
+ +

4! = 24.0 = 24.0

+
+ +

1r = 57.29578 = 57.29578

+
+ +

pi r = 180.0 = 180.0

+
+ +

1 ? 2 : 3 = 2.0 = 2.0

+
+ +

2 == 2 = 1.0 = 1.0

+
+ +

2 == 1 = 0.0 = 0.0

+
+ +

(1+2)*3 = 9.0 = 9.0

+
+ +

sin(30*10) = -0.86603 = -0.86603

+
+ +

mod(72,3) = 0.0 = 0.0

+
+ +

sin 30 = 0.5 = 0.5

+
+ +

sin(30) = 0.5 = 0.5

+
+ +

sin 30*10 = 5 = 5

+
+ +

sin(30)*10 = 5 = 5

+
+ +

5 is smaller than 7.

+
+ +

5 is smaller than 7.

+
+ +

7 is Bigger than 5.

+
+ +

7 is Bigger than 5

+
+
+
+ + 3 + 3 + §3 + + <tag close=" ">3</tag>92.3 Functions + + + 3.1 + 3.1 + §3.1 + + <tag close=" ">3.1</tag>92.3.1 Basic arithmetic functions + +

add(75,6) = 81.0 = 81.0

+
+ +

subtract(75,6) = 69.0 = 69.0

+
+ +

neg(50) = -50.0 = -50.0

+
+ +

multiply(75,6) = 450.0 = 450.0

+
+ +

divide(75,6) = 12.5 = 12.5

+
+ +

div(75,9) = 8.0 = 8.0

+
+ +

factorial(5) = 120.0 = 120.0

+
+ +

sqrt(10) = 3.16228 = 3.16228

+
+ +

sqrt(8765.432) = 93.62389 = 93.62389

+
+ +

pow(2,7) = 128.0 = 128.0

+
+ +

e^2 - e^-2 = 3.62686 = 3.62686

+
+ +

exp(1) = 2.71828 = 2.71828

+
+ +

exp(2.34) = 10.38124 = 10.38124

+
+ +

ln(10) = 2.30259 = 2.30259

+
+ +

ln(exp(5)) = 5.0 = 5.0

+
+ +

log10(100) = 2.0 = 2.0

+
+ +

log2(128) = 7.0 = 7.0

+
+ +

abs(-5) = 5.0 = 5.0

+
+ +

-abs(4*-3) = -12.0 = -12.0

+
+ +

mod(20,6) = 2.0 = 2.0

+
+ +

mod(-100,30) = -10.0 = -10.0

+
+ +

Mod(-100,30) = 20.0 = 20.0

+
+ +

sign(-5) = -1.0 = -1.0

+
+ +

sign(0) = 0.0 = 0.0

+
+ +

sign(5) = 1.0 = 1.0

+
+
+ + + 3.2 + 3.2 + §3.2 + + <tag close=" ">3.2</tag>94.3.2 Rounding functions + +

round(32.5/17) = 2.0 = 2.0

+
+ +

round(398/12) = 33.0 = 33.0

+
+ +

floor(32.5/17) = 1.0 = 1.0

+
+ +

floor(398/12) = 33.0 = 33.0

+
+ +

floor(-398/12) = -34.0 = -34.0

+
+ +

ceil(32.5/17) = 2.0 = 2.0

+
+ +

ceil(398/12) = 34.0 = 34.0

+
+ +

ceil(-398/12) = -33.0 = -33.0

+
+ +

int(32.5/17) = 1 = 1.0

+
+ +

frac(32.5/17) = 0.91176 = 0.91176

+
+ +

int(-32.5/17) = -1 = -1.0

+
+ +

frac(-32.5/17) = 0.91176 = 0.91176

+
+ +

real(4) = 4.0 = 4.0

+
+
+ + + 3.3 + 3.3 + §3.3 + + <tag close=" ">3.3</tag>94.3.3 Integer arithmetic functions + +

gcd(42,56) = 14.0 = 14.0

+
+ +

gcd(24,34) = 2.0 = 2.0

+
+ +

gcd(42.1,56.1) = 0.0 = 0.0

+
+ +

gcd(-42,-56) = 14.0 = 14.0

+
+ +

gcd(-42,0) = 42.0 = 42.0

+
+ +

isodd(2) = 0.0 = 0.0

+
+ +

isodd(3) = 1.0 = 1.0

+
+ +

iseven(2) = 1.0 = 1.0

+
+ +

iseven(3) = 0.0 = 0.0

+
+ +

isprime(1) = 0.0 = 0.0

+
+ +

isprime(2) = 1.0 = 1.0

+
+ +

isprime(31) = 1.0 = 1.0

+
+ +

isprime(64) = 0.0 = 0.0

+
+
+ + + 3.4 + 3.4 + §3.4 + + <tag close=" ">3.4</tag>94.3.4 Trigonometric functions + +

pi = 3.14159 = 3.14159

+
+ +

pi r = 180.0 = 180.0

+
+ +

rad(90) = 1.5708 = 1.5708

+
+ +

deg(3*pi/2) = 270.0 = 270.0

+
+ +

sin(60) = 0.86603 = 0.86603

+
+ +

sin(pi/3 r) = 0.86603 = 0.86603

+
+ +

cos(60) = 0.5 = 0.5

+
+ +

cos(pi/3 r) = 0.5 = 0.5

+
+ +

tan(45) = 1 = 1

+
+ +

tan(2*pi/8 r) = 1 = 1

+
+ +

sec(45) = 1.41421 = 1.41421

+
+ +

cosec(30) = 2 = 2

+
+ +

cot(15) = 3.73205 = 3.73205

+
+
+ + + 3.5 + 3.5 + §3.5 + + <tag close=" ">3.5</tag>94.3.5 Comparison and logical functions + +

equal(20,20) = 1.0 = 1.0

+
+ +

20 == 20 = 1.0 = 1.0

+
+ +

greater(20,25) = 0.0 = 0.0

+
+ +

20 > 25 = 0.0 = 0.0

+
+ +

less(20,25) = 1.0 = 1.0

+
+ +

20 < 25 = 1.0 = 1.0

+
+ +

notequal(20,25) = 1.0 = 1.0

+
+ +

20 != 25 = 1.0 = 1.0

+
+ +

notgreater(20,25) = 1.0 = 1.0

+
+ +

20 <= 25 = 1.0 = 1.0

+
+ +

notless(20,25) = 0.0 = 0.0

+
+ +

20 >= 25 = 0.0 = 0.0

+
+ +

and(5>4,6>7) = 0.0 = 0.0

+
+ +

5>4 && 6>7 = 0.0 = 0.0

+
+ +

or(5>4,6>7) = 1.0 = 1.0

+
+ +

5>4 || 6>7 = 1.0 = 1.0

+
+ +

not(true) = 0.0 = 0.0

+
+ +

! true = 0.0 = 0.0

+
+ +

not(false) = 1.0 = 1.0

+
+ +

! false = 1.0 = 1.0

+
+ +

ifthenelse(5==4,"yes","no") = no = no

+
+ +

5==4 ? "yes" : "no" = no = no

+
+ +

true ? "yes" : "no" = yes = yes

+
+ +

false ? "yes" : "no" = no = no

+
+
+ + + 3.6 + 3.6 + §3.6 + + <tag close=" ">3.6</tag>94.3.6 Pseudo-random functions + + + + 3.7 + 3.7 + §3.7 + + <tag close=" ">3.7</tag>94.3.7 Base conversion functions + +

hex(65535) = ffff = ffff

+
+ +

Hex(65535) = FFFF = FFFF

+
+ +

oct(63) = 77.0 = 77.0

+
+
+ + + 3.8 + 3.8 + §3.8 + + <tag close=" ">3.8</tag>94.3.8 Miscellaneous functions + +

min(3,4,-2,250,-8,100) = -8.0 = -8.0

+
+ +

max(3,4,-2,250,-8,100) = 250.0 = 250.0

+
+ +

veclen(12,5) = 13.0 = 13.0

+
+ +

sinh(0.5) = 0.5211 = 0.5211

+
+ +

cosh(0.5) = 1.12763 = 1.12763

+
+ +

tanh(0.5) = 0.46212 = 0.46212

+
+ +

width("Some Lovely Text") = 78.47 = 78.47

+
+ +

height("Some Lovely Text") = 6.94 = 6.94

+
+ +

depth("Some Lovely Text") = 1.94 = 1.94

+
+
+ + + 3.9 + 3.9 + §3.9 + + <tag close=" ">3.9</tag>Additional predecence tests + +

2^2! = 24.0 = 24.0

+
+ +

2^(2!) = 4.0 = 4.0

+
+ +

2*2! = 4.0 = 4.0

+
+ +

3!! = 720.0 = 720.0

+
+ +

2^2^3 = 64.0 = 64.0

+
+ +

2^3 r * 2 = 916.73247 = 916.73247

+
+ +

2 + !1 + 2 = 4.0 = 4.0

+
+
+
+