Aus Linux-Magazin 10/2011

Einführung in die Skriptsprache Squirrel

© dioxin, photocase.com

Wer eine Skriptsprache mit schlankem Interpreter sucht, der sich möglichst einfach in Anwendungen einbetten lässt, greift schon fast blind zu Lua. In deren Schatten wartet jedoch schon länger ein praxiserprobter Konkurrent namens Squirrel. Dieser Artikel stellt die Open-Source-Sprache in einem Crashkurs vor.

Vor etwas mehr als acht Jahren arbeitete Alberto Demichelis bei dem bekannten deutschen Spieleentwickler Crytek. Dort bastelte man gerade am Erstlingswerk Far Cry, dessen Programmlogik die Entwickler in der Skriptsprache Lua formulieren wollten. Bei der Integration stolperte Alberto Demichelis jedoch über ein Problem in der automatischen Speicherverwaltung (Garbage Collection).

Nach einigen Lösungsversuchen zog er die Notbremse und entwarf kurzerhand seine eigene Skriptsprache. Ihr Interpreter sollte wie der von Lua sein: klein, schlank und leicht in beliebige Anwendungen zu integrieren. Heraus kam Squirrel [1], deren aktueller Interpreter gerade einmal 442 KByte wiegt.

Alte Bekannte

Squirrel ist eine imperative objektorientierte Skriptsprache, deren Syntax an eine Mischung aus C, C++, Java, Javascript und Python erinnert. Von Lua hat Squirrel zudem die so genannten Tabellen abgeschaut, eine flexible Datenstruktur für fast alle Gelegenheiten. Obendrauf gibt es noch eine Garbage Collection, die sich selbstständig um die Speicherverwaltung kümmert.

Unter dem Strich vereint Squirrel somit eine einfache und vertraute Syntax mit den Vorteilen von Lua. Die Referenzimplementierung des Interpreters steht seit Squirrel 3.0 unter der MIT-Lizenz. Damit liegt nicht nur der Quellcode offen, der Interpreter darf auch kostenfrei in kommerziellen Programmen arbeiten.

Zum Einsatz kommt Squirrel derzeit vorwiegend in Computerspielen, die bekanntesten kommerziellen heißen “Left 4 Dead 2”, “Final Fantasy Crystal Chronicles: My Life as a King” und “Portal 2” [2]. Die Entwickler des quelloffenen Transport-Tycoon-Deluxe-Klons “Open TTD” formulieren die künstliche Intelligenz der Computergegner in Squirrel (Abbildung 1).

Abbildung 1: In dem als freie Software erhältlichen Spiel "Open TTD" denken die Computergegner mit Squirrel-Skripten.

Abbildung 1: In dem als freie Software erhältlichen Spiel “Open TTD” denken die Computergegner mit Squirrel-Skripten.

Wer die integrierte Entwicklungsumgebung Code::Blocks nutzt, ist ebenfalls schon auf Squirrel gestoßen: Dort erweitern Squirrel-Skripte den Funktionsumfang (Abbildung 2, [3]). Squirrel empfiehlt sich also auch für Anwendungen jenseits von Spielen. Wie die Squirrel-Shell beweist, taugt sie sogar als Ersatz für die gute alte Bash [4].

Abbildung 2: Die Entwicklungsumgebung Code::Blocks lässt sich mit Squirrel-Skripten erweitern.

Abbildung 2: Die Entwicklungsumgebung Code::Blocks lässt sich mit Squirrel-Skripten erweitern.

Handarbeit

Leider fehlt Squirrel in den Repositories der meisten gängigen Distributionen. Damit bleibt dem Anwender kaum etwas anderes übrig, als sich die Referenzimplementierung von der Squirrel-Homepage zu schnappen und den Interpreter selbst zu übersetzen [1]. Das zum Download angebotene Quelltextarchiv enthält ihn in zwei Geschmacksrichtungen: Einmal als Standalone-Variante, die Squirrel-Skripte direkt auf der Kommandozeile ausführt, und einmal als statische Bibliothek, die sich an C++-Programme anflanschen lässt. Als Bonus erhält der Benutzer die Squirrel Standard Library mit einigen hilfreichen Squirrel-Funktionen, eine Handvoll Beispielprogramme und die Sprachreferenz als PDF-Datei.

Um die Interpreter zu erstellen, genügen ein installierter C++-Compiler sowie ein simples »make« im Verzeichnis des entpackten Archivs. Anschließend findet sich im Unterverzeichnis »bin« der Standalone-Interpreter namens »sq« . Ihm übergibt der Benutzer einfach das auszuführende Squirrel-Skript:

sq meinscript.nut

Um die Abarbeitung des Skripts zu beschleunigen, übersetzt sie der Interpreter in Bytecode. Das kann auch

sq -c meinscript.nut -o meinscript.cnut

vorab explizit veranlassen. Das Ergebnis »meinscript.cnut« führt der Anwender dann einfach wieder »sq« zu: »sq meinscript.cnut« . Ruft er »sq« ohne Parameter auf, startet der Interpreter in einem interaktiven Betriebsmodus, in dem sich Squirrel-Befehle direkt eintippen und absetzen lassen (Abbildung 3). Aus dieser Eingabe-Aufforderung kommt man allerdings nur auf die harte Tour per [Strg]+[C] wieder heraus.

Abbildung 3: Das »@« vor der Zeichenkette sorgt dafür, dass der Text zwischen den Anführungszeichen Zeichen für Zeichen erhalten bleibt.

Abbildung 3: Das »@« vor der Zeichenkette sorgt dafür, dass der Text zwischen den Anführungszeichen Zeichen für Zeichen erhalten bleibt.

Das obligatorische Hallo-Welt-Beispiel besteht in Squirrel aus nur einer Zeile:

print("Hallo Welt!")

In der dynamisch typisierten Sprache lassen sich Variablen einfach nutzen und mit beliebigen Inhalten bestücken:

local a = 3.14;
local a = "Tux liebt Agnes\n";
local a = null;

Strings kann der Squirrel-Anwender mit den aus C bekannten Escape-Zeichen spicken. Das Schlüsselwort »null« leert die Variable und entspricht somit in etwa dem Nullzeiger aus C beziehungsweise »nil« in Lua.

Semikola schließen jeweils einen Ausdruck ab. Wenn er alleine in einer Zeile steht, kann das Semikolon auch entfallen. Squirrel unterscheidet zwischen Groß- und Kleinschreibung, Kommentare stehen zwischen »/*« und »*/« , einer einzelnen Kommentarzeile kann man auch »//« voranstellen.

Unter Kontrolle

Die Kontrollstrukturen mit »if« , »while« und »for« funktionieren wie in C beziehungsweise C++. Listing 1 berechnet mit ihrer Hilfe den größten gemeinsamen Teiler zweier Zahlen.

Listing 1

Größter gemeinsamer Teiler

01 // größter gemeinsamen Teiler von a und b
02 function ggT(a, b=10)
03 {
04 if (b == 0) return a;
05 else {
06 if (a > b) return ggT(a-b, b);
07 else return ggT(a, b-a);
08 }
09 }
10
11 // Berechne ein paar ggTs
12 local x=1;
13 while(x < 10)
14 {
15 print (ggT(x, 27)+"\n");
16 x = x+1;
17 }

Zusätzlich gibt es in Squirrel noch den praktischen Iterator »foreach« :

local a=[1,2,3,4,5,6]
foreach(zahl in a) print("zahl="+zahl+"\n");

Das Listing zeigt auch gleich noch, wie der Squirrel-Programmierer Funktionen definiert: Dem Schlüsselwort »function« folgen der Name der Funktion und dann in Klammern die Parameter, »return« liefert das Ergebnis der Berechnung zurück. In Listing 1 erhält »b« als Vorgabe den Wert »10« . Ihn verwendet der Interpreter immer dann, wenn beim Aufruf das zweite Argument fehlt. Squirrel erlaubt sogar eine variable Parameteranzahl:

function foo(x, ...)
{ local a = x+1; local b = vargv[0]; /* ... */
}

Dieser Funktion kann der Entwickler beliebig viele Argumente übergeben, auf die er im Rumpf über das Array »vargv« zugreift. Funktionen sind in Squirrel Objekte erster Klasse. Man darf sie daher in Variable stecken oder anderen Funktionen als Argument übergeben.

Um Schreibarbeit zu sparen, kennt Squirrel seit Version 3.0 Lambda-Ausdrücke, also Funktionen ohne Namen. Sie sind besonders beim Sortieren und Suchen nützlich:

local zahlen = [9,3,2,4,7];
zahlen.sort(@(a,b) a <=> b);

Alternativ hätte man dies auch als

zahlen.sort(function(a,b){ return a <=> b; } );

schreiben können. Der Vergleichsoperator »<=>« ist ebenfalls eine Neuerung der Squirrel-Version 3.0. Er gibt »0« zurück, wenn beiden Werte gleich sind. »sort()« ist netterweise eine eingebaute Funktion eines jeden Array.

Tabellen

