• Keine Ergebnisse gefunden

Wie bei den meisten Interpretern gibt es eine Menge von Befehlen, die den Interpreter selbst betreffen und bestimmte Eigenschaften ausgeben oder Ein-stellungen ver¨andern.

5.2.1 Syntax

In Anlehnung an die GHCi-Syntax beginnen die Befehle mit einem Doppel-punkt. Danach folgt der Name des Befehls und je nach Befehl noch weitere Argumente.

:name arg1 arg2 ... argn

F¨ur jeden Befehlsname existiert eine Kurzschreibweise in Form eines einzel-nen Symbols. Diese Alternativen geben dem Nutzer die Wahl: Die Langformen der Befehle sind oft deutlich aussagekr¨aftiger hinsichtlich ihrer Wirkung, die Abk¨urzungen sparen Schreibarbeit.

Die Befehle werden durch ein einfaches Aufteilen der Eingabe in W¨orter (ge-trennt durch Leerzeichen) erkannt und voneinander unterschieden. Aus der Wortliste werden dann die n¨otigen Argumente per Index extrahiert. ¨Uberz¨ahlige Argumente werden ignoriert.

Verwendet der Nutzer einen undefinierten Befehl oder gibt nicht gen¨ugend Ar-gumente an, gibt das Programm eine Fehlermeldung zur¨uck.

5.2.2 Semantik

Die verf¨ugbaren Befehle lassen sich in 2 gr¨oßere Kategorien unterteilen.

1. Informationsbefehle helfen dem Nutzer bei der Bedienung, indem Infor-mationen zu einem bestimmten Sachverhalt ausgegeben werden.

2. Konfigurationsbefehle ¨andern die Konfiguration des Programms, um ein anderes Verhalten zu erreichen.

Konfigurationsbefehle beziehen sich in diesem Programm auf die Axiome.

Constraints und Gleichungen werden zusammen eingegeben (dazu sp¨ater mehr), aber das Ergebnis von Algorithmus [2.2.2] h¨angt zus¨atzlich von den verwende-ten Axiomen ab. Jene Axiome m¨ussen ebenfalls definiert werden k¨onnen. Diese Aufgabe ¨ubernehmen Konfigurationsbefehle. Details zur Eingabe der Axiome werden wir ebenfalls sp¨ater behandeln.

Die Informationsbefehle haben verschiedene Aufgaben. Manche geben die m¨ og-lichen Eingaben an, andere dienen allgemein der Orientierung bei Nutzung des Programms. Es gibt auch Befehle zum Durchsuchen des momentan aktiven Axiome nach bestimmten Informationen.

Eine Liste aller Befehle inklusive Beispiele findet sich im Anhang.

Kapitel 6

Ein- und Ausgabe

Jede Eingabe, die kein Befehl ist, gilt als Eingabe f¨ur die Unifikation. Diese soll Haskell-Syntax verwenden k¨onnen und ist daher deutlich schwieriger umzuset-zen als die Befehle. Gleiches gilt f¨ur die Eingabe benutzerdefinierter Axiome.

Dieses Kapitel soll die L¨osung aufzeigen.

6.1 Lexer

Nachdem festgestellt wurde, dass es sich nicht um einen Befehl handelt, oder der Befehl Axiome enth¨alt, wird die Eingabe bzw. die Axiommenge zun¨achst von einem Lexer bearbeitet. Dieser besteht aus einer Funktion1, die den String lexertypisch von links nach rechts in eine Liste von Token umwandelt.

Der f¨ur diesen Lexer ben¨otigte Tokentyp ist

data Token = Variable String | Name String

| Bracket BrType BrState | Separator SepType

| Constraint | Function | Equates

| Unknown String

mit den Hilfstypen

data BrState = Open | Close data BrType = Round | Square data SepType = Comma | Semicolon

Die TokenVariableundName erkennen Variablen- und Typnamen. Variablen beginnen mit Kleinbuchstaben, Typkonstruktoren und Typklassen mit Groß-buchstaben, gefolgt von beliebig vielen alphanumerischen Zeichen. Der Lexer erkennt diese Zeichenfolgen und schreibt den Namen in das Tokenargument.

1Alle Komponenten des Lexers finden sich im ModulHelper

Die ¨ubrigen Token repr¨asentieren bestimmte Symbole:Bracketsteht f¨ur runde und eckige Klammern (jeweils geschlossen oder offen),Separatorf¨ur Kommas und Semikola,Constraintf¨ur=>,Functionf¨ur->undEquatesf¨ur=ohne>.

Außerdem filtert der Lexer Whitespaces, diese werden nicht an den Parser wei-tergeleitet.

