next up previous contents
Next: 6.2.5 Konfiguration der Bedienelemente Up: 6.2 Eingabeanalyse Previous: 6.2.3 Grammatik

6.2.4 Parser

Das zentrale System der Benutzerschnittstelle, die nach obigen Überlegungen entworfen wurde, ist der Parser. Dieser greift auf einen Satz von Regeln zurück, anhand deren er den Fluß der Eingabeworte verarbeitet.

Das Callback-Prinzip (s. unten) der Benutzeroberfläche erfordert, daß der Parser nicht die Hauptschleife des Programms kontrolliert und nicht selbst per aktivem Funktionsaufruf die Eingabeworte anfordert, wie es bei den von YACC generierten Parsern der Fall ist, sondern von außen per passivem Funktionsaufruf die Eingabeworte erhält. Alle Regeln, die beschreiben, wie ein bestimmtes Nonterminal aufgelöst werden kann, sind in einer Liste unter dem Symbol state::<name> zusammengefaßt, wobei <name> für die Bezeichnung des Nonterminals steht (z.B. line).

Der Parser hat einen Zustand, der angibt, welches Nonterminal oder Terminal als nächstes erlaubt ist. Handelt es sich um ein Nonterminal, so prüft der Parser beim nächsten auftretenden Eingabewort alle Ableitungen des Nonterminals, wenn nötig verschachtelt, welche davon das Eingabewort akzeptiert. Diese Regel wird herangezogen und derart im Parser auf den Zustandsstapel gelegt, daß ihre Teile der Reihe nach abgearbeitet werden können. Ist ihr erster Teil wieder ein Nonterminal, wird der Vorgang wiederholt.

Falls keine Teilregel die Eingabe verarbeiten kann, dann bedeutet das einen Eingabefehler.

Liegt nun, ev. nach mehreren Auflösungen von Nonterminalen, ein Terminal an oberster Position des Zustandsstapels, und dieses stimmt mit dem aktuellen Eingabewort übereingif, dann wird das Terminal vom Zustandsstapel entfernt, und das Eingabewort auf den Datenstapel des Parsers gelegt. Dort stehen seine Daten für die semantischen Teile der Regel zur Verfügung.

Um semantische Aktionen durchführen zu können, wurde - ähnlich wie im Parsergenerator YACC - die Möglichkeit vorgesehen, in die Regeln Programm-Code einzubauen, der direkt vom Parser aufgerufen wird. Dies geschieht genau dann, wenn während der Abarbeitung einer Regel statt eines Terminals oder Nonterminals eine LISP-Liste am Stapel an oberster Stelle liegt. Sie wird als ausführbarer Code angenommen und evaluiert. Innerhalb dieses Ausdruckes kann die Funktion $ mit einem ganzzahligen Argument aufgerufen werden, die die Daten zu dem mit der Nummer angegebenen Teil der Regel liefert. Z.B. gibt ($ 2) die Daten, die beim Auswerten des zweiten Teiles der aktuellen Regel anfielen. Natürlich können nur Daten bereits abgearbeiteter Regelteile abgerufen werden. Der solcherart evaluierte LISP-Ausdruck liefert seinerseits ein Resultat, das vom Parser für späteres Abrufen aufgehoben wird.

Diese Datenverwaltung wird auf einem eigenen Stapel, dem Datenstapel, durchgeführt. Das oberste Datum am Stapel ist eine Liste der bisherigen Teilresultate der soeben in Bearbeitung befindlichen Regel. Wenn eine Regel abgearbeitet ist, muß der Datenstapel bereinigt werden. Dazu hat der Parser bereits beim Ablegen der rechten Seite der Regel auf dem Zustandsstapel zuunterst eine Marke :DATA-POP-TOP-PUSH eingefügt (in den Abbildungen mit :DATA abgekürzt). Trifft der Parser nun auf diese Marke, dann entfernt er das oberste Element des Datenstapels - das ja die Liste der Teilresultate ist - und nimmt ihr letztes Teilresultat als Gesamtresultat der Ableitung und hängt es in die nun zuoberst liegende Liste als Teilresultat (Abb. 6.3).

   figure2222
Abbildung 6.3: Zustände der beiden Stapel (oben Parserstapel, unten Datenstapel) während der Abarbeitung des Nonterminals line

