selene unicode Bibliothek

Auf der LuaTeX Mailingliste wurde gefragt, welche Funktionalität die in LuaTeX eingebundene Bibliothek selunicode hat. Die Bibliothek bietet einige Ersatzfunktionen für die eingebaute string-Bibliothek. Speziell handelt es sich um:

  • byte(str, start [,end=-1])
  • char(i [,j...])
  • len(str)
  • lower(str)
  • reverse(str)
  • sub(str, start [,end=-1])
  • upper(str)

sowie veränderte Zeichenklassen für

  • find
  • match
  • gmatch
  • gsub

Ein kleines Beispiel soll zeigen, warum die spezielle Handhabung von UTF-8 Strings notwendig ist:

print(string.format("string-len=%d, unicode.utf8-len=%d",
                    string.len("Löss"), unicode.utf8.len("Löss")))

ergibt

string-len=5, unicode.utf8-len=4

Und das Ergebnis 4 hätte ich auch erwartet. Der Hintergrund ist der, dass das ö als zwei Zeichen in UTF-8 kodiert ist, und zwar als 195 und 182 (0xC3B6). Da die original Lua-String-Funktionen nicht mit UTF-8 umgehen können, sehen sie dieses zusammengesetzte Zeichen als einzelne Zeichen. Dadurch ergibt sich die höhere Ausgabe der string.len()-Funktion.

Die Zeichenklassen für find, match, gmatch und gsub sind folgende:

%a L* (Lu+Ll+Lt+Lm+Lo)
%c Cc
%d 0-9
%l Ll
%n N* (Nd+Nl+No, neu)
%p P* (Pc+Pd+Ps+Pe+Pi+Pf+Po)
%s Z* (Zs+Zl+Zp) und die Controllzeichen 9-13 (HT,LF,VT,FF,CR)
%u Lu (und Lt ?)
%w %a+%n+Pc (z.B. '_')
%x 0-9A-Za-z
%z das Null-byte (\0)

Die Erklärung der Symbole Nd, Nl, No, … findest du unter http://www.unicode.org/Public/4.0-Update1/UCD-4.0.1.html#General_Category_Values.

Hier noch ein Beispiel für die Verwendung der Zeichenklassen:

local str = "...Α-Ω..."

print(string.find(str,"%u"))          --  nil
print(unicode.utf8.find(str,"%u"))    --  4   5

print(string.match(str,"%u"))         --  nil
print(unicode.utf8.match(str,"%u"))   --  A

for w in string.gmatch(str, "%u") do  -- keine Treffer
  -- (wird nicht erreicht)
end

for w in unicode.utf8.gmatch(str, "%u") do
  print(w)                            -- Α Ω
end

Vielen Dank an den Fragesteller für die ergänzenden Hinweise zu den Zeichenklassen!

15. Februar 2010 von Patrick Gundlach
Kategorien: LuaTeX | Schlagwörter: , | Schreibe einen Kommentar

\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

Neuere Artikel →