Unidad 4: IMPLEMENTACION DE ANALIZADORES LÉXICO/ SINTÁTICO
EJEMPLO DE LA TABLA DE SIMBOLO
Fichero Ejemplo.lex
%%
[0-9]+ {
yylval.numero = atoi(yytext);
return NUMERO;
}
":=" { return ASIG; }
[a-zA-Z][a-zA-Z0-9]*{
yylval.ptr_simbolo = buscar(t,yytext);
if (yylval.ptr_simbolo == NULL)
{
yylval.ptr_simbolo=(simbolo *) malloc(sizeof(simbolo));
strcpy(yylval.ptr_simbolo->nombre, yytext);
yylval.ptr_simbolo->valor=0;
insertar(&t, yylval.ptr_simbolo);
}
return ID;
}
[ \t]+ {;}
.|\n {return yytext[0];}
EJEMPLO2Y.YAC
%{
#include "TablaSimbolo.c"
simbolo * t;
%}
%union {
int numero;
simbolo * ptr_simbolo;
}
%token <numero> NUMERO
%token <ptr_simbolo> ID
%token ASIG
%type <numero> expr asig prog
%start prog
%left '+'
%left '*'
%%
prog : prog asig '\n' { printf("Asignaciones efectuadas\n");}
| prog expr '\n' { printf("%d\n",$2);}
| prog error '\n' { yyerrok;}
|
;
asig : ID ASIG expr {
$$ = $3;
$1->valor = $3;
}
| ID ASIG asig {
$$ = $3;
$1->valor = $3;
}
;
expr : expr '+' expr {$$ = $1 + $3;}
| expr '*' expr {$$ = $1 * $3;}
| ID {$$ = $1->valor; }
| NUMERO {$$ = $1;}
;
%%
#include "Ejemplo.c"
#include "errorlib.c"
void main()
{ t = crear();
yyparse ();
imprimir(t);
}
TablaSimbolo.c
#include <stdlib.h>
#include <stdio.h>
typedef struct nulo
{
struct nulo * sig;
char nombre [20];
int valor;
} simbolo;
simbolo * crear()
{
return NULL;
};
void insertar(p_t,s)
simbolo **p_t;
simbolo * s;
{
s->sig = (*p_t);
(*p_t) = s;
};
simbolo * buscar(t,nombre)
simbolo * t;
char nombre[20];
{
while ( (t != NULL) && (strcmp(nombre, t->nombre)) )
t = t->sig;
return (t);
};
void imprimir(t)
simbolo * t;
{
while (t != NULL)
{
printf("%s\n", t->nombre);
t = t->sig;
}
};
CONSIDERACIONES SOBRE LA TABLA DE SIMBOLOS
CONCEPTO
Conforme van apareciendo nuevas declaraciones de identificadores, el analizador léxico,
o el analizador sintáctico según la estrategia que sigamos, insertará nuevas entradas en la
tabla de símbolos, evitando siempre la existencia de entradas repetidas.
CARACTERÍSTICAS
La tabla de símbolos puede iniciarse con cierta información útil, tal como:
- Constantes: PI, E, etc.
- Funciones de librería: EXP, LOG, etc.
- Palabras reservadas. Esto facilita el trabajo al lexicográfico, que tras reconocer
un identificador lo busca en la tabla de símbolos, y si es palabra reservada
devuelve un token asociado. Bien estructurado puede ser una alternativa más
eficiente al lex tal y como lo hemos visto (hash perfecto).
INFORMACIÓN SOBRE DE IDENTIFICADORES DE USUARIO
CONCEPTO
La información que el desarrollador decida almacenar en esta tabla dependerá de las características concretas del traductor que esté desarrollando
CARACTERÍSTICAS
Tipo del elemento. Cuando se almacenan variables, resulta fundamental conocer el tipo de datos a que pertenece cada una de ellas, tanto si es primitivo como si no, con objeto de poder controlar que el uso que se hace de tales variables es coherente con el tipo con que fueron declaradas.
Dirección de memoria en que se almacenará su valor en tiempo de ejecución. Esta dirección es necesaria, porque las instrucciones que referencian a una variable deben saber donde encontrar el valor de esa variable en tiempo de ejecución con objeto de poder generar código máquina, tanto si se trata de variables globales como de locales
Nombre del elemento. El nombre o identificador puede almacenarse limitando o no la longitud del mismo.
Valor del elemento. Cuando se trabaja con intérpretes sencillos, y dado que en un intérprete se solapan los tiempos de compilación y ejecución, puede resultar más fácil gestionar las variables si almacenamos sus valores en la tabla de símbolos.
Número de dimensiones. Si la variable a almacenar es un array, también pueden almacenarse sus dimensiones.
Tipos de los parámetros formales. Si el identificador a almacenar pertenece a una función o procedimiento, es necesario almacenar los tipos de los parámetros formales para controlar que toda invocación a esta función sea hecha con parámetros reales coherentes.
INTEGRACION DE UN ANALIZADOR LÉXICO / SINTÁCTICO
CONCEPTO
La integración permite obtener un compilador mucha más complejo.Se puede integrar cada uno de los procesos de los metacompiladores, Ahorra de manera abismal la generación de código al momento de desarrollar un compilador.
CARACTERÍSTICAS
Soporte completo con caracteres unicode
Permite generar analizadores léxicos rápidamente
Tiene una sintaxis cómoda de manipular y fácil de interpretar.
Es un meta-compilador que permite generar rápidamente analizadores léxicos que se integran con Java
IMPLEMENTACIÓN DE ANALIZADOR LÉXICO SINTACTICO
CONCEPTO
Hasta ahora hemos visto cómo especificar los lenguajes asociados a las diferentes categorías léxicas. Sin embargo, el analizador léxico no se utiliza para comprobar si una cadena pertenece no a un lenguaje. Lo que debe hacer es dividir la entrada en una serie de componente léxicos, realizando en cada uno de ellos determinadas acciones. Algunas de estas acciones son: comprobar alguna restricción adicional (por ejemplo que el valor de un literal entero este dentro de un rango), preparar los atributos del componente y emitir u omitir dicho componente. Así pues, la especificación del analizador léxico deber ́a incluir por cada categoría léxica del lenguaje el conjunto de atributos y acciones asociadas.
CARACTERÍSTICAS
Considerar las ER's de las definiciones regulares de cada categoría léxica.
Añadir otra ER con lo necesario para tratar de separadores, comentarios, reconocer el final del fichero, etc.
Se usa la definición sintáctica directamente para implementarlo (no hace falta llegar a diseñar el autómata a pila que reconoce el lenguaje)
La implementación es manual, usando la gramática incontextual y las restricciones contextuales sólo como “guías”
Dada la secuencia de componentes léxicos de entrada decide si el programa que representa es válido o no en el lenguaje propuesto.
Se usa la definición léxica (expresiones regulares) para diseñar un reconocedor del lenguaje (autómata finito determinista)