Subsecciones


Las Fases de un Compilador

La estructura del compilador, descompuesto en fases, queda explicitada en el código de la subrutina compile:

Esquema del Compilador

 1 package PL::Tutu;
 2 use 5.008004;   # Versión mínima de Perl 5.84
 3 use strict;     # Variables deben ser declaradas, etc.
 4 use warnings;   # Enviar warnings
 5 use IO::File;
 6 use Carp;       # Provee alternativas a "die" and "warn" 
 7 
 8 require Exporter;
 9 
10 our @ISA = qw(Exporter);   # Heredamos los métodos de la clase Exporter
11 our @EXPORT = qw( compile compile_from_file); # Estas funciones serán exportadas
12 our $VERSION = '0.01';     # Variable que define la versión del módulo
13 
14 our %symbol_table;         # La tabla de símbolos $symbol_table{x} contiene
15 our $data;                 # la información asociada con el objeto 'x'
16 our $target;               # tipo, dirección, etc.
17 our @tokens;               # La lista de terminales
18 our $errorflag;
19 our ($lookahead, $value);  # Token actual y su atributo
20 our $tree;                 # referencia al objeto que contiene
21 our $global_address;       # el árbol sintáctico
22 
23 # Lexical analyzer 
24 package Lexical::Analysis;
25 sub scanner {
26 }
27 
28 package Syntax::Analysis;
29 sub parser {
30 }
31 
32 package Machine::Independent::Optimization;
33 sub Optimize {
34 }
35 
36 package Code::Generation;
37 sub code_generator {
38 }
39   
40 package Peephole::Optimization;
41 sub transform {
42 }
43 
44 package PL::Tutu;
45 sub compile {
46   my ($input) = @_; # Observe el contexto!
47   local %symbol_table = (); 
48   local $data = ""; # Contiene todas las cadenas en el programa fuente
49   local $target = ""; # target code
50   local @tokens =();    # "local" salva el valor que será recuperado al finalizar
51   local $errorflag = 0; # el ámbito
52   local ($lookahead, $value) = ();
53   local $tree = undef; # Referencia al árbol sintÃctico abstracto 
54   local $global_address = 0; # Usado para guardar la Ãltima direcciÃn ocupada
55   
56   ########lexical analysis
57   &Lexical::Analysis::scanner($input);
58 
59   ########syntax (and semantic) analysis
60   $tree = &Syntax::Analysis::parser;
61 
62   ########machine independent optimizations
63   &Machine::Independent::Optimization::Optimize;
64 
65   ########code generation
66   &Code::Generation::code_generator;
67 
68   ########peephole optimization
69   &Peephole::Optimization::transform($target);
70 
71   return \$target; #retornamos una referencia a $target
72 }
73 
74 sub compile_from_file {
75   my ($input_name, $output_name) = @_; # Nombres de ficheros
76   my $fhi;                             # de entrada y de salida
77   my $targetref;
78   
79   if (defined($input_name) and (-r $input_name)) {
80     $fhi = IO::File->new("< $input_name");
81   }
82   else { $fhi = 'STDIN'; }
83   my $input;
84   { # leer todo el fichero
85     local $/ = undef; # localizamos para evitar efectos laterales
86     $input = <$fhi>;
87   }
88   $targetref = compile($input);
89 
90   ########code output
91   my $fh = defined($output_name)? IO::File->new("> $output_name") : 'STDOUT';
92   $fh->print($$targetref);
93   $fh->close;
94   1; # El último valor evaluado es el valor retornado
95 }
96 
97 1;  # El 1 indica que la fase de carga termina con éxito
98 # Sigue la documentación ...

Añadiendo Ejecutables

Vamos a añadir un script que use el módulo PL::Tutu para asi poder ejecutar nuestro compilador:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/$ mkdir scripts
lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ cd scripts/
A continuación creamos dos versiones del compilador tutu.pl y tutu y un programa de prueba test01.tutu:

... # despues de crear los ficheros
lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/scripts$ ls
test01.tutu  tutu  tutu.pl
lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/scripts$ cat tutu.pl
#!/usr/bin/perl -w -I../lib/
use PL::Tutu;

PL::Tutu::compile_from_file(@ARGV);

Búsqueda de Librerías en Tiempo de Desarrollo

El programa tutu ilustra otra forma de conseguir que el intérprete Perl busque por la librería que está siendo desarrollada, mediante el uso de use lib:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/scripts$ cat tutu
#!/usr/bin/perl -w
use lib ('../lib');
use PL::Tutu;

&PL::Tutu::compile_from_file(@ARGV);

Una tercera forma (la que recomiendo):

$ export PERL5LIB=~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/lib
$ perl -MPL::Tutu -e 'PL::Tutu::compile_from_file("test01.tutu")'

Añadiendo Los Ejecutables al MANIFEST

Ahora tenemos que añadir estos ficheros en MANIFEST para que formen parte del proyecto. En vez de eso lo que podemos hacer es crear un fichero MANIFEST.SKIP:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ cat MANIFEST.SKIP
\.o$
^\.cvsignore$
/\.cvsignore$
\.cvsignore$
CVS/[^/]+$
\.svn\b
^Makefile$
/Makefile$
^blib/
\.swp$
\.bak$
\.pdf$
\.ps$
\.sal$
pm_to_blib
\.pdf$
\.tar.gz$
\.tgz$
^META.yml$
Ahora al hacer
make manifest
se crea un fichero MANIFEST que contiene los caminos relativos de todos los ficheros en la jerarquía cuyos nombres no casan con una de las expresiones regulares en MANIFEST.SKIP.

Para saber mas sobre MANIFEST léa [*] [10].

No recomiendo el uso de MANIFEST.SKIP. Prefiero un control manual de los ficheros que integran la aplicacion.

Indicando al Sistema de Distribución que los Ficheros son Ejecutables

Es necesario indicarle a Perl que los ficheros añadidos son ejecutables. Esto se hace mediante el parámetro EXE_FILES de WriteMakefile:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ cat Makefile.PL
use 5.008004;
use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
    NAME              => 'PL::Tutu',
    VERSION_FROM      => 'lib/PL/Tutu.pm', # finds $VERSION
    PREREQ_PM         => {}, # e.g., Module::Name => 1.1
    EXE_FILES         => [ 'scripts/tutu.pl', 'scripts/tutu' ],
    ($] >= 5.005 ?     ## Add these new keywords supported since 5.005
      (ABSTRACT_FROM  => 'lib/PL/Tutu.pm', # retrieve abstract from module
       AUTHOR         => 'Lenguajes y Herramientas de Programacion <lhp@>') : ()),
);
Perl utilizará esa información durante la fase de instalación para instalar los ejecutables en el path de búsqueda.

Reconstrucción de la aplicación

A continuación hay que rehacer el Makefile:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ perl Makefile.PL
Writing Makefile for PL::Tutu

Para crear una versión funcional hacemos make:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ make
cp scripts/tutu blib/script/tutu
/usr/bin/perl "-MExtUtils::MY" -e "MY->fixin(shift)" blib/script/tutu
cp scripts/tutu.pl blib/script/tutu.pl
/usr/bin/perl "-MExtUtils::MY" -e "MY->fixin(shift)" blib/script/tutu.pl
Manifying blib/man3/PL::Tutu.3pm

Para crear el MANIFEST hacemos make manifest:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ make manifest
/usr/bin/perl "-MExtUtils::Manifest=mkmanifest" -e mkmanifest
Added to MANIFEST: scripts/tutu

Comprobemos que el test de prueba generado automáticamente por h2xs se pasa correctamente:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ make test
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/PL-Tutu....ok
All tests successful.
Files=1, Tests=1,  0 wallclock secs ( 0.08 cusr +  0.00 csys =  0.08 CPU)

Ejecución

