Die Skriptsprache Lua

Der Vorteil einer Skriptsprache ist, dass Inhalte von Variablen – ja sogar die gesamte Logik des Programms – jederzeit geändert werden können, da ja erst zur Laufzeit in Maschinenbefehle übersetzt wird. Außerdem entfällt während der Entwicklung der aufwendige Compile-Debug-Zyklus, was den Entwicklungsvorgang deutlich beschleunigt.

Der Vorteil einer Skriptsprache ist, dass Inhalte von Variablen – ja sogar die gesamte Logik des Programms – jederzeit geändert werden können, da ja erst zur Laufzeit in Maschinenbefehle übersetzt wird. Außerdem entfällt während der Entwicklung der aufwendige Compile-Debug-Zyklus, was den Entwicklungsvorgang deutlich beschleunigt.

Um den Entwicklungsvorgang komplexer mechatronischer Systeme zu beschleunigen, wird versucht, die Entwicklung von Hard- und Firmware und die der Applikations-Software zu parallelisieren. Ein Skript-Interpreter, mit dem beliebige Aktionen in den hardwarenahen Schichten ausgeführt werden können, kann hier sehr hilfreich sein. Allerdings ist die Entwicklung eines eigenen Interpreters sehr aufwendig – und auch überhaupt nicht nötig, denn es existieren bereits Lösungen wie z.B. die Skriptsprache Lua [4]. Die Erfahrungen, auf denen dieser Artikel beruht, entstanden im Zuge der Entwicklung eines Laborautomaten für die Medizintechnik (siehe Kasten). Lua kann für ein breites Feld von Embedded-Systemen, insbesondere im Bereich der Robotik, eingesetzt werden.

Lua – portugiesisch für Mond – ist eine Skriptsprache zum Einbinden in Programme, um diese leichter weiterentwickeln und warten zu können. Der Name Lua ist eine Anspielung darauf, dass die Sprache ein Nachfolger von „Sol“ (Simple Object Language) ist, dem portugiesischen Wort für „Sonne“. Eine der besonderen Eigenschaften von Lua ist die geringe Größe des compilierten Skript-Interpreters.

Lua wurde 1993 von der Computer Graphics Technology Group der Pontifikalen Katholischen Universität von Rio de Janeiro in Brasilien entwickelt. Lua-Programme werden vor der Ausführung in Bytecode übersetzt. Obwohl man mit Lua auch eigenständige Programme schreiben kann, ist Lua vorrangig als Skriptsprache von C-Programmen konzipiert. Der Lua-Interpreter kann über eine C-Bibliothek angesprochen werden, die auch ein API für die Laufzeitumgebung des Interpreters für Aufrufe vom C-Programm aus enthält. Mittels des Programmier-Interfaces können verschiedene Teile des Programms in C und Lua geschrieben werden, während Variablen und Funktionen in beiden Richtungen erreichbar bleiben, d.h., eine Funktion in Lua kann eine Funktion in C aufrufen und umgekehrt. Lua ist in ANSI-C implementiert und unterstützt sowohl funktionale als auch objektorientierte Programmierung.

Skripte – noch zur Laufzeit modifizierbar

Zu Beginn entdeckten die Spielehersteller die Vorzüge von Lua, weil sie damit künstliche Intelligenz in Skripte auslagern und diese noch zur Laufzeit modifizieren konnten. Aber auch in Anwendungen außerhalb dieses Bereichs, in denen Abläufe oder Konfigurationen durch Skripte gesteuert werden sollen, kam und kommt Lua immer häufiger zum Einsatz.

Da der Lua-Interpreter extrem schnell und hochgradig portabel ist und sich leicht in C-Programme einbetten lässt, ist er gerade für Embedded-Systeme eine attraktive Alternative zu anderen Skript-Interpretern. Obwohl er nur wenige Kilobyte umfasst, passt noch eine vollständige Garbage Collection hinein, die anfallenden Datenmüll automatisch aus dem Speicher wirft.

Lua ist freie Software und wurde bis zur Version 5 unter einer BSD-Lizenz, ab der heute aktuellen Version 5 unter der MIT-Lizenz veröffentlicht. Die MIT-Lizenz erlaubt den Einsatz auch in kommerziellen Produkten, ohne dass man den eigenen Quellcode veröffentlichen muss. Jede Kopie der Software oder substanzielle Teile davon müssen mit einem Copyright und einem Erlaubnishinweis ausgestattet werden. Hinweise hierzu sind den Links unter Open Source Initiative OSI zu entnehmen.

Listing 1. Variablen werden in Lua automatisch erzeugt und sind nicht typgebunden. Der Typ wird automatisch erzeugt.

a = 1.5 print(type(a), a) --> number 1.5
b = "2"; print(type(b), b) --> string 2
b = a + b; print(type(b), b) --> number 3.5
b = b + "a" --> stdin:1: attempt to perform arithmetic on a string value
a = print; a(type(a)) --> function

Listing 2. Funktionen können beliebig viele Parameter haben, sie können Variablen zugewiesen werden und sie können auch einer Funktion wieder als Parameter übergeben werden.

function pow(a, n)
   n = n or 2
   b = 1
   for i=1,n do b = b * a end
   return b
end

print(pow(2, 8)) --> 256
print(pow(2)) --> 4
print(pow(2, 3, 4)) --> 8

Listing 3. Tabellen haben in Lua keine feste Größe. Wird eine Tabelle wie ein Array verwendet, kann man sie mit einer for-Schleife durchlaufen. Der Operator # liefert die Länge des Arrays zurück.

a = {}   -- Erzeugt eine leere Tabelle und speichert die Referenz in 'a'

a["x"] = 11
print(a["x"]) --> 11
print(a.x) --> 11

farbe = {"rot", "grün"}
print(farbe[1]) --> rot
for i=1, #farbe do
   print(farbe[i]) end --> rot grün

de_en = {};
   de_en["rot"] = "red";
   de_en["grün"] = "green"

for i=1, #farbe do
   print(de_en[farbe[i]]) end --> red green
for key, value in pairs(de_en) do
   print(key, value) end --> grün green rot red

Listing 4. Mit Lua kann man Klassen, Datenkapselung, Vererbung und Mehrfachvererbung realisieren. Teilweise ist die Umsetzung aber eher etwas umständlich.

Point = {}
function Point:new(x, y)
   local o = {x=x, y=y}
   setmetatable(o, self)
   self.__index = self
   return o
end

function Point.__add(a, b)
   return Point:new(a.x + b.x, a.y + b.y)
end

function Point:tostring()
   return "P("..self.x.."/"..self.y..")"
end

a = Point:new(1, 2)
b = Point:new(3, 4)

c = a + b

print(a:tostring(), b:tostring(), c:tostring())   --> P(1/2) P(3/4) P(4/6)

Listing 5. Lua lässt sich einfach mit C erweitern. Alle Variablen, die einer Lua-Funktion übergeben werden, werden auf den Lua-Stack kopiert.

00 static int mylib_sum(lua_State *L){
01    double s = 0;
02    int i;
03    for(i=1; i<=lua_gettop(L); i++)
04      s = s + luaL_checknumber(L, i);
05
06      lua_pushnumber(L, s);
07    return 1;
08 }
09
10 static const struct luaL_reg mylib_functions[] = {
11    { "sum", mylib_sum },
12    { NULL,NULL }};
13
14    int luaopen_mylib (lua_State *L){
15       luaL_openlib(L,"mylib", mylib_functions,0);
16       return 1;
17 }
18 print(mylib.sum(1, 2.2, "3")) --> 6.2

Listing 6. Hier wird der Lua-Interpreter in ein C-Programm eingebunden, um einen Befehlszeilen-Taschenrechner zu implementieren.

#include <string.h>
#include <lauxlib.h>
#include <lualib.h>
#include "mylib.h"
int main (void)
{
   char line[1024];
   strcpy(line, "__res=");
   fgets(line + strlen(line), sizeof(line), stdin); // reads characters from stdin

// neuen Lua-Interpreter starten
   lua_State *L = lua_open();
   luaopen_math(L);
   luaopen_mylib(L);

// Lua-Interpreter liest Zeile ein
   if (luaL_dostring(L, line) == 0)
   {
      // __res auf Stack kopieren
      lua_getglobal(L, "__res");
      // TopOfStack wird mit printf ausgegeben
      fprintf(stdout, "%g\n", lua_tonumber(L, -1));
      lua_pop(L, 1); // TopOfStack löschen
   }
else
{
   fprintf(stderr, "%s", lua_tostring(L, -1));
      lua_pop(L, 1);
}

lua_close(L); // Lua-Interpreter schließen
   return 0;
}