Monadick parsery Graham Hutton Erik Mejer Monadic Parser

  • Slides: 16
Download presentation
Monadické parsery Graham Hutton, Erik Mejer: Monadic Parser Combinators http: //www. cs. nott. ac.

Monadické parsery Graham Hutton, Erik Mejer: Monadic Parser Combinators http: //www. cs. nott. ac. uk/Department/Staff/gmh/monparsing. ps Graham Hutton, Erik Mejer: Functional Pearls: Monadic Parsing in Haskell http: //www. cs. nott. ac. uk/~gmh/bib. html#pearl Daan Leijen: Parsec, a fast combinator parser http: //legacy. cs. uu. nl/daan/download/parsec. html Cieľom tejto prednášky je ukázať na probléme syntaktickej analýzy, ktorý sme začali minule, ideu fungovania monád a monadického štýlu programovania. Mnohé analyzátory, ktoré skonštruujeme sa historicky nazývajú inými menami, ale ich analógie nájdete aj v predošlej prednáške. Rôzne typy monád budeme študovať na budúcej prednáške.

Syntaktický analyzátor Minule sme definovali analyzáror ako nasledujúci typ: type Parser symbol result =

Syntaktický analyzátor Minule sme definovali analyzáror ako nasledujúci typ: type Parser symbol result = [symbol] -> [([symbol], result)] dnes to bude (zámena poradia – rešpektujúc použité zdroje): type Parser result = String -> [(result, String)] resp. : type Parser result = Parser(String -> [(result, String)]) Primitívne parsery: return : : a->Parser a -- tento sa volal succeed return v = xs -> [(v, xs)] -- nečíta zo vstupu, dá výsledok v return v xs = [(v, xs)] zero : : Parser a -- tento sa volal fail zero = xs -> [] -- neakceptuje nič zero xs = [] item : : Parser Char -- akceptuje ľubovoľný znak item = xs -> case xs of -- tento znak dá do výsledku [] -> [] -- prázdny vstup, neakceptuje (v: vs) -> [(v, vs)] -- akceptuje znak v

Kombinátory parserov seq : : Parser a -> Parser b -> Parser (a, b)

Kombinátory parserov seq : : Parser a -> Parser b -> Parser (a, b) p `seq` q = xs -> [ ((v, w), xs'') | (v, xs') <- p xs, (w, xs'') <- q xs'] n n n tento kombinátor zbytočne vyrába vnorené výrazy typu (a, (b, (c, d)))->… v tejto časti seq (<*>) upadne do zabudnutia, nahradí ho bind (>>=), nový kombinátor bind (>>=) kombinuje analyzáror p: : Parser a s funkciou qf: : a -> Parser b, ktorá ako argument dostane výsledok analýzy analyzátora p a vráti analyzátor q: bind p `bind` qf n n -- zreťazenie <*> : : Parser a -> (a -> Parser b) -> Parser b = xs -> concat [ (qf v) xs' | (v, xs')<-p xs]) v je výsledok analýzy p, qf v je nový analyzátor parametrizovaný výsledkom v.

Ako sa bind používa bind p `bind` qf : : Parser a -> (a

Ako sa bind používa bind p `bind` qf : : Parser a -> (a -> Parser b) -> Parser b = xs -> concat [ (qf v) xs' | (v, xs')<-p xs]) spôsob použitia: p 1 `bind` x 1 -> p 2 `bind` x 2 -> … pn `bind` xn -> return (f x 1 x 2. …. . xn) preprogramujeme seq: p `seq` q = p `bind` (x -> q `bind` (y -> return (x, y) ) ) spôsob čítania: najprv pustíme analyzátor p 1, ktorého výsledok je v premennej x 1, potom pustíme analyzátor p 2, ktorého výsledok je v premennej x 2, . . . nakoniec pustíme analyzátor pn, ktorého výsledok je v premennej xn, výsledok celej analýzy dostaneme ako funkciu f x 1 x 2. …. . xn.

Príklady jednoduchých parserov Main> parse (item) "abcd" [('a', "bcd")] ilustrujme na niekoľkých príkladoch použitie

Príklady jednoduchých parserov Main> parse (item) "abcd" [('a', "bcd")] ilustrujme na niekoľkých príkladoch použitie bind: sat : : (Char->Bool) -> Parser Char -- ten sa volal satisfy sat pred = item `bind` x-> -- akceptuje symbol, pre ktorý if pred x then return x else zero -- platí predikát pred n char x : : Char -> Parser Char = sat (y -> x==y) Main> parse (sat is. Lower) "a 123 ad" [('a', "123 ad")] -- ten sa volal symbol -- akceptuje znak x Main> parse (char 'a') "a 123 ad" [('a', "123 ad")] digit : : Parser Char = sat is. Digit -- akceptuje cifru Main> parse (digit) "123 ad" [('1', "23 ad")]

Zjednotenie n zjednotenie/disjunkciu analyzátorov poznáme ako <|>: plus p `plus` q xs : :

Zjednotenie n zjednotenie/disjunkciu analyzátorov poznáme ako <|>: plus p `plus` q xs : : Parser a -> Parser a = xs -> (p xs ++ q xs) = (p xs ++ q xs) Main> parse (letter `plus` digit) "123 abc" [('1', "23 abc")] definujme analyzátor word, ktorý akceptuje postupnosť písmen, {letter}*: gramatická idea: word -> letter word | ε word : : Parser String word = non. Empty. Word `plus` return "" where non. Empty. Word = letter `bind` x -> word `bind` xs -> return (x: xs) n Main> parse word "abcd" [("abcd", ""), ("abc", "d"), ("ab", "cd"), ("a", "bcd"), ("", "abcd")] Problém je, že nechceme dostať toľko riešení, chceme greedy verziu. . .

Deterministické plus n predefinujeme zjednotenie analyzátorov: (+++) p +++ q word' : : Parser

Deterministické plus n predefinujeme zjednotenie analyzátorov: (+++) p +++ q word' : : Parser a -> Parser a = xs -> case p `plus` q xs of [] -> [] (x: xs) -> [x] -- z výsledku zober prvé riešenie : : Parser String = non. Empty. Word +++ return "" where non. Empty. Word = letter `bind` x -> word' `bind` xs -> return (x: xs) Main> parse word' "abcd 12" [("abcd", "12")]

Monády Monáda je analógia algebraickej štruktúry nazývanej monoid s doménou M a binárnou asociatívnou

Monády Monáda je analógia algebraickej štruktúry nazývanej monoid s doménou M a binárnou asociatívnou operáciou Mx. M->M s (ľavo a pravo)-neutrálnym prvkom. definícia v Haskelli (zapnite si Options/Allow Hugs-Ghc Extensions): class Monad m where return : : a -> m a -- neutrálny prvok vzhľadom na >>= : : m a -> (a -> m b) -> m b -- asociatívna operácia, nič iné ako bind n chceli by sme vytvoriť Monad Parser t, ale musíme predefinovať typ Parser: data Parser result = Parser(String -> [(result, String)]) n to nás stojí trochu syntaktických zmien: parse : : Parser a -> String -> [(a, String)] parse (Parser p) = p -- inak: parse (Parser p) xs = p xs n instance Monad Parser where return v = Parser(xs -> [(v, xs)]) p >>= f = Parser( -- bind xs -> concat [ parse (f v) xs' | (v, xs')<-parse p xs])

bind je >>= Zákony monád vlastnosti return a bind (>>=): return a >>= f

bind je >>= Zákony monád vlastnosti return a bind (>>=): return a >>= f =fa -- return ako identita zľava p >>= return =p -- retrun ako identita sprava p >>= (a -> (f a >>= g))= (p >>= (a -> f a)) >>= g -- “asociativita” n vlastnosti zero a `plus`: zero `plus` p =p -- zero ako identita zľava p `plus` zero =p -- zero ako identita sprava p `plus` (q `plus` r) = (p plus q) plus r -- asociativita n vlastnosti zero `plus` a >>= : zero >>= f = zero -- zero ako identita zľava p >>= const zero = zero -- zero ako identita sprava (p `plus` q) >>= f = (p >>= f) `plus` (q >>= f) -- distribut. p>>=(a->f a `plus` g a) = (p >>= f) `plus` (q >>= f) -- asociat. n

bind je >>= Monad comprehension p 1 >>= x 1 -> p 2 >>=

bind je >>= Monad comprehension p 1 >>= x 1 -> p 2 >>= x 2 -> … pn >>= xn -> return (f x 1 x 2. …. . xn) [ f x 1 x 2. …. . xn | x 1 <- p 1 x 2 <- p 2 … xn <- pn] string : : String -> Parser String string "" = return "" string (x: xs) = char x >>= _ -> string xs >>= _ -> return (x: xs) string "" string (c: cs) do {x 1 <- p 1, x 2 <- p 2, …. xn <- pn, return (f x 1 x 2. …. . xn) } -- volal sa token -- výsledok char x zahoď -- výsledok string xs zahoď -- výsledok je celé slovo x: xs : : String -> Parser String -- prepíšeme do novej = return "“ -- syntaxi = do { _ <- char c; _ <- string cs; return (c: cs)} -- explicitné. . . = do {char c; string cs; return (c: cs)} -- čitateľnejšie. . . Main> parse (string "begin") "beginend" [("begin", "end")]

Iterátory {p}*, {p}+ • manyp -> many 1 p | ε many : :

