Ich versuche, ein tieferes Verständnis der Funktionsweise von Python zu erlangen, und habe mir die Grammatik unter http angesehen : //docs.python.org/3.3/reference/grammar.html.

Ich stelle fest, dass Sie auch parsermodule.c ändern müssten, aber ehrlich gesagt verfolge ich einfach nicht, was hier vor sich geht.

Ich verstehe, dass eine Grammatik eine Spezifikation für das Lesen der Sprache ist, aber ... ich kann nicht einmal sagen, in was dies geschrieben ist. Es sieht fast wie Python aus, ist es aber nicht.

Ich möchte ein besseres Verständnis für diese Spezifikation bekommen und wie sie intern von Python verwendet wird, um ... Dinge zu tun. Was hängt davon ab (die Antwort ist alles, aber ich meine speziell, welcher Aspekt der "Engine" verarbeitet es), was verwendet es, wie hängt es mit dem Kompilieren / Ausführen eines Skripts zusammen?

Es ist kaum zu glauben, dass die gesamte Sprache auf eine zweiseitige Spezifikation hinausläuft ...

18
temporary_user_name 14 Okt. 2013 im 02:31

4 Antworten

Beste Antwort

Eine Grammatik wird verwendet, um alle möglichen Zeichenfolgen in einer Sprache zu beschreiben. Es ist auch nützlich, um anzugeben, wie ein Parser die Sprache analysieren soll.

In dieser Grammatik scheinen sie ihre eigene Version von EBNF zu verwenden. Dabei ist ein Nicht-Terminal ein Wort in Kleinbuchstaben und ein Terminal in Großbuchstaben oder von Anführungszeichen umgeben. Beispielsweise ist NEWLINE ein Terminal, arith_expr ist ein Nicht-Terminal und 'if' ist auch ein Terminal. Jedes Nicht-Terminal kann durch etwas rechts vom Doppelpunkt der jeweiligen Produktionsregel ersetzt werden. Wenn Sie sich zum Beispiel die erste Regel ansehen:

single_input: NEWLINE | simple_stmt | zusammengesetzte_stmt NEWLINE

Wir können single_input durch eine NEWLINE, eine simple_stmt oder eine Compound_stmt gefolgt von einer NEWLINE ersetzen. Angenommen, wir haben es durch "Compound_stmt NEWLINE" ersetzt, dann würden wir nach der Produktionsregel für Compound_stmt suchen:

zusammengesetzte_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | dekoriert

Und wählen Sie aus, welche davon wir verwenden möchten, und ersetzen Sie sie durch "complex_stmt" (behalten Sie NEWLINE bei)

Angenommen, wir wollten das gültige Python-Programm generieren:

if 5 < 2 + 3 or not 1 == 5:
    raise

Wir könnten die folgende Ableitung verwenden:

  1. single_input
  2. zusammengesetzte_stmt NEWLINE
  3. if_stmt NEWLINE
  4. 'if' test ':' suite NEWLINE
  5. 'if' or_test ':' NEWLINE INDENT stmt stmt DEDENT NEWLINE
  6. 'if' and_test 'oder' and_test ':' NEWLINE INDENT simple_stmt DEDENT NEWLINE
  7. 'if' not_test 'oder' not_test ':' NEWLINE INDENT small_stmt DEDENT NEWLINE
  8. 'if' Vergleich 'oder' 'nicht' not_test ':' NEWLINE INDENT flow_stmt DEDENT NEWLINE
  9. 'if' expr comp_op expr 'oder' 'not' compare ':' NEWLINE INDENT lift_stmt DEDENT NEWLINE
  10. 'if' arith_expr '<' arith_expr 'oder' 'not' arith_expr comp_op arith_expr ':' NEWLINE INDENT 'löst' DEDENT NEWLINE 'aus
  11. 'if' term '<' term '+' term 'oder' 'not' arith_expr == arith_expr ':' NEWLINE INDENT 'löst' DEDENT NEWLINE 'aus
  12. 'if' NUMBER '<' NUMBER '+' NUMBER 'oder' 'not' NUMBER == NUMBER ':' NEWLINE INDENT 'erhöht' DEDENT NEWLINE '

Ein paar Anmerkungen hier, zunächst müssen wir mit einem der Nicht-Terminals beginnen, das als Start-Nicht-Terminal aufgeführt ist. Auf dieser Seite werden sie als single_input, file_input oder eval_input aufgelistet. Zweitens ist eine Ableitung beendet, sobald alle Symbole terminal sind (daher der Name). Drittens ist es üblicher, eine Substitution pro Zeile durchzuführen. Der Kürze halber habe ich alle möglichen Substitutionen gleichzeitig durchgeführt und gegen Ende begonnen, Schritte zu überspringen.

Wie finden wir bei einer Zeichenfolge in der Sprache die Ableitung? Dies ist die Aufgabe eines Parsers. Ein Parser entwickelt eine Produktionssequenz zurück, um zunächst zu überprüfen, ob es sich tatsächlich um eine gültige Zeichenfolge handelt und wie sie aus der Grammatik abgeleitet werden kann. Es ist erwähnenswert, dass viele Grammatiken eine einzelne Sprache beschreiben können. Für eine bestimmte Zeichenfolge ist die Ableitung natürlich für jede Grammatik unterschiedlich. Technisch gesehen schreiben wir einen Parser für eine Grammatik, keine Sprache. Einige Grammatiken sind leichter zu analysieren, andere sind leichter zu lesen / verstehen. Dieser gehört in die erstere.

Auch dies spezifiziert nicht die gesamte Sprache, sondern nur, wie sie aussieht. Eine Grammatik sagt nichts über Semantik aus.

