Homework B (benchmarks)
This commit is contained in:
parent
72cc3206c4
commit
76fbabdf53
141 changed files with 7540 additions and 2032 deletions
40
src/cd/frontend/semantic/FieldQualifier.java
Normal file
40
src/cd/frontend/semantic/FieldQualifier.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.ir.Ast.Var;
|
||||
import cd.ir.AstRewriteVisitor;
|
||||
import cd.ir.Symbol.ClassSymbol;
|
||||
|
||||
/**
|
||||
* Runs after the semantic check and rewrites expressions to be more normalized.
|
||||
* References to a field {@code foo} are rewritten to always use
|
||||
* {@link Ast.Field} objects (i.e., {@code this.foo}.
|
||||
*/
|
||||
public class FieldQualifier {
|
||||
|
||||
public void rewrite(ClassDecl cd) {
|
||||
AstRewriteVisitor<ClassSymbol> rewriter = new AstRewriteVisitor<ClassSymbol>() {
|
||||
@Override
|
||||
public Ast var(Var ast, ClassSymbol cs) {
|
||||
switch (ast.sym.kind) {
|
||||
case PARAM:
|
||||
case LOCAL:
|
||||
// Leave params or local variables alone
|
||||
return ast;
|
||||
case FIELD:
|
||||
// Convert an implicit field reference to "this.foo"
|
||||
Ast.Field f = new Ast.Field(new Ast.ThisRef(), ast.name);
|
||||
f.arg().type = cs;
|
||||
f.sym = ast.sym;
|
||||
f.type = ast.type;
|
||||
return f;
|
||||
}
|
||||
throw new RuntimeException("Unknown kind of var");
|
||||
}
|
||||
};
|
||||
|
||||
cd.accept(rewriter, cd.sym);
|
||||
}
|
||||
|
||||
}
|
|
@ -13,13 +13,13 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
|
||||
public class InheritanceChecker extends AstVisitor<Void, Void> {
|
||||
|
||||
|
||||
ClassSymbol classSym;
|
||||
|
||||
@Override
|
||||
public Void classDecl(ClassDecl ast, Void arg) {
|
||||
classSym = ast.sym;
|
||||
|
||||
|
||||
// check for cycles in the inheritance hierarchy:
|
||||
Set<ClassSymbol> supers = new HashSet<ClassSymbol>();
|
||||
ClassSymbol sc = classSym.superClass;
|
||||
|
@ -28,30 +28,31 @@ public class InheritanceChecker extends AstVisitor<Void, Void> {
|
|||
if (supers.contains(sc))
|
||||
throw new SemanticFailure(
|
||||
Cause.CIRCULAR_INHERITANCE,
|
||||
"Class %s has %s as a superclass twice",
|
||||
"Class %s has %s as a superclass twice",
|
||||
ast.name, sc.name);
|
||||
supers.add(sc);
|
||||
sc = sc.superClass;
|
||||
}
|
||||
|
||||
|
||||
this.visitChildren(ast, null);
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void methodDecl(MethodDecl ast, Void arg) {
|
||||
|
||||
|
||||
// check that methods overridden from a parent class agree
|
||||
// on number/type of parameters
|
||||
MethodSymbol sym = ast.sym;
|
||||
MethodSymbol superSym = classSym.superClass.getMethod(ast.name);
|
||||
sym.overrides = superSym;
|
||||
if (superSym != null) {
|
||||
if (superSym.parameters.size() != sym.parameters.size())
|
||||
throw new SemanticFailure(
|
||||
Cause.INVALID_OVERRIDE,
|
||||
"Overridden method %s has %d parameters, " +
|
||||
"but original has %d",
|
||||
"but original has %d",
|
||||
ast.name, sym.parameters.size(),
|
||||
superSym.parameters.size());
|
||||
for (Pair<VariableSymbol> pair : Pair.zip(sym.parameters, superSym.parameters))
|
||||
|
@ -59,17 +60,17 @@ public class InheritanceChecker extends AstVisitor<Void, Void> {
|
|||
throw new SemanticFailure(
|
||||
Cause.INVALID_OVERRIDE,
|
||||
"Method parameter %s has type %s, but " +
|
||||
"corresponding base class parameter %s has type %s",
|
||||
"corresponding base class parameter %s has type %s",
|
||||
pair.a.name, pair.a.type, pair.b.name, pair.b.type);
|
||||
if (superSym.returnType != sym.returnType)
|
||||
throw new SemanticFailure(
|
||||
Cause.INVALID_OVERRIDE,
|
||||
"Overridden method %s has return type %s," +
|
||||
"but its superclass has %s",
|
||||
"but its superclass has %s",
|
||||
ast.name, sym.returnType, superSym.returnType);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,22 +5,21 @@ import cd.ir.Ast.*;
|
|||
import cd.ir.AstVisitor;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Visitor that checks if all paths of a given sequence have a
|
||||
* return statement.
|
||||
*
|
||||
* <p>
|
||||
* This visitor only needs to be used if are not using the Control Flow Graph.
|
||||
*
|
||||
*
|
||||
* @author Leo Buttiker
|
||||
*/
|
||||
public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
|
||||
public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
|
||||
|
||||
@Override
|
||||
protected Boolean dfltStmt(Stmt ast, Void arg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Boolean returnStmt(ReturnStmt ast, Void arg) {
|
||||
return true;
|
||||
|
@ -28,7 +27,7 @@ public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
|
|||
|
||||
@Override
|
||||
public Boolean ifElse(IfElse ast, Void arg) {
|
||||
boolean allPathHaveAReturnStmt = true;
|
||||
boolean allPathHaveAReturnStmt = true;
|
||||
allPathHaveAReturnStmt &= visit(ast.then(), null);
|
||||
allPathHaveAReturnStmt &= visit(ast.otherwise(), null);
|
||||
return allPathHaveAReturnStmt;
|
||||
|
@ -36,17 +35,17 @@ public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
|
|||
|
||||
@Override
|
||||
public Boolean seq(Seq ast, Void arg) {
|
||||
|
||||
boolean allPathHaveAReturnStmt = false;
|
||||
|
||||
boolean allPathHaveAReturnStmt = false;
|
||||
for (Ast child : ast.children()) {
|
||||
allPathHaveAReturnStmt |= this.visit(child, null);
|
||||
}
|
||||
return allPathHaveAReturnStmt;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Boolean whileLoop(WhileLoop ast, Void arg) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -11,70 +11,76 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
public class SemanticAnalyzer {
|
||||
|
||||
|
||||
public final Main main;
|
||||
|
||||
|
||||
public SemanticAnalyzer(Main main) {
|
||||
this.main = main;
|
||||
}
|
||||
|
||||
public void check(List<ClassDecl> classDecls)
|
||||
throws SemanticFailure {
|
||||
|
||||
public void check(List<ClassDecl> classDecls)
|
||||
throws SemanticFailure {
|
||||
{
|
||||
SymTable<TypeSymbol> typeSymbols = createSymbols(classDecls);
|
||||
checkInheritance(classDecls);
|
||||
checkStartPoint(typeSymbols);
|
||||
checkMethodBodies(typeSymbols, classDecls);
|
||||
{
|
||||
rewriteMethodBodies(classDecls);
|
||||
main.allTypeSymbols = typeSymbols.allSymbols();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a symbol table with symbols for all built-in types,
|
||||
* Creates a symbol table with symbols for all built-in types,
|
||||
* as well as all classes and their fields and methods. Also
|
||||
* creates a corresponding array symbol for every type
|
||||
* creates a corresponding array symbol for every type
|
||||
* (named {@code type[]}).
|
||||
*
|
||||
* @see SymbolCreator
|
||||
*/
|
||||
private SymTable<TypeSymbol> createSymbols(List<ClassDecl> classDecls) {
|
||||
|
||||
|
||||
// Start by creating a symbol for all built-in types.
|
||||
SymTable<TypeSymbol> typeSymbols = new SymTable<TypeSymbol>(null);
|
||||
|
||||
|
||||
typeSymbols.add(PrimitiveTypeSymbol.intType);
|
||||
typeSymbols.add(PrimitiveTypeSymbol.booleanType);
|
||||
typeSymbols.add(PrimitiveTypeSymbol.voidType);
|
||||
typeSymbols.add(ClassSymbol.objectType);
|
||||
|
||||
|
||||
// Add symbols for all declared classes.
|
||||
for (ClassDecl ast : classDecls) {
|
||||
// Check for classes named Object
|
||||
if (ast.name.equals(ClassSymbol.objectType.name))
|
||||
throw new SemanticFailure(Cause.OBJECT_CLASS_DEFINED);
|
||||
ast.sym = new ClassSymbol(ast);
|
||||
typeSymbols.add(ast.sym);
|
||||
typeSymbols.add(ast.sym);
|
||||
}
|
||||
|
||||
|
||||
// Create symbols for arrays of each type.
|
||||
for (Symbol sym : new ArrayList<Symbol>(typeSymbols.localSymbols())) {
|
||||
Symbol.ArrayTypeSymbol array =
|
||||
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
|
||||
Symbol.ArrayTypeSymbol array =
|
||||
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
|
||||
typeSymbols.add(array);
|
||||
}
|
||||
|
||||
|
||||
// For each class, create symbols for each method and field
|
||||
SymbolCreator sc = new SymbolCreator(main, typeSymbols);
|
||||
for (ClassDecl ast : classDecls)
|
||||
sc.createSymbols(ast);
|
||||
|
||||
|
||||
return typeSymbols;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for errors related to inheritance:
|
||||
* Check for errors related to inheritance:
|
||||
* circular inheritance, invalid super
|
||||
* classes, methods with different types, etc.
|
||||
* Note that this must be run early because other code assumes
|
||||
* that the inheritance is correct, for type checking etc.
|
||||
*
|
||||
* @see InheritanceChecker
|
||||
*/
|
||||
private void checkInheritance(List<ClassDecl> classDecls) {
|
||||
|
@ -93,25 +99,26 @@ public class SemanticAnalyzer {
|
|||
MethodSymbol mainMethod = cs.getMethod("main");
|
||||
if (mainMethod != null && mainMethod.parameters.size() == 0 &&
|
||||
mainMethod.returnType == PrimitiveTypeSymbol.voidType) {
|
||||
main.mainType = cs;
|
||||
return; // found the main() method!
|
||||
}
|
||||
}
|
||||
throw new SemanticFailure(Cause.INVALID_START_POINT, "No Main class with method 'void main()' found");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check the bodies of methods for errors, particularly type errors
|
||||
* but also undefined identifiers and the like.
|
||||
*
|
||||
* @see TypeChecker
|
||||
*/
|
||||
private void checkMethodBodies(
|
||||
SymTable<TypeSymbol> typeSymbols,
|
||||
List<ClassDecl> classDecls)
|
||||
{
|
||||
List<ClassDecl> classDecls) {
|
||||
TypeChecker tc = new TypeChecker(typeSymbols);
|
||||
|
||||
|
||||
for (ClassDecl classd : classDecls) {
|
||||
|
||||
|
||||
SymTable<VariableSymbol> fldTable = new SymTable<VariableSymbol>(null);
|
||||
|
||||
// add all fields of this class, or any of its super classes
|
||||
|
@ -119,37 +126,42 @@ public class SemanticAnalyzer {
|
|||
for (VariableSymbol s : p.fields.values())
|
||||
if (!fldTable.contains(s.name))
|
||||
fldTable.add(s);
|
||||
|
||||
|
||||
// type check any method bodies and final locals
|
||||
for (MethodDecl md : classd.methods()) {
|
||||
|
||||
|
||||
boolean hasReturn = new ReturnCheckerVisitor().visit(md.body(), null);
|
||||
|
||||
|
||||
if (!md.returnType.equals("void") && !hasReturn) {
|
||||
|
||||
throw new SemanticFailure(Cause.MISSING_RETURN,
|
||||
"Method %s.%s is missing a return statement",
|
||||
classd.name,
|
||||
|
||||
throw new SemanticFailure(Cause.MISSING_RETURN,
|
||||
"Method %s.%s is missing a return statement",
|
||||
classd.name,
|
||||
md.name);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
SymTable<VariableSymbol> mthdTable = new SymTable<VariableSymbol>(fldTable);
|
||||
|
||||
|
||||
mthdTable.add(classd.sym.thisSymbol);
|
||||
|
||||
|
||||
for (VariableSymbol p : md.sym.parameters) {
|
||||
mthdTable.add(p);
|
||||
}
|
||||
|
||||
|
||||
for (VariableSymbol l : md.sym.locals.values()) {
|
||||
mthdTable.add(l);
|
||||
}
|
||||
|
||||
|
||||
tc.checkMethodDecl(md, mthdTable);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void rewriteMethodBodies(List<ClassDecl> classDecls) {
|
||||
for (ClassDecl cd : classDecls)
|
||||
new FieldQualifier().rewrite(cd);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,40 +1,50 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Thrown by the semantic checker when a semantic error is detected
|
||||
* in the user's program. */
|
||||
* in the user's program.
|
||||
*/
|
||||
public class SemanticFailure extends RuntimeException {
|
||||
private static final long serialVersionUID = 5375946759285719123L;
|
||||
|
||||
|
||||
public enum Cause {
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Caused by an assignment to either a final field, {@code this},
|
||||
* or some other kind of expression which cannot be assigned to.
|
||||
* <b>Not</b> used for type errors in assignments, which fall
|
||||
* under {@link #TYPE_ERROR}. */
|
||||
* under {@link #TYPE_ERROR}.
|
||||
*/
|
||||
NOT_ASSIGNABLE,
|
||||
|
||||
/** Two variables, fields, methods, or classes with the same name
|
||||
* were declared in the same scope */
|
||||
/**
|
||||
* Two variables, fields, methods, or classes with the same name
|
||||
* were declared in the same scope
|
||||
*/
|
||||
DOUBLE_DECLARATION,
|
||||
|
||||
/** A field was accessed that does not exist */
|
||||
/**
|
||||
* A field was accessed that does not exist
|
||||
*/
|
||||
NO_SUCH_FIELD,
|
||||
|
||||
/** A method was called that does not exist */
|
||||
|
||||
/**
|
||||
* A method was called that does not exist
|
||||
*/
|
||||
NO_SUCH_METHOD,
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* A variable or other identifier was used in a method
|
||||
* body which has no corresponding declaration */
|
||||
* body which has no corresponding declaration
|
||||
*/
|
||||
NO_SUCH_VARIABLE,
|
||||
|
||||
/**
|
||||
* A method with a return type is missing a return statement among one of its paths */
|
||||
|
||||
/**
|
||||
* A method with a return type is missing a return statement among one of its paths
|
||||
*/
|
||||
MISSING_RETURN,
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Can occur in many contents:
|
||||
* <ul>
|
||||
* <li> Assignment to a variable from an expression of wrong type
|
||||
|
@ -53,12 +63,12 @@ public class SemanticFailure extends RuntimeException {
|
|||
* </ul>
|
||||
*/
|
||||
TYPE_ERROR,
|
||||
|
||||
|
||||
/**
|
||||
* A class is its own super class
|
||||
*/
|
||||
CIRCULAR_INHERITANCE,
|
||||
|
||||
|
||||
/**
|
||||
* One of the following:
|
||||
* <ul>
|
||||
|
@ -69,13 +79,13 @@ public class SemanticFailure extends RuntimeException {
|
|||
* </ul>
|
||||
*/
|
||||
INVALID_START_POINT,
|
||||
|
||||
|
||||
/**
|
||||
* A class {@code Object} was defined. This class is implicitly
|
||||
* defined and cannot be defined explicitly.
|
||||
*/
|
||||
OBJECT_CLASS_DEFINED,
|
||||
|
||||
|
||||
/**
|
||||
* A type name was found for which no class declaration exists.
|
||||
* This can occur in many contexts:
|
||||
|
@ -86,34 +96,36 @@ public class SemanticFailure extends RuntimeException {
|
|||
* </ul>
|
||||
*/
|
||||
NO_SUCH_TYPE,
|
||||
|
||||
|
||||
/**
|
||||
* The parameters of an overridden method have different types
|
||||
* from the base method, there is a different
|
||||
* from the base method, there is a different
|
||||
* number of parameters, or the return value is different.
|
||||
*/
|
||||
INVALID_OVERRIDE,
|
||||
|
||||
/** A method was called with the wrong number of arguments */
|
||||
|
||||
/**
|
||||
* A method was called with the wrong number of arguments
|
||||
*/
|
||||
WRONG_NUMBER_OF_ARGUMENTS,
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Indicates the use of a local variable that may not have been
|
||||
* initialized (ACD only).
|
||||
*/
|
||||
POSSIBLY_UNINITIALIZED,
|
||||
}
|
||||
|
||||
|
||||
public final Cause cause;
|
||||
|
||||
|
||||
public SemanticFailure(Cause cause) {
|
||||
super(cause.name());
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
|
||||
public SemanticFailure(Cause cause, String format, Object... args) {
|
||||
super(String.format(format, args));
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -3,24 +3,23 @@ package cd.frontend.semantic;
|
|||
import cd.frontend.semantic.SemanticFailure.Cause;
|
||||
import cd.ir.Symbol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A simple symbol table, with a pointer to the enclosing scope.
|
||||
/**
|
||||
* A simple symbol table, with a pointer to the enclosing scope.
|
||||
* Used by {@link TypeChecker} to store the various scopes for
|
||||
* local, parameter, and field lookup. */
|
||||
* local, parameter, and field lookup.
|
||||
*/
|
||||
public class SymTable<S extends Symbol> {
|
||||
|
||||
|
||||
private final Map<String, S> map = new HashMap<String, S>();
|
||||
|
||||
|
||||
private final SymTable<S> parent;
|
||||
|
||||
|
||||
public SymTable(SymTable<S> parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
|
||||
public void add(S sym) {
|
||||
// check that the symbol is not already declared *at this level*
|
||||
if (containsLocally(sym.name))
|
||||
|
@ -28,10 +27,21 @@ public class SymTable<S extends Symbol> {
|
|||
map.put(sym.name, sym);
|
||||
}
|
||||
|
||||
public List<S> allSymbols() {
|
||||
List<S> result = new ArrayList<S>();
|
||||
SymTable<S> st = this;
|
||||
while (st != null) {
|
||||
for (S sym : st.map.values())
|
||||
result.add(sym);
|
||||
st = st.parent;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Collection<S> localSymbols() {
|
||||
return this.map.values();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* True if there is a declaration with the given name at any
|
||||
* level in the symbol table
|
||||
|
@ -40,7 +50,7 @@ public class SymTable<S extends Symbol> {
|
|||
return get(name) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* True if there is a declaration at THIS level in the symbol
|
||||
* table; may return {@code false} even if a declaration exists
|
||||
* in some enclosing scope
|
||||
|
@ -49,9 +59,10 @@ public class SymTable<S extends Symbol> {
|
|||
return this.map.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Base method: returns {@code null} if no symbol by that
|
||||
* name can be found, in this table or in its parents */
|
||||
* name can be found, in this table or in its parents
|
||||
*/
|
||||
public S get(String name) {
|
||||
S res = map.get(name);
|
||||
if (res != null)
|
||||
|
@ -60,11 +71,12 @@ public class SymTable<S extends Symbol> {
|
|||
return null;
|
||||
return parent.get(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the symbol with the given name, or fails with a
|
||||
* Finds the symbol with the given name, or fails with a
|
||||
* NO_SUCH_TYPE error. Only really makes sense to use this
|
||||
* if S == TypeSymbol, but we don't strictly forbid it... */
|
||||
* if S == TypeSymbol, but we don't strictly forbid it...
|
||||
*/
|
||||
public S getType(String name) {
|
||||
S res = get(name);
|
||||
if (res == null)
|
||||
|
|
|
@ -21,10 +21,10 @@ import java.util.Set;
|
|||
* and local variables.
|
||||
*/
|
||||
public class SymbolCreator extends Object {
|
||||
|
||||
|
||||
final Main main;
|
||||
final SymTable<TypeSymbol> typesTable;
|
||||
|
||||
|
||||
public SymbolCreator(Main main, SymTable<TypeSymbol> typesTable) {
|
||||
this.main = main;
|
||||
this.typesTable = typesTable;
|
||||
|
@ -34,33 +34,33 @@ public class SymbolCreator extends Object {
|
|||
// lookup the super class. the grammar guarantees that this
|
||||
// will refer to a class, if the lookup succeeds.
|
||||
cd.sym.superClass = (ClassSymbol) typesTable.getType(cd.superClass);
|
||||
new ClassSymbolCreator(cd.sym).visitChildren(cd, null);
|
||||
new ClassSymbolCreator(cd.sym).visitChildren(cd, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful method which adds a symbol to a map, checking to see
|
||||
* Useful method which adds a symbol to a map, checking to see
|
||||
* that there is not already an entry with the same name.
|
||||
* If a symbol with the same name exists, throws an exception.
|
||||
*/
|
||||
public <S extends Symbol> void add(Map<String,S> map, S sym) {
|
||||
public <S extends Symbol> void add(Map<String, S> map, S sym) {
|
||||
if (map.containsKey(sym.name))
|
||||
throw new SemanticFailure(
|
||||
Cause.DOUBLE_DECLARATION,
|
||||
"Symbol '%s' was declared twice in the same scope",
|
||||
"Symbol '%s' was declared twice in the same scope",
|
||||
sym.name);
|
||||
map.put(sym.name, sym);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates symbols for all fields/constants/methods in a class.
|
||||
* Uses {@link MethodSymbolCreator} to create symbols for all
|
||||
* Uses {@link MethodSymbolCreator} to create symbols for all
|
||||
* parameters and local variables to each method as well.
|
||||
* Checks for duplicate members.
|
||||
*/
|
||||
public class ClassSymbolCreator extends AstVisitor<Void, Void> {
|
||||
|
||||
final ClassSymbol classSym;
|
||||
|
||||
|
||||
final ClassSymbol classSym;
|
||||
|
||||
public ClassSymbolCreator(ClassSymbol classSym) {
|
||||
this.classSym = classSym;
|
||||
}
|
||||
|
@ -75,9 +75,11 @@ public class SymbolCreator extends Object {
|
|||
|
||||
@Override
|
||||
public Void methodDecl(MethodDecl ast, Void arg) {
|
||||
|
||||
|
||||
ast.sym = new MethodSymbol(ast);
|
||||
|
||||
|
||||
ast.sym.owner = classSym;
|
||||
|
||||
add(classSym.methods, ast.sym);
|
||||
|
||||
// create return type symbol
|
||||
|
@ -86,13 +88,13 @@ public class SymbolCreator extends Object {
|
|||
} else {
|
||||
ast.sym.returnType = typesTable.getType(ast.returnType);
|
||||
}
|
||||
|
||||
|
||||
// create symbols for each parameter
|
||||
Set<String> pnames = new HashSet<String>();
|
||||
for (int i = 0; i < ast.argumentNames.size(); i++) {
|
||||
String argumentName = ast.argumentNames.get(i);
|
||||
String argumentType = ast.argumentTypes.get(i);
|
||||
if (pnames.contains(argumentName))
|
||||
if (pnames.contains(argumentName))
|
||||
throw new SemanticFailure(
|
||||
Cause.DOUBLE_DECLARATION,
|
||||
"Method '%s' has two parameters named '%s'",
|
||||
|
@ -102,23 +104,23 @@ public class SymbolCreator extends Object {
|
|||
argumentName, typesTable.getType(argumentType));
|
||||
ast.sym.parameters.add(vs);
|
||||
}
|
||||
|
||||
|
||||
// create symbols for the local variables
|
||||
new MethodSymbolCreator(ast.sym).visitChildren(ast.decls(), null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class MethodSymbolCreator extends AstVisitor<Void, Void> {
|
||||
|
||||
|
||||
final MethodSymbol methodSym;
|
||||
|
||||
|
||||
public MethodSymbolCreator(MethodSymbol methodSym) {
|
||||
this.methodSym = methodSym;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void varDecl(VarDecl ast, Void arg) {
|
||||
ast.sym = new VariableSymbol(
|
||||
|
|
|
@ -11,7 +11,7 @@ import cd.util.debug.AstOneLine;
|
|||
public class TypeChecker {
|
||||
|
||||
final private SymTable<TypeSymbol> typeSymbols;
|
||||
|
||||
|
||||
public TypeChecker(SymTable<TypeSymbol> typeSymbols) {
|
||||
this.typeSymbols = typeSymbols;
|
||||
}
|
||||
|
@ -19,11 +19,12 @@ public class TypeChecker {
|
|||
public void checkMethodDecl(MethodDecl method, SymTable<VariableSymbol> locals) {
|
||||
new MethodDeclVisitor(method, locals).visit(method.body(), null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether two expressions have the same type
|
||||
* and throws an exception otherwise.
|
||||
*
|
||||
* @return The common type of the two expression.
|
||||
*/
|
||||
private TypeSymbol checkTypesEqual(Expr leftExpr, Expr rightExpr, SymTable<VariableSymbol> locals) {
|
||||
|
@ -32,30 +33,30 @@ public class TypeChecker {
|
|||
|
||||
if (leftType != rightType) {
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Expected operand types to be equal but found %s, %s",
|
||||
leftType,
|
||||
rightType);
|
||||
Cause.TYPE_ERROR,
|
||||
"Expected operand types to be equal but found %s, %s",
|
||||
leftType,
|
||||
rightType);
|
||||
}
|
||||
|
||||
|
||||
return leftType;
|
||||
}
|
||||
|
||||
|
||||
private void checkTypeIsInt(TypeSymbol type) {
|
||||
if (type != PrimitiveTypeSymbol.intType) {
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Expected %s for operands but found type %s",
|
||||
PrimitiveTypeSymbol.intType,
|
||||
type);
|
||||
Cause.TYPE_ERROR,
|
||||
"Expected %s for operands but found type %s",
|
||||
PrimitiveTypeSymbol.intType,
|
||||
type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ClassSymbol asClassSymbol(TypeSymbol type) {
|
||||
if (type instanceof ClassSymbol)
|
||||
return (ClassSymbol) type;
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
Cause.TYPE_ERROR,
|
||||
"A class type was required, but %s was found", type);
|
||||
}
|
||||
|
||||
|
@ -63,10 +64,10 @@ public class TypeChecker {
|
|||
if (type instanceof ArrayTypeSymbol)
|
||||
return (ArrayTypeSymbol) type;
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
Cause.TYPE_ERROR,
|
||||
"An array type was required, but %s was found", type);
|
||||
}
|
||||
|
||||
|
||||
private TypeSymbol typeExpr(Expr expr, SymTable<VariableSymbol> locals) {
|
||||
return new TypingVisitor().visit(expr, locals);
|
||||
}
|
||||
|
@ -80,9 +81,9 @@ public class TypeChecker {
|
|||
expected,
|
||||
actual);
|
||||
}
|
||||
|
||||
|
||||
private class MethodDeclVisitor extends AstVisitor<Void, Void> {
|
||||
|
||||
|
||||
private MethodDecl method;
|
||||
private SymTable<VariableSymbol> locals;
|
||||
|
||||
|
@ -95,7 +96,7 @@ public class TypeChecker {
|
|||
protected Void dfltExpr(Expr ast, Void arg) {
|
||||
throw new RuntimeException("Should not get here");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void assign(Assign ast, Void arg) {
|
||||
TypeSymbol lhs = typeLhs(ast.left(), locals);
|
||||
|
@ -113,15 +114,15 @@ public class TypeChecker {
|
|||
checkType(ast.arg(), PrimitiveTypeSymbol.intType, locals);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void builtInWriteln(BuiltInWriteln ast, Void arg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void ifElse(IfElse ast, Void arg) {
|
||||
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
visit(ast.then(), arg);
|
||||
if (ast.otherwise() != null)
|
||||
visit(ast.otherwise(), arg);
|
||||
|
@ -131,17 +132,17 @@ public class TypeChecker {
|
|||
@Override
|
||||
public Void methodCall(MethodCall ast, Void arg) {
|
||||
typeExpr(ast.getMethodCallExpr(), locals);
|
||||
|
||||
|
||||
return null;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void whileLoop(WhileLoop ast, Void arg) {
|
||||
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
return visit(ast.body(), arg);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void returnStmt(ReturnStmt ast, Void arg) {
|
||||
boolean hasArg = ast.arg() != null;
|
||||
|
@ -150,11 +151,11 @@ public class TypeChecker {
|
|||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Return statement of method with void return type should "
|
||||
+ "not have arguments.");
|
||||
+ "not have arguments.");
|
||||
} else if (!hasArg) {
|
||||
// X m() { return; }
|
||||
if (method.sym.returnType != PrimitiveTypeSymbol.voidType) {
|
||||
throw new SemanticFailure(
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Return statement has no arguments. Expected %s but type was %s",
|
||||
method.sym.returnType,
|
||||
|
@ -164,58 +165,58 @@ public class TypeChecker {
|
|||
// X m() { return y; }
|
||||
checkType(ast.arg(), method.sym.returnType, locals);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private class TypingVisitor extends ExprVisitor<TypeSymbol, SymTable<VariableSymbol>> {
|
||||
|
||||
|
||||
@Override
|
||||
public TypeSymbol visit(Expr ast, SymTable<VariableSymbol> arg) {
|
||||
ast.type = super.visit(ast, arg);
|
||||
return ast.type;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TypeSymbol binaryOp(BinaryOp ast, SymTable<VariableSymbol> locals) {
|
||||
switch (ast.operator) {
|
||||
case B_TIMES:
|
||||
case B_DIV:
|
||||
case B_MOD:
|
||||
case B_PLUS:
|
||||
case B_MINUS:
|
||||
TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals);
|
||||
checkTypeIsInt(type);
|
||||
return type;
|
||||
|
||||
case B_AND:
|
||||
case B_OR:
|
||||
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
|
||||
case B_EQUAL:
|
||||
case B_NOT_EQUAL:
|
||||
TypeSymbol left = typeExpr(ast.left(), locals);
|
||||
TypeSymbol right = typeExpr(ast.right(), locals);
|
||||
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Types %s and %s could never be equal",
|
||||
left, right);
|
||||
|
||||
case B_LESS_THAN:
|
||||
case B_LESS_OR_EQUAL:
|
||||
case B_GREATER_THAN:
|
||||
case B_GREATER_OR_EQUAL:
|
||||
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
|
||||
case B_TIMES:
|
||||
case B_DIV:
|
||||
case B_MOD:
|
||||
case B_PLUS:
|
||||
case B_MINUS:
|
||||
TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals);
|
||||
checkTypeIsInt(type);
|
||||
return type;
|
||||
|
||||
case B_AND:
|
||||
case B_OR:
|
||||
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
|
||||
case B_EQUAL:
|
||||
case B_NOT_EQUAL:
|
||||
TypeSymbol left = typeExpr(ast.left(), locals);
|
||||
TypeSymbol right = typeExpr(ast.right(), locals);
|
||||
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Types %s and %s could never be equal",
|
||||
left, right);
|
||||
|
||||
case B_LESS_THAN:
|
||||
case B_LESS_OR_EQUAL:
|
||||
case B_GREATER_THAN:
|
||||
case B_GREATER_OR_EQUAL:
|
||||
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
|
||||
}
|
||||
throw new RuntimeException("Unhandled operator "+ast.operator);
|
||||
throw new RuntimeException("Unhandled operator " + ast.operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -232,10 +233,10 @@ public class TypeChecker {
|
|||
public TypeSymbol cast(Cast ast, SymTable<VariableSymbol> locals) {
|
||||
TypeSymbol argType = typeExpr(ast.arg(), locals);
|
||||
ast.type = typeSymbols.getType(ast.typeName);
|
||||
|
||||
|
||||
if (argType.isSuperTypeOf(ast.type) || ast.type.isSuperTypeOf(argType))
|
||||
return ast.type;
|
||||
|
||||
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Types %s and %s in cast are completely unrelated.",
|
||||
|
@ -295,21 +296,20 @@ public class TypeChecker {
|
|||
|
||||
@Override
|
||||
public TypeSymbol unaryOp(UnaryOp ast, SymTable<VariableSymbol> locals) {
|
||||
|
||||
|
||||
switch (ast.operator) {
|
||||
case U_PLUS:
|
||||
case U_MINUS:
|
||||
{
|
||||
TypeSymbol type = typeExpr(ast.arg(), locals);
|
||||
checkTypeIsInt(type);
|
||||
return type;
|
||||
case U_PLUS:
|
||||
case U_MINUS: {
|
||||
TypeSymbol type = typeExpr(ast.arg(), locals);
|
||||
checkTypeIsInt(type);
|
||||
return type;
|
||||
}
|
||||
|
||||
case U_BOOL_NOT:
|
||||
checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
}
|
||||
|
||||
case U_BOOL_NOT:
|
||||
checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
}
|
||||
throw new RuntimeException("Unknown unary op "+ast.operator);
|
||||
throw new RuntimeException("Unknown unary op " + ast.operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -322,20 +322,20 @@ public class TypeChecker {
|
|||
ast.setSymbol(locals.get(ast.name));
|
||||
return ast.sym.type;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TypeSymbol methodCall(MethodCallExpr ast, SymTable<VariableSymbol> locals) {
|
||||
|
||||
ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals));
|
||||
ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals));
|
||||
MethodSymbol mthd = rcvrType.getMethod(ast.methodName);
|
||||
if (mthd == null)
|
||||
throw new SemanticFailure(
|
||||
Cause.NO_SUCH_METHOD,
|
||||
"Class %s has no method %s()",
|
||||
rcvrType.name, ast.methodName);
|
||||
|
||||
rcvrType.name, ast.methodName);
|
||||
|
||||
ast.sym = mthd;
|
||||
|
||||
|
||||
// Check that the number of arguments is correct.
|
||||
if (ast.argumentsWithoutReceiver().size() != mthd.parameters.size())
|
||||
throw new SemanticFailure(
|
||||
|
@ -344,33 +344,36 @@ public class TypeChecker {
|
|||
ast.methodName,
|
||||
mthd.parameters.size(),
|
||||
ast.argumentsWithoutReceiver().size());
|
||||
|
||||
|
||||
// Check that the arguments are of correct type.
|
||||
int i = 0;
|
||||
for (Ast argAst : ast.argumentsWithoutReceiver())
|
||||
checkType((Expr)argAst, mthd.parameters.get(i++).type, locals);
|
||||
|
||||
checkType((Expr) argAst, mthd.parameters.get(i++).type, locals);
|
||||
|
||||
return ast.sym.returnType;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks an expr as the left-hand-side of an assignment,
|
||||
* returning the type of value that may be assigned there.
|
||||
* May fail if the expression is not a valid LHS (for example,
|
||||
* a "final" field). */
|
||||
* a "final" field).
|
||||
*/
|
||||
private TypeSymbol typeLhs(Expr expr, SymTable<VariableSymbol> locals) {
|
||||
return new LValueVisitor().visit(expr, locals);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see TypeChecker#typeLhs(Expr, SymTable)
|
||||
*/
|
||||
private class LValueVisitor extends ExprVisitor<TypeSymbol, SymTable<VariableSymbol>> {
|
||||
/** Fields, array-indexing, and vars can be on the LHS: */
|
||||
|
||||
/**
|
||||
* Fields, array-indexing, and vars can be on the LHS:
|
||||
*/
|
||||
|
||||
@Override
|
||||
public TypeSymbol field(Field ast, SymTable<VariableSymbol> locals) {
|
||||
return typeExpr(ast, locals);
|
||||
|
@ -385,13 +388,15 @@ public class TypeChecker {
|
|||
public TypeSymbol var(Var ast, SymTable<VariableSymbol> locals) {
|
||||
return typeExpr(ast, locals);
|
||||
}
|
||||
|
||||
/** Any other kind of expression is not a value lvalue */
|
||||
|
||||
/**
|
||||
* Any other kind of expression is not a value lvalue
|
||||
*/
|
||||
@Override
|
||||
protected TypeSymbol dfltExpr(Expr ast, SymTable<VariableSymbol> locals) {
|
||||
throw new SemanticFailure(
|
||||
Cause.NOT_ASSIGNABLE,
|
||||
"'%s' is not a valid lvalue",
|
||||
"'%s' is not a valid lvalue",
|
||||
AstOneLine.toString(ast));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue