StatementCodeGenerator.java

package com.mackenziehigh.autumn.lang.compiler.compilers;

import autumn.lang.Delegate;
import autumn.lang.compiler.ast.commons.*;
import autumn.lang.compiler.ast.nodes.*;
import autumn.lang.internals.Helpers;
import autumn.util.F;
import com.google.common.collect.Lists;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IClassType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IDeclaredType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IType;
import com.mackenziehigh.autumn.lang.compiler.utils.Utils;
import java.util.List;
import java.util.Stack;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;

/**
 * An instance of this class can generate the bytecode that implements a statement.
 *
 * @author Mackenzie High
 */
final class StatementCodeGenerator
        extends ExpressionCodeGenerator
{
    private final FunctionCompiler function;

    private final Stack<LabelNode> break_labels = new Stack<LabelNode>();

    private final Stack<LabelNode> continue_labels = new Stack<LabelNode>();

    private final Stack<LabelNode> redo_labels = new Stack<LabelNode>();

    public StatementCodeGenerator(final FunctionCompiler function)
    {
        super(function.module, function.vars, function.instructions);

        this.function = function;
    }

    @Override
    public void visit(final IfStatement object)
    {
        // Generated Bytecode
        //
        // <condition-1>
        // IF_FALSE @END_OF_CASE_1
        // <body-1>
        // GOTO @END
        // @END_OF_CASE_1
        //
        // <condition-2>
        // IF_FALSE @END_OF_CASE_2
        // <body-2>
        // GOTO @END
        // @END_OF_CASE_2
        //
        // <condition-N>
        // IF_FALSE @END_OF_CASE_N
        // <body-N>
        // GOTO @END
        // @END_OF_CASE_N
        //
        // <body-else>
        //
        // @END
        //////////////////////////////////////////////////////////////////////////////////////////
        //
        // At least one conditional case is present.
        //
        // The <body-else> is only generated, when an else-clause is present.
        //
        //////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode END = new LabelNode();

        // Put all the conditional cases into a single list.
        final List<ConditionalCase> cases = Lists.newLinkedList();
        cases.add(object.getMainCase());
        cases.addAll(object.getElifCases().asMutableList());

        for (ConditionalCase cc : cases)
        {
            final LabelNode END_OF_CASE = new LabelNode();

            compileCondition(cc.getCondition());
            code.add(new JumpInsnNode(Utils.IF_FALSE, END_OF_CASE));
            {
                cc.getBody().accept(this);
                code.add(new JumpInsnNode(Opcodes.GOTO, END));
            }
            code.add(END_OF_CASE);
        }

        if (object.getElseCase() != null)
        {
            object.getElseCase().accept(this);
        }

        code.add(END);
    }

    @Override
    public void visit(final WhenStatement object)
    {
        // Generated Bytecode
        //
        // <condition>
        // IF_FALSE @END
        // <statement>
        // @END
        /////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode END = new LabelNode();

        compileCondition(object.getCondition());
        code.add(new JumpInsnNode(Utils.IF_FALSE, END));
        object.getBody().accept(this);
        code.add(END);
    }

    @Override
    public void visit(final GotoStatement object)
    {
        // Generated Bytecode
        //
        // GOTO @LABEL
        //
        ////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final String name = object.getLabel().getName();

        final LabelNode node = function.labels.nodeOf(name);

        code.add(new JumpInsnNode(Opcodes.GOTO, node));
    }

    @Override
    public void visit(final MarkerStatement object)
    {
        // Generated Bytecode
        //
        // @LABEL
        //
        ////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final String name = object.getLabel().getName();

        final LabelNode node = function.labels.nodeOf(name);

        code.add(node);
    }

    @Override
    public void visit(final BranchStatement object)
    {
        // Generated Bytecode
        //
        // <index>                                 - Evaluate the <index> expression.
        // <conversion>                            - Unbox or auto-widen the index, if needed.
        // TABLESWITCH label[0] , ... , label[n]   - Generate a jump-table.
        //
        ////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        /**
         * Before we can generate any bytecode, we must convert the labels to ASM labels.
         */
        final int max = object.getLabels().size() - 1;

        final LabelNode[] labels = new LabelNode[max + 1];

        for (int i = 0; i < labels.length; i++)
        {
            labels[i] = function.labels.nodeOf(object.getLabels().get(i).getName());
        }

        final LabelNode dflt = function.labels.nodeOf(object.getDefaultLabel().getName());

        /**
         * Now we can perform the actual code generation.
         */
        object.getIndex().accept(this);
        convert(program.typesystem.utils.PRIMITIVE_INT, object.getIndex());

        /**
         * If only the default label is present, then then a simple goto must be generated.
         * Otherwise, a verifier error may result at runtime.
         */
        if (labels.length != 0)
        {
            code.add(new TableSwitchInsnNode(0, max, dflt, labels));
        }
        else
        {
            code.add(new JumpInsnNode(Opcodes.GOTO, dflt));
        }
    }

    @Override
    public void visit(final WhileStatement object)
    {
        // Generated Bytecode
        //
        // @CONTINUE               - This is where continue-statements jump to.
        // <condition>             - Evaluate the condition.
        // UNBOX condition         - Unbox the condition, if needed.
        // IF_FALSE @BREAK         - If the condition was false, exit the loop.
        // @REDO                   - This is where redo-statements jump to.
        // <body>                  - Execute the body.
        // GOTO @CONTINUE          - Start the next iteration of the loop.
        // @BREAK                  - This is where break-statements jump to.
        //
        ///////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode BREAK = new LabelNode();
        final LabelNode CONTINUE = new LabelNode();
        final LabelNode REDO = new LabelNode();

        code.add(CONTINUE);
        compileCondition(object.getCondition());
        code.add(new JumpInsnNode(Utils.IF_FALSE, BREAK));
        code.add(REDO);
        {
            break_labels.push(BREAK);
            continue_labels.push(CONTINUE);
            redo_labels.push(REDO);

            object.getBody().accept(this);

            break_labels.pop();
            continue_labels.pop();
            redo_labels.pop();
        }
        code.add(new JumpInsnNode(Opcodes.GOTO, CONTINUE));
        code.add(BREAK);
    }

    @Override
    public void visit(final UntilStatement object)
    {
        // Generated Bytecode
        //
        // @CONTINUE               - This is where continue-statements jump to.
        // <condition>             - Evaluate the condition.
        // UNBOX condition         - Unbox the condition, if needed.
        // IF_TRUE @BREAK          - If the condition was true, exit the loop.
        // @REDO                   - This is where redo-statements jump to.
        // <body>                  - Execute the body.
        // GOTO @CONTINUE          - Start the next iteration of the loop.
        // @BREAK                  - This is where break-statements jump to.
        //
        ///////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode BREAK = new LabelNode();
        final LabelNode CONTINUE = new LabelNode();
        final LabelNode REDO = new LabelNode();

        code.add(CONTINUE);
        compileCondition(object.getCondition());
        code.add(new JumpInsnNode(Utils.IF_TRUE, BREAK));
        code.add(REDO);
        {
            break_labels.push(BREAK);
            continue_labels.push(CONTINUE);
            redo_labels.push(REDO);

            object.getBody().accept(this);

            break_labels.pop();
            continue_labels.pop();
            redo_labels.pop();
        }
        code.add(new JumpInsnNode(Opcodes.GOTO, CONTINUE));
        code.add(BREAK);
    }

    @Override
    public void visit(final DoWhileStatement object)
    {
        // Generated Bytecode:
        //
        // @TOP              - This is where each iteration starts.
        // @REDO             - This is where redo-statements jump to.
        // <body>            - Execute the body.
        // @CONTINUE         - This is where continue-statements jump to.
        // <condition>       - Evaluate the condition.
        // UNBOX condition   - Unbox the condition, if needed.
        // IF_TRUE @TOP      - If the condition was true, start the next iteration.
        // @BREAK            - This is where break-statements jump to.
        //
        ///////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode BREAK = new LabelNode();
        final LabelNode CONTINUE = new LabelNode();
        final LabelNode REDO = new LabelNode();
        final LabelNode TOP = new LabelNode();

        code.add(TOP);
        code.add(REDO);
        {
            break_labels.push(BREAK);
            continue_labels.push(CONTINUE);
            redo_labels.push(REDO);

            object.getBody().accept(this);

            break_labels.pop();
            continue_labels.pop();
            redo_labels.pop();
        }
        code.add(CONTINUE);
        compileCondition(object.getCondition());
        code.add(new JumpInsnNode(Utils.IF_TRUE, TOP));
        code.add(BREAK);
    }

    @Override
    public void visit(final DoUntilStatement object)
    {
        // Generated Bytecode:
        //
        // @TOP              - This is where each iteration starts.
        // @REDO             - This is where redo-statements jump to.
        // <body>            - Execute the body.
        // @CONTINUE         - This is where continue-statements jump to.
        // <condition>       - Evaluate the condition.
        // UNBOX condition   - Unbox the condition, if needed.
        // IF_FALSE @TOP     - If the condition was false, start the next iteration.
        // @BREAK            - This is where break-statements jump to.
        //
        ///////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode BREAK = new LabelNode();
        final LabelNode CONTINUE = new LabelNode();
        final LabelNode REDO = new LabelNode();
        final LabelNode TOP = new LabelNode();

        code.add(TOP);
        code.add(REDO);
        {
            break_labels.push(BREAK);
            continue_labels.push(CONTINUE);
            redo_labels.push(REDO);

            object.getBody().accept(this);

            break_labels.pop();
            continue_labels.pop();
            redo_labels.pop();
        }
        code.add(CONTINUE);
        compileCondition(object.getCondition());
        code.add(new JumpInsnNode(Utils.IF_FALSE, TOP));
        code.add(BREAK);
    }

    @Override
    public void visit(final ForeverStatement object)
    {
        // Generated Bytecode:
        //
        // @CONTINUE          - This is where continue-statements jump to.
        // @REDO              - This is where redo-statements jump to.
        // <body>             - Execute the body.
        // GOTO @CONTINUE     - Start the next iteration of the loop.
        // @BREAK             - This is where break-statements jump to.
        //
        ///////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode BREAK = new LabelNode();
        final LabelNode CONTINUE = new LabelNode();
        final LabelNode REDO = new LabelNode();

        code.add(CONTINUE);
        code.add(REDO);
        {
            break_labels.push(BREAK);
            continue_labels.push(CONTINUE);
            redo_labels.push(REDO);

            object.getBody().accept(this);

            break_labels.pop();
            continue_labels.pop();
            redo_labels.pop();
        }
        code.add(new JumpInsnNode(Opcodes.GOTO, CONTINUE));
        code.add(BREAK);
    }

    @Override
    public void visit(final ForStatement object)
    {
        // Generated Bytecode:
        //
        // <initializer>        - Evaluate the initializer.
        // UNBOX initializer    - Unbox the initializer, if needed.
        // ISTORE assignee      - Place the initializer into the assignee variable.
        //
        // @TOP                 - This is where each loop iteration starts.
        //
        // <condition>          - Evaluate the condition.
        // UNBOX condition      - Unbox the condition, if needed.
        // IF_FALSE @BREAK      - If the condition was false, exit the loop.
        //
        // @REDO                - This is where redo-statements jump to.
        //
        // <body>               - Execute the body.
        //
        // @CONTINUE            - This is where continue-statements jump to.
        //
        // <next>               - Evaluate the modifier.
        // UNBOX next           - Unbox the modifier, if needed.
        // ISTORE assignee      - Place the modifier (aka next value) into the assignee variable.
        //
        // GOTO @TOP            - Start the next loop iteration.
        //
        // @BREAK               - This is where break-statements jump to.
        //
        ///////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode BREAK = new LabelNode();
        final LabelNode CONTINUE = new LabelNode();
        final LabelNode REDO = new LabelNode();
        final LabelNode TOP = new LabelNode();

        object.getInitializer().accept(this);
        convert(program.typesystem.utils.PRIMITIVE_INT, object.getInitializer());
        vars.store(object.getVariable().getName());

        code.add(TOP);
        compileCondition(object.getCondition());
        code.add(new JumpInsnNode(Utils.IF_FALSE, BREAK));
        code.add(REDO);
        {
            break_labels.push(BREAK);
            continue_labels.push(CONTINUE);
            redo_labels.push(REDO);

            object.getBody().accept(this);

            code.add(CONTINUE);

            object.getNext().accept(this);
            convert(program.typesystem.utils.PRIMITIVE_INT, object.getNext());
            vars.store(object.getVariable().getName());

            break_labels.pop();
            continue_labels.pop();
            redo_labels.pop();
        }
        code.add(new JumpInsnNode(Opcodes.GOTO, TOP));
        code.add(BREAK);
    }

    @Override
    public void visit(final ForeachStatement object)
    {
        // Generated Bytecode:
        //
        // <iterable>                           - Evaluate the iterable.
        // INVOKEINTERFACE iterable.iterator()  - Get an Iterator from the Iterable.
        // ASTORE iterator                      - Save the iterator for later.
        //
        // @CONTINUE                            - This is where each iteration starts.
        // ALOAD iterator                       - Load the iterator object.
        // INVOKEINTERFACE iterator.hasNext()   - Ask whether the iterator has more elements.
        // IF_FALSE @BREAK                      - If no, we can now exit the loop.
        //
        // ALOAD iterator                       - Load the iterator object.
        // INVOKEINTERFACE iterator.next()      - Get the next element in the sequence.
        // CHECKCAST type                       - Cast the element to the required type.
        // ASTORE variable                      - Put the element into the variable.
        //
        // @REDO                                - This is where redo-statements jump to.
        //
        // <body>                               - Execute the body of the loop.
        // GOTO CONTINUE                        - Goto the start of the next iteration.
        //
        // @BREAK                               - This is the exit-point of the loop.
        //
        // ACONST_NULL                          - Load null.
        // ASTORE iterator                      - Clear the iterator temporary variable.
        //
        //////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final IDeclaredType type = (IDeclaredType) function.module.imports.resolveVariableType(object.getType());

        final LabelNode BREAK = new LabelNode();
        final LabelNode CONTINUE = new LabelNode();
        final LabelNode REDO = new LabelNode();

        /**
         * Declare the temporary variable that will store the iterator.
         */
        final String iterator = "autumn$temp$" + F.unique();
        function.allocator.declareTemp(iterator, program.typesystem.utils.OBJECT);

        // Evaluate the iterable.
        object.getIterable().accept(this);

        // Get an iterator over the iterable.
        code.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                    "java/lang/Iterable",
                                    "iterator",
                                    "()Ljava/util/Iterator;"));

        // Save the iterator for later.
        function.vars.store(iterator);

        code.add(CONTINUE);

        // If the iterator is consumed, then break out of the loop.
        function.vars.load(iterator);
        code.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                    "java/util/Iterator",
                                    "hasNext",
                                    "()Z"));
        code.add(new JumpInsnNode(Utils.IF_FALSE, BREAK));

        // Get the next element from the iterator.
        function.vars.load(iterator);
        code.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                    "java/util/Iterator",
                                    "next",
                                    "()Ljava/lang/Object;"));

        // Cast the value to the expected type.
        code.add(new TypeInsnNode(Opcodes.CHECKCAST, Utils.internalName(type)));

        // Assign the value to the variable.
        function.vars.store(object.getVariable().getName());

        // Execute the body of the loop.
        code.add(REDO);
        {
            break_labels.push(BREAK);
            continue_labels.push(CONTINUE);
            redo_labels.push(REDO);

            object.getBody().accept(this);

            break_labels.pop();
            continue_labels.pop();
            redo_labels.pop();
        }
        code.add(new JumpInsnNode(Opcodes.GOTO, CONTINUE));
        code.add(BREAK);

        // Clear the iterator temporary variable.
        code.add(new InsnNode(Opcodes.ACONST_NULL));
        function.vars.store(iterator);
    }

    @Override
    public void visit(final BreakStatement object)
    {
        // Generated Bytecode:
        //
        // GOTO @BREAK
        //
        ////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode BREAK = break_labels.peek();

        code.add(new JumpInsnNode(Opcodes.GOTO, BREAK));
    }

    @Override
    public void visit(final ContinueStatement object)
    {
        // Generated Bytecode:
        //
        // GOTO @CONTINUE
        //
        ////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LabelNode CONTINUE = continue_labels.peek();

        code.add(new JumpInsnNode(Opcodes.GOTO, CONTINUE));
    }

    @Override
    public void visit(final RedoStatement object)
    {
        // Generated Bytecode:
        //
        // GOTO @REDO
        //
        ////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);


        final LabelNode REDO = redo_labels.peek();

        code.add(new JumpInsnNode(Opcodes.GOTO, REDO));
    }

    @Override
    public void visit(final VarStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        /**
         * Get the name of the variable that is being assigned to.
         */
        final String assignee = object.getVariable().getName();

        /**
         * Generate the bytecode that produces the value.
         */
        object.getValue().accept(this);

        /**
         * Generate the bytecode that actually performs the assignment.
         */
        vars.store(assignee);
    }

    @Override
    public void visit(final ValStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        /**
         * Get the name of the variable that is being assigned to.
         */
        final String name = object.getVariable().getName();

        /**
         * Generate the bytecode that produces the value.
         */
        object.getValue().accept(this);

        /**
         * Generate the bytecode that actually performs the assignment.
         */
        vars.store(name);
    }

    @Override
    public void visit(final LetStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        /**
         * Get the name of the variable that is being assigned to.
         */
        final String name_of_variable = object.getVariable().getName();

        /**
         * Get the type of the variable that is being assigned to.
         */
        final IType type_of_variable = function.allocator.typeOf(name_of_variable);

        /**
         * Generate the bytecode that produces the value.
         */
        object.getValue().accept(this);

        /**
         * Generate the bytecode that performs auto-boxing, auto-unboxing, or coercion, if needed.
         */
        convert(type_of_variable, object.getValue());

        /**
         * Generate the bytecode that actually performs the assignment.
         */
        vars.store(object.getVariable().getName());
    }

    @Override
    public void visit(final LambdaStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final LambdaCompiler lambda = program.symbols.lambdas.get(object);

        /**
         * Create a new instance of the lambda.
         * Capture the local variables.
         * Load the lambda onto the operand-stack.
         */
        lambda.load(code);

        /**
         * Place the lambda object into the variable that will store it.
         */
        function.vars.store(object.getVariable().getName());
    }

    @Override
    public void visit(final DelegateStatement object)
    {
        final IClassType functor = module.imports.resolveDefinedFunctorType(object.getType());

        final IMethod handler = program.symbols.delegates.get(object);

        // Generated Bytecode:
        //
        // NEW functor                                    - Create a new uninitialized instance of the functor-type.
        // DUP                                            - Duplicate the reference to the functor.
        // INVOKESTATIC module.instance()                 - Get the singleton instance of the module that contains the delegate function.
        // LDC name                                       - Load the name of the delegate function.
        // INVOKESTATIC Helpers.delegate(Module, String)  - Create the delegate object.
        // INVOKESPECIAL functor.<init>(Functor)          - Wrap the delegate object in the functor object.
        // STORE variable                                 - Place the wrapped delegate object into the variable.
        //
        ////////////////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        code.add(new TypeInsnNode(Opcodes.NEW, Utils.internalName(functor)));

        code.add(new InsnNode(Opcodes.DUP));

        code.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    Utils.internalName(handler.getOwner()),
                                    "instance",
                                    "()" + handler.getOwner().getDescriptor()));

        code.add(new LdcInsnNode(handler.getName()));

        Utils.invoke(code,
                     Opcodes.INVOKESTATIC,
                     Helpers.class,
                     Delegate.class,
                     "delegate",
                     autumn.lang.Module.class,
                     String.class);

        code.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                    Utils.internalName(functor),
                                    "<init>",
                                    "(Lautumn/lang/TypedFunctor;)V"));

        function.vars.store(object.getVariable().getName());
    }

    @Override
    public void visit(final SequenceStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final int break_count = break_labels.size();

        for (IStatement s : object.getElements())
        {
            s.accept(this);
        }

        assert break_labels.size() == break_count;
        assert continue_labels.size() == break_count;
        assert redo_labels.size() == break_count;
    }

    @Override
    public void visit(final ExpressionStatement object)
    {
        // Generated Bytecode:
        //
        // <expression>      - Evaluate the expression.
        // POP-X             - Pop the value, if any, of the expression off of the stack.
        //
        ///////////////////////////////////////////////////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        object.getExpression().accept(this);

        final IType type = program.symbols.expressions.get(object.getExpression());
        code.add(Utils.selectPop(type));
    }

    @Override
    public void visit(final NopStatement object)
    {
        // Generated Bytecode:
        //
        // NOP
        //
        //////////////////////////////////////

        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        code.add(new InsnNode(Opcodes.NOP));
    }

    @Override
    public void visit(final TryCatchStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final List<ExceptionHandler> handlers = program.symbols.handlers.get(object);

        /**
         * This label marks the start of the protected region.
         */
        final LabelNode START = new LabelNode();

        /**
         * This label marks the end of the protected region.
         */
        final LabelNode END = new LabelNode();

        /**
         * This label node marks the end of the entire try-catch construct.
         */
        final LabelNode EXIT = new LabelNode();

        // Enter the protected region.
        code.add(START);

        // Execute the protected region.
        // If an exception occurs, jump directly to the appropriate handler.
        object.getBody().accept(this);

        // Exit the protected region.
        code.add(END);

        // Since no exception occurred, skip the exception handlers.
        code.add(new JumpInsnNode(Opcodes.GOTO, EXIT));

        // Compile the exception handlers.
        for (ExceptionHandler handler : handlers)
        {
            // Compile a single exception handler.
            generateExceptionHandler(START, END, handler);

            // If the handler was executed, then immediately exit the try-catch construct.
            code.add(new JumpInsnNode(Opcodes.GOTO, EXIT));
        }

        // Exit the try-catch construct.
        code.add(EXIT);
    }

    private void generateExceptionHandler(final LabelNode start,
                                          final LabelNode end,
                                          final ExceptionHandler handler)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, handler);

        /**
         * This is the name of the variable that will contain the exception object.
         */
        final String variable = handler.getVariable().getName();

        /**
         * This is the name of the type that this exception-handler handles.
         */
        final String type = Utils.internalName((IDeclaredType) module.imports.resolveReturnType(handler.getType()));

        /**
         * This label marks the entry-point of this try-catch handler.
         */
        final LabelNode CATCHER = new LabelNode();

        /**
         * This object describes the simplified try-catch block created by this exception-handler.
         */
        final TryCatchBlockNode block = new TryCatchBlockNode(start, end, CATCHER, type);

        // Add the exception-handler to the function.
        function.trycatches.add(block);

        // Enter the exception handler.
        code.add(CATCHER);

        // Store the exception object in the appropriate variable.
        vars.store(variable);

        // Execute the exception handling code.
        handler.getBody().accept(this);
    }

    @Override
    public void visit(final ThrowStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        object.getValue().accept(this);

        code.add(new InsnNode(Opcodes.ATHROW));
    }

    @Override
    public void visit(final AssertStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final String descriptor;

        final LabelNode END = new LabelNode();

        compileCondition(object.getCondition());

        code.add(new JumpInsnNode(Utils.IF_TRUE, END));

        code.add(new TypeInsnNode(Opcodes.NEW, "autumn/lang/exceptions/AssertionFailedException"));

        code.add(new InsnNode(Opcodes.DUP));

        code.add(new LdcInsnNode(object.getLocation().getFile().toString()));

        code.add(new LdcInsnNode(object.getLocation().getLine()));

        if (object.getMessage() == null)
        {
            descriptor = "(Ljava/lang/String;I)V";
        }
        else
        {
            descriptor = "(Ljava/lang/String;ILjava/lang/String;)V";

            object.getMessage().accept(this);
        }

        code.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                    "autumn/lang/exceptions/AssertionFailedException",
                                    "<init>",
                                    descriptor));

        code.add(new InsnNode(Opcodes.ATHROW));

        code.add(END);
    }

    @Override
    public void visit(final AssumeStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        final String descriptor;

        final LabelNode END = new LabelNode();

        code.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    "autumn/lang/compiler/Autumn",
                                    "isAssumeOn",
                                    "()Z"));

        code.add(new JumpInsnNode(Utils.IF_FALSE, END));

        compileCondition(object.getCondition());

        code.add(new JumpInsnNode(Utils.IF_TRUE, END));

        code.add(new TypeInsnNode(Opcodes.NEW, "autumn/lang/exceptions/AssumptionFailedException"));

        code.add(new InsnNode(Opcodes.DUP));

        code.add(new LdcInsnNode(object.getLocation().getFile().toString()));

        code.add(new LdcInsnNode(object.getLocation().getLine()));

        if (object.getMessage() == null)
        {
            descriptor = "(Ljava/lang/String;I)V";
        }
        else
        {
            descriptor = "(Ljava/lang/String;ILjava/lang/String;)V";

            object.getMessage().accept(this);
        }

        code.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                    "autumn/lang/exceptions/AssumptionFailedException",
                                    "<init>",
                                    descriptor));

        code.add(new InsnNode(Opcodes.ATHROW));

        code.add(END);
    }

    @Override
    public void visit(final ReturnVoidStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        /**
         * Return from the function immediately.
         */
        code.add(new InsnNode(Opcodes.RETURN));
    }

    @Override
    public void visit(final ReturnValueStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);


        /**
         * Evaluate the expression that produces the value to return.
         */
        object.getValue().accept(this);

        /**
         * Perform auto-boxing or auto-unboxing, if needed.
         */
        convert(function.type.getReturnType(), object.getValue());

        /**
         * Return from the function immediately.
         */
        code.add(Utils.selectReturnInsn(function.type.getReturnType()));
    }

    @Override
    public void visit(final RecurStatement object)
    {
        /**
         * Embed the line number in the bytecode for debugging purposes.
         */
        Utils.addLineNumber(code, object);

        int parameter_index = 0;

        /**
         * Evaluate the arguments and store them in the parameter variables.
         */
        for (IExpression arg : object.getArguments())
        {
            final String parameter = function.node // Get the function's AST node.
                    .getParameters() // Get formal-parameter-list.
                    .getParameters() // From the formal-parameter-list, get the actual list.
                    .asMutableList() // Convert the list, because we need the get(int) method.
                    .get(parameter_index++) // Get the formal-parameter from the list.
                    .getVariable() // We only need the variable, not the type.
                    .getName(); // Get the name of the variable as a string.

            /**
             * Generate the argument's bytecode.
             */
            arg.accept(this);

            /**
             * Generate the bytecode that performs auto-boxing, auto-unboxing, and coercions, if needed.
             */
            code.add(program.typesystem.utils.assign(program.symbols.expressions.get(arg),
                                                     function.allocator.typeOf(parameter)));

            /**
             * Generate the bytecode that assigns the argument to the parameter's local-variable.
             */
            function.vars.store(parameter);
        }

        /**
         * Goto the start of the function.
         */
        code.add(new JumpInsnNode(Opcodes.GOTO, function.recur_label));
    }
}