TeX 和 Lua 之间的交互有时会非常微妙。前者有类别代码和标记,而后者有转义序列和数据类型。将这两个世界的不同概念混合在一起可能会导致意外行为,从而导致常见错误(称为陷阱)。
在下面的社区维基答案中,您可以找到人们在掌握 LuaTeX 时常犯的错误概述。
答案1
转义字符串
当将 TeX 标记作为 Lua 字符串传递时,Lua 中的特殊字符必须进行转义。此类错误的一个简单示例是
\newcommand\justprint[1]{\directlua{tex.sprint("#1")}}
\justprint{"hello"}
导致错误
[\directlua]:1: ')' expected near 'hello'.
\justprint #1->\directlua {tex.sprint("#1")}
因为 #1 已正式被替换,"hello"
这会导致无效的 lua 语法tex.sprint(""hello"")
。这可以通过应用轻松修复\luaescapestring
。
\newcommand\justprint[1]{\directlua{tex.sprint("\luaescapestring{#1}")}}
\justprint{"hello"}
如果
luacode
包已加载,则可以\directlua{tex.sprint("\luaescapestring{#1}")}}
用替换\directlua{tex.sprint(\luastring{#1})}}
。
打印数字
让我们尝试将数字 42 和字符串打印foo
到文档中。
\directlua{tex.sprint(42, "foo")}
foo
但只显示出字符串,发生了什么?
这里的陷阱是,它tex.sprint
接受可变数量的参数,并根据参数类型执行不同的操作。如果有多个参数,并且第一个参数是数字,则它将被解释为类别代码表编号,而不会打印到文档中。因此,将要打印的任何内容明确转换为字符串始终是一个好主意。
\directlua{tex.sprint(tostring(42), tostring("foo"))}
在这种情况下,第二种方法tostring
是多余的,但你应该始终使用tostring
函数返回值。此外,如果你忘记将整数转换为字符串,LuaTeX 可能会默默地为你完成此操作,但请注意,这个未记录的功能(自 1.13 版起)将来可能不受支持。
..
只要一个操作数是字符串,就可以使用连接运算符
\directlua{tex.sprint((42).."foo")}
抑制扩张
原\directlua
语会完全展开其参数,如果存在不可扩展的内容,则会导致错误。例如,如果我们要执行\section{Title}
,
\directlua{tex.sprint("\section{Title}")}
由于命令执行得太早,因此不起作用\section
。为了解决这个问题,可以使用以下常用的习惯用法:
\directlua{tex.sprint("\luaescapestring{\unexpanded{\section{Title}}}")}
该\unexpanded
原语将抑制扩展并\luaescapestring
确保\s
的前导\section
不会被解释为转义序列。
如果
luacode
包已加载,则可以tex.sprint("\luaescapestring{\unexpanded{\section{Title}}}")
用替换tex.sprint(\luastringN{\section{Title}})
。
另一种直接的可能性是\string
将原语与长 lua 字符串一起使用:
\directlua{tex.sprint([[\string\section{Title}]])}
之后\string
,\section
不再被视为命令,并且长的 lua 字符串不再被视为\
控制字符。
对结果进行排版,即 Lua 函数返回的结果
在设置包含\directlua
实时执行某些操作的语句的 LaTeX 宏时,初学者可能没有意识到,仅仅将正确的对象“返回”到 LaTeX 控件并不足以对其进行排版;tex.sprint
还需要一个明确的指令。例如,
\directlua{function modify ( s )
return ( s:gsub ( "Hello World" , "Hola Mundo" ) )
end
}
\newcommand\modify[1]{\directlua{modify("#1")}}
输入时How to say \modify{Hello World} in Spanish?
输出将是“如何用西班牙语说[两个空格]?”
为了获得所需的输出,有必要写入,比如说,\newcommand\modify[1]{\directlua{tex.sprint(modify("#1"))}}
。
评论范围之内\directlua
正如已经看到的,的参数\directlua
首先根据 TeX 规则进行管理。特别是,%
启动 TeX 注释:
\directlua{tex.sprint("this is printed% but not this comment"
(up to the real closing quote)"
)}
相当于
\directlua{tex.sprint("this is printed(up to the real closing quote)" )}
我们观察到,它只\directlua
用一行指令就可以完成。结果是,lua 短注释适用于比预期更多的指令:
\directlua{
tex.sprint("One"); -- One is printed
tex.sprint("Two"); -- but not Two
}
你可以使用长注释来代替
\directlua{
tex.sprint("One"); --[[ One is printed ]]
tex.sprint("Two"); --[[ Two is printed as well ]]
}
或luacode
环境
\begin{luacode}
tex.sprint("One"); -- One is printed
tex.sprint("Two"); -- Two is print as well
\end{luacode}
共享数据
lua 可以对许多 TeX 变量进行读写访问。例如,下面的 LaTeX 代码定义了一个计数器,从 lua 端打印并修改它,然后从 LaTeX 端打印其更新后的值:
\newcounter{abc}
\setcounter{abc}{421}
\directlua{
tex.sprint(tostring(tex.getcount('c@abc')));
tex.setcount('c@abc', 666);
}
\arabic{abc}
输出为“421 666”。这对于尺寸、框等也同样适用。LuaTeX 手册第 10.3 章对这个主题很有帮助。
对于宏来说,这并不那么透明。我们无法从 lua 端看到类别代码,因为token.get_macro('foo')
是 的去标记内容\foo
。我们也不能用 定义带有参数的宏token.set_macro
,为此我们必须使用tex.print
。尽管如此,宏可用于在 TeX 和 lua 之间共享信息(请参阅 LuaTeX 手册第 10.6 章)。我们将在下面说明这一点。
相反,TeX 无法直接访问 lua 变量。
TeX 和 lua 世界之间的一个主要区别是 lua 变量不遵循 TeX 分组机制。例如,下一个 LaTeX 环境在关闭时会打印包含以下内容的行号\begin
:
\newenvironment{Demo}{
\directlua{ begin_lineno = tex.inputlineno }
} {
\directlua{ tex.sprint(begin_lineno) }
}
当我们将 Demo 环境嵌入到另一个环境中时,这种方法就行不通了,因为begin_lineno
只考虑了最后一个\begin
。改用 LaTeX 计数器可以解决这个问题。更一般地说,遵循 TeX 组的数据应该存储在 TeX 端。
在 TeX 和 Lua 之间来回切换
\directlua
允许从 TeX 端执行 lua 指令,而tex.sprint
允许从 lua 端执行 TeX 指令。我们已经看到了喂食\directlua
可能很棘手,现在我们看到打印是异步的,不知何故
在\directlua
参数中,各种tex.print
函数向 TeX 发出指令。但这些指令只在结束时执行\directlua
:此后宏的内容\foo
交替为“白天”和“夜晚”,
\directlua{
token.set_macro('foo', 'day'); --[[ same as \string\def\string\foo{day} ]]
tex.sprint([[\string\foo]]);
token.set_macro('foo', 'night'); --[[ same as \string\def\string\foo{night} ]]
tex.sprint([[\string\foo]]);
}
结果并不是我们预期的“白天黑夜”,而是“夜晚黑夜”!这就像把所有的打印指令都集中在最后一样:
\directlua{
token.set_macro('foo', 'day');
token.set_macro('foo', 'night');
tex.sprint([[\string\foo]]);
tex.sprint([[\string\foo]]);
}
我们看到第一行是完全没用的。为了得到“白天夜晚”,我们必须使用两个单独的\directlua
:
\directlua{
token.set_macro('foo', 'day');
tex.sprint([[\string\foo]]);
}
\directlua{
token.set_macro('foo', 'night');
tex.sprint([[\string\foo]]);
}
由此推论,合并连续的\directlua
指令并不总是安全的。此外,当 TeX 和 lua 之间的讨论涉及更复杂的指令时,某些回调模式可能会有所帮助:
\direclua{
<lua instructions>
tex.print(<tex instructions>)
function callback()
<to be performed after the tex instructions>
end
tex.print([[\string\directlua{callback()}]])
}
tex.runtoks
或 lua 协程图示这个答案在这种情况下可能会有帮助,但这是高级用法。
一些未分类的事情(需要写出来)
- 查找库
- 错误报告(行号可能在
\directlua
调用内) - 百分比和其他“有趣”的字符和 luacode 包