¿Cómo se usa internamente la gramática de Python?

Estoy tratando de comprender mejor cómo funciona Python, y he estado viendo la gramática que se muestra en http://docs.python.org/3.3/reference/grammar.html .

Noté que dice que tendrías que cambiar parsermodule.c también, pero la verdad es que no estoy siguiendo lo que está pasando aquí.

Entiendo que una gramática es una especificación sobre cómo leer el idioma, pero … ni siquiera puedo decir en qué está escrito. Se parece casi a Python, pero no lo es.

Busco entender mejor esta especificación y cómo Python la utiliza internamente para … hacer cosas. ¿Qué depende de eso? (La respuesta lo es todo, pero me refiero específicamente a qué aspecto del “motor” lo está procesando), a qué se debe y cómo se relaciona con la comstackción / ejecución de un script

Es difícil creer que todo el lenguaje se reduce a una especificación de dos páginas …

Una gramática se utiliza para describir todas las cadenas posibles en un idioma. También es útil para especificar cómo debe analizar el lenguaje un analizador.

En esta gramática parece que están usando su propia versión de EBNF , donde un no-terminal es cualquier palabra en minúscula y un terminal está todo en mayúsculas o entre comillas. Por ejemplo, NEWLINE es un terminal, arith_expr no es un terminal y ‘if’ también es un terminal. Cualquier no terminal puede ser reemplazado por cualquier cosa a la derecha de los dos puntos de su respectiva regla de producción. Por ejemplo, si nos fijamos en la primera regla:

single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE

Podemos reemplazar single_input con uno de NEWLINE, simple_stmt o compound_stmt seguido de NEWLINE. Supongamos que lo reemplazamos con “compound_stmt NEWLINE”, entonces buscaríamos la regla de producción para compound_stmt:

compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorado

y elija cuál de estos queremos usar, y sustitúyalo por “compound_stmt” (Mantener NEWLINE en su lugar)

Supongamos que queremos generar el progtwig de python válido:

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

Podríamos utilizar la siguiente derivación:

  1. single_input
  2. compound_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 'o' and_test ':' NEWLINE INDENT simple_stmt DEDENT NEWLINE
  7. 'if' not_test 'o' not_test ':' NEWLINE INDENT small_stmt DEDENT NEWLINE
  8. 'si' comparación 'o' 'no' not_test ':' NEWLINE INDENT flow_stmt DEDENT NEWLINE
  9. 'if' expr comp_op expr 'o' 'no' de comparación ':' NEWLINE INDENT raise_stmt DEDENT NEWLINE
  10. 'if' arith_expr '<' arith_expr 'o' 'not' arith_expr comp_op arith_expr ':' NEWLINE INDENT 'raise' DEDENT NEWLINE
  11. 'if' term '<' term '+' term 'o' 'not' arith_expr == arith_expr ':' NEWLINE INDENT 'raise' DEDENT NEWLINE
  12. 'if' NUMBER '<' NUMBER '+' NUMBER 'o' 'not' NUMBER == NUMBER ':' NEWLINE INDENT 'aumentar' DEDENT NEWLINE

Un par de notas aquí, en primer lugar, debemos comenzar con uno de los no terminales que aparece como un no terminal inicial. En esa página, los enumeran como single_input, file_input o eval_input. En segundo lugar, una derivación se termina una vez que todos los símbolos son terminales (de ahí el nombre). En tercer lugar, es más común hacer una sustitución por línea, en aras de la brevedad, hice todas las posibles sustituciones a la vez y comencé a saltar pasos cerca del final.

Dada una cadena en el lenguaje, ¿cómo encontramos su derivación? Este es el trabajo de un analizador. Un analizador realiza una ingeniería inversa de una secuencia de producción para comprobar primero que es realmente una cadena válida y, además, cómo se puede derivar de la gramática. Vale la pena señalar que muchas gramáticas pueden describir un solo idioma. Sin embargo, para una cadena dada, su derivación, por supuesto, será diferente para cada gramática. Así que técnicamente escribimos un analizador para una gramática, no un idioma. Algunas gramáticas son más fáciles de analizar, algunas gramáticas son más fáciles de leer / entender. Este pertenece a la primera.

