Homework 4

This commit is contained in:
Carlos Galindo 2020-01-15 22:34:57 +01:00
commit 72cc3206c4
Signed by: kauron
GPG key ID: 83E68706DEE119A3
125 changed files with 4200 additions and 1636 deletions

View file

@ -1,346 +0,0 @@
package cd.frontend.semantic;
import cd.frontend.semantic.SemanticAnalyzer.SemanticLocation;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.ExprVisitor;
import cd.ir.Symbol;
import cd.ir.Symbol.*;
import java.util.List;
/**
* Semantic analyzer for {@link Expr} nodes in the pattern of a visitor. <br>
* All methods may throw {@link SemanticFailure} if an error is discovered,
* with the appropriate {@link Cause}. <br>
* The current implementation returns the resulting {@link Symbol} for each expression and
* uses the {@link ClassDecl} argument to obtain the self-reference in the {@link ThisRef} node.
* <br> Some methods may throw a {@link RuntimeException} if they are in a
* illegal situation, given the grammar of Javali and current implementation.
* This signals an error in the implementation, not a user-caused error.
* @see cd.frontend.semantic.StmtAnalyzer
* @author Carlos Galindo
* @author Levin Moser
*/
public class ExprAnalyzer extends ExprVisitor<TypeSymbol,SemanticLocation> {
/** Reference to the main semantic analyzer, used to access the type list via
* {@link SemanticAnalyzer#findType(String)} */
private SemanticAnalyzer sem;
ExprAnalyzer(SemanticAnalyzer sem) {
this.sem = sem;
}
/**
* Checks the possible semantic errors that can appear in a {@link BinaryOp} node
* @param ast The node describing the binary operation
* @param arg The class and method in which the operation is performed
* @return A {@link PrimitiveTypeSymbol} of resulting type, depending on the operation
* performed (either {@code int} or {@code boolean})
* @throws SemanticFailure If the types of both operands aren't equal
* @throws SemanticFailure With {@code ==} or {@code !=} operators, if no argument's type is
* a subtype of the other
* @throws SemanticFailure If the type of the operands doesn't match the type
* of the operation (either {@code int} or {@code boolean}
* @throws RuntimeException If the implementation has missed any {@link BinaryOp.BOp} type
*/
@Override
public TypeSymbol binaryOp(BinaryOp ast, SemanticLocation arg) {
TypeSymbol leftType = visit(ast.left(), arg);
TypeSymbol rightType = visit(ast.right(), arg);
TypeSymbol desiredType;
TypeSymbol returnType = PrimitiveTypeSymbol.booleanType;
switch (ast.operator) {
case B_AND:
case B_OR:
desiredType = PrimitiveTypeSymbol.booleanType;
break;
case B_PLUS:
case B_MINUS:
case B_TIMES:
case B_DIV:
case B_MOD:
desiredType = PrimitiveTypeSymbol.intType;
returnType = desiredType;
break;
case B_LESS_THAN:
case B_GREATER_THAN:
case B_LESS_OR_EQUAL:
case B_GREATER_OR_EQUAL:
desiredType = PrimitiveTypeSymbol.intType;
break;
case B_EQUAL:
case B_NOT_EQUAL:
// Can take any pair of types that are a subtype of each other
desiredType = null;
break;
default:
throw new RuntimeException("Missing BOp in implementation of ExprAnalyzer#binaryOp(...)");
}
if (desiredType == null) {
if (leftType.isSubtypeOf(rightType) || rightType.isSubtypeOf(leftType))
return returnType;
else
throw new SemanticFailure(Cause.TYPE_ERROR,
"Operation \"%s\" operation with incompatible types %s and %s",
ast.operator.repr, leftType, rightType);
} else if (leftType == rightType) {
if (leftType == desiredType)
return returnType;
else
throw new SemanticFailure(Cause.TYPE_ERROR,
"Arguments of %s operation must be of %s type, found %s",
ast.operator.repr, desiredType, leftType);
} else {
throw new SemanticFailure(Cause.TYPE_ERROR,
"Arguments of %s operation must be of same type %s, found %s and %s",
ast.operator.repr, desiredType, leftType, rightType);
}
}
/**
* Obtains the {@code boolean} type
* @param ast Node describing the constant
* @param arg Class and method in which the expression exists
* @return {@link PrimitiveTypeSymbol} representing the {@code boolean} type
*/
@Override
public TypeSymbol booleanConst(BooleanConst ast, SemanticLocation arg) {
return PrimitiveTypeSymbol.booleanType;
}
/**
* Obtains the {@code int} type (all reads in Javali result in an integer)
* @param ast Node describing the expression
* @param arg Class and method in which the expression exists
* @return {@link PrimitiveTypeSymbol} representing the {@code int} type
*/
@Override
public TypeSymbol builtInRead(BuiltInRead ast, SemanticLocation arg) {
return PrimitiveTypeSymbol.intType;
}
/**
* Checks possible semantic errors in a {@link Cast} node. <br>
* Uses {@link TypeSymbol#isSubtypeOf(TypeSymbol)} to determine if the types are compatible.
* @return The resulting type after the cast
* @throws SemanticFailure If the destination type doesn't exist
* @throws SemanticFailure If the types are incompatible (none is a subtype of the other)
*/
@Override
public TypeSymbol cast(Cast ast, SemanticLocation arg) {
TypeSymbol castType = sem.findType(ast.typeName);
TypeSymbol exprType = visit(ast.arg(), arg);
if (exprType.isSubtypeOf(castType) || castType.isSubtypeOf(exprType))
return castType;
else
throw new SemanticFailure(Cause.TYPE_ERROR,
"Type %s cannot be casted to %s, as they are unrelated", exprType, castType);
}
/**
* Checks possible semantic errors in a {@link Field} node.
* @param ast Node describing the field access
* @param arg Class and method in which the expression exists
* @return Field accessed, as defined in the corresponding {@link ClassSymbol}
* @throws SemanticFailure If the field to access doesn't exist
* @throws SemanticFailure If the expression in whose field is accessed
* is not a {@link ClassSymbol}
*/
@Override
public TypeSymbol field(Field ast, SemanticLocation arg) {
TypeSymbol variableType = visit(ast.arg(), arg);
if (!(variableType instanceof ClassSymbol))
throw new SemanticFailure(Cause.TYPE_ERROR,
"The field of a %s variable cannot be accessed, it is not a object type", variableType);
ClassSymbol classType = (ClassSymbol) variableType;
VariableSymbol field = classType.getField(ast.fieldName);
if (field == null)
throw new SemanticFailure(Cause.NO_SUCH_FIELD,
"The field %s doesn't exist in class %s",
ast.fieldName, variableType);
else
return field.type;
}
/**
* Checks possible semantic errors in a {@link Index} node.
* @param ast Node describing the index operation
* @param arg Class and method in which the expression exists
* @return Type of the elements of the array indexed
* @throws SemanticFailure If the index is not an integer
* @throws SemanticFailure If the expression indexed is not an array
*/
@Override
public TypeSymbol index(Index ast, SemanticLocation arg) {
TypeSymbol arrayType = visit(ast.left(), arg);
TypeSymbol indexType = visit(ast.right(), arg);
if (indexType != PrimitiveTypeSymbol.intType)
throw new SemanticFailure(Cause.TYPE_ERROR, "The index must be a int, found %s", indexType);
else if (!(arrayType instanceof ArrayTypeSymbol))
throw new SemanticFailure(Cause.TYPE_ERROR, "Expecting array type, found %s", arrayType);
else
return ((ArrayTypeSymbol) arrayType).elementType;
}
/**
* Obtains the {@code int} type
* @param ast Node describing the constant
* @param arg Class and method in which the expression exists
* @return A PrimitiveTypeSymbol for the type int
*/
@Override
public TypeSymbol intConst(IntConst ast, SemanticLocation arg) {
return PrimitiveTypeSymbol.intType;
}
/**
* Checks that the method referenced exists in the current scope and the typing of
* the parameters.
* @param ast Node describing the method call
* @param arg Class and method in which the expression exists
* @return The return type for the method called
* @throws SemanticFailure If the number of parameters doesn't match the declaration
* @throws SemanticFailure If the parameters' types don't match the declaration
* @throws SemanticFailure If the method doesn't exist
* @throws SemanticFailure If the {@link MethodCallExpr#receiver()} is not
* a object type (methods cannot be called on PrimitiveTypes or Arrays)
*/
@Override
public TypeSymbol methodCall(MethodCallExpr ast, SemanticLocation arg) {
TypeSymbol receiverType = visit(ast.receiver(), arg);
// Check receiver validity
if (!(receiverType instanceof ClassSymbol))
throw new SemanticFailure(Cause.TYPE_ERROR, "Expected object class, found %s", receiverType);
// Find method symbol
ast.sym = sem.findMethod(ast.methodName, (ClassSymbol) receiverType);
if (ast.sym == null)
throw new SemanticFailure(Cause.NO_SUCH_METHOD, "Method %s(...) doesn't exist", ast.methodName);
// Compare expected with available parameters
List<Expr> realParams = ast.argumentsWithoutReceiver();
List<VariableSymbol> expectedParams = ast.sym.parameters;
if (realParams.size() != expectedParams.size())
throw new SemanticFailure(Cause.WRONG_NUMBER_OF_ARGUMENTS,
"%s call has %d parameters, expected %d",
ast.sym, realParams.size(), expectedParams.size());
for (int i = 0; i < realParams.size(); i++) {
TypeSymbol realType = visit(realParams.get(i), arg);
TypeSymbol expectedType = expectedParams.get(i).type;
if (!realType.isSubtypeOf(expectedType))
throw new SemanticFailure(Cause.TYPE_ERROR, "Parameter %d of %s of type %s, expected %s",
i, ast.sym, realType, expectedType);
}
return ast.sym.returnType;
}
/**
* Checks for semantic errors in the creation of a new object of the specified type
* @param ast Node describing the new object expression
* @param arg Class and method in which the expression exists
* @return {@link ClassSymbol} corresponding to the type of the new object
* @throws SemanticFailure If the type referenced doesn't exist
*/
@Override
public TypeSymbol newObject(NewObject ast, SemanticLocation arg) {
return sem.findType(ast.typeName);
}
/**
* Checks for semantic errors in the creation of a new array of the specified type and size
* @param ast Node describing the new array expression
* @param arg Class and method in which the expression exists
* @return ArrayTypeSymbol of the corresponding type
* @throws SemanticFailure If the array type doesn't exist
* @throws SemanticFailure If the expression indicating the size of the array is not an {@code int}
*/
@Override
public TypeSymbol newArray(NewArray ast, SemanticLocation arg) {
// Type exists (if it does, it is guaranteed by the grammar to be a ArrayTypeSymbol)
ArrayTypeSymbol type = (ArrayTypeSymbol) sem.findType(ast.typeName);
// Array length must be of type int
TypeSymbol indexType = visit(ast.arg(), arg);
if (indexType != PrimitiveTypeSymbol.intType)
throw new SemanticFailure(Cause.TYPE_ERROR, "Array size must be of type int, found %s", indexType);
else
return type;
}
/**
* Obtains the {@code null} type
* @param ast Node describing the new object instantiation expression
* @param arg Class and method in which the expression exists
* @return The ClassSymbol corresponding to {@code null}
*/
@Override
public TypeSymbol nullConst(NullConst ast, SemanticLocation arg) {
return ClassSymbol.nullType;
}
/**
* Obtains the reference to {@code this}
* @param ast The AST node
* @param arg Class and method in which the class reference is accessed
* @return The VariableSymbol {@code this} from the class
*/
@Override
public TypeSymbol thisRef(ThisRef ast, SemanticLocation arg) {
return arg.classSymbol.thisSymbol.type;
}
/**
* Checks whether a {@link UnaryOp} is valid semantically
* @param ast The node describing the operation
* @param arg Class and method in which the expression exists
* @return The resulting type of the operation
* @throws SemanticFailure With "{@code !}" operator, if the operand is not a {@code boolean}
* @throws SemanticFailure With "{@code +}" or "{@code -}" operators, if the operand is not an {@code int}
*/
@Override
public TypeSymbol unaryOp(UnaryOp ast, SemanticLocation arg) {
TypeSymbol argType = visit(ast.arg(), arg);
TypeSymbol desiredType = PrimitiveTypeSymbol.intType;
if (ast.operator.equals(Ast.UnaryOp.UOp.U_BOOL_NOT))
desiredType = PrimitiveTypeSymbol.booleanType;
if (argType == desiredType)
return desiredType;
else
throw new SemanticFailure(Cause.TYPE_ERROR,
"Operator %s expected operand of type %s, found %s",
ast.operator.repr, desiredType, argType);
}
/**
* Checks whether the variable referenced by the {@link Var} node exists
* in the current scope, either as local variable, method parameter or field
* (including inherited fields)
* <br> In the process, the {@link Var} node is linked to the
* {@link VariableSymbol}
* @param ast Node describing the variable access
* @param arg Class and method that define the current scope in which the
* variable is looked for
* @return The {@link VariableSymbol} corresponding to the variable
* @throws SemanticFailure If the variable doesn't exist in the current scope
*/
@Override
public TypeSymbol var(Var ast, SemanticLocation arg) {
// Local variable
ast.sym = arg.getMethod().locals.get(ast.name);
if (ast.sym != null)
return ast.sym.type;
// Method parameter
for(VariableSymbol var : arg.getMethod().parameters)
if (var.name.equals(ast.name))
return (ast.sym = var).type;
// Class field (or field of a superclass)
ast.sym = arg.classSymbol.getField(ast.name);
if (ast.sym != null)
return ast.sym.type;
// The variable cannot be found in any known scope
throw new SemanticFailure(Cause.NO_SUCH_VARIABLE, "Variable %s not found", ast.name);
}
}

View file

@ -0,0 +1,75 @@
package cd.frontend.semantic;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.AstVisitor;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.MethodSymbol;
import cd.ir.Symbol.VariableSymbol;
import cd.util.Pair;
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;
supers.add(classSym);
while (sc != null) {
if (supers.contains(sc))
throw new SemanticFailure(
Cause.CIRCULAR_INHERITANCE,
"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);
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",
ast.name, sym.parameters.size(),
superSym.parameters.size());
for (Pair<VariableSymbol> pair : Pair.zip(sym.parameters, superSym.parameters))
if (pair.a.type != pair.b.type)
throw new SemanticFailure(
Cause.INVALID_OVERRIDE,
"Method parameter %s has type %s, but " +
"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",
ast.name, sym.returnType, superSym.returnType);
}
return null;
}
}

View file

@ -0,0 +1,52 @@
package cd.frontend.semantic;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.AstVisitor;
/**
* Visitor that checks if all paths of a given sequence have a
* return statement.
*
* 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> {
@Override
protected Boolean dfltStmt(Stmt ast, Void arg) {
return false;
}
@Override
public Boolean returnStmt(ReturnStmt ast, Void arg) {
return true;
}
@Override
public Boolean ifElse(IfElse ast, Void arg) {
boolean allPathHaveAReturnStmt = true;
allPathHaveAReturnStmt &= visit(ast.then(), null);
allPathHaveAReturnStmt &= visit(ast.otherwise(), null);
return allPathHaveAReturnStmt;
}
@Override
public Boolean seq(Seq ast, Void arg) {
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;
}
}

View file

@ -2,234 +2,154 @@ package cd.frontend.semantic;
import cd.Main;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol;
import cd.ir.Symbol.*;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
public class SemanticAnalyzer {
/** Name of the main class of the program, containing the start point for its execution */
public static final String MAIN_CLASS_NAME = "Main";
/** Name of the start point method inside the main class, with signature {@code void MAIN_METHOD_NAME()} */
public static final String MAIN_METHOD_NAME = "main";
/** Reference to main method, for main class and list of types */
public final Main main;
/** Statement analyzer, entrypoint for the method body analysis */
public final StmtAnalyzer sa = new StmtAnalyzer(this);
/** For analyzing expressions, used by the StmtAnalyzer when an expression is found */
public final ExprAnalyzer ea = new ExprAnalyzer(this);
private List<ClassDecl> classDecls;
public SemanticAnalyzer(Main main) {
this.main = main;
}
/**
* Entrypoint for semantic analysis, will check that a list of classes is correct semantically
* @param classDeclList List of classes that form the Javali program
* @throws SemanticFailure If any semantic error is found
*/
public void check(List<ClassDecl> classDeclList) throws SemanticFailure {
this.classDecls = classDeclList;
checkCircularInheritance(classDecls);
// Built-in type generation
main.allTypeSymbols = new ArrayList<>();
addBuiltInTypes(main.allTypeSymbols);
// Main semantic analysis
for (ClassDecl classDecl : classDecls)
sa.visit(classDecl, null);
// Find entrypoint Main and Main.main(...)
findMainClass();
findEntrypoint(main.mainType);
public void check(List<ClassDecl> classDecls)
throws SemanticFailure {
{
SymTable<TypeSymbol> typeSymbols = createSymbols(classDecls);
checkInheritance(classDecls);
checkStartPoint(typeSymbols);
checkMethodBodies(typeSymbols, classDecls);
}
}
/**
* Generates the basic types that exist in any Javali program by default
* @param list Type list to which the types must be added
* 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
* (named {@code type[]}).
* @see SymbolCreator
*/
private void addBuiltInTypes(List<TypeSymbol> list) {
// Add primitive types
list.add(PrimitiveTypeSymbol.booleanType);
list.add(PrimitiveTypeSymbol.intType);
list.add(PrimitiveTypeSymbol.voidType);
// Add basic class types
list.add(ClassSymbol.objectType);
list.add(ClassSymbol.nullType);
// Add arrays of primitive and class types
list.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.intType));
list.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.booleanType));
list.add(new ArrayTypeSymbol(ClassSymbol.objectType));
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);
}
// Create symbols for arrays of each type.
for (Symbol sym : new ArrayList<Symbol>(typeSymbols.localSymbols())) {
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:
* 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) {
for (ClassDecl cd : classDecls)
new InheritanceChecker().visit(cd, null);
}
/**
* Finds the {@code Main} class in a Javali program
* @throws SemanticFailure If there is no such class
* Guarantee there is a class Main which defines a method main
* with no arguments.
*/
private void findMainClass() {
for (TypeSymbol type : main.allTypeSymbols)
if (type.name.equals(MAIN_CLASS_NAME))
main.mainType = (ClassSymbol) type;
if (main.mainType == null)
throw new SemanticFailure(Cause.INVALID_START_POINT,
"Can't find class called \"%s\"", MAIN_CLASS_NAME);
}
/**
* Finds the entrypoint for the Javali program
* @param mainType {@code Main} class of the program, where the entrypoint should be.
* @throws SemanticFailure If there is no such entrypoint, of it has incorrect return
* value or parameters.
*/
private void findEntrypoint(ClassSymbol mainType) {
MethodSymbol mainMethod = mainType.getMethod(MAIN_METHOD_NAME);
if (mainMethod == null)
throw new SemanticFailure(Cause.INVALID_START_POINT,
"No method called \"%s\" in %s class", MAIN_METHOD_NAME, MAIN_CLASS_NAME);
else if (mainMethod.returnType != PrimitiveTypeSymbol.voidType)
throw new SemanticFailure(Cause.INVALID_START_POINT,
"Method %s.%s(...) returns %s, expected void",
MAIN_CLASS_NAME, MAIN_METHOD_NAME, mainMethod.returnType);
else if (!mainMethod.parameters.isEmpty())
throw new SemanticFailure(Cause.INVALID_START_POINT,
"Method %s.%s(...) must have no arguments, found %d",
MAIN_CLASS_NAME, MAIN_METHOD_NAME, mainMethod.parameters.size());
}
/**
* Looks up the requested type in the list of types. If not found, will try to read the remaining
* {@link ClassDecl} nodes to find the desired type.
* @param type Name of the type. Can be built-in, user-declared, or an array type.
* @return The TypeSymbol object associated with the type (no new symbol is created, only looked up from
* the general list).
* @throws SemanticFailure If the type doesn't exist
*/
public TypeSymbol findType(String type) {
// Option 1: type already exists
for (TypeSymbol symbol : main.allTypeSymbols)
if (type.equals(symbol.name))
return symbol;
// Option 2: type is declared by user but hasn't been visited yet
String noArrayType = type;
if (type.contains("[]")) // To find the type of an array, remove "[]" and search for the name
noArrayType = type.substring(0, type.indexOf("[]"));
for (ClassDecl classDecl : classDecls)
if (classDecl.name.equals(noArrayType)) {
sa.visit(classDecl, null);
return findType(type); // Will use option 1
}
// Option 3: the type doesn't exist
throw new SemanticFailure(Cause.NO_SUCH_TYPE, "Type %s doesn't exist", type);
}
/**
* Looks up the requested method in the corresponding class. If not found, will try to find it as
* {@link MethodDecl} inside the {@link ClassDecl}. Will also look in superclasses of {@code receiverType}.
* @param methodName Name of the method that is being looked up
* @param receiverType Class in which the method must be found
* @return The method symbol (not created, but looked up from the class' method list.
*/
public MethodSymbol findMethod(String methodName, ClassSymbol receiverType) {
// Option 0: looking for methods in Object or null class (that has no methods or associated ast)
if (receiverType == ClassSymbol.objectType || receiverType == ClassSymbol.nullType)
throw new SemanticFailure(Cause.NO_SUCH_METHOD, "Type %s has no methods", receiverType);
// Option 1: method has already been discovered
MethodSymbol res = receiverType.methods.get(methodName);
if (res != null) return res;
// Option 2: method exists but hasn't been read yet (look in superclasses)
assert receiverType.ast != null;
for (MethodDecl methodDecl : receiverType.ast.methods()) {
if (methodDecl.name.equals(methodName)) {
sa.visit(methodDecl, new SemanticLocation(receiverType));
return findMethod(methodName, receiverType); // Will use Option 1
private void checkStartPoint(SymTable<TypeSymbol> typeSymbols) {
Symbol mainClass = typeSymbols.get("Main");
if (mainClass != null && mainClass instanceof ClassSymbol) {
ClassSymbol cs = (ClassSymbol) mainClass;
MethodSymbol mainMethod = cs.getMethod("main");
if (mainMethod != null && mainMethod.parameters.size() == 0 &&
mainMethod.returnType == PrimitiveTypeSymbol.voidType) {
return; // found the main() method!
}
}
// Option 3: method exists in superclass
res = findMethod(methodName, receiverType.superClass);
if (res != null) return res;
// Option 4: method doesn't exist
throw new SemanticFailure(Cause.NO_SUCH_METHOD, "Method %s of class %s doesn't exist",
methodName, receiverType.name);
throw new SemanticFailure(Cause.INVALID_START_POINT, "No Main class with method 'void main()' found");
}
/**
* Recursively checks for circular inheritance <br>
* This check is done with the Strings extracted from the list of class declarations, as to perform it
* before the type check (which can be difficult to deal with given undiscovered circular inheritance), in order
* to avoid methods such as {@link ClassSymbol#getMethod(String)} or {@link ClassSymbol#getField(String)}, which
* are recursive by nature, throw a {@link StackOverflowError}.
* @param classDecls List of class declarations to check against
* @throws SemanticFailure If circular inheritance is found
* @throws SemanticFailure If any type inherited by a class doesn't exist (guaranteed to be a class type by
* the language grammar).
* Check the bodies of methods for errors, particularly type errors
* but also undefined identifiers and the like.
* @see TypeChecker
*/
public void checkCircularInheritance(List<ClassDecl> classDecls) {
Map<String, String> inherits = new HashMap<>();
for (ClassDecl classDecl : classDecls)
inherits.put(classDecl.name, classDecl.superClass);
for (ClassDecl classDecl : classDecls)
checkCircularInheritance(classDecl.name, new HashSet<>(), inherits);
}
private void checkMethodBodies(
SymTable<TypeSymbol> typeSymbols,
List<ClassDecl> classDecls)
{
TypeChecker tc = new TypeChecker(typeSymbols);
for (ClassDecl classd : classDecls) {
SymTable<VariableSymbol> fldTable = new SymTable<VariableSymbol>(null);
private void checkCircularInheritance(String className, Set<String> set, Map<String, String> inherits) {
if (className.equals("Object")) return;
if (!set.add(className))
throw new SemanticFailure(Cause.CIRCULAR_INHERITANCE,
"Class %s has itself as superclass", className);
if (inherits.get(className) != null)
checkCircularInheritance(inherits.get(className), set, inherits);
else
throw new SemanticFailure(Cause.NO_SUCH_TYPE, "Class %s doesn't exist", className);
}
/**
* Helper class that stores the position of a statement or expression being visited, by providing a link
* to the method and class symbols. This is useful when visiting variable nodes, method calls
* and {@code this} references. <br>
* When entering and existing the body of a method, {@link SemanticLocation#beginMethod(MethodSymbol)} and
* {@link SemanticLocation#endMethod()} are used to set and unset the methodSymbol field.
*/
public static class SemanticLocation {
private MethodSymbol methodSymbol;
public final ClassSymbol classSymbol;
/**
* Creates a semantic location that points to the {@code classSymbol}
*/
SemanticLocation(ClassSymbol classSymbol) {
this(classSymbol, null);
}
private SemanticLocation(ClassSymbol classSymbol, MethodSymbol methodSymbol) {
assert classSymbol != null;
this.methodSymbol = methodSymbol;
this.classSymbol = classSymbol;
}
public MethodSymbol getMethod() {
return methodSymbol;
}
public boolean isInMethod() {
return methodSymbol != null;
}
/**
* Setter for methodSymbol
*/
public void beginMethod(MethodSymbol methodSymbol) {
this.methodSymbol = methodSymbol;
}
/**
* Unsetter for methodSymbol
*/
public void endMethod() {
this.methodSymbol = null;
// add all fields of this class, or any of its super classes
for (ClassSymbol p = classd.sym; p != null; p = p.superClass)
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,
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);
}
}
}
}

View file

@ -7,12 +7,7 @@ public class SemanticFailure extends RuntimeException {
private static final long serialVersionUID = 5375946759285719123L;
public enum Cause {
/**
* Caused by a nested method being found. Those are not
* supported.
*/
NESTED_METHODS_UNSUPPORTED,
/**
* Caused by an assignment to either a final field, {@code this},
* or some other kind of expression which cannot be assigned to.
@ -20,9 +15,6 @@ public class SemanticFailure extends RuntimeException {
* under {@link #TYPE_ERROR}. */
NOT_ASSIGNABLE,
/** The value of a final field is not a compile-time constant */
NOT_A_CONSTANT_EXPR,
/** Two variables, fields, methods, or classes with the same name
* were declared in the same scope */
DOUBLE_DECLARATION,

View file

@ -1,353 +0,0 @@
package cd.frontend.semantic;
import cd.frontend.semantic.SemanticAnalyzer.SemanticLocation;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.AstVisitor;
import cd.ir.Symbol;
import cd.ir.Symbol.*;
import cd.ir.Symbol.VariableSymbol.Kind;
/**
* Semantic analyzer for {@link Ast} nodes in the pattern of a visitor. <br>
* All methods may throw {@link SemanticFailure} if an error is discovered,
* with the appropriate {@link Cause} and a short explanation to give some context
* to the user. <br>
* The current implementation employs the {@link Symbol} returned to signal a
* statement, or group of them that contain a unconditional return (or return in both
* branches of a {@link IfElse} node. The type is checked by
* {@link StmtAnalyzer#returnStmt(ReturnStmt, SemanticLocation)} and the existence, by
* {@link StmtAnalyzer#methodDecl(MethodDecl, SemanticLocation)} <br>
* Uses the {@link SemanticLocation} argument to give context to each statement and
* expression visited, so that they may access variables, fields and methods.
* @see ExprAnalyzer
* @author Carlos Galindo
* @author Levin Moser
*/
public class StmtAnalyzer extends AstVisitor<Symbol,SemanticLocation> {
/**
* Reference to the main semantic analyzer <br>
* Needed to access {@link SemanticAnalyzer#findType(String)} and the
* {@link ExprAnalyzer}
*/
private SemanticAnalyzer sa;
StmtAnalyzer(SemanticAnalyzer sa) {
this.sa = sa;
}
/**
* Checks semantic errors in a {@link Assign} node
* @param ast The node containing all the information
* @param arg The class and method in which the statement takes place
* @return {@code null} (No return can occur in an assigment statement)
* @throws SemanticFailure If the right-hand side is not a subtype of the left-hand side
* @throws SemanticFailure If the left-hand side is a method call or a {@code this} reference
*/
@Override
public Symbol assign(Assign ast, SemanticLocation arg) {
TypeSymbol right = sa.ea.visit(ast.right(), arg);
TypeSymbol left = sa.ea.visit(ast.left(), arg);
if (!right.isSubtypeOf(left))
throw new SemanticFailure(Cause.TYPE_ERROR,
"In assignment, %s is not a subtype of %s", right, left);
// Only variables, fields and array dereferences are valid as left-hand side
if (ast.left() instanceof Var)
return null;
if (ast.left() instanceof Field)
return null;
if (ast.left() instanceof Index)
return null;
String leftExprType;
if (ast.left() instanceof MethodCallExpr)
leftExprType = "method call";
else if (ast.left() instanceof ThisRef)
leftExprType = "this reference";
else
throw new RuntimeException("Missing valid or invalid left-hand side of assignment, check grammar");
throw new SemanticFailure(Cause.NOT_ASSIGNABLE,
"Left-hand side of assignment must be field, var or index, found %s", leftExprType);
}
/**
* Checks a possible error in a {@link BuiltInWrite} AST node.
* @param ast The node containing all the information
* @param arg The class and method in which the statement takes place
* @return {@code null} (No return statement can happen inside a write)
* @throws SemanticFailure If the type of the expression to write is not integer
*/
@Override
public Symbol builtInWrite(BuiltInWrite ast, SemanticLocation arg) {
TypeSymbol type = sa.ea.visit(ast.arg(), arg);
if (type != PrimitiveTypeSymbol.intType)
throw new SemanticFailure(Cause.TYPE_ERROR,
"Argument of write function must be an int, found %s", type);
else
return null;
}
/**
* Checks a class declaration and all contained fields and methods for semantic errors. <br>
* Will add itself and its corresponding array type to the types list.
* @param ast The node describing the class declaration
* @param arg {@code null}, as nested classes aren't permitted by the Javali grammar
* @return {@code null} (no need to check return types)
* @throws SemanticFailure If the type of the class already exists
* @throws SemanticFailure If the name of the class is Object (base class)
* @throws SemanticFailure If the superclass is not defined in the Javali program
*/
@Override
public Symbol classDecl(ClassDecl ast, SemanticLocation arg) {
if (ast.sym != null) return null; // Class already visited
// Generate first the symbol and link it to the ast node
ast.sym = new ClassSymbol(ast);
// No class can be named Object
if (ast.name.equals(ClassSymbol.objectType.name))
throw new SemanticFailure(Cause.OBJECT_CLASS_DEFINED, "The %s class cannot be redefined", ast.name);
// All classes have unique names
for (TypeSymbol type : sa.main.allTypeSymbols)
if (type.name.equals(ast.sym.name))
throw new SemanticFailure(Cause.DOUBLE_DECLARATION, "Another class exists with the name %s", type);
// Add basic and array type to typeList (this is the only method that generates
// a ClassSymbol or ArrayTypeSymbol)
sa.main.allTypeSymbols.add(ast.sym);
sa.main.allTypeSymbols.add(new ArrayTypeSymbol(ast.sym));
// The superclass exists (will visit other classes to look it up)
ast.sym.superClass = (ClassSymbol) sa.findType(ast.superClass);
// Visit children (fields and methods)
SemanticLocation thisClass = new SemanticLocation(ast.sym);
for (VarDecl varDecl : ast.fields())
ast.sym.fields.put(varDecl.name, (VariableSymbol) visit(varDecl, thisClass));
for (MethodDecl methodDecl : ast.methods())
visit(methodDecl, thisClass);
return ast.sym;
}
/**
* Checks for semantic correctness of a {@link MethodDecl} node, regarding both the method's
* signature and its body. <br>
* For the declaration of local variables of the method, the underlying
* {@link Seq} node is not visited, as to keep the logic in
* {@link StmtAnalyzer#seq(Seq, SemanticLocation)} simpler, and only related to
* the accumulation of the return paths.
* @param ast The node describing the method declaration
* @param arg The class the method is being declared in
* @return {@code null} (no need to check return types)
* @throws SemanticFailure If multiple local variables share the same name
* @throws SemanticFailure If there is a return type but no return statement in all paths
* @throws SemanticFailure If the method is overloaded
* @throws SemanticFailure If the method overrides another incorrectly
* (mismatched parameter number, type or return type)
* @throws SemanticFailure If the return type doesn't exist
*/
@Override
public Symbol methodDecl(MethodDecl ast, SemanticLocation arg) {
if (ast.sym != null) return null; // Already visited
ast.sym = new MethodSymbol(ast);
arg.beginMethod(ast.sym); // Modify the location to include the method symbol
// Check overloading (none is permitted)
if (arg.classSymbol.methods.get(ast.name) != null)
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
"Another method exists with the name %s in class %s, overloading is not permitted",
ast.name, arg);
// Check return type
ast.sym.returnType = sa.findType(ast.returnType);
// Check params type and generate VariableSymbols
assert ast.argumentNames.size() == ast.argumentTypes.size(); // This should be guaranteed by parser
for (int i = 0; i < ast.argumentNames.size(); i++) {
// check for repeated param name
for (VariableSymbol var : ast.sym.parameters)
if (var.name.equals(ast.argumentNames.get(i)))
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
"There are two parameters named %s in method %s", var, ast.sym);
// add to sym param list
ast.sym.parameters.add(new VariableSymbol(
ast.argumentNames.get(i),
sa.findType(ast.argumentTypes.get(i)),
Kind.PARAM));
}
// Check override (only possible if parameters and return type match)
MethodSymbol overridden = arg.classSymbol.getMethod(ast.name);
if (overridden != null) {
// Return value must be the same
if (overridden.returnType != ast.sym.returnType)
throw new SemanticFailure(Cause.INVALID_OVERRIDE,
"Overridden methods must share return type, expected %s, found %s",
overridden.returnType, ast.sym.returnType);
// Number of parameters must be the same
if (overridden.parameters.size() != ast.sym.parameters.size())
throw new SemanticFailure(Cause.INVALID_OVERRIDE,
"Overridden methods must have the same number of parameters, expected %d, found %d",
overridden.parameters.size(), ast.sym.parameters.size());
// Types must match for all parameters
for (int i = 0; i < ast.sym.parameters.size(); i++)
if (overridden.parameters.get(i).type != ast.sym.parameters.get(i).type)
throw new SemanticFailure(Cause.INVALID_OVERRIDE,
"Overridden methods must match parameters type, expected %s, found %s for parameter %d",
overridden.parameters.get(i).type, ast.sym.parameters.get(i).type, i);
}
// Check all local variables
for (Ast decl : ast.decls().children()) {
VarDecl varDecl = (VarDecl) decl;
VariableSymbol varSymbol = (VariableSymbol) visit(varDecl, arg);
ast.sym.locals.put(varDecl.name, varSymbol);
}
// Check statements and return type
Symbol returnSymbol = visit(ast.body(), arg);
if (returnSymbol == null && ast.sym.returnType != PrimitiveTypeSymbol.voidType) {
throw new SemanticFailure(Cause.MISSING_RETURN,
"Method %s missing return statement", ast.sym);
}
// Add method to class Symbol node
arg.classSymbol.methods.put(ast.name, ast.sym);
arg.endMethod();
return ast.sym;
}
/**
* Checks for semantic errors in a {@link VarDecl} node. It can either receive a local variable
* or a class field.
* @param ast Node containing the type and name for the variable
* @param arg Class [and method] in which the variable is being declared
* @return {@link VariableSymbol} representing the variable
* @throws SemanticFailure If the variable has already been declared in this class
* (hiding a field from a superclass is allowed)
* @throws SemanticFailure If the type of the variable doesn't exist
* @throws SemanticFailure If the local variable has already been declared
*/
@Override
public Symbol varDecl(VarDecl ast, SemanticLocation arg) {
if (arg.isInMethod()) { // Checks for local variable
if (arg.getMethod().locals.get(ast.name) != null)
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
"Variable %s is already declared as a variable", ast.name);
for (VariableSymbol var : arg.getMethod().parameters)
if (var.name.equals(ast.name))
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
"Variable %s is already declared as a parameter", ast.name);
} else { // Checks for field variable
if (arg.classSymbol.fields.get(ast.name) != null)
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
"Class %s cannot have more than one variable with the same name (%s)", arg, ast.name);
}
ast.sym = new VariableSymbol(
ast.name,
sa.findType(ast.type), // Type check included here
arg.isInMethod() ? Kind.FIELD : Kind.LOCAL);
return ast.sym;
}
/**
* Checks for semantic errors in a {@link IfElse} node
* @param ast Contains all the information regarding the condition and if/else bodies
* @param arg The class and method in whose body the if statement exists
* @return The return value if both branches have one, or {@code null} otherwise
* @throws SemanticFailure If the condition is not of boolean type
*/
@Override
public Symbol ifElse(IfElse ast, SemanticLocation arg) {
TypeSymbol condType = sa.ea.visit(ast.condition(), arg);
if (condType != PrimitiveTypeSymbol.booleanType)
throw new SemanticFailure(Cause.TYPE_ERROR, "Condition of if must be int, found %s", condType);
Symbol then = visit(ast.then(), arg);
Symbol otherwise = visit(ast.otherwise(), arg);
if (then != null && otherwise != null)
return then;
else
return null;
}
/**
* Checks for semantic errors in a {@link ReturnStmt} node
* @param ast Contains all information about the return statement
* @param arg The class and method in which this statement takes place
* @return Type of the underlying expression, to be returned by the method
* @throws SemanticFailure If the return type is not a subtype of the method signature's return type
* @throws SemanticFailure If the method does return a non-void but the return statement has no
* expression to return
*/
@Override
public Symbol returnStmt(ReturnStmt ast, SemanticLocation arg) {
if (ast.arg() != null) {
TypeSymbol returnType = sa.ea.visit(ast.arg(), arg);
if (returnType.isSubtypeOf(arg.getMethod().returnType))
return returnType;
throw new SemanticFailure(Cause.TYPE_ERROR,
"Return type of %s must be a subtype of %s, found %s",
arg.getMethod(), arg.getMethod().returnType, returnType);
} else {
if (arg.getMethod().returnType != PrimitiveTypeSymbol.voidType)
throw new SemanticFailure(Cause.TYPE_ERROR,
"Empty return, expecting type %s or subtype", arg.getMethod().returnType);
return null;
}
}
/**
* Checks for semantic errors in a method call
* @see ExprAnalyzer#methodCall(MethodCallExpr, SemanticLocation)
* @return {@code null} (No return statement in this node)
*/
@Override
public Symbol methodCall(MethodCall ast, SemanticLocation arg) {
sa.ea.visit(ast.getMethodCallExpr(), arg);
return null;
}
/**
* Checks that all members of the {@link Seq} node are valid. <br>
* This method will only visit sequences of statements, as the
* sequences of variables are checked in
* {@link StmtAnalyzer#methodDecl(MethodDecl, SemanticLocation)}, therefore
* the logic is simpler and separated for unrelated elements of the method
* body (all local variable declaration must come before the statements)
* @param ast The node with the sequence of statements
* @param arg Class and method in which this sequence exists, to be passed
* down to visited statements
* @return The first type returned by one of the underlying statements
*/
@Override
public Symbol seq(Seq ast, SemanticLocation arg) {
Symbol firstReturn = null;
for (Ast aChildren : ast.children()) {
Symbol sym = visit(aChildren, arg);
if (sym != null && firstReturn == null)
firstReturn = sym;
}
return firstReturn;
}
/**
* Checks semantic errors in a while loop
* @param ast Node containing all the information about the {@code while} loop
* @param arg The class and method in which the loop exists
* @return {@code null} (condition of the loop is not check to determine if
* it is always accessed, therefore it cannot be treated as an unconditional return)
* @throws SemanticFailure If the condition for the loop is not a boolean type
*/
@Override
public Symbol whileLoop(WhileLoop ast, SemanticLocation arg) {
TypeSymbol condType = sa.ea.visit(ast.condition(), arg);
if (condType != PrimitiveTypeSymbol.booleanType)
throw new SemanticFailure(Cause.TYPE_ERROR,
"Condition of while loop must be int, found %s", condType);
visit(ast.body(), arg);
return null;
}
}

View file

@ -0,0 +1,76 @@
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;
/**
* 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. */
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))
throw new SemanticFailure(Cause.DOUBLE_DECLARATION);
map.put(sym.name, sym);
}
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
*/
public boolean contains(String name) {
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
*/
public boolean containsLocally(String name) {
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 */
public S get(String name) {
S res = map.get(name);
if (res != null)
return res;
if (parent == null)
return null;
return parent.get(name);
}
/**
* 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... */
public S getType(String name) {
S res = get(name);
if (res == null)
throw new SemanticFailure(
Cause.NO_SUCH_TYPE,
"No type '%s' was found", name);
return res;
}
}

View file

@ -0,0 +1,131 @@
package cd.frontend.semantic;
import cd.Main;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Ast.VarDecl;
import cd.ir.AstVisitor;
import cd.ir.Symbol;
import cd.ir.Symbol.*;
import cd.ir.Symbol.VariableSymbol.Kind;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A helper class for {@link SemanticAnalyzer} which iterates through
* a class definition and creates symbols for all of its fields and
* methods. For each method, we also create symbols for parameters
* 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;
}
public void createSymbols(ClassDecl cd) {
// 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);
}
/**
* 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) {
if (map.containsKey(sym.name))
throw new SemanticFailure(
Cause.DOUBLE_DECLARATION,
"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
* parameters and local variables to each method as well.
* Checks for duplicate members.
*/
public class ClassSymbolCreator extends AstVisitor<Void, Void> {
final ClassSymbol classSym;
public ClassSymbolCreator(ClassSymbol classSym) {
this.classSym = classSym;
}
@Override
public Void varDecl(VarDecl ast, Void arg) {
ast.sym = new VariableSymbol(
ast.name, typesTable.getType(ast.type), Kind.FIELD);
add(classSym.fields, ast.sym);
return null;
}
@Override
public Void methodDecl(MethodDecl ast, Void arg) {
ast.sym = new MethodSymbol(ast);
add(classSym.methods, ast.sym);
// create return type symbol
if (ast.returnType.equals("void")) {
ast.sym.returnType = PrimitiveTypeSymbol.voidType;
} 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))
throw new SemanticFailure(
Cause.DOUBLE_DECLARATION,
"Method '%s' has two parameters named '%s'",
ast.sym, argumentName);
pnames.add(argumentName);
VariableSymbol vs = new VariableSymbol(
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(
ast.name, typesTable.getType(ast.type), Kind.LOCAL);
add(methodSym.locals, ast.sym);
return null;
}
}
}

View file

@ -0,0 +1,399 @@
package cd.frontend.semantic;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.AstVisitor;
import cd.ir.ExprVisitor;
import cd.ir.Symbol.*;
import cd.util.debug.AstOneLine;
public class TypeChecker {
final private SymTable<TypeSymbol> typeSymbols;
public TypeChecker(SymTable<TypeSymbol> typeSymbols) {
this.typeSymbols = typeSymbols;
}
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) {
final TypeSymbol leftType = typeExpr(leftExpr, locals);
final TypeSymbol rightType = typeExpr(rightExpr, locals);
if (leftType != rightType) {
throw new SemanticFailure(
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);
}
}
private ClassSymbol asClassSymbol(TypeSymbol type) {
if (type instanceof ClassSymbol)
return (ClassSymbol) type;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"A class type was required, but %s was found", type);
}
private ArrayTypeSymbol asArraySymbol(TypeSymbol type) {
if (type instanceof ArrayTypeSymbol)
return (ArrayTypeSymbol) type;
throw new SemanticFailure(
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);
}
private void checkType(Expr ast, TypeSymbol expected, SymTable<VariableSymbol> locals) {
TypeSymbol actual = typeExpr(ast, locals);
if (!expected.isSuperTypeOf(actual))
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Expected %s but type was %s",
expected,
actual);
}
private class MethodDeclVisitor extends AstVisitor<Void, Void> {
private MethodDecl method;
private SymTable<VariableSymbol> locals;
public MethodDeclVisitor(MethodDecl method, SymTable<VariableSymbol> locals) {
this.method = method;
this.locals = locals;
}
@Override
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);
TypeSymbol rhs = typeExpr(ast.right(), locals);
if (!lhs.isSuperTypeOf(rhs))
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Type %s cannot be assigned to %s",
rhs, lhs);
return null;
}
@Override
public Void builtInWrite(BuiltInWrite ast, Void arg) {
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);
visit(ast.then(), arg);
if (ast.otherwise() != null)
visit(ast.otherwise(), arg);
return null;
}
@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);
return visit(ast.body(), arg);
}
@Override
public Void returnStmt(ReturnStmt ast, Void arg) {
boolean hasArg = ast.arg() != null;
if (hasArg && method.sym.returnType == PrimitiveTypeSymbol.voidType) {
// void m() { return m(); }
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Return statement of method with void return type should "
+ "not have arguments.");
} else if (!hasArg) {
// X m() { return; }
if (method.sym.returnType != PrimitiveTypeSymbol.voidType) {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Return statement has no arguments. Expected %s but type was %s",
method.sym.returnType,
PrimitiveTypeSymbol.voidType);
}
} else {
// 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;
}
throw new RuntimeException("Unhandled operator "+ast.operator);
}
@Override
public TypeSymbol booleanConst(BooleanConst ast, SymTable<VariableSymbol> arg) {
return PrimitiveTypeSymbol.booleanType;
}
@Override
public TypeSymbol builtInRead(BuiltInRead ast, SymTable<VariableSymbol> arg) {
return PrimitiveTypeSymbol.intType;
}
@Override
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.",
argType, ast.type);
}
@Override
protected TypeSymbol dfltExpr(Expr ast, SymTable<VariableSymbol> arg) {
throw new RuntimeException("Unhandled type");
}
@Override
public TypeSymbol field(Field ast, SymTable<VariableSymbol> locals) {
ClassSymbol argType = asClassSymbol(typeExpr(ast.arg(), locals)); // class of the receiver of the field access
ast.sym = argType.getField(ast.fieldName);
if (ast.sym == null)
throw new SemanticFailure(
Cause.NO_SUCH_FIELD,
"Type %s has no field %s",
argType, ast.fieldName);
return ast.sym.type;
}
@Override
public TypeSymbol index(Index ast, SymTable<VariableSymbol> locals) {
ArrayTypeSymbol argType = asArraySymbol(typeExpr(ast.left(), locals));
checkType(ast.right(), PrimitiveTypeSymbol.intType, locals);
return argType.elementType;
}
@Override
public TypeSymbol intConst(IntConst ast, SymTable<VariableSymbol> arg) {
return PrimitiveTypeSymbol.intType;
}
@Override
public TypeSymbol newArray(NewArray ast, SymTable<VariableSymbol> locals) {
checkType(ast.arg(), PrimitiveTypeSymbol.intType, locals);
return typeSymbols.getType(ast.typeName);
}
@Override
public TypeSymbol newObject(NewObject ast, SymTable<VariableSymbol> arg) {
return typeSymbols.getType(ast.typeName);
}
@Override
public TypeSymbol nullConst(NullConst ast, SymTable<VariableSymbol> arg) {
return ClassSymbol.nullType;
}
@Override
public TypeSymbol thisRef(ThisRef ast, SymTable<VariableSymbol> locals) {
VariableSymbol vsym = locals.get("this");
return vsym.type;
}
@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_BOOL_NOT:
checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
}
throw new RuntimeException("Unknown unary op "+ast.operator);
}
@Override
public TypeSymbol var(Var ast, SymTable<VariableSymbol> locals) {
if (!locals.contains(ast.name))
throw new SemanticFailure(
Cause.NO_SUCH_VARIABLE,
"No variable %s was found",
ast.name);
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));
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);
ast.sym = mthd;
// Check that the number of arguments is correct.
if (ast.argumentsWithoutReceiver().size() != mthd.parameters.size())
throw new SemanticFailure(
Cause.WRONG_NUMBER_OF_ARGUMENTS,
"Method %s() takes %d arguments, but was invoked with %d",
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);
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). */
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: */
@Override
public TypeSymbol field(Field ast, SymTable<VariableSymbol> locals) {
return typeExpr(ast, locals);
}
@Override
public TypeSymbol index(Index ast, SymTable<VariableSymbol> locals) {
return typeExpr(ast, locals);
}
@Override
public TypeSymbol var(Var ast, SymTable<VariableSymbol> locals) {
return typeExpr(ast, locals);
}
/** 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",
AstOneLine.toString(ast));
}
}
}