Está en la página 1de 28

5.

4 Acciones agregadas en un
analizador sintáctico descendente
(top-down)

Muchas de las actividades que realiza un


analizador semántico no son estándares,
dependerán del objetivo del lenguaje de
programación; por ejemplo, en algunas
aplicaciones es interesante conocer que los
datos estén en algún rango válido o que
ciertos valores se utilicen para uso reservado
Acciones agregadas a un analizador
semántico
• En algunas ocasiones nos interesa conocer el
significado de las palabras de algún lenguaje
dependiendo del contexto (gramáticas de tipo 1)
para diferenciar palabras polisemánticas.
• La Web es una base de datos en la mayoría de
los casos sin sentidos por lo que la tercera
generación de la Web será la llamada Web
semántica
• En un parser recursivo-descendente, el código de las acciones semánticas es
mezclado dentro del flujo de control de las acciones del parser. En un parser
especificado en javaCC, las acciones semánticas son fragmentos de código de
programa en java unido a las producciones gramáticales.
• Cada símbolo terminal y noterminal puede asociarse con su propio tipo de valor
semántico.
Por ejemplo en la siguiente gramática para YACC de una calculadora simple, el tipo
asociado con exp e INT podría ser int:

%token INT PLUS MINUS TIMES UMINUS


%start exp
%left PLUS MINUS
%left TIMES
%left UMINIS
exp: INT | exp PLUS exp | exp MINUS exp | exp TIMES exp 1
MINUS exp %prec UMINUS
(cont.)
• Los otros tokens no necesitarían tener un valor.
• Por otra parte el tipo asociado a un token debe
por supuesto coincidir con el tipo de token que
el scanner retorne.
• Para una regla ABCD, la acción semántica
debe retornar un valor cuyo tipo es el asociado
al noterminal A. Pero puede construír este
valor de los valores asociados a los terminales y
noterminales B, C, D.
Recursivo-descendente
• En un parser recursivo-descendente, las acciones
semánticas son los valores retornados por las funciones
de parsing, o los efectos laterales de esas funciones o
ambos.
• Por cada símbolo terminal y noterminal, asociamos un
tipo (desde el lenguaje de implementación del LP del
compilador) de valor semántico representando frases
derivadas desde ese símbolo.
• El siguiente programa es un intérprete recursivo
descendente para una parte de la gramática en la cual
eliminamos la recursión por la izquierda (por
conveniencia la volvemos a mostrar):
(cont.)

S  E$ E  T E’ E’  + T E’ E’  - T E’ E’  l
T  F T’ T’  * F T’ T’  / F T’ T’  l
F  id F  num F  (E)

Los tokens ID y NUM deben ahora acarrear valores de tipo string e int,
respectivamente. Asumiremos que existe una tabla “lookup” que mapea
identificadores a enteros. El tipo asociado con E, T, F, etc., es int, y la acción semántica
es fácil de implementar.
Interprete
Acciones semánticas

