Skip to content

Commit f15e08b

Browse files
authored
homework: finalize B04 (#376)
ergänze das blatt mit der ehemaligen aufgabe a2.5 und übernehme den first/follow-task. (das berechnen der parser-tabellen findet als kleingruppen-übung in der vorlesung statt.) closes #335 closes #372
1 parent 26afe8e commit f15e08b

File tree

1 file changed

+319
-19
lines changed

1 file changed

+319
-19
lines changed

homework/sheet04.md

Lines changed: 319 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,339 @@ points: 10 Punkte
55
title: "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\nfluppie\nfoo\nbar")
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

Comments
 (0)