Parse::Lex
nos permite crear un analizador léxico.
La estrategia seguida es mover el puntero de búsqueda dentro de la cadena
a analizar utilizando conjuntamente el operador pos()
y el ancla
\G
.
> cat -n tokenizer.pl 1 #!/usr/local/bin/perl 2 3 require 5.004; 4 #BEGIN { unshift @INC, "../lib"; } 5 6 $^W = 0; 7 use Parse::Lex; 8 print STDERR "Version $Parse::ALex::VERSION\n"; 9 10 @token = ( 11 qw( 12 ADDOP [-+] 13 LEFTP [\(] 14 RIGHTP [\)] 15 INTEGER [1-9][0-9]* 16 NEWLINE \n 17 ), 18 qw(STRING), [qw(" (?:[^"]+|"")* ")], 19 qw(ERROR .*), sub { 20 die qq!can\'t analyze: "$_[1]"\n!; 21 } 22 ); 23 24 Parse::Lex->trace; 25 $lexer = Parse::Lex->new(@token); 26 27 $lexer->from(\*DATA); 28 print "Tokenization of DATA:\n"; 29 30 TOKEN:while (1) { 31 $token = $lexer->next; 32 if (not $lexer->eoi) { 33 print "Record number: ", $lexer->line, "\n"; 34 print "Type: ", $token->name, "\t"; 35 print "Content:->", $token->text, "<-\n"; 36 } else { 37 last TOKEN; 38 } 39 } 40 41 __END__ 42 1+2-5 43 "This is a multiline 44 string with an embedded "" in it" 45 this is an invalid string with a "" in it"La línea 3 usa la palabra reservada
require
la cual normalmente indica la
existencia de una dependencia. Cuando se usa como:
require "file.pl";simplemente carga el fichero en nuestro programa. Si se omite el sufijo, se asume
.pm
.
Si el argumento, como es el caso, es un número o un número de versión, se compara el
número de versión de perl (como se guarda en $]
) y se compara con el argumento.
si es mas pequeño, se aborta la ejecución.
La línea 4, aunque comentada, contiene una definición del incializador BEGIN
.
Si aparece, este código será ejecutado tan pronto como sea posible.
el intérprete Perl busca los módulos en uno de varios directorios estándar
contenidos en la variable de entorno PERL5LIB
. Esa lista esta disponible
en un programa Perl a través de la variable @INC
.
Recuerda que el compilador sustituye cada ::
por el separador de caminos.
Asi la orden:
unshift @INC, "../lib";
coloca este directorio entre los
que Perl realiza la búsqueda por módulos.
En mi máquina los mósulos de análisis léxico están en:
> locate Lex.pm /usr/local/lib/perl5/site_perl/5.8.0/Parse/ALex.pm /usr/local/lib/perl5/site_perl/5.8.0/Parse/CLex.pm /usr/local/lib/perl5/site_perl/5.8.0/Parse/Lex.pm /usr/local/lib/perl5/site_perl/5.8.0/Parse/YYLex.pmLa línea 6 establece a 0 la variable
$^W
o $WARNING
la cual
guarda el valora actual de la opción de warning, si es cierto o falso.
La línea 7 indica que hacemos uso de la clase Parse::Lex
y la línea 8 muestra el número de versión. La clase ALex
es la clase abstracta desde la que heredan las otras. Si editas
el módulo ALex.pm
encontrarás el siguiente comentario que aclara la
jerarquía de clases:
# Architecture: # Parse::ALex - Abstract Lexer # | # +----------+ # | | # | Parse::Tokenizer # | | | # LexEvent Lex CLex ... - Concrete lexersen ese mismo fichero encontrarás la declaración de la variable:
$Parse::ALex::VERSION = '2.15';Las líneas de la 10 a la 22 definen el array
@token
:
10 @token = ( 11 qw( 12 ADDOP [-+] 13 LEFTP [\(] 14 RIGHTP [\)] 15 INTEGER [1-9][0-9]* 16 NEWLINE \n 17 ), 18 qw(STRING), [qw(" (?:[^"]+|"")* ")], 19 qw(ERROR .*), sub { 20 die qq!can\'t analyze: "$_[1]"\n!; 21 } 22 );Esto da lugar a un array de la forma:
"ADDOP", "[-+]", "LEFTP", "[(]", "RIGHTP", "[)]", "INTEGER", "[1-9][0-9]*", "NEWLINE", "\n", "STRING", ARRAY(0x811f55c), "ERROR", ".*", CODE(0x82534b8)Observa que los elementos 11 y 14 son referencias. El primero a un array anónimo conteniendo la expresión regular
"(?:[^"]+
"")*"| para las STRING
y el segundo es una
referencia a la subrutina anónima que muestra el mensaje de error.
La línea 24 llama al método trace
como método de la clase, el cual activa
el modo ``traza''. La activación del modo traza debe establecerse antes de
la creación del analizador léxico. Es por esto que precede
a la llamada $lexer = Parse::Lex->new(@token)
en la línea 25.
Se puede desactivar el modo traza sin mas que hacer una segunda llamada a este método.
El método admite la forma trace OUTPUT
, siendo OUTPUT
un
nombre de fichero o un manipulador ed ficheros. En este caso la traza se redirije
a dicho fichero.
La llamada al método from
en la línea 27 establece la fuente de los datos a ser
analizados. el argumento de este método puede ser una cadena o una lista de cadenas o una referencia
a un manipulador de fichero. En este caso la
llamada $lexer->from(\*DATA)
establece que la entrada se produce
dede el manipulador de ficheros especial DATA
, el cual se refiere a todo el texto
que sigue al token __END__
en el fichero que
contiene el guión Perl.
La llamada al método next
en la línea 31 fuerza la búsqueda por el
siguiente token. Devuelve un objeto de la clase Parse::Token
.
Existe un token especial, Token::EOI
| que indica el final de los datos.
En el ejemplo, el final se detecta a través del método eoi
.
El método line
es usado para devolver el número de línea del registro actual.
Los objetos token son definidos mediante la clase Parse::Token
, la cual viene con
Parse::Lex
. la definición de un token normalmente conlleva
un nombre simbólico (por ejemplo, INTEGER
), seguido de una expresión
regular. Se puede dar como tercer argumento una referencia a una subrutina anónima,
en cuyo caso la subrutina es llamada cada vez que se reconoce el token.
Los argumentos que recibe la subrutina son la referencia al objeto token
y la cadena reconocida por la expresión regular.
El valor retornado por la subrutina es usado como los nuevos contenidos de la cadena asociada
con el objeto token en cuestión.
Un objeto token tiene diversos métodos asociados.
Asi el método name
devuelve el nombre del token.
El método text
devuelve lac adena de caracteres reconocida por medio del token.
Se le puede pasar un parámetro text EXPR
en cuyo caso EXPR
define la cadena de
caracteres asociada con el lexema.
> tokenizer.pl Version 2.15 Trace is ON in class Parse::Lex Tokenization of DATA: [main::lexer|Parse::Lex] Token read (INTEGER, [1-9][0-9]*): 1 Record number: 1 Type: INTEGER Content:->1<- [main::lexer|Parse::Lex] Token read (ADDOP, [-+]): + Record number: 1 Type: ADDOP Content:->+<- [main::lexer|Parse::Lex] Token read (INTEGER, [1-9][0-9]*): 2 Record number: 1 Type: INTEGER Content:->2<- [main::lexer|Parse::Lex] Token read (ADDOP, [-+]): - Record number: 1 Type: ADDOP Content:->-<- [main::lexer|Parse::Lex] Token read (INTEGER, [1-9][0-9]*): 5 Record number: 1 Type: INTEGER Content:->5<- [main::lexer|Parse::Lex] Token read (NEWLINE, \n): Record number: 1 Type: NEWLINE Content:-> <- [main::lexer|Parse::Lex] Token read (STRING, \"(?:[^\"]+|\"\")*\"): "This is a multiline string with an embedded "" in it" Record number: 3 Type: STRING Content:->"This is a multiline string with an embedded "" in it"<- [main::lexer|Parse::Lex] Token read (NEWLINE, \n): Record number: 3 Type: NEWLINE Content:-> <- [main::lexer|Parse::Lex] Token read (ERROR, .*): this is an invalid string with a "" in it" can't analyze: "this is an invalid string with a "" in it""