next up previous contents index PLPL moodlepserratamodulosperlmonksperldocapuntes LHPgoogleetsiiullpcgull
Sig: Acciones y Acciones por Sup: Análisis Sintáctico con Parse::Eyapp Ant: Parse::Eyapp: Un Generador de Err: Si hallas una errata ...

Depuración de Errores

Las fuentes de error cuando se programa con una herramienta como eyapp son diversas. En esta sección trataremos tres tipos de error:

Resolución de Conflictos

El siguiente programa eyapp contiene algunos errores. El lenguaje generado esta constituido por listas de Ds (por declaraciones) seguidas de listas de Ss (por sentencias) separadas por puntos y coma:

pl@nereida:~/LEyapp/examples$ cat -n Debug.eyp
 1  %token D S
 2
 3  %{
 4  our $VERSION = '0.01';
 5  %}
 6
 7  %%
 8  p:
 9                  ds ';' ss
10          |       ss
11  ;
12
13  ds:
14            D ';' ds
15          | D
16            {
17              print "Reducing by rule:\n";
18              print "\tds -> D\n";
19              $_[1];
20            }
21  ;
22
23  ss:
24            S ';' ss
25          | S
26  ;
27
28  %%
29
30  my $tokenline = 0;
31
32  sub _Error {
33          my $parser = shift;
34          my ($token) = $parser->YYCurval;
35          my ($what) = $token ? "input: '$token'" : "end of input";
36          die "Syntax error near $what line num $tokenline\n";
37  }
38
39  my $input;
40
41  sub _Lexer {
42
43          for ($input) {
44            s{^(\s)}{} and $tokenline += $1 =~ tr{\n}{};
45            return ('',undef) unless $_;
46            return ($1,$1) if s/^(.)//;
47          }
48          return ('',undef);
49  }
50
51  sub Run {
52          my ($self) = shift;
53
54          $input = shift;
55
56          return $self->YYParse( yylex => \&_Lexer, yyerror => \&_Error,
57                           yydebug => 0xF
58    );
59  }

Al compilar este programa con eyapp produce un mensaje de advertencia anunciándonos la existencia de un conflicto.

pl@nereida:~/LEyapp/examples$ eyapp Debug.eyp
1 shift/reduce conflict (see .output file)
State 4: shifts:
  to state    8 with ';'
La existencia de advertencias da lugar a la creación de un fichero Debug.output conteniendo información sobre la gramática y el analizador construido.

Veamos los contenidos del fichero:

pl@nereida:~/LEyapp/examples$ cat -n Debug.output
   1  Warnings:
   2  ---------
   3  1 shift/reduce conflict (see .output file)
   4  State 4: shifts:
   5    to state    8 with ';'
   6
   7  Conflicts:
   8  ----------
   9  State 4 contains 1 shift/reduce conflict
  10
  11  Rules:
  12  ------
  13  0:      $start -> p $end
  14  1:      p -> ds ';' ss
  15  2:      p -> ss
  16  3:      ds -> D ';' ds
  17  4:      ds -> D
  18  5:      ss -> S ';' ss
  19  6:      ss -> S
  20
  21  States:
  22  -------
  23  State 0:
  24
  25          $start -> . p $end      (Rule 0)
  26
  27          D       shift, and go to state 4
  28          S       shift, and go to state 1
  29
  30          p       go to state 2
  31          ss      go to state 3
  32          ds      go to state 5
  33
  ..  .........................................
  55  State 4:
  56
  57          ds -> D . ';' ds        (Rule 3)
  58          ds -> D .       (Rule 4)
  59
  60          ';'     shift, and go to state 8
  61
  62          ';'     [reduce using rule 4 (ds)]
  63
  ..  .........................................
  84  State 8:
  85
  86          ds -> D ';' . ds        (Rule 3)
  87
  88          D       shift, and go to state 4
  89
  90          ds      go to state 11
  91
  ..  .........................................
 112  State 12:
 113
 114          p -> ds ';' ss .        (Rule 1)
 115
 116          $default        reduce using rule 1 (p)
 117
 118
 119  Summary:
 120  --------
 121  Number of rules         : 7
 122  Number of terminals     : 4
 123  Number of non-terminals : 4
 124  Number of states        : 13

