Nombres para los Atributos

Desventajas de la Notación Posicional

Dentro de las acciones los atributos de la parte derecha de la regla de producción $ A \rightarrow X_1 \ldots X_n$ se pasan como parámetros en $_[1], $_[2], etc. La notación posicional para los atributos puede ser inconveniente si el número de símbolos es grande. También puede resultar inconveniente durante la fase de desarrollo: Puede ocurrir que hemos escrito la regla $ A \rightarrow X_1 X_2 X_3$ y la acción semántica (por ejemplo { $f = $_[1]*$_[3]+$_[2]; $_[2] } y - en determinado momento - nos damos cuenta que debemos cambiar la regla añadiendo un nuevo símbolo $ A \rightarrow X_1 Y X_2 X_3$ o suprimiendo alguno que ya existía. O quizá queremos insertar una acción en algún lugar intermedio de la regla. O quizá reordenamos los símbolos en la parte derecha. En todos estos caso nos encontramos en la situación de que debemos reenumerar todos los argumentos dentro de la acción semántica. En el ejemplo anterior tendríamos que reescribir la acción como:

{ $f = $_[1]*$_[4]+$_[3]; $_[3] }

La notación Dolar

Para tratar con esta situación eyapp provee mecanismos para darle nombres a los atributos semánticos asociados con los símbolos. Uno de esos mecanismos es prefijar el símbolo con un dolar. Ello indica que el nombre del símbolo puede ser usado dentro de la acción como nombre del atributo asociado. Por ejemplo, el código en la línea 22 imprime el atributo asociado con la variable sintáctica expr, que en este caso es su valor numérico.

La Notación Punto

La otra notación usada para darle nombres a los atributos consiste en concatenar el símbolo en la parte derecha de la regla de un punto seguido del nombre del atributo (el código completo figura en el apéndice en la página [*]):

26  exp:
27      NUM
28    | $VAR                    { $s{$VAR} }
29    | VAR.x '=' exp.y         { $s{$x} = $y }
30    | exp.x '+' exp.y         { $x + $y }
31    | exp.x '-' exp.y         { $x - $y }
32    | exp.x '*' exp.y         { $x * $y }
33    | exp.x '^' exp.y         { $x ** $y }
34    | exp.x '/' exp.y
35      {
36        my $parser = shift;
37
38             $y and return($x / $y);
39         $parser->YYData->{ERRMSG}
40           =   "Illegal division by zero.\n";
41         $parser->YYError;
42         undef
43      }
44    |   '-' $exp %prec NEG
45      { -$exp }
46    |   '(' $exp ')'
47      { $exp }
48  ;

La Cabecera y el Arranque

El atributo asociado con start (línea 12) es una referencia a un par. El segundo componente del par es una referencia a la tabla de símbolos. El primero es una referencia a la lista de valores resultantes de la evaluación de las diferentes expresiones.

pl@nereida:~/LEyapp/examples$ cat -n CalcSimple.eyp
 1  # CalcSimple.eyp
 2  %right  '='
 3  %left   '-' '+'
 4  %left   '*' '/'
 5  %left   NEG
 6  %right  '^'
 7  %{
 8  my %s;
 9  %}
10
11  %%
12  start: input { [ $_[1], \%s] }
13  ;
14
15  input:
16      /* empty */     { [] }
17    | input line  { push(@{$_[1]},$_[2]) if defined($_[2]); $_[1] }
18  ;

La línea 17 indica que el atributo asociado con la variable sintáctica input es una referencia a una pila y que el atributo asociado con la variable sintáctica line debe empujarse en la pila.

De hecho, el atributo asociado con line es el valor de la expresión evaluada en esa línea. Asi pues el atributo retornado por input es una referencia a una lista conteniendo los valores de las expresiones evaluadas. Observe que expresiones erróneas no aparecerán en la lista.

20  line:
21    '\n'         { undef }
22    | $exp '\n'  { print "$exp\n"; $exp }
23    | error '\n' { $_[0]->YYErrok; undef }
24  ;

La Cola

Veamos el código del analizador léxico:

50  %%
51
52  sub _Error {
..   .............................
62  }
63
64  my $input;
65
66  sub _Lexer {
67    my($parser)=shift;
68
69    # topicalize $input
70    for ($input) {
71      s/^[ \t]//;      # skip whites
72      return('',undef) unless $_;
73      return('NUM',$1) if s{^([0-9]+(?:\.[0-9]+)?)}{};
74      return('VAR',$1) if s/^([A-Za-z][A-Za-z0-9_]*)//;
75      return($1,$1)    if s/^(.)//s;
76    }
77
78    return('',undef);
79  }
80
81  sub Run {
82      my($self)=shift;
83
84      $input = shift;
85      return $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error,
86                             #yydebug => 0xF
87                           );
88  }

Recuperación de Errores

Las entradas pueden contener errores. El lenguaje eyapp proporciona un token especial, error, que puede ser utilizado en el programa fuente para extender el analizador con producciones de error que lo dotan de cierta capacidad para recuperase de una entrada errónea y poder continuar analizando el resto de la entrada.

20  line:
21    '\n'         { undef }
22    | $exp '\n'  { print "$exp\n"; $exp }
23    | error '\n' { $_[0]->YYErrok; undef }
24  ;

Cuando se produce un error en el análisis, eyapp emite un mensaje de error (Algo como "syntax error") y produce ``mágicamente'' el terminal especial denominado error. A partir de ahí permanecerá silencioso, consumiendo terminales hasta encontrar una regla que sea capaz de consumir el terminal error. La idea general es que, a traves de la regla de recuperación de errores de la línea 23 se indica que cuando se produzca un error el analizador debe descartar todos los tokens hasta llegar a un retorno de carro.

Además, mediante la llamada al método $_[0]->YYErrok el programador anuncia que, si se alcanza este punto, la recuperación puede considerarse ''completa'' y que eyapp puede emitir a partir de ese momento mensajes de error con la seguridad de que no son consecuencia de un comportamiento inestable provocado por el primer error.

La Acción por Defecto

Observemos la regla:

26  exp:
27      NUM
La acción por defecto es retornar $_[1]. Por tanto, en el caso de la regla de la línea 27 el valor retornado es el asociado a NUM.

Manejo de los símbolos

La calculadora usa un hash léxico %s como tabla de símbolos. Cuando dentro de una expresión encontramos una alusión a una variable retornamos el valor asociado $s{$_[1]} que ha sido guardado en la correspondiente entrada de la tabla de símbolos:

 .  ...
 7  %{
 8  my %s;
 9  %}
..  ...
11  %%
..  ...
26  exp:
27      NUM
28    | $VAR                    { $s{$VAR} }
29    | VAR.x '=' exp.y         { $s{$x} = $y }
30    | exp.x '+' exp.y         { $x + $y }
..  ...

El Atributo YYData

En la regla de la división comprobamos que el divisor es distinto de cero.

34    | exp.x '/' exp.y
35      {
36        my $parser = shift;
37
38             $y and return($x / $y);
39         $parser->YYData->{ERRMSG}
40           =   "Illegal division by zero.\n";
41         $parser->YYError;
42         undef
43      }

El método YYData provee acceso a un hash que se maneja como una zona de datos para el programa cliente. En el ejemplo usamos una entrada ERRMSG para alojar el mensaje de error.

Este mensaje es aprovechado por la subrutina de tratamiento de errores:

52  sub _Error {
53    my $private = $_[0]->YYData;
54
55        exists $private->{ERRMSG}
56    and do {
57        print $private->{ERRMSG};
58        delete $private->{ERRMSG};
59        return;
60    };
61    print "Syntax error.\n";
62  }

La subrutina _Error es llamada por el analizador sintáctico generado por eyapp cada vez que ocurre un error sintáctico. Para que ello sea posible en la llamada al analizador se especifican quienes son la rutina de análisis léxico y quien es la rutina a llamar en caso de error:

$self->YYParse( yylex => \&_Lexer, yyerror => \&_Error );

El Programa Cliente

Veamos los contenidos del ejecutable usecalcsimple.pl el cuál utiliza el módulo generado por eyapp:

pl@nereida:~/LEyapp/examples$ cat -n usecalcsimple.pl
 1  #!/usr/bin/perl -w
 2  use strict;
 3  use CalcSimple;
 4  use Carp;
 5
 6  sub slurp_file {
 7    my $fn = shift;
 8    my $f;
 9
10    local $/ = undef;
11    if (defined($fn)) {
12      open $f, $fn
13    }
14    else {
15      $f = \*STDIN;
16    }
17    my $input = <$f>;
18    return $input;
19  }
20
21  my $parser = CalcSimple->new();
22
23  my $input = slurp_file( shift() );
24  my ($r, $s) = @{$parser->Run($input)};
25
26  print "========= Results  ==============\n";
27  print "$_\n" for @$r;
28  print "========= Symbol Table ==============\n";
29  print "$_ = $s->{$_}\n" for sort keys %$s;

Ejecución

Veamos una ejecución:

pl@nereida:~/LEyapp/examples$ cat prueba.exp
a=2*3
b=a+1
pl@nereida:~/LEyapp/examples$ usecalcsimple.pl prueba.exp
6
7
========= Results  ==============
6
7
========= Symbol Table ==============
a = 6
b = 7
La primera aparición de la cadena "6\n7" es debida a la acción print en el cuerpo de la gramática. La segunda es el resultado de la línea 27 en el programa cliente.



Subsecciones
Casiano Rodríguez León
2009-12-09