El Analizador Léxico de rpcalc
El trabajo del analizador léxico es el análisis a bajo nivel: la conversión de los caracteres o secuencia de caracteres en tokens. El analizador de Bison obtiene sus tokens llamando al analizador léxico. See section La Funcion del Analizador Léxico
yylex.
Solamente se necesita un analizador léxico sencillo para la calculadora RPN. Este analizador léxico ignora los espacios en blanco y los tabuladores, luego lee los números como
double y los devuelve como tokens
NUM. Cualquier otro caracter que no forme parte de un número es un token por separado. Tenga en cuenta que el código del token para un token de caracter simple es el propio caracter.
El valor de retorno de la función de análisis léxico es un código numérico que representa el tipo de token. El mismo texto que se utilizó en las reglas de Bison para representar el tipo de token también es una expresión en C con el valor numérico del tipo. Esto funciona de dos maneras. Si el tipo de token es un caracter literal, entonces su código numérico es el código ASCII de ese caracter; puede usar el mismo caracter literal en el analizador léxico para expresar el número. Si el tipo de token es un identificador, ese identificador lo define Bison como una macro en C cuya definición es un número apropiado. En este ejemplo, por lo tanto,
NUM se convierte en una macro para que la use
yylex.
El valor semántico del token (si tiene alguno) se almacena en la variable global
yylval, que es donde el analizador de Bison lo buscará. (El tipo de datos de C para
yylval es
YYSTYPE, que se definió al principio de la gramática; see section Declaraciones para
rpcalc.)
Se devuelve un código de tipo de token igual a cero cuando se llega al final del fichero. (Bison reconoce cualquier valor no positivo como indicador del final del fichero de entrada.)
Aquí está el código para el analizador léxico:
/* El analizador léxico devuelve un número en coma
flotante (double) en la pila y el token NUM, o el
caracter ASCII leído si no es un número. Ignora
todos los espacios en blanco y tabuladores,
devuelve 0 como EOF. */
#include <ctype.h>
yylex ()
{
int c;
/* ignora los espacios en blanco */
while ((c = getchar ())
' ' || c
'\t')
;
/* procesa números */
if (c
'.' || isdigit (c))
{
ungetc (c, stdin);
scanf ("%lf", &yylval);
return NUM;
}
/* devuelve fin-de-fichero */
if (c
EOF)
return 0;
/* devuelve caracteres sencillos */
return c;
}
La Función de Control
Para continuar acordes a este ejemplo, la función de control se mantiene escueta al mínimo. El único requisito es que llame a
yyparse para comenzar el proceso de análisis.
main ()
{
yyparse ();
}
La Rutina de Informe de Errores
Cundo
yyparse detecta un error de sintaxis, realiza una llamada a la función de informe de errores
yyerror para que imprima un mensaje de error (normalmente pero no siempre un
"parse error"). Es cosa del programador el proveer
yyerror (see section Interfaz del Analizador en Lenguaje C), luego aquí está la definición que utilizaremos:
#include <stdio.h>
yyerror (s) /* Llamada por yyparse ante un error */
char *s;
{
printf ("%s\n", s);
}
Después de que
yyerror retorne, el analizador de Bison podría recuperarse del error y continuar analizando si la gramática contiene una regla de error apropiada (see section Recuperación de Errores). De otra manera,
yyparse devolverá un valor distinto de cero. No hemos escrito ninguna regla de error en este ejemplo, así que una entrada no válida provocará que termine el programa de la calculadora. Este no es el comportamiento adecuado para una calculadora real, pero es adecuado en el primer ejemplo.
Ejecutando Bison para Hacer el Analizador
Antes de ejecutar Bison para producir un analizador, necesitamos decidir cómo ordenar todo en código fuente en uno o más ficheros fuente. Para un ejemplo tan sencillo, la manera más fácil es poner todo en un archivo. Las definiciones de
yylex,
yyerror y
main van al final, en la sección de "código C adicional" del fichero. (see section El Formato Global de una Gramática de Bison).
Para un proyecto más grande, probablemente tendría varios ficheros fuente, y utilizaría
make para ordenar la recompilación de estos.
Con todo el fuente en un único archivo, utilice el siguiente comando para convertirlo en el fichero del analizador:
bison nombre_archivo.y
En este ejemplo el archivo se llamó
`rpcalc.y' (de "Reverse Polish CALCulator", "Calculadora Polaca Inversa"). Bison produce un archivo llamado
`nombre_archivo.tab.c', quitando el `.y' del nombre del fichero original. El fichero de salida de Bison contiene el código fuente para
yyparse. Las funciones adicionales en el fichero de entrada (
yylex,
yyerror y
main) se copian literalmente a la salida.
Compilando el Archivo del Analizador
Aquí está la forma de compilar y ejecutar el archivo del analizador:
# Lista los archivos en el directorio actual.
% ls
rpcalc.tab.c rpcalc.y
# Compila el analizador de Bison.
# `-lm' le dice al compilador que busque la librería math para
pow.
% cc rpcalc.tab.c -lm -o rpcalc
# Lista de nuevo los archivos.
% ls
rpcalc rpcalc.tab.c rpcalc.y
El archivo
`rpcalc' contiene ahora el código ejecutable. He aquí una sesión de ejemplo utilizando
rpcalc.
% rpcalc
4 9 +
13
3 7 + 3 4 5 *+-
-13
3 7 + 3 4 5 * + - n Note el menos unario, `n'
13
5 6 / 4 n +
-3.166666667
3 4 ^ Exponenciación
81
^D Indicador de Fin-de-fichero
%
Calculadora de Notación Infija: calc
Ahora modificaremos rpcalc para que maneje operadores infijos en lugar de postfijos. La notación infija trae consigo el concepto de la precedencia de operadores y la necesidad de paréntesis anidados de profundidad arbitraria. Aquí está el código de Bison para
`calc.y', una calculadora infija de escritorio.
/* Calculadora de notación infija--calc */
%{
#define YYSTYPE double
#include <math.h>
%}
/* Declaraciones de BISON */
%token NUM
%left '-' '+'
%left '*' '/'
%left NEG /* negación--menos unario */
%right '^' /* exponenciación */
/* A continuación la gramática */
input: /* cadena vacía */
| input line
;
line: '\n'
| exp '\n' { printf ("\t%.10g\n", $1); }
;
exp: NUM { $$ = $1; }
| exp '+' exp { $$ = $1 + $3; }
| exp '-' exp { $$ = $1 - $3; }
| exp '*' exp { $$ = $1 * $3; }
| exp '/' exp { $$ = $1 / $3; }
| '-' exp %prec NEG { $$ = -$2; }
| exp '^' exp { $$ = pow ($1, $3); }
| '(' exp ')' { $$ = $2; }
;
Las funciones
yylex,
yyerror y
main pueden ser las mismas de antes.
Hay dos propiedades nuevas importantes presentadas en este código.
En la segunda sección (declaraciones de Bison),
%left declara tipos de tokens y dice que son operadores asociativos por la izquierda. Las declaraciones
%left y
%right (asociatividad por la derecha) toma el lugar de
%token que se utiliza para declarar un nombre de tipo de token sin asociatividad. (Estos tokens son caracteres literales simples, que de forma ordinaria no tienen que ser declarados. Los declaramos aquí para especificar la asociatividad.)
La precedencia de operadores se determina por el orden de línea de las declaraciones; cuanto más alto sea el número de línea de la declaración (esta esté más baja en la página o en la pantalla), más alta será la precedencia. Por tanto, la exponenciación tiene la precedencia más alta, el menos unario (
NEG) es el siguiente, seguido por `*' y `/', y así sucesivamente. See section Precedencia de Operadores.
La otra propiedad nueva importante es el
%prec en la sección de la gramática para el operador menos unario. El
%prec simplemente le dice a Bison que la regla `| '-' exp' tiene la misma precedencia que
NEGen este caso la siguiente a la más alta. See section Precedencia Dependiente del Contexto.
Aquí hay un ejemplo de la ejecución de
`calc.y':
% calc
4 + 4.5 - (34/(8*3+-3))
6.880952381
-56 + 2
-54
3 ^ 2
9
Recuperación de Errores Simple
Hasta este punto, este manual no ha tratado el tema de la
recuperación de errorescómo continuar analizando después de que el analizador detecte un error de sintaxis. Todo lo que hemos manejado es el informe de errores con
yyerror. Tenga presente que por defecto
yyparse retorna después de llamar a
yyerror. Esto quiere decir que una línea de entrada errónea hace que el programa de la calculadora finalice. Ahora mostraremos cómo rectificar esta deficiencia.
El lenguaje de Bison por sí mismo incluye la palabra reservada
error, que podría incluirse en las reglas de la gramática. En el siguiente ejemplo esta se ha añadido a una de las alternativas para
line:
line: '\n'
| exp '\n' { printf ("\t%.10g\n", $1); }
| error '\n' { yyerrok; }
;
Esta ampliación a la gramática permite una recuperación de errores simple en caso de un error de análisis. Si se lee una expresión que no puede ser evaluada, el error será reconocido por la tercera regla de
line, y el análisis continuará. (La función
yyerror aún se sigue llamando para imprimir su mensaje también.) La acción ejecuta la sentencia
yyerrok, una macro definida automáticamente por Bison; su significado es que la recuperación de errores ha terminado (see section Recuperación de Errores). Note la diferencia entre
yyerrok y
yyerror; no se trata de ninguna errata.
Esta forma de recuperación de errores trata con errores sintácticos. Existe otro tipo de errores; por ejemplo, la división entre cero, que conlleva una señal de excepción que normalmente es fatal. Una calculadora real debe tratar esta señal y utilizar
longjmp para retornar a
main y reanudar el análisis de líneas de entrada; también tendría que descartar el resto de la línea de entrada actual. No discutiremos esta cuestión más allá porque no es específica de los programas de Bison.