Skip to content

Commit 7a75c5c

Browse files
committed
lecture: add new slide 'parsetree to ast' (ANTLR)
1 parent 2609510 commit 7a75c5c

File tree

2 files changed

+150
-3
lines changed

2 files changed

+150
-3
lines changed

lecture/02-parsing/antlr-parsing.md

Lines changed: 150 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,8 @@ public static class PatternMatching {
656656
case calcParser.ADDContext a -> eval(a.e1) + eval(a.e2);
657657
case calcParser.ZAHLContext n -> Integer.parseInt(n.DIGIT().getText());
658658
default ->
659-
throw new IllegalStateException("Unhandled expr: " + e.getClass().getSimpleName());
659+
throw new IllegalStateException("Unhandled expr: "
660+
+ e.getClass().getSimpleName());
660661
};
661662
}
662663
}
@@ -688,6 +689,152 @@ public class TestMyPM {
688689
href="https://github.com/Compiler-CampusMinden/CPL-Vorlesung-Master/blob/master/lecture/02-parsing/src/TestMyPM.java"}
689690
:::
690691

692+
# Vom Parse-Tree zum AST
693+
694+
::: notes
695+
Der Parse-Tree spiegelt direkt die Strukturen der Grammatik wider, die beim Parsen
696+
gematcht haben. Normalerweise braucht man diesen Detailgrad später nicht mehr und
697+
baut den Parse-Tree zu einem abstrakteren AST (*Abstract Syntax Tree*) um.
698+
:::
699+
700+
![](images/screenshot_parsetree.png){width="30%"}
701+
702+
::: notes
703+
Dieser Parse-Tree entstand mit einer einfachen, nachfolgend aufgeführten
704+
Expression-Grammatik und der Eingabe "2+3\*4;".
705+
706+
Man erkennt gut die Grammatik-Regeln:
707+
708+
``` antlr
709+
grammar MyLang;
710+
711+
start
712+
: stmt* EOF
713+
;
714+
715+
stmt
716+
: id=ID '=' value=expr ';' # Assign
717+
| expr ';' # ExprStmt
718+
;
719+
720+
expr
721+
: lhs=expr '*' rhs=expr # Mul
722+
| lhs=expr '+' rhs=expr # Add
723+
| ID # Name
724+
| NUM # Number
725+
;
726+
727+
728+
ID : [a-zA-Z_] [a-zA-Z_0-9]* ;
729+
NUM : [0-9]+ ;
730+
731+
WS : [ \t\r\n]+ -> skip ;
732+
```
733+
734+
Für das weitere Arbeiten ist aber nicht mehr relevant, ob da ein `EOF`-Token war
735+
oder nicht - der Parser würde eine Eingabe ohne dieses Token am Ende ja ablehnen.
736+
Auch ist die Stufe `expr: NUM;` nicht notwendig, und statt des Tokens möchte man
737+
eigentlich den Integerwert haben.
738+
739+
Es bietet sich also an, einige wenige Typen zu definieren, mit denen man diesen Baum
740+
darstellen kann.
741+
:::
742+
743+
``` java
744+
sealed interface Stmt permits Stmt.Assign, Stmt.ExprStmt {
745+
record Assign(String id, Expr value) implements Stmt {}
746+
record ExprStmt(Expr expr) implements Stmt {}
747+
}
748+
749+
sealed interface Expr permits Expr.Mul, Expr.Add, Expr.Name, Expr.Number {
750+
record Mul(Expr lhs, Expr rhs) implements Expr {}
751+
record Add(Expr lhs, Expr rhs) implements Expr {}
752+
record Name(String id) implements Expr {}
753+
record Number(int value) implements Expr {}
754+
}
755+
```
756+
757+
::: notes
758+
Statements sind syntaktische Strukturen, die ausgeführt werden und i.d.R. keinen
759+
Wert ergeben. In der obigen Grammatik gibt es zwei verschiedene Statements, die man
760+
auch später noch unterscheiden möchte: Zuweisungen und Ausdrücke (mit einem
761+
Semikolon abgeschlossen als eigenständiges Statement). Dies wird in der obigen
762+
Modellierung entsprechend berücksichtigt: Es gibt ein Interface für Statements und
763+
genau zwei Klassen, die dieses Interface implementieren. Bei einer Zuweisung werden
764+
später der Name der Variablen (linke Seite der Anweisung) und die Expression auf der
765+
rechten Seite der Anweisung benötigt, die restlichen Informationen aus dem
766+
Parse-Tree sind nach dem erfolgreichen Parsen nicht mehr interessant.
767+
768+
Expressions sind syntaktische Strukturen, die ausgewertet werden können und dabei
769+
einen Wert ergeben. Auch hier wird wieder ein gemeinsames Interface definiert und je
770+
Expression-Variante eine konkrete Datenklasse. Auch hier werden wieder nur die
771+
wirklich notwendigen Daten übernommen. Man könnte sogar noch überlegen, ob man die
772+
beiden `Mul` und `Add` zu einer gemeinsamen Klasse zusammenfassen möchte, dann
773+
müsste man aber noch die Operation als weiteres Attribut anlegen, und später müsste
774+
eine zusätzliche Fallunterscheidung anhand der Operation erfolgen, während man mit
775+
der obigen Modellierung per `switch/case` auf den Klassen direkt die gesuchte
776+
Information erhält.
777+
:::
778+
779+
::: slides
780+
# Vom Parse-Tree zum AST (cnt.)
781+
:::
782+
783+
``` {.java size="footnotesize"}
784+
static Stmt toAst(MyLangParser.StmtContext s) {
785+
return switch (s) {
786+
case MyLangParser.AssignContext a -> new Stmt.Assign(a.id.getText(), toAst(a.value));
787+
case MyLangParser.ExprStmtContext e -> new Stmt.ExprStmt(toAst(e.expr()));
788+
default -> throw new IllegalStateException();
789+
};
790+
}
791+
792+
static Expr toAst(MyLangParser.ExprContext e) {
793+
return switch (e) {
794+
case MyLangParser.MulContext m -> new Expr.Mul(toAst(m.lhs), toAst(m.rhs));
795+
case MyLangParser.AddContext a -> new Expr.Add(toAst(a.lhs), toAst(a.rhs));
796+
case MyLangParser.NameContext n -> new Expr.Name(n.ID().getText());
797+
case MyLangParser.NumberContext n -> new Expr.Number(Integer.parseInt(n.NUM().getText()));
798+
default -> throw new IllegalStateException();
799+
};
800+
}
801+
```
802+
803+
::: notes
804+
Mit Hilfe der beiden oben gezeigten Methoden und dem folgenden Code kann der
805+
Parse-Tree traversiert werden. Dabei kommt das *Pattern Matching* auf Klassen zur
806+
Anwendung, welches in der Funktionalen Programmierung schon lange bekannt ist und
807+
nun endlich auch Einzug in die OOP-Welt hält.
808+
809+
In jedem einzelnen Knoten im Parse-Tree entscheidet man, ob und welchen neuen Knoten
810+
für den AST man erzeugen möchte und übernimmt die entsprechenden Informationen.
811+
812+
Hier noch der restliche "Starter-Code":
813+
814+
``` java
815+
public class AstBuilder {
816+
static void main(String... args) {
817+
CharStream input = CharStreams.fromString(IO.readln("expr?> "));
818+
MyLangLexer lexer = new MyLangLexer(input);
819+
CommonTokenStream tokens = new CommonTokenStream(lexer);
820+
MyLangParser parser = new MyLangParser(tokens);
821+
822+
MyLangParser.StartContext tree = parser.start();
823+
824+
IO.println(toAst(tree));
825+
}
826+
827+
static List<Stmt> toAst(MyLangParser.StartContext s) {
828+
return s.stmt().stream()
829+
.map(AstBuilder::toAst)
830+
.collect(Collectors.toCollection(ArrayList::new));
831+
}
832+
833+
... // die beiden statischen Methoden von oben
834+
}
835+
```
836+
:::
837+
691838
::: notes
692839
# Eingebettete Aktionen und Attribute
693840

@@ -746,8 +893,8 @@ geschrieben
746893
- ANTLR erlaubt direkte Links-Rekursion
747894
- ANTLR erzeugt Parse-Tree
748895
- Benannte Alternativen und Regel-Elemente
749-
- Traversierung des Parse-Tree: Listener oder Visitoren, Zugriff auf
750-
Kontextobjekte
896+
- Traversierung des Parse-Tree: Listener oder Visitoren oder *Pattern Matching*,
897+
Zugriff auf Kontextobjekte
751898

752899
::: readings
753900
- @Parr2014
67.3 KB
Loading

0 commit comments

Comments
 (0)