Analizador sintáctico


El analizador sintáctico, también conocido como parser, es la segunda fase del compilador y una de las etapas más importantes de la compilación. Este proceso trabaja a nivel de sentencias, lo que lo hace mucho más complejo que el análisis léxico. Su propósito principal es procesar el lenguaje de programación verificando la estructura gramatical de una cadena de entrada, como el código de un programa, para asegurarse de que cumple con las reglas de una gramática ya definida.

El análisis sintáctico se basa en las gramáticas libres de contexto (GLC), que son un conjunto de reglas formales usadas para definir la estructura sintáctica de los lenguajes de programación .

Una gramática libre de contexto tiene cuatro elementos básicos:

  • Un conjunto de no terminales(V). Los no terminales son variables sintácticas que denotan conjuntos de cadenas de texto que ayudan a definir el lenguaje generado por la gramática.
  • Un conjunto de tokens, conocidos como símbolos terminales (?). Los símbolos terminales son los símbolos básicos con los cuales las cadenas de texto son formadas.
  • Un conjunto de producciones (P). Las producciones de una gramática especifican la forma en la cual los no terminales pueden ser combinados para formar cadenas. Cada producción consiste de no terminales, a los que llamamos lado izquierdo de la producción, luego una flecha, seguida de una secuencia de tokens y/o terminales a los que llamamos lado derecho de la producción.
  • Uno de los no terminales es designado como el símbolo de inicio (S) desde el cual comienza la producción.

La función del analizador sintáctico es tomar el programa fuente, representado como una secuencia de tokens generados por el analizador léxico, y determinar la estructura de las sentencias del programa.

El análisis sintáctico organiza los tokens en clases sintácticas también llamadas no terminales en la gramática, como expresiones, procedimientos y otros elementos. El resultado de este proceso es un árbol sintáctico, donde las hojas representan los tokens y los nodos internos corresponden a las clases sintácticas, mostrando cómo se organiza la estructura del programa de acuerdo con la gramática.

Las fases del análisis sintáctico son:

  • Recepción de Tokens: El analizador léxico entrega los tokens al analizador sintáctico.
  • Verificación: Verifica si los tokens coinciden con las reglas de producción de la gramática.
  • Construcción del Árbol Sintáctico: Representa la estructura jerárquica del programa según las reglas gramaticales.
  •  Generación de Errores: Si encuentra una secuencia inválida, genera un mensaje de error indicando el problema.

En la Figura 7. Se toma un flujo de tokens (generado por un analizador léxico) y, utilizando una gramática libre de contexto, el analizador sintáctico (parser) construye un árbol de sintaxis concreta (parse tree) que organiza los elementos según las reglas gramaticales del lenguaje.

Figura 7. Se representa el proceso de análisis sintáctico en la construcción de un compilador o intérprete de lenguajes de programación. (s/f)(S/f). Ytimg.com. Recuperado el 20 de noviembre de 2024, de https://i.ytimg.com/vi/7ptttGTQQmk/maxresdefault.jpg

Objetivo del Análisis Sintáctico

su objetivo principal es verificar  y garantizar que el código fuente recibido sea sintácticamente correcto, lo cual en pocas palabras es que el programa tenga as reglas de sintaxis definidas por la gramática del lenguaje.


¿ Que es un árbol sintáctico?

Es la representación gráfica de la estructura gramatical de una secuencia de símbolos en un lenguaje formal, mostrando cómo se agrupan y se relacionan entre sí. En programación, se usa para mostrar cómo se analizan las instrucciones  según la gramática de un lenguaje de programación. 

La parte mas importante de esta fase es la construcción del árbol sintáctico el cual se clasifica de la siguiente manera acentuación: 

Árbol Sintáctico de Descripción:

1. Árbol Sintáctico de Descripción:

  • Muestra la estructura gramatical de una cadena de símbolos con respecto a una gramática específica.
  • Se utiliza en los analizadores sintácticos de compiladores y lenguajes formales.

2. Árboles Sintácticos Abstractos (AST, por sus siglas en inglés):

  • Representa solo la estructura esencial de una expresión o declaración, sin incluir detalles redundantes de la gramática.
  • Por ejemplo, se omiten paréntesis innecesarios y los nodos de tipo de datos.

3. Árboles Sintácticos Concretos:

  • Representan la cadena de entrada con todos sus detalles gramaticales.
  • Pueden incluir símbolos como paréntesis y operadores, mostrando la estructura exacta de la entrada.

El árbol sintáctico esta compuesto por: 

  • Nodo raíz

El nodo raíz,  es la parte principal de todo árbol de Análisis. La raíz representa la frase que se desglosa en partes, siempre solo hay un nodo de estos por árbol. 

  • Ramificación del Nodo

Este es el nodo rama,  Están situados directamente debajo del nodo raíz, por lo que también se les llama nodos padre porque están por encima de los demás nodos del diagrama.

  • Hoja del Nodo

El último es el nodo Hoja. Son los nodos que encontrarás directamente debajo de los nodos padre.

En la Figura 8. Se muestra la continuación del código fuente que se ingreso del compilador de clasificación de imágenes donde el analizador hace su trabajo clasificando por orden jerárquico los tokens según la gramática que tenga, al igual que se podrán observar como esta compuesto el árbol sintáctico completamente.

