Homework 3
This commit is contained in:
parent
bf60a078d7
commit
0afc86ceeb
129 changed files with 3163 additions and 4316 deletions
346
src/cd/frontend/semantic/ExprAnalyzer.java
Normal file
346
src/cd/frontend/semantic/ExprAnalyzer.java
Normal file
|
@ -0,0 +1,346 @@
|
|||
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);
|
||||
}
|
||||
}
|
235
src/cd/frontend/semantic/SemanticAnalyzer.java
Normal file
235
src/cd/frontend/semantic/SemanticAnalyzer.java
Normal file
|
@ -0,0 +1,235 @@
|
|||
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.*;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the basic types that exist in any Javali program by default
|
||||
* @param list Type list to which the types must be added
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@code Main} class in a Javali program
|
||||
* @throws SemanticFailure If there is no such class
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
127
src/cd/frontend/semantic/SemanticFailure.java
Normal file
127
src/cd/frontend/semantic/SemanticFailure.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
/**
|
||||
* Thrown by the semantic checker when a semantic error is detected
|
||||
* in the user's program. */
|
||||
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.
|
||||
* <b>Not</b> used for type errors in assignments, which fall
|
||||
* 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,
|
||||
|
||||
/** A field was accessed that does not exist */
|
||||
NO_SUCH_FIELD,
|
||||
|
||||
/** 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 */
|
||||
NO_SUCH_VARIABLE,
|
||||
|
||||
/**
|
||||
* 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
|
||||
* <li> A parameter in a method invocation had the wrong type
|
||||
* <li> A condition of a while loop or if statement was not boolean
|
||||
* <li> A non-array was indexed (i.e., a[i] where a is not an array)
|
||||
* <li> The index was not an int (i.e., a[i] where i is not an int)
|
||||
* <li> Arithmetic operators (+,-,etc) used with non-int type
|
||||
* <li> Boolean operators (!,&&,||,etc) used with non-boolean type
|
||||
* <li> Method or field accessed on non-object type
|
||||
* <li> {@code write()} is used with non-int argument
|
||||
* <li> An invalid cast was detected (i.e., a cast to a type that
|
||||
* is not a super- or sub-type of the original type).
|
||||
* <li> The size of an array in a new expression was not an
|
||||
* int (i.e., new A[i] where i is not an int)
|
||||
* </ul>
|
||||
*/
|
||||
TYPE_ERROR,
|
||||
|
||||
/**
|
||||
* A class is its own super class
|
||||
*/
|
||||
CIRCULAR_INHERITANCE,
|
||||
|
||||
/**
|
||||
* One of the following:
|
||||
* <ul>
|
||||
* <li>No class {@code Main}
|
||||
* <li>Class {@code Main} does not have a method {@code main()}
|
||||
* <li>The method {@code main} has > 0 parameters.
|
||||
* <li>The method {@code main} has a non-void return type.
|
||||
* </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:
|
||||
* <ul>
|
||||
* <li>The extends clause on a class declaration.
|
||||
* <li>A cast.
|
||||
* <li>A new expression.
|
||||
* </ul>
|
||||
*/
|
||||
NO_SUCH_TYPE,
|
||||
|
||||
/**
|
||||
* The parameters of an overridden method have different types
|
||||
* 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 */
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
353
src/cd/frontend/semantic/StmtAnalyzer.java
Normal file
353
src/cd/frontend/semantic/StmtAnalyzer.java
Normal file
|
@ -0,0 +1,353 @@
|
|||
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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue