programmieren stefan janssen programmiereninhaskell · programmieren in haskell stefan janssen...
Post on 24-Jul-2018
264 Views
Preview:
TRANSCRIPT
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Programmieren in HaskellKombinator-Sprachen
Stefan Janssen
Universität BielefeldAG Praktische Informatik
20. Januar 2015
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Vorschau
Das heutige Thema hat zwei Teilekontextfreie Grammatiken, Sprachen und ihre “Parser”Domain Specific Languages (DSL, anwendungsspezifischeSprachen) am Beispiel des Parsensals Beispiel einer Spracherweiterung in Haskell
Gemeinsamer Ausgangspunkt: In vielen Anwendungenbesteht die Eingabe aus einem Text, der in seiner Strukturerfasst werden muss, ehe die eigentliche Verarbeitungbeginntkann diese Struktur durch ein Grammatik beschriebenwerden
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Vorschau
Das heutige Thema hat zwei Teilekontextfreie Grammatiken, Sprachen und ihre “Parser”Domain Specific Languages (DSL, anwendungsspezifischeSprachen) am Beispiel des Parsensals Beispiel einer Spracherweiterung in Haskell
Gemeinsamer Ausgangspunkt: In vielen Anwendungenbesteht die Eingabe aus einem Text, der in seiner Strukturerfasst werden muss, ehe die eigentliche Verarbeitungbeginntkann diese Struktur durch ein Grammatik beschriebenwerden
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Beispiel: Übersetzung von Programmen
Wichtigstes Beispiel: CompilerEingabe: Programmtext als ASCII StringZerlegung 1: Folge von Namen, Zahlen, OperatorenZerlegung 2: Erkennen von Deklarationen, Definitionen,Ausdrücken, Import-Listen, ...Resultat der “syntaktischen Analyse”: BaumartigeDarstellung des Programms als rekursiver DatentypWeitere Verarbeitung des Programmbaums: SemantischePrüfung, Transformation, Optimierung, Codeerzeugung, ...
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Beispiele von DSLs (1)
Textbeschreibung in LATEX:
\section{Grammatiken und Parser}\begin{frame}{Vorschau}Das n\"achste Thema hat zwei Teile\begin{itemize}\item ‘‘Parser’’ f\"ur kontextfreie Sprachen\item Domain Specific Languages (DSL, anwendungsspezifische Sprachen) \emph{am Beispiel} des Parsens\item \emph{als Beispiel} einer Spracherweiterung in Haskell\end{itemize}
\pause...
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Beispiele von DSLs (2)
Literatureintrag in BibTeX:
Example1 @book{UBHD1745069 ,2 autho r={Bird , R i cha rd } ,3 t i t l e ={ I n t r o d u c t i o n to f u n c t i o n a l programming u s i n g H a s k e l l } ,4 pages ={433} ,5 p u b l i s h e r ={P r e n t i c e H a l l } ,6 yea r ={1998} ,7 i s b n ={978−0−13−484346−9},8 e d i t i o n ={2nd}9 }
\cite{UBHD 1745069} -- Referenz im Text
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Beispiele von DSLs (2)
Literatureintrag in BibTeX:
Example1 @book{UBHD1745069 ,2 autho r={Bird , R i cha rd } ,3 t i t l e ={ I n t r o d u c t i o n to f u n c t i o n a l programming u s i n g H a s k e l l } ,4 pages ={433} ,5 p u b l i s h e r ={P r e n t i c e H a l l } ,6 yea r ={1998} ,7 i s b n ={978−0−13−484346−9},8 e d i t i o n ={2nd}9 }
\cite{UBHD 1745069} -- Referenz im Text
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Textzerlegung mit groupBy
Eine ausgesprochen nützliche Funktion:groupBy aus Data.List
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
zerlegt einen String in “Gruppen”, so dassconcat . groupBy f == id
für jedes f
gruppiert wird, solange die Bedingung f über dem erstenund dem (bisher) letzten Buchstaben der Gruppe erfüllt istder erste Buchstabe, der die Bedingung f verletzt, starteteine neue Gruppe
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Beispiele zu GroupBy
1 Parse > groupBy (==) " aaggccccggt "2 ["aa","gg","cccc","gg","t"]3
4 Parse > groupBy (/=) " aaccgaccgaaa "5 ["a","accg","accg","a","a","a"]6
7 Parse > groupBy (<=) " abraham "8 [" abraham "]9
10 Parse > groupBy (<) " abraham "11 ["abr","ah","am"]12
13 Parse > groupBy (\a b -> a /= ’ ’ && b /= ’ ’)14 "wenn er aber kommt?"15 ["wenn"," ","er"," ","aber"," ","kommt?"]
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Textstruktur allgemein
groupBy ist nützlich, aber beschränkt.
Nur ein Kriterium der Zerlegung,nur eine Form des Zusammenhangs der Teile (:)
Texte habe Zeichen, Wörter, Sätze, Abschnitte, Kapitel, relativflache StrukturenProgramme haben Operatoren, Variablen, Konstanten, Zahlen,Ausdrücke, Deklarationen, Definitionen, und beliebig tiefeStrukturenKomplexe formale Sprachen beschreibt man durch
Grammatiken, die Sprachen generieren, oderAutomaten, die Sprachen akzeptieren
Beide Methoden sind gleich mächtig
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Klassen formaler Sprachen
Endliche Automaten sind aus A&D bekannt. Sie könnenSprachen wie {(ab)n} beschreiben, aber nicht {anbn}.
Kontextfreie Grammatiken sind etwas mächtiger.Sie können Sprachen wie {anbn} und {anbmcn} beschreiben,aber nicht {anbncn} oder {anbmcndm}.
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Klassen formaler Sprachen
Endliche Automaten sind aus A&D bekannt. Sie könnenSprachen wie {(ab)n} beschreiben, aber nicht {anbn}.
Kontextfreie Grammatiken sind etwas mächtiger.Sie können Sprachen wie {anbn} und {anbmcn} beschreiben,aber nicht {anbncn} oder {anbmcndm}.
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Kontextfreie Grammatiken
Hier eine kleine Grammatik für Ausdrücke:
Terminalsymbole: 0 1 2 a b c d ) ( + *Nichtterminalsymbole: A T F Z B CAxiom A
RegelnA -> T | T + AT -> F | F * TF -> Z | B | ( A )Z -> 0 | 1 | 2B -> C | C BC -> a | b | c | d
Ausdrücke sind also"a+1*(abba+2)", "2*a+2*b", "2*(a+b)"
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Programmbäume
Darstellung der Ausdrücke"a+1*(abba+2)", "2*a+2*b", "2*(a+b)"nach ihrer Zerlegung
1 Plus (Var "a")2 (Mul (Numb "1")3 (Plus (var "abba") (Numb "2")))4
5 Plus (Mul (Numb "2") (Var "a"))6 (Mul (Numb "2") (Var "b"))7
8 Mul (Numb "2")9 (Plus (Var "a")
10 (Var "b"))
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Vorüberlegungen zum Parser
Anforderungen: Der Parser sollwohlgeformte Ausdrücke in Programmbäume übersetzenwenn es mehrere Parses gibt, dann für alle denProgrammbaum liefern ...... und ggf. gar keinen
Wir schreiben einen rekursiven Top-Down Parser:Für jedes Nichtterminalsymbol X
gibt es eine Parserfunktion pXdie Ableitungen aus X konstruiertund die entsprechenden Programmbäume zusammensetzt
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Arbeitsweise der Parser
Eine Parserfunktion pX arbeitet wie folgtEingabe ist ein (restlicher) String wpX leitet Präfix u von w = uv aus X abdazu ruft sie sich selbst und andere Parser für die NTs derrechten Seite aufgibt Programmbaum P(X -> u) und Resteingabe vzurück ...... und das für alle Paare P(X -> u) und vWird kein Parse gefunden, wird eine leere Listezurückgegeben
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Code
1 > type Parser a b = [a] -> [(b, [a])]2
3 > data Ptree = Plus Ptree Ptree |4 > Mul Ptree Ptree |5 > Var String |6 > Numb String deriving Show7
8 > parse xs = [t | (t ,[]) <- pA xs]pA wird als erster Parser aufgerufen, weil A das Axiom derGrammatik istEin Parse ist nur erfolgreich, wenn er die ganze Eingabe“verbraucht”.
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Code
1 > pA xs = pT xs ++2 > [( Plus l r, v) | (l, o:u) <- pT xs ,3 > o == ’+’,4 > (r, v) <- pA u]5 > pT xs = pF xs ++6 > [( Mul l r, v) | (l, ’*’:u) <- pF xs ,7 > (r, v) <- pT u]8 > pF xs = pZ xs ++ pB xs ++9 > [(t, v) | (’(’:u) <- [xs],
10 > (t,’)’:v) <- pA u]
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Code
1 > pZ xs = case xs of ’0’:v -> [( Numb "0", v)]2 > ’1’:v -> [( Numb "1", v)]3 > ’2’:v -> [( Numb "2", v)]4 > otherwise -> []5 > pB xs = [( Var (c:""), v) | (c, v) <- pC xs] ++6 > [( Var (c:cs), v) | (c, u) <- pC xs ,7 > (Var cs ,v) <- pB u]8 > pC xs = case xs of ’a’:v -> [(’a’, v)]9 > ’b’:v -> [(’b’, v)]
10 > ’c’:v -> [(’c’, v)]11 > ’d’:v -> [(’d’, v)]12 > otherwise -> []
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Systematik
Bei der Parser-Konstruktion wiederholen sichLesen bestimmter Zeichen oder ZeichenkombinationenAufruf anderer Parser in der Reihenfolge der Nonterminalsauf den rechten Seiten(++) für die Zusammenführung von Resultaten ausAlternativenWeiterreichen von Teilergebnissen und Resteingabe an dennächsten ParserAnwendung eines Programmbaum-Konstruktors aufTeilergebnisse
Wenn wir dafür spezielle Operationen kreieren, erhalten wireine DSL für die schnelle und fehlersichere Implementierungvon Parsern
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Vorfreude
Der Parser für die Grammatik
A -> T | T + AT -> F | F * TF -> Z | B | ( A )Z -> 0 | 1 | 2B -> C | C BC -> a | b | c | d
wird dann etwa so aussehen:1 > pA = pT ||| Plus <<< pT ~~~ ’+’ ~~~ pA2 > pT = pF ||| Mul <<< pF ~~~ ’*’ ~~~ pT3 > pF = pZ ||| pB ||| ’(’ ~~~ pA ~~~ ’)’4 > pZ = Numb <<< ’0’ ||| ’1’ ||| ’2’5 > pB = (:[]) <<< pC ||| (:) <<< pC ~~~ pB6 > pC = ’a ’||| ’b|||’c ’||| ’d’Achtung: Das ist noch nicht der endgültige Code
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Vorfreude
Der Parser für die Grammatik
A -> T | T + AT -> F | F * TF -> Z | B | ( A )Z -> 0 | 1 | 2B -> C | C BC -> a | b | c | d
wird dann etwa so aussehen:1 > pA = pT ||| Plus <<< pT ~~~ ’+’ ~~~ pA2 > pT = pF ||| Mul <<< pF ~~~ ’*’ ~~~ pT3 > pF = pZ ||| pB ||| ’(’ ~~~ pA ~~~ ’)’4 > pZ = Numb <<< ’0’ ||| ’1’ ||| ’2’5 > pB = (:[]) <<< pC ||| (:) <<< pC ~~~ pB6 > pC = ’a ’||| ’b|||’c ’||| ’d’Achtung: Das ist noch nicht der endgültige Code
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Vorbereitung
Was wird gebraucht?Definition der Parser-KombinatorenDefinition der terminalen ParserDefinition der Funktionen zum Baumaufbau, soweit nichtdirekt die Konstruktoren verwendbar sind
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Zur Erinnerung
Typ eines Parsers war1 > type Parser a b = [a] -> [(b, [a])]Ein Parser
verarbeitet Liste vom Elementtyp a
berechnet Liste von Ergebnissen vom Typ[(Programmbaum, Resteingabe)]
NB: Die Eingabe muss also kein String sein ....
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser für einzelne Zeichen
1 > cchar :: (Eq a) => a -> Parser a a-- vergleiche mit Definition von char weiter unten
2 > cchar c [] = []3 > cchar c (x:xs)4 > | x == c = [ (c, xs) ]5 > | otherwise = []
Parser cchar ’x’ erkennt nur den Buchstaben ’x’ am Anfangder Eingabe.
Später lernen wir einen allgemeineren Parser für beliebigeEingabesymbole kennen.
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Kombinatoren
Kombinatoren nennt man Funktionen, deren Argumente undErgebnisse Funktionen sind.
Unsere Kombinatoren kombinieren Parser mit Parsern zu neuenParsern
1 > infix 8 <<< -- Programmbaum - Aufbau2 > infixl 7 ~~~ -- Verkettung von Parsern3 > infixr 6 ||| -- Zusammenfassen von Alternativen
Die Wahl der Prioritäten und der Assoziierungsrichtung wirdspäter wichtig ...
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Kombinatoren
Kombinatoren für Funktionsanwendung, Verkettung undAlternative
1
2 > (<<<) :: (b -> c) -> Parser a b -> Parser a c3 > (<<<) f p inp = [(f x,rest )| (x,rest) <- p inp]4
5 > (~~~) :: Parser a (b->c)-> Parser a b-> Parser a c6 > (~~~) p q inp = [(x y,r2) | (x,rest) <- p inp ,7 > (y,r2) <- q rest ]8 > (|||) :: Parser a b -> Parser a b -> Parser a b9 > (|||) p q inp = p inp ++ q inp
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Kombinatoren
Jeder Parser kann zum Axiom der Grammatik erklärt werdendurch den Kombinator axiom
1 > axiom :: [a] -> Parser a b -> [b]2 > axiom inp p = [ x | (x, []) <- p inp ]Das Axiom muss die ganze Eingabe ableiten, nicht nur einenPräfix davon (wie alle anderen Parser)
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Grammatik in Kombinator-Schreibweise
Jetzt können wir den Parser wie eine Kopie der Regeln in derGrammatik schreiben:
Aus A -> T | T + A wird1 > pA = pT ||| plus <<< pT ~~~ cchar ’+’ ~~~ pA
aus F -> Z | B | ( A ) wird1 > pF = Numb <<< pZ ||| Var <<< pB |||2 > mid <<< cchar ’(’ ~~~ pA ~~~ cchar ’)’
Wir brauchen aber noch ein paar Bausteine
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Programmbaumaufbau
Die Konstruktoren Plus und Mul erwarten nur zweiArgumente, der Parser liefert drei. Daher:
1 > pC = cchar ’a’ ||| cchar ’b’ ||| cchar ’c’2 > ||| cchar ’d’3 > plus x _ z = Plus x z4 > mul x _ z = Mul x z
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Der komplette Parser
1 > parse_C :: String -> [Ptree]2 > parse_C inp = axiom inp pA where3 > pA = pT ||| plus <<< pT ~~~ cchar ’+’ ~~~ pA4 > pT = pF ||| mul <<< pF ~~~ cchar ’*’ ~~~ pT5 > pF = Numb <<< pZ ||| Var <<< pB |||6 > mid <<< cchar ’(’ ~~~ pA ~~~ cchar ’)’7 > pZ = (:[]) <<< (cchar ’0’ ||| cchar ’1’8 > ||| cchar ’2’)9 > pB = (:[]) <<< pC ||| (:) <<< pC ~~~ pB
10 > pC = cchar ’a’ ||| cchar ’b’ ||| cchar ’c’11 > ||| cchar ’d’12 > plus x _ z = Plus x z13 > mul x _ z = Mul x z14 > mid _ y _ = y
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Vereinfachungen
Reale Parser werden nach der gleichen Methode gebaut, sindaber komplizierter
Zahlen mit beliebig vielen ZiffernOperatoren aus mehreren Zeichen“Whitespace” in der Eingabe
Es muss eine Ebene der lexikalischen Analyse vorgschaltetwerden.Dazu gibt es ein ausgefeilteres Beispiel im nächsten Abschnitt
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
DSLs – Anwendungssprachen
Domain Specific Language (DSL)vs. General-Purpose-Language, Libraries, FrameworksZiele: Unterstützung komplexer Anwendungen mitwiederkehrenden Grund-KonstruktionenBeispiele: TEX, LATEX, Lex, Yacc, awk . . .
FortgeschrittenesPaul Hudak. Modular Domain Specific Languages and Tools.Proceedings of the Fifth International Conference on SoftwareReuse, IEEE Computer Society, 1998. http://haskell.cs.yale.edu/wp-content/uploads/2011/01/DSEL-Reuse.pdf
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
DSLs – Anwendungssprachen
Domain Specific Language (DSL)vs. General-Purpose-Language, Libraries, FrameworksZiele: Unterstützung komplexer Anwendungen mitwiederkehrenden Grund-KonstruktionenBeispiele: TEX, LATEX, Lex, Yacc, awk . . .
FortgeschrittenesPaul Hudak. Modular Domain Specific Languages and Tools.Proceedings of the Fifth International Conference on SoftwareReuse, IEEE Computer Society, 1998. http://haskell.cs.yale.edu/wp-content/uploads/2011/01/DSEL-Reuse.pdf
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Autonome versus eingebettete DSL
Autonome DSL: Eigenständige SpracheBeispiel aus Bielefeld: Bellman’s GAPGebiet: biologische Sequenzanalyse
eigener Compiler mit umfangreicher Optimierung
Embedded DSL: Sprach-Subset, Erweiterung oder Bibliothek inHost Sprache
Beispiel aus Bielefeld: Haskell ADP, ADPfusionGebiet: biologische Sequenzanalyse
kein eigener Compiler, keine spezifische Optimierung, abereinfache Implementierung
Strategie für neue DSL: Erst “embedded” entwickeln underproben, dann “ex-bedding”
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Autonome versus eingebettete DSL
Autonome DSL: Eigenständige SpracheBeispiel aus Bielefeld: Bellman’s GAPGebiet: biologische Sequenzanalyse
eigener Compiler mit umfangreicher Optimierung
Embedded DSL: Sprach-Subset, Erweiterung oder Bibliothek inHost Sprache
Beispiel aus Bielefeld: Haskell ADP, ADPfusionGebiet: biologische Sequenzanalyse
kein eigener Compiler, keine spezifische Optimierung, abereinfache Implementierung
Strategie für neue DSL: Erst “embedded” entwickeln underproben, dann “ex-bedding”
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Autonome versus eingebettete DSL
Autonome DSL: Eigenständige SpracheBeispiel aus Bielefeld: Bellman’s GAPGebiet: biologische Sequenzanalyse
eigener Compiler mit umfangreicher Optimierung
Embedded DSL: Sprach-Subset, Erweiterung oder Bibliothek inHost Sprache
Beispiel aus Bielefeld: Haskell ADP, ADPfusionGebiet: biologische Sequenzanalyse
kein eigener Compiler, keine spezifische Optimierung, abereinfache Implementierung
Strategie für neue DSL: Erst “embedded” entwickeln underproben, dann “ex-bedding”
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Unser eDSL-Beispiel
Eine eDSL zur Parser-Konstruktion, eingebettet in Haskellallgemein: Parser-Combinators (sind unabhängig vongegebener Grammatik)Haskell ist flexibel genug und erlaubt durch die Definitionder Kombinatoren als inf-x-Operatoren eine “eingebettete”Spracherweiterung
Dazu noch ein nicht ganz so einfaches Beispiel: BibTeX-Format
Example1 @book{UBHD1745069 ,2 autho r={Bird , R i cha rd } ,3 t i t l e ={ I n t r o d u c t i o n to f u n c t i o n a l programming u s i n g H a s k e l l } ,4 pages ={433} ,5 p u b l i s h e r ={P r e n t i c e H a l l } ,6 yea r ={1998} ,7 i s b n ={978−0−13−484346−9},8 e d i t i o n ={2nd}9 }
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Unser eDSL-Beispiel
Eine eDSL zur Parser-Konstruktion, eingebettet in Haskellallgemein: Parser-Combinators (sind unabhängig vongegebener Grammatik)Haskell ist flexibel genug und erlaubt durch die Definitionder Kombinatoren als inf-x-Operatoren eine “eingebettete”Spracherweiterung
Dazu noch ein nicht ganz so einfaches Beispiel: BibTeX-FormatExample
1 @book{UBHD1745069 ,2 autho r={Bird , R i cha rd } ,3 t i t l e ={ I n t r o d u c t i o n to f u n c t i o n a l programming u s i n g H a s k e l l } ,4 pages ={433} ,5 p u b l i s h e r ={P r e n t i c e H a l l } ,6 yea r ={1998} ,7 i s b n ={978−0−13−484346−9},8 e d i t i o n ={2nd}9 }
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Grammatik für BibTex-Eintraege
record -> recHead recBody recEndrecHead -> ’@’ entype ’{’ aword ’,’recBody -> entriesrecEnd -> ’}’entype -> "book" | "article"entries -> entry | entry ’,’ entriesentry -> aword ’=’ rhsrhs -> ’{’ content ’}’ |
’"’ content ’"’ |aword
aword: beliebige Zeichenreiheohne die Trennzeichen { } @ = ,
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Weitere Festlegungen
Die “lexikalische Ebene”:“Wort” ist beliebige Zeichenfolge ohne Trennzeichen ...... und ohne die “whitespace” Zeichen <neue Zeile> und<Blank>vor dem eigenlichen Parsen wird der Text in “tokens”(Worte oder Trennzeichen) zerlegt ...... und der Whitespace herausgefiltert
Diese Vorverarbeitung nennt man lexikalische Analyse
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Zur Erinnerung
Typ eines Parsers war1 > type Parser a b = [a] -> [(b, [a])]Ein Parser
verarbeitet Liste vom Elementtyp a
berechnet Liste von Ergebnissen vom Typ[(Programmbaum, Resteingabe)]
NB: Die Eingabe muss also kein String sein ....
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Kombinatoren
Wie zuvor ...Kombinatoren nennt man Funktionen, deren Argumente undErgebnisse Funktionen sind.
Unsere Kombinatoren kombinieren Parser mit Parsern zu neuenParsern
1 > infix 8 <<< -- Programmbaum - Aufbau2 > infixl 7 ~~~ -- Verkettung von Parsern3 > infixr 6 ||| -- Zusammenfassen von Alternativen
Die Wahl der Prioritäten und der Assoziierungsrichtung wirdspäter wichtig ...
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Kombinatoren
Wie zuvor ...1
2 > (<<<) :: (b -> c) -> Parser a b -> Parser a c3 > (<<<) f p inp = [(f x,rest )| (x,rest) <-
p inp]4
5 > (~~~) :: Parser a (b->c)-> Parser a b-> Parser a c6 > (~~~) p q inp = [(x y,r2) | (x,rest) <- p inp ,7 > (y,r2) <- q rest ]8 > (|||) :: Parser a b -> Parser a b -> Parser a b9 > (|||) p q inp = p inp ++ q inp
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Kombinatoren
Wie zuvor...
Jeder Parser kann zum Axiom der Grammatik erklärt werdendurch den Kombinator axiom
1 > axiom :: [a] -> Parser a b -> [b]2 > axiom inp p = [ x | (x, []) <- p inp ]Das Axiom muss die ganze Eingabe ableiten, nicht nur einenPräfix davon (wie alle anderen Parser)
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Grammatik in Kombinator-Schreibweise
Jetzt können wir den Parser wie eine Kopie der Regeln in derGrammatik schreiben:
Aus entries -> entry | entry ’,’ entries wird1 > entries = s1 <<< entry |||2 > s2 <<< entry ~~~ char ’,’ ~~~ entries
aus entry -> aword ’=’ rhs wird1 > entry = e1 <<< aword ~~~ char ’=’ ~~~ rhs
Wir brauchen aber noch ein paar Bausteine
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser für die lexikalischen tokens
1 > char :: (Eq a) => a -> Parser [a] a2 > char c [] = []3 > char c (x:xs)4 > | x == [c] = [ (c, xs) ]5 > | otherwise = []
Parser char ’c’ erkennt nur den Buchstaben ’c’ als "c" amAnfang der Eingabe
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser für die lexikalischen tokens
1
2 > word :: Eq a => a -> Parser a a3 > word w [] = []4 > word w (x:xs)5 > | x == w = [ (w, xs) ]6 > | otherwise = []7
8 > aword :: Parser String String9 > aword [] = []
10 > aword (x:xs)11 > | x /= "{" && x/= "}" = [ (x, xs) ]12 > | otherwise = []
Parser aword akzeptiert ein beliebiges Wort (außer denKlammern),Parser word w nur das Wort w
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Aufbau der Bäume für die Bib-Entries
1 > data BibRecord = BR {2 > bibType :: String ,3 > bibId :: String ,4 > bibEntries :: [ BibEntry ]5 > } deriving (Show)6
7 > data BibEntry = BE {8 > entryKey :: String ,9 > entryVals :: [ String ]
10 > } deriving (Show)
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Lexikalische Analyse
1 > lexer :: String -> [ String ]2 > lexer inp = filter isWS $ groupBy g inp where3 > g a b = and $ map (\c -> a/=c && b/=c) delims4 > delims = " {},@,="5 > isWS w = not $ and $6 > map (\c -> c==’ ’ || c==’\n’) w
Vorgruppieren mittels groupBy,Herausfiltern des Whitespace mit isWS
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Code
Die Grammatik in Kombinator-Schreibweise
> parser inp = axiom inp record> where> record = a1 <<< recHead ~~~ recBody ~~~ recEnd> recHead = h1 <<< char ’@’ ~~~ entype ~~~ char ’{’> ~~~ aword ~~~ char ’,’> recBody = entries> recEnd = char ’}’> entype = word "book" |||> word "article"> entries = s1 <<< entry |||
s2 <<< entry ~~~ char ’,’ ~~~ entries
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Parser-Code
Die Grammatik in Kombinator-Schreibweise
> entry = e1 <<< aword ~~~ char ’=’ ~~~ rhs> rhs = r1 <<< char ’{’ ~~~ content ~~~ char ’}’ |||
r1 <<< char ’"’ ~~~ content ~~~ char ’"’ |||r2 <<< aword
> content = c1 <<< char ’{’ ~~~ content ~~~ char ’}’ |||c2 <<< aword ~~~ content |||c3 <<< aword
Die Funktionen e1, r1, ... sind für den Baumaufbauzuständig
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Baumaufbau
Der Baumaufbau benutzt die Konstruktoren BR und BE, aberlässt irrelvante tokens weg:
1 > a1 (typ , iden) ents _ = BR typ iden ents2 > h1 _ typ _ iden _ = (typ , iden)3 > s1 e = [e]4 > s2 e _ es = e:es5 > e1 k _ xs = BE k xs6 > r1 _ x _ = x7 > r2 x = [x]8 > c1 _ x _ = ("{":x) ++ ["}"]9 > c2 a b = a:b
10 > c3 a = [a]
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Aufruf des Parsers
Lexer und Parser werden hintereinandergeschaltet im Aufrufparser $ lexer xs
Wir sind fertig!!Die Grammatik in Kombinator-Schreibweise IST der Parser ...!Siehe Anwendungsbeispiel im Code von parse.lhs
FortgeschrittenesGraham Hutton. Higher-order functions for parsing. Journal ofFunctional Programming, Volume 2 Issue 3, 323–343, 1992.http://www.cs.nott.ac.uk/~gmh/parsing.pdf
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Aufruf des Parsers
Lexer und Parser werden hintereinandergeschaltet im Aufrufparser $ lexer xs
Wir sind fertig!!Die Grammatik in Kombinator-Schreibweise IST der Parser ...!Siehe Anwendungsbeispiel im Code von parse.lhs
FortgeschrittenesGraham Hutton. Higher-order functions for parsing. Journal ofFunctional Programming, Volume 2 Issue 3, 323–343, 1992.http://www.cs.nott.ac.uk/~gmh/parsing.pdf
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Traum und Wahrheit
Wir lesen den Parser für
A → B C D | F ,
also
pA = f <<< pA ~~~ pB ~~~ pC ||| g <<< pF
als
pA = (f <<< (pA ~~~ pB ~~~ pC)) ||| (g <<< pF)
die Warhheit ist dank der Prioritäten aber
pA = ((((f <<< pA) ~~~ pB) ~~~ pC)) ||| (g <<< pF)
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Nutzung der Parser-eDSL
Kombinatorparser sindeinfach zu entwickelnfehlersicher: die Grammatik IST der Parsereinfach zu anzupassen, wenn die Sprache sich ändert
Grenzen:nicht so effizient wie generierte Parser (z.B. durch Yacc,Bison)bei mehrdeutigen Sprachen müssen sie mit dynamischerProgrammierung verbunden werden zur Bewertung undAuswahl der Lösungen
Ende der Vorlesung “Programmieren in Haskell”
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Nutzung der Parser-eDSL
Kombinatorparser sindeinfach zu entwickelnfehlersicher: die Grammatik IST der Parsereinfach zu anzupassen, wenn die Sprache sich ändert
Grenzen:nicht so effizient wie generierte Parser (z.B. durch Yacc,Bison)bei mehrdeutigen Sprachen müssen sie mit dynamischerProgrammierung verbunden werden zur Bewertung undAuswahl der Lösungen
Ende der Vorlesung “Programmieren in Haskell”
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
Wie geht es weiter
Programmiersprachen und Algorithmik⇒ Objektorientierte Programmierung in Java (2. Semester,
vll. OOP mit Hashing, Heaps, RB-Trees)⇒ Sequenzanalyse (3. Semester)⇒ Algorithmen der Informatik (4. Semester,
Graph-Algorithmen, . . . )Endliche Automaten, Registermaschine, Komplexität
⇒ Grundlagen Theoretischer Informatik (3. Semester,Formale Sprachen, Berechenbarkeit, Komplexität . . . )
von Neumann ArchitekturRechnerarchitektur (3. Semester)
Dynamic Programmingdiverse DP-Algorithmen in verschiedenen Veranstaltungen
⇒ Algebraic Dynamic Programming (im Master)
Programmierenin Haskell
StefanJanssen
Grammatikenund Parser
DSL
The EndViel Erfolg im Studium ...
... und auch etwas Spaß!
Letzte Sitzung: Fragestunde zur Prüfungsvorbereitung
top related