El problema según se nos anuncia ocurre en el estado 4. Como veremos mas adelante el analizador sintáctico generado por Parse::Eyapp es un autómata finito. Cada estado guarda información sobre las reglas que podrían aplicarse durante el análisis de la entrada. Veamos en detalle la información asociada con el estado 4:

  55  State 4:
  56
  57          ds -> D . ';' ds        (Rule 3)
  58          ds -> D .       (Rule 4)
  59
  60          ';'     shift, and go to state 8
  61
  62          ';'     [reduce using rule 4 (ds)]
  63

Un estado es un conjunto de reglas de producción con un marcador en su parte derecha. La idea es que - si estamos en un estado dado - esas reglas son potenciales candidatos para la construcción del árbol sintáctico. Que lo sean o no dependerá de los terminales que se vean a continuación.

El punto que aparece en la parte derecha de una regla indica ''posición'' de lectura. Así el hecho de que en el estado cuatro aparezcan los items:

  57          ds -> D . ';' ds        (Rule 3)
  58          ds -> D .       (Rule 4)

Significa que si estamos en este estado es por que se leyó una D y se espera ver un punto y coma.

El comentario de la línea 60 indica que si el siguiente terminal es ; podríamos ir al estado 8. Obsérvese que el estado 8 contiene un item de la forma ds -> D ';' . ds. La marca recuerda ahora que se vió una D y un punto y coma. El comentario de la línea 62:

  62          ';'     [reduce using rule 4 (ds)]
indica que Parse::Eyapp considera factible otro árbol cuando el token actual es punto y coma: Reducir por la regla ds -> D.

Para ilustrar el problema consideremos las frases D;S y D;D;S.

Para ambas frases, después de consumir la D el analizador irá al estado 4 y el terminal actual será punto y coma. Para la primera frase D;S la decisión correcta es utilizar (''reducir'' en la jerga) la regla 4 ds -> D.

Para la segunda frase D;D;S la decisión correcta es conjeturar la regla 3 ds -> D . ';' ds. El analizador podría saber que regla es la correcta si consumiera el terminal que sigue al punto y coma: si es S se trata de la regla 4 y si es D de la regla 3. Los analizadores generados por Eyapp no ''miran'' mas allá del siguiente terminal y por tanto no están en condiciones de decidir.

Cualquier solución a este tipo de conflictos implica una reformulación de la gramática modificando prioridades o reorganizando las reglas. Reescribiendo la regla para ds recursiva por la derecha desaparece el conflicto:

l@nereida:~/LEyapp/examples$ sed -ne '/^ds:/,/^;/p' Debug1.eyp | cat -n
 1  ds:
 2            ds ';' D
 3          | D
 4      {
 5        print "Reducing by rule:\n";
 6        print "\tds -> D\n";
 7        $_[1];
 8      }
 9  ;

Ahora ante una frase de la forma D ; ... siempre hay que reducir por ds -> D. La antiderivación a derechas para D;D;S es:

Derivación Árbol
D;D;S <= ds;D;S <= ds;S <= ds;ss <= p
p(ds(ds(D),';',D),';',ss(S))
o  
Mientras que la antiderivación a derechas para D;S es:

Derivación Árbol
D;S <= ds;S <= ds;ss <= p
p(ds(D),';',ss(S))

Recompilamos la nueva versión de la gramática. Las advertencias han desaparecido:

pl@nereida:~/LEyapp/examples$ eyapp Debug1.eyp
pl@nereida:~/LEyapp/examples$

Errores en la Construcción del Arbol

Escribimos el típico programa cliente:

pl@nereida:~/LEyapp/examples$ cat -n usedebug1.pl
 1  #!/usr/bin/perl -w
 2  # usetreebypass.pl prueba2.exp
 3  use strict;
 4  use Debug1;
 5
 6  sub slurp_file {
 7    my $fn = shift;
 8    my $f;
 9
10    local $/ = undef;
11    if (defined($fn)) {
12      open $f, $fn  or die "Can't find file $fn!\n";
13    }
14    else {
15      $f = \*STDIN;
16    }
17    my $input = <$f>;
18    return $input;
19  }
20
21  my $input = slurp_file( shift() );
22
23  my $parser = Debug1->new();
24
25  $parser->Run($input);

