Una Herramienta para el Análisis Léxico: Lex



Documentos relacionados
Generador de analizadores léxicos FLEX

Modelos de Computación I. Ingeniería Informática

FLEX: A FAST LEXICAL ANALYZER GENERATOR

LEX. Las definiciones y subrutinas son opcionales. El segundo %% es opcional pero el primer %% indica el comienzo de las reglas.

Lex. Lex. Ing. Adrian Ulises Mercado Martínez. Enero 30, Ing. Adrian Ulises Mercado Martínez Lex Enero 30, / 27

1. Funcionamiento de lex

PROCESADORES DE LENGUAJES

FLEX: A FAST LEXICAL ANALYZER GENERATOR

Bison. Introducción. Índice. Introducción Uso de Bison con Flex. Formato del fichero de especificación de Bison

Analista Universitario en Sistemas. Taller de Programación II. Instituto Politécnico Superior. Trabajo Final

Yacc/Bison. Introducción

YACC. Los símbolos terminales que la gramática empleará. El axioma o símbolo inicial de la gramática. %token. %start

8- LEX-Expresiones regulares

ANÁLISIS LÉXICO EXPRESIONES REGULARES

10 Introducción a BISON/YACC

Qué es Lex? Busca concordancias de un conjunto de expresiones regulares y un archivo de entrada y ejecuta acciones asociadas.

Construcción de un analizador léxico para ALFA con Flex. Construcción de un analizador léxico para ALFA con Flex. Índice (I)

Seminario de introducción a Flex

Lenguaje C. República Bolivariana de Venezuela Fundación Misión Sucre Aldea Fray Pedro de Agreda Introducción a la Programación III

Elementos de un programa en C

Lex (flex,... ) Generación de analizador léxico p.1

Teoría de Autómatas y Lenguajes Formales, IS17 Ingeniería Técnica en Informática de Sistemas. Práctica 1: Introducción al Analizador Léxico FLEX

DESARROLLO DE LA PRÁCTICA DE ANÁLISIS SINTÁCTICO

Construcción de un analizador léxico para ASPLE con Lex/Flex. Construcción de un analizador léxico para ASPLE con Lex/Flex

Práctica 4 Análisis LALR para milenguaje y construcción de un traductor de milenguaje

Nombre y apellidos: Grupo (mañana - tarde). Táchese lo que no proceda.

Tema 3. Estructuras de control

Apunte Laboratorio ALPI - El lenguaje de programación Pascal

Construcción de una calculadora con Lex/Flex y Yacc/Bison

Funciones Definición de función

Seminario de introducción a Bison

Teoría de Autómatas y Lenguajes Formales LEX BISON Dr. Eric Jeltsch F. Introducción para los laboratorios y actividades relacionadas.

Yacc/Bison. Índice. Construcción del programa objetivo Flujo de control de las funciones yylex() e yyparse()

TEMA 2. EL LENGUAJE C. ELEMENTOS BÁSICOS

Programación en C. Algoritmo y Estructura de Datos. Ing. M. Laura López. Programación en C

TEÓRICO ID ; = [ ], $

Práctica No. 4 Programas en Lex

Lenguaje C, tercer bloque: Funciones

ANÁLISIS LÉXICO Ing. Ronald Rentería Ayquipa

1. Presentación del lenguaje C Creado en 1972 por D. Ritchie Lenguaje de propósito general Portátil o transportable (generalmente) Inicialmente de niv

Procesos e Hilos en C

Unidad III Análisis Léxico. M.C. Juan Carlos Olivares Rojas

Introducción a los generadores de analizadores léxicos y sintácticos FLEX y BISON: Implementación de una pequeña calculadora

Todo programa en 'C' consta de una o más funciones, una de las cuales se llama main.

Apellidos: Nombre: Matrícula: Examen Programación para Sistemas Grado en Ingeniería Informática (2009)

PRÁCTICAS DE PROCESADORES DEL LENGUAJE CURSO 2008/2009

Funcionamiento del A.L.

Uso de la herramienta YACC

TEORÍA. definition TAG. PROPERTIES ID expr EXTENDS ID FIN_TAG. DELAYED : expr

roducción a la programación con el lenguaje C usando el entorno de trabajo Dev-C. Nociones básicas de programación

Diagrama de transiciones del autómata. Tabla de transiciones

