Articulo de referencia

Sintaxis (lenguajes de programación)

Este código Python se muestra con colores que resaltan los aspectos sintácticos. La sintaxis del código fuente de una computadora es un código estructurado y ordenado, sujeto a ...

Este código Python se muestra con colores que resaltan los aspectos sintácticos.

La sintaxis del código fuente de una computadora es un código estructurado y ordenado, sujeto a las reglas del lenguaje de programación. Al igual que un lenguaje natural , un lenguaje de programación define la sintaxis válida para ese lenguaje. [ 1 ] Un error de sintaxis ocurre cuando una herramienta , como un compilador o un intérprete, procesa código fuente sintácticamente inválido .

Los lenguajes más utilizados son los basados ​​en texto, con una sintaxis basada en cadenas de caracteres . Por otro lado, la sintaxis de un lenguaje de programación visual se basa en las relaciones entre elementos gráficos.

Al diseñar la sintaxis, un diseñador de cualquier lenguaje podría comenzar escribiendo ejemplos de cadenas válidas e inválidas , antes de intentar deducir las reglas generales a partir de esos ejemplos en uso. De esta manera, su estructura sintáctica general se puede determinar mediante su forma de composición, para producir rangos semánticamente válidos en cada caso para cada posible alteración. De lo contrario, devuelve errores y advertencias para cada entrada inválida. [ 2 ]

Niveles de sintaxis

La sintaxis de los lenguajes informáticos generalmente se distingue en tres niveles:

  • Palabras: el nivel léxico, que determina cómo los caracteres forman tokens ;
  • Frases: el nivel gramatical que, en sentido estricto, determina cómo los elementos forman frases;
  • Contexto: determinar a qué objetos o variables se refieren los nombres, si los tipos son válidos, etc.

Esta distinción genera modularidad, lo que permite describir y procesar cada nivel por separado y, a menudo, de forma independiente.

Primero, un analizador léxico convierte la secuencia lineal de caracteres en una secuencia lineal de tokens; esto se conoce como " análisis léxico " o "lexing". [ 3 ]

En segundo lugar, el analizador sintáctico transforma la secuencia lineal de tokens en un árbol sintáctico jerárquico ; esto se conoce como " análisis sintáctico " en sentido estricto. Esto garantiza que la secuencia de tokens se ajuste a las gramáticas formales del lenguaje de programación. La etapa de análisis sintáctico se divide en dos partes: el árbol de análisis sintáctico , o "árbol sintáctico concreto", que viene determinado por la gramática, pero que suele ser demasiado detallado para su uso práctico, y el árbol sintáctico abstracto (AST), que lo simplifica a un formato utilizable. Los pasos del AST y del análisis contextual pueden considerarse una forma de análisis semántico , ya que añaden significado e interpretación a la sintaxis, o bien, implementaciones manuales e informales de reglas sintácticas que serían difíciles o engorrosas de describir o implementar formalmente.

En tercer lugar, el análisis contextual resuelve nombres y verifica tipos. Esta modularidad a veces es posible, pero en muchos lenguajes del mundo real un paso anterior depende de un paso posterior; por ejemplo, el truco del analizador léxico en C se debe a que la tokenización depende del contexto. Incluso en estos casos, el análisis sintáctico suele considerarse una aproximación a este modelo ideal.

Los niveles generalmente corresponden a los niveles de la jerarquía de Chomsky . Las palabras están en un lenguaje regular , especificado en la gramática léxica , que es una gramática de tipo 3, generalmente dada como expresiones regulares . Las frases están en un lenguaje libre de contexto (CFL), generalmente un lenguaje libre de contexto determinista (DCFL), especificado en una gramática de estructura de frases , que es una gramática de tipo 2, generalmente dada como reglas de producción en forma de Backus-Naur (BNF). Las gramáticas de frases a menudo se especifican en gramáticas mucho más restringidas que las gramáticas libres de contexto completas , para que sean más fáciles de analizar; mientras que el analizador LR puede analizar cualquier DCFL en tiempo lineal, el analizador LALR simple e incluso el analizador LL más simple son más eficientes, pero solo pueden analizar gramáticas cuyas reglas de producción están restringidas. En principio, la estructura contextual puede describirse mediante una gramática sensible al contexto y analizarse automáticamente mediante herramientas como las gramáticas de atributos , aunque, en general, este paso se realiza manualmente, a través de reglas de resolución de nombres y comprobación de tipos , y se implementa mediante una tabla de símbolos que almacena nombres y tipos para cada ámbito.

