@@ -5,39 +5,339 @@ points: 10 Punkte
55title : " Blatt 04: Lexer und Parser selbst implementiert"
66---
77
8- ::: center
9- ** Wir arbeiten gerade an dieser Seite ...**
10- :::
11-
12- <!--
138# Zusammenfassung
149
15- Ziel dieses Aufgabenblattes ist ...
16- TODO
10+ Ziel dieses Aufgabenblattes ist die Erstellung einer Grammatik sowie daraus
11+ ableitend die manuelle Implementierung eines Lexers und Parsers mit * recursive
12+ descent* für eine Lisp-artige Sprache.
1713
1814# Methodik
1915
20- TODO
16+ Sie finden nachfolgend eine Beschreibung der Zielsprache für dieses Blatt.
17+
18+ Erstellen Sie zunächst eine Grammatik für diese Sprache. Implementieren Sie dann
19+ basierend auf dieser Grammatik einen Lexer und einen * recursive descent* Parser
20+ (manuelle Implementierung, kein ANTLR). Überlegen Sie sich, welche Strukturen ein
21+ AST für diese Sprache haben sollte und implementieren Sie diese Strukturen in Java
22+ und schreiben Sie eine Transformation des Parse-Trees in diesen AST.
23+
24+ Es ist empfehlenswert, die Implementierung in Java mehrstufig zu realisieren:
25+
26+ 1 . Einlesen aus einer Datei mit Lisp-artigem Code
27+ 2 . Lexen des eingelesenen Code
28+ 3 . Parsen des Tokenstroms
29+ 4 . Erzeugen und Ausgeben des AST
30+
31+ # Sprachdefinition
32+
33+ Ein Programm besteht aus einem oder mehreren Ausdrücken (* Expressions* ). Die
34+ Ausdrücke haben eine spezielle Form: Sie sind sogenannte
35+ [ S-Expressions] ( https://en.wikipedia.org/wiki/S-expression ) . Dies sind entweder
36+ Literale der Form ` x ` oder einfache listenartige Gebilde der Form ` (. x y) ` , wobei
37+ der ` . ` eine Operation (oder Funktion) darstellt und ` x ` und ` y ` selbst wieder
38+ S-Expressions sind.
39+
40+ Die einfachste Form sind dabei Literale mit konkreten Werten der drei Datentypen
41+ ` Integer ` , ` String ` und ` Boolean ` :
42+
43+ ``` clojure
44+ 42 ; ; Integer
45+ " hello" ; ; String
46+ true ; ; Boolean
47+ false ; ; Boolean
48+ ```
49+
50+ Für eine Variable ` foo ` wäre das Folgende ebenfalls eine S-Expression:
51+
52+ ``` clojure
53+ foo ; ; Variable foo
54+ ```
55+
56+ (Über ` ;; ` wird ein Kommentar eingeleitet, der bis zum Ende der Zeile geht.)
57+
58+ Komplexere Ausdrücke werden über die Listenform gebildet:
59+
60+ ``` clojure
61+ (+ 1 1 ) ; ; 1 + 1
62+ (/ 10 3 ) ; ; 10 / 3
63+ (+ 1 2 3 4 ) ; ; 1 + 2 + 3 + 4
64+ (+ (+ (+ 1 2 ) 3 ) 4 ) ; ; (((1 + 2) + 3) + 4)
65+ (/ (+ 10 2 ) (+ 2 4 )) ; ; ((10 + 2) / (2 + 4))
66+ ```
67+
68+ In der listenartigen Form ist der erste Eintrag der Liste immer eine Operation (oder
69+ ein Funktionsname), danach kommen je nach Operation/Funktion (die Arität muss
70+ passen!) entsprechende Einträge, die als Parameter für die Operation oder Funktion
71+ zu verstehen sind.
72+
73+ Die Ausdrücke sind implizit von links nach rechts geklammert, d.h. der Ausdruck
74+ ` (+ 1 2 3 4) ` ist [ * syntactic sugar* ] ( https://en.wikipedia.org/wiki/Syntactic_sugar )
75+ für ` (+ (+ (+ 1 2) 3) 4) ` .
76+
77+ ## Eingebaute Funktionen
78+
79+ Es gibt zwei Funktionen, die fest in der Sprache integriert sind.
80+
81+ Mit der eingebauten Funktion ` print ` kann der Wert eines Ausdrucks auf der Konsole
82+ ausgegeben werden:
83+
84+ ``` clojure
85+ (print " hello world" )
86+ (print " wuppie\n fluppie\n foo\n bar" )
87+ ```
88+
89+ Die eingebaute Funktion ` str ` verknüpft ihre Argumente und bildet einen String.
90+ Falls nötig, werden die Argumente vorher in einen String umgewandelt.
91+
92+ ``` clojure
93+ (str 42 ) ; ; liefert "42" zurück
94+ (str " wuppie" " fluppie" " foo" " bar" ) ; ; liefert "wuppiefluppiefoobar" zurück
95+ (str " one: " 1 " , two: " 2 ) ; ; liefert "one: 1, two: 2" zurück
96+ ```
97+
98+ ## Operatoren
99+
100+ Es gibt nur wenige vordefinierte Operatoren, diese mit der üblichen Semantik.
101+
102+ ### Vergleichsoperatoren
103+
104+ | Operation | Operator |
105+ | :-----------| :--------:|
106+ | Gleichheit | ` = ` |
107+ | Größer | ` > ` |
108+ | Kleiner | ` < ` |
109+
110+ Die Operanden müssen jeweils beide den selben Typ haben. Dabei sind ` String ` und
111+ ` Integer ` zulässig. Das Ergebnis ist immer vom Typ ` Boolean ` .
112+
113+ ### Arithmetische Operatoren
114+
115+ | Operation | Operator |
116+ | :---------------| :--------:|
117+ | Addition | ` + ` |
118+ | Subtraktion | ` - ` |
119+ | Multiplikation | ` * ` |
120+ | Division | ` / ` |
121+
122+ Die Operanden müssen jeweils beide den selben Typ haben. Dabei sind ` String ` und
123+ ` Integer ` zulässig. Das Ergebnis ist vom Typ der Operanden.
124+
125+ ## Kontrollstrukturen (If-Else)
126+
127+ Die ` if-then-else ` -Abfrage gibt es mit und ohne den ` else ` -Zweig:
128+
129+ ``` clojure
130+ (if boolean-form
131+ then-form
132+ optional-else-form)
133+ ```
134+
135+ ``` clojure
136+ (if (< 1 2 )
137+ (print " true" ) ; ; then
138+ (print " false" )) ; ; else
139+ ```
140+
141+ Dabei kann jeweils nur genau eine S-Expression genutzt werden. Wenn man mehrere
142+ Dinge berechnen möchte, nutzt man ` do ` :
143+
144+ ``` clojure
145+ (do
146+ (print " wuppie" )
147+ (print " fluppie" )
148+ (print " foo" )
149+ (print " bar" ))
150+ ```
151+
152+ Beispiel:
153+
154+ ``` clojure
155+ (if (< 1 2 ) (do (print " true" ) (print " WUPPIE" )) (print " false" ))
156+ ```
157+
158+ oder anders formatiert:
159+
160+ ``` clojure
161+ (if (< 1 2 )
162+ (do (print " true" )
163+ (print " WUPPIE" ))
164+ (print " false" ))
165+ ```
166+
167+ ## Variablen: Bindings mit * def* anlegen
168+
169+ ``` clojure
170+ (def x 42 ) ; ; definiert eine neue Variable mit dem Namen "x" und dem Wert 42
21171
22- # TODO
172+ x ; ; liefert 42
173+ (+ x 7 ) ; ; liefert 49
174+ ```
23175
24- TODO
176+ ## Funktionen mit * defn* definieren
177+
178+ ``` clojure
179+ ; ; name params body
180+ (defn hello (n ) (str " hello " n)) ; ; Definition einer Funktion "hello" mit einem Parameter
181+
182+ (hello " world" ) ; ; Aufruf der Funktion "hello" mit dem Argument "world"
183+ ```
184+
185+ ## Lokale Scopes mit * let*
186+
187+ ``` clojure
188+ ; ; bindings use names here
189+ (let (name value) (code that uses name))
190+
191+ (def x 99 ) ; ; globale Variable x
192+ (def y 101 ) ; ; globale Variable y
193+ (def z 42 ) ; ; globale Variable z
194+ (let (x 1 ; ; lokales x mit Wert 1(verdeckt globales x)
195+ y 2 ) ; ; lokales y mit Wert 2
196+ (+ x y z)) ; ; 1+2+42
197+
198+ (defn hello
199+ (n )
200+ (let (l 42 ) ; ; l is valid in this scope
201+ (str " hello " n " : " l)
202+ ) ; ; end of local scope
203+ ) ; ; end of function definition
204+ ```
205+
206+ Mit ` let ` können lokale Variablen erzeugt werden, die dann in dem jeweiligen Scope
207+ genutzt werden können. Dies funktioniert wie in anderen Sprachen mit Scopes.
208+
209+ ## Rekursion
210+
211+ ``` clojure
212+ (defn fac (n )
213+ (if (< n 2 )
214+ 1
215+ (* n (fac (- n 1 )))))
216+ ```
217+
218+ Da es kein ` while ` oder ` for ` gibt, müssen Schleifen über rekursive Aufrufe
219+ abgebildet werden.
220+
221+ ## Datenstrukturen
222+
223+ In unserer Sprache gibt es Listen:
224+
225+ ``` clojure
226+ (1 2 3 ) ; ; Fehler!
227+ (def v (1 2 3 )) ; ; Fehler!
228+ ```
229+
230+ Das Problem daran ist, dass unsere S-Expressions zwar bereits listenartige
231+ Strukturen sind, der erste Eintrag aber als Operator oder Funktion interpretiert
232+ wird. Der Ausdruck oben würde beim Auswerten versuchen, die "Funktion" 1 auf den
233+ Argumenten 2 und 3 aufzurufen ...
234+
235+ Man braucht also eine Notation, die ein sofortiges Auswerten verhindert und nur die
236+ Liste an sich zurückliefert. Dies erreicht man durch die Funktion ` list ` :
237+
238+ ``` clojure
239+ (list 1 2 3 ) ; ; (1 2 3)
240+
241+ (def v (list 1 2 3 )) ; ; v = (1 2 3)
242+ v ; ; (1 2 3)
243+ ```
244+
245+ Mit der Funktion ` nth ` kann man auf das n-te Element einer Liste zugreifen:
246+
247+ ``` clojure
248+ (nth (list " abc" false 99 ) 2 ) ; ; 99
249+ ```
250+
251+ Zusätzlich gibt es die beiden Funktionen ` head ` und ` tail ` , die das erste Element
252+ einer Liste bzw. die restliche Liste ohne das erste Element zurückliefern:
253+
254+ ``` clojure
255+ (head (list 1 2 3 )) ; ; 1
256+ (tail (list 1 2 3 )) ; ; (2 3)
257+ ```
25258
26259# Aufgaben
27260
28- ## A4.1: Grammatik (2P)
261+ ## A4.1: Kontextfreie Grammatik (1P)
262+
263+ Betrachten sie die folgende Grammatik:
264+
265+ $$ G = (\lbrace S, A \rbrace, \lbrace 1, 2, 3 \rbrace, P, S) $$
266+
267+ mit
268+
269+ $$ \begin{eqnarray}
270+ P = \lbrace && \nonumber \\
271+ &S& \rightarrow 1AS \; | \; 3 \nonumber \\
272+ &A& \rightarrow 2AS \; | \; \epsilon \nonumber \\
273+ \rbrace \nonumber
274+ \end{eqnarray} $$
275+
276+ Berechnen die die * First-* und * Follow-Mengen* der Grammatik.
277+
278+ Zeigen Sie, dass die Grammatik LL(1) ist.
279+
280+ ## A4.2: Grammatik (2P)
281+
282+ 1 . Erstellen Sie zunächst einige Programme in der Zielsprache. Diese sollten von
283+ einfachsten Ausdrücken bis hin zu komplexeren Programmen reichen. Definieren Sie
284+ beispielsweise eine Funktion, die rekursiv die Länge einer Liste berechnet.
285+
286+ Definieren Sie neben gültigen Programmen auch solche, die in der syntaktischen
287+ Analyse zurückgewiesen werden sollten. Welche Fehlerkategorien könnte es hier
288+ geben?
289+
290+ 2 . Definieren Sie nun für die obige Sprache eine geeignete Grammatik.
291+
292+ ## A4.3: Lexer (2P)
293+
294+ 1 . Definieren Sie in Java Strukturen, die Sie für die Repräsentation der Token
295+ entsprechend Ihrer Grammatik benötigen.
296+
297+ 2 . Implementieren Sie dann analog zum Vorgehen in der Vorlesung einen Lexer, der
298+ den entsprechenden Teil Ihrer Grammatik abbildet. Diesen Lexer sollen Sie
299+ manuell in Java implementieren, Sie dürfen also nicht ANTLR oder andere
300+ Generatoren benutzen.
301+
302+ Implementieren Sie dabei das Verarbeiten des Lisp-artigen Codes aus einem
303+ übergebenen String.
304+
305+ Sie können wie in der Vorlesung demonstriert auf Anforderung das nächste Token
306+ berechnen. Sie können aber auch den Eingabecode vollständig verarbeiten und dann
307+ einen Tokenstrom geeignet zurückgeben.
308+
309+ Ihr Lexer soll eine rudimentäre Fehlerbehandlung aufweisen und bei Problemen die
310+ Abweichung vom erwarteten Zeichen zum tatsächlich eingelesenen Zeichen ausgeben.
311+ Darüber hinaus braucht der Lexer keinen Recovery-Modus haben o.ä., d.h. nach der
312+ Fehlermeldung darf Ihr Lexer "aussteigen".
313+
314+ ## A4.4: Parser mit * recursive descent* (3P)
315+
316+ Implementieren Sie analog zum Vorgehen in der Vorlesung einen Parser mit * recursive
317+ descent* , der den entsprechenden Teil Ihrer Grammatik abbildet. Diesen Parser sollen
318+ Sie manuell in Java implementieren, Sie dürfen also nicht ANTLR oder andere
319+ Generatoren benutzen.
320+
321+ Implementieren Sie dabei das Verarbeiten des Lisp-artigen Codes aus einem
322+ übergebenen String.
29323
30- Erstellen Sie eine Grammatik für die Zielsprache.
324+ Ihr Parser soll eine rudimentäre Fehlerbehandlung aufweisen und bei Problemen die
325+ Abweichung vom erwarteten Token zum tatsächlich eingelesenen Token ausgeben. Darüber
326+ hinaus braucht der Parser keinen Recovery-Modus haben o.ä., d.h. nach der
327+ Fehlermeldung darf Ihr Parser "aussteigen".
31328
32- ## A4.2: Lexer und Token (3P )
329+ ## A4.5: AST (1P )
33330
34- TODO
331+ Definieren Sie einen AST für die Zielsprache. Welche Informationen aus dem
332+ Eingabeprogramm müssen repräsentiert werden?
35333
36- ## A4.3: Parser (3P)
334+ Programmieren Sie die entsprechenden Datenstrukturen in Java.
37335
38- TODO
336+ Programmieren Sie außerdem eine Traversierung des Parse-Trees, die den AST erzeugt.
337+ Testen Sie dies mit Ihren in der ersten Aufgabe entwickelten Beispielprogrammen.
39338
40- ## A4.4: AST (2P )
339+ ## A4.6: Recherche und Diskussion (1P )
41340
42- TODO
43- -->
341+ Recherchieren Sie, welche Open-Source-Projekte auf handgeschriebene * recursive
342+ descent* Parser setzen (oder umgestiegen sind) und welche Gründe es dafür in diesen
343+ Projekten gibt. Was spricht aus Ihrer persönlichen Sicht für und gegen ANTLR?
0 commit comments