1. Acciones en Yacc. %{ #include <stdio.h> yyerror (char *s) { fprintf (stderr, %s\n, s) ; } %} %% : S \n {printf ( Correcto\n );} ; : ( S ) S

Tema 2: Lenguajes de Programación de Sistemas: C

ANÁLISIS SINTÁCTICO II GENERADOR DE ANALIZADORES SINTÁCTICOS ASCENDENTES

Índice. Estructuras de datos en Memoria Principal: Vectores. Matrices. Cadenas de caracteres. Estructuras. Estructuras de datos en Memoria Externa:

FORMATO DEL FICHERO CON LA TABLA DE SÍMBOLOS

YACC (Yet Another Compiler Compiler) LALR(1) Parser Generator

Programación : C (6)

Lenguaje de Programación: C++ Directivas al preprocesador

Programación I Funciones

Departamento de Electrónica

Analizador Sintáctico RECURSIVO

Tema 5 Tabla de Símbolos

Teoría de Autómatas y Lenguajes Formales Práctica 4

PRÁCTICA. Apellidos, Nombre: Calificación:

El lenguaje C. 3. Una instrucción que se ejecutará si la condición es verdadera.

El lenguaje de programación C - El primer programa -

Para C un chero no es más que una porción de almacenamiento

UNIVERSIDAD NACIONAL DE JUJUY FACULTAD DE INGENIERÍA APUNTES DE CÁTEDRA EL METACOMPILADOR BISON INTERFAZ FLEX - BISON

TEORÍA. N T f n h g $ S S P f C n S S P f C n S S C h P n S S C h P n S S P P f P n f P

PRÁCTICA DE PROCESADORES DE LENGUAJE EVALUACIÓN ORDINARIA CURSO 2009/2010 OBJETIVO DE LA PRÁCTICA

HERRAMIENTAS YACC Y BISON

ANALIZADOR LEXICO LEX

LABORATORIO DE PROCESADORES DE LENGUAJE Curso: Práctica 2: Analizador léxico/sintáctico/semántico con Flex y Bison

TEMA 7: Ficheros. TEMA 7: Ficheros Concepto de fichero

Examen Teórico (1/3 de la nota final)

FUNCIONES. Identificador valido. Tipo-Funcion Identificador_de_la_funcion (Tipo par1,tipo par2 )

Sintaxis de los aspectos generales de un lenguaje de programación

Programación estructurada (Introducción a lenguaje C)

Introducción a C++ y Code::Blocks

Programación I Teoría I.

Introducción al lenguaje C

Es la estructura que permite ejecutar los comandos solamente si se cumple una determinada condición. La sintaxis más usual:

$0 Representa al parámetro cero o nombre del programa $1 Representa al parámetro uno $2 Representa al parámetro dos

Tema ADQUISICIÓN Y TRATAMIENTO DE DATOS. Departamento de Ciencias de la Computación e IA. Subprogramas en C

FLEX: Un generador de analizadores léxicos. AT&T Lex (más común en UNIX) MKS Lex (MS-Dos) Flex Abraxas Lex Posix Lex ScanGen JLex...

Analizador Léxico en LEX

Carlos Montenegro. Programación Orientada a Objetos Proyecto Curricular de Ingeniería de Sistemas

TEMA 2. LENGUAJE C. CONCEPTOS BÁSICOS Y PROGRAMACIÓN ELEMENTAL.

Introducción general al Lenguaje C (2010/2011)

Examen de Traductores, Intérpretes y Compiladores. Convocatoria ordinaria de Junio de 2007 er. 3 Curso de I.T. Informática de Sistemas.

05 Análisis léxico I Compiladores - Profr. Edgardo Adrián Franco Martínez

Ficheros conceptos. Manejo de ficheros en C. Apertura del fichero Función fopen: nombre del fichero. Apertura del fichero Función fopen

Área de Arquitectura y Tecnología de Computadores. Universidad Carlos III de Madrid SISTEMAS OPERATIVOS. Ejercicio. Programación en bash

Transcripción:

Una Herramienta para el Análisis Léxico: Lex Alejandro Viloria Lanero (aviloria@infor.uva.es) Teoría de Autómatas y Lenguajes Formales Universidad de Valladolid Como hemos ido viendo, el shell de los sistemas UNIX/Linux nos ofrece varias herramientas para el tratamiento masivo de la información contenida en ficheros de texto, que nos permiten hacer búsquedas, reemplazos y múltiples procesamientos de más alto nivel a un nivel de campo dentro de la línea. Todas ellas se apoyaban en las expresiones regulares para llevar a cabo su cometido, y permitían encadenar su salida a modo de tubería con otras herramientas, para que de esta forma se aumentase su potencia de procesamiento. Pero a menudo todo esto no es suficiente. En ocasiones se requiere poder realizar acciones de más alto nivel o bien mucho más complejas en cada ocurrencia de una expresión regular. Lex va a permitirnos definir nuestras propias acciones en un lenguaje mucho más común: C. Introducción Lex es una herramienta de los sistemas UNIX/Linux que nos va a permitir generar código C que luego podremos compilar y enlazar con nuestro programa. La principal característica de Lex es que nos va a permitir asociar acciones descritas en C, a la localización de las Expresiones Regulares que le hayamos definido. Para ello Lex se apoya en una plantilla que recibe como parámetro, y que deberemos diseñar con cuidado. Internamente Lex va a actuar como un autómata que localizará las expresiones regulares que le describamos, y una vez reconocida la cadena representada por dicha expresión regular, ejecutará el código asociado a esa regla. Externamente podemos ver a Lex como una caja negra con la siguiente estructura: plantilla.l Lex yy.lex.c Compilador + Enlazador (cc) a.out (fich. Ejecutable) Otros módulos de nuestro programa En las siguientes secciones veremos todo lo correspondiente a la descripción de la plantilla, y el proceso de compilación.

La plantilla de Lex La plantilla en la que Lex se va a apoyar para generar el código C, y donde nosotros deberemos describir toda la funcionalidad requerida, va a ser un fichero de texto plano con una estructura bien definida, donde iremos describiendo las expresiones regulares y las acciones asociadas a ella. La estructura de la plantilla es la siguiente: Declaraciones Reglas Procedimientos de Usuario Se compone de tres secciones con estructuras distintas y claramente delimitadas por una línea en la que lo único que aparece es el carácter doble %. Las secciones de Declaraciones y la de Procedimientos de Usuario son opcionales, mientras que la de Reglas es obligatoria (aunque se encuentre vacía), con lo que tenemos que la plantilla más pequeña que podemos definir es: Esta plantilla, introducida en Lex, generaría un programa C donde el contenido de la entrada estándar sería copiado en la salida estándar por la aplicación de las reglas y acciones por defecto. El formato de cada una de las secciones lo discutiremos en los apartados siguientes. Lex va a actuar como un pre-procesador que va a trasformar las definiciones de esta plantilla en un fichero de código C.

La Sección de Declaraciones En la sección de Declaraciones podemos encontrarnos con 3 tipos de declaraciones bien diferenciados: - Un bloque donde le indicaremos al pre-procesador que lo que estamos definiendo queremos que aparezca tal cual en el fichero C generado. Es un bloque de copia delimitado por las secuencias % y % donde podemos indicar la inclusión de los ficheros de cabecera necesarios, o la declaración de variables globales, o declaraciones procedimientos descritos en la sección de Procedimientos de Usuario: % /* Este bloque aparecerá tal cual en el fichero yy.lex.c */ #include <stdio.h> #include <stdlib.h> #define VALUE 33 int nl, np, nw % - Un bloque de definición de alias, donde pondremos nombre a algunas de las expresiones regulares utilizadas. En este bloque, aparecerá AL COMIENZO DE LA LÍNEA el nombre con el que bautizaremos a esa expresión regular y SEPARADO POR UN TABULADOR (al menos), indicaremos la definición de la expresión regular. Para utilizar dichos nombres en vez de las expresiones regulares debemos escribirlos entre llaves: entero [0-9]+ real entero.entero real2 real[ee][\+\-]?entero numero entero real real2 - Un bloque de definición de las condiciones de arranque del autómata. (Ver apartado de Condiciones sobre Reglas). %s normal Estos bloques pueden aparecer en cualquier orden, y pueden aparecer varios de ellos a lo largo de la sección de declaraciones. Recordemos que esta sección puede aparecer vacía.

La Sección de Reglas En la sección de Reglas sólo permitiremos un único tipo de escritura. Las reglas se definen como sigue: Exp.reg.tab-> acciones escritas en C AL COMIENZO DE LA LÍNEA se indica la expresión regular, seguida inmediatamente por uno o varios TABULADORES, hasta llegar al conjunto de acciones en C que deben ir encerrados en un bloque de llaves. A la hora de escribir las expresiones regulares podemos hacer uso de los acrónimos dados en la sección de Declaraciones, escribiéndolos entre llaves, y mezclándolos con la sintaxis general de las expresiones regulares. Si las acciones descritas queremos que aparezcan en varias líneas debido a su tamaño, debemos comenzar cada una de esas líneas con al menos un carácter de tabulación. Si queremos incorporar algún comentario en C en una o varias líneas debemos comenzar cada una de esas líneas con al menos un carácter de tabulación. [a-za-z_][a-za-z0-9_]* printf ( ID: %s, yytext); numero printf ( NUM: %s, yytext); ^numero printf ( NUM al ppio linea ); Como normas para la identificación de expresiones regulares, Lex sigue las siguientes: - Siempre intenta encajar una expresión regular con la cadena más larga posible, - En caso de conflicto entre expresiones regulares (pueden aplicarse dos o más para una misma cadena de entrada), Lex, se guía por estricto orden de declaración de las reglas. Existe una regla por defecto, que es:. ECHO; es decir, que en caso de que la entrada no encaje con ninguna de las reglas, se aplicará esta: cualquier carácter -> lo imprime en la salida estándar. Si queremos modificar este comportamiento tan solo debemos sobrescribir la regla. con la acción deseada ( si no queremos que haga nada)..

La Sección de Procedimientos de Usuario En la sección de Procedimientos de Usuario escribiremos en C sin ninguna restricción aquellos procedimientos que hayamos necesitado en la sección de Reglas. Todo lo que aparezca en esta sección será incorporado tal cual al final del fichero yy.lex.l. No debemos olvidar como concepto de C, que si la implementación de los procedimientos se realiza después de su invocación (en el caso de Lex, lo más probable es que se hayan invocado en la sección de reglas), debemos haberlos declarado previamente. Para ello no debemos olvidar declararlos en la sección de Declaraciones. % int func1 (int param); void proc1 (); % Como función típica a ser descrita en una plantilla Lex, aparece el método principal (main). Si no se describe ningún método main Lex incorpora uno por defecto donde lo único que se hace es fijar el fichero de entrada como la entrada estándar e invocar a la herramienta para que comience su procesamiento: int main () yyin = stdin; yylex (); Un ejemplo de método main típico es aquel que acepta un nombre de fichero como fichero de entrada: int main (int argc, char *argv[]) if (argc == 2) yyin = fopen (argv[1], rt ); if (yyin == NULL) printf ( El fichero %s no se puede abrir\n, argv[1]); exit (-1); else yyin = stdin; yylex (); return 0;

Las Variables, Funciones, Procedimientos y Macros internas de Lex Como hemos ido reflejando en algunos ejemplos, Lex incorpora algunas variables, funciones, procedimientos y macros que nos permiten realizar de una forma más sencilla todo el procesamiento. Como variables, las más utilizadas son: Variable Tipo Descripción yytext char * o char [] Contiene la cadena de texto del fichero de entrada que ha encajado con la expresión regular descrita en la regla. yylength int Longitud de yytext. yylength = strlen (yytext). yyin FILE * Referencia al fichero de entrada. yyval yylval struct Contienen la estructura de datos de la pila con la que trabaja YACC. Sirve para el intercambio de información entre ambas herramientas. Como métodos (funciones y procedimientos), tenemos: Método yylex () yymore () yyless (n) yyerror () yywrap () Descripción Invoca al Analizador Léxico, el comienzo del procesamiento. Añade el yytext actual al siguiente reconocido. Devuelve los n últimos caracteres de la cadena yytext a la entrada. Se invoca automáticamente cuando no se puede aplicar ninguna regla. Se invoca automáticamente al encontrar el final del fichero de entrada. Los procedimientos yyerror () e yywrap () pueden ser reescritos en la sección de Procedimientos de Usuario, cambiando de esta forma, el comportamiento ante la localización de errores, o del final del fichero de entrada. Como macros y directivas característicos, existen: Nombre ECHO REJECT BEGIN END Descripción Escribe yytext en la salida estandar. ECHO = printf ( %s, yytext) Rechaza la aplicación de la regla. Pone yytext de nuevo en la entrada y busca otra regla donde encajar la entrada. REJECT = yyless (yylength) + Otra regla Fija nuevas condiciones para las reglas. (Ver Condiciones sobre Reglas). Elimina condiciones para las reglas. (Ver Condiciones sobre Reglas).

Fijando Condiciones a las Reglas Lex permite añadir condiciones de ejecución a las reglas. Esto es, condiciones bajo las cuales una regla puede ser ejecutada. Esto es muy útil sobre todo cuando se da el caso de que unas reglas han de aplicarse bajo ciertas condiciones y no bajo otras. Por ejemplo la detección de palabras clave o de identificadores en un fichero de entrada con formato C: Si las palabras clave están dentro de un bloque de comentario o dentro de un par de comillas dobles (definición de constante de cadena), no han de interpretarse como palabras clave o identificadores, sino como componentes del comentario o de la constante: if (i < 3) a++; /* if (i < 3) a++; */ printf ( if (i < 3) a++;\n ); Para ello, Lex incorpora la posibilidad de definir estas condiciones a las reglas, y los métodos (directivas) para cambiar de unas condiciones a otras. Para definir que una regla está sujeta a una determinada condición, hay que anteceder dicha condición a la regla poniéndola entre pares <> : <cond1 [, cond2]*>exp.reg. acciones Para indicar explícitamente que se ha de entrar en una condición, se utiliza la directiva BEGIN cond1 [, cond2]*; mientras que para salir de una condición se utiliza la directiva END cond1 [, cond2]*. id ECHO; /* BEGIN comentario; <comentario> */ END comentario; /* = BEGIN 0; */ <comentario>. Las reglas que no son precedidas de ninguna condición se presupone que se ejecutan solamente baja la condición 0 (sin condiciones). La invocación de BEGIN cambia la lista de condiciones por completo a la indicada (no las suma). Haciendo BEGIN 0, se vuelve al estado donde las reglas no necesitan ser precedidas de condiciones. Para indicar las condiciones iniciales del procesamiento, se incorpora una sentencia con el formato siguiente en la sección de Declaraciones. %start cond1 [, cond2]* /* %start = %s */

El Proceso de Compilación Una vez escrita la plantilla, invocamos a la herramienta mediante el comando de shell: $ lex plantilla.l Esto nos generará un fichero C, denominado yy.lex.c que contiene todo el código necesario para compilar nuestra aplicación. Luego sólo queda compilar con las librerías adecuadas: $ cc yy.lex.l -ll En los sistemas gnu (Línux), existen ambas herramientas (lex y cc) pero denominadas con los nombres flex, y gcc aunque suelen incorporar enlaces a ellas a través de los nombres tradicionales. Ejemplos El siguiente ejemplo de plantilla, implementa el comando wc de lis sistemas UNIX / Linux, que nos permite contar los caracteres, palabras y líneas de un fichero. % #include <stdio.h> int nc, np, nl; % /*----- Sección de Reglas ----------------*/ [^ \t\n]+ np++; nc += yylength; [ \t]+ nc += yylength; $ nl++; nc++; /*----- Sección de Procedimientos --------*/ int main (int argc, char *argv[]) if (argc == 2) yyin = fopen (argv[1], rt ); if (yyin == NULL) printf ( El fichero %s no se puede abrir\n, argv[1]); exit (-1); else yyin = stdin; nc = np = nl = 0; yylex (); printf ( %d\t%d\t%d\n, nc, np, nl); return 0;

Otro ejemplo interesante es el siguiente: Escribir un fichero HTML a partir de un fichero C, resaltando en otro color las palabras clave, las variables, las constantes numéricas y de cadena, y los comentarios. % #include <stdio.h> #define rojo #define verde #define azul #define morado #define negro 0xFF0000 0x00FF00 0x0000FF 0xFF00FF 0x000000 void escribircabecera (char *nombre_fichero); void cambiaclr (long color); void nuevalinea (); void escribirfin (); % id [a-za-z_][a-za-z0-9_]* entero [0-9]+ real entero.entero real2 real[ee][\+\-]?entero numero entero real real2 pclaves for while do if break return /*----- Sección de Reglas ----------------*/ id cambiaclr (azul); ECHO; cambiaclr (negro); numero cambiaclr (verde); ECHO; cambiaclr (negro); \ cambiaclr (verde); ECHO; BEGIN cadena; <cadena>\ ECHO; cambiaclr (negro); END cadena; <cadena>. ECHO; /* cambiaclr (rojo); ECHO; BEGIN coment; <coment> */ ECHO; cambiaclr (negro); END coment; <coment>. ECHO; pclaves cambiaclr (morado); ECHO; cambiaclr (negro); $ nuevalinea ();. ECHO; /*----- Sección de Procedimientos --------*/ int main (int argc, char *argv[]) if (argc!= 2) printf ( Se necesita especificar un fichero de entrada\n ); exit (-1); yyin = fopen (argv[1], rt ); if (yyin == NULL) printf ( El fichero %s no se puede abrir\n, argv[1]); exit (-1); escribircabecera (argv[1]); yylex (); escribirfin (); return 0;

void escribircabecera (char * fichero) printf ( <head>\n<title>%s</title>\n</head>\n, fichero); printf ( \n<body>\n \n); void cambiaclr (long color) printf ( <font color=%x>, color); void nuevalinea () printf ( \n<br>\n ); void escribirfin () printf ( \n</body> \n);

Lex como un Analizador Léxico Lex puede verse desde otro punto de vista además de cómo una herramienta que nos permite ejecutar acciones tras la localización de cadenas de entrada que confrontan con expresiones regulares. Lex puede ser visto como la primera etapa necesaria a la hora de elaborar un compilador, un intérprete, un emulador o cualquier otro tipo de herramienta que necesite procesar un fichero de entrada para poder cumplir su misión. Supongamos que queremos diseñar una calculadora. Para ello tomaremos las expresiones a evaluar de un fichero de entrada (o de la misma entrada estándar). Diseñar un programa en C que tome una expresión de la entrada estándar y la evalúe, no es nada sencillo. Pensad que la entrada puede ser algo sencillo o algo tan complejo como la siguiente expresión: sqrt (2/3.141592654) * sin (0.707) Lo primero que deberemos hacer es reconocer los elementos que forman dicha entrada. Es decir, las constantes numéricas, los operadores, las funciones predefinidas (seno, coseno ) A estos elementos individuales que llevan asociado un sentido propio, los denominaremos tokens. Los tokens nos dan toda la información necesaria de este primer nivel, pues lo mismo nos da que la constante numérica reconocida sea un 3 que un 300, el procesamiento va a ser el mismo. Lo importante es que es una constante numérica (token). Esta etapa la podemos hacer con Lex sin ningún problema tal y como hemos visto. Tan solo debemos definir las reglas necesarias para reconocer esos tokens. A esta primera etapa se le da el nombre de Análisis Léxico, debido a que lo que el procesamiento fundamental se realiza en base a la detección de los lexemas (tokens) significativos. Pero estos elementos individuales (token) no nos aportan toda la información necesaria para llevar a cabo el procesamiento final. Si nos fijamos sólo en los tokens, nos falta información: Cómo se combinan los elementos? Es posible la siguiente secuencia +*2/(-4-*3)4sin?. Desde el punto de vista de Lex, podemos reconocer todos los tokens correctamente, pero en cambio la sintaxis de la expresión es incorrecta. No tiene sentido. Necesitamos entonces una segunda etapa que nos informe sobre la combinación de los elementos de entrada, y les de una interpretación según su combinación. Esta segunda etapa recibe el nombre de Análisis Sintáctico, donde lo importante el la estructura que tienen los datos (token) en la entrada. Dicha estructura la vamos a definir entorno a un conjunto de reglas especiales, al que denominaremos gramática. La herramienta que nos va a permitir llevar a cabo la implementación de esta segunda etapa va a ser YACC, que al igual que Lex, es un generador de código C basado en una plantilla similar a la de Lex, pero que va a permitirnos codificar estas nuevas reglas de combinación de los elementos. Ambas etapas están muy interrelacionadas. Tal y como se ve en el esquema siguiente.

A Analisis Léxico Get_Next_Token () token Analisis Sintáctico LEX YACC Fichero de entrada Tabla de Símbolos token token A continuación veremos cómo es el funcionamiento del sistema global. Para empezar diremos que el control del flujo pasa a la herramienta de Análisis Sintáctico, que es quien va a decidir lo que se hace en cada momento. El analizador sintáctico va a solicitar al analizador léxico el siguiente token de la entrada. Con él va a poder decidir qué regla aplicar en cada caso, o bien, si no se puede aplicar ninguna regla, decidir que lo que ha ocurrido es un error sintáctico (gramatical). El analizador léxico va a proporcionar cada token de la entrada bajo la petición del analizador sintáctico. Para ello el primer cambio que hay que hacer en la plantilla de Lex es incorporar sentencias de return al final de cada regla que defina un token 1 : exp.reg acciones; return token; Además del token, es importante que el analizador léxico mantenga otra serie de características, como por ejemplo el texto asociado al token (la cadena reconocida) porque aunque lo importante para el analizador sintáctico es la naturaleza de las entradas (tokens), no debemos olvidar que el resultado final ha de tener en cuenta qué valor es con el que hay que trabajar, o qué variable es a la que hacemos referencia Para ello entra en juego la Tabla de símbolos. En ella el analizador léxico deberá ir anotando cada nuevo símbolo reconocido, junto con sus propiedades (su naturaleza -> token, etc.) De esta forma, si un símbolo ya ha sido reconocido previamente, aparecerá en la tabla de símbolos y podemos adjudicarle la misma entrada. Un ejemplo sencillo donde se ilustra el comportamiento del sistema y de la tabla de símbolos es el siguiente: int a; a = 0; 1) El analizador sintáctico (YACC) pide un token, y el analizador léxico (Lex) devuelve el token TOKEN_INT (palabra clave) 1 En las herramientas que estamos utilizando los token se representan mediante constantes numéricas con valores por encima de 256. Cuando veamos YACC veremos que hay una forma de conseguir un listado con asignaciones automáticas de estos valores que podremos utilizar.

