Dokumentation | GOTHIC |
Die Skriptsprache "DAEDALUS" | |
Autor: Ulf Wohlers | Version: 12.Juli 2001 |
Inhalt:
|
1. EinführungDie von uns entworfene Skript-Sprache wird im folgenden Text mit DAEDALUS bezeichnet. Das eigentliche Spielprogramm, das den Skript-Code interpretiert wird in der Regel mit "C++"-Code bezeichnet. Dieses Kapitel beschreibt die Skriptsprache DAEDALUS. Beim Design inspirierten uns vor allem die Programmiersprache "C", die Skript-Sprache "Quake-C" und zum Teil Pascal. DAEDALUS weicht jedoch in manchen Bereichen weit von diesen ab. Einsatzgebiete von DAEDALUS
2. Beschreibung der SyntaxEs gibt 5 Arten von sogenannten Token: Bezeichner (identifier), Schlüsselwörter (keywords), Literale (literals), Operatoren (operators) und andere Trennzeichen. Aus diesen Token ist ein Skript zusammengesetzt. Leerzeichen, Zeilenumbrüche, Kommentare etc. werden ignoriert. Die Länge von Bezeichnern ist nicht beschränkt. Bezeichner sind Namen für Variablen, Konstanten, Instanzen, Prototypen, Klassen und Funktionen. Ein Bezeichner ist eine Folge von Buchstaben und Ziffern. Das erste Zeichen muß ein Buchstabe sein. Danach sind Buchstaben, die Ziffern 0 bis 9, sowie der Unterstrich erlaubt. Schlüsselwörter sind: class const else float func if instance int other prototype return self string this var void while Literale sind Zeichenketten ("Hallo") und konstante Werte (453). Operatoren werden weiter unten eingeführt. In dieser Referenz beschreibe ich die Skriptsprache formal in Anlehnung an das Buch "Die C++ Programmiersprache" von Bjarne Stroustrup. Syntaktische Bezeichner werden in Kursivschrift dargestellt. Die Skriptsprache ist nicht case-sensitive (anders als in C). Wir empfehlen aber folgende Konventionen für die Namensgebung von Bezeichnern: (Sei "dng" die Kurzkennung für ein Modul "dungeon.D")
3. KommentareDie Zeichenfolge /* beginnt einen Kommentar, der mit der Zeichenfolge */ beendet wird. Die Zeichenfolge // beginnt einen Kommentar, der bis zum Ende der Zeile geht. Innerhalb eines Kommentars haben die Zeichenfolgen // und /*, sowie */ nach einem Zeilenkommentar keine weitere Bedeutung und werden wie andere Zeichen behandelt. Das Kommentarkonzept wurde aus C++ übernommen 4. AnweisungenAnweisungen (statements) sind Deklarationen, Befehle oder auch ein Block von Anweisungen: statement vardecl constdecl assignment if-statement return-statement statement-block Beispiel: statement: door_open(); statement-list: door_open(); opened = TRUE; statement-block: { door_open(); opened = TRUE; } 5. AusdrückeOperatorenEin Operator "berechnet" aus einem oder zwei Werten den Ergebnis-Wert. Ein Vergleichsoperator sowie ein boolscher Operator liefert den Integer-Wert 1, falls der Ausdruck wahr ist, sonst 0. Bit-Operatoren manipulieren Variablen auf Bit-Ebene. Die Operatorenpriorität wurde von C++ übernommen. operator: calc-op cmp-op bool-op bit-op a) Operatoren calc-op: + Addition - Subtraktion * Multiplikation / Division % Restdivision (Modulo) c) Vergleichsoperatoren cmp-op: < kleiner <= kleiner gleich > größer >= größer gleich == Gleichheit != Ungleichheit d) Boolesche Operatoren bool-op: ! nicht && und || oder e) Bitweise Operatoren bit-op: & and | or f) Vorzeichen vorzeichen: + positiv - negativ AusdruckAusdrücke werden mit den oben dargestellten Operatoren wie in C üblich gebildet. Hier werden nur Beispiele von Ausdrücken gezeigt. expression: literal calc-expressoin cmp-expression bool-expression bit-expression a) Ausdruck expression: -x1 + x2 x1 * (x2 + x3) (x2 % 2) * x3 b) Vergleiche (compares) cmp- expression: x1 < x2 x1 == x2 c) Boolesche Ausdrücke bool- expression: x1 && x2 x1 || x2 (x 6. Typen, Variablen und KonstantenEs existieren zwei Arten von Typen: elementare Typen und Klassen. Es können keine weiteren Typen definiert werden, wie in C/C++ üblich. Klassen haben eine direkte Entsprechung im C-Code der Engine. Variablen einer Klasse sind die sogenannten Instanzen. Elementare Typenfloat int string Entsprechen den Typen in C/C++ Weiterhin können int- und string-arrays gebildet werden: VAR INT attribute[MAX_ATTRIBUTES]; VAR STRING names[MAX_NAMES]; Es können nur eindimensionale Arrays erstellt werden. Die einzelnen Elemente der Felder werden wie in C++ gewohnt angesprochen, es können dafür aber nur Konstanten als Index benutzt werden: attribute[1] = 0; Das erste Element beginnt mit dem Index Null. C++ und DAEDALUSFunktionen, Variablen und Konstanten, auf die sowohl im C++-Code als auch in DAEDALUS zugegriffen werden muß, werden mindestens in DAEDALUS deklariert. Dazu dient das Schlüsselwort extern. Variablen und Konstanten werden zusätzlich auch definiert, d.h. ihnen werden Werte zugewiesen. Variablen und KonstantenEine Variablendeklaration muß durch das Schlüsselwort var eingeleitet werden. Dies gilt für jede einzelne Deklaration, nicht, wie in PASCAL, für einen ganzen Block. Auflistungen von Variablen aber (wie in C) möglich: vardecl: var type identifier [,identifier]opt [...]opt; Beispiel: korrekt: var int wert1, wert2, wert3; var string frage, antwort; var int wert; falsch: int value; Eine Konstantendefinition muß durch das Schlüsselwort const eingeleitet werden: constsdef: const type identifier = expression; Beispiel: const type identifier[x] = { expression, expression, expression }; Bezugsrahmen: global, lokalEs existieren zwei verschiedene Bezugsrahmen für Variablen und Konstanten:
Beispiele: var int count; func void Test() { var int x; var int y; } Die variable count ist im Skript global verfügbar. Die Variablen x und y haben wie in C/C++ - den gleichen lokalen Bezugsrahmen: die Funktion Test(). 7. Zuweisungenassignment: identifier = expression; // einfache Zuweisung Beispiel: var int x1; x1 = 40; x1 = x1 / 2; x1 = x1* 3; 8. FunktionenDefinitionFunktionsdefinitionen werden mit dem Schlüsselwort func eingeleitet. func-def: func type identifier ( vardecl1opt , ... , vardecl8opt ) statement-block Beispiel: func int UsingSchild(var int x1, var string s1) { [...] }; ParameterübergabeDie Länge der Parameterliste ist unbegrenzt, sollte allerdings aus Speicherplatzgründen möglichst gering gehalten werden. Parameter werden call-by-value übergeben, liefern also keinen Wert zurück. Arrays sind als Übergabeparameter nicht erlaubt. FunktionsaufrufeFunktionen werden wie in C++ üblich aufgerufen. Also mit ihrem Bezeichner sowie einer zwingend notwendigen Parameterklammer. 9. Klassen, Schablonen und InstanzenKlassenDie Klassendeklarationen beschreiben exakt die Datenstrukturen der Engine. Sie sind also nicht beliebig im Skript erweiterbar, sondern direkt mit der Engine verknüpft. classdecl: class classname (base-classname)opt declaration-block Beispiel: class Creature (Vob) { // attributes var string name; var int hitpoints; var int hp_max; // actions var funcref birth; var funcref death; }; Die Attribute erhalten Standardwerte:
PrototypenMit dem Schlüsselwort prototype ist es möglich, sogenannte Prototypen zu erzeugen, die andere Standardwerte haben: prototype-def: prototype class-identifier identifier statement-block Ein Prototyp kann man als eine "abgeleitete" Klasse ansehen, bei der NUR die Standardwerte geändert wurden. Oder als eine Instanz der Klasse, die nur als Vorlage für weitere Instanzen dient. Die Definition der Standardwerte findet im statement-block satt. Es findet eine Trennung zwischen Klassendeklaration (class), welche die exakte Struktur der Engine-internen Klasse widerspiegelt, und der Klassendefinition (prototype)statt. InstanzenInstanzen von Klassen oder Prototypen stellen deren konkrete Repräsentationen dar. Die Instanz einer Klasse Material ist ein bestimmtes Material mit all seinen Eigenschaften. instance-def: instance class-identifier identifier statement-block instance instance-identifier prototype-identifier statement-block Der statement-block einer Instanz-Definition dient zur Definition der Variablen, die zu der Klasse gehören. Dabei behalten nicht definierte Attribute oder Aktionen ihren Standardwert. Es sind aber auch alle weiteren Anweisungen erlaubt, soweit sie in irgendeiner Weise Sinn machen. Kleines (fiktives) Beispiel für Item/Schild/Holzschild class Item(Vob) { // attributes var int damage; var int attack; var string description; // actions var funcref use; }; prototype SchildProtoType (Item) { damage = 0; attack = 0; descriptions = ""; // actions use = UsingSchild(); }; instance HolzSchild1 (SchildProtoType) { // attributes description = "Ein recht erbärmliches Holzschild"; }; 10. KontrollstrukturenVerzweigung: if-then-elseDie if-Abfrage wurde aus C++ übernommen. Zu beachten sind nur die eingeschränkten Möglichkeiten von Ausdrücken. Außerdem wird immer ein statement-block erwartet. if-statement: if ( expression ) statement-block if ( expression ) statement-block else statement-block falsch: if (x<4) SoundPlay(ID_sound_roar); richtig: if (x<4) { SoundPlay(ID_sound_roar); }; Funktionswert-Rückgabe: returnIn Funktionen, die einen Wert zurückliefern, wird wie in C++ die return-Anweisung verwendet: return-statement: return ( expression ); 11. Dynamische VariablenEinige Variablen werden beim Aufruf einer Funktion dynamisch gesetzt und verweisen dann z.B. in einem Dialog auf die Instanz des NPCs (self) und seinen Gesprächspartner(other). Weiterhin wird es Build-In-Funktionen geben, die einen Zugriff auf andere VOBs ermöglicht. Zur Unterstützung dieses Konzeptes werden einige globale Variablen deklariert und im Spielverlauf dynamisch gesetzt, so daß diese in Funktionen abgefragt werden können. Die Variablen sind momentan folgende: VAR C_NPC self; VAR C_NPC other; VAR C_NPC victim; VAR C_NPC hero; VAR C_NPC item; 12. Wichtige Unterschiede zu C++Hier sollen nur in Stichworten einige Fallstricke aufgezeigt werden, die aus den Unterschieden zu C++ entstehen. Wo C++ auch einzelne Anweisungen erlaubt, muß in DAEDALUS ein Anweisungsblock stehen. Dies betrifft if-Statement. Beispiel: if (x<4) { SoundPlay(); }; |