

El siguiente programa calculator.pl
recibe como entrada una expresión en infijo.
La ejecución consta de dos bucles. En la primera parte se inyecta a la jerarquía de clases de los AST generados para las expresiones en infijo una semántica que permite evaluar la expresión:
58 require EvalCalc;
59
60 test_calc(
61 'Evaluating infix arithmetic expressions (CTRL-D to end in unix) ',
62 sub { print &Data::Dumper::Dumper(shift()) },
63 );
En esta primera parte mostraremos además el AST
construido para la expresión infija de entrada.
pl@nereida:~/Lregexpgrammars/demo$ ./calculator.pl Evaluating infix arithmetic expressions (CTRL-D to end in unix) 8-4-2 $VAR1 = bless( { 'operands' => [ bless( { 'operands' => [ bless( { 'operands' => [ bless( { 'operands' => [ bless( { 'operands' => [ bless( { '' => '8', 'val' => '8' }, 'factor' ) ], '' => '8' }, 'factorial' ) ], '' => '8' }, 'power' ) ], '' => '8' }, 'uneg' ) ], '' => '8' }, 'term' ), bless( { 'operands' => [ bless( { 'operands' => [ bless( { 'operands' => [ bless( { 'operands' => [ bless( { '' => '4', 'val' => '4' }, 'factor' ) ], '' => '4' }, 'factorial' ) ], '' => '4' }, 'power' ) ], '' => '4' }, 'uneg' ) ], '' => '4' }, 'term' ), bless( { 'operands' => [ bless( { 'operands' => [ bless( { 'operands' => [ bless( { 'operands' => [ bless( { '' => '2', 'val' => '2' }, 'factor' ) ], '' => '2' }, 'factorial' ) ], '' => '2' }, 'power' ) ], '' => '2' }, 'uneg' ) ], '' => '2' }, 'term' ) ], '' => '8-4-2', 'operators' => [ '-', '-' ] }, 'expr' ); 2Observamos que la asociatividad es la correcta. El
2 final es el resultado de la evaluación de
8-4-2.
La estructura del árbol se corresponde con la de la gramática:
8 my $rbb = do {
9 use Regexp::Grammars;
10
11 qr{
12 \A<expr>\z
13
14 <objrule: expr> <[operands=term]> ** <[operators=addop]>
15
16 <objrule: term> <[operands=uneg]> ** <[operators=mulop]>
17
18 <objrule: uneg> <[operators=minus]>* <[operands=power]>
19
20 <objrule: power> <[operands=factorial]> ** <[operators=powerop]>
21
22 <objrule: factorial> <[operands=factor]> <[operators=(!)]>*
23
24 <objrule: factor> <val=([+-]?\d+(?:\.\d*)?)>
25 | \( <MATCH=expr> \)
26
27 <token: addop> [+-]
28
29 <token: mulop> [*/]
30
31 <token: powerop> \*\*|\^
32
33 <token: minus> - <MATCH=(?{ 'NEG' })>
34
35 }x;
36 };
Ahora, en una segunda parte sobreescribimos los métodos
sem que describen la semántica para producir una traducción
de infijo a postfijo:
66 require PostfixCalc;
67 test_calc('Translating expressions to postfix (CTRL-D to end in unix) ');
Ahora al proporcionar la entrada 6--3! obtenemos:
Translating expressions to postfix (CTRL-D to end in unix) 6--3! 6 3 ! ~ -Aquí
~ es el operador de negación unaria y ! es el operador
factorial.
Estos son los ficheros que integran la aplicación:
pl@nereida:~/Lregexpgrammars/demo/calculator$ tree . |-- EvalCalc.pm # Soporte para la evaluación de la expresión: sem |-- Operator.pm # Soporte a las clases nodo: recorridos |-- PostfixCalc.pm # Soporte para la traducción a postfijo: sem `-- calculator.pl # programa principal
En el programa principal definimos la gramática
y escribimos una subrutina test_calc
que realiza el parsing.
pl@nereida:~/Lregexpgrammars/demo/calculator$ cat -n calculator.pl 1 #!/usr/bin/env perl5.10.1 2 use strict; 3 use warnings; 4 use 5.010; 5 use Data::Dumper; 6 $Data::Dumper::Indent = 1; 7 8 my $rbb = do { 9 use Regexp::Grammars; 10 11 qr{ 12 \A<expr>\z 13 14 <objrule: expr> <[operands=term]> ** <[operators=addop]> 15 16 <objrule: term> <[operands=uneg]> ** <[operators=mulop]> 17 18 <objrule: uneg> <[operators=minus]>* <[operands=power]> 19 20 <objrule: power> <[operands=factorial]> ** <[operators=powerop]> 21 22 <objrule: factorial> <[operands=factor]> <[operators=(!)]>* 23 24 <objrule: factor> <val=([+-]?\d+(?:\.\d*)?)> 25 | \( <MATCH=expr> \) 26 27 <token: addop> [+-] 28 29 <token: mulop> [*/] 30 31 <token: powerop> \*\*|^ 32 33 <token: minus> - <MATCH=(?{ 'NEG' })> 34 35 }x; 36 }; 37 38 sub test_calc { 39 my $prompt = shift; 40 my $handler = shift; 41 42 say $prompt; 43 while (my $input = <>) { 44 chomp($input); 45 if ($input =~ m{$rbb}) { 46 my $tree = $/{expr}; 47 $handler->($tree) if $handler; 48 49 say $tree->ceval; 50 51 } 52 else { 53 say("does not match"); 54 } 55 } 56 } 57 58 require EvalCalc; 59 60 test_calc( 61 'Evaluating infix arithmetic expressions (CTRL-D to end in unix) ', 62 sub { print &Data::Dumper::Dumper(shift()) }, 63 ); 64 65 66 require PostfixCalc; 67 test_calc('Translating expressions to postfix (CTRL-D to end in unix) ');
Los nodos del AST poseen un método ceval que se encarga de
realizar la traducción del nodo.
pl@nereida:~/Lregexpgrammars/demo/calculator$ cat -n Operator.pm
1 # Class hierarchy diagram:
2 # $ vgg -t 'Operator(LeftBinaryOp(expr,term),RightBinaryOp(power),PreUnaryOp(uneg),PostUnaryOp(factorial))'
3 # +--------+
4 # |Operator|
5 # +--------+
6 # .---------------.----^--------.-------------.
7 # +------------+ +-------------+ +----------+ +-----------+
8 # |LeftBinaryOp| |RightBinaryOp| |PreUnaryOp| |PostUnaryOp|
9 # +------------+ +-------------+ +----------+ +-----------+
10 # .---^--. | | |
11 # +----+ +----+ +-----+ +----+ +---------+
12 # |expr| |term| |power| |uneg| |factorial|
13 # +----+ +----+ +-----+ +----+ +---------+
14 #
15 #
16 # NOTE: package "factor" actually implements numbers and is
17 # outside this hierarchy
18 #
19 package Operator;
20 use strict;
21 use Carp;
22
23 sub Operands {
24 my $self = shift;
25
26 return () unless exists $self->{operands};
27 return @{$self->{operands}};
28 }
29
30 sub Operators {
31 my $self = shift;
32
33 return () unless exists $self->{operators};
34 return @{$self->{operators}};
35 }
36
37 sub sem {
38 confess "not defined sem";
39 }
40
41 sub make_sem {
42 my $class = shift;
43 my %semdesc = @_;
44
45 for my $class (keys %semdesc) {
46 my %sem = %{$semdesc{$class}};
47
48 # Install 'sem' method in $class
49 no strict 'refs';
50 no warnings 'redefine';
51 *{$class."::sem"} = sub {
52 my ($self, $op) = @_;
53 $sem{$op}
54 };
55 }
56 }
57
58 package LeftBinaryOp;
59 use base qw{Operator};
60
61 sub ceval {
62 my $self = shift;
63
64 # recursively evaluate the children first
65 my @operands = map { $_->ceval } $self->Operands;
66
67 # then combine them
68 my $s = shift @operands;
69 for ($self->Operators) {
70 $s = $self->sem($_)->($s, shift @operands);
71 }
72 return $s;
73 }
74
75 package RightBinaryOp;
76 use base qw{Operator};
77
78 sub ceval {
79 my $self = shift;
80
81 # recursively evaluate the children first
82 my @operands = map { $_->ceval } $self->Operands;
83
84 # then combine them
85 my $s = pop @operands;
86 for (reverse $self->Operators) {
87 $s = $self->sem($_)->(pop @operands, $s);
88 }
89 return $s;
90 }
91
92 package PreUnaryOp;
93 use base qw{Operator};
94
95 sub ceval {
96 my $self = shift;
97
98 # recursively evaluate the children first
99 my @operands = map { $_->ceval } $self->Operands;
100
101 # then combine them
102 my $s = shift @operands;
103 for (reverse $self->Operators) {
104 $s = $self->sem($_)->($s);
105 }
106 return $s;
107 }
108
109 package PostUnaryOp;
110 use base qw{Operator};
111
112 sub ceval {
113 my $self = shift;
114
115 # recursively evaluate the children first
116 my @operands = map { $_->ceval } $self->Operands;
117
118 # then combine them
119 my $s = shift @operands;
120 for ($self->Operators) {
121 $s = $self->sem($_)->($s);
122 }
123 return $s;
124 }
125
126 package term;
127 use base qw{LeftBinaryOp};
128
129 package expr;
130 use base qw{LeftBinaryOp};
131
132 package power;
133 use base qw{RightBinaryOp};
134
135 package uneg;
136 use base qw{PreUnaryOp};
137
138 package factorial;
139 use base qw{PostUnaryOp};
140
141 package factor;
142
143 sub ceval {
144 my $self = shift;
145
146 return $self->{val};
147 }
148
149 1;
pl@nereida:~/Lregexpgrammars/demo/calculator$ cat -n EvalCalc.pm
1 package EvalCalc;
2 use strict;
3 use Carp;
4
5 use Operator;
6
7 ####
8 sub f {
9 $_[0]>1?$_[0]*f($_[0]-1):1;
10 }
11
12 sub fac {
13 my $n = shift;
14
15 confess "Not valid number" unless $n =~ /^\d+$/;
16 f($n);
17 };
18
19 my $s = sub { shift() ** shift() };
20
21 Operator->make_sem(
22 expr => {
23 '+' => sub { shift() + shift() },
24 '-' => sub { shift() - shift() },
25 },
26 term => {
27 '*' => sub { shift() * shift() },
28 '/' => sub { shift() / shift() },
29 },
30 power => {
31 '^' => $s,
32 '**' => $s,
33 },
34 uneg => {
35 'NEG' => sub { -shift() },
36 },
37 factorial => {
38 '!' => \&fac,
39 },
40 );
41
42 1;
pl@nereida:~/Lregexpgrammars/demo/calculator$ cat -n PostfixCalc.pm
1 package PostfixCalc;
2 use strict;
3
4 use Operator;
5
6 # Modify semantics: now translate to postfix
7 my $powers = sub { shift().' '.shift().' **' };
8
9 Operator->make_sem(
10 expr => {
11 '+' => sub { shift().' '.shift().' +' },
12 '-' => sub { shift().' '.shift().' -' },
13 },
14 term => {
15 '*' => sub { shift().' '.shift().' *' },
16 '/' => sub { shift().' '.shift().' /' },
17 },
18 power => {
19 '^' => $powers,
20 '**' => $powers,
21 },
22 uneg => {
23 # use ~ for unary minus
24 'NEG' => sub { shift().' ~' },
25 },
26 factorial => {
27 '!' => sub { shift().' !'},
28 },
29 );
30
31 1;
pl@nereida:~/Lregexpgrammars/demo$ cat -n calculator.pl 1 #!/usr/bin/env perl5.10.1
$handler en test_calc:
42 sub test_calc {
43 my $prompt = shift;
44 my $handler = shift;
45
46 say $prompt;
47 while (my $input = <>) {
48 chomp($input);
49 if ($input =~ m{$rbb}) {
50 my $tree = $/{expr};
51 $handler->($tree) if $handler;
52
53 say $tree->ceval;
54
55 }
56 else {
57 say("does not match");
58 }
59 }
60 }
make_sem, fac y las llamadas a make_sem
en un módulo Calculator::Semantics aparte.
2*3+4 se traducirá como + * 2 3 4

