Next: Die deutsche Library
Up: Der Baum der Erkenntnis:
Previous: Der Baum der Erkenntnis:
Subsections
Elementare Syntax
,,Inform sieht seinen Quellcode als eine Auflistung
von Dingen, die es sich ansehen soll und die durch Strichpunkte
; getrennt sind.'' (DM
, p. 7)
- Byte
- ist die kleinste adressierbare Speichereinheit;
es hat 8 Bits und kann ein Zeichen (Charakter) oder eine Zahl
zwischen 0 und 255 darstellen. Meistens arbeitet man mit der Zusammenfassung
zweier Bytes zu einem Word.
- Word
- hat 16 Bits (im Z-Code, unter Glulx sind es 4
Bytes - 32 Bits) und wird für die Darstellung von Adressen, Zeigern
(Wörterbucheinträgen) oder Zahlen benutzt.
- Zahlen
- (ein Word) können dezimal
(-32768 bis +32767),
hexadezimal ($0 bis
$FFFF) oder binär ($$0
bis $$1111111111111111) angegeben werden.
- Charakter
- ist ein einzelner Buchstabe in einfachem
Hochkomma: 'a'
- String
- ist eine Zeichenkette aus bis zu 4000 Zeichen
in Anführungszeichen. Eine Tilde ~
wird beim Ausgeben in ein Anführungszeichen, ein Caret
^ in eine Zeilenschaltung
umgesetzt. Die Tilde selbst erhält man mit @@126,
das Caret (zu deutsch ,,Dächle'') mit @@94,
den Backslash \
mit @@92, den Klammeraffen selbst mit @@64.
Latin-1-Zeichen sind zulässig, es ist nicht mehr notwendig, deutsche
Sonderzeichen besonders zu behandeln. Ausnahme:
Im name-Property müssen Umlaute
und ß als ae oe ue ss transkribiert werden.
- Wörterbucheinträge (dictionary words)
- haben
neun38 signifikante Zeichen (Achtung: Umlaute und ß zählen als zwei!) und
werden in einfache Hochkommas eingeschlossen: 'Haus'.
Ein Wörterbucheintrag mit Länge 1 muss von einem Character
unterschieden werden durch eine der Schreibweisen "a"
(nur im name-property zulässig,
veraltet, bitte nicht mehr benutzen) oder besser 'a//'.
Intern werden Wörterbucheinträge als Zeiger vom Typ word
verarbeitet; der Zeiger verweist auf den Eintrag der Zeichenkette
im Wörterbuch. Wörterbucheinträge können auch Leerstellen und Satzzeichen
enthalten; solche Einträge werden aber vom Parser bei der Eingabe
nicht erkannt, weil er Leerstellen etc. als Trennzeichen verwendet.
- Aktionen
- erkennt Inform (und der Leser) an zwei
vorangestellten Gattern: ##Take oder
- in eine Anweisung eingeschlossen - an den umschließenden spitzen
Klammern: <take babelfisch>
- Bezeichner
- müssen mit einem Buchstaben beginnen,
können Ziffern und den Unterstrich enthalten und bis zu 32 Zeichen
lang sein.
- Groß- und Kleinschreibung
- sind
irrelevant39 mit zwei Ausnahmen:
- Bestimmte Schlüsselwörter (die Anweisungen) müssen immer klein geschrieben
sein: box break continue do until font (on
off) for give if else jump move...to new_line objectloop print
print_ret remove return rfalse rtrue spaces string style (roman bold
underline reverse fixed) switch default while
- Die Namen von Dateien, die mit include
eingebunden werden sollen, müssen korrekte Groß-/Kleinschreibung
aufweisen, wenn das verwendete Betriebssystem (wie alle vernünftigen
Betriebssysteme) Groß-/Kleinschreibung unterscheidet.
Konstante und Variablen sind nicht typisiert, d.h. sie können jeden
Datentyp (insbesondere auch den Typ Object) enthalten.
Konstante
werden mit dem Schlüsselwort Constant
definiert; bei der Definition kann ein Wert zugewiesen werden:
-
- Constant NAME_DER_KONSTANTE;
Constant NAME_DER_KONSTANTE = AUSDRUCK;
Inform kennt bereits die Konstanten false
(=0) und true (=1); logisch
wahr ist aber auch jeder andere von 0 verschiedene Wert. Außerdem
noch nothing (=0) und null
(=-1). Null steht für ein undefinierte
Aktion oder Property, nothing für ,,kein
Objekt''.
Variable
Globale Variable werden wie Konstante definiert, jedoch mit dem Schlüsselwort
Global. Lokale Variable
können innerhalb von Objekten (als properties, s.u.) und im
Kopf von Routinen definiert werden.
Arrays
Mit dem Schlüsselwort array
können Felder bestimmter Datentypen eingerichtet werden. Je nach der
Elementgröße (Byte oder Word) und nach der verwendeten Adressierung
verwendet Inform vier Arraytypen. Bei der Adressierung ist zu unterscheiden
zwischen ,,einfachen'' Arrays, die bei Länge N von 0 bis N-1 referenziert
werden und Arrays, die zum Speichern von Text gedacht sind und bei
Länge N von 1 bis N adressiert werden, wobei im ,,nullten'' Element
Längeninformationen gespeichert sind zu Beginn vorbelegt mit der Länge
N des Arrays). Allen Arrays ist gemeinsam, dass sie eine bei der Definition
festgelegte und später nicht mehr änderbare Länge haben.
Die Definition eines Array erfolgt nach folgendem Muster:
-
- Array NAME_DES_ARRAY PFEIL DATEN;
im Einzelnen:
- Array
- das Schlüsselwort Array
- NAME_DES_ARRAY
- ein
Bezeichner für den Namen
- PFEIL
- für jeden
der vier Typen unterschiedlich:
- ->
- für ein von 0 bis n-1 adressiertes
Array aus Bytes (byte array)
- ->
- für ein von 0 bis n-1 adressiertes
Array aus Words (2 Bytes lang, s.o.) (word array)
- string
- für ein von 1 bis
n adressiertes Array aus Bytes (string array)
- table
- für ein von 1 bis
n adressiertes Array aus Words (table array)
- DATEN
- entweder eine
einzelne Zahl, die dann die Länge des zu definierenden Array festlegt;
oder eine Liste von Werten (Ausdrücken), mit denen die einzelnen Elemente
des Array vorbelegt werden (die Länge ist dann gleich der Länge dieser
Liste)
oder schließlich ein String, der zeichenweise auf die einzelnen Elemente
des Array verteilt wird (die Länge ist dann gleich der Länge dieses
Strings).
Der Zugriff auf ein Array erfolgt mit NAME_DES_ARRAY->AUSDRUCK
für die Typen Byte und String bzw. mit NAME_DES_ARRAY->AUSDRUCK
für die Typen Word und Table. Die Auswertung des Ausdrucks
muss eine Zahl innerhalb der Arraygrenzen ergeben.
Die name-property
eines Objekts (3.2.7) besteht tatsächlich aus einem
word array, in dem Referenzen auf Wörterbucheinträge abgelegt
sind. Zur Adressierung der name-property
als Array muss ein & vorangestellt werden,
z.B:
-
- objekt.&name->4='Haus';
Operatoren
Zuweisungsoperator ist das einfache Gleichheitszeichen =.
Inform kennt binäre Operatoren für die Grundrechenarten
+ - * / und die modulo-Funktion
%; außerdem bitweises and
& und or |.40 Unäre Operatoren sind das einfache Minuszeichen, das bitweise not~ und die Pre-
und Postfixoperatoren ++
und -.41
Inform kennt die normalen binären Vergleichsoperatoren
gleich ==, ungleich ~=,
größer >, größer oder gleich >=,
kleiner <, kleiner oder gleich <=,
logisches and && und logisches or ||;
außerdem ein unäres logisches not ~~.
Spezielle binäre Vergleichsoperatoren sind:
- ofclass
- testet
das links stehende Objekt auf Zugehörigkeit zur rechts angegebenen
Klasse
- in
- und
- notin
- testen
das links stehende Objekt darauf, ob es child des rechts stehenden
Objekts ist oder nicht
- provides
- testet,
ob das links stehende Objekt die rechts angegebene property
besitzt;
- has
- und
- hasnt
- testen,
ob das links angegebene Objekt das rechts angegebene attribute
besitzt oder nicht.
Eine weitere Besonderheit ist das Schlüsselwort or,
mit dem auf der rechten Seite eines Vergleichsoperators ganze Listen
gebildet werden können:
-
- if (messer in tisch or schrank or schublade) ...
Logische Ausdrücke werden immer von links nach rechts abgearbeitet
und abgebrochen, sobald das Ergebnis feststeht.
Ausgewertet werden Operatoren in der Reihenfolge ihrer Priorität,
ansonsten von links nach rechts:
Der Superclass-Operator :: |
Der Punkt . als Auswahloperator in Objekt.property |
Die Klammern () im Aufruf einer Routine |
.& und .#
als Auswahloperatoren eines property array |
++ und -,
die Prä- und Postfixoperatoren |
Das unäre Minuszeichen - |
Die Pfeile -> und ->
zur Referenzierung eines Arrayelements |
Die mathematischen Punktrechnungsoperatoren *,
/ und %, sowie
die bitweisen Operatoren &, |
und ~ |
Die mathematischen Strichrechnungsoperatoren +
und - |
or für die Auflistung logischer Alternativen |
Alle oben beschriebenen logischen Operatoren und Vergleichsoperatoren
mit Ausnahme von |
&&, || und
~~ |
Der Zuweisungsoperator = |
Das Komma , als Reihungsoperator für verschiedene
aufeinanderfolgende Zuweisungen. |
Objekte und Klassen
Alles, was der Spieler zu sehen bekommt ist ein Objekt: Räume, Gegenstände,
Lebewesen - auch die Spielerfigur selbst.
Klassen sind vordefinierte Muster für mehrere Objekte, die dann die
vordefinierten Eigenschaften ,,ihrer'' Klasse erben.
Alle Objekte, die sich in einem Raum befinden, sind zusammen mit dem
Raum selbst Knoten in einem Baum, dem object tree.
Die Anfangsposition der Objekte in diesem Baum wird normalerweise
bei der Definition der Objekte festgelegt; sie kann sich im Spiel
dynamisch verändern. Ein Objekt ohne Elternobjekt (parent)
ist entweder ein Raum oder gerade nicht im Spiel.
Ein Objektdefinition enthält bzw. kann enthalten:
- den Namen einer Klasse, deren Eigenschaften das Objekt erbt oder das
Schlüsselwort Object
- (optional) einen oder mehrere Pfeile ->,
die anzeigen, dass das Objekt im object tree unterhalb des
zuletzt definierten Objekts steht, das mit einem Pfeil weniger
definiert wurde (Objekte mit gleichviel Pfeilen stehen also auf gleicher
Ebene, sie sind Geschwister, siblings);
- einen Bezeichner als Namen des Objekts;
- (optional) in Anführungszeichen den extended name, d.h. den
Namen, der vom Spiel ausgegeben wird - im Deutschen macht
das nur bei Räumen Sinn, deren Name nicht dekliniert werden muss;
der extended name von Objekten wird in
der Regel aus den Properties adj,
short_name und post
zusammengesetzt.
- (optional und nur, wenn nicht implizit durch Pfeile angegeben) den
Namen des Objekts, das im object tree einen Knoten weiter
oben sitzt, also das Elternobjekt (parent);
- (optional) eine mit dem Schlüsselwort Class
eingeleitete Liste von Klassen, deren Eigenschaften das Objekt erbt;
- (optional) eine mit dem Schlüsselwort with
eingeleitete Liste von (öffentlichen) Eigenschaftsnamen (properties)
und den dazugehörenden Eigenschaften d.h. von Variablen und ihren
Werten; jeweils mit einem abschließenden Komma,
- (optional) eine weitere mit dem Schlüsselwort private
eingeleitete Liste von Eigenschaftsnamen (properties) und
den dazugehörenden Eigenschaften; jeweils mit einem abschließenden
Komma,
- (optional) eine mit dem Schlüsselwort has
eingeleitete Liste von gesetzten oder ungesetzten Attributen;
- einen Strichpunkt zum Schluss.
Die mit class, with, private und has
eingeleiteten Abschnitte können in beliebiger Folge stehen, sie werden
dann durch Kommata getrennt.
Eine Klassendefinition wird begonnen mit dem Schlüsselwort Class
gefolgt vom Namen der Klasse und optional einer Zahl in runden Klammern.
Diese Zahl gibt an, wieviele Instanzen der Klasse
später dynamisch erzeugt werden können. Danach können
- wiederum in beliebiger Reihenfolge - mit class,
with, private und has eingeleitete Abschnitte
folgen.
Die Funktion metaclass()
liefert die Klasse eines Objekts zurück.
Die mit with und private
definierten Eigenschaften sind Variablen, die samt ihren in der Klassendefinition
definierten Werten vererbt werden. Auf sie kann jederzeit zugegriffen
werden, indem die Bezeichner des Objekts und des Properties
mit einem Punkt verknüpft werden: OBJEKTNAME.PROPERTY
. Statt des Objektnamens kann innerhalb der Objektdefinition
selbst auch self verwendet
werden. Für Code in Klassendefinitionen ist das unumgänglich, weil
die Klasse ja nicht weiß, wie ihre Instanz später heißen wird.
Im Normalfall handelt es sich bei diesen Eigenschaften um Routinen.
Sie können auch mit Parametern aufgerufen werden: x
= Topf.Kochen(Apfel); bedeutet, dass man an das Objekt Topf
eine in dessen Property Kochen
definierte Nachricht mit dem Inhalt Apfel
schickt und den Rückgabewert in der Variablen x
ablegt (unterstellt, wir hätten eine sinnvolle Property Kochen
für den Topf definiert, könnte das dann
etwa ein Objekt Kompott
sein).
Werden vordefinierte Eigenschaften der Klasse in der Objektdefinition
nochmals mit Werten belegt, so werden die in der Klasse vordefinierten
Eigenschaften überschrieben, wenn nicht vor ihrer ersten Verwendung
die Eigenschaft als additiv deklariert wurde:
-
- Property additive NAME_DER_NEUEN_PROPERTY;
Neue Common properties lassen sich auf dieselbe Weise definieren:
-
- Property NAME_DER_NEUEN_PROPERTY;
Bei additiven Properties - in der Regel sind es Routinen -
wird zuerst der zuletzt definierte Teil ausgeführt. Gibt
dieser false zurück, wird in der ,,Ahnenreihe''
eine Klasse weiter zurückgegangen, solange bis true
zurückgegeben wird oder die oberste Klasse erreicht ist. Additiv sind
die vordefinierten common Properties after,
before, describe, each_turn, life, name, time_out.
Attribute werden immer additiv vererbt. Dies führt
bei male, female, neuter zu einem kleinen
Problem; siehe 3.2.6.
An den Code der überschriebenen Properties der Superklasse
kommt man mit dem Superclass-Operator
:: heran: Sei z.B. Dampftopf
von der Klasse Topf hergeleitet,
habe aber die Property Kochen
überschrieben, so kann der Dampftopf mit
-
- Dampftopf.Topf::Kochen(Apfel)
den Apfel genauso kochen wie jeder andere Topf auch. Zumeist wird
der Superclass-Operator aber verwendet werden, um innerhalb der Property,
die man gerade überschreibt, den ursprünglichen Code der Klasse aufrufen
zu können.
Traversieren des object tree
Zur Ermittlung der Umgebung eines
Objekts dienen die Funktionen
- parent()
- liefert
das im Baum übergeordnete Objekt (oder nothing,
wenn es keines gibt)
- children()
- liefert
die Zahl der nachgeordneten Elemente (kann auch 0 sein)
- child()
- liefert
das erste nachgeordnete Objekt (oder nothing),
- sibling()
- liefert
das nächste gleichgeordnete Objekt (oder nothing),
- IndirectlyContains(WALD,BLATT)
- prüft,
ob WALD
im object tree irgendwo oberhalb von BLATT
steht; der Rückgabewert ist kein Objekt, sondern vom Typ boolean.
Verändert wird der Baum durch die Befehle
- move
- MEIN_OBJEKT
to NEUER_PARENT;
setzt ein Objekt samt allen seinen Kindern an eine neue Stelle.
- remove
- MEIN_OBJEKT;
nimmt das Objekt samt allen seinen Kindern aus dem Spiel; löscht es
aber nicht, es kann jederzeit mit move
... to wieder eingesetzt werden.
- OBJEKT.PROPERTY(AUSDRUCK1,AUSDRUCK2,...);
- ist
der Aufruf einer in einer property eines Objekts enthaltenen
Routine,
- ROUTINE.call(AUSDRUCK1,AUSDRUCK2,...);
- ist
in gleicher Weise der Aufruf einer selbständigen Routine.
- STRING.print();
- gibt
den String aus und
- STRING.print_to_array(NAME_DES_ARRAYS);
- kopiert
ihn in einen Array.
- KLASSE.remaining()
- gibt
an, wieviele Instanzen einer Klasse noch mit
- KLASSE.create()
- dynamisch
ins Leben gerufen werden können. Damit das überhaupt geht, muss bei
der Klassendefinition eine Zahl größer 0 angegeben worden sein.
- KLASSE.destroy(OBJEKT)
- löscht
das Objekt und gibt den Platz wieder frei; nur mit create
erzeugte Objekte können gelöscht werden!
- KLASSE.recreate(OBJEKT)
- setzt
ein Objekt auf seine Anfangswerte zurück.
- KLASSE.copy(OBJEKT1,OBJEKT2)
- kopiert
alle Eigenschaften der Klasse von Objekt 1 nach Objekt 2.
Schleifen und andere Konstrukte
if und else
-
- if (BEDINGUNG)
ANWEISUNGSBLOCK1
else
ANWEISUNGSBLOCK2
Ist die Bedingung true, wird ANWEISUNGSBLOCK1
ausgeführt, sonst ANWEISUNGSBLOCK2.
Der else-Zweig ist optional, er bezieht
sich immer auf das letzte if. (Abweichende
Konstruktionen sind durch 'Blocken' natürlich möglich.)
Ein ANWEISUNGSBLOCK besteht
entweder aus einer einzelnen, mit Semikolon terminierten Anweisung
oder aus mehreren { in geschweifte Klammern
eingeschlossenen } Anweisungen. Die Bedingung
muss immer in runden Klammern stehen!
switch
-
- switch (AUSDRUCK)
{
KONSTANTE1: ANWEISUNGEN ... ;
KONSTANTE2, KONSTANTE3: ANWEISUNGEN ... ;
KONSTANTE4 TO KONSTANTE5: ANWEISUNGEN ...;
...
default: ANWEISUNGEN ... ;
}
Der Ausdruck wird ausgewertet, entspricht sein Wert einer der Konstanten,
wird der zugehörige Anweisungszweig ausgeführt; sonst der default-Zweig.
Der default-Zweig ist optional. Der auszuwertende
Ausdruck muss in runden Klammern stehen. Die Anweisungen der einzelnen
Zweige müssen nicht 'geblockt' werden.
Schleifen
Eine Schleife bewirkt die wiederholte Ausführung eines Befehls oder
einer Befehlsfolge. Die Schleifenkonstrukte folgen wie if
und switch ebenfalls der C-Syntax (mit
Ausnahme des Inform-spezifischen objectloop).
Jede Schleife kann mit dem Befehl break;
unmittelbar verlassen werden. Der Befehl continue;
springt unmittelbar an den Schleifenanfang und beginnt mit dem nächsten
Durchlauf. Die Schleifenbedingung steht immer in runden Klammern,
der Anweisungsblock ist auch hier entweder eine einzelne, mit
Semikolon abgeschlossene Anweisung oder eine in geschweifte Klammern
eingeschlossene Folge solcher Anweisungen.
while
definiert eine Schleife mit Anfangsbedingung, die ausgeführt wird,
solange die Auswertung der Bedingung true
ergibt:
-
- while(AUSDRUCK) ANWEISUNGSBLOCK
do ... until
definiert eine Schleife mit Endbedingung, die ausgeführt wird, bis
die Auswertung der Bedingung true ergibt:
-
- do ANWEISUNGSBLOCK until (AUSDRUCK);
for
definiert eine Zählschleife (d.h. eine Schleife, bei der die Anzahl
der Wiederholungen durch eine Zählvariable gesteuert wird) nach dem
von C bekannten Muster (Vorsicht, Doppelpunkt statt Strichpunkt!):
-
- for (INITIALISIERUNG : BEDINGUNG : VERÄNDERUNG) ANWEISUNGSBLOCK
- INITIALISIERUNG
- enthält
üblicherweise einen bei Beginn einmalig auszuführenden Zuweisungsbefehl
an eine Variable (Zählvariable), die zur Steuerung der Schleife dient,
z.B. i=0
- BEDINGUNG
- Die for-Schleife
wird ausgeführt, solange die Auswertung der Bedingung true
ergibt. Beispiel: i<= 100
- VERÄNDERUNG
- Eine
Anweisung, mit der die Variable für jeden Durchlauf verändert wird
(wobei man darauf achten sollte, dass auf diese Weise irgendwann die
Abbruchbedingung erfüllt wird ;-). Beispiel: i++
objectloop
definiert eine Schleife über alle im Spiel definierten Objekte oder
über eine Auswahl dieser Objekte:
-
- objectloop (SELEKTIONSAUSDRUCK) ANWEISUNGSBLOCK
Besteht der SELEKTIONSAUSDRUCK
einfach nur aus dem Namen einer Variable, so wird der Anweisungsblock
für jedes Objekt im Spiel einmal ausgeführt, wobei im Anweisungsblock
mit eben dieser Variable auf das aktuelle Objekt zugegriffen werden
kann.
Der Selektionsausdruck kann auch bestehen aus einer Bedingung
mit einem Variablennamen auf der linken Seite. Dann wird der Anweisungsblock
für jedes Objekt, das diese Bedingung erfüllt, einmal ausgeführt;
wiederum referenziert der Variablenname im Anweisungsblock das gerade
aktuelle Objekt.
Beachte: Eine Konstruktion, die auf einer Bedingung
mit in aufbaut und die so gefundenen Objekte
bewegt, sägt den Ast ab, auf dem sie sitzt. Statt:
-
- objectloop (x in player) remove x; ! SO NICHT!
schreibt man also:
-
- while (child(player)) remove child(player);
Anweisungen
Zuweisung mit =
Inform verwendet das einfache Gleichheitszeichen für Zuweisungen,
das doppelte == ist ein Vergleichsoperator.
Der Compiler warnt bei möglichen Verwechslungen, aber man sollte sich
nicht darauf verlassen! Mehrfache Zuweisungen sind möglich:
-
- VARIABLE1 = VARIABLE2 = AUSDRUCK;
Anweisungsfolgen
Jede Anweisung muss mit einem Semikolon ;
abgeschlossen werden. Ausnahme: Die Definition einer Property
eines Objekts wird mit einem Komma abgeschlossen; das letzte Semikolon
vor diesem Komma entfällt. Umgekehrt wird die gesamte Objektdefinition
mit einem Semikolon beendet, vor diesem abschließenden Semikolon braucht
es kein Komma.
Das Komma , ist eine Möglichkeit, eine Folge aus mehreren
Anweisungen zu schreiben: ANWEISUNG1,
ANWEISUNG2; gilt syntaktisch als nur eine Anweisung.
Anweisungsblock
Ein Anweisungsblock besteht entweder aus einer einzelnen Anweisung
oder aus mehreren aufeinanderfolgenden Anweisungen, die in geschweifte
Klammern {} eingeschlossen sind.
Rückgabewert
Eine Anweisung hat einen Rückgabewert; bei Zuweisungen ist das das
Ergebnis.
Der Rückgabewert von Routinen kann mit der Anweisung return
AUSDRUCK;
explizit angegeben werden. Beachte: Bei Ausführung von return
erfolgt auch der Rücksprung aus der Routine.
return; ohne Zusatz bedeutet immer return
true; und sollte der besseren Lesbarkeit wegen so oder abgekürzt
als rtrue; geschrieben werden.
Das Gegenstück dazu ist der Befehl return
false; abgekürzt rfalse;
Wird der Rückgabewert nicht explizit gesetzt, haben selbständig definierte
Routinen immer true; unselbständige
(als property eines Objekts definierte) Routinen immer false
als Rückgabewert.
Routinen (Unterprogramme)
sind Anweisungsfolgen mit bis zu 15 (oder ohne) lokalen Variablen
und einem Namen. Sie werden in eckige Klammern
[ ] eingeschlossen. Im einzelnen besteht eine
Routinendefinition aus folgenden Elementen:
-
- [ NAME VARIABLENLISTE; ANWEISUNGEN ]
- [
- einer eckigen Klammer
zu Beginn
- NAME
- Nur bei selbständig,
d.h. außerhalb eines Objekts definierten Routinen ist der erste nach
der Klammer folgende Bezeichner der Name der Routine (unter dem sie
auch wieder aufgerufen werden kann, s.u.) . Besondere Regeln gelten
für Aktionsroutinen: Für jede (vom Benutzer
neu definierte) Aktion, z.B. ##MachIrgendwas
muss eine (selbständig definierte) Routine mit dem Namen der Aktion
und einem angehängten Sub (hier also: MachIrgendwasSub
) definiert sein.
Ist eine Routine unselbständig, d.h. als property eines Objektes
definiert, ist kein Name anzugeben, die Routine erhält automatisch
den Namen der property und ist mit der Nachricht OBJEKT.PROPERTY
aufrufbar.
- VARIABLENLISTE
- optional,
Bezeichner für bis zu 15 lokale Variable, die entweder mit 0 vorbelegt
sind oder beim Aufruf als Parameter übergeben werden.
- ;
- ein Semikolon schließt den Kopf der
Routine ab (auch wenn weder ein Name noch lokale Variable angegeben
sind, das Semikolon muss geschrieben werden!) und es folgen die
- ANWEISUNGEN
- in beliebiger
Anzahl und Länge, gefolgt von einer abschließenden
- ]
- eckigen Klammer am Ende. (Semikolon
oder Komma danach nicht vergessen.)
Wird der Rückgabewert nicht explizit gesetzt, haben selbständig definierte
Routinen immer true; unselbständige
(als property eines Objekts definierte) Routinen immer false
als Rückgabewert. (Man kann das gar nicht oft
genug sagen!)
Aufruf von Routinen und Funktionen
Routinen werden aufgerufen mit ihrem Namen gefolgt von einer Parameterliste
in runden Klammern. Die Parameterliste kann
leer sein oder bis zu sieben durch Kommata zu trennende Argumente
enthalten. Die Argumente werden der Reihenfolge nach auf die lokalen
Variablen der Routinendefinition abgebildet. Rekursion ist zulässig.
Der Aufruf kann als Funktion innerhalb einer Zuweisung erfolgen, muss
dies aber nicht. Beispiele:
-
- x = ackermann (y, z); ! (Vom Gebrauch wird abgeraten!)42
streik.posten();
Dispatcherroutinen
Ein besonderes Format gilt für die Routinen in den properties
after, before,
life, orders, react_after
und react_before.
Diese Routinen legen fest, wie ein Objekt sich zu verschiedenen Handlungen
(Actions) des Spielers verhält, die mit ihm oder in seiner Nähe stattfinden.
Sie werden gewissermaßen so geschrieben, als wäre ein switch(Action)
vorangestellt:
-
- [OPTIONALE LISTE LOKALER VARIABLER;
AKTION1: ANWEISUNGEN ... ;
AKTION2, AKTION3: ANWEISUNGEN ...;
...
default: ANWEISUNGEN ... ;
],
Auch hier müssen die Anweisungen in den einzelnen Zweigen nicht 'geblockt'
werden. Die Aktionen werden ohne einleitendes ##
geschrieben.
Der richtige Ausdruck
print und print_ret
Der grundlegende Ausgabebefehl ist print
gefolgt von einer durch Kommata getrennten Liste aus Zeichenketten
und/oder Objekten oder Ausdrücken mit vorangestellter Regel. Die Regel
- in runden Klammern vor das Objekt oder den
Ausdruck gestellt - legt fest, ob z.B. die Adresse eines ihr folgenden
Objekts oder aber sein Name im Nominativ Singular mit vorangestelltem
Artikel ausgegeben werden soll:
-
- print (GDer) Objektname, " hat als Objekt intern die Nummer ", Objektname;
Für die häufig benötigte Folge print ...
; new_line; return true; gibt es die abgekürzte Schreibweise print_ret
... ; oder - noch kürzer und wenn die zu druckenden Daten
mit einem String anfangen - "...";
d.h. einfach der zu Beginn stehende String, optional wie print
gefolgt von der Liste der weiter zu druckenden Daten. Die meisten
auf den ersten Blick unmotiviert herumstehenden Zeichenketten eines
Inform-Programms sind bei näherem Hinsehen solcherart abgekürzte print_ret-Befehle.
Im auszugebenden String haben zwei Zeichen besondere Bedeutung: das
Caret ^
wird als Zeilenumbruch und die Tilde
~ als Anführungszeichen
ausgegeben.
- (number) AUSDRUCK
- Das
Ergebnis des Ausdrucks als ausgeschriebene Zahl
- (char) AUSDRUCK
- Das
Ergebnis des Ausdrucks als Zeichen
- (string) ADRESSE
- Der
String an dieser Adresse
- (address) ADRESSE
- Der
Wörterbucheintrag mit dieser Adresse
- (name) OBJEKT
- Der
short_name des Objekts
- (ROUTINE) AUSDRUCK
- Das
Ergebnis der mit Ausdruck als Parameter aufgerufenen Routine.
Nicht verwechseln mit ROUTINE(AUSDRUCK),
das zwar den selben Text, aber obendrein noch den Rückgabewert der
Routine (meist 1, entsprechend true) ausgibt.
Die deutsche Library enthält eine ganze Reihe solcher Regeln für die
grammatisch wohlgeformte Ausgabe der Namen von Objekten. Dazu später
3.2.5.
- new_line;
- schaltet
eine neue Zeile
- spaces AUSDRUCK;
- gibt
soviele Leerzeichen aus, wie der Ausdruck angibt
- box
- gefolgt von einem oder
mehreren Strings erzeugt eine gesonderte Ausgabe dieser Strings (zentriert
in einem invertierten Fenster über dem normalen Text )
- font off;
- schaltet vom
normalen Ausgabefont auf einen nicht proportionalen Font um
- font on;
- schaltet die (normale) Proportionalschrift
wieder ein (falls vom Interpreter unterstützt!)
- style bold;
- schaltet auf
Fettschrift
- style underline;
- schaltet Unterstreichung
ein
- style reverse;
- schaltet invertiertes
Video ein
- style roman;
- ist normale Schrift ohne
Auszeichnung.
- string N ''EIN_HÄUFIG_GEBRAUCHTER_STRING''
- Mit
Werten von 0 bis 31 für N kann so eines von 32 Stringmakros belegt
werden. (Glulx: 64)
- @00 ... @31
- gibt
eine solches Makro innerhalb eines Strings wieder aus.
- inversion;
- gibt die
Versionsnummer des Compilers aus.
- Release AUSDRUCK;
- definiert
die 'laufende Nummer' des Spiels (üblicherweise 1 für die erste veröffentlichte
Fassung etc.)
- Serial "DATUM";
- definiert
die Seriennummer; Datum ist eine sechsstellige Zahl, default
der Tag der letzten Änderung der Quelldatei.
- Statusline score;
- (Voreinstellung)
und
- Statusline time;
- ändern
das Aussehen der Statuszeile.
- quit;
- Beendet das Programm.
Sofort.
- .MARKE;
- Ein
Bezeichner mit vorangestelltem Punkt markiert eine Stelle des Programms
als Ziel für (geflüstert) duweißtschonwas. Die Marke ist lokal
zu der Routine, in der sie steht.
- jump MARKE;
- (noch
leiser geflüstert:) duweißtschonwas ...43
- save MARKE;
- Speichert
den kompletten Programmstatus und springt dann zur Marke, wenn
- restore MARKE;
- erfolgreich
ausgeführt wird. Im Normalfall braucht man das nicht, weil das Abspeichern
und Laden von Spielständen von der Library erledigt wird, der Spieler
muss nur die Aktionen ##Save und ##Restore auslösen.
- read TEXTPUFFER N;
- und
- read TEXTPUFFER PARSERPUFFER;
- lesen
die Eingabe zeichenweise in den TEXTPUFFER,
einen byte array der Länge N
(TEXTPUFFER->0
enthält N; TEXTPUFFER->1
die Zahl der eingelesenen Zeichen, die ab TEXTPUFFER->2
enthalten sind); die zweite Variante trennt den eingelesenen Text
bereits in Wörterbucheinträge; PARSERPUFFER->1
enthält die Zahl der gelesenen Worte, deren Wörterbucheinträge (oder
0, wenn das Wort nicht bekannt war) stehen
in PARSERPUFFER->3,
PARSERPUFFER->7,
... , PARSERPUFFER->2*i-1
...
Compileranweisungen
- message ''TEXT_DER_NACHRICHT''
- gibt
während des Übersetzens eine Nachricht aus
- System_file;
- gibt
für diese Datei keine Compilerwarnungen aus und ermöglicht das ,,Ausblenden''
einzelner Unterprogrammdefinitionen einer Bibliotheksdatei mit dem
Befehl:
- Replace NAME_EINER_BIBLIOTHEKSROUTINE;
- Diese
Direktive muss vor dem Einbinden der Bibliothek angegeben werden und
bewirkt, dass eben diese Routine der Bibliothek nicht mit eingebunden
wird, so dass sie nachher noch mit eigenem Code definiert werden kann.
- Ifdef BEZEICHNER; Ifndef BEZEICHNER; Iftrue AUSDRUCK; Iffalse AUSDRUCK;
- beginnen
einen Abschnitt, der nur dann kompiliert werden soll, wenn die Bedingung
zutrifft;
- Ifnot;
- (optional) beendet
diesen Abschnitt und beginnt einen alternativen Abschnitt, der nur
dann kompiliert wird, wenn die vorherige Bedingung nicht zutrifft;
- Endif;
- beendet die bedingte
Kompilierung.
- Default BEZEICHNER AUSDRUCK;
- definiert
eine Konstante mit dem Namen Bezeichner (und optional mit dem Wert
des Ausdrucks), aber nur dann, wenn es sie nicht schon gibt.
- Stub BEZEICHNER ANZAHL;
- definiert
in ähnlicher Weise einen Unterprogrammkopf mit der in Anzahl angegebenen
Zahl lokaler Variabler (maximal 3), falls es eine Routine mit diesem
Namen noch nicht gibt.
- Include "QUELLDATEI";
- bindet
eine Datei aus dem Bibliotheksverzeichnis ein; die Endungen .h und
.inf können weggelassen werden.
- Include ">QUELLDATEI";
- bindet
eine Datei aus dem aktuellen Verzeichnis ein.
- Link "MODULDATEI";
- bindet
ein vorkompiliertes Modul ein (das hat schon lange keiner mehr gemacht;
es diente ursprünglich zur Zeitersparnis bei der Kompilierung langer
Spiele, aber heute sind die Computer so schnell, dass es den Aufwand
nicht lohnt).
- Abbreviate "STRING1" "STRING2" ...;
- gibt
bis zu 64 Strings an, die durch Abkürzungen ersetzt
werden sollen. Benötigt Compilerschalter -e.
Die besten Abkürzungen lassen sich mit -u
ermitteln.
ICL-Befehle
Der Compiler wird normalerweise nach folgendem Muster aufgerufen:
-
- inform ICL_BEFEHLE EINGABEDATEI AUSGABEDATEI
Die Angabe einer Ausgabedatei ist optional. ICL
steht für Inform Command Language und umfasst folgende Möglichkeiten:
- -SWITCHES
- Eine
mit einem einfachen Strich eingeleitete Folge von Compilerschaltern
(siehe nächsten Abschnitt)
- (DATEINAME)
- In
runden Klammern der Name einer Datei, die ihrerseits ICL-Kommandos
(eines pro Zeile) enthält
- compile EINGABEDATEI AUSGABEDATEI
- Die
Anweisung, eine weitere Datei zu kompilieren (Ausgabedatei optional).
- +include_path=VERZEICHNIS,VERZEICHNIS...
- Die
Anweisung, Bibliotheksdateien aus einem bestimmten Verzeichnis zu
laden
- +module_path=VERZEICHNIS,VERZEICHNIS...
- Die
Anweisung, vorkompilierte Module aus einem bestimmten Verzeichnis
zu laden.
In gleicher Weise können der source_path
für Quelldateien, der code_path (für das
Kompilat) und auch der icl_path für weitere
Kommandodateien angegeben werden.
- +language_name=german
- damit
die deutschen Varianten der Bibliotheksdateien geladen werden
In gleicher Weise können der transcript_name
für die mit -t erzeugte Textdatei und der debugging_name
für die mit -k zu schreibende Debuggerinformation verändert werden.
$large, $huge und $small
sind drei verschiedene Speichermodelle je
nach der Größe des zu erstellenden Spiels; $large
ist voreingestellt. Angeblich soll mit $listmehr
über die Details des jeweiligen Speichermodells zu erfahren sein,
aber meine Version des Compilers reagiert nicht darauf. So kann ich
auch nicht mit $NAME=WERT
einzelne dieser Einstellungen ändern.
Compilerschalter
Es gibt zwei Arten von Compilerschaltern: solche, die eingeschaltet
(-a) oder mit der Tilde ausgeschaltet (-~a)
werden (markiert mit ein/aus) und solche, an die eine Zahl angehängt
werden muss (markiert mit *). Die Schalter werden ohne Punkt und
Komma einfach hintereinandergesetzt, z.B. bedeutet -a~bcd2,
dass a und c eingeschaltet, b ausgeschaltet und d auf 2 gesetzt wird.
Innerhalb der zu übersetzenden Datei können ebenfalls Compilerschalter
mit dem Befehl switches
LISTE_VERSCHIEDENER_SCHALTER;
gesetzt werden.
- -d*
- (Für englische Schreibmaschinisten) automatisches Eliminieren
doppelter Leerzeichen: 0 - niemals, 1 - nach Punkten, 2 - nach Punkten,
Frage- und Ausrufungszeichen
- -e ein/aus
- Deklarierte Abkürzungen (abbreviations)
benutzen
- -g*
- Trace für Unterprogrammaufrufe: 0 - keine, 1 - außerhalb von
System files, 2 - alle
- -i ein/aus
- In der Datei gesetzte Switches ignorieren
- -k ein/aus
- Ausgabe der Informationen für das Debuggen mit Infix;
schaltet auch -D automatisch ein.
- -r ein/aus
- Der gesamte Text des Spiels wird in eine Textdatei geschrieben
- -v*
- 3 bis 8 um Spieldateien im Format .z3 bis .z8 zu erzeugen
(default 5)
- -C*
- 0 bis 9 für den Zeichensatz der Quelldatei;
0 bedeutet ASCII, 1 bis 9 bedeuten ISO 8859-1 bis -9 (default 1)
- -D ein/aus
- die Debugging-Verben werden mit übersetzt
- -F*
- 0 or 1 temporäre Dateien werden angelegt, um Speicherplatz
zu sparen
- -G ein/aus
- (nur beim Biplattform-Compiler): übersetzt nicht ins
Z-Code-Format, sondern ins Glulx-Format. Das wird hier nicht weiter
beschrieben.
- -M ein/aus
- als Modul kompilierten
- -S ein/aus
- Strenge Fehlerprüfung zur Laufzeit (schließt automatisch
-D mit ein)
- -U ein/aus
- vorkompilierte Bibliotheksmodule einbinden
- -X ein/aus
- den Infix Debugger einbinden
- -a ein/aus
- Trace der Assemblerbefehle (ohne Hexdump)
- -c ein/aus
- ausführlichere Fehlermeldungen
- -f ein/aus
- Ermittelt die tatsächliche Einsparung bei der Verwendung
von Abkürzungen
- -h* ein/1/2
- Hilfe zur Compilerbenutzung
- -j ein/aus
- Konstruierte Objekte auflisten
- -l ein/aus
- Alle übersetzten Anweisungen auflisten
- -m ein/aus
- Speicherbenutzung angeben
- -n ein/aus
- Nummern der properties, attributes und actions
angeben
- -o ein/aus
- Offsetadressen ausgeben
- -p ein/aus
- Compilerfortschritt prozentual anzeigen
- -q ein/aus
- Obsolete Befehle nicht monieren
- -s ein/aus
- Statistik ausgeben
- -t ein/aus
- Trace der Assemblerbefehle (mit Hexdump)
- -u ein/aus
- mögliche Abkürzungen ermitteln (kann länger dauern)
- -w ein/aus
- Warnungen aus
- -x ein/aus
- Je ein Gatter # für 100 übersetzte Zeilen ausgeben
- -y ein/aus
- Trace der Linkerinformationen
- -z ein/aus
- Speicheraufteilung der Z-Maschine
angeben
- -E*
- Fehlermeldungen im Stil von Acorn
(0), Microsoft (1) oder Mac (2)
Footnotes
- ...
neun38
- Auch in Glulx sind es - aus Kompatibilitätsgründen mit der ,,alten''
Inform-Library - nicht mehr als neun.
- ...
irrelevant39
- Ich verwende an vielen Stellen CamelCase, weil es die Lesbarkeit verbessert.
- ... COLOR="#0000ff">|.40
- Bitweise heißt, dass jedes einzelne Bit zweier (bei and und
or) bzw. eines (bei not) Wertes mit dem entsprechenden
Operator verknüpft wird: Sei x=$$0010110;
dann ergibt ~x den Wert
$$1101001. Anders das logische
not: ~~x
ergibt den Wert false (jeder von 0
verschiedene Wert ist true).
- ... COLOR="#0000ff">-.41
- Die Pre- und Postfixoperatoren addieren (++)
bzw. subtrahieren (-) jeweils 1 zum/vom
Wert einer Variablen und weisen der Variablen das Ergebnis wieder
zu. (Kürzer: Sie inkrementieren bzw. dekrementieren die Variable um
den Wert 1.) Dies geschieht entweder vor der Auswertung des gesamten
Ausdrucks (wenn der Operator als Prefix vor der Variablen steht)
oder danach (wenn er als Postfix nach der Variablen steht).
x=i++; weist erst x
den Wert von i zu und erhöht danach i
um 1. y=-i;
vermindert zuerst i um 1
und weist danach y den neuen Wert zu.
- ...(Vom Gebrauch wird abgeraten!)42
- Die Ackermann-Funktion (in Inform nicht vordefiniert!) ist ein Beispiel
für eine zweifach rekursive Funktion. Ihr bekanntester ,,Seiteneffekt''
ist der enorme Verbrauch an Systemressourcen für vergleichsweise kleine
Werte von x und y.
- ...43
- Hier geht es um die Dunkle Seite der Programmierkunst: Die
Marke ist ein Sprungziel, zu dem mit dem Befehl jump
gesprungen werden kann. Die Verwendung von Sprungbefehlen führt in
etwa 1% der Fälle zu eleganten Lösungen und in den anderen 99% zu
Komplikationen in der Form von schwer lesbarem Code bis hin zu völliger
Verwirrung. Wenn du also nicht wußtest, was das Flüstern sollte, bist
du jetzt gewarnt.
Next: Die deutsche Library
Up: Der Baum der Erkenntnis:
Previous: Der Baum der Erkenntnis:
Frank Borger
2003-05-02