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