JP02 - Analisis Lexicografico

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 6

Analisis Lexicografico

El análisis lexicográfico o análisis léxico consiste en el proceso a través del cual el traductor
recibe los símbolos desde el programa fuente.

El programa fuente (PF) es producido por el usuario a través de un software conocido como
editor. Dicho editor usa un formato interno que posibilita su edición visual, específicamente
el editor marca el final de cada línea y el inicio de la siguiente línea a través de 2 caracteres
de control que son CR y LF (códigos ascii 0D y 0A). Estos dos caracteres son ingresados
cuando presionamos la tecla ENTER.

Por ejemplo cuando se escribe el siguiente programa se tendrá el siguiente contenido:

main() {
cout << 4; ---->
}

El resultado de un editor es básicamente una cadena de caracteres, esta cadena de


caracteres será enviada al traductor para su traducción pero el traductor no trabaja con
caracteres sino con símbolos. Son los símbolos los que, en su concatenación, forman los
elementos del lenguaje.

Se necesita entonces un módulo que reciba esa cadena de caracteres y lo convierta en


una cadena de símbolos. Dicho modulo es conocido como el Analizador Lexicografico, o
más comúnmente llamado SCANNER.

El scanner va recorriendo de izquierda a derecha la cadena de caracteres y cada vez que


reconoce un símbolo lo devuelven. Esta cadena de símbolos no es devuelta en un solo
paso sino uno a la vez, ya que el scanner se implementa como una función, la cual cada
vez que es llamada devuelve un símbolo.
Los principales tipos de símbolos son:

ID ---> Identificador, representa un nombre de variable, función, etc.


PR ---> Palabra Reservada, similar al identificador, pero para uso del lenguaje.
OP ---> Operador. El cual puede clasificarse en Operadores de agrupación,
aritmético, lógico, binario, etc.
NUM ---> Numero constante. Tambien podría dividirse en ENTERO y REAL
CAD ---> Cadena de caracteres.

La implementación del scanner puede ser realizada de forma simple o de forma elaborada,
dependiendo de las necesidades del Lenguaje Fuente.

Por ejemplo podemos tener la siguiente implementación básica:

char *CadFuente; // Cadena Fuente de caracteres


