diff options
Diffstat (limited to 'Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath')
-rw-r--r-- | Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/functions.lua | 649 | ||||
-rw-r--r-- | Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/parser.lua | 495 |
2 files changed, 1144 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/functions.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/functions.lua new file mode 100644 index 00000000000..e5573f26f64 --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/functions.lua @@ -0,0 +1,649 @@ +-------------------------------------------------------------------------------------------------- +------ This file is a copy of some part of PGF/Tikz. +------ It has been copied here to provide : +------ - compatibility with older PGF versions +------ - availability of PGF contributions by Christian Feuersaenger +------ which are necessary or helpful for pgfplots. +------ +------ For reasons of simplicity, I have copied the whole file, including own contributions AND +------ PGF parts. The copyrights are as they appear in PGF. +------ +------ Note that pgfplots has compatible licenses. +------ +------ This copy has been modified in the following ways: +------ - nested \input commands have been updated +------ +-- +-- Support for the contents of this file will NOT be done by the PGF/TikZ team. +-- Please contact the author and/or maintainer of pgfplots (Christian Feuersaenger) if you need assistance in conjunction +-- with the deployment of this patch or partial content of PGF. Note that the author and/or maintainer of pgfplots has no obligation to fix anything: +-- This file comes without any warranty as the rest of pgfplots; there is no obligation for help. +---------------------------------------------------------------------------------------------------- +-- Date of this copy: Mi 14. Jan 21:15:32 CET 2015 --- + + + +-- Copyright 2011 by Christophe Jorssen +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License. +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more details. +-- +-- $Id: functions.lua,v 1.2 2015/01/14 20:15:13 cfeuersaenger Exp $ +-- + +local pgfluamathfunctions = pgfluamathfunctions or {} + +-- Maps function names to their function. +-- +-- Note that this allows to register functions which are not in pgfluamathfunctions. +-- +-- Note that the string keys are not necessarily the same as the function +-- names. In particular, the math expression "not(1,1)" will execute notPGF(1,1) +-- +-- Note that each function which is added to pgfluamathfunctions will _automatically_ be inserted into this map, see __newindex. +-- (I fear it will not be erased directly...) +pgfluamathfunctions.stringToFunctionMap = {} + +local newFunctionAllocatedCallback = function(table,key,value) + local keyName = tostring(key):gsub("PGF","") + if not value then + stringToFunctionMap[keyName] = nil + elseif type(value) == 'function' then + -- remember it, and strip PGF suffix (i.e. remember 'not' instead of 'notPGF') + pgfluamathfunctions.stringToFunctionMap[keyName] = value + end + rawset(table,key,value) +end + +setmetatable(pgfluamathfunctions, { __newindex = newFunctionAllocatedCallback }) + +local mathabs, mathacos, mathasin = math.abs, math.acos, math.asin +local mathatan, mathceil = math.atan, math.ceil +local mathcos, mathdeg = math.cos, math.deg +local mathexp, mathfloor, mathfmod = math.exp, math.floor, math.fmod +local mathlog, mathmax = math.log, math.max +local mathmin, mathpi = math.min, math.pi +local mathrad, mathrandom = math.rad, math.random +local mathrandomseed, mathsin = math.randomseed, math.sin +local mathsqrt = math.sqrt +local mathtan = math.tan + +local trigFormatToRadians = mathrad + +local radiansToTrigFormat = mathdeg + +pgfluamathfunctions.TrigFormat = { 'deg', 'rad' } +pgfluamathfunctions.stringToFunctionMap["TrigFormat"] = nil + +-- choice is one of the valid choices in TrigFormat. +function pgfluamathfunctions.setTrigFormat(choice) + if choice == 'deg' then + trigFormatToRadians = mathrad + radiansToTrigFormat = mathdeg + elseif choice == 'rad' then + local identity = function(x) return x end + trigFormatToRadians = identity + radiansToTrigFormat = identity + else + error("The argument '" .. tostring(choice) .. "' is no valid choice for setTrigFormat.") + end +end +pgfluamathfunctions.stringToFunctionMap["setTrigFormat"] = nil + +pgfluamathfunctions.setRandomSeed = mathrandomseed +pgfluamathfunctions.stringToFunctionMap["setRandomSeed"] = nil + +------------------------------------------- + +function pgfluamathfunctions.add(x,y) + return x+y +end + +function pgfluamathfunctions.subtract(x,y) + return x-y +end + +function pgfluamathfunctions.neg(x) + return -x +end + +function pgfluamathfunctions.multiply(x,y) + return x*y +end + +function pgfluamathfunctions.veclen(x,y) + return mathsqrt(x*x+y*y) +end + +function pgfluamathfunctions.divide(x,y) + return x/y +end + +function pgfluamathfunctions.div(x,y) + return mathfloor(x/y) +end + +function pgfluamathfunctions.pow(x,y) + -- do not use math.pow -- it is deprecated as of LUA 5.3 + return x^y +end + +function pgfluamathfunctions.factorial(x) +-- TODO: x must be an integer + if x == 0 then + return 1 + else + return x * pgfluamathfunctions.factorial(x-1) + end +end + +function pgfluamathfunctions.ifthenelse(x,y,z) + if x~= 0 then + return y + else + return z + end +end + +function pgfluamathfunctions.equal(x,y) + if x == y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.greater(x,y) + if x > y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.less(x,y) + if x < y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.min(x,y) + return mathmin(x,y) +end + +function pgfluamathfunctions.max(x,y) + return mathmax(x,y) +end + +function pgfluamathfunctions.notequal(x,y) + if x ~= y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.notless(x,y) + if x >= y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.notgreater(x,y) + if x <= y then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.andPGF(x,y) + if (x ~= 0) and (y ~= 0) then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.orPGF(x,y) + if (x ~= 0) or (y ~= 0) then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.notPGF(x) + if x == 0 then + return 1 + else + return 0 + end +end + +function pgfluamathfunctions.pi() + return mathpi +end + +function pgfluamathfunctions.e() + return mathexp(1) +end + +function pgfluamathfunctions.abs(x) + return mathabs(x) +end + +function pgfluamathfunctions.floor(x) + return mathfloor(x) +end + +function pgfluamathfunctions.ceil(x) + return mathceil(x) +end + +function pgfluamathfunctions.exp(x) + return mathexp(x) +end + +function pgfluamathfunctions.ln(x) + return mathlog(x) +end + +local logOf10 = mathlog(10) +function pgfluamathfunctions.log10(x) + return mathlog(x) / logOf10 +end + +local logOf2 = mathlog(2) +function pgfluamathfunctions.log2(x) + return mathlog(x) / logOf2 +end + +function pgfluamathfunctions.sqrt(x) + return mathsqrt(x) +end + +function pgfluamathfunctions.sign(x) + if x < 0 then + return -1.0 + elseif x == 0 then + return 0.0 + else + return 1.0 + end +end +function pgfluamathfunctions.real(x) + -- "ensure that x contains a decimal point" is kind of a no-op here, isn't it!? + return x +end + +function pgfluamathfunctions.rnd() + return mathrandom() +end + +function pgfluamathfunctions.rand() + return -1 + mathrandom() *2 +end + +function pgfluamathfunctions.random(x,y) + if x == nil and y == nil then + return mathrandom() + elseif y == nil then + return mathrandom(x) + else + return mathrandom(x,y) + end +end + +function pgfluamathfunctions.deg(x) + return mathdeg(x) +end + +function pgfluamathfunctions.rad(x) + return mathrad(x) +end + +function pgfluamathfunctions.round(x) + if x<0 then + return -mathfloor(mathabs(x)+0.5) + else + return mathfloor(x + 0.5) + end +end + +function pgfluamathfunctions.gcd(a, b) + if b == 0 then + return a + else + return pgfluamathfunctions.gcd(b, a%b) + end +end + +function pgfluamathfunctions.isprime(a) + local ifisprime = true + if a == 1 then + ifisprime = false + elseif a == 2 then + ifisprime = true +-- if a > 2 then + else + local i, imax = 2, mathceil(mathsqrt(a)) + 1 + while ifisprime and (i < imax) do + if pgfluamathfunctions.gcd(a,i) ~= 1 then + ifisprime = false + end + i = i + 1 + end + end + if ifisprime then + return 1 + else + return 0 + end +end + + +function pgfluamathfunctions.split_braces_to_explist(s) + -- (Thanks to mpg and zappathustra from fctt) + -- Make unpack available whatever lua version is used + -- (unpack in lua 5.1 table.unpack in lua 5.2) + local unpack = table.unpack or unpack + local t = {} + for i in s:gmatch('%b{}') do + table.insert(t, tonumber(i:sub(2, -2))) + end + return unpack(t) +end + +function pgfluamathfunctions.split_braces_to_table(s) + local t = {} + for i in s:gmatch('%b{}') do + table.insert(t, tonumber(i:sub(2, -2))) + end + return t +end + +function pgfluamathfunctions.mathtrue() + return 1.0 +end +pgfluamathfunctions.stringToFunctionMap["true"] = pgfluamathfunctions.mathtrue + +function pgfluamathfunctions.mathfalse() + return 0.0 +end +pgfluamathfunctions.stringToFunctionMap["false"] = pgfluamathfunctions.mathfalse + +function pgfluamathfunctions.frac(a) + -- should be positive, apparently + return mathabs(a - pgfluamathfunctions.int(a)) +end + +function pgfluamathfunctions.int(a) + if a < 0 then + return -mathfloor(mathabs(a)) + else + return mathfloor(a) + end +end + +function pgfluamathfunctions.iseven(a) + if (a % 2) == 0 then + return 1.0 + else + return 0.0 + end +end + +function pgfluamathfunctions.isodd(a) + if (a % 2) == 0 then + return 0.0 + else + return 1.0 + end +end + +function pgfluamathfunctions.mod(x,y) + if x/y < 0 then + return -(mathabs(x)%mathabs(y)) + else + return mathabs(x)%mathabs(y) + end +end + +function pgfluamathfunctions.Mod(x,y) + local tmp = pgfluamathfunctions.mod(x,y) + if tmp < 0 then + tmp = tmp + y + end + return tmp +end + +function pgfluamathfunctions.Sin(x) + return mathsin(trigFormatToRadians(x)) +end +pgfluamathfunctions.sin=pgfluamathfunctions.Sin + + +function pgfluamathfunctions.cosh(x) + -- math.cosh is deprecated as of LUA 5.3 . reimplement it: + return 0.5* (mathexp(x) + mathexp(-x)) +end +function pgfluamathfunctions.sinh(x) + -- math.sinh is deprecated as of LUA 5.3 . reimplement it: + return 0.5* (mathexp(x) - mathexp(-x)) +end + +local sinh = pgfluamathfunctions.sinh +local cosh = pgfluamathfunctions.cosh +function pgfluamathfunctions.tanh(x) + -- math.tanh is deprecated as of LUA 5.3 . reimplement it: + return sinh(x)/cosh(x) +end + +function pgfluamathfunctions.Cos(x) + return mathcos(trigFormatToRadians(x)) +end +pgfluamathfunctions.cos=pgfluamathfunctions.Cos + +function pgfluamathfunctions.Tan(x) + return mathtan(trigFormatToRadians(x)) +end +pgfluamathfunctions.tan=pgfluamathfunctions.Tan + +function pgfluamathfunctions.aSin(x) + return radiansToTrigFormat(mathasin(x)) +end +pgfluamathfunctions.asin=pgfluamathfunctions.aSin + +function pgfluamathfunctions.aCos(x) + return radiansToTrigFormat(mathacos(x)) +end +pgfluamathfunctions.acos=pgfluamathfunctions.aCos + +function pgfluamathfunctions.aTan(x) + return radiansToTrigFormat(mathatan(x)) +end +pgfluamathfunctions.atan=pgfluamathfunctions.aTan + +local mathatan2 +if math.atan2 == nil then + -- math.atan2 has been deprecated since LUA 5.3 + mathatan2 = function (y,x) return mathatan(y,x) end +else + mathatan2 = math.atan2 +end + +function pgfluamathfunctions.aTan2(y,x) + return radiansToTrigFormat(mathatan2(y,x)) +end +pgfluamathfunctions.atan2=pgfluamathfunctions.aTan2 +pgfluamathfunctions.atantwo=pgfluamathfunctions.aTan2 + +function pgfluamathfunctions.cot(x) + return pgfluamathfunctions.cos(x) / pgfluamathfunctions.sin(x) +end +function pgfluamathfunctions.sec(x) + return 1 / pgfluamathfunctions.cos(x) +end +function pgfluamathfunctions.cosec(x) + return 1 / pgfluamathfunctions.sin(x) +end + +function pgfluamathfunctions.pointnormalised (pgfx, pgfy) + local pgfx_normalised, pgfy_normalised + if pgfx == 0. and pgfy == 0. then + -- Orginal pgf macro gives this result + tex.dimen['pgf@x'] = "0pt" + tex.dimen['pgf@y'] = "1pt" + else + pgfx_normalised = pgfx/math.sqrt(pgfx^2 + pgfy^2) + pgfx_normalised = pgfx_normalised - pgfx_normalised%0.00001 + pgfy_normalised = pgfy/math.sqrt(pgfx^2 + pgfy^2) + pgfy_normalised = pgfy_normalised - pgfy_normalised%0.00001 + tex.dimen['pgf@x'] = tostring(pgfx_normalised) .. "pt" + tex.dimen['pgf@y'] = tostring(pgfy_normalised) .. "pt" + end + return nil +end + +local isnan = function(x) + return x ~= x +end + +pgfluamathfunctions.isnan = isnan + +local infty = 1/0 +pgfluamathfunctions.infty = infty + +local nan = math.sqrt(-1) +pgfluamathfunctions.nan = nan + +local stringlen = string.len +local globaltonumber = tonumber +local stringsub=string.sub +local stringformat = string.format +local stringsub = string.sub + +-- like tonumber(x), but it also accepts nan, inf, infty, and the TeX FPU format +function pgfluamathfunctions.tonumber(x) + if type(x) == 'number' then return x end + if not x then return x end + + local len = stringlen(x) + local result = globaltonumber(x) + if not result then + if len >2 and stringsub(x,2,2) == 'Y' and stringsub(x,len,len) == ']' then + -- Ah - some TeX FPU input of the form 1Y1.0e3] . OK. transform it + local flag = stringsub(x,1,1) + if flag == '0' then + -- ah, 0.0 + result = 0.0 + elseif flag == '1' then + result = globaltonumber(stringsub(x,3, len-1)) + elseif flag == '2' then + result = globaltonumber("-" .. stringsub(x,3, len-1)) + elseif flag == '3' then + result = nan + elseif flag == '4' then + result = infty + elseif flag == '5' then + result = -infty + end + else + local lower = x:lower() + if lower == 'nan' then + result = nan + elseif lower == 'inf' or lower == 'infty' then + result = infty + elseif lower == '-inf' or lower == '-infty' then + result = -infty + end + end + end + + return result +end + +local stringlen = string.len +local globaltonumber = tonumber +local stringformat = string.format +local stringsub = string.sub +local stringfind = string.find +local stringbyte = string.byte +local NULL_CHAR = string.byte("0",1) + +local function discardTrailingZeros(x) + local result = x + -- printf is too stupid: I would like to have + -- 1. a fast method + -- 2. a reliable method + -- 3. full precision of x + -- 4. a fixed point representation + -- the 'f' modifier has trailing zeros (stupid!) + -- the 'g' modified can switch to scientific notation (no-go!) + local periodOff = stringfind(result, '.',1,true) + if periodOff ~= nil then + -- strip trailing zeros + local chars = { stringbyte(result,1,#result) }; + local lastNonZero = #chars + for i = #chars, periodOff, -1 do + if chars[i] ~= NULL_CHAR then lastNonZero=i; break; end + end + if lastNonZero ~= #chars then + -- Ah: we had at least one trailing zero. + -- discard all but the last. + lastNonZero = mathmax(periodOff+1,lastNonZero) + end + result = stringsub(result, 1, lastNonZero) + end + return result; +end + +local function discardTrailingZerosFromMantissa(x) + local mantissaStart = stringfind(x, "e") + + local mantissa = stringsub(x,1,mantissaStart-1) + local exponent = stringsub(x,mantissaStart) + + return discardTrailingZeros(mantissa) .. exponent +end + + +-- a helper function which has no catcode issues when communicating with TeX: +function pgfluamathfunctions.tostringfixed(x) + if x == nil then + return "" + end + + return discardTrailingZeros(stringformat("%f", x)) +end + +-- converts an input number to a string which is accepted by the TeX FPU +function pgfluamathfunctions.toTeXstring(x) + local result = "" + if x ~= nil then + if x == infty then result = "4Y0.0e0]" + elseif x == -infty then result = "5Y0.0e0]" + elseif isnan(x) then result = "3Y0.0e0]" + elseif x == 0 then result = "0Y0.0e0]" + else + result = discardTrailingZerosFromMantissa(stringformat("%.10e", x)) + if x > 0 then + result = "1Y" .. result .. "]" + else + result = "2Y" .. stringsub(result,2) .. "]" + end + end + end + return result +end + +return pgfluamathfunctions diff --git a/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/parser.lua b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/parser.lua new file mode 100644 index 00000000000..cc9663f145f --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgfplots/lua/pgfplotsoldpgfsupp/luamath/parser.lua @@ -0,0 +1,495 @@ +-------------------------------------------------------------------------------------------------- +------ This file is a copy of some part of PGF/Tikz. +------ It has been copied here to provide : +------ - compatibility with older PGF versions +------ - availability of PGF contributions by Christian Feuersaenger +------ which are necessary or helpful for pgfplots. +------ +------ For reasons of simplicity, I have copied the whole file, including own contributions AND +------ PGF parts. The copyrights are as they appear in PGF. +------ +------ Note that pgfplots has compatible licenses. +------ +------ This copy has been modified in the following ways: +------ - nested \input commands have been updated +------ +-- +-- Support for the contents of this file will NOT be done by the PGF/TikZ team. +-- Please contact the author and/or maintainer of pgfplots (Christian Feuersaenger) if you need assistance in conjunction +-- with the deployment of this patch or partial content of PGF. Note that the author and/or maintainer of pgfplots has no obligation to fix anything: +-- This file comes without any warranty as the rest of pgfplots; there is no obligation for help. +---------------------------------------------------------------------------------------------------- +-- Date of this copy: Mi 14. Jan 21:15:32 CET 2015 --- + + + +-- Copyright 2011 by Christophe Jorssen and Mark Wibrow +-- Copyright 2014 by Christian Feuersaenger +-- +-- This file may be distributed and/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License. +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more details. +-- +-- $Id: parser.lua,v 1.1 2014/12/27 14:11:49 cfeuersaenger Exp $ +-- +-- usage: +-- +-- pgfluamathparser = require("pgfplotsoldpgfsupp.luamath.parser") +-- +-- local result = pgfluamathparser.pgfmathparse("1+ 2*4^2") +-- +-- This LUA class has a direct backend in \pgfuselibrary{luamath}, see the documentation of that TeX package. + +local pgfluamathparser = pgfluamathparser or {} + +pgfluamathfunctions = require("pgfplotsoldpgfsupp.luamath.functions") + +-- lpeg is always present in luatex +local lpeg = require("lpeg") + +local S, P, R = lpeg.S, lpeg.P, lpeg.R +local C, Cc, Ct = lpeg.C, lpeg.Cc, lpeg.Ct +local Cf, Cg, Cs = lpeg.Cf, lpeg.Cg, lpeg.Cs +local V = lpeg.V +local match = lpeg.match + +local space_pattern = S(" \n\r\t")^0 +local tex_unit = + P('pt') + P('mm') + P('cm') + P('in') + + -- while valid units, the font-depending ones need special attention... move them to the TeX side. For now. + -- P('ex') + P('em') + + P('bp') + P('pc') + + P('dd') + P('cc') + P('sp'); + +local one_digit_pattern = R("09") +local positive_integer_pattern = one_digit_pattern^1 +-- FIXME : it might be a better idea to remove '-' from all number_patterns! Instead, rely on the prefix operator 'neg' to implement negative numbers. +-- Is that wise? It is certainly less efficient... +local integer_pattern = S("+-")^-1 * positive_integer_pattern +-- Valid positive decimals are |xxx.xxx|, |.xxx| and |xxx.| +local positive_integer_or_decimal_pattern = positive_integer_pattern * ( P(".") * one_digit_pattern^0)^-1 + + (P(".") * one_digit_pattern^1) +local integer_or_decimal_pattern = S("+-")^-1 * positive_integer_or_decimal_pattern +local fpu_pattern = R"15" * P"Y" * positive_integer_or_decimal_pattern * P"e" * P("-")^-1 * R("09")^1 * P"]" +local unbounded_pattern = P"inf" + P"INF" + P"nan" + P"NaN" + P"Inf" +local number_pattern = C(unbounded_pattern + fpu_pattern + integer_or_decimal_pattern * (S"eE" * integer_pattern + C(tex_unit))^-1) + +local underscore_pattern = P("_") + +local letter_pattern = R("az","AZ") +local alphanum__pattern = letter_pattern + one_digit_pattern + underscore_pattern + +local identifier_pattern = letter_pattern^1 * alphanum__pattern^0 + +local openparen_pattern = P("(") * space_pattern +local closeparen_pattern = P(")") +local opencurlybrace_pattern = P("{") +local closecurlybrace_pattern = P("}") +local openbrace_pattern = P("[") +local closebrace_pattern = P("]") + +-- hm. what about '\\' or '\%' ? +-- accept \pgf@x, \count0, \dimen42, \c@pgf@counta, \wd0, \ht0, \dp 0 +local controlsequence_pattern = P"\\" * C( (R("az","AZ") + P"@")^1) * space_pattern* C( R"09"^0 ) + +-- local string = P('"') * C((1 - P('"'))^0) * P('"') + +local comma_pattern = P(",") * space_pattern + + +---------------- +local TermOp = C(S("+-")) * space_pattern +local RelationalOp = C( P"==" + P"!=" + P"<=" + P">=" + P"<" + P">" ) * space_pattern +local FactorOp = C(S("*/")) * space_pattern + +-- Grammar +local Exp, Term, Factor = V"Exp", V"Term", V"Factor" +local Prefix = V"Prefix" +local Postfix = V"Postfix" + + + +local function eval (v1, op, v2) + if (op == "+") then return v1 + v2 + elseif (op == "-") then return v1 - v2 + elseif (op == "*") then return v1 * v2 + elseif (op == "/") then return v1 / v2 + else + error("This function must not be invoked for operator "..op) + end +end + +local pgfStringToFunctionMap = pgfluamathfunctions.stringToFunctionMap +local function function_eval(name, ... ) + local f = pgfStringToFunctionMap[name] + if not f then + error("Function '" .. name .. "' is undefined (did not find pgfluamathfunctions."..name .." (looked into pgfluamathfunctions.stringToFunctionMap))") + end + -- FIXME: validate signature + return f(...) +end + + +local func = + (C(identifier_pattern) * space_pattern * openparen_pattern * Exp * (comma_pattern * Exp)^0 * closeparen_pattern) / function_eval; + +local functionWithoutArg = identifier_pattern / function_eval + +-- this is what can occur as exponent after '^'. +-- I have the impression that the priorities could be implemented in a better way than this... but it seems to work. +local pow_exponent = + -- allows 2^-4, 2^1e4, 2^2 + -- FIXME : why not 2^1e2 ? + Cg(C(integer_or_decimal_pattern) + -- 2^pi, 2^multiply(2,2) + + Cg(func+functionWithoutArg) + -- 2^(2+2) + + openparen_pattern * Exp * closeparen_pattern ) + +local function prefix_eval(op, x) + if op == "-" then + return pgfluamathfunctions.neg(x) + elseif op == "!" then + return pgfluamathfunctions.notPGF(x) + else + error("This function must not be invoked for operator "..op) + end +end + + +local prefix_operator = C( S"-!" ) +local prefix_operator_pattern = (prefix_operator * space_pattern * Cg(Prefix) ) / prefix_eval + +-- apparently, we need to distinghuish between <expr> ! and <expr> != <expr2>: +local postfix_operator = C( S"r!" - P"!=" ) + C(P"^") * space_pattern * pow_exponent + + +local ternary_eval = pgfluamathfunctions.ifthenelse + +local factorial_eval = pgfluamathfunctions.factorial +local deg = pgfluamathfunctions.deg +local pow_eval = pgfluamathfunctions.pow + +-- @param prefix the argument before the postfix operator. +-- @param op either nil or the postfix operator +-- @param arg either nil or the (mandatory) argument for 'op' +local function postfix_eval(prefix, op, arg) + local result + if op == nil then + result = prefix + elseif op == "r" then + if arg then error("parser setup error: expected nil argument") end + result = deg(prefix) + elseif op == "!" then + if arg then error("parser setup error: expected nil argument") end + result = factorial_eval(prefix) + elseif op == "^" then + if not arg then error("parser setup error: ^ with its argument") end + result = pow_eval(prefix, arg) + else + error("Parser setup error: " .. tostring(op) .. " unexpected in this context") + end + return result +end + +local function relational_eval(v1, op, v2) + local fct + if (op == "==") then fct = pgfluamathfunctions.equal + elseif (op == "!=") then fct = pgfluamathfunctions.notequal + elseif (op == "<") then fct = pgfluamathfunctions.less + elseif (op == ">") then fct = pgfluamathfunctions.greater + elseif (op == ">=") then fct = pgfluamathfunctions.notless + elseif (op == "<=") then fct = pgfluamathfunctions.notgreater + else + error("This function must not be invoked for operator "..op) + end + return fct(v1,v2) +end + +-- @return either the box property or nil +-- @param cs "wd", "ht", or "dp" +-- @param intSuffix some integer +local function get_tex_box(cs, intSuffix) + -- assume get_tex_box is only called when a dimension is required. + local result + pgfluamathparser.units_declared = true + local box =tex.box[tonumber(intSuffix)] + if not box then error("There is no box " .. intSuffix) end + if cs == "wd" then + result = box.width / 65536 + elseif cs == "ht" then + result = box.height / 65536 + elseif cs == "dp" then + result = box.depth / 65536 + else + result = nil + end + return result +end + + +local function controlsequence_eval(cs, intSuffix) + local result + if intSuffix and #intSuffix >0 then + if cs == "count" then + result= pgfluamathparser.get_tex_count(intSuffix) + elseif cs == "dimen" then + result= pgfluamathparser.get_tex_dimen(intSuffix) + else + result = get_tex_box(cs,intSuffix) + if not result then + -- this can happen - we cannot expand \chardef'ed boxes here. + -- this will be done by the TeX part + error('I do not know/support the TeX register "\\' .. cs .. '"') + end + end + else + result = pgfluamathparser.get_tex_register(cs) + end + return result +end + +pgfluamathparser.units_declared = false +function pgfluamathparser.get_tex_register(register) + -- register is a string which could be a count or a dimen. + if pcall(tex.getcount, register) then + return tex.count[register] + elseif pcall(tex.getdimen, register) then + pgfluamathparser.units_declared = true + return tex.dimen[register] / 65536 -- return in points. + else + error('I do not know the TeX register "' .. register .. '"') + return nil + end + +end + +function pgfluamathparser.get_tex_count(count) + -- count is expected to be a number + return tex.count[tonumber(count)] +end + +function pgfluamathparser.get_tex_dimen(dimen) + -- dimen is expected to be a number + pgfluamathparser.units_declared = true + return tex.dimen[tonumber(dimen)] / 65536 +end + +function pgfluamathparser.get_tex_sp(dimension) + -- dimension should be a string + pgfluamathparser.units_declared = true + return tex.sp(dimension) / 65536 +end + + +local initialRule = V"initial" + +local Summand = V"Summand" +local Relational = V"Relational" +local LogicalOr = V"LogicalOr" +local LogicalAnd = V"LogicalAnd" + +local pgftonumber = pgfluamathfunctions.tonumber +local tonumber_withunit = pgfluamathparser.get_tex_sp +local function number_optional_units_eval(x, unit) + if not unit then + return pgftonumber(x) + else + return tonumber_withunit(x) + end +end + +-- @param scale the number. +-- @param controlsequence either nil in which case just the number must be returned or a control sequence +-- @see controlsequence_eval +local function scaled_controlsequence_eval(scale, controlsequence, intSuffix) + if controlsequence==nil then + return scale + else + return scale * controlsequence_eval(controlsequence, intSuffix) + end +end + +-- Grammar +-- +-- for me: +-- - use '/' to evaluate all expressions which contain a _constant_ number of captures. +-- - use Cf to evaluate expressions which contain a _dynamic_ number of captures +-- +-- see unittest_luamathparser.tex for tons of examples +local G = P{ "initialRule", + initialRule = space_pattern* Exp * -1; + -- ternary operator (or chained ternary operators): + -- FIXME : is this chaining a good idea!? + Exp = Cf( Relational * Cg(P"?" * space_pattern * Relational * P":" *space_pattern * Relational )^0, ternary_eval) ; + -- FIXME : do we really allow something like " 1 == 1 != 2" ? I would prefer (1==1) != 2 !? + Relational = Cf(LogicalOr * Cg(RelationalOp * LogicalOr)^0, relational_eval); + LogicalOr = Cf(LogicalAnd * (P"||" * space_pattern * LogicalAnd)^0, pgfluamathfunctions.orPGF); + LogicalAnd = Cf(Summand * (P"&&" * space_pattern * Summand)^0, pgfluamathfunctions.andPGF); + Summand = Cf(Term * Cg(TermOp * Term)^0, eval) ; + Term = Cf(Prefix * Cg(FactorOp * Prefix)^0, eval); + Prefix = prefix_operator_pattern + Postfix; + -- this calls 'postfix_eval' with nil arguments if it is no postfix operation.. but that does not hurt (right?) + Postfix = Factor * (postfix_operator * space_pattern)^-1 / postfix_eval; + Factor = + ( + number_pattern / number_optional_units_eval * + -- this construction will evaluate number_pattern with 'number_optional_units_eval' FIRST. + -- also accept '0.5 \pgf@x' here: + space_pattern *controlsequence_pattern^-1 / scaled_controlsequence_eval + + func + + functionWithoutArg + + openparen_pattern * Exp * closeparen_pattern + + controlsequence_pattern / controlsequence_eval + ) *space_pattern + ; +} + +-- does not reset units_declared. +local function pgfmathparseinternal(str) + local result = match(G,str) + if result == nil then + error("The string '" .. str .. "' is no valid PGF math expression. Please check for syntax errors.") + end + return result +end + + +-- This is the math parser function in this module. +-- +-- @param str a string like "1+1" which is accepted by the PGF math language +-- @return the result of the expression. +-- +-- Throws an error if the string is no valid expression. +function pgfluamathparser.pgfmathparse(str) + pgfluamathparser.units_declared = false + + return pgfmathparseinternal(str) +end + +local pgfmathparse = pgfluamathparser.pgfmathparse +local tostringfixed = pgfluamathfunctions.tostringfixed +local tostringfpu = pgfluamathfunctions.toTeXstring + +local tmpFunctionArgumentPrefix = "tmpVar" +local stackOfLocalFunctions = {} + +-- This is a backend for PGF's 'declare function'. +-- \tikzset{declare function={mu(\x,\i)=\x^\i;}} +-- will boil down to +-- pgfluamathparser.declareExpressionFunction("mu", 2, "#1^#2") +-- +-- The local function will be pushed on a stack of known local functions and is +-- available until popLocalExpressionFunction() is called. TeX will call this using +-- \aftergroup. +-- +-- @param name the name of the new function +-- @param numArgs the number of arguments +-- @param expression an expression containing #1, ... #n where n is numArgs +-- +-- ATTENTION: local functions behave DIFFERENTLY in LUA! +-- In LUA, local variables are not expanded whereas TeX expands them. +-- The difference is +-- +-- declare function={mu1(\x,\i)=\x^\i;} +-- \pgfmathparse{mu1(-5,2)} --> -25 +-- \pgfluamathparse{mu1(-5,2)} --> 25 +-- +-- x = -5 +-- \pgfmathparse{mu1(x,2)} --> 25 +-- \pgfluamathparse{mu1(x,2)} --> 25 +-- +-- In an early prototype, I simulated TeX's expansion to fix the first case (successfully). +-- BUT: that "simulated expansion" broke the second case because LUA will evaluate "x" and hand -5 to the local function. +-- I decided to keep it as is. Perhaps we should fix PGF's expansion approach in TeX (which is ugly anyway) +function pgfluamathparser.pushLocalExpressionFunction(name, numArgs, expression) + -- now we have "tmpVar1^tmpVar2" instead of "#1^#2" + local normalizedExpr = expression:gsub("#", tmpFunctionArgumentPrefix) + local restores = {} + local tmpVars = {} + for i=1,numArgs do + local tmpVar = tmpFunctionArgumentPrefix .. tostring(i) + tmpVars[i] = tmpVar + end + + local newFunction = function(...) + local args = table.pack(...) + + -- define "tmpVar1" ... "tmpVarN" to return args[i]. + -- Of course, we need to restore "tmpVar<i>" after we return! + for i=1,numArgs do + local tmpVar = tmpVars[i] + local value = args[i] + restores[i] = pgfStringToFunctionMap[tmpVar] + pgfStringToFunctionMap[tmpVar] = function () return value end + end + + -- parse our expression. + + -- FIXME : this here is an attempt to mess around with "units_declared". + -- It would be better to call pgfmathparse and introduce some + -- semaphore to check if pgfmathparse is a nested call-- in this case, it should + -- not reset units_declared. But there is no "finally" block and pcall is crap (looses stack trace). + local success,result = pcall(pgfmathparseinternal, normalizedExpr) + + -- remove 'tmpVar1', ... from the function table: + for i=1,numArgs do + local tmpVar = tmpVars[i] + pgfStringToFunctionMap[tmpVar] = restores[i] + end + + if success==false then error(result) end + return result + end + table.insert(stackOfLocalFunctions, name) + pgfStringToFunctionMap[name] = newFunction +end + +function pgfluamathparser.popLocalExpressionFunction() + local name = stackOfLocalFunctions[#stackOfLocalFunctions] + pgfStringToFunctionMap[name] = nil + -- this removes the last element: + table.remove(stackOfLocalFunctions) +end + + +-- A Utility function which simplifies the interaction with the TeX code +-- @param expression the input expression (string) +-- @param outputFormatChoice 0 if the result should be a fixed point number, 1 if it should be in FPU format +-- @param showErrorMessage (boolean) true if any error should be displayed, false if errors should simply result in an invocation of TeX's parser (the default) +-- +-- it defines \pgfmathresult and \ifpgfmathunitsdeclared +function pgfluamathparser.texCallParser(expression, outputFormatChoice, showErrorMessage) + local success, result + if showErrorMessage then + result = pgfmathparse(expression) + success = true + else + success, result = pcall(pgfmathparse, expression) + end + + if success and result then + local result_str + if outputFormatChoice == 0 then + -- luamath/output format=fixed + result_str = tostringfixed(result) + else + -- luamath/output format=fixed + result_str = tostringfpu(result) + end + tex.sprint("\\def\\pgfmathresult{" .. result_str .. "}") + if pgfluamathparser.units_declared then + tex.sprint("\\pgfmathunitsdeclaredtrue") + else + tex.sprint("\\pgfmathunitsdeclaredfalse") + end + else + tex.sprint("\\def\\pgfmathresult{}") + tex.sprint("\\pgfmathunitsdeclaredfalse") + end +end + +return pgfluamathparser |