________________________________________________________________________
| ||||||
15. Besonderheiten des Parsers | ||||||
15.1. Wie arbeitet der Parser?Der Reihe nach macht der Parser folgendes:
Tritt in einem der Blöcke ein Fehler auf, so wird die Analyse abgebrochen. Wird die Textanalyse fehlerfrei abgeschlossen, dann wird der Befehl ausgeführt. Beim Durchlaufen des Parsers können natürlich Fehler aufteten. Die Variable Fehler enthält die Fehlerkennung und gibt Aufschluß über eventuell aufge- tretene Fehler:
Die ausgegebene Fehlermeldung ist der Standardtext mit der Fehlernummer. Eine Besonderheit gibt es beim Reden mit anderen Personen. Dort wird ein eventuell auftretender Fehler auf der Flagge zFehler abgelegt. Ist einmal ein Akteur angesprochen, so wird Fehler auf zFehler umgeleitet, so daß der Spielzug auf jeden Fall ausgeführt wird. In BefAusf des Akteurs sollte dann berücksichtigt werden, ob der Befehl an den Akteur korrekt und durchführbar war. Es gilt:
Da der Parser den Spieler in der Ich-Form anredet, könnte man den Akteur mit der Anweisung [Text 0 zFehler] die passende Fehlermeldung auch sagen lassen.<&P> | ||||||
15.2. Der Vor- und Nach-ParserDie Aktionen Vorparser und Nachparser dienen dazu, dem Autor die Möglichkeit zu geben, in die Textanalyse einzugreifen. Im Vorparser ist noch kein Ergebnis der Textanalyse bekannt. Hier können globale Veränderungen der Sichtbarkeit und Erreichbarkeit definiert werden, damit sie nicht ständig in SichtUndRw abgefragt werden müssen. Im Nachparser können die soeben gefundenen Ergebnisse nachträglich modifiziert werden. Automatisch werden im Postparser von TAG z.B. nehmen in herausnehmen umgewandelt, wenn sich das Objekt in einem anderen befindet oder aObj auf das benutze Fahrzeug gesetzt, wenn der Befehl gehen ist. Ähnliche Modifikationen sind hier denkbar, ein Beispiel ist Kap. 9.5, wo die Richtungen nachträglich angepaßt werden, wenn die Landschaft gespiegelt ist. (Eigentlich könnten die Änderungen des Nachparsers auch in der Aktion Vorher vorgenommen werden, aber es ist schöner, die Trennung von Parser und Spielzug auch in diesen Änderungen zu beachten.) | ||||||
15.3. Spezielle Anweisungen zum Parsen von ObjektenDie normale Parser-Routine von TAG versucht, eine Wortkette anhand des zu jedem Objekt angegebenen Vokabulars zu finden. Diese Methode reicht für die meisten Fälle aus. Manchmal möchte man aber spezielle Dinge beim Parsen berücksichtigen. Nehmen wir einmal an, es gibt eine Schalttafel, an der sich viele Knöpfe befinden. Diese Knöpfe haben verschiedene Farben. Das einfachste wäre nun, eine Objektklasse Knopf einzuführen, zu dem dann die Knöpfe gehören. Das erzeugt aber sehr viele Objekte, wenn es viele Knöpfe gibt. Man kann die Knöpfe aber als ein einziges Objekt betrachten:
Flagge Knopffarbe
Konst rot
Konst gelb
Konst grün
Obj Knöpfe
Name 'Schalttafel mit vielen Knöpfen' p 0
Vor 'schalt' 'elektro'
Subst 'knöpfe' p 'knopf' m 'tafel' f 'pult' n
Ort Zentrale
Attr Fest
Besch 'Auf der Schalttafel befinden sich Knöpfe in allen
Farben, deren Bedeutung dir verborgen bleibt. Ein
grüner, ein gelber und ein roter Knopf stechen aus
dem Gewirr von Knöpfen hervor.'
VorAusf
(untersuchen) Jenach Knopffarbe
(rot) Stop 'Auf dem roten Knopf steht
eine große Null.'
(gelb) Stop 'Der gelben Knopf ist mit
dem Symbol eines Bitzes
gekennzeichnet.'
(grün) Stop 'Auf dem grünen Knopf steht eine
große Eins.'
Ende
(drücken) Jenach Knopffarbe
(rot) Bed (Motor an)
'Klick!'
ObjZust Motor aus
Stop 'Das brummende Geräusch
verstummt.'
(gelb) Bed (Motor an)
'Klick!'
Stop 'Ein greller Blitz zuckt
draußen.'
(grün) Bed (Motor aus)
'Klick!'
ObjZust Motor an
Stop 'Klick! Ein brummendes Geräusch
setzt ein, aber du kannst nicht
genau sagen, woher es kommt.'
(sonst) Stop 'Du drückst ein wenig auf den
Knöpfen herum, aber es passiert
(zum Glück) nichts Aufregendes.'
Ende
EndeAusf
In den meisten Fällen werden die Knöpfe als ein Block behandelt. Nur in seltenen Fällen wird zwischen dem roten, grünen und gelben unterschieden, nämlich dann, wenn es konkrete Handlungen ausgeführt werden sollen. Die Farbe des Knopfes steht auf der Flagge Knopffarbe. Wo aber kommt dieser Wert her? Es gibt eine Routine, die ObjParser heißt und die aufgerufen wird, bevor die übliche Parserroutine zum Zuge kommt. Normal ist sie nicht definiert, aber für den Fall der Knöpfe könnte man folgendes programmieren:
Flagge Aux
Aktion ObjParser
Ausf
| Knöpfe?
LeseArt m Aux
LeseAdj 'rot' m Aux
Wenn (Aux) dann
Sei Knopffarbe rot
Sei aObj Knöpfe
sonst
LeseAdj 'gelb' m Aux
Wenn (Aux) dann
Sei Knopffarbe gelb
Sei aObj Knöpfe
sonst
LeseAdj 'grün' m Aux
Wenn (Aux) dann
Sei Knopffarbe grün
Sei aObj Knöpfe
Ende
Ende
Ende
Wenn (aObj = Knöpfe) dann
Wenn /(Wort = 'knopf') dann
Sei aObj 0
sonst
NächstesWort
Ende
Ende
EndeAusf
In dieser Routine tauchen viele unbekannte Variablen und Anweisungen auf. Wort ist das momentan untersuchte Wort. Nehmen wir einmal an, daß der eigegebene Satz lautet "Drücke den roten Knopf". Dann teilt der Parser den Satz auf in die vier Wörter
"drücke" "den" "roten" "knopf"
Er stellt das Verb 'drücke' fest und versucht dann, ab dem zweiten Wort ein Objekt zu lesen. Die Variable Wort steht also auf dem Wert 'den', wenn die Aktion ObjParser aufgerufen wird. In dieser Aktion wird dann zunächst versucht, einen männlichen Artikel zu lesen. Das momentane Wort ist den, und wenn die Aktion aufgerufen wurde, um einen Akkusativ zu lesen, so wird Aux gesetzt und der Wortzähler ein Wort weiter gesetzt. Er steht jetzt auf 'roten'. Wäre das zweite Wort ein Artikel mit einem anderen Geschlecht oder in einem falschen Fall gewesen, so wäre Aux Null, und der Wortzähler wäre immer noch auf dem zweiten Wort. (Anmerkung: die allgemeine Aktion zum Parsen von Substantiven und damit auch die Aktion ObjParser werden unter Umständen mehrmals für verschiedene Fälle aufgerufen. Der Fall wird immer intern mitberücksichtigt, ohne daß er für den Spieler sichtbar ist. Die Anweisungen LeseAdj und LeseArt benutzen ihn dann. Ob mit LeseArt ein bestimmter, ein unbestimmter oder gar kein Artikel gefunden wurde, bestimmt auch die Endung in nachfolgenden Adjektiven: (ein) groß-es Glas, das groß-e Glas.) So, Wort ist jetzt 'roten'. Mit der Anweisung LeseAdj, wird jetzt versucht, ein Adjektiv mit dem Stamm 'rot' zu lesen. Das Wort ist 'roten', also das passende Adjektiv für 'rot' mit der Endung für Akkusative mit bestimmtem Artikel (der ja bereits gelesen wurde). Aux wird gesetzt, und das Wort wird eins weiter gerückt, es ist jetzt 'knopf'. Wenn Aux gesetzt ist, so wird jetzt eine Flagge Knopffarbe auf eins gesetzt, um zu signalisieren, daß der rote Knopf konkret angesprochen wurde, was sich in den Befehlen untersuchen und drücken auswirkt. Die Ergebnisvariable des ObjParsers, aObj, wird auf das Knopf-Objekt gesetzt. Die anderen Adjektive werden nicht mehr überprüft, da 'rot' bereits gefunden wurde. Zum Schluß wird, wenn ein Adjektiv gefunden wurde, überprüft, ob das nächste Wort 'knopf' ist, um sicherzugehen, daß es sich nicht um ein anderes rotes Objekt handelt, etwa um 'den roten Ball'. Ist auch 'knopf' gefunden, so wird der Wortzähler eins weitergerückt, in unserem Fall auf ein leeres Wort. Damit sind wir am Ende der ObjParser-Routine. aObj ist mit den Knöpfen belegt, es ist also ein Objekt gefunden worden. Zusätzlich ist aber auch noch die Information, um welchen Knopf es sich genau handelt, auf der Flagge Knopffarbe abgelegt worden. Wäre jetzt das letzte Wort nicht 'knopf' gewesen, so wäre aObj wieder gelöscht worden. ObjParser wäre ohne Ergebnis beendet worden, und der Parser hätte auf herkömmliche Weise versucht, das Objekt zu bestimmen. Dabei kann es passieren, daß 'gelb' z.B. nicht als Adjektiv verstanden wird, wenn es kein weiteres gelbes Objekt im Spiel gibt. Um diesen unschönen Patzer zu umgehen, fügen wir die Zeile
Adj 'rot' 'gelb' 'grün'
zu unserer Objektdefinition hinzu. Auf diese Weise wird auch 'gelber roter Knopf' vom Parser abgefangen: der Knopf wird erkannt, die Farbe nicht. Wem das jetzt alles zu kompliziert ist, der geht am besten die Routine einmal mit verschiedenen Wortkombinationen durch: 'roten knopf', 'roten apfel', 'den gelben knopf' usw. Es funktioniert eigentlich ganz schematisch. | ||||||
15.4. Parsen von Objekten, die Zahlen enthaltenManchmal unterscheiden sich aber Objekte nicht durch verschiedene Attribute wie die Farben im vorherigen Beispiel, sondern sind durchnumeriert. Denken wir zum Beispiel an die Schließfächer in der Bahnhofshalle:
Integer Fach_nr
Obj Schließfächer
Name 'Schließfächer' p
vor 'schließ'
subst 'fächer' p
Ort Bahnhofshalle
Zust geschlossen
Attr Fest Behälter
Besch 'Die Schließfächer sind in schier unendlichen Reihen
übereinander und nebeneinander angeordnet. Die Fächer
1000 bis 1499 sind links, die Fächer 1500 bis 1999
rechts.'
VorAusf
(öffnen) Bed /(Fach_nr = 0)
'Sage mir genau, welches der Fächer ich
öffnen soll.'
(...)
(global) Bed (Fach_nr > 999) und
(Fach_nr < 2000) oder (Fach_nr = 0)
'Die Fächer sind von 1000 bis 1999
durch[-]numeriert. Ein Fach [Fach_nr]
gibt es nicht.'
EndeAusf
Auch hier benutzt man wieder die Routine Objparser:
Flagge Aux
Aktion Objparser
Ausf
| Schließfach?
LeseArt n Aux
Wenn (Wort = 'fach') oder (Wort = 'schliessfach') dann
NächstesWort
Wenn (Wort = 'nummer') oder (Wort = 'nr') dann
NächstesWort
Ende
Sei aObj Schließfächer
LeseZahl Fach_nr Aux
Ende
EndeAusf
Diese Routine sieht etwas übersichtlicher aus. Zunächst wird wieder ein Artikel gelesen, damit man das Objekt auch mit Artikel eingeben kann. Er kann aber auch weggelassen werden. Aux wird hier nicht benutzt, der Aufruf soll nur den Wortzähler weiterbewegen. Dann wird geprüft, ob das momentane Wort 'fach' oder 'schließfach' ist. Wenn ja, dann kann als Zusatz 'nr' oder 'nummer' angegeben werden, in dem Fall wird das nächste Wort betrachtet. Dann wird mit der Anweisung LeseZahl eine Zahl aus dem momentanen Wort gelesen. Diese Zahl ist vom Typ Integer. Zahlen bis zwanzig können auch als Zahlwörter angegeben werden. Wie gehabt wird der Wortzähler weitergezählt, wenn eine Zahl gefunden wurde. Die Flagge Aux gibt an, ob eine Zahl gefunden wurde. Diese Flagge wird aber hier nicht betrachtet, da es das Fach Nummer 0 nicht gibt. (Vereinfachend wird bei 'fach 0' die Zahl als nicht angegeben betrachtet.) Ein weiterer Schwachpunkt ist, daß 'fach nummer' ohne Zahlenangabe verstanden wird. Das könnte man umgehen, indem man die Aktion abändert:
Flagge Aux
Flagge Nr_definiert
Aktion Objparser
Ausf
| Schließfach?
Lösche Nr_definiert
LeseArt n Aux
Wenn (Wort = 'fach') oder (Wort = 'schliessfach') dann
NächstesWort
Wenn (Wort = 'nummer') oder (Wort = 'nr') dann
NächstesWort
Setze Nr_definiert
Ende
LeseZahl Fach_nr Aux
Wenn (aux) und (nr_definiert) dann
Sei Fehler 50
Text 'Du mußt mir schon sagen, welche Nummer das
gesuchte Schließfach hat!'
sonst
Sei aObj Schließfächer
Ende
Ende
ErstesWort
EndeAusf
In dem Fall wird ein benutzerdefinierter Fehler ausgegeben. Die Fehlernummer muß höher als 40 sein, da die Nummern bis vierzig für Fehler schon vergeben sind. Die fehlermeldung muß dann natürlich auch vor Ort ausgegeben werden. Man hätte hier aber auch einfach den Fehler auf eine bereits benutzte Zahl setzen können, dann wäre die passende Meldung automatisch ausgegeben worden. Im Allgemeinen sollte man aber in ObjParser keinen Text ausgeben. Neben LeseZahl gibt es eine weitere Routine zum Lesen von Nummern, LeseNummer. Eine Nummer ist eine Zahlenkette, die z.B. mit Binde- oder Schrägstrichen getrennt sein können und die sich über mehrere Würter erstrecken kann. Diese Routine wird auch vom Parser automatisch aufgerufen. So werden folgende Eingaben ohne Definition von ObjParser vom Parser verstanden:
> WÄHLE 089/99-43-21
Nummer = 89994321, Nullen = 1
> WÄHLE 1-1-0
Nummer = 110, Nullen = 0
Nullen ist dabei eine Angabe darüber, wieviele Nullen vor der eigentlichen Zahl kommen. Ähnlich wie bei den Schließfächern könnte man dann auch
> LIES AKTE 99-2431/1
Nummer = 9924311, Nullen = 0
mit ObjParser bewerkstelligen. Man muß nur aufpassen, daß die Zahl nicht größer als 231 wird, das sind ca. 2 Millarden. Telefonnummern bleiben also besser neunstellig. Diese Zahlen können auch negativ werden. Die Routine ObjParser wird zum Abfangen aller Objekte mit Sonderregelungen benutzt. Die Abfragen werden dann hintereinander durchgeführt. Ist in einem Block kein Objekt gefunden worden (aber z.B. ein Artikel), so muß der Anfangszustand wiederhergestellt werden. Dies passiert mit ErstesWort. (Dieses Zurück- setzen des Wortzählers geschieht automatisch, wenn ObjParser mit aObj = 0 verlassen wird.) | ||||||
15.5. Parsen von Objekten, die komplizierte Kennungen enthaltenNeben den Objekten, die numeriert sind, kann man sich auch Objekte vorstellen, die eine andere Kennung haben. Zum Beispiel ein Schachbrett mit den 64 Feldern A1 bis H8. Wenn man die Figuren versetzen will, will man die Felder mit ihrem Namen ansprechen:
Flagge Feld_nr
Flagge Aux
Aktion ObjParser
Ausf
| Schachfelder?
LeseArt n Aux
Wenn (Wort = 'feld') dann
NächstesWort
Sei aObj Schachbrett
Ende
Wenn (Wortlänge = 2) dann
Lösche Feld_Nr
Wenn /(Wort.1 < "a") und /(Wort.1 > "h") dann
Wenn /(Wort.2 < "1") und /(Wort.2 > "8") dann
Sei Feld_nr Wort.1
Dekr Feld_nr "a"
Mult Feld_nr 8
Sei Aux Wort.2
Dekr Aux "0"
Inkr Feld_nr Aux
Sei aObj Schachbrett
Ende
Ende
Ende
EndeAusf
In diesem Fall werden also die Buchstaben bzw. Zeichen des Wortes einzeln angesprochen. dazu werden die folgenden Notationen benutzt:
In unserem Beispiel werden alle zweistelligen Angaben, deren erstes Zeichen von "a" bis "h" und deren zweites von "1" bis "8" geht als Koordinate des Schachbretts interpretiert und als Feldnummer von 1 bis 64 auf die Flagge Feld_nr gelegt. (Das Feld fängt hier bei eins an, damit (Feld_nr = 0) bedeutet, daß kein Feld im speziellen angegeben wurde.) | ||||||
15.6. Spezielle Anweisungen zum Parsen von VerbenEs gibt auch eine Aktion VerbParser, mit der man der normalen Verbbestimmung mit den zu jedem Befehl bestimmten Befehlen vorgreifen kann. Dies ist nur in seltenen Fällen nötig, aber wenn z.B. ein Verb von einer bestimmten Person oder, was wahrscheinlicher ist, von einer Maschine verstanden werden soll, vom Spieler aber nicht, dann kann man diese Aktion dazu benutzen. Als einfaches Beispiel können wir einen Befehl zum begrüßen definieren, der aber nur verstanden werden soll, wenn er zu einer anderen Person gesagt wird:
Aktion VerbParser
Ausf
Wenn /(akteur = 0) dann
Wenn (Wort = 'guten') dann
NächstesWort
Wenn (Wort = 'tag' 'abend' 'morgen') dann
Sei aVerb 'hallo.'
sonst
ErstesWort
Ende
Ende
Wenn (Wort = 'hallo' 'hi' 'servus')
Sei aVerb 'hallo.'
Ende
Ende
EndeAusf
Bef hallo
Name 'Hallo!'
Verb 'hallo.'
ObjAttr begrüßt
Obj Verkäufer
[...]
BefAusf
(hallo) Wenn (selbst begrüßt) dann
Text '"Genug geplänkelt!" sagt der
Verkäufer, "Reden wir übers Geschäft."'
sonst
Text '"Einen wunderschönen Tag! Was darf
es sein?" Der Verkäufer setzt ein
verbindliches Grinsen auf.'
AttrZu begrüßt
Ende
EndeAusf
Wenn der Befehl zu einer anderen Person gesagt wird, dann werden die bekannten Begrüßungsformeln in das Verb 'hallo.' umgewandelt. Der Spieler selbst kann diesen Befehl nie ausführen, da die einzige Vokabel nicht vom Parser interpretiert werden kann, da der Punkt bei der Aufteilung der Worte verschwindet. Mit dem Trick, dieses vom Parser nicht lesbare Verb in der Aktion VerbParser zuzuweisen, kann aVerb trotzdem Punkte enthalten. Da der Spieler hallo nicht ausführen kann, braucht dieser Befehl auch keinen Ausführungsblock. | ||||||
|