Está en la página 1de 7

Fases de la compilación y su relación con Lex y Yacc

Código fuente a=b+c*d (se distinguen 7 lexemas2)

Patrones Analizador Léxico Lex Patrones


Scanner Generador de analizadores

Tokens id1 = id2 + id3 * id4 léxicos (tokenizer)

Tabla de Analizador Sintáctico Yacc Gramática


símbolos Parser Generador de analizadores

= sintácticos

id1 +
Id2 * Árbol de sintaxis Abstracta AST
id3 id4

Síntesis o
Generador de código

load id3
Código generado mul id4
add id2
store id1
Los patrones en el diagrama anterior están en un archivo de texto. Son reglas para generar las
secuencias de caracteres que puede representar a un determinado componente léxico
(expresiones regulares, es decir, expresiones que describen un conjunto de cadenas o lexemas),

2
Lexema: Entiéndase en este contexto como una cadena de caracteres que concuerda con un patrón que describe
un componente léxico (valor de cadena). Ej. Lexema: “hola mundo” (incluidas las comillas), Componente léxico:
literal y Patrón: caracteres entre comillas. Sin embargo, en lingüística estrictamente lexema es: “Parte que se
mantiene invariable en todas las palabras de una misma familia; expresa el significado común a toda la familia
y puede coincidir o no con una palabra entera.”Ej. de la familia de palabras: panes, panadería, pancito, etc. El
lexema es pan y el resto son los que se denominan morfemas.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 13
ej. [0-9]* (cero o más dígitos que reconocerían cualquier número natural). Lex lee a estos
patrones y genera código en lenguaje C para crear un analizador léxico (scanner o tokenizer).
El analizador léxico encuentra coincidencia entre los lexemas de la cadena de entrada, respecto
de sus patrones, y los convierte en cadenas de tokens o components léxicos. Los tokens son
cadenas con referencias numéricas.
Cuando el analizador léxico encuentra identificadores en el flujo de entrada los introduce en
una tabla de símbolos, en esta también se puede contener los tipos de los datos (entero, real,
etc.) y la ubicación de cada variable dentro del fuente. Todas las referencias hechas a los
identificadores conforman el índice de la tabla símbolos.
La gramática en el diagrama anterior es un archivo de texto. Yacc3 leerá esta gramática y
generará el código en lenguaje C para crear un analizador sintáctico (o parser). El analizador
de sintaxis utiliza reglas gramaticales que le permiten analizar tokens desde el analizador léxico
y crear un árbol de sintaxis abstracta (AST).
El árbol de sintaxis abstracta impone una estructura jerárquica a los tokens. Tal es así, que la
precedencia y asociatividad de los operadores son relevantes en el árbol sintáctico y son las
que evitan la eventual ambigüedad.

Análisis Léxico
Esta fase de rastreo (o scanner), lee caracteres del programa fuente, identifica lexemas y los
convierte en tokens.
x = a + b; (id,x) (op,=) (id,a) (op,+) (id,b) (pu,;)
Los lexemas son los símbolos terminales de una gramática, es decir, una secuencia de uno o
más caracteres, que según su funcionalidad en un lenguaje, se los pueden clasificar en:
 palabras reservadas (void, int, return, etc.)
 identificadores (nombres de variables, constantes, funciones, etiquetas, etc.)
 operadores (+, -, *, /, etc.)
 literales: valores fijos (números enteros, hexadecimales, caracteres, cadenas, etc.)
 puntuación (coma, punto, punto y coma, paréntesis)

3
El código fuente tanto de Lex como de Yacc se encuentran en Unix para su compilación y ejecución. En
Windows existen varios clones pero sobresalen dos programas open source análogos, Flex y Bison. Asimismo,
existen herramientas modernas con prestaciones equivalentes como son AntLRx x NetBeans/Eclipse y Ply x
Phyton, PCCTS, DLG, Soucerer, etc.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 14
La identificación de estos lexemas dentro del código fuente es factible, porque se encuentran
separados por delimitadores (espacios, saltos de línea, retornos de carro, tabulaciones y
comentarios) que permiten identificarlos sucesivamente.
En cada caso, un lexema se corresponde con un cierto patrón de caracteres que el analizador
léxico utiliza para reconocerlo. Para dicho fin, es necesario generar un mecanismo
computacional que nos permita identificar el patrón de transición entre los caracteres de entrada.
Este mecanismo es posible crearlo a partir de un tipo específico de máquina abstracta, llamada
autómata finito.
Ej. de autómata finito que verificaría si un identificador en C está correctamente conformado
Identificador del C

Una vez comprendido el concepto de analizador léxico, debemos saber que este trabaja bajo
petición del analizador sintáctico, devolviendo un token conforme el analizador sintáctico lo va
necesitando para avanzar en la secuencia. Al analizador léxico se lo suele implementar como
una subrutina del analizador sintáctico. Cuando recibe la “solicitud del siguiente token”, el
analizador léxico lee los caracteres de entrada hasta identificar el lexema y entregar el token.

Por ejemplo, para una entrada:


posicion = inicial + velocidad * 60
la salida del analizador léxico podría ser :
<ID, 24> <OPASIGN,> <ID, 25> <OPSUM,> <ID, 26> <OPPROD,> <LITNUM, 60>