Neben den beispielsweise von C und C++ bekannten Arrays

local farben = ["rot", "gruen", "blau"];

bietet Squirrel auch so genannte Tables (Tabellen). Andere Sprachen kennen diese Datenstruktur als Dictionary oder assoziatives Datenfeld. Wie ein Telefonbuch speichert dieser Typ jede ihm anvertraute Information unter einem eindeutigen Schlüssel, über den sie sich auch wieder auslesen lässt. Beispielsweise könnte man unter dem Schlüsselwort »Name« den Wert »Tim« ablegen.

In Squirrel darf der Programmierer in Tabellen nicht nur Texte als Nutzdaten einlagern, sondern auch Zahlen oder sogar komplette Funktionen – und das bunt gemischt:

local test=
{ farbe="rot" b=function(x) { return x*x; }
}

Jedes Pärchen aus Schlüssel und Wert bezeichnet Squirrel als Slot. Zugriff auf eine gespeicherte Information erhält man mit der Punktnotation:

print(test.farbe);
print(test.b(2));

Einen vorhandenen Slot ändert der Programmierer mit simpler Zuweisung:

test.b = 20;

Um einen neuen Slot hinzuzufügen, kommt der Operator »<-« zum Einsatz:

test.c <- 20;

Jeder Tabelle kann der Programmierer eine Elterntabelle (Parent Table) zuordnen. Fragt er dann irgendwann in der Tabelle einen Schlüssel nach, den es dort gar nicht gibt, delegiert der Squirrel-Interpreter automatisch die Anfrage an die Elterntabelle. Ein einfaches Beispiel dafür zeigt Listing 2: Da es »_farbname« in der Tabelle »Mischer« nicht gibt, fragt der Interpreter automatisch die Elterntabelle »Farbe« , die den Wert von »_farbname« ausspuckt.

Listing 2

Delegation

01 Mischer <- {
02 }
03
04 Mischer.Inhalt <- function()
05 {
06 ::print(_farbname);
07 }
08
09 local Farbe = {
10 _farbname="rot"
11 }
12 Farbe.setdelegate(Mischer)
13
14 Farbe.Inhalt();

Ab Squirrel 3.0 lassen sich Tabellen auch über die aus der Webentwicklung bekannte Javascript Object Notation (Json) erstellen:

local farbe = { "name": "blau", "farbnummer": 173, "varianten": ["metallic","matt"]
}

Alle globalen Variablen, Funktionen und Tabellen speichert Squirrel in einer so genannten globalen oder Root-Tabelle. Am Ende von Listing 1 enthält diese Tabelle beispielsweise Slots für die Funktion »ggT()« und die Variable »x« . Über einen vorangestellten Doppelpunkt greift man explizit auf einen dieser Slots in der Root-Tabelle zu:

::ggT(1,2);

In der Praxis muss der Programmierer häufig Listen durchlaufen. In Squirrel helfen ihm bei dieser Aufgabe so genannte Generatoren.

Gib’s mir!

Diese speziellen Funktionen arbeiten ähnlich wie ein Kaugummi-Automat, der per Fußtritt immer die nächste Kugel ausspuckt. Um einen solchen Generator zu erstellen, muss der Entwickler in Squirrel lediglich in einer normalen Funktion den Rückgabewert mit »yield« kennzeichnen:

function spender(n)
{ for(local i=0; i<n; i=i+1) yield i; return null;
}

Wer diese Funktion aufruft, erhält einen zunächst noch angehaltenen Generator zurück (also einen fabrikneuen Kaugummi-Automaten):

local eingen=spender(10);

Der Befehl »resume« veranlasst den Generator seine Arbeit aufzunehmen:

local x=resume eingen;

Die Funktion »spender()« läuft jetzt bis zum »yield« -Befehl. Dieser gibt das Ergebnis des nachfolgenden Ausdrucks zurück, im Beispiel also den Inhalt der Variablen »i« und somit eine »1« . Danach stoppt die »spender()« -Funktion sofort wieder. Erneut per »resume« geweckt, läuft sie bis zum nächsten »yield« weiter, der im Beispiel die »2« ausgeben würde. Auf diese Weise holt die Schleife

while((x=resume eingen)!=null)print(x+"\n");

nacheinander alle Zahlen von »1« bis »n« aus dem Generator.

Anhalter

Ergänzend zu den Generatoren gibt es die Threads. So bezeichnet die Squirrel-Terminologie Funktionen, die sich mittendrin anhalten und später wieder aufwecken lassen. Im Gegensatz zu den oben beschriebenen Generatoren besitzen Thread-Funktionen im Interpreter ihren eigenen Stack, eine eigene Root-Tabelle und einen eigenen Error-Handler. Um aus einer Funktion einen Thread zu machen, legt der Squirrel-Programmierer darin mit »suspend()« die Punkte fest, an denen sie einschlafen soll:

