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