DispatchCompiler.java
package com.mackenziehigh.autumn.lang.compiler.compilers;
import autumn.lang.compiler.ast.commons.IExpression;
import autumn.lang.compiler.ast.nodes.DispatchExpression;
import autumn.util.F;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IExpressionType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IFormalParameter;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IReferenceType;
import com.mackenziehigh.autumn.lang.compiler.utils.Utils;
import com.mackenziehigh.autumn.resources.Finished;
import java.util.List;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
/**
* An instance of this class performs the compilation of a dispatch-expression.
*
* @author Mackenzie High
*/
@Finished("2015/01/16")
final class DispatchCompiler
{
/**
* This is the Abstract Syntax Tree representation of the dispatch-expression itself.
*/
private final DispatchExpression node;
/**
* Essentially, this is the module that encloses the dispatch-expression.
*/
private final ModuleCompiler module;
/**
* This object controls the allocation of local variables in the enclosing function.
*/
private final VariableAllocator allocator;
/**
* This object simplifies the manipulation of local-variables.
*/
private final VariableManipulator vars;
/**
* This is the name of the function whose overloads will handle the invocation.
*/
private final String name;
/**
* These are the expressions that produce the arguments of the invocation.
*/
private final List<IExpression> arguments;
/**
* These are the types of the static-types of the arguments.
*/
private final List<IExpressionType> argument_types = Lists.newArrayList();
/**
* These are the overloads of the function that may handle the invocation.
*/
private final List<IMethod> applicable = Lists.newArrayList();
/**
* These are the names of the temporary local variables used to store the arguments.
*/
private final List<String> temporaries = Lists.newArrayList();
/**
* This is used to ensure that this class is used correctly.
*/
private int state = 1;
/**
* Sole Constructor.
*
* @param node is the dispatch-expression itself.
* @param module is essentially the enclosing module.
* @param allocator is used to allocate variables.
* @param vars is used to generate bytecode that manipulates variables.
* @param name is the name of the function being dispatched.
* @param arguments are the arguments being passed to the function.
*/
public DispatchCompiler(final DispatchExpression node,
final ModuleCompiler module,
final VariableAllocator allocator,
final VariableManipulator vars,
final String name,
final List<IExpression> arguments)
{
Preconditions.checkNotNull(node);
Preconditions.checkNotNull(module);
Preconditions.checkNotNull(allocator);
Preconditions.checkNotNull(vars);
Preconditions.checkNotNull(name);
Preconditions.checkNotNull(arguments);
this.node = node;
this.allocator = allocator;
this.arguments = arguments;
this.module = module;
this.name = name;
this.vars = vars;
/**
* Retrieve the types of the arguments.
*/
for (IExpression arg : arguments)
{
argument_types.add(module.program.symbols.expressions.get(arg));
}
}
/**
* This method searches for the applicable function overloads.
*/
public void resolve()
{
/**
* This is the 1st step in the compilation of a dispatch-expression.
*/
assert state++ == 1;
/**
* Select the overloads that are applicable under the terms of a regular invocation.
*/
final List<IMethod> selected = module.program.typesystem.utils.select(module.type,
module.type.getAllVisibleMethods(),
true,
name);
/**
* This list will hold the unsorted applicable overloads.
*/
final List<IMethod> unsorted = Lists.newArrayList();
/**
* Transfer the truly applicable overloads to the unsorted list.
*/
for (IMethod method : selected)
{
if (isApplicable(method))
{
unsorted.add(method);
}
}
/**
* Sort the overloads and store them for later.
*/
applicable.addAll(module.program.typesystem.utils.sort(unsorted));
}
/**
* This method determines whether a function is potentially applicable to the arguments.
*
* @param function is the function overload that may be able to accept the arguments.
* @return true, iff the function is potentially applicable to the arguments.
*/
private boolean isApplicable(final IMethod function)
{
/**
* Dispatch-expressions can only invoke functions whose parameters are reference-types.
*/
for (IFormalParameter parameter : function.getParameters())
{
if (parameter.getType().isReferenceType() == false)
{
return false;
}
}
/**
* The function is potentially applicable to the arguments.
*/
return true;
}
/**
* This method performs the static-checking of the dispatch-expression.
*/
public void check()
{
/**
* This is the 2nd step in the compilation of a dispatch-expression.
*/
assert state++ == 2;
/**
* There must be applicable function overloads available.
*/
if (applicable.isEmpty())
{
module.program.checker.reportNoSuchMethod(node, true, module.type, name, argument_types);
}
/**
* The arguments must produce a value.
*/
for (IExpression argument : arguments)
{
module.program.checker.requireNonVoid(argument);
}
}
/**
* This function performs the actual bytecode generation.
*
* @param expr is used to compile the argument expressions.
*/
public void compile(final ExpressionCodeGenerator expr)
{
/**
* This is the 3rd step in the compilation of a dispatch-expression.
*/
assert state++ == 3;
/**
* This label marks the location that execution jumps to after a successful dispatch.
*/
final LabelNode done = new LabelNode();
/**
* Evaluate the arguments and store them in temporaries.
*/
loadTemps(expr);
/**
* Dispatch Table
*/
for (IMethod function : applicable)
{
generateDispatchTableEntry(expr.code, function, done);
}
/**
* Default Termination
*/
generateDefaultDispatch(expr.code);
expr.code.add(done);
}
/**
* This method generates the bytecode that evaluates the argument expressions
* and then places the resulting arguments into the temporary local variables.
*
* @param expr will be used to compile the argument expressions.
*/
private void loadTemps(final ExpressionCodeGenerator expr)
{
for (IExpression argument : arguments)
{
/**
* Get the static-type of the argument.
*/
final IExpressionType type = module.program.symbols.expressions.get(argument);
/**
* Declare a new temporary local variable.
*/
final String local = "autumn$dispatch$" + F.unique();
allocator.declareTemp(local, module.program.typesystem.utils.OBJECT);
temporaries.add(local);
/**
* Compile the argument expression.
*/
argument.accept(expr);
/**
* Generate bytecode that automatically boxes the argument, if necessary.
*/
module.program.typesystem.utils.autoboxToObject(expr.code, type);
/**
* Generate bytecode that places the argument into the temporary.
*/
vars.store(local);
}
}
/**
* This method generates the bytecode of a single entry in the dispatch-table entry.
*
* <p>
* A bytecode generated by a dispatch-table entry does several things.
* The entry decides whether the related overload is applicable to the arguments.
* If so, the entry invokes the related overload passing in the arguments.
* Then, the entry forces execution to exit the dispatch-expression.
* If not, the entry simply allows execution to fall through.
* </p>
*
* @param code is the bytecode that is being generated.
* @param target represents the function overload associated with the entry.
* @param done is a bytecode label that marks the end of the dispatch-expression.
*/
private void generateDispatchTableEntry(final InsnList code,
final IMethod target,
final LabelNode done)
{
assert arguments.size() == target.getParameters().size();
/**
* This label marks the end of the this dispatch-table entry only.
*/
final LabelNode skip = new LabelNode();
/**
* Generate bytecode that determines whether the arguments match the parameters.
*
* If any of the arguments fail to match, skip this entry in the dispatch-table.
*/
for (int i = 0; i < arguments.size(); i++)
{
generateArgumentTest(code, target, skip, i);
}
/**
* Since all the arguments matched, invoke the target method.
*/
generateInvocation(code, target);
/**
* Exit the dispatch expression.
*/
code.add(new JumpInsnNode(Opcodes.GOTO, done));
code.add(skip);
}
/**
* This method generates bytecode that determines whether an argument matches a parameter.
*
* @param code is the bytecode being generated.
* @param target is the function overload that contains the parameter.
* @param skip is a bytecode label that marks the endpoint of the enclosing dispatch-table entry.
* @param index identifies the argument and parameter.
*/
private void generateArgumentTest(final InsnList code,
final IMethod target,
final LabelNode skip,
final int index)
{
assert arguments.size() == target.getParameters().size();
/**
* Get the static-type of the parameter.
*/
final IReferenceType parameter = (IReferenceType) target.getParameters().get(index).getType();
/**
* Get the name of the temporary local variable that stores the argument.
*/
final String argument = temporaries.get(index);
/**
* If the argument matches the parameter, execution will redirect to the location of this label.
*/
final LabelNode ok = new LabelNode();
/**
* Generated Bytecode:
*
* LOAD argument - Load the argument onto the operand-stack.
* IFNULL ok - If the argument is null, then the argument matches.
* LOAD argument - Load the argument onto the operand-stack.
* INSTANCEOF argument : parameter - If (argument instanceof parameter-type), then the argument matches.
* IF_FALSE skip - Otherwise, the argument does not match.
* LABEL ok -
*/
vars.load(argument);
code.add(new JumpInsnNode(Opcodes.IFNULL, ok));
vars.load(argument);
code.add(new TypeInsnNode(Opcodes.INSTANCEOF, Utils.internalName(parameter)));
code.add(new JumpInsnNode(Utils.IF_FALSE, skip));
code.add(ok);
}
/**
* This method generates the bytecode of a function invocation.
*
* @param code is the bytecode that is being generated.
* @param target is the function overload
*/
private void generateInvocation(final InsnList code,
final IMethod function)
{
/**
* Load the arguments onto the operand-stack.
*/
for (int i = 0; i < temporaries.size(); i++)
{
/**
* Get the name of the temporary local variable that stores the argument.
*/
final String argument = temporaries.get(i);
/**
* Get the type of the parameter related to the argument.
*/
final IReferenceType type = (IReferenceType) function.getParameters().get(i).getType();
/**
* Generate the bytecode that loads the argument onto the operand-stack.
*/
vars.load(argument);
/**
* Generate a checked-cast that casts the argument to the type of the parameter.
*/
code.add(new TypeInsnNode(Opcodes.CHECKCAST, Utils.internalName(type)));
}
/**
* Generate the bytecode that actually performs the invocation of the function.
*/
code.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
Utils.internalName(function.getOwner()),
function.getName(),
function.getDescriptor()));
/**
* If the function returns void, make it appear to return null instead.
*/
if (function.getReturnType().isVoidType())
{
code.add(new InsnNode(Opcodes.ACONST_NULL));
}
/**
* If the function returns a primitive-type, box the return-value.
*/
else if (function.getReturnType().isPrimitiveType())
{
module.program.typesystem.utils.autoboxToObject(code, function.getReturnType());
}
}
/**
* This method generates the bytecode that executes when dispatch fails.
*
* <p>
* The generated bytecode creates an exception object and then throws it.
* </p>
*
* @param code is the bytecode being generated.
*/
private void generateDefaultDispatch(final InsnList code)
{
code.add(new TypeInsnNode(Opcodes.NEW, "autumn/lang/exceptions/DispatchException"));
code.add(new InsnNode(Opcodes.DUP));
code.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
"autumn/lang/exceptions/DispatchException",
"<init>",
"()V"));
code.add(new InsnNode(Opcodes.ATHROW));
}
}