Figura 8. Árbol sintáctico estructurando el orden jerárquico. Imagen creada por el autor en Excel el 26 de noviembre del 2024. 

La derivación de la gramática del árbol sintáctico de la Figura 8 seria el siguiente:

  • Comienza con el símbolo inicial:

    • <programa>
  • Primera Producción (import tensorflow as tf):

    • <programa> ::= <importacion> <programa>
    • Derivamos la primera línea:
      • import tensorflow as tf
    • Usamos la regla:
      • <importacion> ::= import <identificador> as <identificador>
      • Esto deriva a import tensorflow as tf.
  • Segunda Producción (categorias = ["gatos", "perros"]):

    • <programa> ::= <declaracion> <programa>
    • Derivamos:
      • categorias = ["gatos", "perros"]
    • Usamos la regla:
      • <declaracion> ::= <asignacion>
      • <asignacion> ::= <identificador> = <expresion>
      • Esto deriva a categorias = ["gatos", "perros"].
  • Tercera Producción (modelo = tf.keras.models.load_model("modeloPreEntrenado.clf")):

    • <programa> ::= <declaracion> <programa>
    • Derivamos:
      • modelo = tf.keras.models.load_model("modeloPreEntrenado.clf")
    • Usamos la regla:
      • <declaracion> ::= <asignacion>
      • <asignacion> ::= <identificador> = <expresion>
      • Esto deriva a modelo = tf.keras.models.load_model("modeloPreEntrenado.clf").
  • Cuarta Producción (entrada = image.load_img("perro.jpg", target_size=(224, 224))):

    • <programa> ::= <declaracion> <programa>
    • Derivamos:
      • entrada = image.load_img("perro.jpg", target_size=(224, 224))
    • Usamos la regla:
      • <declaracion> ::= <asignacion>
      • <asignacion> ::= <identificador> = <expresion>
      • Esto deriva a entrada = image.load_img("perro.jpg", target_size=(224, 224)).
  • Quinta Producción (entrada = image.img_to_array(entrada)):

    • <programa> ::= <declaracion> <programa>
    • Derivamos:
      • entrada = image.img_to_array(entrada)
    • Usamos la regla:
      • <declaracion> ::= <asignacion>
      • <asignacion> ::= <identificador> = <expresion>
      • Esto deriva a entrada = image.img_to_array(entrada).
  • Sexta Producción (entrada = entrada.reshape((1, 224, 224, 3))):

    • <programa> ::= <declaracion> <programa>
    • Derivamos:
      • entrada = entrada.reshape((1, 224, 224, 3))
    • Usamos la regla:
      • <declaracion> ::= <asignacion>
      • <asignacion> ::= <identificador> = <expresion>
      • Esto deriva a entrada = entrada.reshape((1, 224, 224, 3)).
  • Séptima Producción (resultado = modelo.predict(entrada)):

    • <programa> ::= <declaracion> <programa>
    • Derivamos:
      • resultado = modelo.predict(entrada)
    • Usamos la regla:
      • <declaracion> ::= <asignacion>
      • <asignacion> ::= <identificador> = <expresion>
      • Esto deriva a resultado = modelo.predict(entrada).
  • Octava Producción (print(f"La imagen es un {categorias[resultado.argmax()]})):

    • <programa> ::= <declaracion> <programa>
    • Derivamos:
      • print(f"La imagen es un {categorias[resultado.argmax()]})
    • Usamos la regla:
      • <declaracion> ::= <print>
      • <print> ::= print ( <cadena> )
      • Esto deriva a print(f"La imagen es un {categorias[resultado.argmax()]}).

  • Tipos de analizadores sintácticos 

    Existen dos tipos de algoritmo para la realización de analizadores sintácticos  los cuales son los analizadores sintácticos descendentes y los analizadores sintácticos ascendentes.

    Análisis sintáctico por descenso: 

    Este analizador hace una búsqueda en profundidad retroceso, es decir, que puede hacer varios exámenes de la entrada. Presenta el inconveniente de que no es eficiente. 

    Análisis sintáctico por ascendencia:

    Analiza el código desde las partes más simples (los tokens) y construye una estructura jerárquica (como un árbol sintáctico) hacia arriba, hasta llegar a la unidad completa del programa. Este método es útil para manejar lenguajes complejos y sigue una estrategia conocida como derivación por la derecha. 

    (s/f-b)(S/f-b). Cartagena99.com. Recuperado el 20 de noviembre de 2024, de https://www.cartagena99.com/recursos/alumnos/apuntes/ININF2_M4_U3_T2.pdf

    Visualiza el siguiente video para que fortalezcas mas los conocimientos vistos.

    (Castillo, s/f), Castillo, A. [@andrescastillo6660]. (s/f). Compiladores analisis sintactico. Youtube. Recuperado el 28 de noviembre de 2024, de https://www.youtube.com/watch?v=44DctS7TfHw 

    ¡Crea tu página web gratis! Esta página web fue creada con Webnode. Crea tu propia web gratis hoy mismo! Comenzar