Unknownrepr¨asentiert den einzigen Fehlertyp, den der Lexer finden kann, n¨amlich Symbole oder Sequenzen, auf die keines obengenannter Token passt. Mit dem Auftreten dieses Tokens ist das Lexing fehlgeschlagen, die Eingabe enth¨alt ung¨ ul-tige Zeichenfolgen. Die fraglichen Zeichen werden zwecks Fehlermeldung mit

¨ubernommen.

6.2 Parser

Die vom Lexer erstellte Tokenliste wird dem Parser ¨ubergeben.

6.2.1 Parser-Kombinatoren

F¨ur den Parser wurden Parser-Kombinatoren2verwendet. Dabei handelt es sich um eine Bibliothek f¨ur einen rekursiv absteigenden Parser.

Grundlagen

F¨ur jedes Nichtterminal muss eine Funktion geschrieben werden, welche die entsprechende Sprache erkennt. Diese Funktionen haben den Typ

type Parser a b = [a] -> [([a],b)],

wobeiader Tokentyp undbder Typ des Syntaxbaums ist. Der R¨uckgabetyp des Parsers ist eine Liste, da die Grammatik mehrdeutig sein kann. Außerdem wird eine fehlgeschlagene Regelanwendung durch eine leere Liste repr¨asentiert. Die Liste enth¨alt Paare aus einer Tokenliste, bei der es sich um die noch einzulesen-den Token handelt, und dem bisher konstruierten Syntaxbaum. Ein gegebenes Wort konnte hergeleitet werden, wenn es in der R¨uckgabeliste des entsprechen-den Parsers Paare gibt, die keine Token mehr enthalten, Resultat sind die zu-geh¨origen Syntaxb¨aume. Andernfalls konnte keine Herleitung gefunden werden, oder die Eingabe enth¨alt nach allen m¨oglichen Herleitungswegen noch Symbole, die nicht verarbeitet wurden.

Die Bibliothek enth¨alt grundlegende Funktionen zum Erkennen von Terminalen, z.B.symbol, sowie Kombinatoren, mit denen mehrere Parser zu einem gr¨oßeren zusammengesetzt werden k¨onnen. Die wichtigsten Kombinatoren sind<|>zum Kennzeichnen von Alternativen und <**>3 zum Konkatenieren von Parsern.

Die Umsetzung dieser Kombinatoren als Operatoren hat den Vorteil, dass die

2siehe [9]

3Das Symbol dieses und einiger anderer Operatoren wurde ver¨andert, um die Kompatibi-lit¨at mit derApplicative-Typklasse zu wahren.

Grammatiksyntax aus der Theorie nahezu direkt in einen Parser ¨ubersetzt wer-den kann. Die Elementarfunktionen geben als

”Syntaxbaum“ grunds¨atzlich die gefundenen Token zur¨uck. Um den gew¨unschten Syntaxbaum zu konstruieren, steht der Kombinator<@zur Verf¨ugung. Dieser wendet eine spezifizierte Funk-tion auf das Resultat an, um die Token in den Baum zu ¨uberf¨uhren.

Anwendung

In einem Compiler w¨are das Ergebnis des Parsens der pure Syntaxbaum, welcher dann einer semantischen Analyse unterzogen w¨urde. F¨ur unsere Zwecke gen¨ugt es, den Parser direkt die ben¨otigte Datenstruktur erzeugen zu lassen, da jede vom Parser erkannte Eingabe auch eine g¨ultige Eingabe ist.

Im Programm wird<!>statt<|>verwendet, wodurch bei Alternativen nur die erste erfolgreiche behalten wird. Damit entf¨allt das Durchsuchen der Resultatli-ste und bei sorgf¨altiger Konstruktion des Parsers stellt dies keine Einschr¨ankung dar. Dabei ist besonders auf die Reihenfolge der Produktionen zu achten, sodass die erste erfolgreiche Produktion auch die gew¨unschte ist.

Funktionsweise und Verwendung der Parser-Kombinatoren werden beschrieben in [12] und [6].

6.2.2 Grammatiken

Im Folgenden einige Grammatiken, die das Eingabeformat angeben. Der ¨ Uber-sichtlichkeit halber sind diese Grammatiken reduziert, d.h. sie erkennen eine Teilsprache, welche syntaktische Besonderheiten z.B. einiger Typkonstruktoren ignoriert. Die vollst¨andigen Grammatiken sowie weitere Hinweise zur Syntax finden sich in Anhang B.

Gleichungen und Constraints