Podemos ahora ejecutar los guiones:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ cd scripts/
lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/scripts$ ls -l
total 12
-rw-r--r--  1 lhp lhp 15 2005-09-29 12:56 test01.tutu
-rwxr-xr-x  1 lhp lhp 92 2005-09-29 13:29 tutu
-rwxr-xr-x  1 lhp lhp 80 2005-09-29 12:58 tutu.pl
lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/scripts$ tutu test01.tutu test01.sal
lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/scripts$ ls -l
total 12
-rw-r--r--  1 lhp lhp  0 2005-09-29 13:53 test01.sal
-rw-r--r--  1 lhp lhp 15 2005-09-29 12:56 test01.tutu
-rwxr-xr-x  1 lhp lhp 92 2005-09-29 13:29 tutu
-rwxr-xr-x  1 lhp lhp 80 2005-09-29 12:58 tutu.pl

Veamos los contenidos del programa fuente test01.tutu que usaremos para hacer una prueba:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu/scripts$ cat test01.tutu
int a,b;
a = 4

Construcción de una Distribución

Para hacer una distribución instalable hacemos make dist:

lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ make dist
rm -rf PL-Tutu-0.01
/usr/bin/perl "-MExtUtils::Manifest=manicopy,maniread" \
        -e "manicopy(maniread(),'PL-Tutu-0.01', 'best');"
mkdir PL-Tutu-0.01
mkdir PL-Tutu-0.01/scripts
mkdir PL-Tutu-0.01/lib
mkdir PL-Tutu-0.01/lib/PL
mkdir PL-Tutu-0.01/t
tar cvf PL-Tutu-0.01.tar PL-Tutu-0.01
PL-Tutu-0.01/
PL-Tutu-0.01/scripts/
PL-Tutu-0.01/scripts/test01.tutu
PL-Tutu-0.01/scripts/tutu
PL-Tutu-0.01/scripts/tutu.pl
PL-Tutu-0.01/META.yml
PL-Tutu-0.01/Changes
PL-Tutu-0.01/MANIFEST
PL-Tutu-0.01/lib/
PL-Tutu-0.01/lib/PL/
PL-Tutu-0.01/lib/PL/Tutu.pm
PL-Tutu-0.01/MANIFEST.SKIP
PL-Tutu-0.01/t/
PL-Tutu-0.01/t/PL-Tutu.t
PL-Tutu-0.01/Makefile.PL
PL-Tutu-0.01/README
rm -rf PL-Tutu-0.01
gzip --best PL-Tutu-0.01.tar
Después de esto tenemos en el directorio de trabajo el fichero PL-Tutu-0.01.tar.gz con la distribución:
lhp@nereida:~/Lperl/src/topdown/PL0506/02fases/PL-Tutu$ ls -ltr
total 72
drwxr-xr-x  2 lhp lhp  4096 2005-09-29 12:01 t
-rw-r--r--  1 lhp lhp  1196 2005-09-29 12:01 README
drwxr-xr-x  3 lhp lhp  4096 2005-09-29 12:01 lib
-rw-r--r--  1 lhp lhp   152 2005-09-29 12:01 Changes
-rw-r--r--  1 lhp lhp   167 2005-09-29 13:23 MANIFEST.SKIP
-rw-r--r--  1 lhp lhp     0 2005-09-29 13:23 pm_to_blib
drwxr-xr-x  6 lhp lhp  4096 2005-09-29 13:23 blib
-rw-r--r--  1 lhp lhp   113 2005-09-29 13:23 MANIFEST.bak
drwxr-xr-x  2 lhp lhp  4096 2005-09-29 13:29 scripts
-rw-r--r--  1 lhp lhp   616 2005-09-29 13:49 Makefile.PL
-rw-r--r--  1 lhp lhp 20509 2005-09-29 13:51 Makefile
-rw-r--r--  1 lhp lhp  3654 2005-09-29 16:34 PL-Tutu-0.01.tar.gz
-rw-r--r--  1 lhp lhp   298 2005-09-29 16:34 META.yml
-rw-r--r--  1 lhp lhp   205 2005-09-29 16:34 MANIFEST


