// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz)
// All rights reserved.
//
// This software may be modified and distributed under the terms
// of the BSD license. See the LICENSE file for details.
package wyc.builder;
import java.util.*;
import wybs.lang.Attribute;
import wybs.lang.NameID;
import wybs.lang.SyntacticElement;
import wybs.lang.SyntaxError;
import wybs.util.ResolveError;
import static wyc.lang.WhileyFile.internalFailure;
import static wyil.util.ErrorMessages.*;
import wyc.lang.*;
import wyc.lang.Stmt.*;
import wyc.lang.WhileyFile.Context;
import wycc.util.Pair;
import wycc.util.Triple;
import wyfs.lang.Path;
import wyil.lang.*;
import wyil.lang.Bytecode.AliasDeclaration;
import wyil.lang.SyntaxTree.Location;
import wyil.util.TypeSystem;
/**
* <p>
* Responsible for compiling the declarations, statements and expression found
* in a WhileyFile into WyIL declarations and bytecode blocks. For example:
* </p>
*
* <pre>
* type nat is (int x) where x >= 0
*
* function f(nat x) -> int:
* return x-1
* </pre>
*
* <p>
* The code generator is responsible for generating the code for the constraint
* on <code>nat</code>, as well as compiling the function's statements into
* their corresponding WyIL bytecodes. For example, the code generated
* constraint on type <code>nat</code> would look like this:
* </p>
*
* <pre>
* type nat is int
* where:
* load x
* const 0
* ifge goto exit
* fail("type constraint not satisfied")
* .exit:
* </pre>
*
* This WyIL bytecode simply compares the local variable x against 0. Here, x
* represents the value held in a variable of type <code>nat</code>. If the
* constraint fails, then the given message is printed.
*
* @author David J. Pearce
*
*/
public final class CodeGenerator {
private final TypeSystem typeSystem;
/**
* Construct a code generator object for translating WhileyFiles into
* WyilFiles.
*
* @param builder
* The enclosing builder instance which provides access to the
* global namespace.
*/
public CodeGenerator(CompileTask builder) {
this.typeSystem = builder.getTypeSystem();
}
// =========================================================================
// WhileyFile
// =========================================================================
/**
* Generate a WyilFile from a given WhileyFile by translating all of the
* declarations, statements and expressions into WyIL declarations and
* bytecode blocks.
*
* @param whileyFile
* The WhileyFile to be translated.
* @return
*/
public WyilFile generate(WhileyFile whileyFile, Path.Entry<WyilFile> target) {
WyilFile wyilFile = new WyilFile(target);
// Go through each declaration and translate in the order of appearance.
for (WhileyFile.Declaration d : whileyFile.declarations) {
try {
if (d instanceof WhileyFile.Type) {
generate(wyilFile, (WhileyFile.Type) d);
} else if (d instanceof WhileyFile.Constant) {
generate(wyilFile, (WhileyFile.Constant) d);
} else if (d instanceof WhileyFile.Property) {
generate(wyilFile, (WhileyFile.Property) d);
} else if (d instanceof WhileyFile.FunctionOrMethodOrProperty) {
generate(wyilFile, (WhileyFile.FunctionOrMethodOrProperty) d);
}
} catch (SyntaxError se) {
throw se;
} catch (Throwable ex) {
WhileyFile.internalFailure(ex.getMessage(), (WhileyFile.Context) d, d, ex);
}
}
// Done
return wyilFile;
}
// =========================================================================
// Constant Declarations
// =========================================================================
/**
* Generate a WyilFile constant declaration from a WhileyFile constant
* declaration. This requires evaluating the given expression to produce a
* constant value. If this cannot be done, then a syntax error is raised to
* indicate an invalid constant declaration was encountered.
*/
private void generate(WyilFile enclosing, WhileyFile.Constant declaration) {
WyilFile.Constant block = new WyilFile.Constant(enclosing, declaration.modifiers(), declaration.name(),
declaration.resolvedValue);
enclosing.blocks().add(block);
}
// =========================================================================
// Type Declarations
// =========================================================================
/**
* Generate a WyilFile type declaration from a WhileyFile type declaration.
* If a type invariant is given, then this will need to be translated into
* Wyil bytecode.
*
* @param td
* @return
* @throws Exception
*/
private void generate(WyilFile enclosing, WhileyFile.Type td) throws Exception {
// Construct new WyIL type declaration
WyilFile.Type declaration = new WyilFile.Type(enclosing, td.modifiers(), td.name(), td.resolvedType);
SyntaxTree tree = declaration.getTree();
//
EnclosingScope scope = new EnclosingScope(tree, td);
// Allocate declared parameter
if (td.parameter.name() != null) {
// If no parameter declared, then there will no invariant either
scope.declare(td.resolvedType, td.parameter.name(), td.attributes());
// Generate code for each invariant condition
for (Expr invariant : td.invariant) {
int index = generateCondition(invariant, scope).operand;
Location<Bytecode.Expr> loc = (Location<Bytecode.Expr>) tree.getLocation(index);
declaration.getInvariant().add(loc);
}
}
// done
enclosing.blocks().add(declaration);
}
// =========================================================================
// Function / Method Declarations
// =========================================================================
private void generate(WyilFile enclosing, WhileyFile.Property fmd) throws Exception {
// Construct new WyIL function or method
WyilFile.Property declaration = new WyilFile.Property(enclosing, fmd.modifiers(), fmd.name(),
fmd.resolvedType());
SyntaxTree tree = declaration.getTree();
// Construct environments
EnclosingScope scope = new EnclosingScope(tree,fmd);
addDeclaredParameters(fmd.parameters, fmd.resolvedType().params(), scope);
// Generate precondition(s)
for (Expr precondition : fmd.requires) {
int index = generateCondition(precondition, scope).operand;
Location<Bytecode.Expr> loc = (Location<Bytecode.Expr>) tree.getLocation(index);
declaration.getPrecondition().add(loc);
}
// Add declaration itself to enclosing file
enclosing.blocks().add(declaration);
}
private void generate(WyilFile enclosing, WhileyFile.FunctionOrMethodOrProperty fmd) throws Exception {
// Construct new WyIL function or method
WyilFile.FunctionOrMethod declaration = new WyilFile.FunctionOrMethod(enclosing, fmd.modifiers(), fmd.name(),
fmd.resolvedType());
SyntaxTree tree = declaration.getTree();
// Construct environments
EnclosingScope scope = new EnclosingScope(tree,fmd);
addDeclaredParameters(fmd.parameters, fmd.resolvedType().params(), scope);
addDeclaredParameters(fmd.returns, fmd.resolvedType().returns(), scope);
// Generate precondition(s)
for (Expr precondition : fmd.requires) {
int index = generateCondition(precondition, scope).operand;
Location<Bytecode.Expr> loc = (Location<Bytecode.Expr>) tree.getLocation(index);
declaration.getPrecondition().add(loc);
}
// Generate postcondition(s)
for (Expr postcondition : fmd.ensures) {
int index = generateCondition(postcondition, scope).operand;
Location<Bytecode.Expr> loc = (Location<Bytecode.Expr>) tree.getLocation(index);
declaration.getPostcondition().add(loc);
}
// Generate function or method body
scope = scope.clone();
int bodyIndex = generateBlock(fmd.statements,scope);
SyntaxTree.Location<Bytecode.Block> body = (SyntaxTree.Location<Bytecode.Block>) tree.getLocation(bodyIndex);
declaration.setBody(body);
// Add declaration itself to enclosing file
enclosing.blocks().add(declaration);
}
/**
* Add a list of parameter declarations to a given environment
*
* @param parameters
* --- List of parameters to add
* @param types
* --- List of nominal parameter types
* @param declarations
* --- List of declarations being constructed
*/
private void addDeclaredParameters(List<WhileyFile.Parameter> parameters, Type[] types,
EnclosingScope scope) {
for (int i = 0; i != parameters.size(); ++i) {
WhileyFile.Parameter parameter = parameters.get(i);
String name = parameter.name;
if (name == null) {
// This can happen for an unnamed return value. If named return
// values become mandatory, this check will be redundant.
name = "$";
}
// allocate parameter to register in the current block
scope.declare(types[i], name, parameter.attributes());
}
}
// =========================================================================
// Blocks
// =========================================================================
/**
* Translate a sequence of zero or more statements into a bytecode block.
*
* @param stmts
* @param scope
* @return
*/
private int generateBlock(List<Stmt> stmts, EnclosingScope scope) {
int[] block = new int[stmts.size()];
for (int i = 0; i != stmts.size(); ++i) {
Stmt st = stmts.get(i);
block[i] = generate(st, scope);
}
return scope.add(new Bytecode.Block(block));
}
// =========================================================================
// Statements
// =========================================================================
/**
* Translate a source-level statement into a WyIL block, using a given
* environment mapping named variables to slots.
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generate(Stmt stmt, EnclosingScope scope) {
try {
if (stmt instanceof VariableDeclaration) {
return generateVariableDeclaration((VariableDeclaration) stmt, scope);
} else if (stmt instanceof Assign) {
return generateAssign((Assign) stmt, scope);
} else if (stmt instanceof Assert) {
return generateAssert((Assert) stmt, scope);
} else if (stmt instanceof Assume) {
return generateAssume((Assume) stmt, scope);
} else if (stmt instanceof Return) {
return generateReturn((Return) stmt, scope);
} else if (stmt instanceof Debug) {
return generateDebug((Debug) stmt, scope);
} else if (stmt instanceof Fail) {
return generateFail((Fail) stmt, scope);
} else if (stmt instanceof IfElse) {
return generateIfElse((IfElse) stmt, scope);
} else if (stmt instanceof Switch) {
return generateSwitch((Switch) stmt, scope);
} else if (stmt instanceof Break) {
return generateBreak((Break) stmt, scope);
} else if (stmt instanceof Continue) {
return generateContinue((Continue) stmt, scope);
} else if (stmt instanceof NamedBlock) {
return generateNamedBlock((NamedBlock) stmt, scope);
} else if (stmt instanceof While) {
return generateWhile((While) stmt, scope);
} else if (stmt instanceof DoWhile) {
return generateDoWhile((DoWhile) stmt, scope);
} else if (stmt instanceof Expr.FunctionOrMethodCall) {
return generateAsStmt((Expr.FunctionOrMethodCall) stmt, scope);
} else if (stmt instanceof Expr.IndirectFunctionOrMethodCall) {
return generateAsStmt((Expr.IndirectFunctionOrMethodCall) stmt, scope);
} else if (stmt instanceof Expr.New) {
return generateNew((Expr.New) stmt, scope);
} else if (stmt instanceof Skip) {
return generateSkip((Skip) stmt, scope);
} else {
// should be dead-code
WhileyFile.internalFailure("unknown statement: " + stmt.getClass().getName(), scope.getSourceContext(),
stmt);
}
} catch (ResolveError ex) {
internalFailure(ex.getMessage(), scope.getSourceContext(), stmt, ex);
} catch (SyntaxError ex) {
throw ex;
} catch (Exception ex) {
internalFailure(ex.getMessage(), scope.getSourceContext(), stmt, ex);
}
return -1; // deadcode
}
/**
* Translate a variable declaration statement into WyIL bytecodes.
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateVariableDeclaration(VariableDeclaration s, EnclosingScope scope) {
// Translate initialiser expression (if applicable).
if (s.expr != null) {
int operand = generateExpression(s.expr, scope);
return scope.add(s.type,new Bytecode.VariableDeclaration(s.parameter.name, operand), s.attributes());
} else {
return scope.add(s.type,new Bytecode.VariableDeclaration(s.parameter.name), s.attributes());
}
}
/**
* Translate an assignment statement into WyIL bytecodes.
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement (i.e. type, constant,
* function or method declaration). The scope is used to aid with
* error reporting as it determines the enclosing file.
* @return
*/
private int generateAssign(Stmt.Assign s, EnclosingScope scope) throws ResolveError {
int[] lhs = generate((List) s.lvals, scope);
int[] rhs = generateMultipleReturns(s.rvals, scope);
return scope.add(new Bytecode.Assign(lhs, rhs), s.attributes());
}
/**
* Translate an assert statement into WyIL bytecodes.
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateAssert(Stmt.Assert s, EnclosingScope scope) {
// First, translate assertion
int operand = generateExpression(s.expr, scope);
// Second, create assert bytecode
return scope.add(new Bytecode.Assert(operand), s.attributes());
}
/**
* Translate an assume statement into WyIL bytecodes.
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateAssume(Stmt.Assume s, EnclosingScope scope) {
// First, translate assumption
int operand = generateExpression(s.expr, scope);
// Second, create assert bytecode
return scope.add(new Bytecode.Assume(operand), s.attributes());
}
/**
* Translate a return statement into WyIL bytecodes.
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateReturn(Stmt.Return s, EnclosingScope scope) throws ResolveError {
List<Expr> returns = s.returns;
// Here, we don't put the type propagated for the return expression.
// Instead, we use the declared return type of this function. This
// has the effect of forcing an implicit coercion between the
// actual value being returned and its required type.
int[] operands = generateMultipleReturns(returns,scope);
return scope.add(new Bytecode.Return(operands), s.attributes());
}
/**
* Translate a skip statement into a WyIL bytecode.
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateSkip(Stmt.Skip s, EnclosingScope scope) {
return scope.add(new Bytecode.Skip(),s.attributes());
}
/**
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateDebug(Stmt.Debug s, EnclosingScope scope) {
int operand = generateExpression(s.expr, scope);
return scope.add(new Bytecode.Debug(operand), s.attributes());
}
/**
* Translate a fail statement into WyIL bytecodes.
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateFail(Stmt.Fail s, EnclosingScope scope) {
return scope.add(new Bytecode.Fail(), s.attributes());
}
/**
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateIfElse(Stmt.IfElse s, EnclosingScope scope) throws ResolveError {
// translate condition itself
FlowResult fr = generateCondition(s.condition, scope);
// the true/false branches from the enclosing scope. In particular,
// the case where two variables of the same name are declared with
// different types.
int trueBlockIndex = generateBlock(s.trueBranch, fr.trueScope);
//
if (!s.falseBranch.isEmpty()) {
// There is a false branch, so translate that as well
int falseBlockIndex = generateBlock(s.falseBranch, fr.falseScope);
//
return scope.add(new Bytecode.If(fr.operand, trueBlockIndex, falseBlockIndex), s.attributes());
} else {
// No false branch to translate
return scope.add(new Bytecode.If(fr.operand, trueBlockIndex), s.attributes());
}
}
private int generateBreak(Stmt.Break s, EnclosingScope scope) {
return scope.add(new Bytecode.Break(), s.attributes());
}
private int generateContinue(Stmt.Continue s, EnclosingScope scope) {
return scope.add(new Bytecode.Continue(), s.attributes());
}
private int generateSwitch(Stmt.Switch s, EnclosingScope scope) throws Exception {
int operand = generateExpression(s.expr, scope);
Bytecode.Case[] cases = new Bytecode.Case[s.cases.size()];
// FIXME: the following check should really occur earlier in the
// pipeline. However, it is difficult to do it earlier because it's only
// after FlowTypeChecker that we have determined the concrete values.
// See #628
checkNoDuplicateLabels(s.cases, scope);
for (int i = 0; i != cases.length; ++i) {
Stmt.Case c = s.cases.get(i);
EnclosingScope bodyScope = scope.clone();
int body = generateBlock(c.stmts, bodyScope);
cases[i] = new Bytecode.Case(body, c.constants);
}
return scope.add(new Bytecode.Switch(operand, cases), s.attributes());
}
/**
* Check that not two case statements have the same constant label.
*
* @param cases
* @param indent
*/
private void checkNoDuplicateLabels(List<Stmt.Case> cases, EnclosingScope scope) {
// The set of seen case labels captures those which have been seen
// already in some previous case block. Thus, if we see one again
// then we have a syntax error.
HashSet<Constant> labels = new HashSet<>();
//
for (int i = 0; i != cases.size(); ++i) {
Stmt.Case caseBlock = cases.get(i);
List<Constant> caseLabels = caseBlock.constants;
if (caseLabels != null) {
for (int j = 0; j != caseLabels.size(); ++j) {
Constant c = caseLabels.get(j);
if (labels.contains(c)) {
WhileyFile.syntaxError(errorMessage(DUPLICATE_CASE_LABEL), scope.getSourceContext(), caseBlock);
} else {
labels.add(c);
}
}
}
}
}
/**
* Translate a named block into WyIL bytecodes.
*/
private int generateNamedBlock(Stmt.NamedBlock s, EnclosingScope scope) {
EnclosingScope bodyScope = scope.clone();
int block = generateBlock(s.body, bodyScope);
return scope.add(new Bytecode.NamedBlock(block,s.name),s.attributes());
}
/**
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateWhile(Stmt.While s, EnclosingScope scope) {
// Translate loop invariant(s)
int[] invariants = generate(s.invariants, scope);
// Determine set of modified variables. This is done by
// traversing the loop body to see which variables are assigned.
int[] modified = determineModifiedVariables(s.body, scope);
// Translate loop condition
int condition = generateExpression(s.condition, scope);
// Translate loop body
EnclosingScope bodyScope = scope.clone();
int body = generateBlock(s.body, bodyScope);
//
return scope.add(new Bytecode.While(body, condition, invariants, modified), s.attributes());
}
/**
*
* @param stmt
* --- Statement to be translated.
* @param scope
* --- Enclosing scope of this statement.
* @return
*/
private int generateDoWhile(Stmt.DoWhile s, EnclosingScope scope) {
// Determine set of modified variables. This is done by
// traversing the loop body to see which variables are assigned.
int[] modified = determineModifiedVariables(s.body, scope);
// Translate loop body
EnclosingScope bodyScope = scope.clone();
int body = generateBlock(s.body, bodyScope);
// Translate loop invariant(s)
int[] invariants = generate(s.invariants, scope);
// Translate loop condition
int condition = generateExpression(s.condition, scope);
//
return scope.add(Type.T_VOID,new Bytecode.DoWhile(body, condition, invariants, modified), s.attributes());
}
// =========================================================================
// Multi-Expressions
// =========================================================================
/**
* Generate an invoke expression as a statement. There are only limited
* cases where this can arise. In particular, when a method is invoked and
* the return value is ignored. In such case, we generate an assignment with
* and empty left-hand side.
*
* @param expr
* The expression to be translated as a statement
* @param scope
* The enclosing scope of the expression
*/
public int generateAsStmt(Expr.FunctionOrMethodCall expr, EnclosingScope scope) throws ResolveError {
//
int[] operands = generate(expr.arguments, scope);
Type.FunctionOrMethod type = expr.type();
return scope.add(Type.T_VOID,new Bytecode.Invoke(type, operands, expr.nid()), expr.attributes());
}
/**
* Generate an indirect invoke expression as a statement. There are only limited
* cases where this can arise. In particular, when a method is invoked and
* the return value is ignored. In such case, we generate an assignment with
* and empty left-hand side.
*
* @param expr
* The expression to be translated as a statement
* @param scope
* The enclosing scope of the expression
*/
public int generateAsStmt(Expr.IndirectFunctionOrMethodCall expr, EnclosingScope scope) throws ResolveError {
//
int operand = generateExpression(expr.src, scope);
int[] operands = generate(expr.arguments, scope);
Type.FunctionOrMethod type = expr.type();
return scope.add(Type.T_VOID,new Bytecode.IndirectInvoke(type, operand, operands), expr.attributes());
}
// =========================================================================
// Conditions
// =========================================================================
/**
* Translate a source-level conditional expression into WyIL bytecodes,
* using a given scope mapping named variables to locations. This produces a
* location index, and updates two environments which represent two sides of
* the same coin.
*
* @param condition
* @param scope
* @return A flow result where both true and false scopes are unaliased.
* @throws ResolveError
*/
public FlowResult generateCondition(Expr condition, EnclosingScope scope) throws ResolveError {
if (condition instanceof Expr.BinOp) {
Expr.BinOp bop = (Expr.BinOp) condition;
switch (bop.op) {
case AND:
return generateAndCondition(bop, scope);
case OR:
return generateOrCondition(bop, scope);
case IS:
return generateIsCondition(bop, scope);
}
} else if (condition instanceof Expr.UnOp) {
Expr.UnOp uop = (Expr.UnOp) condition;
if (uop.op == Expr.UOp.NOT) {
return generateNotCondition(uop, scope);
}
}
// default: fall back to standard generation
int index = generateExpression(condition, scope);
// We have to clone the two scopes here to prevent them from being
// aliases.
return new FlowResult(index, scope.clone(), scope.clone());
}
/**
* Translate a source-level conjunction into a sequence of WyIL bytecodes.
* The key challenge here is to correctly propagate the scope information
* into the lhs and rhs. Since the rhs is only executed when the lhs holds,
* we use the "true scope" from the lhs when translating the rhs. For
* example:
*
* <pre>
* x is int && x >= 0
* </pre>
*
* Here, the true scope coming out of the lhs will identify
* <code>x<code> with type <code>int</code>. This is necessary for the rhs
* to make sense. Observe that this is exploiting the fact that operators
* have short circuiting behaviour in Whiley.
*
* @param condition
* Condition being translated
* @param scope
* Enclosing scope going into this condition.
* @return
* @throws ResolveError
*/
public FlowResult generateAndCondition(Expr.BinOp condition, EnclosingScope scope) throws ResolveError {
FlowResult lhs = generateCondition(condition.lhs, scope);
FlowResult rhs = generateCondition(condition.rhs, lhs.trueScope);
int[] operands = new int[] { lhs.operand, rhs.operand };
int result = scope.add(condition.result(), new Bytecode.Operator(operands, Bytecode.OperatorKind.AND),
condition.attributes());
// Must join lhs.falseScope and rhs.falseScope; this can result in the
// creation of new alias declarations.
EnclosingScope falseScope = join(scope, lhs.falseScope, rhs.falseScope);
return new FlowResult(result, rhs.trueScope, falseScope);
}
/**
* Translate a source-level disjunction into a sequence of WyIL bytecodes.
* The key challenge here is to correctly propagate the scope information
* into the lhs and rhs. Since the rhs is only executed when the lhs doesn't
* hold, we use the "false scope" from the lhs when translating the rhs. For
* example:
*
* <pre>
* x is null || x >= 0
* </pre>
*
* Here, assume x is declared with type <code>int|null</code>. Then, the
* false scope coming out of the lhs will identify
* <code>x<code> with type <code>int</code>. This is necessary for the rhs
* to make sense. Observe that this is exploiting the fact that operators
* have short circuiting behaviour in Whiley.
*
* @param condition
* Condition being translated
* @param scope
* Enclosing scope going into this condition.
* @return
* @throws ResolveError
*/
public FlowResult generateOrCondition(Expr.BinOp condition, EnclosingScope scope) throws ResolveError {
FlowResult lhs = generateCondition(condition.lhs, scope);
FlowResult rhs = generateCondition(condition.rhs, lhs.falseScope);
int[] operands = new int[] { lhs.operand, rhs.operand };
int result = scope.add(condition.result(),new Bytecode.Operator(operands, Bytecode.OperatorKind.OR), condition.attributes());
// Must join lhs.trueScope and rhs.trueScope; this can result in the
// creation of new alias declarations.
EnclosingScope trueScope = join(scope,lhs.trueScope,rhs.trueScope);
return new FlowResult(result, trueScope, rhs.falseScope);
}
/**
* Translate a source-level type test. This produces two potentially updated
* scopes, one for the true branch and one for the false branch. In the case
* of a variable being retyped, then the true branch contains the updated
* type whilst the false branch contains the negated type. For example:
*
* <pre>
* x is int
* </pre>
*
* Assum <code>x</code> is declared with type <code>int|null</code>. Then on
* the true branch <code>x</code> has type <code>int&(int|null)</code> which
* reduces to <code>int</code>. And, on the false branch, <code>x</code> has
* type <code>!int&(int|null)</code> which reduces to <code>null</code>.
*
* @param condition
* @param scope
* @return
* @throws ResolveError
*/
public FlowResult generateIsCondition(Expr.BinOp condition, EnclosingScope scope) throws ResolveError {
int lhs = generateExpression(condition.lhs,scope);
int rhs = generateExpression(condition.rhs,scope);
EnclosingScope trueScope = scope.clone();
EnclosingScope falseScope = scope.clone();
// Check to see whether the lhs is a variable being retyped. If so, we
// need to construct the true/false scopes accordingly.
if (condition.lhs instanceof Expr.LocalVariable) {
Expr.LocalVariable var = (Expr.LocalVariable) condition.lhs;
Type varType = var.result();
Expr.TypeVal typeTest = (Expr.TypeVal) condition.rhs;
Type trueBranchType = Type.Intersection(varType, typeTest.type);
Type falseBranchType = Type.Intersection(varType, Type.Negation(typeTest.type));
trueScope.createAlias(trueBranchType, var.var, condition.attributes());
falseScope.createAlias(falseBranchType, var.var, condition.attributes());
}
// do something
int[] operands = new int[] { lhs, rhs };
int result = scope.add(condition.result(),new Bytecode.Operator(operands, Bytecode.OperatorKind.IS), condition.attributes());
return new FlowResult(result, trueScope, falseScope);
}
public FlowResult generateNotCondition(Expr.UnOp condition, EnclosingScope scope) throws ResolveError {
FlowResult mhs = generateCondition(condition.mhs, scope);
int[] operands = new int[] { mhs.operand };
int result = scope.add(condition.result(), new Bytecode.Operator(operands, Bytecode.OperatorKind.NOT),
condition.attributes());
return new FlowResult(result, mhs.falseScope, mhs.trueScope);
}
/**
* Join two scopes together, creating new alias declarations as necessary.
* Each scope maps variables to their location index. An ancestor scope is
* included, which must be an ancestor of both. When the index of a given
* variable differs between the two scopes, this indicates at least one of
* them has diverged from the ancestor by introducing an alias. Note that
* the only situation in which they have identify the same location for a
* given variable is when that matches the ancestor as well.
*
* @param leftChild
* @param rightChild
* @return
*/
private EnclosingScope join(EnclosingScope ancestor, EnclosingScope leftChild, EnclosingScope rightChild) {
EnclosingScope result = ancestor.clone();
for (String var : ancestor.environment.keySet()) {
int leftLocation = leftChild.get(var);
int rightLocation = rightChild.get(var);
if (leftLocation != rightLocation) {
// Here, we need to do something.
Location<?> origDecl = ancestor.getLocation(var);
Location<?> lhsDecl = leftChild.getLocation(var);
Location<?> rhsDecl = rightChild.getLocation(var);
Type type = Type.Union(lhsDecl.getType(), rhsDecl.getType());
if (type.equals(origDecl.getType())) {
// Easy case, as no new alias required. Therefore, we can
// simply reuse the original declaration.
result.environment.put(var, origDecl.getIndex());
} else {
// Harder case. Since the combine type differs from the
// original declaration, a new alias declaration is
// required.
int newDecl = result.createAlias(type, var, Collections.EMPTY_LIST);
result.environment.put(var, newDecl);
}
}
}
return result;
}
/**
* The flow result is essentially a triple being returned from the
* generateCondition() family of functions. It's purpose is just to make
* their signatures a little neater.
*
* @author David J. Pearce
*
*/
private static class FlowResult {
/**
* Location index for generated expression
*/
public final int operand;
/**
* Scope which holds on the true branch
*/
public final EnclosingScope trueScope;
/**
* Scope which holds on the false branch
*/
public final EnclosingScope falseScope;
public FlowResult(int operand, EnclosingScope trueScope, EnclosingScope falseScope) {
if(trueScope == falseScope) {
throw new IllegalArgumentException("true/false scopes cannot be aliases");
}
this.operand = operand;
this.trueScope = trueScope;
this.falseScope = falseScope;
}
}
// =========================================================================
// Expressions
// =========================================================================
/**
* Translate a source-level expression into a WyIL bytecode block, using a
* given environment mapping named variables to registers. This expression
* may generate zero or more results.
*
* @param expression
* @param scope
* @return
*/
public int[] generateMultipleReturns(List<Expr> expressions, EnclosingScope scope) throws ResolveError {
int[] returns = new int[0];
for(int i=0;i!=expressions.size();++i) {
Expr expression = expressions.get(i);
if(expression instanceof Expr.FunctionOrMethodCall) {
returns = append(returns,generateFunctionOrMethodCall((Expr.FunctionOrMethodCall) expression,scope));
} else if(expression instanceof Expr.IndirectFunctionOrMethodCall) {
returns = append(returns,generateIndirectFunctionOrMethodCall((Expr.IndirectFunctionOrMethodCall) expression,scope));
} else {
returns = append(returns,generateExpression(expression,scope));
}
}
return returns;
}
/**
* Translate a source-level expression into a WYIL bytecode block, using a
* given environment mapping named variables to registers. The result of the
* expression is stored in a given target register.
*
* @param expression
* --- Source-level expression to be translated
* @param scope
* --- Enclosing scope of the condition
*
* @return --- the register
*/
public int generateExpression(Expr expression, EnclosingScope scope) {
try {
if (expression instanceof Expr.Constant) {
return generateConstant((Expr.Constant) expression, scope);
} else if (expression instanceof Expr.LocalVariable) {
return generateLocalVariable((Expr.LocalVariable) expression, scope);
} else if (expression instanceof Expr.ConstantAccess) {
return generateConstantAccess((Expr.ConstantAccess) expression, scope);
} else if (expression instanceof Expr.ArrayInitialiser) {
return generateArrayInitialiser((Expr.ArrayInitialiser) expression, scope);
} else if (expression instanceof Expr.ArrayGenerator) {
return generateArrayGenerator((Expr.ArrayGenerator) expression, scope);
} else if (expression instanceof Expr.BinOp) {
return generateBinaryOperator((Expr.BinOp) expression, scope);
} else if (expression instanceof Expr.Dereference) {
return generateDereference((Expr.Dereference) expression, scope);
} else if (expression instanceof Expr.Cast) {
return generateCast((Expr.Cast) expression, scope);
} else if (expression instanceof Expr.IndexOf) {
return generateIndexOf((Expr.IndexOf) expression, scope);
} else if (expression instanceof Expr.UnOp) {
return generateUnaryOperator((Expr.UnOp) expression, scope);
} else if (expression instanceof Expr.FunctionOrMethodCall) {
return generateFunctionOrMethodCall((Expr.FunctionOrMethodCall) expression, scope);
} else if (expression instanceof Expr.IndirectFunctionOrMethodCall) {
return generateIndirectFunctionOrMethodCall((Expr.IndirectFunctionOrMethodCall) expression, scope);
} else if (expression instanceof Expr.Quantifier) {
return generateQuantifier((Expr.Quantifier) expression, scope);
} else if (expression instanceof Expr.FieldAccess) {
return generateFieldAccess((Expr.FieldAccess) expression, scope);
} else if (expression instanceof Expr.Record) {
return generateRecord((Expr.Record) expression, scope);
} else if (expression instanceof Expr.FunctionOrMethod) {
return generateFunctionOrMethod((Expr.FunctionOrMethod) expression, scope);
} else if (expression instanceof Expr.Lambda) {
return generateLambda((Expr.Lambda) expression, scope);
} else if (expression instanceof Expr.New) {
return generateNew((Expr.New) expression, scope);
} else if (expression instanceof Expr.TypeVal) {
return generateTypeVal((Expr.TypeVal) expression, scope);
} else {
// should be dead-code
internalFailure("unknown expression: " + expression.getClass().getName(), scope.getSourceContext(),
expression);
}
} catch (ResolveError rex) {
internalFailure(rex.getMessage(), scope.getSourceContext(), expression, rex);
} catch (SyntaxError se) {
throw se;
} catch (Exception ex) {
internalFailure(ex.getMessage(), scope.getSourceContext(), expression, ex);
}
return -1; // deadcode
}
public int generateFunctionOrMethodCall(Expr.FunctionOrMethodCall expr, EnclosingScope scope) throws ResolveError {
int[] operands = generate(expr.arguments, scope);
Type.FunctionOrMethod type = expr.type();
return scope.add(type.returns(), new Bytecode.Invoke(type, operands, expr.nid()),
expr.attributes());
}
public int generateIndirectFunctionOrMethodCall(Expr.IndirectFunctionOrMethodCall expr, EnclosingScope scope) throws ResolveError {
int operand = generateExpression(expr.src, scope);
int[] operands = generate(expr.arguments, scope);
Type.FunctionOrMethod type = expr.type();
return scope.add(type.returns(), new Bytecode.IndirectInvoke(type, operand, operands),
expr.attributes());
}
private int generateConstant(Expr.Constant expr, EnclosingScope scope) {
Constant val = expr.value;
Bytecode.Expr operand = new Bytecode.Const(val);
return scope.add(expr.result(), operand, expr.attributes());
}
private int generateTypeVal(Expr.TypeVal expr, EnclosingScope scope) {
Constant val = new Constant.Type(expr.type);
return scope.add(expr.result(), new Bytecode.Const(val), expr.attributes());
}
private int generateFunctionOrMethod(Expr.FunctionOrMethod expr, EnclosingScope scope) {
// FIXME: should really remove Expr.FunctionOrMethod from the AST. This
// should be just an Expr.Constant
Type.FunctionOrMethod type = expr.type;
Constant.FunctionOrMethod val = new Constant.FunctionOrMethod(expr.nid, type);
Bytecode.Expr operand = new Bytecode.Const(val);
return scope.add(expr.result(), operand, expr.attributes());
}
private int generateLambda(Expr.Lambda expr, EnclosingScope scope) {
Type.FunctionOrMethod lambdaType = expr.type;
// Create a new scope for the lambda body. This will contain any
// parameters which are declared as part of the lambda expression.
EnclosingScope lambdaScope = scope.clone();
// Now, declare lambda parameters parameters
HashSet<String> declaredVariables = new HashSet<>();
int[] parameters = new int[expr.parameters.size()];
for (int i = 0; i != parameters.length; ++i) {
WhileyFile.Parameter parameter = expr.parameters.get(i);
// allocate parameter to register in the lambda scope
parameters[i] = lambdaScope.declare(lambdaType.parameter(i), parameter.name, parameter.attributes());
declaredVariables.add(parameter.name);
}
// Now, determine the set of used variables from the enclosing scope
// which forms the environment of the lambda
ArrayList<Integer> environment = new ArrayList<>();
for (Pair<Type, String> v : Exprs.uses(expr.body, scope.getSourceContext())) {
if (!declaredVariables.contains(v.second())) {
int variable = scope.get(v.second());
environment.add(variable);
}
}
// Translate the lambda body
int body = generateExpression(expr.body, lambdaScope);
//
return scope.add(lambdaType,
new Bytecode.Lambda(lambdaType, body, parameters, toIntArray(environment)),
expr.attributes());
}
private int generateConstantAccess(Expr.ConstantAccess expr, EnclosingScope scope) throws ResolveError {
// FIXME: the concept of a constant access should propagate through to
// the bytecode, rather than having the constants inlined here.
Constant val = expr.value;
return scope.add(expr.result(), new Bytecode.Const(val), expr.attributes());
}
private int generateLocalVariable(Expr.LocalVariable expr, EnclosingScope scope) throws ResolveError {
int decl = scope.get(expr.var);
Location<?> vd = scope.enclosing.getLocation(decl);
return scope.add(expr.result(),new Bytecode.VariableAccess(true,decl), expr.attributes());
}
private int generateUnaryOperator(Expr.UnOp expr, EnclosingScope scope) {
int[] operands = new int[] { generateExpression(expr.mhs, scope) };
Bytecode.OperatorKind op;
switch (expr.op) {
case NEG:
op = Bytecode.OperatorKind.NEG;
break;
case INVERT:
op = Bytecode.OperatorKind.BITWISEINVERT;
break;
case NOT:
op = Bytecode.OperatorKind.NOT;
break;
case ARRAYLENGTH:
op = Bytecode.OperatorKind.ARRAYLENGTH;
break;
default:
// should be dead-code
internalFailure("unexpected unary operator encountered", scope.getSourceContext(), expr);
return -1;
}
return scope.add(expr.result(), new Bytecode.Operator(operands, op), expr.attributes());
}
private int generateDereference(Expr.Dereference expr, EnclosingScope scope) {
int[] operands = new int[] { generateExpression(expr.src, scope) };
return scope.add(expr.result(), new Bytecode.Operator(operands, Bytecode.OperatorKind.DEREFERENCE),
expr.attributes());
}
private int generateIndexOf(Expr.IndexOf expr, EnclosingScope scope) {
int[] operands = { generateExpression(expr.src, scope), generateExpression(expr.index, scope) };
return scope.add(expr.result(), new Bytecode.Operator(operands, Bytecode.OperatorKind.ARRAYINDEX),
expr.attributes());
}
private int generateCast(Expr.Cast expr, EnclosingScope scope) {
int operand = generateExpression(expr.expr, scope);
return scope.add(expr.result(), new Bytecode.Convert(operand), expr.attributes());
}
private int generateBinaryOperator(Expr.BinOp v, EnclosingScope scope) throws Exception {
Type result = v.result();
int[] operands = { generateExpression(v.lhs, scope), generateExpression(v.rhs, scope) };
return scope.add(result, new Bytecode.Operator(operands, OP2BOP(v.op, v, scope.getSourceContext())),
v.attributes());
}
private int generateArrayInitialiser(Expr.ArrayInitialiser expr, EnclosingScope scope) {
int[] operands = generate(expr.arguments, scope);
return scope.add(expr.result(), new Bytecode.Operator(operands, Bytecode.OperatorKind.ARRAYCONSTRUCTOR),
expr.attributes());
}
private int generateArrayGenerator(Expr.ArrayGenerator expr, EnclosingScope scope) {
int[] operands = new int[] { generateExpression(expr.element, scope), generateExpression(expr.count, scope) };
return scope.add(expr.result(), new Bytecode.Operator(operands, Bytecode.OperatorKind.ARRAYGENERATOR),
expr.attributes());
}
private int generateQuantifier(Expr.Quantifier expr, EnclosingScope scope) {
EnclosingScope quantifierScope = scope.clone();
// First, translate sources and declare variables in the quantifier
// scope.
Bytecode.Range[] ranges = new Bytecode.Range[expr.sources.size()];
for (int i = 0; i != ranges.length; ++i) {
Triple<String, Expr, Expr> t = expr.sources.get(i);
int start = generateExpression(t.second(), quantifierScope);
int end = generateExpression(t.third(), quantifierScope);
// FIXME: the attributes provided here are not very "precise".
int var = quantifierScope.declare(Type.T_INT, t.first(), expr.attributes());
ranges[i] = new Bytecode.Range(var, start, end);
}
// Second, translate the quantifier body in the context of the new
// scope.
int body = generateExpression(expr.condition, quantifierScope);
//
Bytecode.QuantifierKind kind = Bytecode.QuantifierKind.valueOf(expr.cop.name());
return scope.add(expr.result(), new Bytecode.Quantifier(kind, body, ranges), expr.attributes());
}
private int generateRecord(Expr.Record expr, EnclosingScope scope) {
ArrayList<String> keys = new ArrayList<>(expr.fields.keySet());
Collections.sort(keys);
int[] operands = new int[expr.fields.size()];
for (int i = 0; i != operands.length; ++i) {
String key = keys.get(i);
Expr arg = expr.fields.get(key);
operands[i] = generateExpression(arg, scope);
}
return scope.add(expr.result(), new Bytecode.Operator(operands, Bytecode.OperatorKind.RECORDCONSTRUCTOR),
expr.attributes());
}
private int generateFieldAccess(Expr.FieldAccess expr, EnclosingScope scope) {
int operand = generateExpression(expr.src, scope);
return scope.add(expr.result(), new Bytecode.FieldLoad(operand, expr.name), expr.attributes());
}
private int generateNew(Expr.New expr, EnclosingScope scope) throws ResolveError {
int[] operands = new int[] { generateExpression(expr.expr, scope) };
return scope.add(expr.result(), new Bytecode.Operator(operands, Bytecode.OperatorKind.NEW), expr.attributes());
}
private int[] generate(List<Expr> arguments, EnclosingScope scope) {
int[] operands = new int[arguments.size()];
for (int i = 0; i != operands.length; ++i) {
Expr arg = arguments.get(i);
operands[i] = generateExpression(arg, scope);
}
return operands;
}
// =========================================================================
// Helpers
// =========================================================================
/**
* Determine the list of variables which are assigned in a statement block,
* or any child block.
*
* @param block
* @return
*/
private int[] determineModifiedVariables(List<Stmt> block, EnclosingScope scope) {
SyntaxTree tree = scope.getSyntaxTree();
HashSet<Integer> modified = new HashSet<>();
determineModifiedVariables(block,scope,modified);
int[] result = new int[modified.size()];
int index = 0;
for(Integer i : modified) {
Bytecode.VariableAccess va = new Bytecode.VariableAccess(true,i);
Location<?> location = tree.getLocation(i);
result[index++] = scope.add(location.getType(),va);
}
return result;
}
private void determineModifiedVariables(List<Stmt> block, EnclosingScope scope, Set<Integer> modified) {
for(Stmt stmt : block) {
if(stmt instanceof Stmt.Assign) {
Stmt.Assign s = (Stmt.Assign) stmt;
for(Expr.LVal lval : s.lvals) {
Expr.LocalVariable lv = extractAssignedVariable(lval,scope);
if(lv == null) {
// FIXME: this is not an ideal solution long term. In
// particular, we really need this method to detect not
// just modified variables, but also modified locations
// in general (e.g. assignments through references, etc)
continue;
}
Integer variableIndex = scope.get(lv.var);
if(lv != null && variableIndex != null) {
modified.add(variableIndex);
}
}
} else if(stmt instanceof Stmt.DoWhile) {
Stmt.DoWhile s = (Stmt.DoWhile) stmt;
determineModifiedVariables(s.body,scope,modified);
} else if(stmt instanceof Stmt.IfElse) {
Stmt.IfElse s = (Stmt.IfElse) stmt;
determineModifiedVariables(s.trueBranch,scope,modified);
determineModifiedVariables(s.falseBranch,scope,modified);
} else if(stmt instanceof Stmt.NamedBlock) {
Stmt.NamedBlock s = (Stmt.NamedBlock) stmt;
determineModifiedVariables(s.body,scope,modified);
} else if(stmt instanceof Stmt.Switch) {
Stmt.Switch s = (Stmt.Switch) stmt;
for(Stmt.Case c : s.cases) {
determineModifiedVariables(c.stmts,scope,modified);
}
} else if(stmt instanceof Stmt.While) {
Stmt.While s = (Stmt.While) stmt;
determineModifiedVariables(s.body,scope,modified);
}
}
}
private Expr.LocalVariable extractAssignedVariable(Expr.LVal lval, EnclosingScope scope) {
if (lval instanceof Expr.LocalVariable) {
return (Expr.LocalVariable) lval;
} else if (lval instanceof Expr.FieldAccess) {
Expr.FieldAccess e = (Expr.FieldAccess) lval;
return extractAssignedVariable((Expr.LVal) e.src, scope);
} else if (lval instanceof Expr.IndexOf) {
Expr.IndexOf e = (Expr.IndexOf) lval;
return extractAssignedVariable((Expr.LVal) e.src, scope);
} else if (lval instanceof Expr.Dereference) {
return null;
} else {
internalFailure(errorMessage(INVALID_LVAL_EXPRESSION), scope.getSourceContext(), lval);
return null; // dead code
}
}
private int[] append(int[] lhs, int... rhs) {
int[] rs = new int[lhs.length + rhs.length];
System.arraycopy(lhs, 0, rs, 0, lhs.length);
System.arraycopy(rhs, 0, rs, lhs.length, rhs.length);
return rs;
}
private Bytecode.OperatorKind OP2BOP(Expr.BOp bop, SyntacticElement elem, Context scope) {
switch (bop) {
case ADD:
return Bytecode.OperatorKind.ADD;
case SUB:
return Bytecode.OperatorKind.SUB;
case MUL:
return Bytecode.OperatorKind.MUL;
case DIV:
return Bytecode.OperatorKind.DIV;
case REM:
return Bytecode.OperatorKind.REM;
case EQ:
return Bytecode.OperatorKind.EQ;
case NEQ:
return Bytecode.OperatorKind.NEQ;
case LT:
return Bytecode.OperatorKind.LT;
case LTEQ:
return Bytecode.OperatorKind.LTEQ;
case GT:
return Bytecode.OperatorKind.GT;
case GTEQ:
return Bytecode.OperatorKind.GTEQ;
case AND:
return Bytecode.OperatorKind.AND;
case OR:
return Bytecode.OperatorKind.OR;
case BITWISEAND:
return Bytecode.OperatorKind.BITWISEAND;
case BITWISEOR:
return Bytecode.OperatorKind.BITWISEOR;
case BITWISEXOR:
return Bytecode.OperatorKind.BITWISEXOR;
case LEFTSHIFT:
return Bytecode.OperatorKind.LEFTSHIFT;
case RIGHTSHIFT:
return Bytecode.OperatorKind.RIGHTSHIFT;
case IS:
return Bytecode.OperatorKind.IS;
default:
internalFailure(errorMessage(INVALID_BINARY_EXPRESSION), scope, elem);
}
// dead-code
return null;
}
public List<Integer> toIntegerList(int... items) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i != items.length; ++i) {
list.add(items[i]);
}
return list;
}
private int[] toIntArray(List<Integer> items) {
int[] arr = new int[items.size()];
for (int i = 0; i != arr.length; ++i) {
arr[i] = items.get(i);
}
return arr;
}
private static int _idx = 0;
public static String freshLabel() {
return "blklab" + _idx++;
}
/**
* Captures all useful information about the scope in which a statement or
* expression is being translated. For example, it determines which WyIL
* register all visible variables and parameters map to. Furthermore, it
* determines where break and continue statements will jump to.
*
* @author David J. Pearce
*
*/
private static final class EnclosingScope {
/**
* Maps variables to their WyIL location.
*/
private final HashMap<String, Integer> environment;
/**
* The enclosing source file scope (needed for error reporting)
*/
private final WhileyFile.Context context;
/**
* The enclosing syntax tree
*/
private final SyntaxTree enclosing;
public EnclosingScope(SyntaxTree enclosing, WhileyFile.Context context) {
this(new HashMap<String, Integer>(), enclosing, context);
}
private EnclosingScope(Map<String, Integer> environment, SyntaxTree enclosing, WhileyFile.Context context) {
this.environment = new HashMap<>(environment);
this.enclosing = enclosing;
this.context = context;
}
public SyntaxTree getSyntaxTree() {
return enclosing;
}
public WhileyFile.Context getSourceContext() {
return context;
}
public Type.FunctionOrMethod getEnclosingFunctionType() {
WhileyFile.FunctionOrMethodOrProperty m = (WhileyFile.FunctionOrMethodOrProperty) context;
return m.resolvedType();
}
public Integer get(String name) {
return environment.get(name);
}
public Location<?> getLocation(String name) {
return enclosing.getLocation(environment.get(name));
}
/**
* Declare a new variable in the enclosing bytecode forest.
*
* @param type
* The declared type of the variable
* @param name
* The declare name of the variable
* @return
*/
public int declare(Type type, String name, List<Attribute> attributes) {
List<SyntaxTree.Location<?>> locations = enclosing.getLocations();
int index = locations.size();
environment.put(name, index);
Bytecode.VariableDeclaration decl = new Bytecode.VariableDeclaration(name);
locations.add(new SyntaxTree.Location<Bytecode>(enclosing, type, decl, attributes));
return index;
}
/**
* Declare a variable alias in the enclosing bytecode forest.
*
* @param type
* The declared type of the variable
* @param name
* The declare name of the variable
* @return
*/
public int createAlias(Type type, String name, List<Attribute> attributes) {
List<SyntaxTree.Location<?>> locations = enclosing.getLocations();
int original = environment.get(name);
int index = locations.size();
environment.put(name, index);
Bytecode.AliasDeclaration alias = new Bytecode.AliasDeclaration(original);
locations.add(new SyntaxTree.Location<Bytecode>(enclosing, type, alias, attributes));
return index;
}
public int add(Bytecode stmt, Attribute... attributes) {
return add(stmt,Arrays.asList(attributes));
}
public int add(Type type, Bytecode stmt, Attribute... attributes) {
return add(type,stmt,Arrays.asList(attributes));
}
public int add(Bytecode operand, List<Attribute> attributes) {
return add(new Type[0],operand,attributes);
}
public int add(Type type, Bytecode operand, List<Attribute> attributes) {
return add(new Type[]{type},operand,attributes);
}
/**
* Allocate a multi-operand on the stack.
*
* @param Type
* @param operand
* @return
*/
public int add(List<Type> types, Bytecode operand, List<Attribute> attributes) {
Type[] nominals = new Type[types.size()];
for (int i = 0; i != nominals.length; ++i) {
nominals[i] = types.get(i);
}
return add(nominals,operand,attributes);
}
public int add(Type[] types, Bytecode operand, List<Attribute> attributes) {
List<SyntaxTree.Location<?>> locations = enclosing.getLocations();
int index = locations.size();
locations.add(new SyntaxTree.Location<>(enclosing, types, operand, attributes));
// Check whether this is declaring a new variable or not.
if (operand instanceof Bytecode.VariableDeclaration) {
Bytecode.VariableDeclaration vd = (Bytecode.VariableDeclaration) operand;
environment.put(vd.getName(), index);
}
return index;
}
/**
* Create a new clone scope. This is a subscope where new variables
* can be declared and, furthermore, it corresponds to a new block in
* the underlying forest.
*
* @return
*/
@Override
public EnclosingScope clone() {
return new EnclosingScope(environment, enclosing, context);
}
}
}