________________________________________________________________________
| ||||||
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. | ||||||
|