y ejecutamos. Cuando damos la entrada D;S introduciendo algunos blancos y retornos de carro entre los terminales ocurre un mensaje de error:

casiano@cc111:~/LPLsrc/Eyapp/examples/debuggingtut$ usedebug1.pl
D

;

S
Reducing by rule:
        ds -> D
Syntax error near end of input line num 1
Como esta conducta es anómala activamos la opción yydebug => 0xF en la llamada a YYParser.

Es posible añadir un parámetro en la llamada a YYParse con nombre yydebug y valor el nivel de depuración requerido. Ello nos permite observar la conducta del analizador. Los posibles valores de depuración son:



Bit Información de Depuración
0x01 Lectura de los terminales
0x02 Información sobre los estados
0x04 Acciones (shifts, reduces, accept ...)
0x08 Volcado de la pila
0x10 Recuperación de errores


Veamos que ocurre cuando damos la entrada D;S introduciendo algunos blancos y retornos de carro entre los terminales:

pl@nereida:~/LEyapp/examples$ usedebug1.pl
D

;

S

----------------------------------------
In state 0:
Stack:[0]
Need token. Got >D<
Shift and go to state 4.
----------------------------------------
In state 4:
Stack:[0,4]
Don't need token.
Reduce using rule 4 (ds --> D): Reducing by rule:
        ds -> D
Back to state 0, then go to state 5.
----------------------------------------
In state 5:
Stack:[0,5]
Need token. Got ><
Syntax error near end of input line num 1
¿Que está pasando? Vemos que después de leer D el analizador sintáctico recibe un end of file. Algo va mal en las comunicaciones entre el analizador léxico y el sintáctico. Repasemos el analizador léxico:
pl@nereida:~/LEyapp/examples$ sed -ne '/sub.*_Lexer/,/^}/p' Debug1.eyp | cat -n
 1  sub _Lexer {
 2
 3          for ($input) {
 4             s{^(\s)}{} and $tokenline += $1 =~ tr{\n}{};
 5             return ('',undef) unless $_;
 6             return ($1,$1) if s/^(.)//;
 7          }
 8          return ('',undef);
 9  }
El error está en la línea 4. ¡Sólo se consume un blanco!. Escribimos una nueva versión Debug2.eyp corrigiendo el problema:
pl@nereida:~/LEyapp/examples$ sed -ne '/sub.*_Lexer/,/^}/p' Debug2.eyp | cat -n
 1  sub _Lexer {
 2
 3          for ($input) {
 4             s{^(\s+)}{} and $tokenline += $1 =~ tr{\n}{};
 5             return ('',undef) unless $_;
 6             return ($1,$1) if s/^(.)//;
 7          }
 8          return ('',undef);
 9  }

Ahora el análisis parece funcionar correctamente:

pl@nereida:~/LEyapp/examples$ usedebug2.pl
D

;

S

----------------------------------------
In state 0:
Stack:[0]
Need token. Got >D<
Shift and go to state 4.
----------------------------------------
In state 4:
Stack:[0,4]
Don't need token.
Reduce using rule 4 (ds --> D): Reducing by rule:
        ds -> D
Back to state 0, then go to state 5.
----------------------------------------
In state 5:
Stack:[0,5]
Need token. Got >;<
Shift and go to state 8.
----------------------------------------
In state 8:
Stack:[0,5,8]
Need token. Got >S<
Shift and go to state 1.
----------------------------------------
In state 1:
Stack:[0,5,8,1]
Need token. Got ><
Reduce using rule 6 (ss --> S): Back to state 8, then go to state 10.
----------------------------------------
In state 10:
Stack:[0,5,8,10]
Don't need token.
Reduce using rule 1 (p --> ds ; ss): Back to state 0, then go to state 2.
----------------------------------------
In state 2:
Stack:[0,2]
Shift and go to state 7.
----------------------------------------
In state 7:
Stack:[0,2,7]
Don't need token.
Accept.

Errores en las Acciones Semánticas

Un tercer tipo de error ocurre cuando el código de una acción semántica no se conduce como esperamos.