Repaso: Fases de un Compilador

  1. ¿Que hace la declaración package nombredepaquete?
  2. ¿Cual es la función de la declaración use 5.008004?
  3. ¿Cuál es la función de la declaración use strict?
  4. ¿Cuál es la función de la declaración use warnings?
  5. ¿Que diferencia hay entre use warnings y perl -w?
  6. ¿Cuál es la función de la declaración use Carp? ¿Que diferencia hay entre croak y die?
  7. ¿Qué hace la declaración our?
  8. ¿Qué es una variable de paquete?
  9. ¿Cuál es el nombre completo de una variable de paquete?
  10. ¿En que variable especial se situán los argumentos pasados a una subrutina?
  11. ¿Que hace la declaración local?
  12. ¿Cómo se declara una variable léxica?
  13. ¿Cuál es el prefijo para los hashes?
  14. ¿Cómo se hace referencia a un elemento de un hash %h de clave k?
  15. ¿Cómo se hace referencia a un elemento de un array @a de índice 7? ¿Que lugar ocupa ese elemento en el array?
  16. ¿Cuál es el significado de undef?
  17. ¿Cuál es el prefijo para las subrutinas?
  18. Señale la diferencia entre
    my ($input) = @_;
    
    y
    my $input = @_;
    
    Repase [*] [10].
  19. Toda referencia es un escalar: ¿Cierto o falso?
  20. Toda referencia es verdadera ¿Cierto o falso?
  21. ¿Que diferencia hay entre use y require? ¿La línea require Exporter se ejecuta en tiempo de compilación o en tiempo de ejecución?
  22. ¿Que hace la línea our @ISA = qw(Exporter)?. Repáse [*] [10].

  23. ¿Que hace la línea our @EXPORT = qw( compile compile_from_file)?
  24. ¿Que diferencia hay entre EXPORT y EXPORT_OK?. Repase [*] [10].
  25. ¿Que hace la línea our $VERSION = '0.01?
  26. ¿Que valor tiene una variable no incializada? ¿y si es un array?
  27. ¿Que es un array anónimo? (Repase [*] [10])
  28. ¿Que es un hash anónimo? (Repase [*] [10])
  29. ¿Que hace el operador =>?. Repase [*] [10].
  30. ¿En que lugar se dejan los ejecutables asociados con una distribución? ¿Cómo se informa a Perl que se trata de ejecutables?
  31. ¿Cuál es la función de MANIFEST.SKIP? ¿Que hace make manifest?
  32. ¿Que hace la opción -I? ¿Porqué la primera línea de tutu.pl comienza:
    #!/usr/bin/perl -w -I../lib/?
  33. ¿Cómo puedo saber lo que hace el módulo lib? ¿Qué hace la línea use lib ('../lib') en el programa tutu?
  34. ¿Que contiene la variable PERL5LIB?
  35. ¿Cómo se crea una distribución?
  36. ¿Que devuelve -r $input_name en la línea 79? Repase [*] [10].
  37. ¿Cuál es la función de la variable mágica $/? ¿Que se leerá en la línea 86
    85   local $/ = undef;
    86   my $input = <$fhi>;
    
  38. ¿Que hace el operador \? ¿Que relación hay entre \$target y $target?.
  39. Si $targetref es una referencia a la cadena que va a contener el código objeto, ¿Cómo se denota a la cadena referenciada por $targetref? Explique la línea
    92   $fh->print($$targetref);
    


Práctica: Fases de un Compilador

Reproduzca los pasos explicados en la sección 33.2 extendiendo el módulo PL::Tutu con las funciones de compilación y los correspondientes guiones de compilación.

Mejore el script tutu para que acepte opciones desde la línea de comandos. Debera soportar al menos las siguientes opciones:

Use para ello el módulo Getopt::Long . Este módulo provee la función GetOptions la cual se atiene a los estándares de especificación de opciones en la línea de comandos POSIX y GNU. Esta función soporta el uso del guión doble -- y el simple así como admitir el prefijo mas corto que deshace la ambiguedad entre las diferentes opciones.

