Programmiersprachenentwurf

Ähnliche Dokumente
Programmierkurs Java

Modellierung und Programmierung 1

Fachseminar. Semantische Analyse

9.4 Grundlagen des Compilerbaus

Exercise 6. Compound Types and Control Flow. Informatik I für D-MAVT. M. Gross, ETH Zürich, 2017

Ein Fragment von Pascal

Angewandte Mathematik und Programmierung

Informatik - Übungsstunde

Informatik für Mathematiker und Physiker Woche 7. David Sommer

if ( Logischer Operator ) { } else { Anweisungen false

Grundlagen der Programmierung in C++ Kontrollstrukturen

Software Entwicklung 1. Fallstudie: Arithmetische Ausdrücke. Rekursive Klassen. Überblick. Annette Bieniusa / Arnd Poetzsch-Heffter

Informatik I Übung, Woche 40

Struktur des MicroJava-Compilers

Übungs- und Praktikumsaufgaben zur Systemprogrammierung Dipl.-Ing. H. Büchter (Lehrbeauftragter) FH-Dortmund WS 2001/2002 / SS 2002

Struktur des MicroJava-Compilers

Pascal Schärli

Konzepte von Programmiersprachen

Begriffe (Wiederholung)

Elementare Konzepte von

Ursprünge. Die Syntax von Java. Das Wichtigste in Kürze. Konsequenzen. Weiteres Vorgehen. Rund um Java

Lexikalische Programmanalyse der Scanner

Herzlich willkommen!

Verwenden Sie für jede Aufgabe die C++ IDE von Eclipse (pro Aufgabe ein Projekt).

Syntax der Sprache PASCAL

Alphabet, formale Sprache

3.1 Reservierte Wörter

Grundlagen der Informatik 4. Kontrollstrukturen I

EXCEL VBA Cheat Sheet

6 Kontextfreie Grammatiken

Allgemeine Hinweise:

Struktur des MicroJava- Compilers

Programmieren I. Kapitel 5. Kontrollfluss

Informatik für Mathematiker und Physiker Woche 2. David Sommer

Informatik I Übung, Woche 40

Programmieren in Java

Vorlesung Programmieren

Einführung - Parser. Was ist ein Parser?

Boolean Wertemenge: Wahrheitswerte {FALSE,TRUE}, auch {0,1} Deklaration:

15. Rekursion. Rekursive Funktionen, Korrektheit, Terminierung, Aufrufstapel, Bau eines Taschenrechners, BNF, Parsen

Algorithmen zur Datenanalyse in C++

Einstieg in die Informatik mit Java

Technische Universität München WS 2004/2005 Fakultät für Informatik 11. Dezember 2004 Prof. Dr. Seidl

3. Wahrheitswerte. Boolesche Funktionen; der Typ bool; logische und relationale Operatoren; Kurzschlussauswertung; Assertions und Konstanten

Programmierkurs I. Gliederung: Deklarationsteil als BNF 2. Blöcke in Ada95 (Lebenszeit, Sichtbarkeit von Variablen)

C++11. neu in C++11: range-based for. Objektorientierte Programmierung mit C++ Ersetzung durch: 1. Elementares C++ int array[] = { 1, 2, 3, 4, 5 };

Informatik II Übung 3. Pascal Schärli

Informatik 1. Prüfung im Wintersemester 1997/98

1 Bedingte Anweisungen. 2 Vergleiche und logische Operatoren. 3 Fallunterscheidungen. 4 Zeichen und Zeichenketten. 5 Schleifen.

Einstieg in die Informatik mit Java

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf Seite 1 von 29

Konstruieren der SLR Parsing Tabelle

Grundlagen der Programmierung in C++ Kontrollstrukturen

Steuerungsstrukturen. Ablaufsteuerung der PL/SQL Ausführung. IF Anweisungen. Einfaches IF

Teil IX. Eine kleine Programmiersprache

Fachseminar Compilerbau

Java Tools JDK. IDEs. Downloads. Eclipse. IntelliJ. NetBeans. Java SE 8 Java SE 8 Documentation

17. Rekursion 2. Bau eines Taschenrechners, Ströme, Formale Grammatiken, Extended Backus Naur Form (EBNF), Parsen von Ausdrücken

Programmieren in Java

Übung zur Vorlesung Wissenschaftliches Rechnen Sommersemester 2012 Auffrischung zur Programmierung in C++, 1. Teil

RO-Tutorien 3 / 6 / 12

1 Formale Sprachen, reguläre und kontextfreie Grammatiken

Vorlesung Programmieren

Grundlagen der Programmiersprache C für Studierende der Naturwissenschaften

Transkript:

Programmiersprachenentwurf 4. Übung Lösungen Syntaktische Analyse Material: Für diese Aufgabenserie benötigen Sie als Ausgangsmaterial die folgenden Übersetzer von der Kurs-Seite Modellcompiler expressionparser0 Analyse einfacher arithmetischer Ausdrücke PL0-Parser0 PL0-Parser (Basisversion) In den Übungsaufgaben entwickeln Sie daraus Parser, die Sprachen mit erweiterter oder modifizierter Syntax analysieren. Implementierung der Lösung: Den Programmcode mit der Implementierung der Syntaxerweiterungen können Sie hier downloaden. expressionparser1 Analyse einfacher arithmetischer Ausdrücke PL0-Parser1 PL0-Parser (Basisversion) Aufgabe 1 Analysieren Sie mit dem PL/0-Parser (Programm PL0-Parser0) das folgende PL/0-Programm. Verfolgen Sie dabei die Inhalte der Symboltabelle und die Aufruffolge der syntaktischen Prozeduren! const a=10, b=15 ; var min, max, mean ; begin min := a ; max := b ; mean := (a+b)/2 ; if a > b then begin min := b ; max := a ; end end. Die Aufruffolge wird vom Parser protokolliert, wenn man im Hauptprogramm den Aufruf P.setParserProtocol (on); aktiviert. Sie ist hier mit einem Editor nachbearbeitet: constdeclaration constdeclaration vardeclaration vardeclaration vardeclaration statement : statement : expression : term : factor statement : expression : term : factor statement : expression : term : factor : expression : term : factor : term : factor : factor statement : condition : expression : term : factor : expression : term : factor statement : statement : expression : term : factor statement : expression : term : factor statement

Die Symboltabelle erhält folgende Einträge: ------------------------------------------------------- Symbol Table for block Main index name kind level addr size ------------------------------------------------------- 1 a constobj 10 2 b constobj 15 3 min varobj 0 0 0 4 max varobj 0 0 0 5 mean varobj 0 0 0 Aufgabe 2 Erweitern Sie den Recursive Descent-Parser für arithmetische Ausdrücke expressionparser0 für arithmetische Ausdrücke zu einem Parser, der beliebige logische Ausdrücke mit den Operatoren and, or, xor, not analysiert. Beispiele für solche Ausdrücke: a; a and b; (a and b); a and (b or c); a and b or c and d; a and (b or c) and d. Die Syntax der logischen Ausdrücke ist wie folgt festgelegt: lexpr lterm or lterm xor lterm lterm lfact and lfact lfact condition ident ( lexpr ) not lfact Zeigen Sie, daß die Grammatik vom Typ LL(1) ist und implementieren Sie einen Recursive Descent-Parser dafür. Nur die dritte Produktionsregel enthält mehrere rechte Seiten, die mit den (verschiedenen Terminalzeichen condition, ident, ( sowie not beginnen und daher disjunkte FIRST-Mengen besitzen. Da die Grammatik keine ε -Produktionen enthält, ist die LL(1)-Bedingung E2 gar nicht relevant. Somit ist die Grammatik vom Typ LL(1). Die Syntaxgraphen der Grammatik sind: lexpr lterm or lterm lterm lfact xor lfact and 2

lfact condition not lfact ( lexpr ) Der Code, mit dem der Parser logische Ausdrücke dieser Form analysiert ist: void exprparser::lexpression (void) lterm(); while((sym==orsym) (sym == xorsym)) lterm(); void exprparser::lterm(void) lfactor(); while (sym==andsym) lfactor(); void exprparser::lfactor(void) switch (sym) case ident: case notsym: lfactor(); case lparen: lexpression (); if (sym == rparen) cout << "Symbol can t follow an lexpression!\n"; default: cout << "lfactor expected!" << endl; 3

Aufgabe 3 Erweitern Sie den Recursive Descent-Parser für Ausdrücke expressionparser0 für arithmetische Ausdrücke zu einem Parser, der Vergleiche (conditions) analysiert von der folgenden Form: a = b; x-17 < a+(b-c); alpha + beta # pi*2/(radius1-radius2); (x17 - x18) / 15 <= a*b+c. Zeigen Sie, daß die Grammatik vom Typ LL(1) ist und implementieren Sie einen Recursive Descent-Parser dafür. Die Syntaxdiagramme zu dieser Sparcherweiterung sind: condition odd expr # = expr < expr > expr + term + term term fact fact ident fact / number ( expr ) 4

Zur Überprüfung der LL(1)-Eigenschaften bestimmen wir zunächst die FIRST- und FOLLOW- Mengen der Nichtterminalen Zeichen: X N FIRST( X ) FOLLOW( X ) condition ( ident number + odd expr ( ident number + = > >= < <= ) term ( ident number = > >= < <= ) + fact ( ident number = > >= < <= ) + / Zum Nachweis der LL(1) - Eigenschaft von Γ sind die folgenden Bedingungen notwendig: 1. Das Diagramm für condition besitzt zwei Alternativen, dern FIRST-Mengen FIRST(odd) FIRST(expr) disjunkt sind. 2. Das Diagramm für expr besitzt drei Alternativen, daher müssen die folgenden FIRST-Mengen paarweise disjunkt sein: FIRST ( + TERM + TERM TERM ) FIRST ( TERM + TERM TERM ) FIRST ( TERM + TERM TERM ) 3. Das Diagramm für fact besitzt drei Alternativen, daher müssen die folgenden FIRST-Mengen paarweise disjunkt sein: FIRST ( ident ) FIRST ( number ) FIRST ( ( expr ) ) Wie man sieht sind diese Bedingungen alle erfüllt. Damit ist sichergestellt, daß mit Γ arithmetische Ausdrücke sackgassenfrei analysiert werden können. Der Code von expressionparser0 kann daher für die Analyse von conditions wie folgt erweitert werden: void exprparser::condition (void) if (sym == oddsym) expression(); if ( (sym == ident) (sym == number) (sym == lparen) (sym == plus ) (sym == minus ) ) expression(); if ( (sym == eql) (sym == neq) (sym == lss) (sym == leq) (sym == gtr) (sym == geq) ) expression(); 5

Aufgabe 4 Entwickeln Sie den Recursive Descent-Parser für arithmetische Ausdrücke expressionparser0 weiter zu zu einem Parser, der Listen analysiert von der folgenden Form: (); (a, b, c); (a, ((b,c), d)). Eine Grammatik zu dieser Sprache ist: N T P = = L ident,(, ) L ident ( L,L ) = Begründen Sie dazu zunächst, dass die Grammatik vom Typ LL(1) ist. Modifizieren Sie dann expressionparser0. Da die Grammatik nur eine Produktion ohne Alternativen enthält, muss keine der LL(1)- Bedingungen überprüft werden. Der Code von expressionparser0 kann daher für die Analyse dieser Listen wie folgt erweitert werden. void exprparser::l(void) switch (sym) case ident: case lparen: if (sym == rparen) L(); while (sym==comma) L(); if (sym == rparen) cout << "')' expected!!" << endl; 6

Aufgabe 5 Erweitern Sie die Basisversion des Parser PL0-Parser0 um die folgenden Syntaxerweiterungen: 5.1 Verhindern von Mehrfach-Deklarationen Der PL/0-Parser erkennt in der vorgebenen Form nicht, wenn innerhalb eines Blocks Namen mehrfach deklariert werden. Ändern Sie ihn so ab, daß Mehrfachdeklarationen innerhalb eines Blocks erkannt und als Fehler protokolliert werden. Wie werden Mehrfachdeklarationen auf verschiedenen Stufen einer Blockhierarchie dabei behandelt? Mehrfachdeklarationen auf verschiedenen Stufen einer Blockhierarchie werden wie in C und ähnlichen Sprachen behandelt nach den dort üblichen Gültigkeitsregeln. void pl0parser::block() int ppos; ppos = tx; int i; // symtable position above this block // save symtable position above this block // symtable index of new objects... while (sym == procsym) if (sym == ident) i = position(id); if (i<=ppos) // parse procedure declarations // get procedure name // if it is an identifier // if identifier is not yet // defined in this block enter(procobj); // put it into the symbol table // error(35); // multiple defined error(4); if (sym == semicolon) error(5); block(); if (sym == semicolon) error(5);... // check if ";" is present // parse the prodedure body // check if ";" is present void pl0parser::constdeclaration (int ppos) int i; if (sym == ident) // if the next symbol is a // constant identifier if (sym == eql) // check, if "=" is present if (sym == number) // check if const value is present i = position(id); // if identifier is not yet if (i<=ppos) // defined in this block enter(constobj); // put it into the symbol table // error(35); // error(2); error(3); error(4); 7

void pl0parser::vardeclaration (int ppos) int i; parserprotocol("vardeclaration"); // output parser protocol if (sym == ident) // if next symbol is a variable i = position(id); // if identifier is not yet if (i<=ppos) // defined in this block enter(varobj); // put it into the symbol table error(35); error(4); 5.2 Exponentiation als zusätzliche arithmetische Operation Erweitern Sie den Parser für PL/0 so, daß er die Exponentiation als arithmetische Operation mit der höchsten Priorität zuläßt: Welche Erweiterungen sind an den Syntaxgraphen vorzunehmen? Ist die erweiterte Grammatik immer noch vom Typ LL(1)? Erweitern Sie den Programmcode des Parser! Die Teilgrammatik von PL0 für arithmetische Ausdrücke ist zu erweitern wie folgt: expr + term + term term fact fact / fact sfact sfact ^ sfact ident number ( expr ) 8

Zum Nachweis. dass die erweiterte Grammatik immer noch vom Typ LL(1) ist, bestimmen wir zunächst die First- und Follow-Mengen der Nonterminals: X FIRST(X) FOLLOW (X) expr ( ident number + ). ; = # < <= > >= then do end term ( ident number ). ; = # < <= > >= then do end + fact ( ident number ). ; = # < <= > >= then do end + / sfact ( ident number ). ; = # < <= > >= then do end + / ^ Bezuüglich der LL(1)-Eigenschaft E1 fallen gegenüber früher für die Produktionen expr..., term... die selben Prüfungen an, da die zu prüfenden Mengen-Durchschnitte die selben sind. Für die neue Produktion fact... gibt es keine alternativen rechten Seiten. Für die neue Produktion sfact... sind die zu überprüfenden Durchschnitte alle leer: FIRST(ident) FIRST(number) FIRST( (expr) ) FIRST(number) FIRST( (expr) ) FIRST(ident) Bezüglich der LL(1)-Eigenschaft E2 ergibt sich auch keine neue Situation, da die Regel statement ε weiterhin die einzige ε-produktion ist und die Mengen FIRST(statement) und FOLLOW(statement) gleich bleiben. Da in den übrigen Teilen der PL0-Grammatik nur expr vorkommt, und FIRST( (expr) ) sowie FOLLOW( (expr) ) sich nicht geändert haben, sind auch dort weiterhin die LL(1)-Bedingungen erfüllt. Daher ist die PL0-Grammatik weiterhin vom Typ LL(1)! Erweiterter Programm-Code des PL0-Parser: void pl0parser::expression(void) if ((sym==plus) (sym == minus)) // if there is a sign operator +/- term(); // accept sign and parse the first term // if there is no sign term(); // parse the first term while((sym==plus) (sym == minus)) // parse following +term -term structures term(); void pl0parser::term(void) factor(); // parse the first factor while ((sym==times) (sym == slash)) // parse *factor /factor structures factor(); 9

void pl0parser::factor(void) sfactor(); while ( sym==expo ) sfactor(); // parse the first factor // parse all following ^factor structures void pl0parser::sfactor(void) int i; switch (sym) case ident: i = position(id); // search identifier in the symbol table if (i == 0) // identifier not found in the table error(11); if (symtable[i].kind == procobj) error(21); // neither variable nor constant // proceed to the next symbol case number: // factor is a number case lparen: // factor is "( expression )" expression(); if (sym == rparen) error (22); default: error(23); // no factor found 5.3 repeat-statement In P/L0 ist eine repeat-anweisung aufzunehmen, die in Syntax und Semantik der repeat- Anweisung von PASCAL entspricht. Die folgenden Produktionen beschreiben ihre Syntax: repeat-anw. repeat-rumpf until condition repeat-rumpf repeat-symbol Anw.folge Anw.folge repeat-symbol repeat statement Anw.folge ; statement Warum ist die PL/0-Grammatik mit diesen Erweiterungen zunächst nicht mehr vom Typ LL(1)? Die Produktion Anw.folge statement Anw.folge ; statement ist linksrekursiv, und genügt daher nicht der LL(1)-Bedingung E1 Modifizieren Sie das Produktionensystem so, daß sich wieder eine LL(1)-Grammatik ergibt! Die Umwandlung der linksrekursiven Produktion in eine iteration hilft: repeat-anw. repeat-rumpf until condition repeat-rumpf repeat-symbol Anw.folge Anw.folge statement ; Anw.folge repeat-symbol repeat 10

Geben Sie die Syntaxgraphen dazu an! repeat-anweisung : repeat-rumpf until condition repeat-rumpf : repeat statement statement ; Erweitern Sie den PL/0 - Parser so, daß er auch repeat-anweisungen korrekt analysiert! Erweiterung von PL0Parser.cpp: void pl0parser::statement() int i; switch (sym) // switch to the actual statement type... case repeatsym: // now we have while-statement // get next symbol after "while" statement(); while (sym == semicolon) statement(); / if (sym == untilsym) // test if "until" is present error(36); condition(); // parse the condition...... Erweiterung von Scanner.h: const NORW = 16; // (2 additional reserved words!) enum symbol nul, ident, number, plus, minus, times, slash, oddsym, eql, neq, lss, leq, gtr, geq, lparen, rparen, comma, semicolon, period, becomes, beginsym, endsym, ifsym, thensym, whilesym, dosym, callsym, constsym, varsym, procsym, expo, repeatsym, untilsym, Sym, colon, casesym, ofsym ; Erweiterung des Konstruktors in Scanner.cpp Scanner::Scanner ()... // table of reserved words strcpy (word[ 0], "begin "); strcpy (word[ 1], "call "); strcpy (word[ 2], "case "); strcpy (word[ 3], "const "); strcpy (word[ 4], "do "); strcpy (word[ 5], " "); strcpy (word[ 6], "end "); strcpy (word[ 7], "if "); strcpy (word[ 8], "odd "); strcpy (word[ 9], "of "); strcpy (word[10], "procedure "); strcpy (word[11], "repeat "); strcpy (word[12], "then "); strcpy (word[13], "until "); strcpy (word[14], "var "); strcpy (word[15], "while "); 11

// internal representation of reserved words wsym[ 0] = beginsym ; wsym[ 1] = callsym ; wsym[ 2] = casesym ; wsym[ 3] = constsym ; wsym[ 4] = dosym ; wsym[ 5] = Sym ; wsym[ 6] = endsym ; wsym[ 7] = ifsym ; wsym[ 8] = oddsym ; wsym[ 9] = ofsym ; wsym[10] = procsym ; wsym[11] = repeatsym ; wsym[12] = thensym ; wsym[13] = untilsym ; wsym[14] = varsym ; wsym[15] = whilesym ;... 5.4 -Zweig in if-statements Die Programmiersprache PL/0 ist so zu erweitern, daß bedingte Anweisungen auch einen - Zweig besitzen können. Der Aufbau bedingter Anweisungen ist dann: entweder : if condition then statement oder: if condition then statement statement Ergänzen Sie die Syntaxdiagramme von PL/0 entsprechend! if-statement : condition then statement statement Erweitern Sie den PL/0-Parser, so daß er bedingte Anweisungen in dieser Form akzeptiert! Erweiterung von PL0Parser.cpp: void pl0parser::statement() int i; switch (sym)... // switch to the actual statement type case ifsym: // now we have an if-statement // proceed to the first symbol of the condition condition(); // parse the condition if (sym==thensym) /// check if "then" is present error(16); statement(); // parse then-alternative if (sym==sym) // alternative present? // statement(); //...... Erweiterung von Scanner.h: const NORW = 16; // (1 additional reserved words!) enum symbol nul, ident, number, plus, minus, times, slash, oddsym, eql, neq, lss, leq, gtr, geq, lparen, rparen, comma, semicolon, period, becomes, beginsym, endsym, ifsym, thensym, whilesym, dosym, callsym, constsym, varsym, procsym, expo, repeatsym, untilsym, Sym, colon, casesym, ofsym ; 12

Erweiterung des Konstruktors in Scanner.cpp Scanner::Scanner ()... // table of reserved words strcpy (word[ 0], "begin "); strcpy (word[ 1], "call "); strcpy (word[ 2], "case "); strcpy (word[ 3], "const "); strcpy (word[ 4], "do "); strcpy (word[ 5], " "); strcpy (word[ 6], "end "); strcpy (word[ 7], "if "); strcpy (word[ 8], "odd "); strcpy (word[ 9], "of "); strcpy (word[10], "procedure "); strcpy (word[11], "repeat "); strcpy (word[12], "then "); strcpy (word[13], "until "); strcpy (word[14], "var "); strcpy (word[15], "while "); // internal representation of reserved words wsym[ 0] = beginsym ; wsym[ 1] = callsym ; wsym[ 2] = casesym ; wsym[ 3] = constsym ; wsym[ 4] = dosym ; wsym[ 5] = Sym ; wsym[ 6] = endsym ; wsym[ 7] = ifsym ; wsym[ 8] = oddsym ; wsym[ 9] = ofsym ; wsym[10] = procsym ; wsym[11] = repeatsym ; wsym[12] = thensym ; wsym[13] = untilsym ; wsym[14] = varsym ; wsym[15] = whilesym ;... 5.5 case-statement PL/0 ist um eine case-anweisung zu erweitern. Ihre Syntax wird durch das folgende Diagramm beschrieben. Ergänzen Sie den PL/0-Parser so, daß er auch case-anweisungen analysiert. case expr of end ; statement : number Die folgenden Erweiterungen des PL0-Parsers sind notwendig: Erweiterung von PL0Parser.cpp: void pl0parser::statement()... switch (sym)... // switch to the actual statement type case casesym: expression(); if (sym == ofsym) error(37); while (sym == number) if (sym == colon) // now we have a case-statement // get next symbol after "case" // test if "of" is present // loop for case alternatives // read the number // test if ":" is present 13

error(38); statement(); // parse body of this alternative if (sym == semicolon) // test if ";" is present error(5); / if (sym == endsym) error(17); condition();...... Erweiterung von Scanner.h: // test if "end" is present const NORW = 16; // (2 additional reserved words!) enum symbol nul, ident, number, plus, minus, times, slash, oddsym, eql, neq, lss, leq, gtr, geq, lparen, rparen, comma, semicolon, period, becomes, beginsym, endsym, ifsym, thensym, whilesym, dosym, callsym, constsym, varsym, procsym, expo, repeatsym, untilsym, Sym, colon, casesym, ofsym ; Erweiterung des Konstruktors in Scanner.cpp Scanner::Scanner ()... // table of reserved words strcpy (word[ 0], "begin "); strcpy (word[ 1], "call "); strcpy (word[ 2], "case "); strcpy (word[ 3], "const "); strcpy (word[ 4], "do "); strcpy (word[ 5], " "); strcpy (word[ 6], "end "); strcpy (word[ 7], "if "); strcpy (word[ 8], "odd "); strcpy (word[ 9], "of "); strcpy (word[10], "procedure "); strcpy (word[11], "repeat "); strcpy (word[12], "then "); strcpy (word[13], "until "); strcpy (word[14], "var "); strcpy (word[15], "while "); // internal representation of reserved words wsym[ 0] = beginsym ; wsym[ 1] = callsym ; wsym[ 2] = casesym ; wsym[ 3] = constsym ; wsym[ 4] = dosym ; wsym[ 5] = Sym ; wsym[ 6] = endsym ; wsym[ 7] = ifsym ; wsym[ 8] = oddsym ; wsym[ 9] = ofsym ; wsym[10] = procsym ; wsym[11] = repeatsym ; wsym[12] = thensym ; wsym[13] = untilsym ; wsym[14] = varsym ; wsym[15] = whilesym ;... 14