Das Resultat des letzten Teiles jeder Regel wird als Ergebnis dieser Regel angesehen und an der entsprechenden Stelle des übergeordneten Nonterminals eingesetzt.

Ein Sonderfall der semantischen Ausdrücke sind solche mit Seiteneffekten, bei denen das Resultat nur Nebenbedeutung hat oder gar nicht weiterverwendet wird. Damit kann die graphische Oberfläche des PED an den aktuellen Zustand des Parsers angepaßt werden, vor allem betreff Titel, Konfiguration der Mausknöpfe und Wirkung des graphischen Zeigers.

Um Regeln zu definieren, dient die Funktion newrule. Diese akzeptiert drei Argumente mit den folgenden Bedeutungen:

(defun newrule
  (left         ; Eine Zeichenkette mit dem Namen des Nonterminals,
                ; das den linken Teil der Regel darstellt. Wenn mit
                ; diesem Funktionsaufruf die erste Regel fuer dieses
                ; Nonterminal auftritt, dann wird ein Symbol mit
                ; dem Namen state::<left> angelegt.
                ; Unter diesem Symbol wird eine Liste mit allen 
                ; "rechten Seiten" dieses Nonterminals gefuehrt.
   test         ; Ein Testausdruck, der prueft, ob das aktuelle Eingabe-
                ; Element in ped::token die Bedingung erfuellt, die diese
                ; Regel aktiviert.
                ; Wenn das erste Element der "rechten Seite" (siehe naechstes
                ; Argument) ein konstantes Terminal ist, kann dieser Ausdruck
                ; weggelassen werden (NIL); wenn dieses erste Element ein
                ; Nonterminal ist, dann MUSS der Ausdruck NIL sein!
   right        ; Die rechte Seite der Regel; eine Liste von Terminalen, 
                ; Nonterminalen und Aktionen (LISP-Ausdruecken). 
                ; Das erste Element sollte keine Aktion sein.
  )

Im Detail sehen die Bestandteile der rechten Seite so aus:

Terminale sind Konstante wie Fix- oder Fließkommazahlen oder Schlüsselworte (diese sind Symbole beginnend mit `:', und evaluieren in LISP auf sich selbst statt auf ihren Variablenwert). Der resultierende Wert ist die Konstante selbst.

Nonterminale werden durch Zeichenketten mit ihrem Namen angegeben. Der resultierende Wert ist das Ergebnis der Unterregel.

Aktionen sind LISP-Listen. Diese werden bei Abarbeitung einer Regel sofort nach Erledigung des vorhergehenden Terminals oder Nonterminals ausgeführt, ohne auf ein weiteres Eingabewort zu warten. Das Resultat ergibt sich durch die Evaluierung des Ausdruckes. Sinnvoll ist insbesondere die Verwendung der Funktion $, um auf die Ergebnisse der vorhergegangenen Regelteile zuzugreifen.

Die Regeln für den Parser können zur Laufzeit nachgeladen werden. Diese Eigenschaft ist notwendig, um die Flexibilität des PED sicherzustellen. Da die möglichen Regeln zu einem Nonterminal in einer Liste abgespeichert und bei Auftreten eines Eingabewortes zwecks Auswahl der Reihe nach getestet werden, ist die Reihenfolge von großer Bedeutung. Wird eine Unterregel (P) durch ein Eingabewort vom Typ clsPoint gestartet, und eine andere (Q) durch clsStruct, das ja auch clsPoint umfaßt, dann muß (P) in der Liste vor (Q) stehen, andernfalls (P) nicht erreichbar wäre! Da die Reihenfolge in der Liste durch jene der Regeldefinitionen bestimmt wird, muß also Regel (P) vor (Q) definiert werden. Besondere Vorsicht ist geboten, wenn derartig abhängige Regeln in verschiedenen VLISP-Dateien stehen.

Da neue Teilregeln eines Nonterminals bei der Definition mit newrule einfach an die Liste der vorhandenen Teilregeln angehängt werden, ist beim Nachladen veränderter VLISP-Dateien auch darauf zu achten, daß umdefinierte Regeln nicht erreichbar sind, weil ja die alte Fassung in der Liste einen vorderen Platz einnimmt!


next up previous contents
Next: 6.2.5 Konfiguration der Bedienelemente Up: 6.2 Eingabeanalyse Previous: 6.2.3 Grammatik

Martin Knaipp
Wed Jun 12 15:41:33 MET DST 1996