Gleichungen und Constraints werden zusammen eingegeben. Jede Eingabe, die kein Befehl ist, wird als diese Kombination interpretiert. Nachfolgende Gram-matik beschreibt die Eingabe der Unifikationsgleichungsmenge:4

USet → Eq Eqs Das Startsymbol ist jeweils das oberste Nichtterminal.

4Zu beachten ist, dass diese und alle folgenden Grammatiken eigentlich auf Token definert sind. Zur einfacheren Lesbarkeit wurden hier die Token durch die zugeh¨origen Symbole ersetzt.

V arundN amebeziehen sich auf die TokenVariableundName.

Das Eingabeformat kann verbal folgendermaßen beschrieben werden: Die Uni-fikationsgleichungen werden mit Komma oder Semikolon getrennt eingegeben.

Jede Gleichung besteht aus zwei polymorphen Typen, die durch ein Gleichheits-zeichen miteinander verbunden werden. Ein polymorpher Typ ist entweder ein Variablen- oder ein Konstruktorname, jeweils mit einer beliebigen Anzahl Ty-pen als Argumente. Die Klammerung stellt sicher, dass die Argumente korrekt zugeordnet werden.

Als n¨achstes die Grammatik der Constraintmenge:5 CSet → Cl Cls

Cls → ,Cl Cls |

Cl → N ameEx

Constraints werden also mit Komma getrennt und ein Constraint besteht aus einem Typklassennamen und einem polymorphen Typ.

Zuletzt die Grammatik f¨ur die Gesamteingabe (jeden Nicht-Befehl):

All → (CSet) => USet | USet | CSet

Eine Eingabe besteht somit entweder nur aus einer Constraintmenge oder nur aus einer Gleichungsmenge (in diesen F¨allen wird die andere Menge implizit als leer angenommen) oder aus beiden, wobei die Constraintmenge in Klammern gesetzt und von den Gleichungen durch=>getrennt wird.

Axiome

Axiome sind kein Teil der Standard-Eingabe. Jedoch k¨onnen ¨uber bestimmte Befehle eigene Axiome definiert werden.6Die dabei eingegebenen Axiome folgen einer speziellen Syntax, die sich mit folgender Grammatik beschreiben l¨asst:

ASet → Ax Axs

Die einzelnen Axiome werden mit Komma oder Semikolon voneinander getrennt.

Die ersten drei Alternativen vonAxentsprechen den 3 verschiedenen Axiomar-ten. Die Syntax entspricht der im Theorieteil vorgestellAxiomar-ten. Die letzte Alternati-ve deckt den Fall ab, dass ein Axiom 2. Typs keine Constraints als Voraussetzung hat.

5Alle Nichtterminale, die nicht mehr explizit aufgef¨uhrt sind, beziehen sich auf obenste-hende Grammatik(en).

6siehe Anhang A f¨ur Informationen zu diesen Befehlen

6.2.3 Beispiel

Nachfolgend ein (vereinfachtes) Beispiel f¨ur die Anwendung der Parser-Kombina-toren im Programm:

parseUniEq = parseEx <**> token [Equates] **> parseEx

<@ (\(e1,e2) -> UniEq e1 e2)

Der Parser f¨ur eine Unifikationsgleichung. Diese Funktion implementiert das NichtterminalEqobenstehender Grammatik.parseExist der Parser zuEx, die Funktiontokenerkennt eine Liste von Terminalen (in diesem Fall das Token f¨ur

=). Die Operatoren <**>und **>konkatenieren die Ergebnisse, d.h. erst wird der linke Parser ausgef¨uhrt, dann der rechte.**>gibt automatisch das Ergebnis des rechten Parsers zur¨uck, w¨ahrend<**>beide Ergebnisse zu einem Paar kom-biniert. Beide Operatoren sind rechtsassoziativ, wodurch die erste Zeile einen Parser erstellt, dessen Ergebnis das Token ignoriert (denn es tr¨agt keine Infor-mation ¨uber die Gleichung, sondern dient nur der Erkennung) und die beiden Seiten der Gleichung (TypUniExpr) zu einem Paar zusammenf¨ugt. Dieses muss nun noch in den Typ einer Unifikationsgleichung umgewandelt werden. Daf¨ur ist der Operator<@ zust¨andig, der eine Funktion auf das Paar anwendet, die dieses in den gew¨unschten Datentyp verpackt. Der Typ der Parse-Funktion ist somitParser Token UniEq.

Der Parser f¨ur eine Datenstruktur findet sich meistens in der N¨ahe ihrer Defini-tion. Grundlegende Parser, z.B. f¨ur die TokenVariable undName wurden ins ModulHelperausgelagert.

ÄHNLICHE DOKUMENTE