int PosActual= 0; // Posicion índice dentro de la cadena fuente
Definición
char *token; // Variable global que recibe al símbolo actual
de
variables
scanner() {
token = “”;
while ( CadFuente[ PosActual ] == ‘ ‘ ) // ignorar espacios en blanco
PosActual ++;
If (CadFuente[ PosActual ] IN [ ‘a’ .. ‘z’] ) { // Si es una letra ==> ID
token = CadFuente[ PosActual ] ;
PosActual ++ ;
while (CadFuente[ PosActual ] IN [ ‘a’ .. ‘z’, ‘0’ .. ‘9’] ) { // Si es letra o digito
token = token + CadFuente[ PosActual ] ;
PosActual ++;
}
}
elseif (CadFuente[ PosActual ] IN [ ‘0’ .. ‘9’ ) { // Si es un digito ==> NUM
token = CadFuente[ PosActual ] ;
PosActual ++ ;
while (CadFuente[ PosActual ] IN [ ‘0’ .. ‘9’] ) { // Si es digito
token = token + CadFuente[ PosActual ] ;
PosActual ++;
}
}
elseif ( CadFuente[ PosActual ] IN [ ‘+, ‘-’, ‘*’, ‘/’ ] ) { // operador de 1 caracter
token = CadFuente[ PosActual ] ;
}
else
ErrorLexicografico() ; // Carácter No Valido
}
Al empezar avanzamos la posición índice hasta encontrar el primer carácter diferente de
espacio en blanco. Si el lenguaje acepta comentarios entonces habría que ampliar el
scanner para que las ignore.

Dependiendo del primer carácter identificamos el símbolo. Si es un carácter entonces es un


símbolo IDENTIFICADOR, si es un digito entonces es un símbolo NUMERO y si esta en el
conjunto de operadores de 1 carácter entonces será un símbolo OPERADOR.

De no estar el carácter en ninguno de los conjuntos de caracteres definidos entonces se


trata de un error lexicográfico y habrá que llamar a la rutina de error.

Funciones del Scanner. Podemos resumir y complementar las funciones en:


 Entregar el token o símbolo actual
 Ignorar los elementos redundantes y no necesarios para el traductor: espacios en
blanco, tabuladores, comentarios, caracteres de control CR y LF.
 Reconocer el tipo y clase de símbolo. Por ejemplo puede distinguir entre un
identificador común y una palabra reservada.
 Puede interactuar con la Tabla de Simbolos para verificar o almacenar el
identificador que se haya reconocido.
 Puede entregar información para la gestión de errores, como por ejemplo el
numero de línea donde se produjo el error.

Podríamos entonces obtener mayor información del token, no solo su nombre sino también
su tipo de símbolo. Igualmente podemos diferenciar un identificador de una palabra
reservada.

Aquí estaría entonces una implementación mejorada:

char *CadFuente; // Cadena Fuente de caracteres


int PosActual= 0; // Posicion índice dentro de la cadena fuente
char *token.nombre; // Variable global que recibe el texto del símbolo actual
char *token.tipo; // Variable global que recibe al tipo del símbolo actual

scanner() {
token.nombre = “”;
while ( CadFuente[ PosActual ] == ‘ ‘ ) // ignorar espacios en blanco
PosActual ++;
If (CadFuente[ PosActual ] IN [ ‘a’ .. ‘z’] ) { // Si es una letra ==> ID
token = CadFuente[ PosActual ] ;
PosActual ++ ;
while (CadFuente[ PosActual ] IN [ ‘a’ .. ‘z’, ‘0’ .. ‘9’] ) { // Si es letra o digito
token.nombre = token.nombre + CadFuente[ PosActual ] ;
PosActual ++;
}
if ( BuscaTPR( token.nombre ) ) // Devuelve TRUE si es una PR
token.tipo = “PR”; // tipo PALABRA RESERVADA
else
token.tipo = “ID”; // tipo IDENTIFICADOR
}
elseif (CadFuente[ PosActual ] IN [ ‘0’ .. ‘9’ ) { // Si es un digito ==> NUM
token.nombre = CadFuente[ PosActual ] ;
PosActual ++ ;
while (CadFuente[ PosActual ] IN [ ‘0’ .. ‘9’] ) { // Si es digito
token.nombre = token.nombre + CadFuente[ PosActual ] ;
PosActual ++;
}
token.tipo = “NUM”; // tipo NUMERO
}
elseif ( CadFuente[ PosActual ] IN [ ‘+, ‘-’, ‘*’, ‘/’ ] ) { // operador de 1 caracter
token = CadFuente[ PosActual ] ;
token.tipo = “OP”; // tipo OPERADOR
}
else
ErrorLexicografico() ; // Carácter No Valido
}

La función BuscaTPR(token.nombre) buscara la cadena obtenida en una tabla previamente


cargada con las palabras reservadas del lenguaje. Si la encuentra devuelve TRUE, es decir
se trata de una palabra reservada. Si no la encuentra devolverá FALSE, es decir se trata
de solo un identificador.

Podemos resumir los conceptos que hemos utilizado mediante las siguientes definiciones.

Símbolo. Es la unidad lexica. Corresponde a una entidad indivisible y con significado


propio. Por ejemplo NUMERO, IDENTIFICADOR, OPERADOR, etc.

Lexema. Es la ocurrencia específica del símbolo. Por ejemplo para el símbolo NUMERO
podemos tener los siguientes lexemas 4, 325, 87, etc. Para el símbolo IDENTIFICADOR
podemos tener los siguientes lexemas: x, suma, a21.

Token. Es el símbolo que entrega el scanner. Al ultimo símbolo entregado por el scanner
se le conoce como el token actual.

Patrón de Formación
Los símbolos generalmente tienen un patrón de formación que caracteriza a todos sus
lexemas. Por ejemplo un nombre de variable debe empezar con una LETRA, luego puede
terminar ahí o bien seguir una secuencia de LETRAS, DIGITOS o bien una combinación
de LETRAS y DIGITOS.

Dicho patrón de formación debe ser expresado en una notación adecuada para que el
scanner lo pueda interpretar. La notación mas utilizada es la expresión regular, que se
vera mas adelante.
Generadores de Scanner. Se dispone de diversos software que automatizan la
construcción del Scanner. Estos software reciben como entrada el patrón de formación,
generalmente denotado como una expresión regular, y producen como salida el código
fuente de una función scanner.

Como ejemplo de estos software tenemos:

 Lex. Codigo generado C.


 Flex. Codigo generado C++
 JFlex. Codigo generado Java.

Aquí tenemos por ejemplo el archivo de entrada para un generador Flex.

% /* Inclusion de librerias */ %

DIGITO [0-9]
ID [a-z][a-z0-9]*

%%
{DIGITO}+ { printf( "Entero: %s (%d)\n", yytext, atoi( yytext ) ); }

If | then | begin | end { printf( "palabra clave: %s\n", yytext ); }

{ID} printf( "Identificador: %s\n", yytext );

"+"|"-"|"*"|"/" printf( "Operador: %s\n", yytext );

[ \t\n]+ /* ignora los espacios en blanco */

. printf( "Caracter no reconocido: %s\n", yytext );

%%
main( int argc, char **argv )
{
++argv, --argc; /* ignora el nombre del programa */
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;
yylex();
}
Observaciones adicionales.

1. Cuando recorremos la cadena fuente podemos, por ejemplo, apuntar al carácter ‘<’
y entonces podríamos preliminarmente suponer que nos encontramos con el
operador relacional MENOR, pero esto no es necesariamente cierto pues
dependerá de cual es el siguiente carácter. Si el siguiente carácter es ‘=’ o ‘>’ o ‘<’
entonces se trata de otro operador.

2. El scanner solo realiza análisis léxico, es decir identifica y entrega los símbolos
reconocidos al analizador sintáctico. Puede realizar algunas otras tareas
complementarias como eliminar comentarios y cambiar nombres por identificadores,
pero no realiza validaciones sintácticas. Esa tarea le corresponde al analizador
sintáctico.

3. El scanner puede interactuar con el pre procesador. Por ejemplo podría encontrar
la declaración ‘#define DOS 2’ y a partir de ahí cuando encuentre una ocurrencia
de DOS dentro del programa fuente lo reemplazara por 2.

4. El conjunto de símbolos validos dependerá del Lenguaje Fuente, lo que puede ser
valido en un lenguaje puede no serlo en otro. Por ejemplo el operador ‘++’ es
valido en el lenguaje C pero no es valido en algunos otros lenguajes.

--- EOF ----

También podría gustarte