Week 4.1

CourseCompilers & Interpreters
SemesterS1 2022

Week 4.1

🏷️ Replace this section with some keywords that can be used for searching, tagging and the indexing of this page.

Sections

🌱 Links to all Heading1 Blocks in this page.

  1. Static Checker

1.0 - Static Checker

🌱 Performs static semantics checks on the abstract syntax tree using a visitor pattern to traverse the tree.

public class StaticChecker implements DeclVisitor, StatementVisitor, 
		ExpTransform {
		...
}
  • Note that the StaticChecker class implements the DeclVisitor, StatementVisitor and ExpTransform interfaces.

    • These interfaces are implemented so that it can make use of the visitor pattern and visit the three types of nodes - declarations, statements and expressions
  • The StatementVisitor interface is an interface that defines a visit method for each type of Statement node.

    • This also means that the StaticChecker must implement all of these methods.
    public interface StatementVisitor {
        void visitBlockNode(StatementNode.BlockNode node);
        void visitStatementErrorNode(StatementNode.ErrorNode node);
        void visitStatementListNode(StatementNode.ListNode node);
        void visitAssignmentNode(StatementNode.AssignmentNode node);
        void visitReadNode(StatementNode.ReadNode node);
        void visitWriteNode(StatementNode.WriteNode node);
        void visitCallNode(StatementNode.CallNode node);
        void visitIfNode(StatementNode.IfNode node);
        void visitWhileNode(StatementNode.WhileNode node);
    }
    
    • Additionally, for this to work, we require that the StatementNode (and all subclasses) have to implement an accept(StatementVisitor visitor); method
  • The ExpTransform interface is an interface that defines a visit method for each type of Exp (expression) node that could appear in an abstract syntax tree

    public interface ExpTransform {
        ResultType visitErrorExpNode(ExpNode.ErrorNode node);
        ResultType visitConstNode(ExpNode.ConstNode node);
        ResultType visitIdentifierNode(ExpNode.IdentifierNode node);
        ResultType visitVariableNode(ExpNode.VariableNode node);
        ResultType visitBinaryNode(ExpNode.BinaryNode node);
        ResultType visitUnaryNode(ExpNode.UnaryNode node);
        ResultType visitDereferenceNode(ExpNode.DereferenceNode node);
        ResultType visitNarrowSubrangeNode(ExpNode.NarrowSubrangeNode node);
        ResultType visitWidenSubrangeNode(ExpNode.WidenSubrangeNode node);
    }
    
    • Note that the StaticChecker class implements the ExpTransform
    • In the ExpNode class, there’s a transform method that essentially calls the visit__ method as well as potentially some type coercions

1.1 - Expressions in the Static Checker.

🌱 How do we perform type coercions and all of these other operations on Expression Nodes?

  • When we visit a node in the type checker, we have to both transform and type check it:
  • Potential transformation operations
    1. Replace identifier nodes (e.g. variable nodes) with constant nodes
    2. Type coercion
    3. Introduce Dereference nodes
    4. Add WidenSubrange or NarrowSubrange nodes.
  • Type Checking
    • Make sure it’s correct with respect to the static semantics rules
    • If we need to update anything, need to update the type of that expression

1.1.1 - Visiting an Error Expression Node

🌱 Error expression nodes may have been introduced in the parse tree, how do we deal with them?

public ExpNode visitErrorExpNode(ExpNode.ErrorNode node) {
    beginCheck("ErrorExp");
    // Nothing to do - already invalid.
    endCheck("ErrorExp");
    return node;
}
  • If we indeed have an error node, we don’t have to do anything else as there is inherently an issue with the code
    • Philosophy within the construction of compilers to never report the same error twice.
    • Any error associated with the code has already been reported, so there is nothing to do.

1.1.2 - Visiting an Identifier Node

public ExpNode visitIdentifierNode(ExpNode.IdentifierNode node) {
    beginCheck("Identifier");
    // First we look up the identifier in the symbol table.
    ExpNode newNode;
    SymEntry entry = currentScope.lookup(node.getId());
    if (entry instanceof SymEntry.ConstantEntry) {
        SymEntry.ConstantEntry constEntry = (SymEntry.ConstantEntry) entry;
        // Set up a new node which is a constant.
        debugMessage("Transformed " + node.getId() + " to Constant");
        newNode = new ExpNode.ConstNode(node.getLocation(),
                constEntry.getType(), constEntry.getValue());
    } else if (entry instanceof SymEntry.VarEntry) {
        SymEntry.VarEntry varEntry = (SymEntry.VarEntry) entry;
        debugMessage("Transformed " + node.getId() + " to Variable");
        // Set up a new node which is a variable.
        newNode = new ExpNode.VariableNode(node.getLocation(), varEntry);
    } else {
        // Undefined identifier or a type or procedure identifier.
        // Set up new node to be an error node.
        newNode = new ExpNode.ErrorNode(node.getLocation());
        //System.out.println("Entry = " + entry);
        staticError("Constant or variable identifier required", node.getLocation());
    }
    endCheck("Identifier");
    return newNode;
}
  • There are two static semantics rules that involve identifiers - the Symbolic Constant rule and the Variable Identifier rule.

    Symbolic Constant RuleVariable Identifier Rule
    $$

\color{lightblue}\text{id}\in\text{dom(syms)}\ \text{\underline{syms(id) = ConstEntry(T,v)}}\ \text{syms}\vdash \text{id : T}

|

\color{lightblue}\text{id}\in\text{dom(syms)}\ \text{\underline{syms(id)=VarEntry(T)}}\ \text{syms}\vdash\text{id : T}

| - In short, the identifier must be in the symbol table, and it must either be a constant or variable. - All type inference rules are expressed with respect to a symbol table $\color{lightblue}\text{syms}$ - In the implementation, the symbol table is organised hierarchically into scopes. - The `currentScope` is the symbol table that we’re type checking the current node with respect to: ```java beginCheck("Identifier"); // First we look up the identifier in the symbol table. ExpNode newNode; // currentScope is essentially our symbol table in this context. SymEntry entry = currentScope.lookup(node.getId()); ``` - If the symbol is in the symbol table, we need to determine what type it is. 1. If it is a `ConstEntry`, we want to transform the input `ExpNode.IdentifierNode` into an `ExpNode.ConstNode` ```java if (entry instanceof SymEntry.ConstantEntry) { SymEntry.ConstantEntry constEntry = (SymEntry.ConstantEntry) entry; // Set up a new node which is a constant. debugMessage("Transformed " + node.getId() + " to Constant"); newNode = new ExpNode.ConstNode( node.getLocation(), // Location of ExpNode.IdentifierNode constEntry.getType(), // Type of the ConstEntry in the symbol table constEntry.getValue()); // Value of constant in symbol table. } ``` 2. If it is a `VariableNode`, we want to transform the input `ExpNode.IdentifierNode` into an `ExpNode.VarEntry` ```java else if (entry instanceof SymEntry.VarEntry) { SymEntry.VarEntry varEntry = (SymEntry.VarEntry) entry; debugMessage("Transformed " + node.getId() + " to Variable"); // Set up a new node which is a variable. // Constructor for VariableNode is able to infer the type of the variable // from the two input arguments. newNode = new ExpNode.VariableNode(node.getLocation(), varEntry); } ``` 3. If the identifier (a) not in the symbol table (b) not a `ConstantEntry` or `VarEntry`, we have to report an error - Node that in this case, we transform the `ExpNode.IdentifierNode` into an `ExpNode.ErrorNode` ```java else { // i.e. not in symbol table or (not a ConstantEntry or VarEntry) // Undefined identifier or a type or procedure identifier. // Set up new node to be an error node. newNode = new ExpNode.ErrorNode(node.getLocation()); //System.out.println("Entry = " + entry); staticError("Constant or variable identifier required", node.getLocation()); } ``` - We then return the transformed `ExpNode.IdentifierNode` - note that we have set the type of the returned node correctly. ```java endCheck("Identifier"); return newNode; ``` ### 1.1.3 - Visiting Unary Nodes - Code for Visiting a Unary Node ```java public ExpNode visitUnaryNode(ExpNode.UnaryNode node) { beginCheck("Unary"); /* Check the argument to the operator */ ExpNode arg = node.getArg().transform(this); /* Lookup the operator in the symbol table to get its type. * The operator may not be defined. */ SymEntry.OperatorEntry opEntry = currentScope.lookupOperator(node.getOp().getName()); if (opEntry == null) { staticError("operator not defined", node.getLocation()); node.setType(Type.ERROR_TYPE); node.setOp(Operator.INVALID_OP); } else if (opEntry.getType() instanceof Type.OperatorType) { /* The operator is not overloaded. Its type is represented * by a FunctionType from its argument's type to its * result type. */ Type.FunctionType fType = ((Type.OperatorType)opEntry.getType()).opType(); Type argType = fType.getArgType(); node.setArg(argType.coerceExp(arg)); node.setType(fType.getResultType()); node.setOp(((Type.OperatorType)opEntry.getType()).getOperator()); } else if (opEntry.getType() instanceof Type.IntersectionType) { /* The operator is overloaded. Its type is represented * by an IntersectionType containing a set of possible * types for the operator, each of which is a FunctionType. * Each possible type is tried until one succeeds. */ debugMessage("Coercing " + arg + " to " + opEntry.getType()); errors.incDebug(); for (Type t : ((Type.IntersectionType) opEntry.getType()).getTypes()) { Type.FunctionType fType = ((Type.OperatorType)t).opType(); Type argType = fType.getArgType(); try { /* Coerce the argument to the argument type for * this operator type. If the coercion fails an * exception will be trapped and an alternative * function type within the intersection tried. */ ExpNode newArg = argType.coerceToType(arg); /* The coercion succeeded if we get here */ node.setArg(newArg); node.setType(fType.getResultType()); node.setOp(((Type.OperatorType)t).getOperator()); errors.decDebug(); endCheck("Unary"); return node; } catch (IncompatibleTypes ex) { // Allow "for" loop to try an alternative } } errors.decDebug(); debugMessage("Failed to coerce " + arg + " to " + opEntry.getType()); // no match in intersection type staticError("Type of argument " + arg.getType().getName() + " does not match " + opEntry.getType().getName(), node.getLocation()); node.setType(Type.ERROR_TYPE); } else { errors.fatal("Invalid operator type", node.getLocation()); } endCheck("Unary"); return node; } ``` - We only currently have static semantic rule involving unary operators, the Unary Negation rule (but this is to be extended)

\color{lightblue}\underline{\text{syms}\vdash {\text{e : int}}}\ \text{syms}\vdash \text{op(-_,e) : int}

- Therefore, we want to implement the static semantic checking in a way that’s easy to extend - Let’s start by transforming the node ```java public ExpNode visitUnaryNode(ExpNode.UnaryNode node) { beginCheck("Unary"); // Now check that the opeartor is well typed. ExpNode arg = node.getArg().transform(this); ``` - We then start type-checking the operator ```java // Before starting to type-check the operator, we need to know the type of // the operator itself. // We can do this by looking it up in the symbol table. SymEntry.OperatorEntry opEntry = currentScope.lookupOperator( node.getOp().getName()); ``` - If the operator isn’t defined in the symbol table, then there is an issue - report it ```java if (opEntry == null) { staticError("operator not defined", node.getLocation()); node.setType(Type.ERROR_TYPE); node.setOp(Operator.INVALID_OP); } ``` - If the operator is defined (and is an `Type.OperatorType`) - this is the most common case - In the statement `argType.coerceExp(arg)` we try to coerce the argument to be of the operator’s type - To ensure that our node has the correct type, we use the returned type of the above statement to update the node type `node.setArg(argType.coerceExp(arg));` - This is important - we need to transform things, but also ensure that our AST representation is updated. - If `argType.coerceExp(arg)` can’t coerce the operator, a Static Semantics error will be reported. - We then set the node’s type to the resulting type of the operator - `node.setType(fType.getResultType());` - This is also *resolving* the type of the expression ```java else if (opEntry.getType() instanceof Type.OperatorType) { /* The operator is not overloaded. Its type is represented * by a FunctionType from its argument's type to its * result type. */ Type.FunctionType fType = ((Type.OperatorType)opEntry.getType()).opType(); Type argType = fType.getArgType(); // Operator argument type node.setArg(argType.coerceExp(arg)); // Try to coerce arg to be of type argType node.setType(fType.getResultType()); // Resulting type of the argument. node.setOp(((Type.OperatorType)opEntry.getType()).getOperator()); } ``` - We can also have overloaded operators - Which of these types can we coerce the node into? ```java else if (opEntry.getType() instanceof Type.IntersectionType) { /* The operator is overloaded. Its type is represented * by an IntersectionType containing a set of possible * types for the operator, each of which is a FunctionType. * Each possible type is tried until one succeeds. */ debugMessage("Coercing " + arg + " to " + opEntry.getType()); errors.incDebug(); for (Type t : ((Type.IntersectionType) opEntry.getType()).getTypes()) { Type.FunctionType fType = ((Type.OperatorType)t).opType(); Type argType = fType.getArgType(); try { /* Coerce the argument to the argument type for * this operator type. If the coercion fails an * exception will be trapped and an alternative * function type within the intersection tried. */ ExpNode newArg = argType.coerceToType(arg); /* The coercion succeeded if we get here */ node.setArg(newArg); node.setType(fType.getResultType()); node.setOp(((Type.OperatorType)t).getOperator()); errors.decDebug(); endCheck("Unary"); return node; } catch (IncompatibleTypes ex) { // Allow "for" loop to try an alternative } } errors.decDebug(); debugMessage("Failed to coerce " + arg + " to " + opEntry.getType()); // no match in intersection type staticError("Type of argument " + arg.getType().getName() + " does not match " + opEntry.getType().getName(), node.getLocation()); node.setType(Type.ERROR_TYPE); } ``` ### 1.1.4 - Visiting a Binary Node - There’s only one Static Semantic rule involving binary operators, the Binary Operator rule.

\color{lightblue} \text{syms}\vdash\text{e1 : T1}\ \text{syms}\vdash\text{e2 : T2}\ \underline{\text{syms}\vdash_\odot_\ :\ T1\times T2\rightarrow T3}\ \text{syms}\rightarrow \text{op}(_\odot_,\text{e1,e2)) : T3}

