________________________________________________________________________
[<<] [Inhalt] [>>] III. Beispiele

15. Besonderheiten des Parsers

15.1.   Wie arbeitet der Parser?
15.2.   Der Vor- und Nach-Parser
15.3.   Spezielle Anweisungen zum Parsen von Objekten
15.4.   Parsen von Objekten, die Zahlen enthalten
15.5.   Parsen von Objekten, die komplizierte Kennungen enthalten
15.6.   Spezielle Anweisungen zum Parsen von Verben

15.1. Wie arbeitet der Parser?

Der Reihe nach macht der Parser folgendes:

  1. Die Eingabe des Spielers wird in einzelne Worte aufgeteilt. Dabei werden Synonyme bereits ersetzt.

  2. Grammatikalische Satzanalyse:

    1. Die Aktion VorParser wird aufgerufen

    2. Der Parser untersucht den eingegebenen Satz auf seine grammatikalischen Bestandteile hin. Am Ende dieses Vorgangs sind das Verb mit Klammer, die verschiedenen Objekte (Akkusativ und Dativ mit und ohne Präposition, Mittelobjekt), die Richtung und die Präposition bekannt.

    3. Der Parser versucht nun, aus den gegebenen Objekten einen Befehl zu bestimmen. Sind falsche oder zuviele Objekte in einem Satz, wird ein Fehler ausgegeben, fehlen Objekte, so wird nachgefragt.

  3. Ortsüberprüfung der gefundenen Objekte

    1. Zum Schluß wird der Ort eines jeden Objekts überprüft. Gibt es mehrere Objekte, auf die die Eingabe zutrifft und die am richtigen Ort sind, so wird nachgefragt. Mit Ort ist hier gemeint, ob das Objekt beim Spieler, nicht beim Spieler, für ihn erreichbar oder nur in Sicht sein muß.

    2. Die Aktion NachParser wird aufgerufen, in der nachträglich einiges geändert werden kann.

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:

  • Ist Fehler Null, so wurde der Parser fehlerfrei durchlaufen und der Spielzug wird ausgeführt.

  • Eine Fehlernummer von 1 bis 19 bedeutet, daß ein grammatikalischer Fehler aufgetreten ist: Ein Wort ist unbekannt, der Satz wurde nicht verstanden oder ähnliches. Dann bricht die Textanalyse im Punkt 2. vorzeitig ab.

  • Eine Fehlernummer von 20 bis 39 bedeutet, daß ein angesprochener Gegenstand nicht am richtigen Ort ist, daß die Eingabe mehrdeutig oder unvollständig ist. Die Textanalyse bricht im Punkt 3. ab.

  • Höhere Fehlercodes können vom Spieler generiert werden. Die Fehlermeldung muß dann gesondert mit einer Text-Anweisung ausgegeben werden. Eine Ausnahme ist der Fehler 42, der bereits für eine mehrdeutige Eingabe reserviert ist.

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:

  • Wenn zFehler = 0 ist, dann war der Befehl an den Akteur korrekt.

  • Ein Fehler von 1 bis 19 zeigt nicht erkannte Anweisung an. In diesem Fall ist aBef = nicht_verst.

  • Ein größerer Fehler gibt Probleme mit der Sichtbarkeit oder Erreichbarkeit eines angegebenen Objekts an. (Ob der Akteur ein Objekt sehen oder anfassen kann, wird natürlich genau wie beim Spieler mit denselben Regeln geprüft.) Dann ist aBef gleich dem erkannten Befehl. Soll ein Akteur einen Befehl tatsächlich ausführen, dann sollte der zFehler erst geprüft werden, damit z.B. der Akteur nicht ein Objekt aufheben kann, das nicht im Raum ist.

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-Parser

Die 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 Objekten

Die 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 enthalten

Manchmal 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 enthalten

Neben 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:

"x"

ein Zeichen in doppelten Anführungszeichen stellt den ASCII- Wert dieses Zeichens dar. Es wird wie eine Konstante behandelt, die nur gelesen, aber nicht geändert werden kann. Da z.B. "a" identisch ist mit der Zahl 97, kann man mit den Zeichen auch Rechenoperationen durchführen.

Die doppelten Anführungszeichen unterscheiden einzelne Zeichen von Verben mit nur einem Buchstaben, wie z.B. "i" von 'i'.

Wort.1 gibt das erste Zeichen des momentan betrachteten Wortes an. Dabei muß beachtet werden, daß ein Wort maximal 12 Zeichen besitzen kann. Die Länge des Worts steht auf der Flagge Wortlänge.
Wort.x gibt das x-te Zeichen an, wobei x eine Flagge sein muß. Ist der Wert der Flagge 0 oder größer als 12, dann hat das Zeichen den Wert Null.

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 Verben

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

Martin Oehm, 04.02.2000 Vorheriges KapitelInhaltsverzeichnisNächstes Kapitel