Wenn Sie mehr über Parsing und Grammatik erfahren möchten, empfehle ich Grune, Jacobs - Parsing Techniques. Es ist kostenlos und gut zum Selbststudium.

15
user2097752 30 Dez. 2013 im 08:03

Wenn Sie ein Programm in einer Sprache schreiben, muss Ihr Interpreter / Compiler als erstes diese Zeichenfolge in eine Struktur mit höherer Komplexität übersetzen, um von einer Zeichenfolge zu einer tatsächlichen Aktion zu gelangen. Dazu zerlegt es zuerst Ihr Programm in eine Folge von Token, die ausdrücken, was jedes "Wort" darstellt. Zum Beispiel das Konstrukt

if foo == 3: print 'hello'

Wird umgewandelt in

1,0-1,2:    NAME    'if'
1,3-1,6:    NAME    'foo'
1,7-1,9:    OP  '=='
1,10-1,11:  NUMBER  '3'
1,11-1,12:  OP  ':'
1,13-1,18:  NAME    'print'
1,19-1,26:  STRING  "'hello'"
2,0-2,0:    ENDMARKER   ''

Beachten Sie jedoch, dass sogar so etwas wie "wenn wenn wenn wenn" korrekt in Token umgewandelt wird

1,0-1,2:    NAME    'if'
1,3-1,5:    NAME    'if'
1,6-1,8:    NAME    'if'
1,9-1,11:   NAME    'if'
2,0-2,0:    ENDMARKER   ''

Was auf die Tokenisierung folgt, ist das Parsen in eine übergeordnete Struktur, die analysiert, ob die Token zusammengenommen tatsächlich Sinn ergeben, was das letztere Beispiel nicht tut, das erste jedoch. Dazu muss der Parser die tatsächliche Bedeutung der Token erkennen (z. B. if ist ein Schlüsselwort und foo ist eine Variable), dann einen Baum aus den Token erstellen, sie in einer Hierarchie organisieren und prüfen, ob diese Hierarchie tatsächlich funktioniert Sinn. Hier kommt die Grammatik ins Spiel, die Sie sehen. Diese Grammatik ist in BNF, einer Notation, um die Konstrukte auszudrücken, die die Sprache erkennen kann. Diese Grammatik wird von einem Programm (z. B. Bison) verdaut, das die magische Eigenschaft hat, diese Grammatik zu übernehmen und tatsächlichen C-Code zu generieren, der die schwere Arbeit für Sie erledigt. Normalerweise erkennt es die Token, organisiert sie und gibt Ihnen einen Analysebaum zurück. oder sag dir, wo es einen Fehler gibt.

Kurzfassung: Bei der Entwicklung einer Sprache geht es darum, Token zu definieren und wie diese Token zusammengesetzt werden, um etwas Sinnvolles zu ergeben. Dies geschieht über die Grammatik, mit der Sie den eigentlichen "Parser" -Code mit automatisierten Tools generieren.

4
Stefano Borini 19 Dez. 2013 im 22:06

Die Python-Grammatik wird - wie die meisten anderen - in BNF oder Backus-Naur-Form angegeben . Lesen Sie nach, wie man es liest, aber die Grundstruktur lautet:

<something> ::= (<something defined elsewhere> | [some fixed things]) [...]

Dies wird gelesen, wenn <something> als something else oder eines der festen Dinge definiert wird, die mehrmals wiederholt werden.

BNF basiert auf einem fast 2000 Jahre alten Format zur Beschreibung der zulässigen Struktur einer Sprache, ist unglaublich knapp und beschreibt alle zulässigen Strukturen in einer bestimmten Sprache, nicht unbedingt alle, die Sinn machen würden .

Beispiel

Grundlegende Arithmetik kann beschrieben werden als:

<simple arithmetic expression> ::= <numeric expr>[ ]...(<operator>[ ]...<numeric expr>|<simple arithmetic expression>)
<numeric expr> ::= [<sign>]<digit>[...][.<digit>[...]]
<sign> ::= +|-
<operator> ::= [+-*/]
<digit> ::= [0123456789]

Was besagt, dass eine einfache arithmetische Operation eine optional vorzeichenbehaftete Zahl ist, die aus einer oder mehreren Ziffern besteht, möglicherweise mit einem Dezimalpunkt und einer oder mehreren nachfolgenden Ziffern, optional gefolgt von Leerzeichen, gefolgt von genau einer von +-*/ , optional gefolgt von Leerzeichen, gefolgt von einer Zahl oder einer anderen einfachen arithmetischen Operation, dh einer Zahl gefolgt von usw.

Dies beschreibt fast alle alle grundlegenden arithmetischen Operationen und kann um Funktionen usw. erweitert werden. Beachten Sie, dass ungültige Operationen zulässig sind, die eine gültige Syntax haben, z. B.: 22.34 / -0.0 ist gültig syntaktisch, obwohl das Ergebnis ungültig ist.

Es kann Sie manchmal darauf aufmerksam machen, dass Operationen möglich sind, an die Sie möglicherweise nicht gedacht haben, z. B.: 56+-50 ist eine gültige Operation wie 2*-10, 2*/3 jedoch nicht.

Beachten Sie, dass SGML und XML / Schema sind beide verwandte, aber unterschiedliche Methoden zur Beschreibung der Struktur von any Sprache. YAML ist eine weitere Methode zur Beschreibung der zulässigen Strukturen in einem Computer bestimmten Sprachen.

Haftungsausschluss: Mein BNF ist etwas verrostet. Wenn ich in den oben genannten Fällen größere Fehler gemacht habe, entschuldige ich mich und korrigiere mich bitte.

8
Steve Barnes 15 Mai 2015 im 07:14

Dies ist im Grunde eine EBNF -Spezifikation (Extended Backus-Naur Form).

5
Amber 13 Okt. 2013 im 22:44