Syntax von Python-Anweisungen

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:

graph TB n0(Anweisung) --- n1("Zuweisung (Variable = Wert)") & n2(Kontrollstruktur) & n3("Ausgabe (print)") n2 --- n4("Bedingte Anweisung") & n5(Schleife) n4 --- n6("Optionale Anweisung (if-then)") & n7("Bedingte Verzweigung (if-then-else)") n5 --- n8("Zählschleife") & n9("Bedingte Schleife") classDef default fill:none,stroke-width:0px;

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←'