Iterátory {p}*, {p}+ • manyp -> many 1 p | ε many : : Parser a -> Parser [a] many p = many 1 p +++ return [] Main> parse (many digit) "123 abc" • many 1 p -> p manyp [("123", "abc")] many 1 : : Parser a -> Parser [a] many 1 p = do {a <- p; as <- many p; return (a: as)} opakovanie p s oddeľovačom sep: {p sep}*p | ε sepby : : Parser a -> Parser b -> Parser [a] p `sepby` sep = (p `sepby 1` sep) +++ return [] n {p sep}*p sepby 1 p `sepby 1` sep n : : Parser a -> Parser b -> Parser [a] = do { a<-p; as<- many(do {sep; p}); return (a: as) } Main> parse ((many digit) `sepby` (char '+' `plus` char '*')) "1+2*3 abc" [(["1", "2", "3"], "abc")]

Zátvorky open close n verzia 1 paren Analyzátor pre dobre uzátvorkované výrazy podľa gramatiky:

Zátvorky open close n verzia 1 paren Analyzátor pre dobre uzátvorkované výrazy podľa gramatiky: P -> ( P ) P | ε : : Parser Char = char '(' = char ')' : : Parser () -- nezaujíma nás výstupná hodnota = do { open; paren; close; paren; return () } Main> parse paren. Bin "()" +++ [(Node Nil, "")] return () Main> parse paren. Bin "()()" [(Node Nil Nil), "")] verzia 2 data Bin = Nil | Node Bin -- vnútorná reprezentácia deriving(Show, Read, Eq) n paren. Bin : : Parser Bin -- analyzátor zo String do Bin = do { open; x<-paren. Bin; close; y<-paren. Bin; return (Node x y) } Main> parse paren. Bin "(())()" +++ [(Node Nil Nil) (Node Nil), "")] return Nil Main> parse paren. Bin "())()" [(Node Nil, ")()")]

P -> (P) P | [P] P | ε Zátvorky 2 najivne paren. Br

P -> (P) P | [P] P | ε Zátvorky 2 najivne paren. Br n reálne paren. Br : : Parser () = do { (open `plus` open. Br) ; paren. Br; (close `plus` close. Br) ; paren. Br; return () } +++ return () n : : Parser () = do { open; paren. Br; close; paren. Br; return () } +++ do { open. Br ; paren. Br; close. Br ; paren. Br; return () } +++ Main> parse paren. Br "([[]])([])" return () [((), "")] Main> parse paren. Br "([[(])])([])" [((), "([[(])])([])")]

expr najivne <expr> : : = <expr> + <expr> | <expr> - <expr> <factor>

expr najivne <expr> : : = <expr> + <expr> | <expr> - <expr> <factor> : : = nat | ( <expr> ) Gramatika je ľavo-rekurzívna, preto sa to zacyklí: expr factor addop n n n = do {x <- expr; f <- addop; y<-factor; return (f x y)} `plus` factor = nat `plus` do { open; x<-expr; close; return x} = do { char '+'; return (+) } `plus` do { char '-'; return (-) } riešenie je zlé, čo ale stojí za zmienku je typ addop : : Parser (Int -> Int) parser, ktorého vnútorná reprezentácia toho, čo zanalyzuje je funkcia, táto funkcia sa potom použije na kombináciu dvoch posebe-idúcich výsledkov iného parsera (f x z)

chainl <expr> : : = <term> | <term> + <expr> | <term> - <expr>

chainl <expr> : : = <term> | <term> + <expr> | <term> - <expr> <term> : : = <factor> | <factor> * <term> | <factor> / <term> <factor> : : = number | ( <expr> ) Ak tú myšlienku zovšeobecníme dostaneme nasledujúci kód: chainl 1 : : Parser a -> Parser (a -> a) -> Parser a p `chainl 1` op = do {a <- p; rest a} where rest a = do {f <- op; b <- p; rest (f a b)} `plus` return a n Aritmetické výrazy: expr = term `chainl 1` addop term = factor `chainl 1` mulop factor = nat `plus` (do {open; n <- expr; close; return n}) mulop = do { char '*'; return (*) } `plus` do { char '/'; return (div) } n Main> parse expr "1+2*3+10" [(17, ""), (8, "0"), (7, "+10"), (3, "*3+10"), (1, "+2*3+10")]

Cvičenia n parser pre λ-calcul, t. j. Parser LExp: Main> parse lambda "(\x. (x

Cvičenia n parser pre λ-calcul, t. j. Parser LExp: Main> parse lambda "(\x. (x x))" [(APL (LAMBDA "x" (APL (ID "x"))), "")] n parser binárnej/hexa konštanty Main> parse bin. Const "1101" [13, "")] Main> parse hexa. Const "FF" [255, "")] n parser palindromov, t. j. Parser () n parser morseovej abecedy, t. j. Parser String