• Keine Ergebnisse gefunden

Bachelorarbeit Implementierung einer Überprüfung der Typkonsistenz von Happy-Parserspezifikationen

N/A
N/A
Protected

Academic year: 2022

Aktie "Bachelorarbeit Implementierung einer Überprüfung der Typkonsistenz von Happy-Parserspezifikationen"

Copied!
36
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Implementierung einer Überprüfung der Typkonsistenz von Happy-Parserspezifikationen

Florian Stein

Goethe Universität Frankfurt am Main Institut für Informatik

Betreuer: Prof. Dr. Manfred Schmidt-Schauß Abgabedatum: 13. April 2017

(2)
(3)

Erklärung zur Abschlussarbeit

gemäß § 25, Abs. 11 der Ordnung für den Bachelorstudiengang Informatik vom 06. Dezember 2010:

Hiermit erkläre ich, Herr Florian Stein:

Die vorliegende Arbeit habe ich selbstständig und ohne Benutzung anderer als der angegebenen Quellen und Hilfsmittel verfasst.

Frankfurt am Main, den

(4)

Zusammenfassung

Diese Arbeit beschäftigt sich mit der Überprüfung der Typkonsistenz bei dem Compilergenerator Happy für die funktionale Programmiersprache Has- kell. Happy ist ein Metacompiler, der Schiebe-Reduziere-Parser für Haskell generiert. Haskell ist eine stark und statisch typisierte, funktionale Program- miersprache.

Schiebe-Reduziere-Parser ermöglichen es, Operatorgrammatiken zu par- sen und automatisch Parsebäume zu generieren. Operatorgrammatiken sind zum Beispiel die Syntax einfacher Taschenrechner bis hin zu höheren Pro- grammiersprachen. Parser zu programmieren, die Syntaxbäume generieren, ist sehr aufwendig. Happy erleichtert diese Arbeit wesentlich, da nicht viel mehr getan werden muss, als die Grammatik ergänzt durch kleine Haskell- codeabschnitte aufzuschreiben.

Happy überprüft den Code nicht vollständig, bevor es den Parser gene- riert. Besonders die Haskellcodeabschnitte werden nahezu ungeprüft über- nommen. Fehlermeldungen werden erst beim Starten des erzeugten Parsers ausgegeben. Da sich diese nicht auf den Happycode, sondern auf den au- tomatisch generierten Code beziehen, ist es schwer, sie zu verstehen und zurückzuverfolgen. Schon kleine Tippfehler führen zu Fehlermeldungen, die unverständlich sind.

In dieser Arbeit wurde ein vorgeschaltetes Haskell-Programm entwickelt mit dem Ziel, Fehlermeldungen vor dem Kompilieren auszugeben. In Anleh- nung an Happy wurde es „funny“ genannt. Dabei liegt der Fokus auf Typ- fehlern. Durch die starke, statische Typisierung von Haskell ist eine Über- prüfung der Typkonsistenz ohne Ausführung des Haskellcodes möglich.

Da die Syntax von Happy auch eine Operatorsprache ist, bot es sich an, Happyparser für die Analyse des Happycodes zu verwenden. So war es möglich, während des Programmierens die schon fertigen Funktionalitäten zu nutzen, um das Programm weiterzuentwickeln.

Das Programm funny kann eine große Anzahl der Fehler bereits ausfiltern und stellt dadurch eine enorme Erleichterung beim Programmieren dar. Die- ser Ansatz bietet noch Erweiterungsmöglichkeiten auf schwerer detektierbare Fehler, was im Rahmen dieser Bachelorarbeit nicht möglich war.

(5)

Inhaltsverzeichnis

1 Einleitung 1

1.1 Motivation . . . 1

1.2 Ziel der Arbeit . . . 1

2 Grundlagen 2 2.1 Haskell . . . 2

2.1.1 Funktionale Programmierung . . . 2

2.1.2 Starke, statische Typisierung und Typvariablen . . . 2

2.1.3 Typkonstruktoren . . . 3

2.2 Happy . . . 3

2.2.1 Kontextfreie Grammatik . . . 4

2.2.2 Operatorgrammatik . . . 5

2.2.3 Tokens vs. Terminale . . . 5

2.2.4 Schiebe-Reduziere-Parser . . . 6

2.2.5 Aufbau eines Happycodes . . . 7

3 Prinzip der Fehlersuche 10 3.1 Haskellcode . . . 10

3.2 Tokenabschnitt . . . 10

3.3 Grammatikabschnitt . . . 11

4 Praktische Umsetzung 13 4.1 Typüberprüfung mit Haskell . . . 13

4.1.1 Typ über einen Konsolenbefehl ermitteln . . . 13

4.1.2 Typ über einen Konsolenbefehl überprüfen . . . 13

4.1.3 Haskellfunktion für Konsolenbefehle . . . 14

4.2 Programmcode von funny . . . 14

4.2.1 Übersicht über den Programmablauf . . . 15

4.2.2 Datentyp . . . 15

4.2.3 Rekursive Hauptfunktion „testToks“ . . . 15

4.2.4 Rekursive Unterfunktion „testToksIntern“ . . . 16

4.2.5 Verschränkt-rekursive Funktion „testArgIntern“ . . . 16

5 Installation und Ausführung 18 5.1 funny als Funktion eines Moduls . . . 18

5.2 funny installieren . . . 18

(6)

6 Fehlermeldungen 20

6.1 Fehlermeldungen bei Happy . . . 20

6.2 Fehlermeldungen bei funny . . . 21

6.2.1 „type missmatch error“ . . . 21

6.2.2 „type missmatch error“ mit verschiedenen möglichen Ursachen 21 6.2.3 „type error“ . . . 22

6.2.4 „argument count error“ . . . 23

6.2.5 „index error“ . . . 24

7 Fazit und Ausblicke 26 7.1 Fazit . . . 26

7.2 Ausblicke . . . 26

7.2.1 Noch offene Probleme . . . 26

7.2.2 Ergänzende Arbeiten . . . 26

8 Anhang 27 8.1 Beispielhappycode „deutsch.y“ . . . 27

(7)

1 Einleitung

Happy ist ein Metacompiler, der es sehr einfach macht, Schiebe-Reduziere-Parser für Operatorgrammatiken in Haskell zu erstellen. Happy erzeugt dabei ein Haskell- modul, das in ein größeres Projekt eingebunden werden kann. Dabei ist die Syntax von Happy sehr ähnlich der einer kontextfreien Grammatik ergänzt durch Haskell- code.

1.1 Motivation

Leider wird dieser Haskellcode von Happy nicht gut überprüft. Typfehler und zum Teil sogar Syntaxfehler werden von Happy in das neu erzeugte Haskellmodul übernommen. Beim Starten dieses Modul, werden Fehlermeldungen ausgegeben, die man in der Regel nicht zu verstehen und nicht zuzuordnen sind. Das liegt daran, dass Happy den Code so sehr verändert, dass es schwer ist, nachzuvoll- ziehen, welcher Teil des geschriebenen Happycodes den Fehler verursacht hat.

1.2 Ziel der Arbeit

Ziel der Arbeit ist es, einen Parser zu schreiben, der den Happycode überprüft und Fehler vor dem Kompilieren findet und die entsprechende Zeilennummer ausgibt.

Dabei liegt das Hauptaugenmerk auf Typfehlern. Eine vollständige Überprüfung findet nicht statt.

(8)

2 Grundlagen

In diesem Kapitel wird ein Überblick über Besonderheiten von Haskell und Happy gegeben. Das Kapitel basiert weitgehend auf [9] und der Abschnitt über kontext- freie Grammatiken auf [10].

Im Zusammenhang mit Haskell betrachten wir die Definition funktionaler Pro- grammiersprachen, starker, statischer Typisierung und Typkonstruktoren.

