\directlua{…} und directtex(…)

Bei LuaTeX wird der  Befehl \directlua{...} sehr häufig benutzt. Er macht nichts anderes, als den TeX-Interpreter „anzuhalten“ und in den Lua-Modus zu springen. Jetzt kann man beliebige Lua-Befehle ausführen und wenn TeX wieder zurück springt (wenn keine Lua-Befehle mehr abgearbeitet werden sollen), dann ist TeX wieder im normalen Modus und verarbeitet wieder normale TeX-Befehle. Gibt es eigentlich einen äquivalenten Lua-Befehl, der in den TeX-Modus springt?

Man könnte sagen: ja, es gibt doch zum Beispiel tex.sprint(). Doch folgendes Beispiel zeigt ein kleines Problem damit:

dokument.tex:

\directlua{ dofile("luacode.lua")  }
\bye

luacode.lua:

tex.sprint("\\setbox1\\hbox{Hallo Welt}")
texio.write_nl("Die Breite der Box 1 ist ".. tostring(tex.wd[1]))

(Warum der Inhalt von luacode.lua nicht im \directlua{..}-Block steht dürfte dir klar werden, wenn du es ausprobierst.)

Was ist die Ausgabe von dem Programm? Ich hätte erwartet, dass die Breite der Box (in scaled points) ausgegeben wird. Doch gefehlt! Es wird eine 0 ausgegeben. Das liegt daran, dass TeX noch gar nicht dazu gekommen ist, die \hbox zu setzen und der Box 1 zuzuordnen. Die Inhalte von tex.sprint() werden gesammelt und erst dann ausgeführt, wenn der \directlua{...}-Block verlassen wird. Nun, wie kann ich dann TeX zwischenzeitlich dazu veranlassen, den Code auszuführen? Die erste Lösung liegt nahe:

dokument.tex:

\directlua{ dofile("luacode.lua") }
\directlua{
 a()
}
\directlua{
 b()
}
\bye

luacode.lua:

function a(  )
  tex.sprint("\\setbox1\\hbox{Hallo Welt}")
end

function b(  )
  texio.write_nl("Die Breite der Box 1 ist ".. tostring(tex.wd[1]))
end

Es funktioniert und die Ausgabe ist 3076557. Doch schön ist die Lösung nicht, da der Kontrollfluss immer noch auf der TeX-Seite ist. Du musst im TeX-Code immer die passenden Lua-Routinen aufrufen. Für manche Problemstellungen mag das machbar sein, doch sobald du dich hauptsächlich auf der Lua-Seite bewegen möchtest (es macht einfach Spaß in Lua zu programmieren), ist das eine Sackgasse. Eine Lösung für das Problem sind Coroutinen. Mithilfe von Coroutinen kannst du zwischen zwei Programmteilen hin- und herspringen, der Lua-Interpreter merkt sich die Stelle, an der du den einen Teil des Programms verlassen hast. An dieser Stelle gibt es keine Einführung in Coroutinen in Lua, das würde den Rahmen wirklich sprengen. Ich musste mich schon ein klein wenig damit beschäftigen, bis ich auf folgende Lösung für das Problem gekommen bin:

dokument.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("\\setbox1\\hbox{Hallo Welt}")
  texio.write_nl("Die Breite der Box 1 ist ".. tostring(tex.wd[1]))
  directtex("\\continuefalse")
end

Der Kern besteht aus der Schleife \loop ... \repeat, die mit directlua{...} und coroutine.resume() in den Lua-Modus springt, bis dort ein coroutine.yield() aufgerufen wird, das Argument von coroutine.yield() an TeX übergibt, in den TeX-Modus springt und das wieder von vorne, bis die Bedingung \ifcontinue false ergibt. Naheliegend, nicht wahr?  😉

Eine wirklich praktikable Lösung, wie ich finde, die auch nicht allzu komplex ist. Zumindest wenn man sich mal mit Coroutinen beschäftigt hat. Du wirst aber ein kleines Problem erkennen, wenn du dieses Schema benutzt: die Fehlerhandhabung ist ziemlich schlecht geworden, manchmal reagiert TeX nicht mehr, manchmal gibt es kryptische Fehlermeldungen – in den seltensten Fällen gibt es einen echten Hinweis auf die Fehlerursache. Da hilft die Lua-Funktion pcall(), die ruft die angegebene Funktion im geschützten Modus auf, dadurch hast du eine Chance noch echte Fehlermeldungen auszugeben, in etwa so:

function call(...)
  local ret = { pcall(...) }
  if ret[1]==false then
    texio.write_nl("Fehler:" .. tostring(ret[2]))--  .. debug.traceback())
    directtex("\\continuefalse")
  end
  return unpack(ret,2)
end

call(funktion,argument1,argument2)

So, viel Spaß beim Verstehen und Ausprobieren – ich freue mich über Kommentare!

29. Januar 2010 von Patrick Gundlach
Kategorien: LuaTeX | Schlagwörter: | Schreibe einen Kommentar

Schreibe einen Kommentar