La llamada a GetOptions analiza la línea de comandos en ARGV inicializa la variable asociada de manera adecuada. Retorna un valor verdadero si la línea de comandos pudo ser procesada con En caso contrario emitirá un mensaje de error y devolverá falso. Recuerde hacer perldoc Getopt::Long para obtener información mas detallada

El siguiente ejemplo ilustra el uso de Getopt::Long. Se hace uso también del módulo (función pod2usage en la línea 63) Pod::Usage el cual permite la documentación empotrada.

nereida:~/LEyapp/examples> cat -n treereg
 1  #!/usr/bin/perl -w
 2  use strict;
 3  use Parse::Eyapp::YATW;
 4  use Parse::Eyapp::Node;
 5  use Parse::Eyapp::Treeregexp;
 6  use Carp;
 7  use Getopt::Long;
 8  use Pod::Usage;
 9
10  my $infile;
11  my $outfile;
12  my $packagename;
13  my $prefix = '';
14  my $syntax = 1;
15  my $numbers = 1;
16  my $severity = 0; # 0 = Don't  check arity. 1 = Check arity. 
17                    # 2 = Check arity and give a warning 3 = ... and croak
18  GetOptions(
19    'in=s'       => \$infile,
20    'out=s'      => \$outfile,
21    'mod=s'      => \$packagename,
22    'prefix=s'   => \$prefix,
23    'severity=i' => \$severity,
24    'syntax!'    => \$syntax,
25    'numbers!'   => \$numbers,
26    'version'    => \&version,
27    'usage'      => \&usage,
28    'help'       => \&man,
29  ) or croak usage();
30
31  # If an argument remains is the inputfile 
32  ($infile) = @ARGV unless defined($infile);
33  die usage() unless defined($infile);
34
35  my $treeparser = Parse::Eyapp::Treeregexp->new(
36                      INFILE   => $infile,
37                      OUTFILE  => $outfile,
38                      PACKAGE  => $packagename,
39                      PREFIX   => $prefix,
40                      SYNTAX   => $syntax,
41                      NUMBERS  => $numbers,
42                      SEVERITY => $severity
43                    );
44
45  $treeparser->generate();
46
47  sub version {
48    print "Version $Parse::Eyapp::Treeregexp::VERSION\n";
49    exit;
50  }
51
52  sub usage {
53    print <<"END_ERR";
54  Supply the name of a file containing a tree grammar (.trg)
55  Usage is:
56  treereg [-m packagename] [[no]syntax] [[no]numbers] [-severity 0|1|2|3] \
57          [-p treeprefix] [-o outputfile] -i filename[.trg]
58  END_ERR
59    exit;
60  }
61
62  sub man {
63    pod2usage(
64      -exitval => 1,
65      -verbose => 2
66    );
67  }
68  __END__
69
70  =head1 SYNOPSIS
71
72    treereg [-m packagename] [[no]syntax] [[no]numbers] [-severity 0|1|2|3] \
73            [-p treeprefix] [-o outputfile] -i filename[.trg]
74    treereg [-m packagename] [[no]syntax] [[no]numbers] [-severity 0|1|2|3] \
75            [-p treeprefix] [-o outputfile] filename[.trg]
76 ... # Follows the documentation bla, bla, bla

Ahora podemos ejecutar el guión de múltiples formas:

nereida:~/LEyapp/examples> treereg -nos -nonu -se 3 -m Tutu Foldonly1.trg 
nereida:~/LEyapp/examples> treereg -nos -nonu -s 3 -m Tutu Foldonly1.trg  
Option s is ambiguous (severity, syntax)
nereida:~/LEyapp/examples> treereg -nos -bla -nonu -m Tutu Foldonly1.trg
Unknown option: bla
nereida:~/LEyapp/examples>

La librería estandar de Perl incluye el módulo Getopt::Long. No es el caso de Pod::Usage. Descarge el módulo e instalelo en un directorio local en el que tenga permisos. Si es preciso repase las secciones [*] [10] y [*] [10] de los apuntes de introducción a Perl.

Casiano Rodríguez León
2013-04-23