在 LuaTeX 中并发交叉执行 Lua 和 TeX

在 LuaTeX 中并发交叉执行 Lua 和 TeX

在生成文档时交错使用 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 代码发出?有几种方法可以做到这一点:对于我正在使用的某些代码,我正在尝试以下方案:

  1. 将执行的代码包装\directlua在协程中,并调用它myco
  2. 此时我们要确保 Tex 到目前为止 tex.print-ed 都被执行,使用:

    tex.print("\\directlua{coroutine.resume(myco)}")
    coroutine.yield()
    
  3. 如果一切正常,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

相关内容