class Token2 {
int kind; Object val;
Token2(int k, Object v) int T() {switch(tok.kind) {
{ case ID:
kind=k; case NUM:
val=v; case LPAREN: return Tprime(F());
} default: print("2 esperaba ID, NUM o parent izq");
} //skipto(T_follow);
final int EOF=0, ID=1, NUM=2, PLUS=3, return 0;
MINUS=4,LPAREN=5, RPAREN=6, TIMES=7; }}
int lookup(String id) { …. }
int F_follow[] = {PLUS,TIMES,RPAREN,EOF}; int Tprime(int a) {switch (tok.kind) {
case TIMES: eat(TIMES); return Tprime(a*F());
int F() {switch(tok.kind) { case PLUS:
case ID: int i=lookup((String)(tok.val));advance()return i; case RPAREN:
case NUM: int i=((integer)(tok.val)).intVal(); case EOF: return a;
advance();return i; default: print("3 esperaba ID, NUM o parent izq");
case LPAREN: eat(LPAREN); //skipto(T_follow);
int i=E(); return 0;
eatOrSkipTo(RPAREN,F_follow); }}
return i;
case EOF: void eatOrSkipTo(int expected, int[] stop) {
default: print("1 esperaba ID,NUM, o parent izq"); if (tok.kind==expected)
//skipto(F_follow); return 0; eat(expected);
}} else {print("4 esperaba ID, NUM o parent izq");
//skipto(stop);}
int T_follow[]= {PLUS,RPAREN,EOF}; }
Parser Automáticamente generado

• Una especificación del parser para javaCC consistiría de un conjunto de reglas gramaticales,
cada una anotada o agregada con una acción semántica el cual sería un estatuto java.
Ejemplo:

void Start(): Int Term():


{ int i; } { int a,i; }
{ i=Exp() <EOF> {System.Out.println(i);} { a=factor()
} ( “*” i=Factor() { a=a*i;}
Int Exp(): | “/” i=Factor() {a=a/i;}
{ int a,i; } )*
{ a=Term() { return a; }
( “+” i=Term() {a=a+i;} }
| “-” i=Term() {a=a-i;} Int Factor():
)* { Token t; int i; }
{ return a; } { t=<IDENTIFIER> { return lookup(t.image); }
} | t=<INTEGER_LITERAL>
{return Integer.parseInt(t.image); }
| “(“ i=Exp() “)” {return i; }
}
Árboles de Parsing Abstractos
• Para mejorar la modularidad del compilador, es recomendable separar detalles de la sintaxis
con detalles de la semántica (chequeo de tipos y traducción a código máquina).
• Una forma de hacerlo es producir un árbol de sintaxis abstracta (una forma condensada de
árbol de parsing útil para representar construcciones del LP).
• Por ejemplo la producción S  if B then S1 else S2 pudiera aparecer en un arbol sintáctico
como:

Árbol
Est-if
Árbol If-then-else De
sintáctico parsing

If Exp ( Exp ) Est

B S1 S2

En un árbol sintáctico, los operadores y las palabras claves (reservadas) no


aparecen como hojas, sino que están asociadas con el nodo interior que sería el
padre de esas hojas en el arbol de parsing.
(cont.)
• Otro ejemplo es en el árbol de parsing:
L

E + T

T * F F

F 4 8

2 +
Cuyo árbol sintáctico abstracto sería: * 8

2 4
Ejemplo:
• La gramática siguiente nos muestra una sintaxis abstracta
de un lenguaje para expresiones:
EE+E EE-EEE*E
EE/E Eid Enum
• Esta gramática es impráctica para un parser ya que es
ambigua pues no tiene precedencia de operadores.
• Sin embargo, esta gramática no es para el parser. El
analizador semántico podría usarla el cual no se molesta
por la ambiguedad puesto que ya tiene su arbol.
Árboles de Sintaxis en Java
• En Java las estructuras de datos para el árbol
de sintaxis contienen una clase abstracta para
cada noterminal y una subclase para cada
producción. Así, las clases de el programa
siguiente son las clases de la sintaxis abstracta
para la gramática de la diapositiva anterior.
Programa de clases para Exp
public abstract class ExpCh4 {
public abstract int eval();
}
class PlusExp extends ExpCh4 {
private ExpCh4 e1,e2; class DivideExp extends ExpCh4 {
public PlusExp(ExpCh4 a1, ExpCh4 a2) private ExpCh4 e1,e2;
{e1=a1; e2=a2;} public DivideExp(ExpCh4 a1, ExpCh4 a2)
public int eval() { {e1=a1; e2=a2;}
return e1.eval()+e2.eval(); public int eval() {
} return e1.eval()/e2.eval();
} }
class MinusExp extends ExpCh4 { }
private ExpCh4 e1,e2; class Identifier extends ExpCh4 {
public MinusExp(ExpCh4 a1, ExpCh4 a2) private String f0;
{e1=a1; e2=a2;} public Identifier(String n0) {f0=n0;}
public int eval() { public int eval() {
return e1.eval()-e2.eval(); return (7); //return lookup(f0);
} }
} }
class TimesExp extends ExpCh4 { class IntegerLiteral extends ExpCh4 {
private ExpCh4 e1,e2; private String f0;
public TimesExp(ExpCh4 a1, ExpCh4 a2) public IntegerLiteral(String n0) {f0=n0;}
{e1=a1; e2=a2;} public int eval() {
public int eval() { return (4);
return e1.eval()*e2.eval(); //return Integer.parseInt(f0);
} }
} }
(cont.)
• Ahora veamos un intérprete para el lenguaje de expresiones de la gramática de sección 4.1.1.
Por conveniencia la mostramos de nuevo.

S  E$
ETE ’ E’  + T E’ E’  - T E’ E’  l
T  F T’ T’  * F T’ T’  / F T’ T’  l
F  id F  num F  (E)

 Nuestro intérprete primero construye árboles


sintácticos y después los interpreta. El siguiente código
es el de la gramática JavaCC con acciones semánticas para
interpretar (evaluar) y producir (construir) árboles
sintácticos. Cada clase de nodos de árbol sintáctico
contiene una función eval que cuando es llamada retorna
el valor de la expresión representada.
Gramática con acciones semánticas para árboles
sintácticos

PARSER_BEGIN(InterSinTree)
class InterSinTree {}
PARSER_END(InterSinTree)

TOKEN: { ExpCh4 Exp():


{ ExpCh4 e1,e2; }
<#DIGIT: ["0"-"9"]> { e1=Term()
|<ID: ["a"-"z"] (["a"-"z"]|<DIGIT>)*> ("+" e2=Term() { e1=new PlusExp(e1,e2);}
|<INTEGER_LITERAL: (<DIGIT>)+> |"-" e2=Term() { e1=new MinusExp(e1,e2);}
)*
} { return e1;}
}
SKIP: { ExpCh4 Term():
<"--" (["a" - "z"])* ("\n" | "\r" | "\r\n")> { ExpCh4 e1,e2; }
|" " { e1=Factor()
|"\t" ("*" e2=Factor() { e1=new TimesExp(e1,e2);}
|"\n" |"/" e2=Factor() { e1=new DivideExp(e1,e2);}
|"\r" )*
} { return e1;}
}

