Universität Bielefeld Programmieren in Haskell Giegerich Programmieren in Haskell WS 2012/2013 Robert Giegerich Universität Bielefeld AG Praktische Informatik 29. Januar 2014 Leftovers Grammatiken und Parser DSL Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Programm heute: Nachtrag: “Records” : Konstruktoren mit Feldnamen Grammatiken und Parser Eine anwendungsspezifische Sprache (eingebettet in Haskell) Grammatiken und Parser DSL Konstruktoren mit benannten Feldern Universität Bielefeld Programmieren in Haskell Giegerich 1 2 1 2 3 Konstruktoren können mit Namen (field names) für ihre Leftovers Argumente deklariert werden Grammatiken .. das kennt man aus anderen Sprachen als “records” und Parser DSL Beispiel: Punkte im Koordinatensystem Neben der bisherigen Typ Definition wie z.B. > data Point = Pt Float Float | Pol Float Float String deriving ( Show gibt es auch die Form > data Point = Pt { pointx , pointy :: Float } | > Pol { angle , stretch :: Float , > color :: String } deriving ( Show , Eq ) Verwendung der Konstruktoren Universität Bielefeld Programmieren in Haskell 1 2 3 4 Mit oder ohne Verwendung der Feldnamen > p1 = Pt { pointx =1 , pointy =2} > p2 = Pt { pointy =2 , pointx =1} > p3 = Pol 45 1 > p30 = Pol { angle =45 , stretch =1} Giegerich Leftovers Grammatiken und Parser DSL 5 6 7 > p4 = Pt { pointx =0 , pointy =1} > p5 = Pol { angle =90 , stretch =1 , color = " yellow " } Bei verwendung der Namen: Die Klammern sind notwendig die Reihenfolge ist beliebig Nutzen der Feldnamen Universität Bielefeld Programmieren in Haskell Feldnamen vorwiegend gebraucht bei Konstruktoren mit sehr vielen Argumenten Anordnung ist egal: Pt 1 2 = Pt{pointy=2, pointx=1} Giegerich Leftovers Grammatiken und Parser DSL Feldnamen dienen als vordefinierte Selektoren mirror p = Pt {pointx= pointy p, pointy = pointx p} gleichnamige Feldnamen unter verschiedenen Konstruktoren sind erlaubt ... mit gleichem Typ Nicht alle Felder müssen definiert werden: p30 = Pol{angle=45, stretch=1} lässt Feld color undefiniert – im Unterschied zu p3 = Pol 45 1 Subtiler Unterschied –was geht hier vor? Main> p30 Pol {angle = 45.0, stretch = 1.0, color = " Program error: undefined field: Pol Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Main> p3 ERROR - Cannot find "show" function for: *** Expression : p3 *** Of type : String -> Point Main> p3 "red" Pol {angle = 45.0, stretch = 1.0, color = "red"} Main> p30 "red" ERROR - Type error *** Expression *** Term *** Type *** Does not match in application : p30 "red" : p30 : Point : a -> b Grammatiken und Parser DSL Vorschau Universität Bielefeld Programmieren in Haskell Das nächste Thema hat zwei Teile Giegerich kontextfreie Grammatiken, Sprachen und ihre “Parser” Leftovers Domain Specific Languages (DSL, anwendungsspezifische Sprachen) am Beispiel des Parsens Grammatiken und Parser als Beispiel einer Spracherweiterung in Haskell DSL Vorschau Universität Bielefeld Programmieren in Haskell Das nächste Thema hat zwei Teile Giegerich kontextfreie Grammatiken, Sprachen und ihre “Parser” Leftovers Domain Specific Languages (DSL, anwendungsspezifische Sprachen) am Beispiel des Parsens Grammatiken und Parser als Beispiel einer Spracherweiterung in Haskell Gemeinsamer Ausgangspunkt: In vielen Anwendungen besteht die Eingabe aus einem Text, der in seiner Struktur erfasst werden muss, ehe die eigentliche Verarbeitung beginnt kann diese Struktur durch ein Grammatik beschrieben werden DSL Beispiel: Übersetzung von Programmen Universität Bielefeld Programmieren in Haskell Giegerich Wichtigstes Beispiel: Compiler Eingabe: Programmtext als ASCII string Zerlegung 1: Folge von Namen, Zahlen, Operatoren Zerlegung 2: Erkennen von Deklarationen, Definitionen, Ausdrücken, Import-Listen, ... Resultat der “syntaktischen Analyse”: Baumartige Darstellung des Programms als rekursiver Datentyp Weitere Verarbeitung des Programmbaums: Semantische Prüfung, Transformation, Optimierung, Codeerzeugung, ... Leftovers Grammatiken und Parser DSL Beispiele von DSLs (1) Universität Bielefeld Programmieren in Haskell Textbeschreibung in LATEX: Giegerich Leftovers \section{Grammatiken und Parser} Grammatiken \begin{frame}{Vorschau} und Parser Das n\"achste Thema hat zwei Teile DSL \begin{itemize} \item ‘‘Parser’’ f\"ur kontextfreie Sprachen \item Domain Specific Languages (DSL, anwendungsspezifische S \item \emph{als Beispiel} einer Spracherweiterung in Haskell \end{itemize} \pause ... Beispiele von DSLs (2) Universität Bielefeld Programmieren in Haskell Literatureintrag in BibTeX: Example 1 2 3 4 5 6 7 8 9 Giegerich Leftovers Grammatiken und Parser @book {UBHD1745069 , DSL a u t h o r ={B i r d , R i c h a r d } , t i t l e ={ I n t r o d u c t i o n t o f u n c t i o n a l programming u s i n g H a s k e l l } , p a g e s ={433} , p u b l i s h e r ={ P r e n t i c e H a l l } , y e a r ={1998} , i s b n ={978−0−13−484346−9}, e d i t i o n ={2nd } } Beispiele von DSLs (2) Universität Bielefeld Programmieren in Haskell Giegerich Literatureintrag in BibTeX: Leftovers Example 1 2 3 4 5 6 7 8 9 Grammatiken und Parser @book {UBHD1745069 , DSL a u t h o r ={B i r d , R i c h a r d } , t i t l e ={ I n t r o d u c t i o n t o f u n c t i o n a l programming u s i n g H a s k e l l } , p a g e s ={433} , p u b l i s h e r ={ P r e n t i c e H a l l } , y e a r ={1998} , i s b n ={978−0−13−484346−9}, e d i t i o n ={2nd } } \ cite { UBHD 1745069} -- Referenz im Text Beispiele von DSLs (2) Universität Bielefeld Programmieren in Haskell Giegerich Literatureintrag in BibTeX: Leftovers Example 1 2 3 4 5 6 7 8 9 Grammatiken und Parser @book {UBHD1745069 , DSL a u t h o r ={B i r d , R i c h a r d } , t i t l e ={ I n t r o d u c t i o n t o f u n c t i o n a l programming u s i n g H a s k e l l } , p a g e s ={433} , p u b l i s h e r ={ P r e n t i c e H a l l } , y e a r ={1998} , i s b n ={978−0−13−484346−9}, e d i t i o n ={2nd } } \ cite { UBHD 1745069} -- Referenz im Text Textzerlegung mit groupBy Universität Bielefeld Programmieren in Haskell Giegerich Eine ausgesprochen nützliche Funktion: groupBy aus Data.List groupBy :: (a -> a -> Bool) -> [a] -> [[a]] zerlegt einen String in “Gruppen”, so dass concat . groupBy f == id für jedes f gruppiert wird, solange die Bedingung f über dem ersten und dem (bisher) letzten Buchstaben der Gruppe erfüllt ist der erste Buchstabe, der die Bedingung f verletzt, startet eine neue Gruppe Leftovers Grammatiken und Parser DSL Beispiele zu GroupBy 1 2 Parse > groupBy (==) " aaggccccggt " [ " aa " ," gg " ," cccc " ," gg " ," t " ] 3 4 5 Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Parse > groupBy (/=) " aaccgaccgaaa " [ " a " ," accg " ," accg " ," a " ," a " ," a " ] Grammatiken und Parser DSL 6 7 8 Parse > groupBy ( <=) " abraham " [ " abraham " ] 9 10 11 Parse > groupBy ( <) " abraham " [ " abr " ," ah " ," am " ] 12 13 14 15 Parse > groupBy (\ a b -> a /= ’ ’ && b /= ’ ’) " wenn er aber kommt ? " [ " wenn " ," " ," er " ," " ," aber " ," " ," kommt ? " ] 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, relativ flache Strukturen Programme haben Operatoren, Variablen, Konstanten, Zahlen, Ausdrücke, Deklarationen, Definitionen, und beliebig tiefe Strukturen Komplexe formale Sprachen beschreibt man durch Grammatiken, die Sprachen generieren, oder Automaten, die Sprachen akzeptieren Beide Methoden sind gleich mächtig Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser DSL Klassen formaler Sprachen Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Endliche Automaten sind aus A&D bekannt. Sie können Sprachen wie {(ab)n } beschreiben, aber nicht {an b n }. Grammatiken und Parser DSL Klassen formaler Sprachen Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Endliche Automaten sind aus A&D bekannt. Sie können Sprachen wie {(ab)n } beschreiben, aber nicht {an b n }. Kontextfreie Grammatiken sind etwas mächtiger. Sie können Sprachen wie {an b n } und {an b m c n } beschreiben, aber nicht {an b n c n } oder {an b m c n d m }. Grammatiken und Parser DSL Kontextfreie Grammatiken Hier eine kleine Grammatik für Ausdrücke: Universität Bielefeld Programmieren in Haskell Giegerich Terminalsymbole: 0 1 2 a b c d ) ( + * Nichtterminalsymbole: A T F Z B C Axiom A Leftovers Grammatiken und Parser DSL Regeln A -> T T -> F F -> Z Z -> 0 B -> C C -> a | | | | | | T F B 1 C b + A * T | ( A ) | 2 B | c | d Ausdrücke sind also "a+1*(abba+2)", "2*a+2*b", "2*(a+b)" Programmbäume Universität Bielefeld Programmieren in Haskell 1 2 3 Darstellung der Ausdrücke "a+1*(abba+2)", "2*a+2*b", "2*(a+b)" nach ihrer Zerlegung Plus ( Var " a " ) ( Mul ( Numb " 1 " ) ( Plus ( var " abba " ) ( Numb " 2 " ))) 4 5 6 Plus ( Mul ( Numb " 2 " ) ( Var " a " )) ( Mul ( Numb " 2 " ) ( Var " b " )) 7 8 9 10 Mul ( Numb " 2 " ) ( Plus ( Var " a " ) ( Var " b " )) Giegerich Leftovers Grammatiken und Parser DSL Vorüberlegungen zum Parser Universität Bielefeld Programmieren in Haskell Giegerich Anforderungen: Der Parser soll Leftovers wohlgeformte Ausdrücke in Programmbäume übersetzen wenn es mehrere Parses gibt, dann für alle den Programmbaum liefern ... ... und ggf. gar keinen Wir schreiben einen rekursiven Top-Down Parser: Für jedes Nichtterminalsymbol X gibt es eine Parserfunktion pX die Ableitungen aus X konstruiert und die entsprechenden Programmbäume zusammensetzt Grammatiken und Parser DSL Arbeitsweise der Parser Universität Bielefeld Programmieren in Haskell Eine Parserfunktion pX arbeitet wie folgt Eingabe ist ein (restlicher) String w pX leitet Präfix u von w = uv aus X ab dazu ruft sie sich selbst und andere Parser für die NTs der rechten Seite auf gibt Programmbaum P(X -> u) und Resteingabe v zurück ... ... und das für alle Paare P(X -> u) und v Wird kein Parse gefunden, wird eine leere Liste zurückgegeben Giegerich Leftovers Grammatiken und Parser DSL Parser-Code Universität Bielefeld Programmieren in Haskell 1 > type Parser a b = [ a ] -> [( b , [ a ])] Leftovers 2 3 4 5 6 > data Ptree = Plus > Mul > Var > Numb Ptree Ptree | Ptree Ptree | String | String deriving Show 7 8 Giegerich > parse xs = [ t | (t ,[]) <- pA xs ] pA wird als erster Parser aufgerufen, weil A das Axiom der Grammatik ist Ein Parse ist nur erfolgreich, wenn er die ganze Eingabe “verbraucht”. Grammatiken und Parser DSL Parser-Code Universität Bielefeld Programmieren in Haskell Giegerich 1 2 3 4 5 6 7 8 9 10 > pA xs = pT xs ++ Leftovers > [( Plus l r , v ) | (l , o : u ) <- pT xs ,Grammatiken und Parser > o == ’+ ’ , DSL > (r , v ) <- pA u ] > pT xs = pF xs ++ > [( Mul l r , v ) | (l , ’* ’: u ) <- pF xs , > (r , v ) <- pT u ] > pF xs = pZ xs ++ pB xs ++ > [( t , v ) | ( ’( ’: u ) <- [ xs ] , > (t , ’) ’: v ) <- pA u ] Parser-Code Universität Bielefeld Programmieren in Haskell 1 2 3 4 5 6 7 8 9 10 11 12 > pZ xs = > > > > pB xs = > > > pC xs = > > > > case xs of ’0 ’: v -> [( Numb " 0 " , v )] Giegerich ’1 ’: v -> [( Numb " 1 " , v )]Leftovers ’2 ’: v -> [( Numb " 2 " , v )]Grammatiken und Parser otherwise -> [] DSL [( Var ( c : " " ) , v ) | (c , v ) <- pC xs ] ++ [( Var ( c : cs ) , v ) | (c , u ) <- pC xs , ( Var cs , v ) <- pB u ] case xs of ’a ’: v -> [( ’a ’ , v )] ’b ’: v -> [( ’b ’ , v )] ’c ’: v -> [( ’c ’ , v )] ’d ’: v -> [( ’d ’ , v )] otherwise -> [] Systematik Bei der Parser-Konstruktion wiederholen sich Universität Bielefeld Programmieren in Haskell Giegerich Lesen bestimmter Zeichen oder Zeichenkombinationen Aufruf anderer Parser in der Reihenfolge der Nonterminals auf den rechten Seiten (++) für die Zusammenführung von Resultaten aus Alternativen Weiterreichen von Teilergebnissen und Resteingabe an den nächsten Parser Anwendung eines Programmbaum-Konstruktors auf Teilergebnisse Wenn wir dafür spezielle Operationen kreieren, erhalten wir eine DSL für die schnelle und fehlersichere Implementierung von Parsern Leftovers Grammatiken und Parser DSL Vorfreude Universität Bielefeld Der Parser für die Grammatik A T F Z B C -> -> -> -> -> -> T F Z 0 C a | | | | | | T F B 1 C b + * | | B | A T ( A ) 2 c | d Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser DSL Vorfreude Universität Bielefeld Der Parser für die Grammatik A T F Z B C 1 2 3 4 5 6 -> -> -> -> -> -> T F Z 0 C a | | | | | | T F B 1 C b + * | | B | A T ( A ) 2 c | d wird dann etwa so aussehen: > pA = pT ||| Plus <<< pT ~~~ ’+ ’ ~~~ pA > pT = pF ||| Mul <<< pF ~~~ ’* ’ ~~~ pT > pF = pZ ||| pB ||| ’( ’ ~~~ pA ~~~ ’) ’ > pZ = Numb <<< ’0 ’ ||| ’1 ’ ||| ’2 ’ > pB = (:[]) <<< pC ||| (:) <<< pC ~~~ pB > pC = ’a ’||| ’ b ||| ’ c ’||| ’ d ’ Achtung: Das ist noch nicht der endgültige Code Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser DSL Vorbereitung Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Was wird gebraucht? Definition der Parser-Kombinatoren Definition der terminalen Parser Definition der Funktionen zum Baumaufbau, soweit nicht direkt die Konstruktoren verwendbar sind Grammatiken und Parser DSL Zur Erinnerung Universität Bielefeld Programmieren in Haskell Giegerich 1 Typ eines Parsers war > 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 .... Leftovers Grammatiken und Parser DSL Parser für einzelne Zeichen Universität Bielefeld Programmieren in Haskell 1 2 3 4 5 > cchar :: ( Eq a ) = > a -> Parser a a Giegerich -- vergleiche mit Definition von char weiter unten Leftovers > cchar c [] = [] Grammatiken > cchar c ( x : xs ) und Parser > | x == c = [ (c , xs ) ] DSL > | otherwise = [] Parser cchar ’x’ erkennt nur den Buchstaben ’x’ am Anfang der Eingabe. Später lernen wir einen allgemeineren Parser für beliebige Eingabesymbole kennen. Parser-Kombinatoren Universität Bielefeld Programmieren in Haskell Kombinatoren nennt man Funktionen, deren Argumente und Ergebnisse Funktionen sind. Giegerich Leftovers Grammatiken und Parser 1 2 3 DSL Unsere Kombinatoren kombinieren Parser mit Parsern zu neuen Parsern > infix 8 <<< -- Programmbaum - Aufbau > infixl 7 ~~~ -- Verkettung von Parsern > infixr 6 ||| -- Zusammenfassen von Alternativen Die Wahl der Prioritäten und der Assoziierungsrichtung wird später wichtig ... Parser-Kombinatoren Universität Bielefeld Programmieren in Haskell Giegerich Kombinatoren für Funktionsanwendung, Verkettung und Alternative 1 2 3 Leftovers Grammatiken und Parser > ( < < <) :: ( b -> c ) -> Parser a b -> Parser a c DSL > ( < < <) f p inp = [( f x , rest )| (x , rest ) <- p inp ] 4 5 6 7 8 9 > > > > > (~~~) :: Parser a (b - > c ) - > Parser a b - > Parser a c (~~~) p q inp = [( x y , r2 ) | (x , rest ) <- p inp , (y , r2 ) <- q rest ] (|||) :: Parser a b -> Parser a b -> Parser a b (|||) p q inp = p inp ++ q inp Parser-Kombinatoren Universität Bielefeld Programmieren in Haskell Giegerich Leftovers 1 2 Jeder Parser kann zum Axiom der Grammatik erklärt werden durch den Kombinator axiom > axiom :: [ a ] -> Parser a b -> [ b ] > axiom inp p = [ x | (x , []) <- p inp ] Das Axiom muss die ganze Eingabe ableiten, nicht nur einen Präfix davon (wie alle anderen Parser) Grammatiken und Parser DSL Grammatik in Kombinator-Schreibweise Universität Bielefeld Programmieren in Haskell Jetzt können wir den Parser wie eine Kopie der Regeln in der Grammatik schreiben: 1 1 2 Giegerich Leftovers Grammatiken und Parser Aus > A -> T | T + A wird DSL pA = pT ||| plus <<< pT ~~~ cchar ’+ ’ ~~~ pA aus > > F -> Z | B | ( A ) wird pF = Numb <<< pZ ||| Var <<< pB ||| mid <<< cchar ’( ’ ~~~ pA ~~~ cchar ’) ’ Wir brauchen aber noch ein paar Bausteine Programmbaumaufbau Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Grammatiken 1 2 3 4 Die Konstruktoren Plus und Mul erwarten nur zwei und Parser DSL Argumente, der Parser liefert drei. Daher: > pC = cchar ’a ’ ||| cchar ’b ’ ||| cchar ’c ’ > ||| cchar ’d ’ > plus x _ z = Plus x z > mul x _ z = Mul x z Der komplette Parser Universität Bielefeld Programmieren in Haskell 1 2 3 4 5 6 7 8 9 10 11 12 13 14 > parse_C :: String -> [ Ptree ] Giegerich > parse_C inp = axiom inp pA where > pA = pT ||| plus <<< pT ~~~ cchar ’+ ’ ~~~Leftovers pA Grammatiken > pT = pF ||| mul <<< pF ~~~ cchar ’* ’ ~~~undpT Parser > pF = Numb <<< pZ ||| Var <<< pB ||| DSL > mid <<< cchar ’( ’ ~~~ pA ~~~ cchar ’) ’ > pZ = (:[]) <<< ( cchar ’0 ’ ||| cchar ’1 ’ > ||| cchar ’2 ’) > pB = (:[]) <<< pC ||| (:) <<< pC ~~~ pB > pC = cchar ’a ’ ||| cchar ’b ’ ||| cchar ’c ’ > ||| cchar ’d ’ > plus x _ z = Plus x z > mul x _ z = Mul x z > mid _ y _ = y Vereinfachungen Universität Bielefeld Programmieren in Haskell Giegerich Reale Parser werden nach der gleichen Methode gebaut, sind aber komplizierter Zahlen mit beliebig vielen Ziffern Operatoren aus mehreren Zeichen “Whitespace” in der Eingabe Es muss eine Ebene der lexikalischen Analyse vorgschaltet werden. Dazu gibt es ein ausgefeilteres Beispiel im nächsten Abschnitt Leftovers Grammatiken und Parser DSL DSLs – Anwendungssprachen Universität Bielefeld Programmieren in Haskell Domain Specific Language (DSL) vs. General-Purpose-Language, Libraries, Frameworks Ziele: Unterstützung komplexer Anwendungen mit wiederkehrenden Grund-Konstruktionen Beispiele: TEX, LATEX, Lex, Yacc, awk . . . Giegerich Leftovers Grammatiken und Parser DSL DSLs – Anwendungssprachen Universität Bielefeld Programmieren in Haskell Domain Specific Language (DSL) vs. General-Purpose-Language, Libraries, Frameworks Ziele: Unterstützung komplexer Anwendungen mit wiederkehrenden Grund-Konstruktionen Beispiele: TEX, LATEX, Lex, Yacc, awk . . . Fortgeschrittenes Paul Hudak. Modular Domain Specific Languages and Tools. Proceedings of the Fifth International Conference on Software Reuse, IEEE Computer Society, 1998. http://haskell.cs. yale.edu/wp-content/uploads/2011/01/DSEL-Reuse.pdf Giegerich Leftovers Grammatiken und Parser DSL Autonome versus eingebettete DSL Autonome DSL: Eigenständige Sprache Universität Bielefeld Programmieren in Haskell Giegerich Beispiel aus Bielefeld: Bellman’s GAP Gebiet: biologische Sequenzanalyse eigener Compiler mit umfangreicher Optimierung Leftovers Grammatiken und Parser DSL Autonome versus eingebettete DSL Autonome DSL: Eigenständige Sprache Universität Bielefeld Programmieren in Haskell Giegerich Beispiel aus Bielefeld: Bellman’s GAP Gebiet: biologische Sequenzanalyse eigener Compiler mit umfangreicher Optimierung Leftovers Grammatiken und Parser DSL Embedded DSL: Sprach-Subset, Erweiterung oder Bibliothek in Host Sprache Beispiel aus Bielefeld: Haskell ADP, ADPfusion Gebiet: biologische Sequenzanalyse kein eigener Compiler, keine spezifische Optimierung, aber einfache Implementierung Autonome versus eingebettete DSL Autonome DSL: Eigenständige Sprache Universität Bielefeld Programmieren in Haskell Giegerich Beispiel aus Bielefeld: Bellman’s GAP Gebiet: biologische Sequenzanalyse eigener Compiler mit umfangreicher Optimierung Leftovers Grammatiken und Parser DSL Embedded DSL: Sprach-Subset, Erweiterung oder Bibliothek in Host Sprache Beispiel aus Bielefeld: Haskell ADP, ADPfusion Gebiet: biologische Sequenzanalyse kein eigener Compiler, keine spezifische Optimierung, aber einfache Implementierung Strategie für neue DSL: Erst “embedded” entwickeln und erproben, dann “ex-bedding” Unser eDSL-Beispiel Eine eDSL zur Parser-Konstruktion, eingebettet in Haskell allgemein: Parser-Combinators (sind unabhängig von gegebener Grammatik) HAskell ist flexibel genug und erlaubt durch die Definition der Kombinatoren als inf-x-Operatoren eine “eingebettete” Spracherweiterung Dazu noch ein nicht ganz so einfaches Beispiel: BibTeX-Format Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser DSL Unser eDSL-Beispiel Eine eDSL zur Parser-Konstruktion, eingebettet in Haskell allgemein: Parser-Combinators (sind unabhängig von gegebener Grammatik) HAskell ist flexibel genug und erlaubt durch die Definition der Kombinatoren als inf-x-Operatoren eine “eingebettete” Spracherweiterung Dazu noch ein nicht ganz so einfaches Beispiel: BibTeX-Format Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser DSL Example 1 2 3 4 5 6 7 8 9 @book {UBHD1745069 , a u t h o r ={B i r d , R i c h a r d } , t i t l e ={ I n t r o d u c t i o n t o f u n c t i o n a l programming u s i n g H a s k e l l } , p a g e s ={433} , p u b l i s h e r ={ P r e n t i c e H a l l } , y e a r ={1998} , i s b n ={978−0−13−484346−9}, e d i t i o n ={2nd } } Unser eDSL-Beispiel Eine eDSL zur Parser-Konstruktion, eingebettet in Haskell allgemein: Parser-Combinators (sind unabhängig von gegebener Grammatik) HAskell ist flexibel genug und erlaubt durch die Definition der Kombinatoren als inf-x-Operatoren eine “eingebettete” Spracherweiterung Dazu noch ein nicht ganz so einfaches Beispiel: BibTeX-Format Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser DSL Example 1 2 3 4 5 6 7 8 9 @book {UBHD1745069 , a u t h o r ={B i r d , R i c h a r d } , t i t l e ={ I n t r o d u c t i o n t o f u n c t i o n a l programming u s i n g H a s k e l l } , p a g e s ={433} , p u b l i s h e r ={ P r e n t i c e H a l l } , y e a r ={1998} , i s b n ={978−0−13−484346−9}, e d i t i o n ={2nd } } Grammatik für BibTex-Eintraege Universität Bielefeld Programmieren in Haskell Giegerich record recHead recBody recEnd entype entries entry rhs -> -> -> -> -> -> -> -> recHead recBody recEnd ’@’ entype ’{’ aword ’,’ entries ’}’ "book" | "article" entry | entry ’,’ entries aword ’=’ rhs ’{’ content ’}’ | ’"’ content ’"’ | aword aword: beliebige Zeichenreihe ohne die Trennzeichen { } @ = , Leftovers Grammatiken und Parser DSL Weitere Festlegungen Universität Bielefeld Programmieren in Haskell Giegerich 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 Leftovers Grammatiken und Parser DSL Zur Erinnerung Universität Bielefeld Programmieren in Haskell Giegerich 1 Typ eines Parsers war > 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 .... Leftovers Grammatiken und Parser DSL Parser-Kombinatoren Wie zuvor ... Kombinatoren nennt man Funktionen, deren Argumente und Ergebnisse Funktionen sind. Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser DSL 1 2 3 Unsere Kombinatoren kombinieren Parser mit Parsern zu neuen Parsern > infix 8 <<< -- Programmbaum - Aufbau > infixl 7 ~~~ -- Verkettung von Parsern > infixr 6 ||| -- Zusammenfassen von Alternativen Die Wahl der Prioritäten und der Assoziierungsrichtung wird später wichtig ... Parser-Kombinatoren Universität Bielefeld Programmieren in Haskell Giegerich Wie zuvor ... Leftovers 1 Grammatiken 2 3 > ( < < <) :: ( b -> c ) -> Parser a b -> Parser aundcParser DSL > ( < < <) f p inp = [( f x , rest )| (x , rest ) <p inp ] 4 5 6 7 8 9 > > > > > (~~~) :: Parser a (b - > c ) - > Parser a b - > Parser a c (~~~) p q inp = [( x y , r2 ) | (x , rest ) <- p inp , (y , r2 ) <- q rest ] (|||) :: Parser a b -> Parser a b -> Parser a b (|||) p q inp = p inp ++ q inp Parser-Kombinatoren Universität Bielefeld Programmieren in Haskell Giegerich Wie zuvor... Leftovers Grammatiken und Parser DSL 1 2 Jeder Parser kann zum Axiom der Grammatik erklärt werden durch den Kombinator axiom > axiom :: [ a ] -> Parser a b -> [ b ] > axiom inp p = [ x | (x , []) <- p inp ] Das Axiom muss die ganze Eingabe ableiten, nicht nur einen Präfix davon (wie alle anderen Parser) Grammatik in Kombinator-Schreibweise Universität Bielefeld Programmieren in Haskell Giegerich Jetzt können wir den Parser wie eine Kopie der Regeln in der Grammatik schreiben: Leftovers Grammatiken und Parser Aus entries -> entry | entry ’,’ entries wird 1 2 > > entries DSL = s1 <<< entry ||| s2 <<< entry ~~~ char ’,’ ~~~ entries aus entry -> aword ’=’ rhs wird 1 > entry = e1 <<< aword ~~~ char ’= ’ ~~~ Wir brauchen aber noch ein paar Bausteine rhs Parser für die lexikalischen tokens Universität Bielefeld Programmieren in Haskell Giegerich 1 2 3 4 5 > char :: ( Eq a ) = > a -> Parser [ a ] a > char c [] = [] > char c ( x : xs ) > | x == [ c ] = [ (c , xs ) ] > | otherwise = [] Parser char ’c’ erkennt nur den Buchstaben ’c’ als "c" am Anfang der Eingabe Leftovers Grammatiken und Parser DSL Parser für die lexikalischen tokens Programmieren in Haskell 1 2 3 4 5 6 > word :: Eq a = > a -> Parser a a > word w [] = [] > word w ( x : xs ) > | x == w = [ (w , xs ) ] > | otherwise = [] 7 8 9 10 11 12 Universität Bielefeld > aword :: Parser String String > aword [] = [] > aword ( x : xs ) > | x /= " { " && x /= " } " = [ (x , xs ) ] > | otherwise = [] Parser aword akzeptiert ein beliebiges Wort (außer den Klammern), Giegerich Leftovers Grammatiken und Parser DSL Aufbau der Bäume für die Bib-Entries Universität Bielefeld Programmieren in Haskell Giegerich 1 2 3 4 5 > data BibRecord = BR { Leftovers > bibType :: String , Grammatiken und Parser > bibId :: String , DSL > bibEntries :: [ BibEntry ] > } deriving ( Show ) 6 7 8 9 10 > data BibEntry = BE { > entryKey :: String , > entryVals :: [ String ] > } deriving ( Show ) Lexikalische Analyse Universität Bielefeld Programmieren in Haskell Giegerich 1 2 3 4 5 6 > lexer :: String -> [ String ] Leftovers > lexer inp = filter isWS $ groupBy g inp where Grammatiken und Parser > g a b = and $ map (\ c -> a /= c && b /= c ) delims DSL > delims = " {} ,@ ,= " > isWS w = not $ and $ > map (\ c -> c == ’ ’ || c == ’\ n ’) w Vorgruppieren mittels groupBy, Herausfiltern des Whitespace mit isWS Parser-Code Universität Bielefeld Programmieren in Haskell Die Grammatik in Kombinator-Schreibweise > parser inp > where > record > recHead > > recBody > recEnd > entype > > entries = axiom inp record Giegerich Leftovers Grammatiken und Parser = a1 <<< recHead ~~~ recBody ~~~ recEnd DSL = h1 <<< char ’@’ ~~~ entype ~~~ char ’{’ ~~~ aword ~~~ char ’,’ = entries = char ’}’ = word "book" ||| word "article" = s1 <<< entry ||| s2 <<< entry ~~~ char ’,’ ~~~ entries Parser-Code Universität Bielefeld Programmieren in Haskell Die Grammatik in Kombinator-Schreibweise > > entry rhs > content = e1 <<< = r1 <<< r1 <<< r2 <<< = c1 <<< c2 <<< c3 <<< Giegerich Leftovers aword ~~~ char ’=’ ~~~ rhs Grammatiken char ’{’ ~~~ content ~~~ char ’}’ ||| und Parser char ’"’ ~~~ content ~~~ char ’"’ ||| DSL aword char ’{’ ~~~ content ~~~ char ’}’ ||| aword ~~~ content ||| aword Die Funktionen e1, r1, ... sind für den Baumaufbau zuständig Baumaufbau Universität Bielefeld Programmieren in Haskell 1 2 3 4 5 6 7 8 9 10 Der Baumaufbau benutzt die Konstruktoren lässt irrelvante tokens weg: > a1 ( typ , iden ) ents _ = > h1 _ typ _ iden _ = > s1 e = > s2 e _ es = > e1 k _ xs = > r1 _ x _ = > r2 x = > c1 _ x _ = > c2 a b = > c3 a = BR und BE, aber Giegerich Leftovers Grammatiken BR typ iden ents und Parser ( typ , iden ) DSL [e] e : es BE k xs x [x] ( " { " : x ) ++ [ " } " ] a:b [a] Aufruf des Parsers Lexer und Parser werden hintereinandergeschaltet im Aufruf parser $ lexer xs Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser Wir sind fertig!! Die Grammatik in Kombinator-Schreibweise IST der Parser ...! Siehe Anwendungsbeispiel im Code von parse.lhs DSL Aufruf des Parsers Lexer und Parser werden hintereinandergeschaltet im Aufruf parser $ lexer xs Universität Bielefeld Programmieren in Haskell Giegerich Leftovers Grammatiken und Parser Wir sind fertig!! Die Grammatik in Kombinator-Schreibweise IST der Parser ...! Siehe Anwendungsbeispiel im Code von parse.lhs Fortgeschrittenes Graham Hutton. Higher-order functions for parsing. Journal of Functional Programming, Volume 2 Issue 3, 323–343, 1992. http://www.cs.nott.ac.uk/~gmh/parsing.pdf DSL Traum und Wahrheit Universität Bielefeld Programmieren in Haskell Wir lesen den Parser für Giegerich A → B C D | F, Leftovers Grammatiken und Parser also pA = f <<< pA ~~~ pB ~~~ pC DSL ||| 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) Nutzung der Parser-eDSL Universität Bielefeld Programmieren in Haskell Kombinatorparser sind Giegerich einfach zu entwickeln Leftovers fehlersicher: die Grammatik IST der Parser Grammatiken und Parser einfach zu anzupassen, wenn die Sprache sich ändert DSL Grenzen: nicht so effizient wie generierte Parser (z.B. durch Yacc, Bison) bei mehrdeutigen Sprachen müssen sie mit dynamischer Programmierung verbunden werden zur Bewertung und Auswahl der Lösungen Nutzung der Parser-eDSL Universität Bielefeld Programmieren in Haskell Kombinatorparser sind Giegerich einfach zu entwickeln Leftovers fehlersicher: die Grammatik IST der Parser Grammatiken und Parser einfach zu anzupassen, wenn die Sprache sich ändert DSL Grenzen: nicht so effizient wie generierte Parser (z.B. durch Yacc, Bison) bei mehrdeutigen Sprachen müssen sie mit dynamischer Programmierung verbunden werden zur Bewertung und Auswahl der Lösungen Ende der Vorlesung “Programmieren in Haskell” Wie geht es weiter Universität Bielefeld Programmieren in Haskell 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 Architektur Rechnerarchitektur (3. Semester) Dynamic Programming diverse DP-Algorithmen in verschiedenen Veranstaltungen ⇒ Algebraic Dynamic Programming (im Master) Giegerich Leftovers Grammatiken und Parser DSL Universität Bielefeld Programmieren in Haskell Giegerich Leftovers The End Viel Erfolg im Studium ... ... und auch etwas Spaß! Nächsten Mittwoch: Fragestunde zur Prüfungsvorbereitung Grammatiken und Parser DSL