-- 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