Las fuentes de error
cuando se programa con una herramienta como eyapp
son diversas. En esta sección trataremos tres tipos de error:
eyapp
se usa un sólo símbolo
de predicción - no queda claro para el generador
que analizador sintáctico producir
El siguiente programa eyapp
contiene
algunos errores. El lenguaje generado
esta constituido por listas de D
s (por
declaraciones)
seguidas de listas de S
s (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 |
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$
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 1Como 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.
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 10Ahora 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>