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}; }