

Aunque la veamos como una fase separada del análisis sintáctico, puede
en numerosas ocasiones llevarse a cabo al mismo tiempo que se construye
el árbol. Así lo hacemos en este ejemplo: incrustamos la acción semántica
en la correspondiente rutina de análisis sintáctico. Así, en la rutina term, una vez
que hemos obtenido los dos operandos, comprobamos que son de tipo numérico
llamando (línea 8) a
Semantic::Analysis::check_type_numeric_operator:
Observe como aparece un nuevo atributo TYPE decorando el nodo
creado (línea 9):
1 sub term() {
2 my ($t, $t2);
3
4 $t = factor;
5 if ($lookahead eq '*') {
6 match('*');
7 $t2 = term;
8 my $type = Semantic::Analysis::check_type_numeric_operator($t, $t2, '*');
9 $t = TIMES->new( LEFT => $t, RIGHT => $t2, TYPE => $type);
10 }
11 return $t;
12 }
En el manejo de errores de tipo, un tipo especial $err_type
es usado para indicar un error de tipo:
sub check_type_numeric_operator {
my ($op1, $op2, $operator) = @_;
my $type = numeric_compatibility($op1, $op2, $operator);
if ($type == $err_type) {
Error::fatal("Operandos incompatibles para el operador $operator")
}
else {
return $type;
}
}
La subrutina numeric_compatibility comprueba que los
dos operandos son de tipo numérico y devuelve el correspondiente
tipo. Si ha ocurrido un error de tipo, intenta encontrar
un tipo conveniente para el operando:
sub numeric_compatibility {
my ($op1, $op2, $operator) = @_;
if (($op1->TYPE == $op2->TYPE) and is_numeric($op1->TYPE)) {
return $op1->TYPE; # correct
}
... # código de recuperación de errores de tipo
}
sub is_numeric {
my $type = shift;
return ($type == $int_type); # añadir long, float, double, etc.
}
Es parte del análisis semántico la declaración de tipos:
sub declaration() {
my ($t, $class, @il);
if (($lookahead eq 'INT') or ($lookahead eq 'STRING')) {
$class = $lookahead;
$t = &type();
@il = &idlist();
&Semantic::Analysis::set_types($t, @il);
&Address::Assignment::compute_address($t, @il);
return $class->new(TYPE => $t, IDLIST => \@il);
}
else {
Error::fatal('Se esperaba un tipo');
}
}
Para ello se utiliza una tabla de símbolos que es un hash %symbol_table
indexado en los identificadores del programa:
sub set_types {
my $type = shift;
my @vars = @_;
foreach my $var (@vars) {
if (!exists($symbol_table{$id})) { $symbol_table{$var}->{TYPE} = $type; }
else { Error::error("$id declarado dos veces en el mismo ámbito"); }
}
}
Cada vez que aparece una variable en el código, bien en un factor o en una asignación, comprobamos que ha sido declarada:
sub factor() {
my ($e, $id, $str, $num);
if ($lookahead eq 'NUM') { ... }
elsif ($lookahead eq 'ID') {
$id = $value;
match('ID');
my $type = Semantic::Analysis::check_declared($id);
return ID->new( VAL => $id, TYPE => $type);
}
elsif ($lookahead eq 'STR') { ... }
elsif ($lookahead eq '(') { ... }
else { Error::fatal("Se esperaba (, NUM o ID"); }
}
La función check_declared devuelve el atributo
TYPE de la correspondiente entrada en la tabla de
símbolos.
sub check_declared {
my $id = shift;
if (!exists($symbol_table{$id})) {
Error::error("$id no ha sido declarado!");
# auto-declaración de la variable a err_type
Semantic::Analysis::set_types($err_type, ($id));
}
return $symbol_table{$id}->{TYPE};
}