- If we apply a binary operator $\color{lightblue}\odot$ to two arguments $\color{lightblue}e1,e2$ then we require: - $\color{lightblue}e1$ is well typed in the context of the symbol table, and has type $\color{lightblue}\text{T1}$ - $\color{lightblue}e2$ is well typed in the context of the symbol table, and has type $\color{lightblue}\text{T2}$ - The types $\color{lightblue}T1,T2$ are compatible with the binary operator $\color{lightblue}\odot$ - In the code, the first thing we do is check whether the two expressions are well typed / type correct by transforming them. - We need to have a record of the transformed type as they may have been transformed (so we can update the AST) ```java public ExpNode visitBinaryNode(ExpNode.BinaryNode node) { beginCheck("Binary"); /* Check the arguments to the operator */ ExpNode left = node.getLeft().transform(this); ExpNode right = node.getRight().transform(this); ``` - We then need to know the type of the operator - this is done by looking it up in the symbol table. ```java SymEntry.OperatorEntry opEntry = currentScope.lookupOperator(node.getOp().getName()); ``` - We can then switch based on the operator type - if the operator is null then we have a Static Semantic error ```java if (opEntry == null) { staticError("operator not defined", node.getLocation()); node.setType(Type.ERROR_TYPE); node.setOp(Operator.INVALID_OP); } ``` - If there is only one type defined for the operator (i.e. not overloaded) then we execute the following code: ```java else if (opEntry.getType() instanceof Type.OperatorType) { /* The operator is not overloaded. Its type is represented * by a FunctionType from its argument's type to its * result type. */ Type.FunctionType fType = ((Type.OperatorType)opEntry.getType()).opType(); List argTypes = ((Type.ProductType)fType.getArgType()).getTypes(); node.setLeft(argTypes.get(0).coerceExp(left)); node.setRight(argTypes.get(1).coerceExp(right)); node.setType(fType.getResultType()); node.setOp(((Type.OperatorType)opEntry.getType()).getOperator()); } ``` - In the line `node.setLeft(argTypes.get(0).coerceExp(left));` we coerce the left node into the type of the operator’s left argument and update the AST with the potentially coerced new type. - We do the same for the RHS argument - If after coercing the LHS and RHS successfully, we set the node’s result type and operation - Otherwise, we deal with the case where the operator is overloaded ```java else if (opEntry.getType() instanceof Type.IntersectionType) { /* The operator is overloaded. Its type is represented * by an IntersectionType containing a set of possible * types for the operator, each of which is a FunctionType. * Each possible type is tried until one succeeds. */ debugMessage("Coercing " + left + " and " + right + " to " + opEntry.getType()); errors.incDebug(); for (Type t : ((Type.IntersectionType) opEntry.getType()).getTypes()) { Type.FunctionType fType = ((Type.OperatorType)t).opType(); List argTypes = ((Type.ProductType)fType.getArgType()).getTypes(); try { /* Coerce the argument to the argument type for * this operator type. If the coercion fails an * exception will be trapped and an alternative * function type within the intersection tried. */ ExpNode newLeft = argTypes.get(0).coerceToType(left); ExpNode newRight = argTypes.get(1).coerceToType(right); /* Both coercions succeeded if we get here else exception was thrown */ node.setLeft(newLeft); node.setRight(newRight); node.setType(fType.getResultType()); node.setOp(((Type.OperatorType)t).getOperator()); errors.decDebug(); endCheck("Binary"); return node; } catch (IncompatibleTypes ex) { // Allow "for" loop to try an alternative } } errors.decDebug(); debugMessage("Failed to coerce " + left + " and " + right + " to " + opEntry.getType()); // no match in intersection type staticError("Type of argument (" + left.getType().getName() + "*" + right.getType().getName() + ") does not match " + opEntry.getType().getName(), node.getLocation()); node.setType(Type.ERROR_TYPE); } else { errors.fatal("Invalid operator type", node.getLocation()); } ``` - Then return the coerced and transformed node AST node ```java endCheck("Binary"); return node; ``` ### 1.1.5 - Visiting an Assignment Node 🌱 How do we type check an assignment node? - There’s one rule for assignment - the Assignment rule

\color{lightblue}\text{syms}\vdash\text{lv : ref(T)}\ \underline{\text{syms}\vdash\text{e : T}}\ \text{syms}\vdash\text{WFStatement(assign(lv,e))}

- Note that the assignment statement is well formed iff: 1. The left value $\color{lightblue}\text{lv}$ is well typed and is a reference to some type $\color{lightblue}\text{T}$, i.e. the type of $\color{lightblue}\text{lv}$ is $\color{lightblue}\text{ref(T)}$ 2. The value that we’re trying to assign, $\color{lightblue}\text{e}$ is well typed, and has the same type $\color{lightblue}\text{T}$ --- - Start off by validating the left value ```java public void visitAssignmentNode(StatementNode.AssignmentNode node) { beginCheck("Assignment"); ExpNode left = node.getVariable().transform(this); // Type check this bitch node.setVariable(left); // Update node so that lv is the transformed left value. ``` - We then do the above to the expression - we want to know that the expression is well typed and has the correct type ```java ExpNode exp = node.getExp().transform(this); node.setExp(exp); // Set AST node again as it may have been changed when transformed ``` - We next one to check that our left value is a reference type. - From there, need to check that we can coerce the reference type to the base type. ```java if (left.getType() instanceof Type.ReferenceType) { Type baseType = ((Type.ReferenceType) left.getType()).getBaseType(); node.setExp(baseType.coerceExp(exp)); } ``` - If it’s not a reference type, report an error ```java else if (left.getType() != Type.ERROR_TYPE) { // If error hasn't been report before and isn't reference type: staticError("variable expected", left.getLocation()); } ``` - Return out ```java endCheck("Assignment"); } ``` ### 1.1.16 - Visiting a Write Node - There’s only one rule involving the write function / method - the Write rule.

\color{lightblue}\text{syms}\vdash\text{e : int}\ \overline{\text{syms}\vdash\text{WFStatement(write(e))}}

- We can write the expression $\color{lightblue}\text{e}$ if it is well formed in the context of the symbol table, and it is an integer. ```java public void visitWriteNode(StatementNode.WriteNode node) { beginCheck("Write"); // check type correctness ExpNode exp = node.getExp().transform(this); node.setExp(Predefined.INTEGER_TYPE.coerceExp(exp)); endCheck("Write"); } ``` ### 1.1.17 - Visiting a Procedure Call - A procedure call is well formed iff: 1. the dentifier $\color{lightblue}\text{id}$ is in the symbol table 2. The $\color{lightblue}\text{id}$ has a ProcEntry (Procedure entry) - This is defined by the Procedure Call rule

\color{lightblue}\text{id}\in\text{dom(syms)}\ \underline{\text{syms(id)=ProcEntry(block)}}\ \text{syms}\vdash\text{WFStatement(call(id))}

--- ```java public void visitCallNode(StatementNode.CallNode node) { beginCheck("Call"); // Check that the symbol is in the symboltable. SymEntry entry = currentScope.lookup(node.getId()); if (entry instanceof SymEntry.ProcedureEntry) { // if entry is instanceof Sym>Entry.ProcedureEntry then it's type correct. SymEntry.ProcedureEntry procEntry = (SymEntry.ProcedureEntry) entry; node.setEntry(procEntry); } else { staticError("Procedure identifier required", node.getLocation()); } endCheck("Call"); } ``` ### 1.1.18 - Visiting a Statement List - The Statement List rule is as follows:

\footnotesize\color{lightblue} \underline{\forall\text{s}\in\text{elems(ls)}\ \bullet\ \text{(syms }\vdash \text{ WFStatement(s))}}\ \text{syms}\vdash\text{WFStatement(list(ls))}

- Essentially, a Statement List is well formed as long as all of the statements in the statement list are well formed. - So, when implementing the Static Semantic checking for a Statement List, we just need to check that the individual statements are well formed: ```java public void visitStatementListNode(StatementNode.ListNode node) { beginCheck("StatementList"); for (StatementNode s : node.getStatements()) { s.accept(this); // Dynamically dispatch call to appropriate visit method. } endCheck("StatementList"); } ``` ### 1.1.19 - Visiting a Conditional Node - The Conditional rule is as follows:

\footnotesize\color{lightblue}\text{syms}\vdash\text{e : boolean}\ \text{syms}\vdash\text{WFStatement(s1)}\ \text{syms}\vdash\text{WFStatement(s2)}\ \overline{\text{syms}\vdash\text{WFStatement(if(e,s1,s2))}}

- For a conditional to be well formed, we require: 1. The expression (condition) to be well formed in the context of the symbol table and `returns a boolean` 2. The first statement to be executed $\color{lightblue}s1$ is well formed 3. The second statement to be executed $\color{lightblue}s2$ is well formed ```java public void visitIfNode(StatementNode.IfNode node) { beginCheck("If"); // Check the condition and replace with (possibly) transformed node node.setCondition(checkCondition(node.getCondition())); node.getThenStmt().accept(this); // Check the 'then' part node.getElseStmt().accept(this); // Check the 'else' part. endCheck("If"); } ``` 1. The `checkCondition` method does two things: 1. Transforms the condition (type check) 2. Coerce the condition to a `boolean` (essentially, checking it returns a `boolean`) or return a Static Semantic error. 2. We check that the two statements are well formed by calling their accept methods. ### 1.1.20 - Visiting a While Node / Iteration Node - There’s only one Static Semantic rule for the while node / iteration node - the Iteration Rule

\color{lightblue}\footnotesize

\text{syms}\vdash\text{e : boolean}\\
\text{syms}\vdash\text{WFStatement(s)}\\
\overline{\text{syms}\vdash\text{WFStatement(while(e,s))}}

- From this, we see that for a while loop to be well formed, we require: 1. The expression $\color{lightblue}\text{e}$ to be a well formed statement, and to have a `boolean` return type. 2. The statement $\color{lightblue}\text{s}$ is well formed in the context of the symbol table. - To achieve this, we: 1. Transform the expression $\color{lightblue}\text{e}$ and try and coerce it to a boolean (and set result back into AST) 2. Call the corresponding visit method for the statement. ```java public void visitWhileNode(StatementNode.WhileNode node) { beginCheck("While"); // Check the condition and replace with (possibly) transformed node node.setCondition(checkCondition(node.getCondition())); node.getLoopStmt().accept(this); // Check the body of the loop endCheck("While"); } ``` ## 1.2 - The coerceExp() method 🌱 The `checkCondition` method uses the `coerceExp` method, but how does it actually work: ```java private ExpNode checkCondition(ExpNode cond) { // Check and transform the condition cond = cond.transform(this); /* Validate that the condition is boolean, which may require * coercing the condition to be of type boolean. */ return Predefined.BOOLEAN_TYPE.coerceExp(cond); } ``` --- - The `coerceExp` method is a method that is defined for each type - It takes an expression node and tries to coerce it to the type that we have called it on (in this case, trying to coerce the expression node `cond` to a `Boolean` - When we coerce types, we may be `introducing deference` nodes: - Consider the case where we have a reference to an integer, and we want to coerce it to an integer type - we can do this by introducing a `dereference node`. ```java Predefined.BOOLEAN_TYPE.coerceExp(cond); ``` --- ```java public ExpNode coerceExp(ExpNode exp) { /* Try coercing the expression. */ try { return this.coerceToType(exp); } catch (IncompatibleTypes e) { /* At this point the coercion has failed. */ // Report errors.debugMessage("******" + e.getMessage()); errors.error(e.getMessage(), e.getLocation()); return new ExpNode.ErrorNode(e.getLocation()); } } ``` - We first try to coerce to the type that `coerceExp` was called on. - If this fails, then we get an `IncompatibleType` error, report the static semantics error and add the `ErrorNode` to AST. ### 1.2.1 - How does the coerceToType method work? ```java public ExpNode coerceToType(ExpNode exp) throws IncompatibleTypes { errors.debugMessage("Coercing " + exp + ":" + exp.getType().getName() + " to " + this.getName()); errors.incDebug(); ExpNode newExp = exp; /* Unless this type is a reference type, optionally dereference * the expression to get its base type. */ if (!(this instanceof ReferenceType)) { newExp = optDereferenceExp(newExp); // Try to dereference it. } Type fromType = newExp.getType(); /* No need to coerce if already the same type or * fromType is ERROR_TYPE and hence an error message has already been given. */ if (!this.equals(fromType) && fromType != ERROR_TYPE) { /* Try coercing the expression. Dynamic dispatch on the desired * type is used to control the coercion process. */ try { newExp = this.coerce(newExp); } catch (IncompatibleTypes e) { errors.debugMessage("Failed to coerce " + newExp + " to " + this.getName()); errors.decDebug(); /* Throw an error to allow the caller to decide whether an error * message needs to be generated. */ throw e; } } errors.debugMessage("Succeeded"); errors.decDebug(); return newExp; } ``` 1. First, we check if the type that we’re calling this method is a reference type ```java if (!(this instanceof ReferenceType)) { newExp = optDereferenceExp(newExp); // Try to dereference it. } ``` - If it is NOT a reference type, our argument might have a reference type - I.e. we may be able to coerce it by dereferencing it using the `optDereferenceExp` method 2. If the types still don’t match after dereferencing (and it isn’t an error type), we have to perform more coercion - If the types match, we skip this block and return the dereferenced value - We use the `coerce` method as our last attempt to coerce the node. ```java if (!this.equals(fromType) && fromType != ERROR_TYPE) { /* Try coercing the expression. Dynamic dispatch on the desired * type is used to control the coercion process. */ try { newExp = this.coerce(newExp); } catch (IncompatibleTypes e) { errors.debugMessage("Failed to coerce " + newExp + " to " + this.getName()); errors.decDebug(); /* Throw an error to allow the caller to decide whether an error * message needs to be generated. */ throw e; } } ``` ### 1.2.2 - optDereferenceExp Method 🌱 “*Optional Dereference Expression*”, try to dereference an expression ```java public static ExpNode optDereferenceExp(ExpNode exp) { Type fromType = exp.getType(); if (fromType instanceof ReferenceType) { errors.debugMessage("Coerce dereference " + fromType.getName()); return new ExpNode.DereferenceNode(exp); } else { return exp; } } ``` 1. If the expression’s type is a reference type - We return the expression wrapped in a dereference node 2. If the expression’s type is not a reference type: - We just return the expression ### 1.2.3 - coerce Method 🌱 Last attempt to coerce. Every expression node has a coerce method defined. ```java /** * Default/generic implementation of the ceroce method throws an IncompatibleType error */ ExpNode coerce(ExpNode exp) throws IncompatibleTypes { throw new IncompatibleTypes( "cannot treat " + exp.getType().getName() + " as " + this.getName(), exp.getLocation()); } ``` - We may be able to coerce for certain types - `ScalarType` and `SubrangeType` **Coerce Method for `ScalarType`** - Able to coerce if the current type is a Subrange Type and the type of subrange (i.e. subrange(T)) matches the current type T. - If it isn’t a subrange type, we can’t coerce, and therefore, throw an Incompatible Type ```java @Override protected ExpNode coerce(ExpNode exp) throws IncompatibleTypes { Type fromType = exp.getType(); if (fromType instanceof SubrangeType) { /* This code implements Rule Widen subrange. * If the types do not match, the only other possible type * for the expression which can be coerced to 'this' scalar type * is a subrange type, provided its base type matches * 'this' type. If that is the case we insert a WidenSubrangeNode * of 'this' type with the expression as a subtree. */ Type baseType = ((SubrangeType) fromType).getBaseType(); if (this.equals(baseType)) { errors.debugMessage("Widened " + fromType.getName() + " to " + baseType.getName()); return new ExpNode.WidenSubrangeNode(exp); } } /* Otherwise we report the failure to coerce the expression via * an IncompatibleTypes exception. */ throw new IncompatibleTypes("cannot coerce " + exp.getType().getName() + " to " + this.getName(), exp.getLocation()); } ``` **Coerce Method for `SubrangeType`** - Redefine the generic coerce method to implement the narrow subrange rule. - Allow coercion if the base type is compatible. ```java @Override protected ExpNode coerce(ExpNode exp) throws IncompatibleTypes { /* This implements Rule Narrow subrange in the static semantics. * If the types do not match, we can try coercing the expression * to the base type of 'this' subrange, and then narrow that * to 'this' type. If the coercion to the base type fails it will * generate an exception, which is allowed to pass up to the caller. */ ExpNode coerceExp = baseType.coerceToType(exp); /* If we get here, coerceExp is of the same type as the base * type of 'this' subrange type. We just need to narrow it * down to 'this' subrange. */ errors.debugMessage("Narrowed " + exp.getType().getName() + " to " + this.getName()); return new ExpNode.NarrowSubrangeNode(this, coerceExp); } ```