El paradigma de Bison es analizar tokens en primer lugar, entonces agruparlos en unidades sintácticas más grandes. En muchos lenguajes, el significado de un token se ve afectado por su contexto. A pesar de que esto viola el paradigma de Bison, ciertas técnicas (conocidas como kludges) podrían permitirle escribir analizadores de Bison para tales lenguajes.
(Realmente, "kludge" significa cualquier técnica que hace su trabajo pero no de una manera limpia o robusta.)
Información Semántica en Tipos de Tokens
El lenguaje C tiene una dependencia del contexto: la manera en la que se utiliza un identificador depende de cuál es su significado. Por ejemplo, considere esto:
foo (x);
Esto parece la sentencia de llamada a una función, pero si foo es un nombre de tipo definido, entonces esto realmente es una declaración de x. ¿Cómo puede un analizador de Bison para C decidirse cómo analizar esta entrada?
El método utilizado en GNU C es tener dos tipos diferentes de tokens, IDENTIFIER y TYPENAME. Cuando yylex encuentre un identificador, localiza la declaración actual del identificador para decidir que tipo de token devolver: TYPENAME si el identificador se declara como una definición de tipo, IDENTIFIER en otro caso.
Las reglas gramaticales pueden entonces expresar la dependencia del contexto eligiendo el tipo de token a reconocer. IDENTIFIER se acepta como una expresión, pero TYPENAME no lo es. TYPENAME puede empezar una declaración, pero TYPENAME no. En contextos donde el significado del identificador no es significativo, tal como en declaraciones que pueden ocultar nombre de definición de tipo, no se acepta ni TYPENAME o IDENTIFIER
hay una regla para cada uno de los dos tipos de tokens.
Esta técnica es sencilla de usar si la decisión de qué tipos de identificadores se permiten se hace en un lugar cercano a donde se analiza el identificador. Pero en C esto no es siempre así: C permite en una declaración redeclarar un nombre de tipo definido siempre que se haya especificado antes un tipo explícito:
typedef int foo, bar, lose;
static foo (bar); /* redeclara bar como variable estática */
static int foo (lose); /* redeclara foo como función */
Por desgracia, el nombre que se está declarando se encuentra separado de la construcción de la declaración por una estructura sintáctica complicada--el "declarador".
Como resultado, la parte del analizador de Bison para C necesita ser duplicada, con todos los nombres de los no terminales cambiados: una vez para el análisis de una declaración en la que se puede redefinir un nombre de declaración de tipo, y una vez para el análisis de una declaración en la que no puede hacerse. Aquí hay parte de la duplicación, con las acciones omitidas por brevedad:
initdcl:
declarator maybeasm '='
init
| declarator maybeasm
;
notype_initdcl:
notype_declarator maybeasm '='
init
| notype_declarator maybeasm
;
Aquí initdcl puede redeclarar un nombre de definición de tipo, pero notype_initdcl no puede. La distinción entre declarator y notype_declarator es del mismo tipo.
Hay aquí alguna similitud entre esta técnica y una ligadura léxica (descrita a continuación), en que la información que altera el análisis léxico se cambia durante el análisis por otras partes del programa. La diferencia es que aquí la información es global, y se utiliza para otros propósitos en el programa. Una verdadera ligadura léxica tiene una bandera de propósito especial controlada por el contexto sintáctico.
Ligaduras Léxicas
Una manera de manejar las dependencias del contexto es la ligadura léxica: una bandera que se activa en acciones de Bison, cuyo propósito es alterar la manera en la que se analizan los tokens.
Por ejemplo, soponga que tenemos un lenguaje vagamente parecido a C, pero con una construcción especial `hex (hex-expr)'. Después de la palabra clave hex viene una expresión entre paréntesis en el que todos los enteros son hexadecimales. En particular, el token `a1b' debe tratarse como un entero en lugar de como un identificador si aparece en ese contexto. He aquí cómo puede hacerlo:
%{
int hexflag;
%}
%%
...
expr: IDENTIFIER
| constant
| HEX '('
{ hexflag = 1; }
expr ')'
{ hexflag = 0;
$$ = $4; }
| expr '+' expr
{ $$ = make_sum ($1, $3); }
...
;
constant:
INTEGER
| STRING
;
Aquí asumimos que yylex mira el valor de hexflag; cuando no es cero, todos los enteros se analizan en hexadecimal, y los tokens que comiencen con letras se analizan como enteros si es posible.
La declaración de hexflag mostrada en la sección de declaraciones en C del archivo del analizador se necesita para hacerla accesible a las acciones (see section La Sección de Declaraciones en C). Debe también escribir el código en yylex para obedecer a la bandera.
Ligaduras Léxicas y Recuperación de Errores
Las ligaduras léxicas hacen demandas estrictas sobre cualquier regla de recuperación de errores que tenga. See section Recuperación de Errores.
La razón de esto es que el propósito de una regla de recuperación de errores es abortar el análisis de una construcción y reanudar en una construcción mayor. Por ejemplo, en lenguajes como C, una regla típica de recuperación de errores es saltarse los tokens hasta el siguiente punto y coma, y entonces comenzar una sentencia nueva, como esta:
stmt: expr ';'
| IF '(' expr ')' stmt { ... }
...
error ';'
{ hexflag = 0; }
;
Si hay aquí un error de sintaxis en medio de una construcción `hex (expr)', esta regla de error se aplicará, y entonces la acción para la `hex (expr)' nunca se ejecutará. Así que hexflag continuaría activada durante el resto de la entrada, o hasta la próxima palabra clave hex, haciendo que los identificadores se malinterpreten como enteros.
Para evitar este problema la regla de recuperación de errores por sí misma desactiva hexflag.
Aquí podría existir también una regla de recuperación de errores que trabaje dentro de expresiones. Por ejemplo, podría haber una regla que se aplique dentro de los paréntesis y salte al paréntesis-cerrar:
expr: ...
| '(' expr ')'
{ $$ = $2; }
| '(' error ')'
...
Si esta regla actúa dentro de la construcción hex, no se va a abortar esa construcción (ya que ésta aparece a un nivel más interno de paréntesis dentro de la construcción). Por lo tanto, no debería desactivar la bandera: el resto de la construcción hex debería analizarse con la bandera aún activada.
¿Qué sucedería si hay una regla de recuperación de errores que pudiese abortar fuera la construcción hex o pudiese que no, dependiendo de las circunstancias? No hay manera de escribir la acción para determinar si una construcción hex está siendo abortada o no. De manera que si está utilizando una ligadura léxica, es mejor que esté seguro que sus reglas de recuperación de errores no son de este tipo. Cada regla debe ser tal que pueda estar seguro que siempre tendrá que tener que limpiar la bandera, o siempre no.