GAP.pm
que provea una subrutina gap
para
la generación automática de un APDR supuesto que la gramática de entrada es
LL(1).
La subrutina gap
recibe como entrada la gramática según la estructura de
datos generada por la función Grammar::Parse
de la versión 0.3 del módulo
Grammar.
La estructura de datos generada por la función Grammar::Parse
se explicó en la práctica
4.6.8.
La estructura ha sido extendida
en esta versión para incluir el código que se sitúe en la zona de cola.
Por ejemplo, dada la gramática de entrada:
Grammar/03/scripts$ cat -n aSb.yp 1 %% 2 S: 3 | 'a' S 'b' 4 ; 5 %% 6 7 sub Lex { 8 local $_ = shift; # input 9 my @tokens; 10 11 12 while ($_) { 13 s/^\s*//; # fuera blancos 14 push @tokens, $1, $1 if s/^(.)//s 15 } 16 @tokens; 17 } 18 19 sub main { 20 my $filename = shift; 21 my $input; 22 23 if (defined($filename)) { 24 local $/ = undef; 25 open my $FILE, $filename or die "No se pudo abrir $filename\n"; 26 $input = <$FILE>; 27 close($FILE); 28 } 29 else { $input = <STDIN> } 30 31 my @tokens = Lex($input); 32 Parse(@tokens); # Llamada al analizador generado 33 print "Sintácticamente correcto\n"; 34 }se genera la siguiente estructura de datos:
{ 'SYMS' => { 'S' => 2, 'b' => 3, 'a' => 3 }, # Símbolo => línea de aparición 'NULL' => { 'S' => 1 }, # Símbolos que se anulan 'RULES' => [ # Reglas [ 'S', [] ], # S produce vacío [ 'S', [ 'a', 'S', 'b' ] ] # S-> a S b ], 'START' => 'S', # Símbolo de arranque 'TERM' => [ 'b', 'a' ], # Terminales 'NTERM' => { 'S' => [ 0, 1 ] } # Variables sintácticas e índices de las reglas de esa variable 'TAIL' => [ # [ 'Código de cola', línea en la que está el segundo %% ] ' sub Lex { local $_ = shift; # input my @tokens; while ($_) { s/^\\s*//; # fuera blancos push @tokens, $1, $1 if s/^(.)//s } @tokens; } sub main { my $filename = shift; my $input; if (defined($filename)) { local $/ = undef; open my $FILE, $filename or die "No se pudo abrir $filename\\n"; $input = <$FILE>; close($FILE); } else { $input = <STDIN> } my @tokens = Lex($input); my $ok = Parse(@tokens); # Llamada al analizador generado print "Sintácticamente correcto\\n" if $ok; } ', 5 ], # línea en la que está el segundo %% };
Asi pues la entrada con clave TAIL
contiene el código auxiliar de
cola. Este código debe ser incluido por su programa dentro del texto del
paquete generado por gap
.
La función gap
también recibe como entrada el nombre
del package:
$package_text = &gap($grammar, 'Package_name');La función
gap
retorna
una cadena conteniendo el package
en el que estan las subrutinas del
analizador sintáctico.
La idea es que dicha cadena se salvará en un fichero
con nombre Package_name.pm
que podrá posteriormente ser usado
(use Package_name
) por un programa que necesite analizar entradas
que se conforman de acuerdo a la especificación de la gramática.
parser
(esto es, su nombre completo es: Package_name::parser
.
Evidentemente Package_name
debe ser un nombre Perl válido).
Ninguna subrutina deberá ser exportada sino que deberán ser llamadas
por su nombre completo.
La subrutina parser
recibe como argumento el array de
terminales, obtiene el primer terminal y llama a la subrutina
asociada con el símbolo de arranque. Los terminales están representados
como parejas
.
Observe que, una vez que la cadena $package_text
conteniendo el paquete ha sido
generada y salvada en un fichero con nombre Package_name.pm
, podemos escribir
un programa cliente:
use strict; use Package_name; &Package_name::main;
Este programa espera una entrada desde fichero o STDIN
e
informa si dicha entrada es sintácticamente correcta o no
para la gramática en cuestión.
Para facilitar la escritura de GAP.pm
pueden hacer uso
del módulo PL::FirstFollow
el cual calcula los
y los
. El módulo PL::FirstFollow
depende de
Set::Scalar
escrito por
Jarkko Hietaniemi: instálelo primero.
Deberá familiarizarse con PL::FirstFollow, rellenar la
documentación de todas las subrutinas (apariciones de ????
en el texto)
y escribir la documentación siguiendo el template que se provee.
Rellene los fragmentos de código que se han sustituido por
signos de interrogación.
Haga un estudio de cubrimiento
y añada pruebas para mejorar el actual. El actual cubrimiento es:
---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ ...ammar-0.03/lib/Grammar.pm 100.0 n/a n/a 100.0 0.0 75.3 97.2 blib/lib/PL/FirstFollow.pm 100.0 92.9 50.0 100.0 0.0 24.7 95.1 Total 100.0 92.9 50.0 100.0 0.0 100.0 95.5 ---------------------------- ------ ------ ------ ------ ------ ------ ------Si observa un fallo en PL::FirstFollow háganoslo saber y además de resolverlo escriba una prueba para detectar el fallo.
Haga un estudio de profiling de su aplicación.
Un módulo que puede facilitar la escritura de esta práctica es Text::Template debido a Mark Jason Dominus. El siguiente ejemplo de uso es un fragmento de un traductor - que nunca acabo de terminar - que toma con fuente un fichero en el formato que usa Moodle para los cuestionarios (conocido como formato GIFT) y lo convierte en un cuestionario LATEX:
lhp@nereida:~/projects/Gift2LaTeX/Gift2LaTeX/lib$ cat -n Gift2LaTeX.pm 1 package Gift2LaTeX; 2 3 use strict; 4 use warnings; 5 use Gift; 6 use Text::Template; 7 use HTML::Latex; .. ...................................................... 49 package Gift::TRUEFALSE; # True-false questions belong to this class 50 51 { # closure 52 53 die "Can't find $TEMPLATE_DIR/TRUEFALSE_question.tep\n" 54 unless -e "$TEMPLATE_DIR/TRUEFALSE_question.tep"; 55 my $tfq_tmpl = Text::Template->new( #tfq = true-false question 56 DELIMITERS => ['%<', '%>']; 57 SOURCE => "$TEMPLATE_DIR/TRUEFALSE_question.tep", 58 ); .. ...................................................... 67 sub gen_latex { 68 my $self = shift; 69 70 ########## Generate latex for question 71 my $prefix = $self->PREFIX; 72 73 my $sufix = $self->POSTSTATE; 74 75 $self->Error("Only HTML and PLAIN formats are supported\n") 76 unless (!$self->FORMAT or ($self->FORMAT =~ m{html|plain}i)); 77 78 my ($prefix_tex, $sufix_tex); 79 if (defined($self->FORMAT) and $self->FORMAT =~ m{plain}i) { 80 $prefix_tex = $prefix; 81 $sufix_tex = $sufix; 82 } 83 else { # HTML by default .. ...................................................... 86 } 87 my $params = { 88 prefix => $prefix_tex, 89 sufix => $sufix_tex, 90 separator => $separator, 91 label => $label_prefix.$question_number, 92 question_number => $question_number 93 }; 94 my $question_tex = $tfq_tmpl->fill_in(HASH => $params); 96 ########## Generate latex for answer ... .................................... 101 } 102 }En la línea 55 se crea el template. El template se lee desde el fichero
"$TEMPLATE_DIR/TRUEFALSE_question.tep"
cuyo contenido es una mezcla
de texto (en este caso texto LATEX y HTML) con código Perl:
El código Perl aparece acotado entre los delimitadores '%<'
y '%>'
.
lhp@nereida:~/projects/Gift2LaTeX/Gift2LaTeX/etc/en$ cat -n TRUEFALSE_question.tep 1 \ begin{latexonly} 2 %<$separator%> 3 \ label{question:%<$label%>} 4 %<$prefix%> 5 6 \ begin{center} 7 \ begin{tabular}{llll} 8 $\ bigcirc$ & TRUE & $\ bigcirc$ & FALSE 9 \ end{tabular} 10 11 \noindent %<$sufix%> 12 \ end{center} 13 \ end{latexonly} 14 15 \ begin{htmlonly} 16 %<$separator%> 17 \ label{question:%<$label%>} 18 %<$prefix%> 19 20 \ begin{center} 21 \ begin{tabular}{llll} 22 \ htmlref{$\bigcirc$}{answer:%<$label%>} & TRUE & 23 \ htmlref{$\bigcirc$}{answer:%<$label%>} & FALSE 24 \ end{tabular} 25 26 \ noindent %<$sufix%> 27 \ end{center} 28 \ end{htmlonly}
El template se rellena en las líneas 87-94. En esa llamada se ejecuta el código Perl incrustado en el esqueleto y su resultado se inserta en la posición que ocupa en el texto.
my $usage = "Usage: $0 <file> [-full] [-o] [-beans]\n" . "Options:\n" . " -full : produce a full dump\n" . " -o : dump in octal\n" . " -beans : source is Java\n" ;ponga el punto al principio de la siguiente línea, no al final.
Pero cuando el número de líneas es grande es mejor usar un here document o documento aqui. Veamos un ejemplo:
print <<"EOI"; El programa se deberá ejecutar con: $0 numfiles $opciones initialvalue EOIPara definir un ``documento aqui'' se escribe la etiqueta entrecomillada y precedida de
<<
y
sigue el texto que consituye el here document
que se delimita por una línea en blanco que empieza por la etiqueta.
Al documento aquí se le trata como una cadena de doble comilla si
la etiqueta aparece en doble comilla y como de comilla simple
si la etiqueta esta entre comillas simples.
Observe que el punto y coma se escribe despues
de la primera aparición
de la etiqueta.
Un problema con el uso de los heredoc es que rompen la estructura normal del sangrado:
if ($usage_error) { warn <<'END_USAGE'; Usage: qdump <file> [-full] [-o] [-beans] Options: -full : produce a full dump -o : dump in octal -beans : source is Java END_USAGE }Es mejor que cada heredoc se aisle en una subrutina y se parametrice con las variables que van a ser interpoladas:
sub build_usage { my ($prog_name, $file_name) = @_; return <<"END_USAGE"; Usage: $prog_name $file_name [-full] [-o] [-beans] Options: -full : produce a full dump -o : dump in octal -beans : source is Java END_USAGE }
que mas tarde puede ser llamado con los valores de interpolación adecuados:
if ($usage_error) { warn build_usage($PROGRAM_NAME, $requested_file); }Véase el libro de Conway Perl Best Practices [7] para mas detalles sobre buenas prácticas de programación con heredocs.