2) YACC pide el siguiente token (TOKEN_ID) y lo encaja en su gramática. YACC ya sabe que el ID reconocido es de tipo INTEGER. Puede ir a la tabla de símbolos y modificar su propiedad type (por ejemplo) y asociarle el tipo entero. De esta forma cada vez que se haga referencia a a sabremos que es un TOKEN_ID y es de tipo INTEGER. Y si en algún momento se intenta operar con a utilizando otro tipo de datos, podemos saber que se comete un error. Este error es un error semántico, no sintáctico (la sintaxis gramatical puede ser correcta), pero ambos tipos de análisis pueden llevarse a cabo simultáneamente como se puede ver. La estructura de la tabla de símbolos depende del tipo de procesamiento que queramos realizar. Entre sus campos podemos hacernos una idea de cuáles nos van a poder ser útiles para el procesamiento: Campo Símbolo Tipo léxico Tipo sintáctico Tipo de retorno Contexto Descripción Nombre del símbolo reconocido (p.e. a ) Tipo de token (p.e. TOKEN_ID ) Tipo lógico asignado por el contexto (p.e. INTEGER) En caso de funciones o arreglos, necesitaremos saber que tipo devuelve. Contexto sobre el cual se ha definido (p.e. la función donde se ha definido) Valor del símbolo Si estamos realizando un emulador o un intérprete, necesitaremos contar con el valor que se le ha asignado a cada símbolo. (p.e. 0 ) Como hemos visto en el ejemplo, la tabla de símbolos es accesible a todos los analizadores, y cada uno va a tomar de ella los datos que necesita en cada caso, y va a aportar su granito de arena en la definición completa de ese símbolo. Con respecto a qué símbolos han de introducirse en la tabla, diremos que tampoco hay una regla concreta. Podemos introducirlos todos, incluso los operadores o los signos de puntuación, o bien sólo introducir aquellos que nos sean útiles (identificadores y/o constantes). Es recomendable que la tabla de símbolos esté indexada para poder realizar las búsquedas más rápidamente. Para ello debemos de tener en cuenta que un símbolo puede ser distinto aunque tenga el mismo nombre, si se define bajo contextos distintos (una variable global y una redefinición dentro de una función), con lo que la clave de búsqueda debería contemplar el contexto como componente junto con el nombre del símbolo.