1

For example I define \fpeval in Plain LuaTeX:

\begingroup
\catcode`\%=12
\directlua{
   function math.round_int ( x )
     return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5)
   end
   function math.round ( x , n )
     return math.round_int ( x*10^n ) / 10^n 
   end
   function gobblezero(x)
     local y = math.round ( x , 8 )
     if y == math.floor(y) then
       return string.format ( "%.0f", y )
     else
       return math.round(y, 7)
     end
   end}
\endgroup

\def\fpeval#1{\directlua{tex.print(gobblezero(#1))}}

\directlua{% p = 68 P = 80}

You have \fpeval{p} of \fpeval{P} points, this makes $\fpeval{p * 100/P},%$.

\bye

But I would like to have a macro called \fpexpr that behaves like \numexpr, so that I can write:

You have \fpexpr p of \fpexpr P points, this makes $\fpexpr p * 100/P \,\%$.

In the first case \fpexpr should take only p as the argument because of has never been defined to be a number or a function like \directlua{of = ...}. The second and the third case are similar as points and \, are undefined in the Lua interpreter.

So \fpexpr should add everything to the lua code until something would be added that would cause an error.

Is this possible?

  • 1
    You would have to define rules for what delimits a valid argument. – Steven B. Segletes Jul 15 '21 at 20:12
  • @DavidCarlisle I mean parsing an arbitrary Lua expression up to the first non-legal token as in the third case. ;) – Weißer Kater Jul 15 '21 at 20:13
  • to do this in full generality would require full lua interpreter and multiple back tracing eg after \fpeval 3+ zzz you would presumably need to consume the + then see zzz and back up and just take a 3. – David Carlisle Jul 15 '21 at 20:14
  • Trying to make a defined macro act like \numexpr is very hard. See Phelype's answer at https://tex.stackexchange.com/questions/492443/understanding-implicit-delimiters-terminators – Steven B. Segletes Jul 15 '21 at 20:14
  • I can't see any way this is remotely feasible in luatex. – David Carlisle Jul 15 '21 at 20:15
  • 4
    you could add the tokens one by one and catch errors but how can you tell when to stop \fpexpr p error-free, fpexpr p * error \fpexpr p * 100 error-free \fpexpr p * 100 / error, \fpexpr p * 100/P error-free, \fpexpr p * 100/P \, how do you know to stop here? – David Carlisle Jul 15 '21 at 20:18
  • @DavidCarlisle I have been thinking about this for some hours - without success. – Weißer Kater Jul 15 '21 at 20:29
  • @WeißerKater I have a plan.... might post an answer in a bit – David Carlisle Jul 15 '21 at 20:31
  • You would "just" need to write a parser+interpreter for Lua expressions in Lua. :-) – ShreevatsaR Jul 15 '21 at 22:28
  • Here comes the "purely academic" view, which is of no use at all: Implementing in terms of macros and \directlua a "mechanism" that in all aspects behaves like \numexpr will be difficult, to say the least: Behaving like \numexpr implies that evaluability depends on the same conditions as the evaluability of \numexpr. For example, \numexpr is not expandable, but can be evaluated by prepending \the (or \number) or when assigning values to count registers or TeX integer parameters and the like. :-) – Ulrich Diez Nov 02 '21 at 23:54

1 Answers1

3

enter image description here

This is far from complete but it does some cases.

It is very chatty on the terminal:

token is p
variable found 68 p
token is of
stop at of collected: 68
2: eval result is 68 x
token is P
variable found 80 P
token is points,
stop at points, collected: 80
2: eval result is 80 x
token is p
variable found 68 p
token is *
token is 100
token is /
token is P
variable found 80 P
token is ?
1: eval result is 85.0

code is

\begingroup
\catcode`\%=12
\directlua{
   function math.round_int ( x )
     return x>=0 and math.floor(x+0.5) or math.ceil(x-0.5)
   end
   function math.round ( x , n )
     return math.round_int ( x*10^n ) / 10^n 
   end
   function gobblezero(x)
     local y = math.round ( x , 8 )
     if y == math.floor(y) then
       return string.format ( "%.0f", y )
     else
       return math.round(y, 7)
     end
   end}
\endgroup

\def\fpeval#1{\directlua{tex.print(gobblezero(#1))}}

{\catcode`%=12 \xdef\pc{%} }

\directlua{ function zz (s) local t = token.scan_string() print ('token is ' .. (t or '?')) if (t == nil) then local f = loadstring('r= ' .. s) f() print('1: eval result is ' .. (r or '?')) tex.print(r) else % t is local variable or operator or number (inexact pattern) if (_G[t] \string~= nil) then % recurse with value print ('variable found ' .. _G[t] .. ' ' .. t) zz(s .. _G[t]) else if(string.find(t,'^[+-/*().]?\pc d*$')) then % recurse zz(s .. t) else % evaluate string so far and retrun result and the rejected token print('stop at ' .. t .. ' collected: ' .. s) local f = loadstring('r= ' .. s) f() print('2: eval result is ' .. (r or '?') .. ' x') tex.print(r .. ' ' .. t ) end end end end }

\def\fpzz{\directlua{zz('')}}

\directlua{% p = 68 P = 80}

You have \fpzz p of \fpzz P points, this makes $\fpzz p * 100 / P ,%$.

\bye

David Carlisle
  • 757,742
  • Thank you for the answer. Now I think how to avoid the crashes in some cases like \fpzz 1*1, \fpzz math.sin(1) and \fpzz 1 * 1 * a .... – Weißer Kater Jul 15 '21 at 21:59
  • @WeißerKater yes you may notice I already cheated a bit and added space / P so the variable was a separate token not /P you could add known functions as well, but really I think the answer is don't do this. It's Ok for an evening amusement but I can not see this ever being robust enough to be in a real document, and has no real advantages over using a brace group around the expression. – David Carlisle Jul 15 '21 at 22:03
  • OK, I accept your argument ... Maybe it is my lazyness. There are so much braces, I would like to avoid some of them. – Weißer Kater Jul 15 '21 at 22:06
  • @WeißerKater avoiding braces is usually a bad idea even in tex, but in luatex you have a two language system and you are wanting to drop the marking of language boundaries so having to implement an in-language parser to detect where the lua ends which is syntacticaly exceedingy weird even if you make it work. if you don't like braces use some other terminator \relax or ! or whatever and grab the whole thing as tex-delimited argument \def\fpxx#1\relax{\directlua{whatever(#1)} you could even use a space as the terminator (and then just use {..} if you need a space mid expression – David Carlisle Jul 15 '21 at 22:09
  • So, implementing \numexpr and \dimexpr was a bad idea? Now I see how complicated this has probably been. – Weißer Kater Jul 15 '21 at 22:15
  • @WeißerKater they are a same language system, so don't have the parse issues here, but the syntax isn't great and of course latex and expl3 syntax hides that and exposes consistent brace delimited arguments, – David Carlisle Jul 15 '21 at 22:18