@@ -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 {
688689href="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
0 commit comments