Parser-Kombinatoren, State-Monade. Eine kleine¨Ubung in Haskell

Werbung
Parser-Kombinatoren, State-Monade.
Eine kleine Übung in Haskell
SS 2012
Sven Eric Panitz
Hochschule Rhein-Main
Version 12. Juni 2012
Dieses Dokument beschreibt ein kleines Beispiel für eine Haskell-Anwendung. Die
Anwendung stellt einen Interpreter für eine kleine Programmiersprache dar. Diese
Sprache weist folgende Elemente auf:
• Funktionsdefinitionen und Funktionsaufrufe
• veränderbare lokale Variablen mit Zuweisungen
• While-Schleifen
• If-Bedingungen
• Infix-Operatoren mit der gängigen Präzedenz
Dieses Dokument ist wahrscheinlich nur mit den Vorwissen und den Erklärungen
aus meiner Vorlesung oder mit weiterer Literatur verständlich. Es ist nicht als allgemeines Tutorial angelegt, sondern speziell für die Hörer meiner Vorlesung.
Das Beispiel ist in einem einzigen Haskell-Modul definiert, das die Parserbibliothek,
den eigentlichen Parser und den Interpreter der Sprache enthält.
Der Quelltext dieses Dokuments ist zum einen eine XML-Datei, um das druckbare
Dokument zu erzeugen, zum anderen aber gleichzeitig direkt eine literate Haskell
Quelltextdatei, die direkt dem Haskell-Compiler zum Übersetzen gegeben werden
kann.
Wir importieren drei Standard-Module.
WhileLang.hs
1
2
3
4
5
6
7
>module Main where
> import System
> import Data.Char
> import Data.List
> import Control.Monad.State
8
9
Hier ist lediglich das Modul State von Interesse. Es enthält eine Implementierung
der Zustandsmonade, die im Interpreter Verwendung findet.
Um diese zu benutzen muss zusätzlich zum Haskell-Compiler ghc die Zusatzbibliothek mtl (Haskell monad transformer library for GHC) auf dem Rechner installiert
sein.
2
Kapitel 1
Parser
1.1 Ein Datentyp für Parser
Ein Parser ist eine Funktion, die aus einer Liste von irgendwelchen Token eine Liste von
möglichen Parsergebnissen erzeugt. Ein einzelnes Parsergebnis besteht dabei aus einem Paar,
das das eigentliche Ergebnis enthält, sowie die Liste der restlichen Eingabe-Token, die bisher
noch nicht durch Parser verarbeitet wurden.
Entsprechend lässt sich der Typ eines Parsers allgemein wie folgt definieren:
WhileLang.hs
10
11
12
> newtype Parser token result = P ([token] -> [( result, [token])])
13
14
Aus eher technischen Gründen bezüglich des Typinferenzmechanismus von Haskell, wird für
diese Definition das newtype Konstrukt gewählt (im Gegensatz zu einer Typsynonym-Definition
mit type). Somit ist die eigentliche Funktion des Parsers in einem Konstruktor gekapselt. Um
den Parser auf eine Liste von Token anzuwenden, muss dieser also erst wieder entpackt werden.
Hierzu wird die Funktion parse vorgesehen:
WhileLang.hs
15
16
17
> parse (P p) = p
18
19
1.1.1 Parser als monadischer Typ
Ein Parser eignet sich hervorragend, um ihn zu einer Instanz der Typklasse Monad zu machen.
Ein Parser liefert nach Ausführung ein Ergebnis und zusätzlich eine Liste von Token, die für
weitere anschließende Parser benutzt werden soll. Das Hintereinanderausführen von Parsern
spiegelt sich eins zu eins in dem Operator >>= der Monadenklasse wieder.
Instanz von Monad
Die gesamte Instanzdefinition für Parser lässt sich wie folgt formulieren:
WhileLang.hs
20
21
22
> instance Monad (Parser token) where
1-1
Kapitel 1 Parser
23
24
>
>
return a = P (\xs-> [(a,xs)])
p >>= f = P (\cs -> concat [parse (f a) cs’ |(a,cs’) <- parse p cs])
25
26
Die Funktion return stellt hierbei einen Parser dar, der immer erfolgreich ist, genau ein Ergebnis
erzielt und keinen Token der Eingabe verarbeitet. Damit entspricht return einer Epsilonregeln
in einer Grammatik.
Der Sequenzoperator >>= erzeugt die Sequenz aus zwei Parsern, um einen neuen Parser zu
erzeugen. Das zweite Argument ist allerdings nicht ein Parser, sondern eine Funktion, die mit
dem Ergebnis des ersten Parsers einen zweiten in Sequenz zu setzenden Parser erzeugt.
In der Definition oben, wird zunächst mit parse p cs der erste Parser auf die Eingabe angewendet. Für alle Ergebnisse dieser Anwendung (a,cs’) wird dann mit dem zweiten Operanden
f über den Ausdruck (f a) der zweite Parser erzeugt. Dieser wird auf die Restliste von Token
angewendet: parse (f a) cs’. Da so insgesamt eine Liste von Listen von Ergebnissen erzeugt
wird, ist auf diese noch die Standardlistenfunktion concat anzuwenden.
Die Plus-Monade
Monaden sind für verzögert ausgewertete Programmiersprachen dazu entwickelt worden, um
die Sequenzierung von Aktionen auszudrücken. Im Falle der Parser bedeutet dieses, das nacheinander Ausführen von Parsers auf eine Eingabe.
Im Gegensatz zur sequenziellen Abarbeitung gibt es auch eine parallele Abarbeitung. Insbesondere bei Parsern wollen wir ausdrücken, das zwei Parser alternativ versucht werden sollen. Das
bedeutet im weitesten Sinne, nicht sequenziell hintereinander sondern parallel nebeneinander
her. Die beiden Ergebnissen sollen dann zu einen einheitlichen Ergebnis gesammelt werden.
Um eine solche paralle Abarbeitung auszudrücken gibt es die Typklasse MonadPlus. Es bietet
sich an, für diese auch eine Instanz unserer Parser zu definieren.
Zunächst die komplette Definition:
WhileLang.hs
1
2
3
4
5
> instance MonadPlus (Parser token) where
>
mzero = P (\xs-> [])
>
mplus (P p1) (P p2) = P (\xs -> p1 xs ++ p2 xs)
6
7
Die Funktion mzero drückt ein Scheitern des Parsers aus. Das Ergebnis ist die leere Liste, also
eine Liste ohne erfolgreiche Verarbeitung mit einem Parser.
Die Funktion mplus kombiniert zwei Parser zu einem neuen. Dabei werden beide Parser auf
ein und dieselbe Eingabeliste von Token angewendet und die beiden Ergebnislisten einfach
aneinander gehängt. (p1 xs ++ p2 xs). Man sieht hier sehr schön die parallele Verarbeitung:
p1 xs und p2 xs sind unabhängig und könnten auch parallel ausgewertet werden.
WhileLang.hs
1
2
3
4
5
6
1-2
> infixl 1 |||
> (|||) = mplus
1.2 Parsen eines einzelnen Tokens
Die Funktor
Es gibt noch eine dritte Typklasse, von der unser Parser eine Instanz bilden kann. Dieses ist der
Functor, der die für Listen bekannte Funktion map enthält. Bei Listen wendet diese Funktion
eine übergebene Funktion f auf alle Listenelemente an. Erweitert auf Parsern bedeutet das,
dass eine Funktion auf alle Ergebnisse einer Pars-Aktion angewendet werden soll. Es können
also die Ergebnisse eines Parser anschließend noch mit einer Funktion verändert werden.
Zunächst die Definition:
WhileLang.hs
7
8
9
10
11
12
> instance Functor (Parser t)
>
fmap f p = do
>
r1 <- p
>
return$f r1
where
13
14
Da Parser bereits eine Instanz von Monaden sind, können wir hier zur Definition die do-Notation
benutzen. Zunächst benutze den Parser p und lasse von ihm das Ergebnis geben r1 <- p.
Schließlich erzeuge einen neuen Parser, der dieses Ergebnis mit der Funktion f verändert:
return$f r1.
Man beachte, dass der Operator $ als Standardoperator in Haskell für die Funktionsanwendung
steht. Mit ihm lassen sich Klammern sparen. Statt return (f r1) können wir return$f r1
schreiben.
WhileLang.hs
15
16
17
18
> infixl 2 <*
> (<*) = flip fmap
19
20
1.2 Parsen eines einzelnen Tokens
Bisher haben wir alle möglichen schönen Dinge definiert, die wir mit Parsern machen können,
aber noch keinen einzigen Parser definiert. Die atomare Form eines Parsers ist der Parser, der
genau ein Token erkennt. Aus diesem Parser lassen sich über die Kombinationsmöglichkeiten
von Parsern alle möglichen Parser kombinieren.
Auch diese Definition ist denkbar einfach.
WhileLang.hs
21
22
23
24
25
26
27
28
> token t = P (tokenA t) where
> tokenA _ [] = []
> tokenA t (x:xs)
>
|x==t = [(x ,xs)]
>
|otherwise = []
>
29
30
1-3
Kapitel 1 Parser
Für ein beliebiges Token t wird mit der Funktion token ein Parser erzeugt, der dieses Token
erkennt. Sollte dieser Parser auf eine leere Eingabeliste angewendet werden, so kann er kein
erfolgreiches Ergebnis erzeugen: tokenA t [] = [].
Hat die Eingabeliste des Parsers mindestens ein Token x (tokenA t (x:xs)), so wird dieses mit
dem erwarteten Token verglichen: x==t. Im Erfolgsfall gibt es eine einelementige Ergebnisliste,
mit diesem Token als Ergebnis und den restlichen Token als Liste, der weiter zu verarbeitenden
Token: [(x ,xs)].
Ansonsten zeugt wieder die leere Liste davon, dass keine erfolgreiche Pars-Aktion durchgeführt
werden konnte.
1.3 Weitere Parserfunktionen
In diesem Abschnitt werden weitere Funktionen zum Kombinieren von Parsers definiert, die
typische erweiterte Kontrukte in Grammatiken ausdrücken.
Statt des monadischen Sequenzoperators >>= seien weitere Möglichkeiten, zwei Parser sequentiell zu kombinieren vorgesehen. Zunächst eine Funktion, die zwei Parser sequentiell hintereinander ausführt und beide Ergebnisse schließlich in einem Paar zusammenfügt:
WhileLang.hs
31
32
33
34
> seqPair :: Parser t r1 -> Parser t r2 -> Parser t (r1,r2)
> seqPair p1 p2 = p1 >>= \r1 -> fmap (\r2 -> (r1,r2)) p2
35
36
Für diese Funktion sei auch ein Infixoperator definiert.
WhileLang.hs
37
38
39
40
> infixl 3 -*> (-*-) = seqPair
41
42
Eine zweite Sequenzoperation kombiniert zwei Parser, die jeweils denselben Listentypen als
Ergebnis haben. Die beiden Ergebnislisten werden aneinandergehängt.
WhileLang.hs
43
44
45
46
> seqList :: Parser t [a] -> Parser t [a] -> Parser t [a]
> seqList p1 p2 = fmap (\(r1,r2) -> r1 ++ r2) $seqPair p1 p2
47
48
Ein häufiges Bildungskonstrukt bei Grammatiken ist die n-fache Wiederholung einer ParsAktion.
Wir benutzen in der Definition der entsprechenden Funktion die Möglichkeiten der do-Notation.
Das Ergebnis der Pars-Aktion wird eine Liste der einzelnen Pars-Ergebnisse sein.
WhileLang.hs
49
50
51
1-4
> nullBisNMal p =
1.3 Weitere Parserfunktionen
52
53
54
55
56
57
58
>
>
>
>
>
>
>
(do
r<-p
rs<-nullBisNMal p
return (r:rs)
)
|||
(return [])
59
60
Auf gleiche Weise kann jetzt relativ einfach mit der do-Notation eine Wiederholung mit mindestens einen erfolgreichen Ergebnis definiert werden.
WhileLang.hs
61
62
63
64
65
66
67
> einBisNMal p = do
>
r <- p
>
rs <- nullBisNMal p
>
return (r:rs)
>
68
69
In den vorangegangenen Funktionen wurde die 0 bis n-fache bzw. 1 bis n-fache Wiederholung
eines Parser definiert. Ein weiterer Fall ist die entsprechende 0 bis 1-fache Wiederholung. Als
Ergebnis wird hierfür nicht der Listentyp sondern der Haskell Standardtyp Maybe verwendet.
Entweder ist der Parser p erfolgreich mit geraden dem entsprechenden Ergebnis, oder der Parser
ist zwar erfolgreich, doch das Ergebnis ist Nothing.
WhileLang.hs
70
71
72
> vielleicht p = p <* Just ||| (return Nothing)
73
74
Schließlich wird noch häufig eine Wiederholung mit einem Seperator in Grammatiken definiert.
So etwas findet sich in Komma-getrennten Parameterlisten und Ähnlichen.
Auch hierfür sei eine eigene Funktion definiert. Das Ergebnis des Parsers für den Seperator wird
dabei nicht benötigt. Die Ergebnisliste darf auch leer sein.
WhileLang.hs
75
76
77
78
79
80
81
82
83
> sepBy sepP p =
>
(do
>
r<-p
>
rs <- nullBisNMalW (sepP -*-- p <* snd)
>
return (r:rs)
>
)
>
||| return []
84
85
1-5
Kapitel 1 Parser
1.3.1 Spezielle Parser für String
Wir werden in diesem Beispiel nicht einen Tokenizer haben, der die Eingabedatei zunächst
in einzelnen Token splittet. Stattdessen werden wir direkt auf einem Eingabestring arbeiten.
Damit ist ein Token für uns ein einzelnes Zeichen vom Typ Char. Typische Aufgaben eines
Tokenizers sind damit direkt im Parser ausgedrückt. Das sind:
• Erkennen von Schlüsselwörtern
• Löschen von irrelevantem Zwischenraum (Whitespace)
• Erkennen von Literalen
• Erkennen von allgemeinen Bezeichnern (Variablen-,Funktionsnamen)
Schlüsselwörter
Zunächst sei eine Funktion zum Parsen eines vorgegebenen Schlüsselwortes definiert. Es ist also
eine Sequenz von Zeichen, die Buchstaben des Schlüsselwortes, zu erkennen.
WhileLang.hs
86
87
88
> keyword ws = foldl1 seqList (map (\t -> (token t <* (\x->[x]))) ws)
89
90
Zwischenraum
Wir definieren das Leerzeichen, den Tabulator und das Zeilenende als irrelevanten Zwischenraum. Auch eine beliebige Folge davon ist ein Zwischenraum.
WhileLang.hs
91
92
93
> whitespace = nullBisNMal (msum (map token " \t\n"))
94
95
Man beachte hier die Standardfunktion msum für die Plus-Monade. Diese stellt eine Faltung von
mehreren Parsern mit der Operation mplus dar.
Wir können für einen Parser jetzt eine Funktion definieren, die vor dem zu parsenden Zeichen
noch beliebig viel Zwischenraum löscht.
WhileLang.hs
96
97
98
> removeWhite p = whitespace -*- p <* snd
99
100
Es bietet sich an einen zweiten Sequenzoperator zu definieren, der zusätzlich noch beliebigen
Zwischenraum zwischen den zu parsenden Konstrukten übergeht.
WhileLang.hs
101
102
103
104
1-6
> infixl 3 -*-> (-*--) p1 p2 = p1 -*- removeWhite p2
1.3 Weitere Parserfunktionen
105
106
Auch die Funktionen für unterschiedliche Wiederholungen, seien in Versionen, die Zwischenraum
erlauben, definiert.
WhileLang.hs
107
108
109
110
111
> einBisNMalW p = einBisNMal (removeWhite p)
> nullBisNMalW p = nullBisNMal (removeWhite p)
> sepByW tokP p = sepBy (removeWhite tokP) (removeWhite p)
112
113
Buchstaben und Ziffern
Eine Ziffer ist eines der 10 arabischen Ziffernzeichen.
WhileLang.hs
114
115
116
> ziffer =
msum (fmap token "0123456789")
117
118
Ein alphabetisches Zeichen sei einer der 26 lateinischen Buchstaben in Groß- oder Kleinschreibung.
WhileLang.hs
119
120
121
> alpha = msum (map token ([’a’,’b’..’z’]++[’A’,’B’..’Z’]))
122
123
Für Bezeichner werden oft Zeichen und Ziffernfolgen erlaubt. Dieses ist aus den zwei voran
gegangenen Parsers leicht zu kombinieren
WhileLang.hs
124
125
126
> alphaNum = alpha ||| ziffer
127
128
1-7
Kapitel 1 Parser
1-8
Kapitel 2
Eine kleine While-Sprache
Es soll schließlich eine kleine Sprache mit einer While-Schleife geparst und anschließend interpretiert werden. In diesem Kapitel wird beides implementiert.
2.1 Datentyp für die While-Sprach
Zunächst benötigen wir einen Datentypen, um ein Programm dieser While-Sprache darstellen
zu können. Dieser Datentyp sei Variabel gehalten, über den Typen von Daten, mit dem die
Sprache operiert.
Wir sehen in der Sprache vor:
• Variablen
• Zahlenkonstanten
• Operatorausdrücke
• While-Schleifen
• If-Bedingungen
• Zuweisungsoperationen
• Funktionsaufrufe
Dieses mündet in folgenden Datentypen:
WhileLang.hs
129
130
131
132
133
134
135
136
137
138
> data Prog a=
>
Var String
>
|Zahl a
>
|Op String (a -> a -> a) (Prog a) (Prog a)
>
|WhileEx (Prog a) [Prog a]
>
|IfEx (Prog a) [Prog a] [Prog a]
>
|Assign String (Prog a)
>
|FunCall String [Prog a]
139
140
Zusätzlich soll ein Programm eine Reihe von Funktionsdefinitionen enthalten. Auch für diese
sei ein Datentyp vorgesehen. Eine Funktionsdefinition habe dabei einen Funktionsnamen, eine
Parameterliste und eine Liste von Anweisungen als Funktionsrumpf:
WhileLang.hs
141
142
143
> data FunDef a = FunDef String [String] [Prog a]
2-1
Kapitel 2 Eine kleine While-Sprache
144
145
Eine simple Stringdarstellung für den Datentypen sei wie folgt gegeben:
WhileLang.hs
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
> instance Show a => Show (Prog a) where
>
show (Zahl n) = show n
>
show (Op op o l r ) = "("++show l++" "++op++" "++show r++")"
>
show (Var v) = v
>
show (Assign v p) = v++" := "++show p
>
show (WhileEx bed ps)
>
= "while ("++show bed++"){ "++(concat$(map show) ps)++"}"
>
show (IfEx bed ps1 ps2)
>
= "if ("++show bed++"){\n "++(concat$(map show) ps1)
>
++"}else{\n"++(concat$(map show) ps2)++"\n}"
>
show (FunCall name ps)
>
|null ps = name++"()"
>
|otherwise
>
= name++"("++(foldr1 (\x y -> x++", "++y)(map show ps))++")"
>
> data IntOrBool = I Integer | B Bool deriving (Eq)
>
> instance Show IntOrBool where
> show (I n) = show n++" :: Int"
> show (B b) = show b++" :: Bool"
168
169
2.2 Parser der While-Sprache
2.2.1 Basistoken
WhileLang.hs
170
171
172
173
174
175
> ident = do
>
x <- alpha
>
xs <- nullBisNMal alphaNum
>
return$Var (x:xs)
176
177
WhileLang.hs
178
179
180
181
182
183
184
2-2
> zahl = do
>
zs <- einBisNMal ziffer
>
return$Zahl$I$(read :: String -> Integer) zs
2.2 Parser der While-Sprache
2.2.2 Programme
WhileLang.hs
185
186
187
188
189
190
191
192
193
194
195
> program = do
>
funs <- nullBisNMalW fundef
>
e <- removeWhite prog
>
return (map (\fd@(FunDef name _ _) -> (name,fd)) funs,e)
>
> prog = do
> st <- stat
> sts <- removeWhite$ nullBisNMalW (token ’;’ -*-- stat <* snd)
> return (st:sts)
196
197
WhileLang.hs
198
199
200
201
202
203
204
205
> fundef = do
>
keyword "function"
>
(Var name) <- removeWhite ident
>
ps <- removeWhite params
>
bdy <- removeWhite body
>
return$ FunDef name ps bdy
206
207
WhileLang.hs
208
209
210
211
212
213
214
> params = do
>
token ’(’
>
ps <- sepByW (token ’,’) ident
>
removeWhite$token ’)’
>
return (map (\(Var v)->v) ps)
215
216
2.2.3 Anweisungen
WhileLang.hs
217
218
219
220
221
222
> stat =
ifStat
>
||| whileStat
>
||| assign
>
||| expr
223
224
Zuweisung
WhileLang.hs
225
226
227
> assign = do
2-3
Kapitel 2 Eine kleine While-Sprache
228
229
230
231
>
>
>
>
(Var v) <- ident
removeWhite (keyword ":=")
e<- removeWhite expr
return$ Assign v e
232
233
Schleifen
WhileLang.hs
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
> whileStat = do
>
keyword "while"
>
removeWhite$token ’(’
>
cond <- expr
>
removeWhite$token ’)’
>
bdy <- removeWhite body
>
return$ WhileEx cond bdy
>
> body = do
>
token ’{’
>
sts <- removeWhite prog
>
removeWhite$ token ’}’
>
return sts
249
250
Verzweigungen
WhileLang.hs
251
252
253
254
255
256
257
258
259
260
261
> ifStat = do
>
keyword "if"
>
removeWhite$token ’(’
>
cond <- removeWhite expr
>
removeWhite$token ’)’
>
a1<-removeWhite body
>
removeWhite$keyword "else"
>
a2<-removeWhite body
>
return$ IfEx cond a1 a2
262
263
2.2.4 Ausdrücke
WhileLang.hs
264
265
266
267
268
2-4
> expr = andExpr
2.2 Parser der While-Sprache
WhileLang.hs
269
270
271
272
> andExpr = sepByW (keyword "&&") orExpr <* (foldl1 (Op "&&" boolAnd))
> where boolAnd (B b1) (B b2) = B (b1&&b2)
273
274
WhileLang.hs
275
276
277
278
> orExpr = sepByW (keyword "||") cmpExpr <* (foldl1 (Op "||" boolOr))
> where boolOr (B b1) (B b2) = B (b1||b2)
279
280
WhileLang.hs
281
282
283
284
> cmpExpr = (addExpr -*-- (vielleicht (cmpOp-*--addExpr)) )
>
<* \(x,mb) -> maybe x (\((o,op),y)->Op o op x y) mb
285
286
WhileLang.hs
287
288
289
290
291
292
293
294
295
> cmpOp
> = msum$
>
map (\(n,op)-> (keyword n <* \_ -> (n,mkBProgFun op)))
>
[("==",(==)),("!=",(/=)),("<=",(<=)),(">=",(>=)),("<",(<)),(">",(>))]
>
> mkBProgFun eqOp (I i1)(I i2) = B$eqOp i1 i2
> mkBProgFun _ _ _ = B False
296
297
WhileLang.hs
298
299
300
301
> addExpr = (multExpr -*-- nullBisNMalW (addOp -*-- multExpr))
>
<* (\(o0,os) -> foldl (\o1 ((o,op),o2)-> Op o op o1 o2) o0 os)
302
303
WhileLang.hs
304
305
306
307
308
> addOp =
>
(keyword "+" <* \_ -> ("+",mkIProgFun (+)))
>
||| (keyword "-" <* \_ -> ("-",mkIProgFun (-)))
309
310
WhileLang.hs
311
312
313
314
> mkIProgFun eqOp (I i1)(I i2) = I$eqOp i1 i2
> mkIProgFun _ _ _ = I 0
315
316
2-5
Kapitel 2 Eine kleine While-Sprache
WhileLang.hs
317
318
319
320
> multExpr = (atomExpr -*-- nullBisNMalW (multOp -*-- atomExpr))
>
<* (\(o0,os) -> foldl (\o1 ((o,op),o2)-> Op o op o1 o2) o0 os)
321
322
WhileLang.hs
323
324
325
326
327
> multOp =
>
(keyword "*" <* \_ -> ("*",mkIProgFun (*)))
>
||| (keyword "/" <* \_ -> ("/",mkIProgFun div))
328
329
WhileLang.hs
330
331
332
333
334
335
336
> atomExpr
>
=
funCall
>
||| zahl
>
||| (token ’(’ -*-- expr -*-- token ’)’ <* (\((_,e),_) -> e))
>
||| ident
337
338
WhileLang.hs
339
340
341
342
343
344
345
346
> funCall = do
>
Var v <- ident
>
removeWhite$token ’(’
>
args <- sepByW (token ’,’) expr
>
removeWhite$token ’)’
>
return$ FunCall v args
347
348
2.3 Interpreter der While Sprache
WhileLang.hs
349
350
351
352
353
354
355
> type MySt a = ([(String, FunDef a)],[(String, a)])
>
> type StMonad a = State (MySt a)
>
> eval (Zahl z) = return z
356
357
2.3.1 Operatorausdrücke
WhileLang.hs
358
359
2-6
2.3 Interpreter der While Sprache
360
361
362
363
> eval (Op _ op e1 e2) = do
>
r1 <- eval e1
>
r2 <- eval e2
>
return$ op r1 r2
364
365
2.3.2 Zuweisung
WhileLang.hs
366
367
368
369
370
371
372
> eval (Assign v p) = do
>
r <- eval p
>
(funs,st) <- get
>
put (funs,((v,r):st))
>
return r
373
374
2.3.3 Variablen
WhileLang.hs
375
376
377
378
379
380
> eval (Var v) = do
>
st <- get
>
let mbRes = lookup v (snd st)
>
return$maybe (I (-1)) id mbRes
381
382
2.3.4 Schleifen
WhileLang.hs
383
384
385
386
387
388
389
390
391
> eval whl@(WhileEx bed body) = do
>
bd <- eval bed
>
if (bd==B False)
>
then return bd
>
else do
>
rs <- sequence (map eval body)
>
eval whl
392
393
2.3.5 Verzweigungen
WhileLang.hs
394
395
396
397
> eval (IfEx bed a1 a2) = do
>
bd <- eval bed
2-7
Kapitel 2 Eine kleine While-Sprache
398
399
400
401
402
403
404
>
>
>
>
>
>
>
if (bd==B True)
then do
rs<-sequence (map eval a1)
return$last rs
else do
rs<-sequence (map eval a2)
return$last rs
405
406
2.3.6 Funktionsaufrufe
WhileLang.hs
407
408
409
410
411
412
413
414
415
416
417
418
419
420
> eval (FunCall name args) = do
>
(funs,st) <- get
>
let mf = lookup name funs
>
maybe (error "unknown function")
>
(\(FunDef _ vars body) -> do
>
put (funs,
>
(zip vars$map (\x ->evalState (eval x) (funs,st)) args)++st)
>
res <- sequence (map eval body)
>
put (funs,st)
>
return$last res
>
)
>
mf
421
422
2.3.7 Alles zusammen
WhileLang.hs
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
2-8
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
runProg pr
|null rest = evalState (sequence (map eval p)) (funs,[])
|otherwise = error (">"++rest++"<")
where
(((funs,p),rest):_) = parse program pr
run = last . runProg
fac = "x := 5;\n\
\result := 1;\n\
\while (x!=1){\n\
\ result:=result*x ;\n\
\ x := x-1\n\
\};\n\
\x;\n\
\result"
main = do
(a:rgs) <- getArgs
load a
2.3 Interpreter der While Sprache
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
load a = do
file<-readFile (trim a)
let ((funs,_):_) = parse (nullBisNMalW fundef) file
sequence (map (\(FunDef name _ _) ->putStr (name++"\n")) funs)
readEvalLoop (map (\fd@(FunDef name _ _) -> (name,fd)) funs,[])
putStr "\n"
trim = (dropWhile isSpace).(dropWhileEnd isSpace)
--why is this not in my prelude, old ghc version
dropWhileEnd p = foldr (\x xs -> if p x && null xs then [] else x:xs)[]
readEvalLoop st = do
line <- getLine
case line of
":q" -> return ()
(’:’:l’:fileName) -> load fileName
_ -> do
let ((e,_):_) = parse (removeWhite prog) line
putStr$show$last$evalState (sequence (map eval e)) st
putStr "\n"
readEvalLoop st
start = readEvalLoop ([],[])
471
472
2-9
Herunterladen