Se han desarrollado herramientas que generan automáticamente un analizador léxico a partir de una especificación léxica escrita en expresiones regulares y un analizador sintáctico a partir de la gramática de frases escrita en BNF: esto permite utilizar programación declarativa , en lugar de programación procedimental o funcional . Un ejemplo notable es el par lex - yacc . Estos generan automáticamente un árbol de sintaxis concreto ; el desarrollador del analizador sintáctico debe escribir manualmente el código que describe cómo se convierte este árbol en un árbol de sintaxis abstracto . El análisis contextual también se implementa generalmente de forma manual. A pesar de la existencia de estas herramientas automáticas, el análisis sintáctico a menudo se implementa manualmente por diversas razones: quizás la estructura de la frase no sea libre de contexto, o una implementación alternativa mejore el rendimiento o la notificación de errores, o permita modificar la gramática con mayor facilidad. Los analizadores sintácticos suelen escribirse en lenguajes de programación funcional, como Haskell , o en lenguajes de scripting , como Python o Perl , o en lenguajes de programación imperativos, como C o C++ .

Definición de sintaxis

Árbol de análisis sintáctico de código Python con tokenización insertada

La sintaxis de los lenguajes de programación textuales se define generalmente mediante una combinación de expresiones regulares (para la estructura léxica ) y la forma de Backus-Naur (un metalenguaje para la estructura gramatical ) para especificar inductivamente categorías sintácticas ( no terminales ) y símbolos terminales . [ 4 ] Las categorías sintácticas se definen mediante reglas llamadas producciones , que especifican los valores que pertenecen a una categoría sintáctica particular. [ 1 ] Los símbolos terminales son los caracteres concretos o cadenas de caracteres (por ejemplo, palabras clave como define , if , let o void ) a partir de los cuales se construyen programas sintácticamente válidos.

La sintaxis se puede dividir en sintaxis libre de contexto y sintaxis sensible al contexto. [ 4 ] La sintaxis libre de contexto son reglas dirigidas por el metalenguaje del lenguaje de programación. Estas no estarían restringidas por el contexto que rodea o al que se refiere esa parte de la sintaxis, mientras que la sintaxis sensible al contexto sí lo estaría.

Un idioma puede tener gramáticas equivalentes distintas, como expresiones regulares equivalentes (a nivel léxico), o reglas de sintagma distintas que generan el mismo idioma. El uso de una categoría más amplia de gramáticas, como las gramáticas LR, permite gramáticas más cortas o sencillas en comparación con categorías más restringidas, como la gramática LL, que puede requerir gramáticas más largas con más reglas. Gramáticas de sintagma distintas pero equivalentes dan lugar a árboles de análisis sintáctico diferentes, aunque el idioma subyacente (conjunto de documentos válidos) sea el mismo.

Ejemplo: Expresiones S de Lisp

A continuación se muestra una gramática simple, definida mediante la notación de expresiones regulares y la forma extendida de Backus-Naur . Describe la sintaxis de las expresiones S , una sintaxis de datos del lenguaje de programación Lisp , que define producciones para las categorías sintácticas expresión , átomo , número , símbolo y lista :

expresión = átomo | lista átomo = número | símbolo número = [ + - ] ? [ '0' - '9' ] + símbolo = [ 'A' - 'Z' ][ 'A' - 'Z''0' - '9' ]. * lista = '(' , expresión * , ')'

Esta gramática especifica lo siguiente:

  • una expresión es un átomo o una lista ;
  • Un átomo es un número o un símbolo ;
  • Un número es una secuencia ininterrumpida de uno o más dígitos decimales, precedida opcionalmente por un signo más o menos;
  • Un símbolo es una letra seguida de cero o más caracteres cualesquiera (excluyendo espacios en blanco); y
  • Una lista es un par de paréntesis emparejados, con cero o más expresiones en su interior.

Aquí, los dígitos decimales, las letras mayúsculas y minúsculas, y los paréntesis son símbolos terminales.

Los siguientes son ejemplos de secuencias de tokens bien formadas en esta gramática: ' 12345', ' ()', ' (A B C232 (1))'

Gramáticas complejas

La gramática necesaria para especificar un lenguaje de programación se puede clasificar según su posición en la jerarquía de Chomsky . La gramática sintáctica de la mayoría de los lenguajes de programación se puede especificar mediante una gramática de tipo 2, es decir, son gramáticas libres de contexto [ 5 ] , aunque la sintaxis general es sensible al contexto (debido a las declaraciones de variables y los ámbitos anidados), por lo que es de tipo 1. Sin embargo, existen excepciones, y para algunos lenguajes la gramática sintáctica es de tipo 0 (Turing-completa).