Im folgenden Teil konzentrieren wir uns auf Happy und gehen dafür auf kon- textfreie Grammatiken im Allgemeinen und Operatorgrammatiken im Speziellen und deren Besonderheiten in Happy ein. Wir stellen auch die Schiebe-Reduziere- Parser und den groben Aufbau des Happycodes vor.

2.1 Haskell

Zunächst wird auf die Definition funktionaler Programmiersprachen, starker, sta- tischer Typisierung und Typkonstruktoren eingegangen.

2.1.1 Funktionale Programmierung

Haskell ist eine funktionale Programmiersprache. Im Gegensatz zu imperativen Programmiersprachen wie Java und C kommen funktionale Programmiersprachen ohne Schleifen und globale Variablen aus. Statt Schleifen werden rekursive Funk- tionen benutzt, also solche, die sich direkt oder indirekt selbst aufrufen.

So kann zum Beispiel die Funktionalität einer for-Schleife durch eine Funktion realisiert werden, die als Übergabeparameter eine Zahl i hat. Diese Funktion ruft sich selbst auf, allerdings mit erhöhtem Wert für i. Ist der maximale Wert erreicht, wird das Ergebnis ausgegeben. Alle benötigten Variablen werden dabei übergeben, da es keine globalen Variablen gibt.

Funktionale Programmiersprachen sind turingmächtig. Es ist also möglich, jede Funktionalität aus einer imperativen Programmiersprache auch in einer funktio- nalen Programmiersprache zu realisieren und umgekehrt.

2.1.2 Starke, statische Typisierung und Typvariablen

Außerdem ist Haskell stark und statisch typisiert. Eine Variable oder eine Funk- tion kann den Typ also nicht ändern. Jedoch lässt Happy Typvariablen zu. Das bedeutet, dass eine Funktion den Typ a -> [a] haben kann. [a] liest sich als

„Liste von a“. Dabei wird je nach Typ des Inputs der Typ des Outputs ermittelt.

Bekommt diese Funktion also einIntals Inputtyp, ist der Outputtyp[Int], beim Input vom Typ Char ist der Output [Char], was äquivalent zu String ist. Auch der Inputtyp String mit Outputtyp [String], also[[Char]] ist möglich.

(9)

2.1.3 Typkonstruktoren

Neben den Basistypen (Int,Integer,Float, Rationalund Char) gibt es in Has- kell unter anderem die Möglichkeit, Typkonstruktoren zu definieren. Diese sind keine einfachen Werte, können aber Werte enthalten. Das Schlüsselwort um Typ- konstruktoren zu definieren ist „data“. Hier ein Beispiel für einen einfachen Typ- konstruktor:

d a t a Token = TokenWord S t r i n g

Hier ist der Konstruktortyp Token definiert. „TokenWord“ ist hier der Kon- struktor, der eine Instanz des Typs Token einleitet. Ein Beispiel für eine Instanz des Typen Token ist „TokenWord "Haus" “.

Es kann auch mehrere Konstruktoren für den gleichen Typ geben, wie in diesem Beispiel:

d a t a Token = TokenWord S t r i n g

| TokenSymbol Char

Mit dem senkrechten Strich „|“ lassen sich verschiedene Konstruktoren für den gleichen Typ definieren. In diesem Fall gibt es zwei Konstruktoren, die ein Token einleiten können. (TokenWord und TokenSymbol) Je nachdem, welches man be- nutzt, muss ein String oder ein Char folgen.

Es ist auch möglich, rekursive Konstruktoren zu definieren:

d a t a Tree = Branch Tree Tree

| L e a f I n t

Hier ist Tree als Baum definiert. Die inneren Knoten heißen „Branch“ und erwarten zwei Kinderknoten, also Teilbäume, vom Typ Tree. Die Blätter heißen

„Leaf“ und erwarten einen Wert, in diesem Fall muss es ein Int sein. Man kann auch verschiedene Blätter definieren, die verschiedene Typen unterstützen.

2.2 Happy

Nun gehen wir auf kontextfreie Grammatiken im Allgemeinen und Operatorgram- matiken im Speziellen und deren Besonderheiten in Happy ein. Wir stellen auch die Schiebe-Reduziere-Parser und den groben Aufbau des Happycodes vor.

(10)

2.2.1 Kontextfreie Grammatik

Eine kontextfreie Grammatik[10] beschreibt, welche Wörter in einer kontextfreien Sprache zulässig sind. Sie lässt sich durch ein Viertupel G= (Σ, V, S, P)beschrei- ben, das aus folgenden Komponenten besteht:

• Σ: Das Alphabet oder die endliche Menge der Terminalen. Diese sind nicht- ableitbare Zeichen, die in den Wörtern der Sprache vorkommen können.

• V: Die endliche Menge der Nichtterminalen oder Variablen. Diese können durch Ableitungsregeln abgeleitet werden.

Dabei muss immer gelten:Σ∩V =∅

• S: Das Startsymbol. Dabei muss gelten: S ∈Σ

• P: Die endliche Menge der Produktionen oder Ableitungsregeln.

Ableitungsregeln bestehen aus einer linken und einer rechten Seite. Man trennt die beiden Seiten für gewöhnlich mit einem Pfeil nach rechts (→) oder einem Doppelpunkt (:). Auf der linken Seite steht das Nichtterminale, das abgeleitet wird und auf der rechten Seite steht eine Kette von Terminalen und Nichtterminalen.

Diese Kette darf leer sein. In diesem Fall schreibt man das leere Wort () auf die rechte Seite. Bei kontextfreien Grammatiken dürfen auf der linken Seite der Ableitungsregeln nur genau ein Nichtterminales und kein Terminales stehen.

Ein Wort, das zu der Sprache gehört, die eine Grammatik beschreibt, besteht nur aus Terminalen der Grammatik und ist vom Startsymbol aus erreichbar. Es ist also möglich, mit dem Startsymbol zu beginnen und solange einzelne Nicht- terminalen abzuleiten, bis sich das gesuchte Wort ergibt.

Hier ein Beispiel für eine kontextfreie Grammatik:

Σ ={+,−,∗, /,(,),0,1, ...,9}

V ={E,O,N,Z}

S =E P ={

E:E O E|(E)|N O: +| − | ∗ |/ N: 1Z|2Z|. . . |9Z Z:ZZ||0|1|. . . |9 }

(11)

2.2.2 Operatorgrammatik

Mit Happy ist es nur möglich, Operatorgrammatiken abzubilden. Operatorgram- matiken gehören zu den kontextfreien Grammatiken. Zusätzlich haben sie die Ein- schränkungen, dass sie kein leeres Wort zulassen und keine direkt benachbarten Nichtterminalen auf der rechten Seite der Ableitungsregeln.

Die kontextfreie Grammatik aus dem letzten Abschnitt ist also keine Opera- torgrammatik, da die Ableitungsregeln von E und Z benachbarte Nichtterminale enthalten und die Ableitungsregel von Z zusätzlich das leere Wort . Folgende Grammatik beschreibt die gleiche Sprache und erfüllt die Kriterien einer Opera- torgrammatik:

Σ ={+,−,∗, /,(,),0,1, ...,9}

V ={E,N,Z}

S =E P ={

E:E+E|E−E|E∗E|E/E|(E)|N N: 1Z|2Z|...|9Z|0|1|...|9

Z : 0Z|1Z|...|9Z|N }

2.2.3 Tokens vs. Terminale

Happy unterscheidet im Wesentlichen zwischen Tokens und Nichtterminalen mit Ableitungsregeln. Der Begriff der Tokens ist bei Happy weitergefasst als die Ter- minalen der kontextfreien Grammatik. Tokens sind Terminale im engeren Sinne – zum Beispiel Operatoren – oder Bezeichner, die einen Wert annehmen können – zum Beispiel Zahlen oder Zeichenketten. Dieser Wert wird direkt aus dem Input übernommen und unterliegt keiner Ableitungsregel. Dadurch zählen sie für Happy nicht zu den Nichtterminalen. Eine Besonderheit bei Happy ist also, dass Bezeich- ner nicht zu den Nichtterminalen zählen, obwohl sie im Sinne der kontextfreien Grammatik ableitbar sind.

Betrachten wir die Operatorgrammatik aus dem letzten Beispiel in modifizier- ter Form, wie sie in Happy zulässig ist:

Σ ={+,−,∗, /,(,), N} V ={E}

S =E

(12)

P ={

E:E+E|E−E|E∗E|E/E|(E)|N }

Hier wird die Definition einer natürlichen Zahl durch einen Bezeichner umge- setzt, der die Zahl direkt aus dem Input übernimmt. Dies verkürzt die Grammatik.

Die Grammatik lässt sich noch weiter vereinfachen:

Σ ={O,(,), N} V ={E}

S =E P ={

E:EOE|(E)|N }

Hier wurde auch der Operator zu einem Bezeichner-Token. Auch so können benachbarte Nichtterminale vermieden werden. Dies führt allerdings zu anderen Problemen, die in Kapitel 2.2.5 aufgegriffen werden.

2.2.4 Schiebe-Reduziere-Parser

Die Aufgabe eines Schiebe-Reduziere-Parsers ist es, aus einer Operatorgrammatik einen Baum zu parsen. Schiebe-Reduziere-Parser arbeiten mit einer Zustandsta- belle und einem Stack. Je nach Zustand und oberstem Element des Stacks wird dabei der Stack reduziert oder der nächste Input auf den Stack geschoben. Da- bei wird neben dem Input auch der Zustand geschoben, sodass nach der gleichen Reduktionsregel verschiedene Zustände möglich sind. Am Ende der Eingabe kann akzeptiert werden. Bei ungültigen Eingaben können Fehler ausgegeben werden.

Oft ist es notwendig, neben der Grammatik die Assoziativität und Priorität der Operatoren anzugeben. Dies ermöglicht eine automatische Klammerung.

So könnte man einer einfachen Taschenrechnergrammatik mit den natürlichen Zahlen und den Operatoren „+“ und „∗“ die Priorität „Punkt vor Strich“, also „∗“

vor „+“, und Linksassoziativität bei beiden Operatoren geben. Der Term „1 + 2∗ 3 + 4“ wird durch die „Punkt vor Strich“-Priorität zu „1 + (2∗3) + 4“ und schließlich durch die Linksassoziativität zu „(1 + (2∗3)) + 4“. Allein durch die Grammatik wären auch andere Klammerungen möglich wie „((1 + 2)∗3) + 4“, „(1 + 2)∗(3 + 4)“

oder „1 + ((2∗3) + 4)“. Beim letzten Beispiel entspricht das arithmetische Ergebnis dem des korrekt geklammerten Terms, der Parsebaum allerdings nicht.

(13)

Diese Zustandstabellen zu erstellen, ist sehr kompliziert und umständlich. Da- her gibt es Programme wie Happy, die diese Arbeit erledigen. Happy erzeugt Schiebe-Reduziere-Parser aus einer Grammatik und den Priorisierungen und As- soziativitäten.

2.2.5 Aufbau eines Happycodes

Schauen wir uns nun den Aufbau eines Happyprogramms an. Aus der Grammatik, die mit einer Ableitungsregel auskommt (E:EOE|(E)|N), ergibt sich folgender Happycode:

{

module P a r s e r where i m p o r t Data .Char }

5 %name p a r s e r

%t o k e n t y p e { Token }

%t o k e n

i n t { TokenInt $$ }

( { TokenSymbol ’ ( ’ }

10 ) { TokenSymbol ’ ) ’ }

op { TokenSymbol $$ }

%l e f t op

%%

E : : { Expr }

15 E : ( E ) { $2 }

| i n t { Number $1 }

| E op E { O p e r a t o r $2 $1 $3 } {

20 d a t a Expr = O p e r a t o r Char Expr Expr

| Number I n t d e r i v i n g(Show)

d a t a Token = TokenInt I n t

25 | TokenSymbol Char

d e r i v i n g(Show)

l e x e r : : S t r i n g > [ Token ] l e x e r [ ] = [ ]

30 l e x e r ( c : c s )

| c elem [ ’ + ’ , ’’ , ’’ , ’ / ’ , ’ ( ’ , ’ ) ’ ] = ( TokenSymbol c ) : l e x e r c s

| i s S p a c e c = l e x e r c s

| i s D i g i t c = lexNum ( c : c s )

| o t h e r w i s e = e r r o r (" p a r s e e r r o r , can ’ t l e x symbol " ++ show c )

35

lexNum c s = TokenInt (r e a d num) : l e x e r r e s t where (num , r e s t ) = span i s D i g i t c s

(14)

}

Der Happycode lässt sich in drei Abschnitte aufteilen: Die Parserdirektiven, die Grammatik und ein optionaler Haskellabschnitt, der von Happy unverändert übernommen wird.

Der unveränderte Haskellcodeabschnitt (Z. 2-3 und 20-37) kann wie hier am Anfang und / oder am Ende stehen. Er wird in geschweiften Klammern ge- setzt. Wie in diesem Beispiel kann er am Anfang die eingebundenen Module (Z. 2- 3) enthalten. Am Ende enthält dieser Beispielcode die Deklaration der benötigten Datentypen (Z. 20-26) und einen Lexer (Z. 28-37). Die Datentypen sind Kon- struktortypen. „deriving (Show)“ bedeutet, dass die show-Funktion automatisch generiert werden soll. Ein Lexer ist eine Funktion, die den Input in eine Liste von Tokens umwandelt. Oft ist der Input ein String und die Tokens sind selbstdefi- nierte Konstruktortypen (Z. 24-26). Ein Lexer ist nicht immer notwendig. Benutzt man Char als Tokentyp, so ist ein Input als String schon eine Liste von Tokens und ein Lexer damit überflüssig. Wiederum muss der Input keinStringsein. Auch das kann den Lexer überflüssig machen.

Die Parserdirektiven (Z. 5-13) enthalten zum Beispiel die Assoziativitäten (Z. 12) und durch ihre Reihenfolge auch ihre Prioritäten, den Namen der Parser- funktion (Z. 5), des Lexers, der Fehlerfunktion, . . . . Die meisten Parserdirektiven sind optional.

Im oberen Beispiel lässt sich durch den einfachen Aufbau der Grammatik

„Punkt vor Strich“ nicht realisieren. Man braucht mindestens zwei verschiedene Operatortokens, um eine Priorisierung umsetzen zu können. Wichtig ist hier aber, eine Assoziativität anzugeben („%left op“), sonst entstehen Schiebe-Reduziere- Konflikte und der Parser kann nicht gestartet werden. Das Beispiel aus dem letzten Abschnitt „1 + 2∗3 + 4“ wird von diesem Parser interpretiert als „((1 + 2)∗3) + 4“.

Für die Typüberprüfung relevant sind aus diesem Abschnitt nur der Tokentyp (Z. 6) und die Tokens selbst(Z. 7-11). Die Syntax hierfür ist so aufgebaut, dass erst der Bezeichner aufgeführt wird (z.B. „int“) und dann Haskellcode in geschweiften Klammern folgt (z.B. „ { TokenInt $$ } “). Der Haskellcode muss dem angegebe- nen Tokentyp (hier: Token) entsprechen. Bei Bezeichnern gibt ein „$$“-Symbol im Haskellcode an, welcher Wert weitergegeben wird.

Zwischen den Parserdirektiven und den Ableitungsregeln muss das doppelte Prozentzeichen „%%“ stehen.

(15)

Bei den Ableitungsregeln (Z. 14-17) ist die Syntax ähnlich der von kontext- freien Grammatiken ergänzt durch Haskellcode. Der Haskellcode sieht dem der Tokens sehr ähnlich. Es ist möglich, den Haskelltyp der Nichtterminalen anzu- geben (Z. 14). Dies ist nicht verpflichtend, ermöglicht aber eine Überprüfung des Typs. Hier gibt es etwas Ähnliches wie das „$$“-Symbol, und zwar das „$i“-Symbol, wobei „i“ eine natürliche Zahl ist. Es gibt an, auf das wievielte Zeichen der rechten Seite der Regel es verweist.

