package freeboogie.tc;
import java.util.HashMap;
import java.util.LinkedList;
import freeboogie.ast.*;
import freeboogie.util.Err;
/**
* Constructs a {@code SymbolTable} from an AST.
*
* NOTE: generic types in boogie are a hack so I'll treat them as such.
*
* @author rgrig
* @author reviewed by TODO
*/
@SuppressWarnings("unused") // lots of unused parameters
public class SymbolTableBuilder extends Transformer {
private LinkedList<HashMap<String, VariableDecl>> localScopes;
private SymbolTable symbolTable;
private GlobalsCollector gc;
private boolean errors;
// for modifies spec we ignore the arguments
private boolean lookInLocalScopes;
/*
* HACK to support `generic' types: Do not warn if we are under
* an array because it might be a `type variable'. The typechecker
* should enforce that things make sense a bit later on.
*/
// the number of ArrayType nodes above the current node
private int arrayCnt;
/**
* Builds a symbol table. Reports name clashes (because it
* uses {@code GlobalsCollector}. Reports undeclared variables.
* @param ast the AST to analyze
* @return whether there were any problems detected
*/
public boolean process(Declaration ast) {
errors = false;
localScopes = new LinkedList<HashMap<String, VariableDecl>>();
symbolTable = new SymbolTable();
gc = new GlobalsCollector();
arrayCnt = 0;
lookInLocalScopes = true;
boolean e = gc.process(ast);
ast.eval(this);
return errors || e;
}
/**
* Returns the symbol table.
* @return the symbol table
*/
public SymbolTable getST() {
return symbolTable;
}
/**
* Returns the globals collector, which can be used to resolve global names.
* @return the globals collector
*/
public GlobalsCollector getGC() {
return gc;
}
// === helpers ===
// reports an error at location l if d s null
private <T> T check(T d, String s, AstLocation l) {
if (d != null || arrayCnt > 0) return d;
Err.error("" + l + ": Undeclared identifier " + s + ".");
errors = true;
return null;
}
// the return might by ConstDecl or VariableDecl
private Declaration lookup(String s, AstLocation l) {
if (lookInLocalScopes) {
for (HashMap<String, VariableDecl> scope : localScopes) {
VariableDecl d = scope.get(s);
if (d != null) return d;
}
}
return check(gc.idDef(s), s, l);
}
// === visit methods ===
// Grr, why doesn't Java have functions as parameters or at least macros?
@Override
public void see(UserType userType, String name) {
symbolTable.types.put(userType, check(gc.typeDef(name), name, userType.loc()));
}
@Override
public void see(CallCmd callCmd, String p, Identifiers results, Exprs args) {
symbolTable.procs.put(callCmd, check(gc.procDef(p), p, callCmd.loc()));
if (results != null) results.eval(this);
if (args != null) args.eval(this);
}
@Override
public void see(AtomFun atomFun, String f, Exprs args) {
symbolTable.funcs.put(atomFun, check(gc.funDef(f), f, atomFun.loc()));
if (args != null) args.eval(this);
}
@Override
public void see(AtomId atomId, String id) {
symbolTable.ids.put(atomId, lookup(id, atomId.loc()));
}
// === collect info from local scopes ===
@Override
public void see(VariableDecl variableDecl, String name, Type type, Declaration tail) {
HashMap<String, VariableDecl> scope = localScopes.peekFirst();
if (scope != null && name != null) {
// we are in a local scope
VariableDecl old = scope.get(name);
if (old != null) {
Err.error("" + variableDecl.loc() + ": Variable already defined.");
errors = true;
} else
scope.put(name, variableDecl);
}
type.eval(this);
if (tail != null) tail.eval(this);
}
// === keep track of local scopes ===
@Override
public void see(Procedure procedure, Signature sig, Specification spec, Declaration tail) {
symbolTable.procs.seenDef(procedure);
HashMap<String, VariableDecl> newScope = new HashMap<String, VariableDecl>();
localScopes.addFirst(newScope);
sig.eval(this);
if (spec != null) spec.eval(this);
localScopes.removeFirst();
if (tail != null) tail.eval(this);
}
@Override
public void see(Implementation implementation, Signature sig, Body body, Declaration tail) {
HashMap<String, VariableDecl> newScope = new HashMap<String, VariableDecl>();
localScopes.addFirst(newScope);
sig.eval(this);
body.eval(this);
localScopes.removeFirst();
if (tail != null) tail.eval(this);
}
@Override
public void see(AtomQuant atomQuant, AtomQuant.QuantType quant, Declaration vars, Trigger trig, Expr e) {
HashMap<String, VariableDecl> newScope = new HashMap<String, VariableDecl>();
localScopes.addFirst(newScope);
vars.eval(this);
if (trig != null) trig.eval(this);
e.eval(this);
localScopes.removeFirst();
}
// === remember if we are below a modifies spec ===
@Override
public void see(Specification specification, Specification.SpecType type, Expr expr, boolean free, Specification tail) {
if (type == Specification.SpecType.MODIFIES) {
assert lookInLocalScopes; // no nesting
lookInLocalScopes = false;
}
expr.eval(this);
lookInLocalScopes = true;
if (tail != null) tail.eval(this);
}
// === remember if we are below an ArrayType ===
@Override
public void see(ArrayType arrayType, Type rowType, Type colType, Type elemType) {
++arrayCnt;
super.see(arrayType, rowType, colType, elemType);
--arrayCnt;
}
// === do not lok at goto-s ===
@Override
public void see(Block block, String name, Commands cmds, Identifiers succ, Block tail) {
if (cmds != null) cmds.eval(this);
if (tail != null) tail.eval(this);
}
}