En algunos lenguajes como Perl y Lisp, la especificación (o implementación) del lenguaje permite construcciones que se ejecutan durante la fase de análisis sintáctico. Además, estos lenguajes tienen construcciones que permiten al programador alterar el comportamiento del analizador. Esta combinación difumina la distinción entre análisis sintáctico y ejecución, y convierte el análisis sintáctico en un problema indecidible en estos lenguajes, lo que significa que la fase de análisis sintáctico puede no finalizar. Por ejemplo, en Perl es posible ejecutar código durante el análisis sintáctico mediante una BEGINinstrucción, y los prototipos de funciones de Perl pueden alterar la interpretación sintáctica, e incluso la validez sintáctica del código restante. [ 6 ] [ 7 ] Coloquialmente, esto se conoce como "solo Perl puede analizar Perl" (porque el código debe ejecutarse durante el análisis sintáctico y puede modificar la gramática), o más fuertemente "ni siquiera Perl puede analizar Perl" (porque es indecidible). De manera similar, las macros de Lisp introducidas por la defmacrosintaxis también se ejecutan durante el análisis sintáctico, lo que significa que un compilador de Lisp debe tener presente un sistema de tiempo de ejecución de Lisp completo. En contraste, las macros de C son simplemente reemplazos de cadenas y no requieren ejecución de código. [ 8 ] [ 9 ]

Sintaxis versus semántica

La sintaxis de un lenguaje describe la forma de un programa válido, pero no proporciona información sobre su significado ni sobre los resultados de su ejecución. El significado que se le da a una combinación de símbolos lo gestiona la semántica (ya sea formal o codificada en una implementación de referencia ). Es necesario establecer una sintaxis válida antes de que la semántica pueda interpretarla. [ 4 ] No todos los programas sintácticamente correctos son semánticamente correctos. Muchos programas sintácticamente correctos, sin embargo, están mal formados según las reglas del lenguaje y pueden (dependiendo de la especificación del lenguaje y la solidez de la implementación) generar un error en la traducción o ejecución. En algunos casos, dichos programas pueden presentar un comportamiento indefinido . Incluso cuando un programa está bien definido dentro de un lenguaje, puede tener un significado que no es el previsto por quien lo escribió.

Tomando como ejemplo el lenguaje natural , puede que no sea posible asignar un significado a una oración gramaticalmente correcta, o que la oración sea falsa:

  • " Las ideas verdes incoloras duermen furiosamente ." está bien formada gramaticalmente, pero no tiene un significado generalmente aceptado.
  • "John is a married single" (John es un soltero casado) está gramaticalmente bien formado, pero expresa un significado que no puede ser cierto.

El siguiente fragmento de código C es sintácticamente correcto, pero realiza una operación que no está definida semánticamente (porque pes un puntero nulo , las operaciones y no tienen significado):p->realp->im

complejo * p = NULL ; complejo abs_p = sqrt ( p -> real * p -> real + p -> im * p -> im );

Como ejemplo más sencillo,

int x ; printf ( "%d" , x );

es sintácticamente válido, pero no semánticamente definido, ya que utiliza una variable no inicializada . Aunque los compiladores de algunos lenguajes de programación (por ejemplo, Java y C#) detectarían errores de variables no inicializadas de este tipo, deberían considerarse errores semánticos en lugar de errores sintácticos. [ 10 ] [ 11 ]

Véase también

Referencias

  1. 1 2 Friedman, Daniel P.; Mitchell Wand; Christopher T. Haynes (1992). Fundamentos de los lenguajes de programación (1.ª ed.). The MIT Press. ISBN  0-262-06145-7.
  2. Smith, Dennis (1999). Diseño de software mantenible . Springer Science & Business Media.
  3. Pai, Vaikunta; Aithal, PS (31 de diciembre de 2020). "Una revisión sistemática de la literatura sobre técnicas de implementación de analizadores léxicos en el diseño de compiladores" . International Journal of Applied Engineering and Management Letters . 4 (2): 285– 301. doi : 10.47992/IJAEML.2581.7000.0087 . ISSN 2581-7000 . SSRN 3770588 .  
  4. 1 2 3 Sloneggger, Kenneth; Kurtz, Barry (1995). Sintaxis formal y semántica de los lenguajes de programación . Addison-Wesley Publishing Company . ISBN 0-201-65697-3.
  5. Michael Sipser (1997). "2.2 Autómatas de pila". Introducción a la teoría de la computación . PWS Publishing. págs. 101–114 . ISBN  0-534-94728-X.
  6. Comentario de LtU que aclara que el problema indecidible es la pertenencia a la clase de programas Perl.
  7. Ejemplo de código Perl de chromatic que produce un error de sintaxis dependiendo del valor de la variable aleatoria.
  8. "Una introducción a las macros de Common Lisp" . Apl.jhu.edu. 8 de febrero de 1996. Archivado del original el 6 de agosto de 2013. Consultado el 17 de agosto de 2013 .
  9. "The Common Lisp Cookbook - Macros and Backquote" . Cl-cookbook.sourceforge.net. 16 de enero de 2007. Consultado el 17 de agosto de 2013 .
  10. Errores semánticos en Java
  11. ¿Problema de sintaxis o de semántica?
  • Diversas construcciones sintácticas utilizadas en los lenguajes de programación informática.
  • Error de Python “ImportError: No module named” ¿Por qué? ¿Cómo? ¿Línea de comandos? [Resuelto en 2021] . Archivado el 9 de octubre de 2021 en Wayback Machine .