5

The following code is a simple example of passing arguments from TeX to Lua. Just passing the arguments directly (the version without \luastringN) gives an error.

(./luafunction.aux)./luafunction.lua:2: attempt to concatenate local 's' (a nil value)
stack traceback:
        ./luafunction.lua:2: in function 'joinstring'
        [\directlua]:1: in main chunk.
\joinstring #1#2-> \directlua {joinstring(#1, #2)}

l.19 \joinstring{foo}{bar}

I noticed that other code used the macro \luastringN when passing arguments from TeX to Lua, so I did too. But this is completely Cargo Cult programming, because I have no idea why. The docunentation in the luacode manual isn't very enlightening. On section 1.2 on page 3 of the v1.2a manual where \luastringN is documented, it talks about escaping special characters. But there are no special characters in my example. And I don't understand what special handling is required to pass from TeX to Lua. Explanations appreciated - feel free to dumb it down.

Additionally, this answer to the question "LuaLaTeX set a path in lua" mentioned that one should use token.get_macro instead. The documentation on pg 10.6.4 of the luatex v 1.10 manual says:

The get_macro function can be used to get the content of a macro

but I'm not sure what that means. Also, I did a search in TeX SE for token.get_macro, and got two hits.

So is that a better choice, and if so, why? And what does the get_macro function do, exactly?

\documentclass{article}
\usepackage{luacode}
\usepackage{filecontents}
\begin{filecontents*}{luafunction2.lua}
function joinstring (s, t)
 tex.sprint(s .. t)
end

\end{filecontents*}
\directlua{require "luafunction2.lua"}
\begin{document}

\newcommand\joinstring[2]
{
  % \directlua{joinstring(#1, #2)}
  \directlua{joinstring(\luastringN{#1}, \luastringN{#2})}
}

\joinstring{foo}{bar}

\end{document}

Chat room discussion starts around here

  • your example runs without error in texlive 2019 – David Carlisle May 25 at 17:56
  • If all you are wondering about is how a TeX argument #1 or whatever should be passed to Lua, you are overthinking things: is that the core question? – Joseph Wright May 25 at 17:57
  • 1
    @DavidCarlisle The version without \luastringN gives the error. I'll edit to clarify. – Faheem Mitha May 25 at 17:58
  • @JosephWright That's one of my questions, yes. I ask several related ones here. Why is that "overthinking"? – Faheem Mitha May 25 at 17:59
  • @FaheemMitha Well for example get_macro is in an entirely different area – Joseph Wright May 25 at 18:02
4

All that \luastringN does here is provide a wrapper for \luaescapestring plus a few wrinkles. I'd just write that out, at least for a 'practice' file

\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{luafunction2.lua}
function joinstring (s, t)
 tex.sprint(s .. t)
end
\end{filecontents*}
\directlua{require "luafunction2.lua"}
\begin{document}

\newcommand\joinstring[2]
{%
  \directlua{joinstring(
    "\luaescapestring{\unexpanded{#1}}",
    "\luaescapestring{\unexpanded{#2}}")
  }%
}

\joinstring{foo}{bar}

\end{document}

Notice that \luaescapestring carries out (e-type) expansion, so we need \unexpanded to avoid strange stuff happening. Also notice the " characters, which are needed to tell Lua we are passing a string. We don't have to worry about any " in the TeX input as \luaescapestring makes them safe.

4

As an alternative to the \luaescapestring based solution in the other answers, you can also use the token library to pass arguments from TeX to Lua. This leads to minor speed improvements, because LuaTeX doesn't have to escape the string only for Lua to parse it again. So you might consider it especially when you expect long strings. Also this avoids mixing TeX and Lua code which can help if you later decide to use your code with \luafunction (or \luadef).

Anyway, you first have to decide if you want (e-type) expansion or not. The token functions always expand the arguments, so you have to apply \unexpanded if you do not want this. Then you can use token.scan_argument to read a argument that is given directly after the whole Lua code. If you decide to read an expanded argument, you can also handle the argument reading entirely through Lua, but then slightly different scanning rules apply. Especially in simple cases no braces are required around string arguments.

Here are some examples to see the actual effects of the different options:

\documentclass{article}
\usepackage{luacode}
\usepackage{filecontents}
\begin{filecontents*}{luafunction2.lua}
function joinstring (s, t)
 tex.write(s, ', ' , t, ': ')
 tex.sprint(s .. t .. '.')
end
\end{filecontents*}
\directlua{require "luafunction2.lua"}
\begin{document}

\newcommand\joinstring[2]{
  \directlua{joinstring(token.scan_argument(), token.scan_argument())}%
  {\unexpanded{#1}}{\unexpanded{#2}}
}

\newcommand\expandedjoinstring[2]
{
  \directlua{joinstring(token.scan_argument(), token.scan_argument())}%
  {#1}{#2}%
}

\newcommand\otherexpandedjoinstring
{
  \directlua{joinstring(token.scan_argument(), token.scan_argument())}%
}

\newcommand\foo{foo}
\newcommand\Bar{bar}
\verb|\joinstring{foo}{bar}|: \joinstring{foo}{bar}\\
\verb|\joinstring{\foo}{\Bar}|: \joinstring{\foo}{\Bar}\\
\verb|\joinstring foo bar|: \joinstring foo bar\\
\verb|\expandedjoinstring{foo}{bar}|: \expandedjoinstring{foo}{bar}\\
\verb|\expandedjoinstring{\foo}{\Bar}|: \expandedjoinstring{\foo}{\Bar}\\
\verb|\expandedjoinstring foo bar|: \expandedjoinstring foo bar\\
\verb|\otherexpandedjoinstring{foo}{bar}|: \otherexpandedjoinstring{foo}{bar}\\
\verb|\otherexpandedjoinstring{\foo}{\Bar}|: \otherexpandedjoinstring{\foo}{\Bar}\\
\verb|\otherexpandedjoinstring foo bar|: \otherexpandedjoinstring foo bar\\
\end{document}

enter image description here

3

Since the Lua function tex.sprint expects to operate on strings (either string constants, variables of type string, or something that can be coerced into type string on the fly), defining the LaTeX-side macro as

\newcommand\joinstring[2]{\directlua{joinstring(#1,#2)}}

and then running

\joinstring{foo}{bar}

is incorrect, because foo and bar are not automatically converted into what Lua recognizes as strings. Indeed, the error message you reproduced shows that Lua considers foo and bar to be of type nil. Variables of type nil are definitely going to trip up the string concatenation operation .. in the tex.sprint ( s .. t ) instruction.

The two obvious remedies are

  1. Place explicit quotation marks around the arguments of \joinstring, i.e., run

    \joinstring{"foo"}{"bar"}
    
  2. define the LaTeX macro as

    \newcommand\joinstring[2]{\directlua{joinstring("#1","#2")}}
    

    I suppose that one could say that \luastring{#1} and especially \luastringN{#1} are elaborations (say, to prohibit expansion of #1) of the "#1" approach. They are also more robust in the sense that they can handle unbalanced (single and double) quote marks in their arguments; unbalanced double-quote marks is something that would trip up the "#1" approach.


A full MWE that implements the second idea -- notice that for the simple code at hand it's not necessary to load the luacode package:

enter image description here

\documentclass{article}
\directlua{function joinstring (s,t) tex.sprint (s..t) end}
\newcommand\joinstring[2]{\directlua{joinstring("#1","#2")}}     
\begin{document}
\joinstring{foo}{bar}
\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.