FunctorCompiler.java

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

import autumn.lang.Functor;
import autumn.lang.compiler.ClassFile;
import autumn.lang.compiler.ast.nodes.FormalParameter;
import autumn.lang.compiler.ast.nodes.FunctorDefinition;
import autumn.lang.internals.ArgumentStack;
import autumn.lang.internals.Helpers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.mackenziehigh.autumn.lang.compiler.typesystem.CustomConstructor;
import com.mackenziehigh.autumn.lang.compiler.typesystem.CustomDeclaredType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.CustomMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IConstructor;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IFormalParameter;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IInterfaceType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVariableType;
import com.mackenziehigh.autumn.lang.compiler.utils.BridgeMethod;
import com.mackenziehigh.autumn.lang.compiler.utils.FunctorAnalyzer;
import com.mackenziehigh.autumn.lang.compiler.utils.TypeSystemUtils;
import com.mackenziehigh.autumn.lang.compiler.utils.Utils;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
 * An instance of this class controls the compilation of a functor-definition.
 *
 * @author Mackenzie High
 */
final class FunctorCompiler
        implements ICompiler
{
    private final ProgramCompiler program;

    private final ModuleCompiler module;

    private final FunctorDefinition node;

    public CustomDeclaredType type;

    /**
     * This will be the type-system representation of the sole constructor.
     */
    private CustomConstructor ctor;

    /**
     * This will be the type-system representation of the invoke(*) method.
     */
    private CustomMethod invoke;

    private final List<IVariableType> parameters = Lists.newLinkedList();

    private FunctorAnalyzer analyzer;

    /**
     * Sole Constructor.
     *
     * @param module is essentially the module that is being compiled.
     * @param node is the AST node that represents the functor-definition.
     */
    public FunctorCompiler(final ModuleCompiler module,
                           final FunctorDefinition node)
    {
        this.module = module;
        this.node = node;

        this.program = module.program;
    }

    /**
     * This method generates the compiled class-file.
     *
     * @return the compiled class-file.
     */
    public ClassFile build()
    {
        final String internal_name = Utils.internalName(type);

        final String source_name = Utils.sourceName(type);

        /**
         * Create the bytecode representation of the enum itself.
         */
        final ClassNode clazz = new ClassNode();
        {
            clazz.version = Opcodes.V1_6;
            clazz.visibleAnnotations = module.anno_utils.compileAnnotationList(type.getAnnotations());
            clazz.access = type.getModifiers();
            clazz.name = internal_name;
            clazz.superName = Utils.internalName(type.getSuperclass());
            clazz.interfaces = program.typesystem.utils.internalNamesOf(type.getSuperinterfaces());
            clazz.fields = ImmutableList.of();
            clazz.methods = Lists.newLinkedList();
            clazz.sourceFile = String.valueOf(node.getLocation().getFile());

            clazz.methods.add(this.generateConstructor());
            clazz.methods.add(this.generateMethodInvoke());
            clazz.methods.add(this.generateMethodParameterTypes());
            clazz.methods.add(this.generateMethodReturnType());
            clazz.methods.addAll(generateBridgeMethods());
        }

        /**
         * Assemble the bytecode into an array of bytes.
         */
        final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        clazz.accept(writer);
        final byte[] bytecode = writer.toByteArray();

        /**
         * Create the class-file object that will store the emitted bytecode.
         */
        final ClassFile file = new ClassFile(source_name, bytecode);

        return file;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void performTypeDeclaration()
    {
        /**
         * Determine the descriptor of the functor.
         */
        final String namespace = module.type.getNamespace().replace('.', '/');
        final String name = node.getName().getName();
        final String descriptor = "L" + namespace + '/' + name + ';';

        /**
         * Ensure that the type was not already declared elsewhere.
         */
        program.checker.requireNonDuplicateType(node.getName(), descriptor);

        /**
         * Declare the functor.
         */
        this.type = program.typesystem.typefactory().newClassType(descriptor);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void performTypeInitialization()
    {
        /**
         * Create the type-system representations of the annotation-list.
         */
        type.setAnnotations(module.anno_utils.typesOf(node.getAnnotations()));

        /**
         * Add a special annotation.
         */
        module.anno_utils.add(type, autumn.lang.internals.annotations.FunctorDefinition.class);

        /**
         * Check the list of annotations.
         */
        program.checker.checkAnnotations(node.getAnnotations(), type.getAnnotations());

        /**
         * Set the type's modifier flags.
         */
        type.setModifiers(Opcodes.ACC_PUBLIC);

        /**
         * Set the superclass of the functor's type.
         */
        if (node.getSuperclass() == null)
        {
            type.setSuperclass(program.typesystem.utils.ABSTRACT_STATIC_FUNCTOR);
        }
        else
        {
            type.setSuperclass(module.imports.resolveClassType(node.getSuperclass()));
        }

        /**
         * Add the constructor to the type.
         */
        type.setConstructors(Lists.newArrayList(typeOfConstructor()));

        /**
         * Add the invoke(*) method to the type.
         */
        type.setMethods(Lists.newArrayList(typeOfMethodInvoke()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void performTypeStructureChecking()
    {
        /**
         * Prevent circular inheritance.
         */
        if (TypeSystemUtils.detectCircularInheritance(type))
        {
            program.checker.reportCircularInheritance(node, type);
        }

        /**
         * The superclass must be a subtype of Functor.
         */
        program.checker.requireDefinedFunctorType(node.getSuperclass(), type.getSuperclass());

        /**
         * Create an object that will be used to find problem with the functor-type.
         */
        this.analyzer = new FunctorAnalyzer(program.typesystem.utils, type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void performTypeUsageChecking()
    {
        // Pass, because no usages need checked.
    }

    /**
     * This method creates the type-system representation of constructor.
     *
     * @return the type of the constructor.
     */
    public IConstructor typeOfConstructor()
    {
        ctor = new CustomConstructor(type.getTypeFactory());

        final IInterfaceType TYPED_FUNCTOR = program.typesystem.utils.TYPED_FUNCTOR;

        final IFormalParameter formal = program.typesystem.utils.formal(TYPED_FUNCTOR);

        ctor.setOwner(type);
        ctor.setAnnotations(new LinkedList());
        ctor.setModifiers(Opcodes.ACC_PUBLIC);
        ctor.setName("<init>");
        ctor.setParameters(Lists.newArrayList(formal));
        ctor.setReturnType(program.typesystem.utils.VOID);
        ctor.setThrowsClause(new LinkedList());

        return ctor;
    }

    /**
     * This method creates the type-system representation of the invoke(*) method.
     *
     * @return the type of the aforesaid method.
     */
    public IMethod typeOfMethodInvoke()
    {
        invoke = new CustomMethod(type.getTypeFactory(), false);

        /**
         * Create the list of formal parameters.
         */
        final List<IFormalParameter> formals = Lists.newLinkedList();

        for (FormalParameter param : node.getParameters().getParameters())
        {
            final IVariableType param_type = module.imports.resolveVariableType(param.getType());

            formals.add(program.typesystem.utils.formal(param_type));

            parameters.add(param_type);
        }

        /**
         * Initialize the type-system representation of the method.
         */
        invoke.setOwner(type);
        invoke.setAnnotations(new LinkedList());
        invoke.setModifiers(Opcodes.ACC_PUBLIC);
        invoke.setName("invoke");
        invoke.setParameters(formals);
        invoke.setReturnType(module.imports.resolveReturnType(node.getReturnType()));
        invoke.setThrowsClause(Lists.newArrayList(program.typesystem.utils.THROWABLE));

        return invoke;
    }

    /**
     * This method generates the bytecode representation of the constructor.
     *
     * @return the constructor's bytecode.
     */
    private MethodNode generateConstructor()
    {
        final MethodNode method = Utils.bytecodeOf(module, ctor);

        /**
         * Invoke the super constructor.
         */
        method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // Load 'this'
        method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // Load param[0]
        method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                                   Utils.internalName(type.getSuperclass()),
                                                   "<init>",
                                                   "(Lautumn/lang/TypedFunctor;)V"));

        /**
         * Return from the constructor.
         */
        method.instructions.add(new InsnNode(Opcodes.RETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the parameterTypes().
     *
     * @return the method's bytecode.
     */
    private MethodNode generateMethodParameterTypes()
    {
        final MethodNode method = new MethodNode();

        method.access = Opcodes.ACC_PUBLIC;
        method.name = "parameterTypes";
        method.desc = "()Ljava/util/List;";
        method.exceptions = ImmutableList.of();

        final CollectionCompiler<IVariableType> cmp = new CollectionCompiler<IVariableType>()
        {
            @Override
            public void compile(final IVariableType element)
            {
                code().add(Utils.ldcClass(element));
            }

            @Override
            public InsnList code()
            {
                return method.instructions;
            }
        };

        /**
         * Generate the bytecode that creates the mutable list of class objects.
         */
        cmp.compile(parameters);

        /**
         * Convert the mutableList to an immutable list.
         */
        Utils.invoke(method.instructions,
                     Opcodes.INVOKESTATIC,
                     Helpers.class,
                     List.class,
                     "newImmutableList",
                     Iterable.class);

        /**
         * Return from the method.
         */
        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the returnType().
     *
     * @return the method's bytecode.
     */
    private MethodNode generateMethodReturnType()
    {
        final MethodNode method = new MethodNode();

        method.access = Opcodes.ACC_PUBLIC;
        method.name = "returnType";
        method.desc = "()Ljava/lang/Class;";
        method.exceptions = ImmutableList.of();

        /**
         * Generate the method's body
         */
        method.instructions.add(Utils.ldcClass(invoke.getReturnType()));
        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method generates the bytecode representation of the invoke(*) method.
     *
     * @return the method's bytecode.
     */
    private MethodNode generateMethodInvoke()
    {
        final MethodNode method = Utils.bytecodeOf(module, invoke);

        /**
         * Get the argument-stack associated with the current thread.
         */
        Utils.loadArgumentStack(method.instructions);

        /**
         * Load each of the formal-parameters onto the argument-stack.
         */
        int address = 1;
        for (IVariableType parameter : parameters)
        {
            method.instructions.add(new InsnNode(Opcodes.DUP));

            method.instructions.add(Utils.selectLoadVarInsn(parameter, address));

            Utils.pushArgument(program, method.instructions, parameter);

            address += Utils.sizeof(parameter);
        }

        /**
         * At this point, there is an ArgumentStack reference on the operand-stack.
         * We will need another later, so duplicate the object-reference.
         */
        method.instructions.add(new InsnNode(Opcodes.DUP));

        /**
         * Invoke the apply(ArgumentStack) method.
         */
        method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
        method.instructions.add(new InsnNode(Opcodes.SWAP));
        Utils.invoke(method.instructions,
                     Opcodes.INVOKEINTERFACE,
                     Functor.class,
                     void.class,
                     "apply",
                     ArgumentStack.class);

        /**
         * Generate the code that will exit the invoke(*) method.
         * If the method returns void, a simply return is sufficient.
         * If a value must be returned, we must pop it off the argument-stack and then return it.
         */
        if (invoke.getReturnType().isVoidType())
        {
            method.instructions.add(new InsnNode(Opcodes.RETURN));
        }
        else
        {
            // Transfer the value to return from the argument-stack to the operand-stack.
            Utils.peekArgument(program, method.instructions, invoke.getReturnType());

            // Clear the argument-stack.
            // Simply retrieving the argument-stack causes it to be cleared.
            Utils.loadArgumentStack(method.instructions);
            method.instructions.add(new InsnNode(Opcodes.POP));

            // Return from the invoke method.
            method.instructions.add(Utils.selectReturnInsn(invoke.getReturnType()));
        }

        return method;
    }

    /**
     * This method generates the bytecode representations of the bridge invoke(*) methods.
     *
     * @return the bytecode of the methods.
     */
    private List<MethodNode> generateBridgeMethods()
    {
        final List<MethodNode> methods = Lists.newLinkedList();

        for (BridgeMethod bridge : analyzer.bridges())
        {
            methods.add(bridge.compile(module));
        }

        return Collections.unmodifiableList(methods);
    }
}