Además, esto no especifica el idioma completo, solo lo que parece. Una gramática no dice nada sobre la semántica.

Si está interesado en más sobre análisis y gramática, recomiendo Grune, Jacobs - Técnicas de análisis . Es gratis y bueno para el auto-estudio.

La gramática de los pitones, como la mayoría de los otros, se proporciona en formato BNF o Backus-Naur . Intente leer sobre cómo leerlo, pero la estructura básica es:

  ::= ( | [some fixed things]) [...] 

Esto se lee como un se define como something else o cualquiera de las cosas fijas repetidas una multitud de veces.

BNF se basa en un formato de casi 2000 años para describir la estructura permitida de un idioma, es increíblemente conciso y describirá todas las estructuras permitidas en un idioma determinado, no necesariamente todas aquellas que tengan sentido .

Ejemplo

La aritmética básica se puede describir como:

  ::= [ ]...([ ]...|)  ::= [][...][.[...]]  ::= +|-  ::= [+-*/]  ::= [0123456789] 

Lo que dice que una operación aritmética simple es un número, opcionalmente firmado, que consta de uno o más dígitos, posiblemente con un punto decimal y uno o más, dígitos subsiguientes, opcionalmente seguidos de espacios, seguidos de exactamente uno de +-*/ , opcionalmente, seguido de espacios, seguido de un número u otra operación aritmética simple, es decir, un número seguido de, etc.

Esto describe casi todas las operaciones aritméticas básicas y puede extenderse para incluir funciones, etc. Tenga en cuenta que permite operaciones no válidas que son una syntax válida, por ejemplo: 22.34 / -0.0 es sintácticamente válido aunque el resultado no sea válido .

Algunas veces puede hacerle saber que es posible que no haya pensado en operaciones, por ejemplo: 56+-50 es una operación válida, ya que es 2*-10 pero 2*/3 no lo es.

Tenga en cuenta que SGML y XML / Schema son metodologías relacionadas pero diferentes para describir la estructura de cualquier idioma. YAML es otro método para describir las estructuras permitidas en lenguajes específicos de una computadora .

Descargo de responsabilidad: Mi BNF está un poco oxidado, así que si he cometido algún error importante en lo que antecede, pido disculpas y corríjame.

Eso es básicamente una especificación EBNF (Extended Backus-Naur Form).

Cuando escribe un progtwig en un idioma, lo primero que debe hacer su intérprete / comstackdor para pasar de una secuencia de caracteres a una acción real es traducir esa secuencia de caracteres en una estructura de mayor complejidad. Para hacerlo, primero se divide el progtwig en una secuencia de tokens que expresan lo que representa cada “palabra”. Por ejemplo, la construcción

 if foo == 3: print 'hello' 

será convertido en

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

Pero tenga en cuenta que incluso algo como “if if if if” se convierte correctamente en tokens

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

Lo que sigue a la tokenización es el análisis en una estructura de nivel superior que analiza si los tokens realmente tienen sentido en conjunto, algo que el último ejemplo no tiene, pero el primero sí. Para hacerlo, el analizador debe reconocer el significado real de los tokens (por ejemplo, if es una palabra clave, y foo es una variable), luego construir un árbol a partir de los tokens, organizarlos en una jerarquía y ver si esta jerarquía realmente hace sentido. Aquí es donde entra la gramática que está viendo. Esa gramática está en BNF, que es una notación para express los conceptos que el lenguaje puede reconocer. Esa gramática es digerida por un progtwig (por ejemplo, bison) que tiene la propiedad mágica de tomar esa gramática y generar el código C real que hace el trabajo pesado por ti, normalmente reconociendo los tokens, organizándolos, devolviéndote un árbol de análisis, O te diré dónde hay un error.

Versión corta: desarrollar un lenguaje se trata de definir tokens y cómo se juntan para dar algo significativo. Esto se hace a través de la gramática, que se usa para generar el código real del “analizador” con herramientas automatizadas.