Programmieren in Haskell

Werbung
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
Herunterladen