Está en la página 1de 6

Conferencia 5: Adecuación de la sintaxis del lenguaje.

- Rutinas semánticas para declaraciones de etiquetas y saltos.

Bibliografía:
- Construcción de Compiladores. David Gries. Cap. 13, pp. 323–328.
- Conferencia en el servidor.

Introducción

Recordar:
- Esencia del analizador semántico (al reducir por x = U, ya U se redujo).
- Rutinas semánticas para asignación con elementos de arreglo.
Problemática: Arreglos en parte izquierda y parte derecha de la asignación.
Solución: corregir la FI generada.

Desarrollo

Comenzaremos el estudio de este tema a partir del análisis y solución del problema siguiente:

Problema: Escribir las rutinas semánticas asociadas a las siguientes reglas de producción, utilizadas para
generar la sentencia if de Pascal. Las rutinas semánticas deben generar la forma interna en notación
polaca de dicha sentencia.

(1) <Sent-If>  <Parte-If> <Sent-V> else <Sent>


(2) <Parte-If>  if <Cond>
(3) <Sent-V>  then <Sent>

Recordemos la forma interna en notación polaca de la sentencia if:

(a) (b) (c)

R(<Cond>), SSF, , R(<Sent>), SI, , R(<Sent>), ,

Aquí es necesario generar código en los puntos (a), (b) y (c) de esta forma interna. En cada caso es
necesario realizar las siguientes acciones:

En (a):
- Colocar en la cadena polaca el operador SSF.
- Dejar un espacio en blanco para más adelante colocar la dirección de salto.
- Guardar en la pila semántica este lugar.

En (b):
- Colocar en la cadena polaca el operador SI.
- Dejar un espacio en blanco para más adelante colocar la dirección de salto.
- Completar el salto desde (a).
- Resguardar en la pila semántica el lugar actual.

En (c):
- Completar el salto desde (b).

Las rutinas semánticas asociadas a cada regla son:


(2): POLACA[J++] = SSF;
SEM[++T] = J++;

(3): POLACA[J++] = SI;


POLACA[SEM[T]] = J+1;
SEM[T] = J++;

(1): POLACA[SEM[T--]] = J;

La pregunta lógica que surge ahora es: ¿será posible generar también esta forma interna utilizando la
regla de producción conocida para la sentencia if?

<Sent-If>  if <Cond> then <Sent> else <Sent>

Nos damos cuenta de que no es posible, pues al reducir por esta regla, ya se habrán hecho las
reducciones de todos los no terminales involucrados en ella (recordar introducción de la clase), y por
consiguiente se habrá generado:

R(<Cond>), R(<Sent>), R(<Sent>),


  
(a) (b) (c)

y no tendríamos forma de generar el código asociado a los puntos (a), (b), y (c).

Adecuación de la sintaxis de un lenguaje

Cuando se diseña la sintaxis de un lenguaje, se debe tener en cuenta, no solo que éste genere las
cadenas del lenguaje en cuestión, sino además, que sea posible asociar adecuadamente las acciones
semánticas a las diferentes reglas. Muchas veces es necesario, como en el caso anterior, reescribir la
gramática (dividir las reglas de producción) para poder generar la forma interna definida, o realizar otros
tipos de chequeos semánticos.

Un ejemplo de esto ya lo vimos al analizar la inclusión en la gramática de la sintaxis de asignación el no


terminal <id-arreglo> en las reglas:

<var>  <ident> | <id-arreglo>[<lista-de-expr>]


<id-arreglo>  <ident>

Aunque el no terminal <id-arreglo> parece innecesario, el mismo ha sido introducido para poder
resguardar la entrada del identificador en la tabla de símbolos, al reducir por la regla:

<id-arreglo>  ident

En muchas ocasiones, como en el ejemplo de la sentencia IF visto anteriormente, es necesario reescribir


las reglas de producción que definen la sintaxis de una sentencia para poder generar la forma interna
asociada. Veamos otros ejemplos:

a) Sentencia while de C

<Sent-While>  while (<Cond>) do <Sent>

La forma interna asociada a esta sentencia es la siguiente:


(a) (b) (c)

R(<Cond>), SSF, , R(<Sent>), SI, ,

Aquí es necesario generar código en los puntos (a), (b) y (c) de esta forma interna.

En (a):
- Guardar en la pila semántica este lugar.

En (b):
- Colocar en la cadena polaca el operador SSF.
- Dejar un espacio en blanco para más adelante colocar la dirección de salto.
- Resguardar en la pila semántica el lugar actual.

En (c):
- Colocar en la cadena polaca el operador SI.
- Completar los saltos hacia (a) y desde (b).

Se necesitan, por tanto, reglas de producción a las cuales asociar cada una de estas acciones. En este
caso serán necesarias al menos tres reglas de producción para las acciones correspondientes a los puntos
(a), (b) y (c). Por lo que la regla:

(a) (b) (c)

<Sent-While>  while │ (<Cond>) │ do <Sent> │

puede reescribirse de la forma siguiente:

(1) <Sent-While>  <Parte-Cond> do <Sent>


(2) <Parte-Cond>  <TK_WHILE> (<Cond>)
(3) <TK_WHILE>  while

Las rutinas semánticas asociadas a cada regla son:

(3): SEM[++T] = J;

(2): POLACA[J++] = SSF;


SEM[++T] = J++;

(1): POLACA[J++] = SI;


POLACA[SEM[T--]] = J+1;
POLACA[J++] = SEM[T--];

b) Sentencia for de C

<Sent-For>  for (<Exp1>; <Exp2>; <Exp3>) <Sent>

La forma interna asociada a esta sentencia es la siguiente:

(a) (b) (c) (d)


R(<Exp1>), R(<Exp2>, SSF, , SI, , R(<Exp3>), SI, , R(<Sent>), SI, ,

Aquí es necesario generar código en los puntos (a), (b), (c) y (d) de esta forma interna.

En (a):
- Guardar en la pila semántica este lugar.

En (b):
- Colocar en la cadena polaca el operador SSF.
- Dejar un espacio en blanco para más adelante colocar la dirección de salto.
- Resguardar en la pila semántica el lugar actual.
- Colocar en la cadena polaca el operador SI.
- Dejar un espacio en blanco para más adelante colocar la dirección de salto.
- Resguardar en la pila semántica el lugar actual.

En (c):
- Colocar en la cadena polaca el operador SI.
- Completar el salto hacia (a).
- Completar el salto incondicional desde (b).

En (d):
- Colocar en la cadena polaca el operador SI.
- Completar el salto hacia (b).
- Completar el salta si falso desde (b).

Se necesitan aquí también reglas de producción a las cuales asociar cada una de estas acciones. En este
caso serán necesarias al menos cuatro reglas de producción para las acciones correspondientes a los
puntos (a), (b), (c) y (d). Por lo que la regla:

(a) (b) (c) (d)

<Sent-For>  for (<Exp1>; │ <Exp2>; │ <Exp3>) │ <Sent> │

puede reescribirse de la forma siguiente:

(1) <Sent-For>  <Parte-Exp> <Sent>


(2) <Parte-Exp>  <Parte-Cond> <Exp3>)
(3) <Parte-Cond>  <Parte-Inic> <Exp2>;
(4) <Parte-Inic>  for (<Exp1>;

Las rutinas semánticas asociadas a cada regla son:

(4): SEM[++T] = J;

(3): POLACA[J++] = SSF;


SEM[++T] = J++;
POLACA[J++] = SI;
SEM[++T] = J++;

(2): POLACA[J++] = SI;


POLACA[SEM[T]] = J+1;
POLACA[J++] = SEM[T-2];

(1): POLACA[J++] = SI;


POLACA[J++] = SEM[T]+1;
POLACA[SEM[T-1]] = J;

Rutinas semánticas para sentencias condicionales

Para realizar las acciones semánticas asociadas a cada sentencia, es necesario estructurar la gramática
de forma tal que las empuñaduras sucesivas lleguen exactamente hasta cada punto donde se necesita
realizar una acción. En otras palabras, siempre que se requiera generar código o realizar análisis
semántico después de la aparición de un no terminal, debemos tener una regla de producción donde ése
sea el último no terminal (eventualmente pueden aparecer símbolos terminales después de él), y asociar
dichas acciones a esa regla.

En el caso de las sentencias condicionales una forma de reescribir las reglas de producción para generar
la forma interna asociada es como aparece en el problema del inicio. Con ello obtendríamos el árbol
sintáctico siguiente:

<Sent-If>

<Parte-If> <Sent-V> else <Sent>

if <Cond> then <Sent>

Vale la pena aclarar que la forma de reescribir las reglas de producción no es única; puede hacerse de
varias formas equivalentes y obtener el mismo resultado.

Rutinas semánticas para declaraciones de etiquetas y saltos

Las etiquetas aparecen en los programas generalmente en dos momentos diferentes:

(1) cuando se definen  como en Etiq1:


(2) cuando se utilizan  como en goto Etiq1;

Analicemos la conveniencia de utilizar el operador de forma interna SI para la generación del código
asociado a las etiquetas. ¿Qué sucede, por ejemplo, si el lenguaje permite (como generalmente sucede)
realizar saltos a etiquetas cuya definición no ha aparecido aún:

goto A;
...
goto A;
...
goto A;
...
...
A:
...

En este caso quedarían en la forma interna generada una serie de saltos sin resolver, que necesitan
completarse cuando aparezca la definición de la etiqueta A. Una alternativa, que simplifica la solución a
este problema es introducir un nuevo operador de forma interna: SE. La sintaxis de este operador es:

SE, p

donde p indica la entrada en la tabla de símbolos de la etiqueta a donde se debe saltar. Adoptaremos las
siguientes convenciones:
- Las etiquetas tienen un 4 en el campo tipo de la tabla de símbolos (TS).
- Tienen un campo dir en la TS que indica su dirección en memoria.
- Si una etiqueta es utilizada sin definir, tiene 4 en el campo tipo, y 0 en el campo dir.
- Si no ha sido utilizada tiene 0 en ambos campos.

Analicemos las siguientes rutinas semánticas para cada momento de aparición de una etiqueta en un
programa fuente:

(1) <Etiq>  <ident>:

if (TS[p].tipo == 0)
TS[p].tipo = 4;
if (TS[p].tipo != 4)
error("Identificador inválido usado como etiqueta");
else
if (TS[p].dir != 0)
error("Etiqueta multidefinida");
else
TS[p].dir = J;

(2) <Salto-Inc>  goto <ident>

if (TS[p].tipo == 0)
TS[p].tipo = 4;
else
if (TS[p].tipo != 4)
error("Salto a un identificador que no es etiqueta");
POLACA[J++] = 'SE';
POLACA[J++] = p;

Ejercicios propuestos:

1. Analizar las rutinas semánticas cuando se genera la forma interna utilizando el operador SI, en
lugar de SE.
2. Analizar los cambios que son necesarios realizar si en la definición del lenguaje se incluye una
sección de declaración de etiquetas.

También podría gustarte