Nachdem wir einen Teil von Python-Ausdrücken in der BNF beschrieben haben, wollen wir nun Anweisungen beschreiben. Dazu definieren wir eine EBNF mit einem Nichtterminalsymbol Stmt
unter Verwendung der vorher definierten Nichtterminalsymbole (insbesondere Exp
für arithmetische und BExp
für logische Ausdrücke, siehe Übung). Die folgende Grafik veranschaulicht eine Hierarchie von
Python-Anweisungen:
Einfache Anweisungen sind demnach Zuweisungen und Ausgabe-Anweisungen, von der wir exemplarisch die print
-Anweisung als mögliche Ableitung des Nichtterminals Stmt
spezifizieren:
Stmt ::= 'print(' (Exp | BExp) ')'
Als Argument kann der print
-Anweisung ein beliebiger arithmetischer oder logischer Ausdruck übergeben werden, dessen Wert ausgegeben werden soll. Um dies zu spezifizieren, verwenden wir eine mit Klammern gruppierte Alternative der Nichtterminalsymbole Exp
und BExp
in der Argumentposition.
Ebenso verfahren wir bei Anweisungen, mit denen der Wert
eines Ausdrucks einer Variablen zugewiesen wird und erweitern
entsprechend die Definition von Stmt
:
Stmt ::= ...
| Var '=' (Exp | BExp)
In Python lassen sich mehrere Anweisungen kombinieren, indem man sie untereinander schreibt. Diese Möglichkeit formalisieren wir mit Hilfe des Nichtterminals Stmts
:
Stmts ::= Stmt { '\n' Stmt }
\n
symbolisiert hier einen Zeilenumbruch.
Hier verwenden wir geschweifte Klammern, um mehrere Anweisungen durch einen Zeilenumbruch trennen zu können. Generell ignorieren wir Leerzeichen bei der Ableitung. Bei der Anwendung dieser Regel zur Ableitung von Anweisungsfolgen mit mehr als einer Anweisung müssen nach unser Definition jedoch Zeilenumbrüche vorhanden sein.
Es bleibt noch die Spezifikation von Kontrollstrukturen, also bedingten Anweisungen und Schleifen.
Bedingte Anweisungen treten in zwei Formen auf, nämlich mit und ohne Alternative hinter dem Schlüsselwort else
. Zu ihrer Spezifikation fügen wir eine weitere Regel zur Ableitung aus dem Nichtterminal Stmt
hinzu:
Stmt ::= ...
| 'if' BExp ':\n→' Stmts [ '\n←else:\n→' Stmts] '\n←'
Hier verwenden wir BExp
für logische Ausdrücke und das eben definierte Nichtterminal Stmts
für Anweisungsfolgen. Optionale Alternativen spezifizieren wir mit Hilfe eckiger Klammern. Die Pfeile →
und ←
symbolisieren die Einrückungen, die in Python vorgeschrieben sind. Sie kommen in Python-Programmen nicht vor, könnten aber in einem Extra-Schritt vor der syntaktischen Analyse anhand der Einrückungen eingefügt werden. Anschließend spielt dann die Einrückung für die Analyse, die stattdessen die Pfeile berücksichtigt, keine Rolle mehr.
Mit Schleifen können wir ähnlich verfahren. Zählschleifen definieren eine Zählvariable, einen Zahlenbereich, den diese durchläuft, und eine Anweisungsfolge, die wiederholt wird.
Stmt ::= ...
| 'for' Var 'in range(' Exp ',' Exp '):\n→' Stmts '\n←'
Wir verwenden entsprechend das Nichtterminal Var
für die Zählvariable, Exp
für die Grenzen des Zahlenbereiches und Stmts
für den Rumpf der Wiederholung.
Schließlich fügen wir noch eine Regel zur Spezifikation bedingter Schleifen hinzu.
Stmt ::= ...
| 'while' BExp ':\n→' Stmts '\n←'
Zusammengefasst ergibt sich die folgende Definition in EBNF zur Beschreibung von Python-Anweisungen:
Stmts ::= Stmt { '\n' Stmt }
Stmt ::= 'print(' (Exp | BExp) ')'
| Var '=' (Exp | BExp)
| 'if' BExp ':\n→' Stmts [ '\n←else:\n→' Stmts] '\n←'
| 'for' Var 'in range(' Exp ',' Exp '):\n→' Stmts '\n←'
| 'while' BExp ':\n→' Stmts '\n←'