在生成文档时交错使用 Lua 和 TeX 的通常方法是通过\directlua
工具从 Tex 调用 Lua,然后使用 从 Lua 生成 Tex tex.print()
。例如,考虑 Plain LuaTeX:
\directlua{
es=""; someluatex = function () tex.everypar="PAR!" end
tex.print("one", es, "two")
someluatex()
tex.print("three", es, "four")
}
five\bye
查看 LuaTeX 为该输入生成的文档,我们看到正在someluatex()
运行前Tex 后端对输出进行任何操作:PAR!s 将插入到“one”和“two”之前,以及“three”之后。
是否有最佳实践来确保某些 Lua 代码发出的 TeX 代码先于其他 LuaTeX 代码发出?有几种方法可以做到这一点:对于我正在使用的某些代码,我正在尝试以下方案:
- 将执行的代码包装
\directlua
在协程中,并调用它myco
; 此时我们要确保 Tex 到目前为止 tex.print-ed 都被执行,使用:
tex.print("\\directlua{coroutine.resume(myco)}") coroutine.yield()
- 如果一切正常,Lua 将在 Tex 处理完成后恢复。
然而,这并不理想:我们必须确保 TeX 后端处于正确的状态才能理解命令\directlua
,但情况可能并非总是如此。
有没有更好的方法来实现这个目标?
答案1
简短的回答:您别无选择,只能使用某种 directlua/coroutine 技巧。
更长的答案:如果我没记错的话,它不是那样工作的。我可以展示一个我已经使用了很长时间的技巧(你提到了协程,所以你可能已经用过了,但为了记录,我会把它写在这里)。问题是,正如问题中指出的那样,LuaTeX(TeX 端)\directlua
直到右括号才执行“命令” }
。
假设你正在制作一个盒子,想知道它有多大。简单的方法不起作用:
\directlua{
tex.print("\\setbox20=\\hbox{foo}")
print(tex.box[20].width)
}
因为 TeX 在访问框的宽度时没有排版框。所以你需要做的是
\directlua{
tex.print("\\setbox20=\\hbox{foo}")
}
\directlua{
print(tex.box[20].width)
}
这很快就会变得很糟糕,因为控制流仍然在 TeX 那边。如果您需要越来越多的 Lua 代码,您希望拥有类似directtex("...")
Lua 函数的东西。没有,但您可以使用上述技巧和协程来做到这一点。协程是 Lua(和其他语言)中的一个编程概念,它允许您在程序中的两个位置之间来回跳转。所以这个想法是:跳转到 Lua 代码,当您需要 TeX 时返回,然后立即返回到您之前所在的 Lua 代码。
这个想法是创建一个函数main_loop()
并使用此参数创建一个协程。然后您以此函数作为入口点创建一个协程:co = coroutine.create(main_loop)
并使用 跳转到协程(函数main_loop()
)coroutine.resume(co)
。现在在 Lua 代码中,您可以说coroutine.yield("\\some\\TeX\\code")
,控制流会跳回到您调用 的位置coroutine.resume()
。换句话说(document.tex
):
\directlua{ dofile("luacode.lua") }
\newif\ifcontinue
\continuetrue
\directlua { co = coroutine.create(main_loop) }
\loop \directlua{ ok,b=coroutine.resume(co) tex.sprint(b) }\ifcontinue \repeat
\bye
和luacode.lua
:
function directtex(str)
coroutine.yield(str)
end
function main_loop()
directtex("\\setbox20\\hbox{foo}")
print(tex.box[20].width))
directtex("\\continuefalse")
end
其核心是围绕coroutine.resume
和 的TeX 循环tex.sprint()
。为了跳出这个无限循环,我有这个新的布尔值continue
。现在,您拥有了程序 Lua 端的控制流,并且可以选择在需要时返回 TeX。只需用 结束程序,directtex("\\continuefalse")
循环就会结束。
但是也有一个缺点。错误处理现在真的很糟糕。如果你的 Lua 代码中出现错误,计算机就会崩溃或世界末日,但 LuaTeX 可能会继续运行。获得更好的错误处理的一个解决方案是使用 lua 函数pcall()
:
function call(...)
local ret = { pcall(...) }
if ret[1]==false then
texio.write_nl("Error:" .. tostring(ret[2]))
directtex("\\continuefalse")
end
return unpack(ret,2)
end
call(func,arg1,arg2,...)
但这不会解决您所说的情况:directtex("\\csname")
答案2
(正如评论中详述的那样,这更多的是评论而不是答案。)
我认为你误认为tex.everypar="..."
插入\everypar{...}
到 TeX 输入流中。使用你在使用 的位置tex.everypar="..."
设置令牌寄存器,而你真正想要的是将设置插入到 TeX 输入流中\everypar
\directlua
\directlua{
es=""; someluatex = function () tex.print("\string\\everypar{PAR!}") end
tex.print("one", es, "two")
someluatex()
tex.print("three", es, "four")
}
five\bye
我的例子意味着 TeX 看到了标记
one
two \everypar{PAR!} three
four five\bye
而你的原文实际上和
\everypar{PAR!}% Occurs where \directlua was
one
two three
four five\bye