Análisis Sintáctico
El análisis sintáctico (parser) toma los tokens que le envía el analizador léxico y verifica su
estructura sintáctica, es decir, si con ellos se puede formar una sentencia válida

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 15
sintacticamete según la gramática del lenguaje, entendiendo a esta como el conjunto de reglas
formales que especifica como se construyen las sentencias en un lenguaje.

Si continuamos el ej. de la posición:

Expresión de
asignación

Expresión Id = Expresión +

ID (24)
posicion Expresión Id + Expresión *

ID (25)
inicial Expresión Id * Expresión Lit

ID (26)
LIT (27)
inicial
60
Los numeros 24, 25, 26 y 27 hacen referencia a lugares en la tabla de símbolos

Análisis Semántico
En esta fase no explicitada en el gráfico de Fases de la compilación, si estará presente en todos
los compiladores y es de vital importancia, ya que revisa semánticamente a las frases, reúne y
verifica información sobre compatibilidad de tipos, declaraciones repetidas, etc., brindando la
información necesaria a la fase posterior de síntesis o generación de código. En ella se utiliza
el AST determinado por la fase de análisis sintáctico.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 16
En la verificación de tipos controla si cada operador tiene operandos permitidos por la
especificación del lenguaje. Por ejemplo, las definiciones de muchos lenguajes de
programación requieren que el compilador indique un error si se usa un número real como
índice de un arreglo, cuando se aplique un operador binario a un número real, cuando se declare
dos veces una misma variable, cuando se quiere sumar un string con un entero, etc.

Errores de Programación

Se sabe que los programas pueden contener errores de muy diverso tipo. Los errores pueden
ser:

 Léxicos: como escribir mal un identificador. palabra clave u operador.


 Sintácticos: como una expresión aritmética con paréntesis no equilibrados.
 Semánticos: como un operador aplicado a un operando incompatible.
 Lógicos: como una llamada infinitamente recursiva.

Estrategias de recuperación de errores


a) Modo pánico: Al descubrir un error el analizador sintáctico desecha símbolos de entradas
de uno en uno hasta que encuentra alguno perteneciente al conjunto de componentes léxicos de
sincronización.
b) Recuperación a nivel de frase: Al descubrir un error el analizador sintáctico puede realizar
una corrección local de la entrada restante es decir puede sustituir un prefijo de la entrada
restante por alguna cadena que le permita continuar.
c) Producción de error: Si se tiene una idea básica de los errores comunes que se pueden
encontrar, se puede aumentar la gramática del lenguaje con las producciones que generan las
construcciones erroneas. Entonces se usa esta gramática aumentada con las producciones de
error, para construir el analizador sintáctico.
d) Correción global: Idealmente, un compilador debería hacer el menor número de cambios
posibles al procesar una cadena de entrada incorrecta. Existen algoritmos para elegir una
secuencia mínima de cambios para obtener una corrección global de menor costo. Dada una
cadena de entrada incorrecta x y la gramática G, estos algoritmos encontraran un árbol de
análisis sintáctico para una cadena relacionada, tal que el número de inserciones, supresiones y
modificaciones de componentes léxicos necesarios para transformar x en y, sea el mínimo
posible. Esto en la actualidad es solo de interes teórico.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 17
La síntesis
La síntesis se subdivide en tres fases generación de código intermedio, optimización de código
y generación de código de máquina.

Generación de código intermedio


El código intermedio debe ser fácil de producir y fácil de traducir al programa objeto. Puede
tener diversas formas. Una posible es la llamada código de tres direcciones (de memoria), que
consiste en una secuencia de intrucciones, cada una de las cuales involucra
 a lo sumo un operador (unario o binario), además de la asignación
 tres direcciones a lo sumo (las de los operandos y la del resultado)
Además, deberá generar nombres temporales para almacenar los resultados intermedios.
Continuando con el ejemplo de posición, la salida de esta fase podría ser:
temp1 := InToReal(60)
temp2 := id26 * temp1
temp3 := id25 + temp2
id24 := temp3

Optimización de código
Se trata en esta fase de mejorar el código, en el sentido de reducir la cantidad de recursos
(tiempo y memoria) necesarios. Algunas optimizaciones son triviales, como por ejemplo hacer
algunas transformaciones directamente en la compilación, en lugar de dejarlo para la ejecución
(sustituir InToReal(60) por 60.0). Otras pueden requerir un trabajo mucho mayor, pero mejorar
significativamente la eficiencia,
normalmente a costa de alejarse
bastante del código original,
como eliminar código inactivo
(inaccesible), eliminar variables
intermedias o dentro de un bucle
a las sentencias independientes
(que no son alteradas por el
bucle) relocalizarlas fuera de
éste. Muchos compiladores permiten elegir el nivel de optimización a realizar o no hacerla.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 18
Generación de código
En esta fase final se genera por fin el código objeto, normalmente código máquina (binario)
relocalizable (o ensamblador, en este caso requeriría además ensamblarlo). Se seleccionan
entonces posiciones de memoria relativas para almacenar las variables y cada sentencia del
código intermedio se traduce a una secuencia de instrucciones al microprocesador que ejecutará
la tarea. Por ejemplo:
MOVF 0068, R2
MULF #60.0, R2
MOVF 0060, R1
ADDF R2, R1
MOVF R1, 0064

PL 27 03 90’

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 19

También podría gustarte