(16)

3 Prinzip der Fehlersuche

Dieses Kapitel beschäftigt sich mit der Funktionsweise von funny und damit der Fehlersuche. Anhand von Beispielen wird deutlich gemacht, nach welchem Prinzip funny arbeitet.

3.1 Haskellcode

Zuerst wird der Haskellcode überprüft, der direkt übernommen wird. Dafür wird eine Datei erzeugt, die auch später noch benutzt wird. Diese Datei ist nur für den lokalen Gebrauch gedacht, daher heißt sie „<Dateiname>_flo.hs“. „flo“ steht da- bei für „For Local use Only“. Diese Datei enthält neben dem Haskellcode auch alle Zeilenumbrüche, sodass Fehlermeldungen für diese Datei die korrekten Zeilennum- mern aus der Happydatei ausgeben. Im nächsten Schritt wird diese Datei durch den Haskell-eigenen Typchecker geprüft.

3.2 Tokenabschnitt

Um die Tokens zu prüfen, gibt es einen rekursiven Datentyp, der mithilfe einer Happy-Grammatik aus der Happydatei herausgelesen wird. Durch rekursive Funk- tionen wird jedes einzelne Token überprüft. Dafür wird die schon erwähnte „flo“- Datei gestartet und der Typ des Tokens mit dem angegebenen Tokentyp verglichen.

Da die Happysyntax auch „$$“ und „_“ zulässt, reicht es nicht, den Codeschnipsel als Ganzes zu überprüfen, da dieser eine Fehlermeldung produzieren würde. Auch hier ist es notwendig, rekursiv jede Typbezeichnung einzeln zu prüfen und weiter- zugeben, welcher Typ erwartet wird.

Betrachten wir dafür die Tokens aus der schon benutzten Happygrammatik in Kapitel 2.2.5:

%t o k e n t y p e { Token }

%t o k e n

i n t { TokenInt $$ }

( { TokenSymbol ’ ( ’ }

10 ) { TokenSymbol ’ ) ’ }

op { TokenSymbol $$ }

%l e f t op

Durch die Direktive „%tokentype { Token } “ ist der Tokentyp als Token fest- gelegt. Es muss überprüft werden, ob der Haskellcode in geschweiften Klammern diesem Typ entspricht.

(17)

Das erste Token heißt „int“ und der Haskellcode ist „ { TokenInt $$ } “. Wir betrachten zunächst den Typ des Konstruktors „TokenInt“ und überprüfen dann die Argumente.

„TokenInt“ ist vom Typ Int -> Token. Das bedeutet, dass ein Argument vom Typ Intfolgen muss und der Ausgabetyp Token ist. Der Ausgabetyp Token ent- spricht dem allgemeinen Tokentyp dieser Grammatik und ist damit korrekt. Zu prüfen bleibt, ob das Argument den Typ Interfüllt.

„$$“ bedeutet, dass das Token diesen Wert weitergibt. An dieser Stelle muss der Typ nicht überprüft werden. Im Grammatikabschnitt kommen wir darauf zu- rück. Ein „_“ hätte bedeutet, dass das Argument verworfen wird. Der Typ wäre also irrelevant. Beides würde im Tokenabschnitt gleichermaßen akzeptiert werden.

Implizit wurde auch die Anzahl der Argumente überprüft und das Token wurde akzeptiert.