ExpCh4 Start(): ExpCh4 Factor() :


{ ExpCh4 e; } { Token t; ExpCh4 e; }
{ e=Exp() <EOF> { (t=<ID>
{System.out.println(e.eval()); return e; } {return new Identifier(t.image); } |
} t=<INTEGER_LITERAL>
{return new IntegerLiteral(t.image); } |
"(" e=Exp() ")" {return e; })
}
VISITADORES
• Es una técnica de patrones (opuesta a la orientada a
objetos) que se puede usar para implementar el árbol
sintáctico del compilador o intérprete. Un visitador es un
objeto que contiene un método visit por cada clase de
árbol sintáctico. Cada clase de árbol sintáctico debe
contener un método accept. Cada método accept sirve
como enganche para cada diferente tipo de operación
sobre el árbol y es llamado por un visitador donde tiene
una tarea: pasar el control de ida y venida (back and
forth) entre el visitador y las clases del árbol sintáctico.
(cont.)
• A continuación veremos un ejemplo del
intérprete de expresiones anterior pero ahora
implementado con visitadores. Cada visitador
implementa la interfase Visitor. Cada método
accept toma un visitador como argumento y
cada método visit toma un objeto de un nodo
del árbol sintáctico como argumento.
Sintáxis Abstracta para MiniJava
• En la siguiente figura (siguiente diapositiva) mostramos las clases de la
sintaxis abstracta para minijava. Solo los constructores son mostrados en
la figura. Cada una de las clases de listas se implementa en la misma
forma. Por ejemplo:
public class ExpList {
private Vector list;
public ExpList() {
list=new vector();
}
Public void addElement (Exp n) {
list.addElement(n);
}
public Exp elementAt(int i) {
return (exp)list.elementAt(i);
}
public int size() {
return list.size();
}
}
Package syntaxtree;

Program(MainClass m, ClassDeclList cl)


MainClass(Identifier i1, Identifier i2, Statement s)

Abstract class ClassDecl


ClassDeclSimple(Identifier i, VarDeclList vl, MethodDeclList ml)
ClassDeclExtends(Identifier i, identifier j, VarDeclList vl, MethodDeclList ml)

VarDecl(Type t, Identifier i)
MethodDecl(Type t, Identifier i, FormalList fl, VarDeclList vl, StatementList,
Exp e)
Formal(Type t, Identifier i)

Abstract class Type


IntArrayType() BooleanType() IntegerType() IdentifierType(String s)

Abstract class Statement


Block(StatementList sl)
If(Exp e, Statement s1, Statement s2)
While(Exp e, Statement s)
Print(Exp e)
Assign(Identifier i, Exp e)
ArrayAssign(Identifier i, Exp e1, Exp e2)
(cont. figura)

Abstract class Exp


And(Exp e1, Exp e2)
LessThan(Exp e1, Exp e2)
Plus(Exp e1, Exp e2) Minus(Exp e1, Exp e2)Times(Exp e1, Exp e2)
ArrayLoockup(Exp e1, Exp e2)
ArrayLength(Exp e)
Call(Exp e, Identifier i, ExpList el)
IntegerLiteral(int i)
True()
False()
IdentifierExp(String s)
This()
NewArray(Exp e)
NewObject(Identifier i)
Not(Exp e)

Identifier(String s)

List classes
ClassDeclList() ExpList() FormalList() MethodDeclList()
StatementList() VarDeclList()
Arbol Sintáctico
• Cada una de las clases que no son listas tiene
un método accept para usarse con el patrón
visitador. La interface Visitador se muestra en
la siguiente diapositiva.
public interface Visitor {
public void visit(Program n);
public void visit(MainClass n);
public void visit(ClassDeclSimple n);
public void visit(ClassDeclextends n);
public void visit(VarDecl n);
public void visit(MethodDecl n);
public void visit(Formal n);
public void visit(IntArrayType n);
public void visit(BooleanType n);
public void visit(IntegerType n);
public void visit(IdentifierType n);
public void visit(Block n);
Visitador public void visit(If n);
public void visit(While n);
MiniJava public void visit(Print n);
public void visit(Assign n);
public void visit(ArrayAssign n);
public void visit(And n);
public void visit(LessThan n);
public void visit(Pluss n);
public void visit(Minus n);
public void visit(Times n);
public void visit(ArrayLoockup n);
public void visit(ArrayLength n);
public void visit(Call n);
public void visit(IntegerLiteral n);
public void visit(True n);
public void visit(False n);
public void visit(IdentifierExp n);
public void visit(This n);
public void visit(NewArray n);
public void visit(NewObject n);
public void visit(Not n);
public void visit(Identifier n);
}
(cont. Arbol Sintáctico)
• Podemos construir un árbol sintáctico usando expresiones new anidadas.
Por ejemplo el árbol sintáctico para el estatuto MiniJava:
x = y.m(1,4+5);

usaría el siguiente código:

ExpList el= new ExpList();


el.addElement(new IntegerLiteral(1));
el.addelement(new Plus(new IntegerLiteral(4),
new IntegerLiteral(5)));
Statement s = new Assign(new Identifier “x”),
new Call(new identifierExp(“y”),
new Identifier(“m”),
el));
5.5 Pila semántica en un
analizador sintáctico ascendente
(bottom-up).

• El diseño ascendente se refiere a la


identificación de aquellos procesos que
necesitan computarizarse con forme vayan
apareciendo, su análisis como sistema y su
codificación, o bien, la adquisición de
paquetes de software para satisfacer el
problema inmediato.
Pila semántica
• Los problemas de integración entre los
subsistemas son sumamente costosos y
muchos de ellos no se solucionan hasta que
la programación alcanza la fecha limite para
la integración total del sistema.
• Se necesita una memoria auxiliar que nos
permita guardar los datos intermedios para
poder hacer la comparación.
5.6 Administración de la tabla de
símbolos
• La tabla de símbolos también recibe el
nombre de ambiente. Un ambiente contiene
un conjunto de parámetros que sólo son
visibles en ese ambiente.
• La tabla de símbolos se mantiene durante
todo el proceso de traducción agregando
elementos específicos en cada paso.
Operaciones sobre la tabla de
símbolos
• Inserta(símbolo)
• Existe(nombre)
• Tipo(nombre)
• Declaración TIPO {tipo=obtengo(yytext());}
• listavar PYC
• Listavar var {inserta(símbolo);} | var
• {inserta(simbolo);}
• Var ID {simbolo=yytext; símbolo.tipo=tipo;
• simbolo.amb=ambito;}
Operaciones sobre la tabla de
símbolos
• ExprlogPI exprlog {A=A;} PD
• |NOT exprlog {A=A;}
• |exprlog {A1=A;} OPLOG exprlog
• {A2=A
• If(A1==INT && A2==INT)
• A=INT;
• Else
• A=ERROR_TIPO;}

También podría gustarte