function sagzwei()
{ ::print("Erster\n"); ::suspend(); ::print("Zweiter\n");
}

Mittels der Funktion »newthread()« erstellt er anschließend aus der Funktion ein Thread-Objekt und startet den Thread via »call()« :

local einthread = ::newthread(sagzwei);
einthread.call();

Dieser läuft brav bis zum ersten »suspend()« . Um ihn wieder zu wecken, kommt »einthread.wakeup()« zur Verwendung.

Klassisches

Im Unterschied zu Lua weist Squirrel Ansätze zur Objektorientierung auf. Ein Beispiel dafür zeigt Listing 3. Es definiert zunächst die Klasse »Punkt« , von der sich anschließend die Klasse »Rechteck« ableitet. »Rechteck« überschreibt dabei die Funktion »constructor()« . Über das Schlüsselwort »base« kommt sie an die Funktion der Basisklasse heran. »this« stellt sicher, dass die Variablen der jeweiligen Klasse gemeint sind und der Interpreter nicht etwa neue, lokale Variablen erstellt.

Listing 3

Vererbung

01 class Punkt {
02 x = 0;
03 y = 0;
04
05 function constructor(x1,y1)
06 {
07 this.x=x1; this.y=y1;
08 }
09 }
10
11
12 class Rechteck extends Punkt {
13 hoehe = 0;
14 breite = 0;
15
16 function constructor (x,y, h,b)
17 {
18 this.hoehe=h; this.breite=b;
19 base.constructor(x,y);
20 }
21 function Ausgeben()
22 {
23 ::print(x+","+y+","+hoehe+","+breite+" \n");
24 }
25 }

Nun lässt sich ein Objekt der fertigen Klasse anlegen:

local einpunkt = Punkt(1,2);

Die beiden Argumente übergibt Squirrel automatisch an die Funktion »constructor()« der Klasse »Punkt« und liefert anschließend ein fertiges Objekt zurück. Auf seine Variablen und Funktionen greift der Programmierer mit der Punktnotation zu und schreibt schlicht »einpunkt.Ausgeben();« . Diese Notation deutet es schon an: Squirrel behandelt Klassen intern wie Tabellen. Die Klasse »Punkt« ließe sich daher auch wie in Listing 4 notieren. Das hat den witzigen Nebeneffekt, dass sich Klassen nachträglich über die Pfeilnotation mit weiteren Funktionen oder Variablen bestücken lassen:

Listing 4

Alternative Klassendefinition

01 Punkt <- class {
02 x = 0;
03 y = 0;
04
05 function constructor(x1,y1)
06 {
07 this.x=x1; this.y=y1;
08 }
09 }
Punkt.addieren <- function(c,d)
{ this.x=this.x+c; this.y=this.y+d;
}

Hierfür gibt es wiederum eine abkürzende und C++-Programmierern geläufigere Notation:

function Punkt::addieren(c,d)
{ this.x=this.x+c; this.y=this.y+d;
}

Der Weg über die Tabellen hat allerdings den Nachteil, dass grundsätzlich alle Funktionen und Variablen öffentlich sind. Dafür darf der Programmierer Klassen genau wie Funktionen in Variablen speichern oder einer Funktion als Parameter übergeben.

Fazit

Squirrel knackt seine Nüsse zu Unrecht im Schatten von Lua. Es ist in der Praxis erprobt und besonders für objektorientierte Programmierer wesentlich schneller zu lernen. Niemand sollte sich davon abschrecken lassen, dass die Skriptsprache derzeit vorwiegend in Computerspielen zum Einsatz kommt. Im Gegenteil: Diese Programme fordern meist besonders schlanke und flinke Interpreter.

Ein Pferdefuß ist gegenwärtig noch die karge Dokumentation. Sie besteht im Wesentlichen aus der Sprachreferenz, die aber immerhin viele kleine Beispiele mitbringt, leicht zu lesen und extrem ausführlich ist. Zusätzliche Informationen liefert das etwas chaotisch wirkende Wiki [5], für weitere Fragen steht außerdem ein Forum bereit [6]. (mhu)

Online PLUS

Einen weiterführenden Online-Artikel finden Sie unter https://www.linux-magazin.de/plus/2011/10 Er zeigt, wie Sie den Squirrel-Interpreter und Skripte in C++-Anwendungen einbinden.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben