summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/generic/pgf/libraries/luamath/pgfluamath.parser.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/generic/pgf/libraries/luamath/pgfluamath.parser.lua')
-rw-r--r--Master/texmf-dist/tex/generic/pgf/libraries/luamath/pgfluamath.parser.lua647
1 files changed, 647 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/generic/pgf/libraries/luamath/pgfluamath.parser.lua b/Master/texmf-dist/tex/generic/pgf/libraries/luamath/pgfluamath.parser.lua
new file mode 100644
index 00000000000..b059e334358
--- /dev/null
+++ b/Master/texmf-dist/tex/generic/pgf/libraries/luamath/pgfluamath.parser.lua
@@ -0,0 +1,647 @@
+-- Copyright 2011 by Christophe Jorssen and Mark Wibrow
+--
+-- 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: pgfluamath.parser.lua,v 1.14 2012/04/29 20:36:29 ludewich Exp $
+
+local pgfluamathparser = pgfluamathparser or {}
+
+require("pgfluamath.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 space_pattern = S(" \n\r\t")^0
+
+local exponent_pattern = S("eE")
+
+local one_digit_pattern = R("09")
+local positive_integer_pattern = one_digit_pattern^1
+local integer_pattern = P("-")^-1 * positive_integer_pattern
+-- Valid positive decimals are |xxx.xxx|, |.xxx| and |xxx.|
+local positive_decimal_pattern = (one_digit_pattern^1 * P(".") *
+ one_digit_pattern^1) +
+ (P(".") * one_digit_pattern^1) +
+ (one_digit_pattern^1 * P("."))
+local decimal_pattern = P("-")^-1 * positive_decimal_pattern
+local float_pattern = decimal_pattern * exponent_pattern * integer_pattern
+local number_pattern = float_pattern + decimal_pattern + integer_pattern
+
+local at_pattern = P("@")
+
+local underscore_pattern = P("_")
+
+local lower_letter_pattern = R("az")
+local upper_letter_pattern = R("AZ")
+local letter_pattern = lower_letter_pattern + upper_letter_pattern
+local alphanum_pattern = letter_pattern + one_digit_pattern
+local alphanum__pattern = alphanum_pattern + underscore_pattern
+local alpha__pattern = letter_pattern + underscore_pattern
+
+local openparen_pattern = P("(")
+local closeparen_pattern = P(")")
+local opencurlybrace_pattern = P("{")
+local closecurlybrace_pattern = P("}")
+local openbrace_pattern = P("[")
+local closebrace_pattern = P("]")
+
+-- local string = P('"') * C((1 - P('"'))^0) * P('"')
+
+local orop_pattern = P("||")
+local andop_pattern = P("&&")
+
+local neqop_pattern = P("!=")
+
+local then_pattern = P("?")
+local else_pattern = P(":")
+local factorial_pattern = P("!")
+local not_mark_pattern = P("!")
+local radians_pattern = P("r")
+
+local comma_pattern = P(",")
+
+--[[
+local grammar = P {
+ -- "E" stands for expression
+ "ternary_logical_E",
+ ternary_logical_E = Cf(V("logical_or_E") *
+ Cg( then_mark * V("logical_or_E") * else_mark * V("logical_or_E"))^0,evalternary);
+ logical_or_E = Cf(V("logical_and_E") * Cg(orop * V("logical_and_E"))^0,evalbinary);
+ logical_and_E = Cf(V("equality_E") * Cg(andop * V("equality_E"))^0,evalbinary);
+ equality_E = Cf(V("relational_E") *
+ Cg((eqop * V("relational_E")) + (neqop * V("relational_E")))^0,evalbinary);
+ relational_E = Cf(V("additive_E") * Cg((lessop * V("additive_E")) +
+ (greatop * V("additive_E")) +
+ (lesseqop * V("additive_E")) +
+ (greateqop * V("additive_E")))^0,evalbinary);
+ additive_E = Cf(V("multiplicative_E") * Cg((addop * V("multiplicative_E")) +
+ (subop * V("multiplicative_E")))^0,evalbinary);
+ multiplicative_E = Cf(V("power_E") * Cg((mulop * V("power_E")) +
+ divop * V("power_E"))^0,evalbinary);
+ power_E = Cf(V("postfix_unary_E") * Cg(powop * V("postfix_unary_E"))^0,evalbinary);
+ postfix_unary_E = Cf(V("prefix_unary_E") * Cg(radians + factorial)^0,evalpostfixunary);
+ prefix_unary_E = Cf(Cg(not_mark + negop)^0 * V("E"),evalprefixunary),
+ E = string + float + decimal + integer / tonumber +
+ (openparen * V("ternary_logical_E") * closeparen) +
+ (func * param_beg * V("ternary_logical_E") *
+ (comma * V("ternary_logical_E"))^0 * param_end);
+}
+
+local parser = space_pattern * grammar * -1
+--]]
+
+-- NOTE: \pgfmathparse{pi/3.14} will fail giving pgfluamathfunctions.pi()3.14
+-- (the / is missing, probably gobbled by one of the captures of the grammar).
+
+pgfluamathparser.transform_operands = P({
+ 'transform_operands';
+
+ one_char = lpeg.P(1),
+ lowercase = lpeg.R('az'),
+ uppercase = lpeg.R('AZ'),
+ numeric = lpeg.R('09'),
+ dot = lpeg.P('.'),
+ exponent = lpeg.S('eE'),
+ sign_prefix = lpeg.S('-+'),
+ begingroup = lpeg.P('('),
+ endgroup = lpeg.P(')'),
+ backslash = lpeg.P'\\',
+ at = lpeg.P'@',
+
+ alphabetic = lpeg.V'lowercase' + lpeg.V'uppercase',
+ alphanumeric = lpeg.V'alphabetic' + lpeg.V'numeric',
+
+ integer = lpeg.V'numeric'^1,
+ real = lpeg.V'numeric'^0 * lpeg.V'dot' * lpeg.V'numeric'^1,
+ scientific = (lpeg.V'real' + lpeg.V'integer') * lpeg.V'exponent' * lpeg.V'sign_prefix'^0 * lpeg.V'integer',
+
+ number = lpeg.V'scientific' + lpeg.V'real' + lpeg.V'integer',
+ function_name = lpeg.V('alphabetic') * lpeg.V('alphanumeric')^0,
+
+ tex_cs = lpeg.V'backslash' * (lpeg.V'alphanumeric' + lpeg.V'at')^1,
+ tex_box_dimension_primative = lpeg.V'backslash' * (lpeg.P'wd' + lpeg.P'ht' + lpeg.P'dp'),
+ tex_register_primative = lpeg.V'backslash' * (lpeg.P'count' + lpeg.P'dimen'),
+
+ tex_primative = lpeg.V'tex_box_dimension_primative' + lpeg.V'tex_register_primative',
+ tex_macro = -lpeg.V'tex_primative' * lpeg.V'tex_cs',
+
+ tex_unit =
+ lpeg.P('pt') + lpeg.P('mm') + lpeg.P('cm') + lpeg.P('in') +
+ lpeg.P('ex') + lpeg.P('em') + lpeg.P('bp') + lpeg.P('pc') +
+ lpeg.P('dd') + lpeg.P('cc') + lpeg.P('sp'),
+
+ tex_register_named = lpeg.C(lpeg.V'tex_macro'),
+ tex_register_numbered = lpeg.C(lpeg.V'tex_register_primative') * lpeg.C(lpeg.V'integer'),
+ tex_register_basic = lpeg.Cs(lpeg.Ct(lpeg.V'tex_register_numbered' + lpeg.V'tex_register_named') / pgfluamathparser.process_tex_register),
+
+ tex_multiplier = lpeg.Cs((lpeg.V'tex_register_basic' + lpeg.C(lpeg.V'number')) / pgfluamathparser.process_muliplier),
+
+ tex_box_width = lpeg.Cs(lpeg.P('\\wd') / 'width'),
+ tex_box_height = lpeg.Cs(lpeg.P('\\ht') / 'height'),
+ tex_box_depth = lpeg.Cs(lpeg.P('\\dp') / 'depth'),
+ tex_box_dimensions = lpeg.V'tex_box_width' + lpeg.V'tex_box_height' + lpeg.V'tex_box_depth',
+ tex_box_named = lpeg.Cs(lpeg.Ct(lpeg.V'tex_box_dimensions' * lpeg.C(lpeg.V'tex_macro')) / pgfluamathparser.process_tex_box_named),
+ tex_box_numbered = lpeg.Cs(lpeg.Ct(lpeg.V'tex_box_dimensions' * lpeg.C(lpeg.V'number')) / pgfluamathparser.process_tex_box_numbered),
+ tex_box_basic = lpeg.V'tex_box_named' + lpeg.V'tex_box_numbered',
+
+ tex_register = lpeg.Cs(lpeg.V'tex_multiplier' * lpeg.V'tex_register_basic') + lpeg.V'tex_register_basic',
+ tex_box = lpeg.Cs(lpeg.Cs(lpeg.V'tex_multiplier') * lpeg.V'tex_box_basic') + lpeg.V'tex_box_basic',
+ tex_dimension = lpeg.Cs(lpeg.V'number' * lpeg.V'tex_unit' / pgfluamathparser.process_tex_dimension),
+
+ tex_operand = lpeg.Cs(lpeg.V'tex_dimension' + lpeg.V'tex_box' + lpeg.V'tex_register'),
+
+ function_name = lpeg.V'alphabetic' * (lpeg.V'alphanumeric'^1),
+ function_operand = lpeg.Cs(lpeg.Ct(lpeg.C(lpeg.V'function_name') * lpeg.C(lpeg.V'one_char') + lpeg.C(lpeg.V'function_name')) / pgfluamathparser.process_function),
+
+ -- order is (always) important!
+ operands = lpeg.V'tex_operand' + lpeg.V'number' + lpeg.V'function_operand',
+
+ transform_operands = lpeg.Cs((lpeg.V'operands' + 1)^0)
+ })
+
+-- Namespaces will be searched in the following order.
+pgfluamathparser.function_namespaces = {
+ 'pgfluamathfunctions', 'math', '_G'}
+
+pgfluamathparser.units_declared = false
+
+function pgfluamathparser.get_tex_box(box, dimension)
+ -- assume get_tex_box is only called when a dimension is required.
+ pgfluamathparser.units_declared = true
+ if dimension == 'width' then
+ return tex.box[box].width / 65536 -- return in points.
+ elseif dimension == 'height' then
+ return tex.box[box].height / 65536
+ else
+ return tex.box[box].depth / 65536
+ end
+end
+
+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
+ pgfluamathparser.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
+
+
+
+-- transform named box specification
+-- e.g., \wd\mybox -> pgfluamathparser.get_tex_box["mybox"].width
+--
+function pgfluamathparser.process_tex_box_named(box)
+ return 'pgfluamathparser.get_tex_box(\\number' .. box[2] .. ', "' .. box[1] .. '")'
+end
+
+-- transform numbered box specification
+-- e.g., \wd0 -> pgfluamathparser.get_tex_box["0"].width
+--
+function pgfluamathparser.process_tex_box_numbered(box)
+ return 'pgfluamathparser.get_tex_box("' .. box[2] .. '", "' .. box[1] .. '")'
+end
+
+-- transform a register
+-- e.g., \mycount -> pgfluamathparser.get_tex_register["mycount"]
+-- \dimen12 -> pgfluamathparser.get_tex_dimen[12]
+--
+function pgfluamathparser.process_tex_register(register)
+ if register[2] == nil then -- a named register
+ return 'pgfluamathparser.get_tex_register("' .. register[1]:sub(2, register[1]:len()) .. '")'
+ else -- a numbered register
+ return 'pgfluamathparser.get_tex_' .. register[1]:sub(2, register[1]:len()) .. '(' .. register[2] .. ')'
+ end
+end
+
+-- transform a 'multiplier'
+-- e.g., 0.5 -> 0.5* (when followed by a box/register)
+--
+function pgfluamathparser.process_muliplier(multiplier)
+ return multiplier .. '*'
+end
+
+-- transform an explicit dimension to points
+-- e.g., 1cm -> pgfluamathparser.get_tex_sp("1cm")
+--
+function pgfluamathparser.process_tex_dimension(dimension)
+ return 'pgfluamathparser.get_tex_sp("' .. dimension .. '")'
+end
+
+-- Check the type and 'namespace' of a function F.
+--
+-- If F cannot be found as a Lua function or a Lua number in the
+-- namespaces in containted in pgfluamathparser.function_namespaces
+-- then an error results.
+--
+-- if F is a Lua function and isn't followd by () (a requirement of Lua)
+-- then the parentheses are inserted.
+--
+-- e.g., random -> random() (if random is a function in the _G namespace)
+-- pi -> math.pi (if pi is a constant that is only in the math namespace)
+--
+function pgfluamathparser.process_function(function_table)
+ local function_name = function_table[1]
+ local char = function_table[2]
+ if (char == nil) then
+ char = ''
+ end
+ for _, namespace in pairs(pgfluamathparser.function_namespaces) do
+ local function_type = assert(loadstring('return type(' .. namespace .. '.' .. function_name .. ')'))()
+ if function_type == 'function' then
+ if not (char == '(') then
+ char = '()'
+ end
+ return namespace .. '.' .. function_name .. char
+ elseif function_type == 'number' then
+ return namespace .. '.' .. function_name .. char
+ end
+ end
+ pgfluamathparser.error = 'I don\'t know the function or constant \'' .. function_name .. '\''
+end
+
+
+pgfluamathparser.remove_spaces = lpeg.Cs((lpeg.S' \n\t' / '' + 1)^0)
+
+
+function pgfluamathparser.evaluate(expression)
+ assert(loadstring('pgfluamathparser._result=' .. expression))()
+ pgfluamathparser.result = pgfluamathparser._result
+end
+
+function pgfluamathparser.parse(expression)
+ pgfluamathparser.write_to_log("Parsing expression:" .. expression)
+ pgfluamathparser.units_declared = false
+
+ pgfluamathparser.error = nil
+ pgfluamathparser.result = nil
+
+ -- Remove spaces
+ expression = pgfluamathparser.remove_spaces:match(expression)
+
+ parsed_expression =
+ pgfluamathparser.transform_operands:match(expression)
+ pgfluamathparser.write_to_log("Transformed expression:"
+ .. parsed_expression)
+end
+
+function pgfluamathparser.eval(expression)
+ pgfluamathparser.write_to_log("Evaluating expression:" .. expression)
+ pcall(pgfluamathparser.evaluate, expression)
+ pgfluamathparser.write_to_log("Result:" .. pgfluamathparser.result)
+ if pgfluamathparser.result == nil then
+ pgfluamathparser.error = "Sorry, I could not evaluate '" .. expression .. "'"
+ return nil
+ else
+ return pgfluamathparser.result
+ end
+end
+
+pgfluamathparser.trace = true
+
+function pgfluamathparser.write_to_log (s)
+ if pgfluamathparser.trace then
+ texio.write_nl(s)
+ end
+end
+
+return pgfluamathparser
+
+--[[ NEW: 2012/02/21, CJ, work in progress
+-- We need 3 parsers (or 2, 2 and 3 could be merged)
+-- (1) To get the number associated with a (\chardef'ed) box (e.g.
+-- \ht\mybox -> \ht26)
+-- This is part of a TeX ->(\directlua == \edef) -> lua -> (lpeg parser 1)
+-- -> TeX -> (\directlua == \edef)
+-- (2) To transform math and functions operands
+-- (3) To transform units, dimen and count register
+
+-- TODO: strings delimited by "..."
+-- TODO: units. \pgfmathparse{12+1cm}\pgfmathresult and \the\dimexpr 12pt+1cm\relax have the same result. Note: \pgfmathparse{(12+1)cm} won't work in plain pgfmath.
+-- TODO: Parser 3
+
+
+pgfluamath = pgfluamath or {}
+
+pgfluamath.debug = true
+
+local lpeg = require('lpeg')
+local P, R, S, V = lpeg.P, lpeg.R, lpeg.S, lpeg.V
+local C, Cc, Cs = lpeg.C, lpeg.Cc, lpeg.Cs
+local Cf, Cg, Ct = lpeg.Cf, lpeg.Cg, lpeg.Ct
+local match = lpeg.match
+
+local space = S(' \n\t')
+
+local lowercase = R('az')
+local uppercase = R('AZ')
+local alphabetic = lowercase + uppercase
+
+local digit = R('09')
+
+local alphanumeric = alphabetic + digit
+
+local dot = P('.')
+local exponent = S('eE')
+local sign_prefix = S('+-')
+
+local integer = digit^1
+local float = (digit^1 * dot * digit^0) + (digit^0 * dot * digit^1)
+local scientific = (float + integer) * exponent * sign_prefix^-1 * integer
+
+local number = scientific + float + integer
+
+local at = P('@')
+
+local backslash = P('\\')
+
+local box_width = P('\\wd')
+local box_height = P('\\ht')
+local box_depth = P('\\dp')
+local box_dimension = box_width + box_height + box_depth
+
+local tex_cs = backslash * (alphabetic + at)^1
+
+local lparen = P('(')
+local rparen = P(')')
+
+local lbrace = P('{')
+local rbrace = P('}')
+
+local lbracket = P('[')
+local rbracket = P(']')
+
+local thenop = P('?')
+local elseop = P(':')
+
+local orop = P('||')
+local andop = P('&&')
+
+local eqop = P('==')
+local neqop = P('!=')
+
+local greaterop = P('>')
+local lessop = P('<')
+local greatereqop = P('>=')
+local lesseqop = P('<=')
+
+local addop = P('+')
+local subop = P('-')
+
+local mulop = P('*')
+local divop = P('/')
+
+local powop = P('^')
+
+local radop = P('r')
+
+local notop = P('!')
+local negop = P('-')
+
+local comma = P(',')
+
+function pgfluamath.namedbox_to_numberedbox (s)
+ -- Transforms '{\wd|\ht|\dp}\mybox' to '{\wd|\ht|\dp}\number\mybox'
+ local function transform (capture)
+ print('Captured ' .. capture)
+ return '\\number' .. capture
+ end
+ local dimension_of_a_named_box =
+ box_dimension * space^0 * (tex_cs / transform)
+ -- P(1) matches exactly one character
+ -- V(1) is the entry of the grammar with index 1. It defines the initial rule.
+ -- The manual says:
+ -- "If that entry is a string, it is assumed to be the name of the initial rule.
+ -- Otherwise, LPeg assumes that the entry 1 itself is the initial rule."
+ local grammar =
+ P({
+ [1] = (dimension_of_a_named_box + P(1)) * V(1) + P(true)
+ })
+ return print(match(Cs(grammar),s))
+end
+--[[
+namedbox_to_numberedbox('test')
+namedbox_to_numberedbox('\\test')
+namedbox_to_numberedbox('\\wd\\test')
+namedbox_to_numberedbox('\\wd\\test0')
+namedbox_to_numberedbox('\\wd\\test01')
+namedbox_to_numberedbox('\\wd\\test@t 012')
+namedbox_to_numberedbox(' \\wd \\test012')
+namedbox_to_numberedbox('\\wd\\test012\\test \\ht\\zozo0')
+namedbox_to_numberedbox('\\wd0')
+--]]
+
+-- Grammar (from lowest to highest precedence)
+-- Need to adjust to the precedences in plain pgfmath parser
+-- IfThenElse Expression
+local ITE_E = V('ITE_E')
+-- logical OR Expression
+local OR_E = V('OR_E')
+-- logical AND Expression
+local AND_E = V('AND_E')
+-- EQuality Expression
+local EQ_E = V('EQ_E')
+-- Relational EQuality Expression
+local REQ_E = V('REQ_E')
+-- ADDitive Expression
+local ADD_E = V('ADD_E')
+-- MULtiplicative Expression
+local MUL_E = V('MUL_E')
+-- POWer Expression
+local POW_E = V('POW_E')
+-- POSTfix Expression
+local POST_E = V('POST_E')
+-- PREfix Expression
+local PRE_E = V('PRE_E')
+-- ARRAY Expression
+local ARRAY_E = V('ARRAY_E')
+-- Expression
+local E = V('E')
+
+function pgfluamath.transform_math_expr (s, f_patt)
+ if pgfluamath.debug then
+ texio.write_nl('pgfluamath: parsing expression "' .. s ..'"')
+ end
+ -- f_patt extends the grammar dynamically at the time of the call to the
+ -- parser.
+ -- The idea is to have a set of predefined acceptable functions (via e.g.
+ -- \pgfmathdeclarefunction)
+ if not f_patt then
+ -- P(false) is a pattern that always fails
+ -- (neutral element for the + operator on patterns)
+ f_patt = P(false)
+ end
+ local function transform_ITE(a, b, c)
+ if not b then
+ return a
+ else
+ return string.format('pgfluamath.defined_functions.ifthenelse.code(%s,%s,%s)', a, b, c)
+ end
+ end
+ local function transform_binary(a, b, c)
+ if not b then
+ return a
+ else
+ return string.format('pgfluamath.defined_functions.%s.code(%s,%s)', b, a, c)
+ end
+ end
+ local function transform_postunary(a, b)
+ if not b then
+ return a
+ else
+ return string.format('pgfluamath.defined_functions.%s.code(%s)', b, a)
+ end
+ end
+ local function transform_preunary(a, b)
+ if not b then
+ return a
+ else
+ return string.format('pgfluamath.defined_functions.%s.code(%s)', a, b)
+ end
+ end
+ local function transform_array(a, b)
+ -- One exception to the mimmic of plain pgfmath. I do not use the equivalent array function to transform the arrays because I don't know how to handle both cases {1,{2,3}[1],4}[1] and {1,{2,3},4}[1][1] with the parser and the array function.
+ -- So I convert a pgf array to a lua table. One can access one entry of the table like this ({1,2})[1] (note the parenthesis, ie {1,2}[1] won't work).
+ local s
+ if not b then
+ s = '{'
+ else
+ s = '({'
+ end
+ for i = 1,#a do
+ -- We change the index to fit with pgfmath plain convention (index starts at 0 while in lua index starts at 1)
+ s = s .. '[' .. tostring(i-1) .. ']=' .. a[i]
+ if i < #a then
+ s = s .. ','
+ end
+ end
+ if b then
+ s = s .. '})'
+ for i = 1,#b do
+ s = s .. '[' .. b[i] .. ']'
+ end
+ else
+ s = s .. '}'
+ end
+ return s
+ end
+ local grammar =
+ lpeg.P({
+ 'ITE_E',
+ ITE_E = (OR_E * (thenop * OR_E * elseop * OR_E)^-1) / transform_ITE,
+ OR_E = (AND_E * (orop * Cc('orPGF') * AND_E)^-1) / transform_binary,
+ AND_E = (EQ_E * (andop * Cc('andPGF') * EQ_E)^-1) / transform_binary,
+ EQ_E = (REQ_E * ((eqop * Cc('equal') + neqop * Cc('notequal')) * REQ_E)^-1) / transform_binary,
+ REQ_E = (ADD_E * ((lessop * Cc('less') + greaterop * Cc('greater') + lesseqop * Cc('notgreater') + greatereqop * Cc('notless')) * ADD_E)^-1) / transform_binary,
+ ADD_E = Cf(MUL_E * Cg((addop * Cc('add') + subop * Cc('substract')) * MUL_E)^0,transform_binary),
+ MUL_E = Cf(POW_E * Cg((mulop * Cc('multiply') + divop * Cc('divide')) * POW_E)^0,transform_binary),
+ POW_E = Cf(POST_E * Cg(powop * Cc('pow') * POST_E)^0,transform_binary),
+ POST_E = (PRE_E * (radop * Cc('rad'))^-1) / transform_postunary,
+ PRE_E = ((notop * Cc('notPGF') + negop * Cc('neg'))^-1 * E) / transform_preunary,
+ ARRAY_E = (lbrace * Ct(ITE_E * (comma * ITE_E)^0) * rbrace * Ct((lbracket * ITE_E * rbracket)^0)) / transform_array,
+ E = ((integer + float)^-1 * tex_cs^1) + f_patt + C(number) + (lparen * ITE_E * rparen) + ARRAY_E + lbrace * ITE_E * rbrace
+ })
+ return lpeg.match(Cs(grammar),s)
+end
+
+function pgfluamath.ptransform_math_expr(s, f_patt)
+ local st = pgfluamath.transform_math_expr(s, f_patt)
+ if st ~= nil then
+ return texio.write_nl(st)
+ end
+end
+
+pgfluamath.defined_functions = {}
+pgfluamath.defined_functions_pattern = P(false)
+
+function pgfluamath.declare_new_function (name, nargs, code)
+ -- nil is true
+ -- The function name CANNOT be a lua reserved word (so no 'and' nor 'or')
+ if pgfluamath.defined_functions[name] then
+ print('Function ' .. name .. ' is already defined. ' ..
+ 'I overwrite it!')
+ end
+ pgfluamath.defined_functions[name] = {['name'] = name, ['nargs'] = nargs, ['code'] = code}
+
+ -- TODO
+ -- We need a function to dynamically (depending on the number of arguments) define a patten (in order to avoid the long ifcase-type structure).
+ local pattern
+ if nargs == 0 then
+ pattern = P(name) / function (s) return name .. '()' end
+ else if nargs == 1 then
+ pattern = ((P(name) * lparen * Cs(ITE_E) * rparen) / function (s) return 'pgfluamath.defined_functions.' .. name .. '.code(' .. s .. ')' end)
+ else if nargs == 2 then
+ pattern = (P(name) * lparen * Cs(ITE_E) * comma * Cs(ITE_E) * rparen / function (s1,s2) return 'pgfluamath.defined_functions.' .. name .. 'code(' .. s1 .. ',' .. s2 .. ')' end)
+ else if nargs == 3 then
+ pattern = (P(name) * lparen * Cs(ITE_E) * comma * Cs(ITE_E) * comma * Cs(ITE_E) * rparen / function (s1,s2,s3) return 'pgfluamath.defined_functions.' .. name .. 'code(' .. s1 .. ',' .. s2 .. ',' .. s3 .. ')' end)
+ end
+ end
+ end
+ end
+ -- TODO
+ -- This needs to be regenerated every time a new function is added, in conjunction with a sort of the table (see IMPORTANT below)
+ pgfluamath.defined_functions_pattern = pgfluamath.defined_functions_pattern + pattern
+end
+
+-- IMPORTANT: for 'function' with *0* argument, the longest string, the first
+-- ie declaring pi before pit won't work.
+pgfluamath.declare_new_function('pit',0)
+pgfluamath.declare_new_function('pi',0)
+pgfluamath.declare_new_function('exp',1)
+pgfluamath.declare_new_function('toto',1)
+pgfluamath.declare_new_function('gauss',2)
+
+pgfluamath.ptransform_math_expr('exp(exp(1+1))',pgfluamath.defined_functions_pattern)
+
+pgfluamath.ptransform_math_expr('1?(2?3:4^5):gauss(6+toto(7),8+9>10?11:12))',pgfluamath.defined_functions_pattern)
+
+pgfluamath.ptransform_math_expr('!(1!=-2)>3?(4+5*6+7?8||9&&10:11):12^13r||14')
+pgfluamath.ptransform_math_expr('-1^2\\test\\toto^3')
+pgfluamath.ptransform_math_expr('{1,{2+3,4}[1],5}[2]+6')
+pgfluamath.ptransform_math_expr('{1,{2+3,4},5}[1][1]')
+pgfluamath.ptransform_math_expr('{1,{2,3},4}[1][1]')
+pgfluamath.ptransform_math_expr('{1,{2,3}[1],4}[1]')
+
+--Loadstring: If it succeeds in converting the string to a *function*, it returns that function; otherwise, it returns nil and an error message.
+toto = loadstring('texio.write_nl(' .. pgfluamath.transform_math_expr('{1,{2,3},4}[1][1]') .. ')')
+toto()
+
+-- IMPORTANT NOTE!!
+-- local function add (a,b) will fail within loadstring. Needs to be global. loadstring opens a new chunk that does not know the local variables of other chunks.
+
+--toto = loadstring('texio.write_nl(' .. pgfluamath.transform_math_expr('{1,{2,3E-1+7}[1],4}[1]') .. ')')
+--toto()
+
+return pgfluamath