Ganz analog prüfen wir die nächsten Tokens. Das Nächste ist die öffnende Klammer „(“. Hier ist der Haskellcode „ { TokenSymbol ’(’ } “. „TokenSymbol“

erwartet einen Char und gibt ein Token aus. Auch hier stimmt der Ausgabetyp mit dem allgemeinen Tokentyp überein. Zu prüfen bleibt, ob das Argument ’(’ den Typ Char hat. Auch das ist erfüllt. Damit hat auch dieses Token den korrekten Typ.

Die Prüfung der folgenden Tokens erfolgt analog. Die Direktive „%left op“ gibt die Assoziativität des Tokens „op“ an und ist für die Typüberprüfung irrelevant.

3.3 Grammatikabschnitt

Die Ableitungsregeln der Grammatik erweisen sich als komplexer. Auch hier be- trachten wir die in Kapitel 2.2.5 schon benutzte Happygrammatik:

E : : { Expr }

15 E : ( E ) { $2 }

| i n t { Number $1 }

| E op E { O p e r a t o r $2 $1 $3 }

Hier gibt es nur ein Nichtterminales („E“). In der ersten Zeile ist der Typ auf Exprfestgelegt. Dies ist für Happy obligatorisch, ermöglicht aber eine Typüberprü- fung durch funny. Zu überprüfen ist, ob die Haskellcodeabschnitte in geschweiften Klammern der Ableitungsregeln Expr und damit dem Typ des Nichtterminalen entsprechen.

Die erste Regel lautet „ E : ( E ) “ und der Haskellcodeabschnitt „ { $2 } “. „$2“

verweist auf das zweite Element der Rechten Seite der Ableitungsregel „ ( E ) “ und damit auf „E“. „E“ ist ein Nichtterminales vom Typ Expr. Damit ist der Typ erfüllt und diese Regel korrekt.

(18)

Die zweite Regel lautet „ E : int “ und der Haskellcodeabschnitt dazu „ { Num- ber $1 } “. Ermittelt wird zunächst der Typ von „Number“. Er ist Int -> Expr.

Der zweite TypExprist der Ausgabetyp, der erste bezieht sich auf das Argument.

Somit stimmt der erwartete mit dem gefundenen Ausgabetyp überein.

Zu prüfen bleibt das Argument. Es wird einInterwartet. Das Argument lautet

„$1“ und bezieht sich auf das erste Element der rechten Seite der Ableitungsregel und damit auf „int“.

„int“ ist ein Token mit dem Haskellcodeabschnitt „ { TokenInt $$ } “. „TokenInt“

erwartet einenInt. Durch das $$-Pattern wird dieserIntweitergegeben. Der Typ von „int“ ist damit Int und entspricht dem erwarteten Typ. Auch diese Regel ist also typkonsitent.

Auch bei der letzten Regel „ E: E op E “ muss der Ausgabetyp des Haskellcode- abschnitts „ { Operator $2 $1 $3 } “ dem erwarteten Typ Expr entsprechen. Der Typ von Operator istChar -> Expr -> Expr -> Expr. Der letzte Typ (Expr) ist der Ausgabetyp und entspricht dem erwarteten. Die anderen Typen beziehen sich auf die drei Argumente, die „Operator“ übergeben werden (Charauf „$2“,Exprauf

„$1“ und Expr auf „$3“).

„$2“ verweist auf „op“ und „op“ gibt analog zu „int“ durch das $$-Pattern den TypCharweiter. „$1“ und „$3“ beziehen sich jeweils auf ein „E“. „E“ ist ein Nicht- terminales vom TypExpr. Damit wird auch in den Argumenten der erwartete Typ erfüllt.

Wir halten fest, dass in der Regel auf zwei Arten von Zeichen verwiesen wird:

auf Nichtterminale mit Ableitungsregeln und auf Tokens mit einem $$-Pattern.

Es kann auch auf ein Token ohne $$-Pattern verwiesen werden. Dann wird das gesamte Token weitergegeben.

(19)

4 Praktische Umsetzung

Im Folgenden wird der Code des erstellten Programms behandelt. Dafür betrachten wir zunächst funktionale Grundlagen und danach einen exemplarischen Ausschnitt des Codes.

4.1 Typüberprüfung mit Haskell

Wir benötigen eine automatische Typüberprüfung des geparsten Happycodes. Da- für betrachten wir nun, welche Mechanismen Haskell bereithält, den Typ zu über- prüfen.

4.1.1 Typ über einen Konsolenbefehl ermitteln

Über die Konsole lässt sich der Typ einer Funktion oder eines Konstruktors ermit- teln. Der Compiler für Haskell heißt „Glasgow Haskell Compiler“ kurz „GHC“[2].

Die interaktive Version heißt „GHCi“, dabei steht das „i“ für „interactive“.

In diesem interaktiven Compiler gibt es die Möglichkeit, den Typ einer Funktion oder eines Konstruktors zu ermitteln. Die Syntax hierfür lautet: „:t <Funktions- /Konstruktorname>“. „t“ steht für „type“ und kann auch augeschrieben werden.

Die Funktion „isSpace“ aus dem Modul „Data.Char“ erwartet zum Beispiel einen Char und gibt einen Bool aus, der angibt, ob die Eingabe ein Leerzeichen ist. Die Typabfrage sieht folgendermaßen aus:

Main> :t isSpace isSpace :: Char -> Bool Main>

Der doppelte Doppelpunkt „::“ liest sich als „ist vom Typ“. „isSpace“ ist also vom TypChar -> Bool. Das bedeutet, dass der InputtypCharund der Outputtyp Bool ist. Bei Konstruktoren ist die Syntax analog. So lässt sich über die Konsole der Typ einer Funktion oder eines Konstruktors ermitteln.

4.1.2 Typ über einen Konsolenbefehl überprüfen

Bei einzelnen Instanzen (z.B. „3“ oder „’c’“ ) lässt sich der Typ auch mit dem

„:t“-Befehl ermitteln. Zusätzlich gibt es die Möglichkeit, ihren Typ mit der „::“- Schreibweise festzulegen. Bei einem falschen Typ gibt GHCi eine Fehlermeldung aus, kommt diese nicht, ist der Typ korrekt.

In der Konsole sieht das folgendermaßen aus:

(20)

Main> 3.1 :: Int

<interactive>:2:1:

No instance for (Fractional Int) arising from the literal ‘3.1’

In the expression: 3.1 :: Int

In an equation for ‘it’: it = 3.1 :: Int Main> 3 :: Int

3

Main> :t 3 3 :: Num a => a Main>

„3.1“ ist kein Int. Daher ruft die Eingabe „3.1 :: Int“ einen Fehler hervor.

„3“ ist ein Int, daher ist die Eingabe „3 :: Int“ korrekt. Die Ausgabe des „:t“- Befehls berücksichtigt, dass „3“ auch andere Zahlentypen haben kann. Zur reinen Überprüfung des Typs ist dies nicht hilfreich.

Auf diese Weise lässt sich der Typ über die Konsole überprüfen.

4.1.3 Haskellfunktion für Konsolenbefehle

Ist der Happycode geparst, liegt der Typ (z.B. Int oder Tree) und die Instanz (z.B. „3“ oder „Leaf 3“) als String vor. Aufgabe ist es also, diese Strings auf Typkonsistenz zu prüfen.

Haskell bietet über das Modul „System.Process“[1] die Möglichkeit, Konsolen- eingaben zu simulieren. So lassen sich die oben erklärten Konsolenbefehle durch eine Haskellfunktion auswerten:

r e a d P r o c e s s " g h c i " ["Main . hs "] (" 3 : : I n t ")

„Main.hs“ ist dabei der Dateiname des Haskellmoduls und „3 :: Int“ der Konso- lenbefehl, der simuliert werden soll. Die Ausgabe dieser Funktion ist ein String.

4.2 Programmcode von funny

Da das Programm zu umfangreich ist, um es im Ganzen vorzustellen, wird zunächst eine Übersicht über den Programmablauf gegeben. Anschließend wird exemplarisch der Codeabschnitt vorgestellt, der die Tokens überprüft. Für das Verständnis un- wichtige Abschnitte sind durch Pseudocode in geschweiften Klammern abgekürzt.

(21)

4.2.1 Übersicht über den Programmablauf Kurz gesagt führt funny folgende Schritte aus:

Der direkt übernommene Haskellcodewird in die neu erzeugte „_flo.hs“- Datei gespeichert. Diese enthält den Haskellcode, der von Happy übernommen wird und damit in der Regel die benötigten Module und die Datentypen.

Die Tokens werden durch eine Happygrammatik geparst und in den im Fol- genden erklärten Datentyp gespeichert und auf Typkonsistenz überprüft.

Die Ableitungsregelnwerden auf ähnlichem Weg wie die Tokens geparst und überprüft.

Happy kann auf Wunsch ausgeführt werden. Dafür erscheint eine Frage unab- hängig von der Anzahl und Schwere den Fehlermeldungen.

4.2.2 Datentyp

Betrachten wir zunächst den zugrunde liegenden Datentyp des geparsten Token- abschnitts. Wichtig ist, dass es sich nicht um einen Tokentyp handelt, der Input eines Happyprogramms ist, sondern um einen Parsebaum des geparsten Token- abschnitts. Es ist also der Output des Happyprogramms, das den Tokenabschnitt parst.

d a t a Expr = Toks Expr Expr

| Tok L o c a t e d S t r i n g L o c a t e d S t r i n g d e r i v i n g(Show)

Der Datentyp ist eine Baumstruktur, die nur in den Blättern Daten enthält.

In den Blättern sind zwei LocatedString. Der Erste beschreibt das Token selbst und der Zweite den Haskellcodeabschnitt.

EinLocatedStringenthält einenStringund die Position aus Zeile und Spalte als Zweitupel vom Typ Int. Dies ist für die Fehlerausgabe zentral.

Ein Blatt dieses Parserbaumes könnte zum Beispiel so aussehen:

Tok ( L o c S t r " i n t " ( 8 , 9 ) ) ( L o c S t r " TokenInt $$ " ( 8 , 2 2 ) )

4.2.3 Rekursive Hauptfunktion „testToks“

Die Hauptfunktion ist „testToks“. Sie geht rekursiv in alle Tokens und ruft für jedes Token die Funktion „testToksIntern“ auf.

t e s t T o k s : : TokMod . Expr > S t r i n g > S t r i n g > IO ( ) t e s t T o k s ( Toks a b ) tokenType f i l e = do

(22)

t e s t T o k s a tokenType f i l e t e s t T o k s b tokenType f i l e

5

t e s t T o k s ( Tok _ hsk ) tokenType f i l e =

t e s t T o k s I n t e r n ( p a r s e r T y p e $ lexType $ hsk ) [ tokenType ] f i l e hsk

4.2.4 Rekursive Unterfunktion „testToksIntern“

Die Funktion „testToksIntern“ erhält im Wesentlichen den geparsten Haskellcode (vom Typ TypeTree) und die erwarteten Typen („tokenType“) als String. Die Aufgabe dieser Funktion ist es, zu überprüfen, ob der Haskellcode den erwarteten Typ erfüllt.

Beim ersten Aufruf ist „tokenType“ der allgemeine Tokentyp, bei zusammen- gesetzten Typen kann die Liste auch länger werden. „file“ und „hsk“ werden nur übergeben, um den Typ überprüfen zu können und den Fehler mit der richtigen Zeilennummer ausgeben zu können.

Der Haskellcode wird durch eine Happygrammatk in einen Typenbaum ge- parst. Diese unterscheidet zwischen einzelnen Typen (SingleType) und Typen mit Argumenten (ComposedType).

Außerdem lässt er „_“ und „$$“ zu. Beides wird akzeptiert, sofern noch genau ein Typ erwartet wird.

t e s t T o k s I n t e r n : : TypeTree > [S t r i n g] > S t r i n g > L o c a t e d S t r i n g >

IO ( )

t e s t T o k s I n t e r n ( S i n g l e T y p e nam) tokenType f i l e hsk = do { u e b e r p r u e f e den Ausgabetyp }

5 t e s t T o k s I n t e r n ( ComposedType nam a r g ) tokenType f i l e hsk = do { u e b e r p r u e f e den Ausgabetyp }

t e s t A r g I n t e r n a r g { e r m i t t l e e r w a r t e t e Typen d e r Argumente } f i l e hsk

t e s t T o k s I n t e r n _ tokenType f i l e hsk = do

10 { u e b e r p r u e f e d i e Anzahl d e r e r w a r t e t e n Typen }

4.2.5 Verschränkt-rekursive Funktion „testArgIntern“

Die Argumente sind in einem rekursiven Datentyp gespeichert, der wiederum Typenbäume enthält. Daher ruft „testArgIntern“ neben sich selbst auch „testToks- Intern“ auf.

(23)

t e s t A r g I n t e r n : : Arguement > [S t r i n g] > S t r i n g > L o c a t e d S t r i n g >

IO ( )

t e s t A r g I n t e r n ( Args a b ) tokenType f i l e hsk = do t e s t A r g I n t e r n a [head tokenType ] f i l e hsk t e s t A r g I n t e r n b (t a i l tokenType ) f i l e hsk

5

t e s t A r g I n t e r n ( Arg t r e e ) tokenType f i l e hsk = t e s t T o k s I n t e r n t r e e tokenType f i l e hsk

(24)

5 Installation und Ausführung

Es gibt verschiedene Möglichkeiten, funny anzuwenden. Auf diese wird im folgen- den Abschnitt eingegangen.

5.1 funny als Funktion eines Moduls

funny ist zunächst eine Funktion in einem Haskell-Modul. Ist dieses Modul einge- bunden, kann die Funktion „funny“ mit dem Dateinamen der „.y“-Datei als Argu- ment aufgerufen werden. Dies ist auch ohne Weiteres im interaktiven Haskellcom- piler „GHCi“ möglich.

Ein solcher Funktionsaufruf kann folgendermaßen aussehen:

$*Main> funny "deutsch.y"

5.2 funny installieren

Eine andere Möglichkeit ist es, funny zu installieren und direkt über die Konsole aufzurufen. Dies wird durch das „cabal“-Paketsystem[5] ermöglicht. Die Schritte, die beim Erstellen und Installieren benutzt wurden, sind nachzulesen unter: [3]

Es folgen die Schritte, die für den Anwender wichtig sind: [3]

Das Paketsystem cabal sollte bereits installiert sein, da es zum Haskellpaket gehört und sich auch Happy nicht ohne cabal installieren lässt. Es sollte aber ein Update über den Konsolenbefehl „cabal update“ durchgeführt werden.

Als nächstes muss funny installiert werden. Das funktioniert über die cabal- sandbox. Folgende Befehle müssen dafür im Ordner, in dem funny gespeichert ist, ausgeführt werden:

$ cabal sandbox init

$ cabal install -j

Nun kann funny über die cabal-sandbox ausgeführt werden:

$ .cabal-sandbox/bin/funny deutsch.y

Dabei spielt es keine Rolle, ob der Dateiname in Anführungszeichen gesetzt wird.

Nun kann der Aufruf von funny mit folgendem Befehl erleichtert werden:

(25)

$ sudo cp .cabal-sandbox/bin/funny /usr/local/bin/funny

So kann funny von überall ausgeführt werden, ohne auf das sandbox-Verzeichnis zu verweisen. Der Befehl zur Ausführung von funny sieht nun folgendermaßen aus:

$ funny deutsch.y

(26)

6 Fehlermeldungen

Im Folgenden werden die möglichen Fehlermeldungen gezeigt. Hierfür wird die Da- tei „deutsch.y“ benutzt, die im Anhang zu finden ist. Um die Fehler zu provozieren, werden einzelne Zeilen geändert, die hier in geänderter Form zusammen mit den entsprechenden Fehlermeldungen zu sehen sind.

6.1 Fehlermeldungen bei Happy

Bevor wir uns mit den benutzerfreundlichen Fehlermeldungen beschäftigen, wol- len wir uns anschauen, welche Fehlermeldungen von Happy und Haskell generiert werden. Dafür wurde die Happygrammatik im Anhang minimal geändert. Happy akzeptiert sie weiterhin und erzeugt eine Haskelldatei. Wird diese gestartet, wird folgende Fehlermeldung ausgegeben:

$ deutsch.hs:362:37:

$ Couldn’t match expected type ‘Satz’ with actual type ‘Token’

$ In the second argument of ‘Reihe’, namely ‘happy_var_2’

$ In the first argument of ‘HappyAbsSyn4’, namely

$ ‘(Reihe happy_var_1 happy_var_2)’

$ Failed, modules loaded: none.

Dieser Fehler verweist auf die Zeile 362 im generierten Haskellcode. Zum Ver- gleich: Der Happycode umfasst 101 Zeilen. Schaut man dort nach in der Hoffnung, etwas zu finden, das an den selbst geschriebenen Happycode erinnert, findet man Folgendes:

happyReduce_2 = happySpecReduce_3 5 happyReduction_2 happyReduction_2 _

( HappyTerminal happy_var_2 )

360 ( HappyAbsSyn4 happy_var_1 )

= HappyAbsSyn4

( R e i h e happy_var_1 happy_var_2 )

Dieser Code ist eine Reduktionsregel und hat als solche wenig mit dem Happy- code zu tun. Weder aus der Fehlermeldung noch aus dem Haskellcode ist ersichtlich, dass der Index falsch war:

Sa : : { S a t z }

Sa : Sa und Sa { R e i h e $1 $2 }

(27)

Gemeint war „Reihe $1 $3“. Hingegen verweist „$2“ auf „und“ und damit auf ein terminales Token. Folglich ist die Fehlermeldung korrekt, hilft allerdings nicht weiter.

6.2 Fehlermeldungen bei funny

Betrachten wir nun Fehlermeldungen, wie sie von dem erstellten Programm aus- gegeben werden.

6.2.1 „type missmatch error“

Ein „type missmatch error“ tritt auf, wenn der erwartete Typ nicht erfüllt wird.

’ . ’ { TokenSymbol " . " }

„TokenSymbol“ erwartet in diesem Modul einen Char. Hier bekommt es einen String (=[Char]) übergeben. Die Fehlermeldung sieht folgendermaßen aus:

$ <interactive>:2:1:

$ Couldn’t match expected type ‘Char’ with actual type ‘[Char]’

$ In the expression: ".":: Char

$ In an equation for ‘it’: it = ".":: Char

$ type missmatch error

$ 21.22: >>TokenSymbol "."<<

$ in: >"."<

$ Expected: Char

$ Found: [Char]

Der erste Teil der Fehlermeldung (Z. 1-4) wird von Haskell beim Überprüfen des Typs produziert. Er entstammt nicht dem Happycode und liefert daher auch keine Informationen über den Codeabschnitt, der den Fehler produziert hat.

Um den Fehler einordnen zu können, gibt es den zweiten Teil der Fehlermeldung (Z. 5-9). Er zeigt die Zeile und Spalte des fehlerhaften Codeabschnitts an und das genaue Element, das den Fehler verursacht.

6.2.2 „type missmatch error“ mit verschiedenen möglichen Ursachen Um einen komplexeren „type error“ zu betrachten, der im Grammatikabschnitt auftreten kann, greifen wir das Beispiel aus Abschnitt 6.1 wieder auf:

(28)

Sa : : { S a t z }

Sa : Sa und Sa { R e i h e $1 $2 }

Zur Erinnerung: Hier verweist „$2“ auf das Token „und“. Da das Token „und“

kein $$-Pattern hat, übergibt es sich selbst, und damit den Typ Token. Erwartet wird aber der Typ Satz. Es ist also ein „type missmatch error“.

In diesem Fall liegt der Fehler allerdings woanders: Der Index ist falsch, gemeint ist „$3“. Außerdem könnte der Fehler darin liegen, dass das Token „und“ keinen Wert übergibt. Die Fehlermeldung gibt daher beide Stellen aus.

$ type missmatch error

$ 31.21: >>Reihe $1 $2<<

$ 31.6: >>Sa und Sa<<

$ 20.22: >>TokenWord "und"<<

$ in: >$2<

$ in: >und<

$ Expected: Satz

$ Found: Token

$ Hint: Maybe wrong index in rule or missing $$-pattern in token

Diese Fehlermeldung verweist sowohl auf die Ableitungsregel mit dem Index- fehler als auch auf das Token und dessen Deklaration, die auch fehlerhaft sein könnte. Zusätzlich gibt sie den Hinweis, dass der Fehler vielleicht kein Typfehler ist, sondern ein Indexfehler oder das $$-Pattern vergessen wurde.

6.2.3 „type error“

Ein „type error“ tritt auf, wenn ein Typ nicht zugeordnet werden kann.

p f e r d { D i e s I s t K e i n C o n s t r u c t o r " P f e r d " }

„DiesIstKeinConstructor“ ist in diesem Modul nicht definiert. Es wird eine Fehlermeldung ausgegeben, die uns darauf aufmerksam macht und mitteilt, wel- cher Typ erwartet wird:

$ <interactive>:1:1:

$ Not in scope: data constructor ‘DiesIstKeinConstructor’

(29)

$ type error

$ 8.22: >>DiesIstKeinConstructor "Pferd"<<

$ in: >DiesIstKeinConstructor<

$ Couldn’t match with: Token

Auch diese Fehlermeldung besteht ganz analog aus zwei Teilen. Der erste Teil kann uns bei kleinen Tippfehlern das richtige vorschlagen.

p f e r d { TokenWert " P f e r d " }

Bei dem oberen Beispiel bekommen wir folgende Fehlermeldung:

$ <interactive>:1:1:

$ Not in scope: data constructor ‘TokenWert’

$ Perhaps you meant ‘TokenWord’ (line 81)

$ type error

$ 8.22: >>TokenWert "Pferd"<<

$ in: >TokenWert<

$ Couldn’t match with: Token

Hier wird der richtige Konstruktor mit der richtigen Zeile vorgeschlagen. Es können auch Konstruktoren vorgeschlagen werden, die den falschen Typ haben.

Das liegt daran, dass der Vorschlag von Haskell produziert wird und dies nicht unter Berücksichtigung des erwarteten Typs.

6.2.4 „argument count error“

Eine weitere Fehlerquelle ist die Anzahl der Argumente. Ein Konstruktor, der zum Beispiel einen String erwartet, aber kein Argument erhält, produziert einen Fehler:

p f e r d { TokenWord }

Die obere Codeänderung produziert folgenden Fehler:

$ argument count error

$ 8.22: >>TokenWord<<

$ in: >TokenWord<

$ too few arguments

(30)

Hier fehlt der erste Teil, da der Typchecker von Haskell nicht gebraucht wird.

Analog produziert das Vorhandensein von zu vielen Argumenten einen ähn- lichen Fehler. Hier wird der Typ der überzähligen Argumente nicht geprüft, im Gegensatz zu den ersten, erwarteten Argumenten. Folgende Zeile produziert also zwei Fehlermeldungen:

p f e r d { TokenWord d i e s h a t k e i n e n typ }

Eine Fehlermeldung entsteht durch den Typ des ersten Arguments, eine zweite durch das Vorhandensein der übrigen:

$ <interactive>:1:1: Not in scope: ‘dies’

$ type error

$ 8.22: >>TokenWord dies hat keinen typ<<

$ in: >dies<

$ Couldn’t match with: String

$

$ argument count error

$ 8.22: >>TokenWord dies hat keinen typ<<

$ in: >TokenWord<

$ in: >hat<

$ too many arguments

Ausgegeben wird hier neben dem Konstruktor, der zu viele Argumente hat, auch das erste überzählige Argument.

6.2.5 „index error“

Ein Fehler, der nicht im eigentlichen Sinne zu den Typfehlern gehört, ist der Index- fehler. Ist der Index zu hoch, verweist also auf ein Element, das nicht existiert, kann eine Typüberprüfung nicht stattfinden.

Ein Codeabschnitt, der diesen Fehler enthält, ist der folgende:

S : : { S a t z }

28 S : Sa ’ . ’ { $5 }

Die rechte Seite der Ableitungsregel hat hier zwei Zeichen. „$5“ verweist auf das fünfte und dieses gibt es nicht. Ein Verweis auf ein anderes Zeichen mit dem falschen Typ ist kein Indexfehler, sondern ein „typ missmatch error“ und wurde bereits behandelt. funny gibt daher folgende Fehlermelung aus:

(31)

$ index error

$ 28.21: >>$5<<

$ in: >$5<

$ Index out of range

Auch Happy gibt eine Fehlermeldung mit Zeilennummer des Happycodes aus, allerdings stimmt in diesem Beispiel die Zeile nicht:

$ deutsch.y: 30: $5 out of range

(32)

7 Fazit und Ausblicke

7.1 Fazit

Zusammenfassend lässt sich sagen, dass auf diesem Wege viele Fehler gefunden und vermieden werden können. Durch die gut verständlichen Fehlermeldungen ist es möglich, die Fehler zu lokalisieren und zu korrigieren. Das erleichtert die Arbeit mit Happy wesentlich, da Typfehler sehr häufig auftreten und schwer zu finden sind.

7.2 Ausblicke

Obwohl funny die Fehlersuche sehr vereinfacht, findet keine vollständige Fehler- überprüfung statt. Es gibt Sonderfälle, in denen der Typ nicht korrekt geprüft werden kann sowie häufige Fehler außerhalb der Typfehler, die nicht gefunden werden. Im Folgenden betrachten wir zunächst die Sonderfälle, bei denen eine Typüberprüfung mit funny fehlschlägt. Des Weiteren behandeln wir andere Feh- lerquellen als die der Typkonsistenz.

7.2.1 Noch offene Probleme

funny ist auf Konstruktoren optimiert, da dies die häufigste und fehleranfälligste Verwendung von Happy ist. Happy kann aber auch mit Funktionen arbeiten. Das ist solange kein Problem, wie die Funktionen einen festen Typ haben.

Allgemeine Operatoren wie „+“ oder „:“, die für alle Zahlen oder alle Typen gül- tig sind, werden von funny nicht richtig erkannt. Dies führt zu Fehlermeldungen bei korrektem Code. Hier müsste man die Typerkennung grundlegend überarbeiten, um Typvariablen als solche zu erkennen und entsprechend zu interpretieren.

Außerdem werden Operatoren, die nicht mit einem Leerzeichen getrennt wer- den, nicht als Operatoren erkannt und folglich falsch interpretiert. Um das zu lösen, müsste der Lexer alle entsprechenden Haskellfunktionen kennen.

7.2.2 Ergänzende Arbeiten

Die Aufgabe dieser Arbeit ist es, Typfehler zu finden. Offensichtliche Syntaxfehler werden auch gefunden, eine allgemeine Fehlersuche findet nicht statt.

Neben Typfehlern gibt es noch weitere häufige Fehler. Dazu gehören vor al- lem unbenutzte Regeln, Schiebe-Reduziere- und Reduziere-Reduziere-Fehler. Hap- py gibt aus, wie viele Fehler dieser Art auftreten, gibt aber keine Hilfestellung, an welcher Stelle sie entstehen und wie sie gelöst werden können. Eine Arbeit, die die vorliegende ergänzt, könnte Fehler dieser Art überprüfen.

(33)

8 Anhang

8.1 Beispielhappycode „deutsch.y“

Hier folgt der Beispielhappycode, der für die Fehlermeldungen genutzt wurde. Die Grammatik ist an die deutsche Sprache angelehnt, lässt aber auch Sätze zu, die grammatikalisch falsch sind.

{

module P a r s e r where i m p o r t Data .Char }

5 %name p a r s e r

%t o k e n t y p e { Token }

%t o k e n

p f e r d { TokenWord " P f e r d " }

g u r k e n s a l a t { TokenWord " G u r k e n s a l a t " }

10 k a r t o f f e l s a l a t { TokenWord " K a r t o f f e l s a l a t " } f r i s s t { TokenWord " f r i s s t " }

mag { TokenWord "mag" } k e i n e n { TokenWord " k e i n e n " } k e i n { TokenWord " k e i n " }

15 d e r { TokenWord " d e r " } das { TokenWord " das " } n i c h t { TokenWord " n i c h t " } w e i l { TokenWord " w e i l " } d a s s { TokenWord " d a s s " }

20 und { TokenWord "und" }

’ . ’ { TokenSymbol ’ . ’ }

’ , ’ { TokenSymbol ’ , ’ }

’ ; ’ { TokenSymbol ’ ; ’ }

%l e f t und ’ , ’ ’ ; ’

25%l e f t d a s s w e i l

%%

S : : { S a t z }

S : Sa ’ . ’ { $1 }

30 Sa : : { S a t z }

Sa : Sa und Sa { R e i h e $1 $3 }

| Sa ’ , ’ Sa { R e i h e $1 $3 }

| Sa ’ ; ’ Sa { R e i h e $1 $3 }

| Sa ’ , ’ NS { G e f u e g e $1 $3 }

35 | NS ’ , ’ Sa { G e f u e g e $3 $1 }

| Su P O { E i n z e l S a t z $1 $2 $3 }

| Su P n i c h t O { E i n z e l S a t z $1 ( NPred $2 ) $4 } NS : : { S a t z }

40 NS : w e i l Su O P { E i n z e l S a t z $2 $4 $3 }

| d a s s Su O P { E i n z e l S a t z $2 $4 $3 }

(34)

| w e i l Su O n i c h t P { E i n z e l S a t z $2 ( NPred $5 ) $3 }

| d a s s Su O n i c h t P { E i n z e l S a t z $2 ( NPred $5 ) $3 }

45 Su : : { S u b j e c t }

Su : p f e r d { Subj " P f e r d " }

| g u r k e n s a l a t { Subj " G u r k e n s a l a t " }

| k a r t o f f e l s a l a t { Subj " K a r t o f f e l s a l a t " }

| das p f e r d { Subj " P f e r d " }

50 | d e r g u r k e n s a l a t { Subj " G u r k e n s a l a t " }

| d e r k a r t o f f e l s a l a t { Subj " K a r t o f f e l s a l a t " }

| k e i n Su { NSubj $2 }

O : : { O b j e c t }

55 O : g u r k e n s a l a t { Obj " G u r k e n s a l a t " }

| k a r t o f f e l s a l a t { Obj " K a r t o f f e l s a l a t " }

| k e i n e n O { NObj $2 } P : : { P r e d i c a t }

60 P : f r i s s t { Pred " f r e s s e n " }

| mag { Pred "mö gen " } {

happyError : : [ Token ] > a

65 happyError [ ] = e r r o r " p a r s e e r r o r : u n e r w a r t e t e s Ende"

happyError x s = e r r o r (" p a r s e e r r o r : " ++ show x s ) d a t a S a t z = E i n z e l S a t z S u b j e c t P r e d i c a t O b j e c t

| R e i h e S a t z S a t z

70 | G e f u e g e S a t z S a t z d e r i v i n g(Show)

d a t a S u b j e c t = Subj S t r i n g

| NSubj S u b j e c t

75 d e r i v i n g(Show)

d a t a O b j e c t = Obj S t r i n g

| NObj O b j e c t d e r i v i n g(Show)

80

d a t a P r e d i c a t = Pred S t r i n g

| NPred P r e d i c a t d e r i v i n g(Show)

85 d a t a Token = TokenWord S t r i n g

| TokenSymbol Char d e r i v i n g(Show)

l e x e r : : S t r i n g > [ Token ]

90 l e x e r x s = l e x I n t e r n x s " "

(35)

l e x I n t e r n : : S t r i n g > S t r i n g > [ Token ] l e x I n t e r n [ ] " " = [ ]

l e x I n t e r n [ ] p u f = [ TokenWord p u f ]

95

l e x I n t e r n ( ’ ’ : x s ) " " = l e x I n t e r n x s " "

l e x I n t e r n ( ’ ’ : x s ) p u f = TokenWord p u f : l e x I n t e r n x s " "

100 l e x I n t e r n ( x : x s ) p u f

| x ‘elem [ ’ , ’ , ’ . ’ , ’ ; ’ ] = TokenWord p u f : TokenSymbol x : l e x I n t e r n x s " "

| o t h e r w i s e = l e x I n t e r n x s ( p u f++[x ] ) }

deutsch.y

(36)

Literatur

[1] https://hackage.haskell.org/package/process-1.6.0.0/docs/system- process.html. abgerufen am 8. April 2016.

[2] https://wiki.haskell.org/GHC. abgerufen am 8. April 2016.

[3] https://wiki.haskell.org/How_to_write_a_Haskell_program. abgerufen am 9. April 2016.

[4] https://www.haskell.org. abgerufen am 8. April 2016.

[5] https://www.haskell.org/cabal/users-guide/. abgerufen am 9. April 2016.

[6] https://www.haskell.org/happy. abgerufen am 8. April 2016.

[7] https://www.haskell.org/happy/doc/html/sec-grammar.html. abgerufen am 8. April 2016.

[8] Richard Bird. Thinking Functionally with Haskell. Cambridge University Press, 2014.

[9] Prof. Dr. Schmidt-Schauß. Unterlagen zu Programmierung 2 im Sommerse- mester 2016.

[10] Prof. Dr. Nicole Schweikard. Diskrete Modellierung. Eine Einführung in grundlegende Begriffe und Methoden der Theoretischen Informatik. Skript zur Vorlesung, Februar 2013.

[11] Uwe Schöning. Theoretische Informatik - kurz gefasst. Spektrum Akademi- scher Verlag, 5th edition, 2008.

[12] Simon Thompson. Haskell: the Craft of Functional Programming. Addison- Wesley Professional, 3rd edition, 2011.

[13] Manuel M. T. Chakravarty und Gabriele C. Keller. Einführung in die Pro- grammierung mit Haskell. Pearson Studium, Juli 2004.

[14] Alfred V. Aho und Ravi Sethi und Jeffrey D. Ullman.Compilers — Principles, Techniques, and Tools. Addison-Wesley, 1988.

Referenzen

ÄHNLICHE DOKUMENTE

Dasselbe gilt für eine regelmä- ßige Überprüfung der Brems- anlage: Sind die Beläge oder die Bremsscheiben verschlissen ist keine optimale Funktion mehr gewährleistet - das

Wich- tig ist auch, dass sich Betroffene nicht hängen lassen, sondern sich mit Freunden treffen oder etwas unternehmen.. Trotz an- dauernder Müdigkeit sollten sie sich nicht

Viren brauchen einen Wirt, und bei diesem richten sie in ihrer Vermehrungswut oft schreck- liche Schäden an?. Der Essig der vier Diebe

aminomed – die medizinische Kamillenblüten-Zahncreme Personengruppen mit erhöhtem Risiko für Zahnfleisch- erkrankungen wissen oft gar nicht, dass sie besonders gefährdet sind!.

Wearing shoes that are too tight, or socks that don’t fit and bunch up and cause pressure, high- heels are the main cause for calluses on the underside of the toes, even

Glück selbst ist aber im Moment und nicht etwas, worüber wir erst groß nachdenken müssen, ob wir glücklich sind oder nicht, eine Sache, die Glück auf jeden Fall definiert, ist,

Wenn dem hellenischen Wirtschaftskreislauf jetzt noch weitere Milliarden Euro entzogen werden, droht der Stillstand.. Zwei bis vier Prozent Wachstumsverlust kostet

Es gibt immer noch Wirtschaftsinstitute und Arbeitgeberverbände sowie einzelne Unionspolitiker, die sich einfach nicht damit abfinden wollen, dass die