TECHNISCHE UNIVERSITÄT MÜNCHEN FAKULTÄT FÜR INFORMATIK Studentische Repetitorien für Informatik Stefan Berktold ([email protected]) WS 2016/17 12.04.2017 Repetitorium zu Einführung in die Informatik 1 Übungsblock B: Syntax 1. KONTEXTFREIE GRAMMATIKEN & REGULÄRE AUSDRÜCKE Reguläre Ausdrücke (engl. regular expression, kurz: „regex“) werden durch reguläre Grammatiken – das sind eingeschränkte kontextfreie Grammatiken – erzeugt. Die Einschränkung ist im Besonderen, dass reguläre Ausdrücke eigentlich tatsächlich Ausdrücke sind und somit in einer Zeile, ohne Definition bzw. Verwendung jeglicher Nichtterminale, geschrieben werden. Beispiel: pdigit ::= 1 | ... | 9 digit ::= 0 | pdigit number ::= -? pdigit digit* | 0 ist eine kontextfreie Grammatik, welche in erweiterter Backus-Naur-Form (EBNF) angegeben ist und sich als regulärer Ausdruck schreiben lässt: -? (1 | ... | 9) (0 | 1 | ... | 9)* | 0 Gegenbeispiel: word ::= (a word b)* lässt sich nicht ohne Nichtterminale (hier: word) darstellen und ist daher zwar eine kontextfreie Grammatik, aber kein regulärer Ausdruck. Relevante Symbole: a Terminalzeichen (a) x | y Alternative (entweder x oder y) a | ... | z Alternativen (eines der Zeichen von a bis z) x* Iteration (keinmal, einmal oder beliebig oft) x+ Iteration (mindestens einmal) x y Konkatenation (Zusammenfügen) x? Option (keinmal oder einmal) ① Verwenden Sie für folgende Teilaufgaben abgesehen von letter ::= a|...|z keine Definition. Nennen Sie einen regulären Ausdruck für Textmuster, ... a) die weder a noch z enthalten: (b | ... | y)* b) die eine gerade Anzahl (möglicherweise auch 0) an Buchstaben enthalten: (letter letter)* c) die nicht mit a beginnen und entweder mit b oder mit a enden: (b | ... | z) letter* (b | a) | b d) die mit a enden, sofern sie auch mit a beginnen: a letter* a | a | (b | ... | z) letter* e) die genau zwei oder genau vier Zeichen lang sind: letter letter (letter letter)? f) die genau zwei a’s enthalten, wobei diese aufeinander folgen: (b | ... | z)* aa (b | ... | z)* g) die mit beliebig vielen a’s beginnen dürfen, sonst aber nicht zwei oder mehr aufeinanderfolgende a’s enthalten: a* ((b | ... | z) a?)* Übungsblock B (Lösung) – Stefan Berktold Seite 2 von 13 ② Vermeiden Sie im Folgenden Redundanz und verwenden Sie als Literale/Terminalsymbole keine (zusammengesetzten) Zahlen, sondern Ziffern. Leerzeichen müssen nicht explizit angegeben werden. Geben Sie eine Grammatik an für ... a) Datumsangaben, wobei ein Datum aus einer zweistelligen Tag- (01-31) und einer zweistelligen Monatsangabe (01-12) besteht, jeweils gefolgt von einem Punkt (.) (z. B. 30.03.). Im Anschluss kann noch eine zwei- oder vierstellige Jahresangabe folgen (z. B. 01.01.00 oder 01.12.2017). pdigit ::= 1 | ... | 9 digit ::= 0 | pdigit tag ::= 0 pdigit | (1|2) digit | 3 (0|1) monat ::= 0 pdigit | 1 (0|1|2) jahr ::= (digit digit)? digit digit datum ::= tag . monat . jahr? b) Uhrzeiten im 24-Stunden-Format, wobei eine Uhrzeit aus einer ein- oder zweistelligen Stundenzahl gefolgt von einem Trennzeichen (Punkt oder Doppelpunkt) und einer zweistelligen Minutenzahl besteht (z. B. 16:00, 04:59, 0:00 oder 5.43). Optional kann „Uhr“ angehängt werden (z. B. 16:00 Uhr). Alternativ können Uhrzeiten nur mit der Stundenzahl (ohne führende Null) gefolgt von „Uhr“ angegeben werden (z. B. 7 Uhr). digit ::= 0 | ... | 9 stundeOhneNull ::= digit | 1 digit | 2 (0|...|3) stunde ::= stundeOhneNull | 0 digit minute ::= 0 digit | (1|...|5) digit name ::= U h r uhrzeit ::= stunde (.|:) minute name? | stundeOhneNull name Übungsblock B (Lösung) – Stefan Berktold Seite 3 von 13 2. SYNTAXBÄUME Mit Syntaxbäumen wird Code syntaktisch in Einheiten gegliedert und so baumartig dargestellt. Die Grammatik ist vorgegeben und kann beispielsweise der nachfolgenden entsprechen, welche (lange) nicht alle Möglichkeiten abdeckt, aber für MiniJava genügt. Deklarationen können laut dieser Grammatik bspw. nur ganz am Anfang eines jeden Codes stehen. Außerdem werden keine Auswertungsreihenfolgen (d. h. Bindungsstärken) definiert, was mehrere Darstellungsmöglichkeiten zulässt. Vorgehen (grobe Erklärung): Das oberste Statement ist immer program – hier starten wir. Wir betrachten nun den gesamten Quelltext und gliedern ihn gedanklich in Deklarationen und Statements. Jeder Knoten (<Nichtterminal>) wird nun einzeln betrachtet und weiter unterteilt, bis alle Terminalsymbole zugeordnet wurden. Der Code wird von links nach rechts durchlaufen. Für nachfolgende Aufgaben gültige MiniJava-Grammatik (in der Klausur geg.): <program> ::= <decl>* <stmt>* <decl> ::= <type> <name> (, <name>)*; <stmt> ::= | | | | | | | ; { <stmt>* } <name> = <expr> ; <name> = read(); write( <expr> ); if ( <cond> ) <stmt> if ( <cond> ) <stmt> else <stmt> while (<cond>) <stmt> <expr> ::= | | | | <number> <name> ( <expr> ) <unop> <expr> <expr> <binop> <expr> <cond> ::= | | | | | true false ( <cond> ) <expr> <comp> <expr> <bunop> <cond> <cond> <bbinop> <cond> <comp> <unop> <binop> <bbinop> <bunop> ::= ::= ::= ::= ::= == | != | <= | < | >= | > -|+|*|/|% && | || ! <type> <name> <number> ::= ::= ::= int letter ( letter | digit )* digit digit* Übungsblock B (Lösung) – Stefan Berktold Seite 4 von 13 ② Zeichnen Sie den Syntaxbaum zu folgenden Codeauszügen. Die Terminalsymbole wurden bereits für Sie eingezeichnet – Sie finden die Vorlagen auf den folgenden Seiten. a) int a; a = 0; while (a < 10) a = read(); if (a > 100) { write(10); } b) int x, y; y = 1; while (y > 0) { x = read(); y = y - x; } write(y); c) int a, b; a = 1; b = -2; if ((a%b) <= -a || b == 2) { a = 0; } else { if (true) write(b+1); }; Übungsblock B (Lösung) – Stefan Berktold Seite 5 von 13 type Übungsblock B (Lösung) – Stefan Berktold name number expr comp expr number name cond stmt expr stmt name stmt name expr expr number comp cond stmt number expr stmt stmt int a ; a = 0 ; while ( a < 10 ) a = read ( ) ; if ( a > 100 ) { write ( 10 ) ; } name decl program zu ② a): Seite 6 von 13 type Übungsblock B (Lösung) – Stefan Berktold name name number expr comp expr number name cond expr stmt name stmt stmt name stmt expr name binop name expr expr stmt name expr stmt int x , y ; y = 1 ; while ( y > 0 ) { x = read ( ) ; y = y - x ; } write ( y ) ; name decl program zu ② b): Seite 7 von 13 Übungsblock B (Lösung) – Stefan Berktold name name expr number name stmt expr name name binop expr cond name unop expr expr name number expr comp cond expr bbinop cond comp expr expr number expr expr unop name stmt number expr stmt stmt name stmt cond stmt stmt name expr number expr binop expr stmt stmt int a , b ; a = 1 ; b = - 2 ; if ( ( a % b ) <= - a || b == 2 ) { a = 0 ; } else { if ( true ) write ( b + 1 ) ; } ; type decl program zu ② c): Seite 8 von 13 3. KONTROLLFLUSSDIAGRAMME Mit Kontrollflussdiagrammen wird veranschaulicht, wie bzw. in welcher Reihenfolge einzelne Programmstücke ausgeführt werden. Es kann bspw. nachvollzogen werden, ob und unter welchen Umständen ein Programm terminiert. Es werden im Grunde alle Statements (außer Deklarationen!) in einzelnen Knoten gezeichnet, wobei die Einzeichnung von Statements wie break oder continue uneinheitlich gehandhabt wird. So kann das break-Statement als separater Knoten gezeichnet werden (Empfehlung) oder lediglich als Beschriftung eine Kante dienen. Ein Kontrollflussgraph (Synonym zu Kontrollflussdiagramm) zu einem Codeauszug oder einer bestimmten Methode verfügt über genau einen Startknoten und beliebig viele Endknoten (keiner, bestenfalls einer oder beliebig viele). Soll eine ganze Methode/Funktion dargestellt werden, so wird entsprechend auch der Startknoten für Funktionen (siehe unten) und als Endknoten ein (ggf. leeres) return-Statement benutzt. Bei mehreren Funktionen existiert dann pro Funktion ein Startknoten. Knotenarten: start stop Startknoten Endknoten a=read() yes a>5 Kantenzusammenlauf no a += 1 Ein-/Ausgabe bedingte Verzweigung „normale“ Anweisung f(a,b) return a b=g(7) Startknoten der Funktion f Endknoten einer Funktion Funktionsaufruf (g(7)) Weiteres: Das Semikolon (;) kann ebenfalls eingezeichnet werden. Beim Endknoten genügt eine einfache Umrandung. Start-/Endknoten auch in Rechteck mit abgerundeten Ecken möglich. Mehrere „normale“ Anweisungen (bspw. a++ und --b) können zu einem Knoten zusammengefasst werden. Statt „yes“ und „no“ kann auch „true“ und „false“, „wahr“ und „falsch“, „ja“ und „nein“ o. Ä. geschrieben werden. Wo die Kanten einen Verzweigungsknoten verlassen oder ggf. wieder erreichen spielt keine Rolle. Übungsblock B (Lösung) – Stefan Berktold Seite 9 von 13 ③ Geben Sie die Kontrollflussdiagramme zu folgenden Codeauszügen an. Auf den nächsten Seiten ist Platz für Ihre Ausführungen. a) int x = 1; while (x < 5) { int y = x - 1; if (y > 3) { write("x") } else break; x = x * 2; } b) int x; x = read(); while (x < 0 || x > 100) { write("Wrong Input"); x = read(); } c) public int f() { int a = read(); if (a > 5) a = g(5); return a; } int g(int x) { if (x <= 5) return x-1; return 5; } d) for (int i = 1; i < 10; i *= 2) { if (i % 3 == 0) write(i); } e) public int fun(int x) { switch(x) { case 0: x = 5; break; case 1: case 3: x = 2; case 2: return 2; default: write(x); } return 1; } Übungsblock B (Lösung) – Stefan Berktold Seite 10 von 13 zu ③ a): zu ③ b): Übungsblock B (Lösung) – Stefan Berktold Seite 11 von 13 zu ③ c): zu ③ d): Übungsblock B (Lösung) – Stefan Berktold Seite 12 von 13 zu ③ e): Das switch-Statement ist nicht eindeutig definiert, vermutlich aber nicht klausurrelevant. Man könnte beispielsweise auch „switch(x)“ in einen Knoten packen und in den bedingten Verzweigungen dann bspw. „Case 0“ schreiben. Das „break“Statement kann auch auf den Pfeil geschrieben werden, wie es bei dem „default“Schlüsselwort gemacht wurde. Wichtig ist es, dass break-Statements beachtet werden (vgl. Unterschied Case 0 und Case 3). Übungsblock B (Lösung) – Stefan Berktold Seite 13 von 13