Las acciones semánticas son convertidas en métodos anónimos del objeto analizador sintáctico generado por Parse::Eyapp. Puesto que son subrutinas anónimas no podemos utilizar comandos de ruptura del depurador como

b nombre # parar cuando se entre en la subrutina ''nombre''
o bien
c nombredesub  # continuar hasta alcanzar la subrutina ''nombre''
Además el fichero cargado durante la ejecución es el .pm generado. El código en Debug.pm nos es ajeno - fue generado automáticamente por Parse::Eyapp - y puede resultar difícil encontrar en él las acciones semánticas que insertamos en nuestro esquema de traducción.

La estrategia a utilizar para poner un punto de parada en una acción semántica se ilustra en la siguiente sesión con el depurador. Primero arrancamos el depurador con el programa y usamos la opción f file para indicar el nuevo ''fichero de vistas'' por defecto:

pl@nereida:~/LEyapp/examples$ perl -wd usedebug2.pl

Loading DB routines from perl5db.pl version 1.28
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(usedebug2.pl:21):        my $input = slurp_file( shift() );
  DB<1> f Debug2.eyp
1       2       #line 3 "Debug2.eyp"
3
4:      our $VERSION = '0.01';
5
6       7       8       9       10
Ahora usamos la orden b 18 para poner un punto de ruptura en la línea 18. El comando l muestra las correspondientes líneas del fichero .eyp:
  DB<2> b 18
  DB<3> l
11      12      13      14      15      16      #line 17 "Debug2.eyp"
17
18:b          print "Reducing by rule:\n";
19:           print "\tds -> D\n";
20:           $_[1];
La orden c (continuar) hace que el programa se ejecute hasta alcanzar el único punto de ruptura en la línea 18 de Debug2.eyp:
  DB<3> c
D

;

S

Debug2::CODE(0x85129d8)(Debug2.eyp:18):
18:           print "Reducing by rule:\n";
  DB<3> n
Reducing by rule:
En este momento podemos usar cualesquiera comandos del depurador para visualizar el estado interno de nuestro programa y determinar la causa de una potencial conducta anómala de la acción semántica:
Debug2::CODE(0x85129d8)(Debug2.eyp:19):
19:           print "\tds -> D\n";
  DB<3> x $_[0]{GRAMMAR}
0  ARRAY(0x8538360)
   0  ARRAY(0x855aa88)
      0  '_SUPERSTART'
      1  '$start'
      2  ARRAY(0x855ab60)
         0  'p'
         1  '$end'
      3  0
   1  ARRAY(0x855a890)
      0  'p_1'
      1  'p'
      2  ARRAY(0x855a8fc)
         0  'ds'
         1  ';'
         2  'ss'
      3  0
   2  ARRAY(0x855a800)
      0  'p_2'
      1  'p'
      2  ARRAY(0x855a830)
         0  'ss'
      3  0
   3  ARRAY(0x855a764)
      0  'ds_3'
      1  'ds'
      2  ARRAY(0x855a7a0)
         0  'ds'
         1  ';'
         2  'D'
      3  0
   4  ARRAY(0x85421d4)
      0  'ds_4'
      1  'ds'
      2  ARRAY(0x855a6e0)
         0  'D'
      3  0
   5  ARRAY(0x8538474)
      0  'ss_5'
      1  'ss'
      2  ARRAY(0x854f9c8)
         0  'S'
         1  ';'
         2  'ss'
      3  0
   6  ARRAY(0x85383b4)
      0  'ss_6'
      1  'ss'
      2  ARRAY(0x85383f0)
         0  'S'
      3  0
  DB<4>
Con el comando c podemos hacer que la ejecución continúe, esta vez hasta a finalización del programa
  DB<3> c
Debugged program terminated.  Use q to quit or R to restart,
  use o inhibit_exit to avoid stopping after program termination,
  h q, h R or h o to get additional info.
  DB<3>



Subsecciones
next up previous contents index PLPL moodlepserratamodulosperlmonksperldocapuntes LHPgoogleetsiiullpcgull
Sig: Acciones y Acciones por Sup: Análisis Sintáctico con Parse::Eyapp Ant: Parse::Eyapp: Un Generador de Err: Si hallas una errata ...
Casiano Rodríguez León
2013-03-05