fr.insalyon.citi.golo.compiler.JavaBytecodeGenerationGoloIrVisitor.java Source code

Java tutorial

Introduction

Here is the source code for fr.insalyon.citi.golo.compiler.JavaBytecodeGenerationGoloIrVisitor.java

Source

/*
 * Copyright 2012-2014 Institut National des Sciences Appliques de Lyon (INSA-Lyon)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.insalyon.citi.golo.compiler;

import fr.insalyon.citi.golo.compiler.ir.*;
import fr.insalyon.citi.golo.compiler.parser.GoloParser;
import fr.insalyon.citi.golo.runtime.OperatorType;
import org.objectweb.asm.*;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.*;

import static fr.insalyon.citi.golo.compiler.JavaBytecodeUtils.*;
import static fr.insalyon.citi.golo.compiler.ir.GoloFunction.Visibility.PUBLIC;
import static fr.insalyon.citi.golo.runtime.OperatorType.*;
import static java.lang.invoke.MethodType.genericMethodType;
import static java.lang.invoke.MethodType.methodType;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.*;

class JavaBytecodeGenerationGoloIrVisitor implements GoloIrVisitor {

    private static final String JOBJECT = "java/lang/Object";
    private static final String TOBJECT = "Ljava/lang/Object;";
    private static final Handle FUNCTION_INVOCATION_HANDLE;
    private static final Handle OPERATOR_HANDLE;
    private static final Handle METHOD_INVOCATION_HANDLE;
    private static final Handle CLASSREF_HANDLE;
    private static final Handle CLOSUREREF_HANDLE;
    private static final Handle CLOSURE_INVOCATION_HANDLE;

    static {
        String bootstrapOwner = "fr/insalyon/citi/golo/runtime/FunctionCallSupport";
        String bootstrapMethod = "bootstrap";
        String description = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
        FUNCTION_INVOCATION_HANDLE = new Handle(H_INVOKESTATIC, bootstrapOwner, bootstrapMethod, description);

        bootstrapOwner = "fr/insalyon/citi/golo/runtime/OperatorSupport";
        bootstrapMethod = "bootstrap";
        description = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;";
        OPERATOR_HANDLE = new Handle(H_INVOKESTATIC, bootstrapOwner, bootstrapMethod, description);

        bootstrapOwner = "fr/insalyon/citi/golo/runtime/MethodInvocationSupport";
        bootstrapMethod = "bootstrap";
        description = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;I)Ljava/lang/invoke/CallSite;";
        METHOD_INVOCATION_HANDLE = new Handle(H_INVOKESTATIC, bootstrapOwner, bootstrapMethod, description);

        bootstrapOwner = "fr/insalyon/citi/golo/runtime/ClassReferenceSupport";
        bootstrapMethod = "bootstrap";
        description = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
        CLASSREF_HANDLE = new Handle(H_INVOKESTATIC, bootstrapOwner, bootstrapMethod, description);

        bootstrapOwner = "fr/insalyon/citi/golo/runtime/ClosureReferenceSupport";
        bootstrapMethod = "bootstrap";
        description = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;II)Ljava/lang/invoke/CallSite;";
        CLOSUREREF_HANDLE = new Handle(H_INVOKESTATIC, bootstrapOwner, bootstrapMethod, description);

        bootstrapOwner = "fr/insalyon/citi/golo/runtime/ClosureCallSupport";
        bootstrapMethod = "bootstrap";
        description = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
        CLOSURE_INVOCATION_HANDLE = new Handle(H_INVOKESTATIC, bootstrapOwner, bootstrapMethod, description);
    }

    private ClassWriter classWriter;
    private String klass;
    private String jvmKlass;
    private MethodVisitor methodVisitor;
    private List<CodeGenerationResult> generationResults;
    private String sourceFilename;
    private Context context;

    private static class Context {
        private final Deque<ReferenceTable> referenceTableStack = new LinkedList<>();
        private final Map<LoopStatement, Label> loopStartMap = new HashMap<>();
        private final Map<LoopStatement, Label> loopEndMap = new HashMap<>();
    }

    public List<CodeGenerationResult> generateBytecode(GoloModule module, String sourceFilename) {
        this.sourceFilename = sourceFilename;
        this.classWriter = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
        this.generationResults = new LinkedList<>();
        this.context = new Context();
        module.accept(this);
        this.generationResults
                .add(new CodeGenerationResult(classWriter.toByteArray(), module.getPackageAndClass()));
        return this.generationResults;
    }

    @Override
    public void visitModule(GoloModule module) {
        classWriter.visit(V1_7, ACC_PUBLIC | ACC_SUPER, module.getPackageAndClass().toJVMType(), null, JOBJECT,
                null);
        classWriter.visitSource(sourceFilename, null);
        writeImportMetaData(module.getImports());
        klass = module.getPackageAndClass().toString();
        jvmKlass = module.getPackageAndClass().toJVMType();
        for (GoloFunction function : module.getFunctions()) {
            function.accept(this);
        }
        for (Map.Entry<String, Set<GoloFunction>> entry : module.getAugmentations().entrySet()) {
            generateAugmentationBytecode(module, entry.getKey(), entry.getValue());
        }
        if (module.getStructs().size() > 0) {
            JavaBytecodeStructGenerator structGenerator = new JavaBytecodeStructGenerator();
            for (Struct struct : module.getStructs()) {
                generationResults.add(structGenerator.compile(struct, sourceFilename));
            }
        }
        for (LocalReference moduleState : module.getModuleState()) {
            writeModuleState(moduleState);
        }
        writeAugmentsMetaData(module.getAugmentations().keySet());
        classWriter.visitEnd();
    }

    private void writeModuleState(LocalReference moduleState) {
        String name = moduleState.getName();
        classWriter.visitField(ACC_PRIVATE | ACC_STATIC, name, "Ljava/lang/Object;", null, null).visitEnd();

        MethodVisitor mv = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, name,
                "()Ljava/lang/Object;", null, null);
        mv.visitCode();
        mv.visitFieldInsn(GETSTATIC, jvmKlass, name, "Ljava/lang/Object;");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();

        mv = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, name, "(Ljava/lang/Object;)V", null,
                null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(PUTSTATIC, jvmKlass, name, "Ljava/lang/Object;");
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    private void writeImportMetaData(Set<ModuleImport> imports) {
        ModuleImport[] importsArray = imports.toArray(new ModuleImport[imports.size()]);
        methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, "$imports",
                "()[Ljava/lang/String;", null, null);
        methodVisitor.visitCode();
        loadInteger(methodVisitor, importsArray.length);
        methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/String");
        for (int i = 0; i < importsArray.length; i++) {
            methodVisitor.visitInsn(DUP);
            loadInteger(methodVisitor, i);
            methodVisitor.visitLdcInsn(importsArray[i].getPackageAndClass().toString());
            methodVisitor.visitInsn(AASTORE);
        }
        methodVisitor.visitInsn(ARETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();
    }

    private void writeAugmentsMetaData(Set<String> augmentations) {
        String[] augmentArray = augmentations.toArray(new String[augmentations.size()]);
        methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, "$augmentations",
                "()[Ljava/lang/String;", null, null);
        methodVisitor.visitCode();
        loadInteger(methodVisitor, augmentArray.length);
        methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/String");
        for (int i = 0; i < augmentArray.length; i++) {
            methodVisitor.visitInsn(DUP);
            loadInteger(methodVisitor, i);
            methodVisitor.visitLdcInsn(augmentArray[i]);
            methodVisitor.visitInsn(AASTORE);
        }
        methodVisitor.visitInsn(ARETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();
    }

    private void generateAugmentationBytecode(GoloModule module, String target, Set<GoloFunction> functions) {
        ClassWriter mainClassWriter = classWriter;
        String mangledClass = target.replace('.', '$');
        PackageAndClass packageAndClass = new PackageAndClass(module.getPackageAndClass().packageName(),
                module.getPackageAndClass().className() + "$" + mangledClass);
        String augmentationClassInternalName = packageAndClass.toJVMType();

        String outerName = module.getPackageAndClass().toJVMType();
        mainClassWriter.visitInnerClass(augmentationClassInternalName, outerName, mangledClass,
                ACC_PUBLIC | ACC_STATIC);

        classWriter = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
        classWriter.visit(V1_7, ACC_PUBLIC | ACC_SUPER, augmentationClassInternalName, null, JOBJECT, null);
        classWriter.visitSource(sourceFilename, null);
        classWriter.visitOuterClass(outerName, null, null);

        for (GoloFunction function : functions) {
            function.accept(this);
        }

        Set<ModuleImport> imports = new HashSet<>(module.getImports());
        imports.add(new ModuleImport(module.getPackageAndClass()));
        writeImportMetaData(imports);

        classWriter.visitEnd();
        generationResults.add(new CodeGenerationResult(classWriter.toByteArray(), packageAndClass));
        classWriter = mainClassWriter;
    }

    @Override
    public void visitFunction(GoloFunction function) {
        int accessFlags = (function.getVisibility() == PUBLIC) ? ACC_PUBLIC : ACC_PRIVATE;
        String signature;
        if (function.isMain()) {
            signature = "([Ljava/lang/String;)V";
        } else if (function.isVarargs()) {
            accessFlags = accessFlags | ACC_VARARGS;
            signature = goloVarargsFunctionSignature(function.getArity());
        } else if (function.isModuleInit()) {
            signature = "()V";
        } else {
            signature = goloFunctionSignature(function.getArity());
        }
        if (function.isSynthetic()) {
            accessFlags = accessFlags | ACC_SYNTHETIC;
        }
        methodVisitor = classWriter.visitMethod(accessFlags | ACC_STATIC, function.getName(), signature, null,
                null);
        methodVisitor.visitCode();
        visitLine(function, methodVisitor);
        function.getBlock().accept(this);
        if (function.isModuleInit()) {
            methodVisitor.visitInsn(RETURN);
        }
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();
    }

    private String goloFunctionSignature(int arity) {
        return MethodType.genericMethodType(arity).toMethodDescriptorString();
    }

    private String goloVarargsFunctionSignature(int arity) {
        return MethodType.genericMethodType(arity - 1, true).toMethodDescriptorString();
    }

    @Override
    public void visitBlock(Block block) {
        ReferenceTable referenceTable = block.getReferenceTable();
        context.referenceTableStack.push(referenceTable);
        Label blockStart = new Label();
        Label blockEnd = new Label();
        methodVisitor.visitLabel(blockStart);
        for (GoloStatement statement : block.getStatements()) {
            visitLine(statement, methodVisitor);
            statement.accept(this);
            insertMissingPop(statement);
        }
        methodVisitor.visitLabel(blockEnd);
        for (LocalReference localReference : referenceTable.ownedReferences()) {
            if (localReference.isModuleState()) {
                continue;
            }
            methodVisitor.visitLocalVariable(localReference.getName(), TOBJECT, null, blockStart, blockEnd,
                    localReference.getIndex());
        }
        context.referenceTableStack.pop();
    }

    private void insertMissingPop(GoloStatement statement) {
        Class<? extends GoloStatement> statementClass = statement.getClass();
        if (statementClass == FunctionInvocation.class) {
            methodVisitor.visitInsn(POP);
        } else if (statementClass == BinaryOperation.class) {
            BinaryOperation operation = (BinaryOperation) statement;
            if (isMethodCall(operation)) {
                methodVisitor.visitInsn(POP);
            }
        }
    }

    private boolean isMethodCall(BinaryOperation operation) {
        return operation.getType() == METHOD_CALL || operation.getType() == ELVIS_METHOD_CALL;
    }

    @Override
    public void visitConstantStatement(ConstantStatement constantStatement) {
        Object value = constantStatement.getValue();
        if (value == null) {
            methodVisitor.visitInsn(ACONST_NULL);
            return;
        }
        if (value instanceof Integer) {
            int i = (Integer) value;
            loadInteger(methodVisitor, i);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
            return;
        }
        if (value instanceof Long) {
            long l = (Long) value;
            loadLong(methodVisitor, l);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
            return;
        }
        if (value instanceof Boolean) {
            boolean b = (Boolean) value;
            loadInteger(methodVisitor, b ? 1 : 0);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
            return;
        }
        if (value instanceof String) {
            methodVisitor.visitLdcInsn(value);
            return;
        }
        if (value instanceof Character) {
            loadInteger(methodVisitor, (Character) value);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf",
                    "(C)Ljava/lang/Character;");
            return;
        }
        if (value instanceof GoloParser.ParserClassRef) {
            GoloParser.ParserClassRef ref = (GoloParser.ParserClassRef) value;
            methodVisitor.visitInvokeDynamicInsn(ref.name.replaceAll("\\.", "#"), "()Ljava/lang/Class;",
                    CLASSREF_HANDLE);
            return;
        }
        if (value instanceof GoloParser.FunctionRef) {
            GoloParser.FunctionRef ref = (GoloParser.FunctionRef) value;
            String module = ref.module;
            if (module == null) {
                module = klass;
            }
            methodVisitor.visitLdcInsn(ref.name);
            methodVisitor.visitInvokeDynamicInsn(module.replaceAll("\\.", "#"), "()Ljava/lang/Class;",
                    CLASSREF_HANDLE);
            methodVisitor.visitInvokeDynamicInsn("gololang#Predefined#fun",
                    "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", FUNCTION_INVOCATION_HANDLE);
            return;
        }
        if (value instanceof Double) {
            double d = (Double) value;
            methodVisitor.visitLdcInsn(d);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
            return;
        }
        if (value instanceof Float) {
            float f = (Float) value;
            methodVisitor.visitLdcInsn(f);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
            return;
        }
        throw new IllegalArgumentException("Constants of type " + value.getClass() + " cannot be handled.");
    }

    @Override
    public void visitReturnStatement(ReturnStatement returnStatement) {
        returnStatement.getExpressionStatement().accept(this);
        if (returnStatement.isReturningVoid()) {
            methodVisitor.visitInsn(RETURN);
        } else {
            methodVisitor.visitInsn(ARETURN);
        }

    }

    @Override
    public void visitThrowStatement(ThrowStatement throwStatement) {
        throwStatement.getExpressionStatement().accept(this);
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Throwable");
        methodVisitor.visitInsn(ATHROW);
    }

    private void visitInvocationArguments(AbstractInvocation invocation) {
        for (ExpressionStatement statement : invocation.getArguments()) {
            statement.accept(this);
        }
    }

    @Override
    public void visitFunctionInvocation(FunctionInvocation functionInvocation) {
        if (functionInvocation.isOnReference()) {
            ReferenceTable table = context.referenceTableStack.peek();
            methodVisitor.visitVarInsn(ALOAD, table.get(functionInvocation.getName()).getIndex());
        }
        if (functionInvocation.isOnModuleState()) {
            visitReferenceLookup(new ReferenceLookup(functionInvocation.getName()));
        }
        if (functionInvocation.isAnonymous() || functionInvocation.isOnReference()
                || functionInvocation.isOnModuleState()) {
            methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/invoke/MethodHandle");
            MethodType type = genericMethodType(functionInvocation.getArity() + 1).changeParameterType(0,
                    MethodHandle.class);
            visitInvocationArguments(functionInvocation);
            methodVisitor.visitInvokeDynamicInsn(functionInvocation.getName().replaceAll("\\.", "#"),
                    type.toMethodDescriptorString(), CLOSURE_INVOCATION_HANDLE);
        } else {
            visitInvocationArguments(functionInvocation);
            methodVisitor.visitInvokeDynamicInsn(functionInvocation.getName().replaceAll("\\.", "#"),
                    goloFunctionSignature(functionInvocation.getArity()), FUNCTION_INVOCATION_HANDLE);
        }
        for (FunctionInvocation invocation : functionInvocation.getAnonymousFunctionInvocations()) {
            invocation.accept(this);
        }
    }

    @Override
    public void visitMethodInvocation(MethodInvocation methodInvocation) {
        visitInvocationArguments(methodInvocation);
        methodVisitor.visitInvokeDynamicInsn(methodInvocation.getName().replaceAll("\\.", "#"),
                goloFunctionSignature(methodInvocation.getArity() + 1), METHOD_INVOCATION_HANDLE,
                (Boolean) methodInvocation.isNullSafeGuarded());
        for (FunctionInvocation invocation : methodInvocation.getAnonymousFunctionInvocations()) {
            invocation.accept(this);
        }
    }

    @Override
    public void visitAssignmentStatement(AssignmentStatement assignmentStatement) {
        assignmentStatement.getExpressionStatement().accept(this);
        LocalReference reference = assignmentStatement.getLocalReference();
        if (reference.isModuleState()) {
            methodVisitor.visitInvokeDynamicInsn((klass + "." + reference.getName()).replaceAll("\\.", "#"),
                    "(Ljava/lang/Object;)V", FUNCTION_INVOCATION_HANDLE);
        } else {
            methodVisitor.visitVarInsn(ASTORE, reference.getIndex());
        }
    }

    @Override
    public void visitReferenceLookup(ReferenceLookup referenceLookup) {
        LocalReference reference = referenceLookup.resolveIn(context.referenceTableStack.peek());
        if (reference.isModuleState()) {
            methodVisitor.visitInvokeDynamicInsn((klass + "." + referenceLookup.getName()).replaceAll("\\.", "#"),
                    "()Ljava/lang/Object;", FUNCTION_INVOCATION_HANDLE);
        } else {
            methodVisitor.visitVarInsn(ALOAD, reference.getIndex());
        }
    }

    @Override
    public void visitConditionalBranching(ConditionalBranching conditionalBranching) {
        Label branchingElseLabel = new Label();
        Label branchingExitLabel = new Label();
        conditionalBranching.getCondition().accept(this);
        asmBooleanValue();
        methodVisitor.visitJumpInsn(IFEQ, branchingElseLabel);
        conditionalBranching.getTrueBlock().accept(this);
        if (conditionalBranching.hasFalseBlock()) {
            if (!conditionalBranching.getTrueBlock().hasReturn()) {
                methodVisitor.visitJumpInsn(GOTO, branchingExitLabel);
            }
            methodVisitor.visitLabel(branchingElseLabel);
            conditionalBranching.getFalseBlock().accept(this);
            methodVisitor.visitLabel(branchingExitLabel);
        } else if (conditionalBranching.hasElseConditionalBranching()) {
            if (!conditionalBranching.getTrueBlock().hasReturn()) {
                methodVisitor.visitJumpInsn(GOTO, branchingExitLabel);
            }
            methodVisitor.visitLabel(branchingElseLabel);
            conditionalBranching.getElseConditionalBranching().accept(this);
            methodVisitor.visitLabel(branchingExitLabel);
        } else {
            methodVisitor.visitLabel(branchingElseLabel);
        }
    }

    @Override
    public void visitLoopStatement(LoopStatement loopStatement) {
        // TODO handle init and post statement and potential reference scoping issues
        Label loopStart = new Label();
        Label loopEnd = new Label();
        context.loopStartMap.put(loopStatement, loopStart);
        context.loopEndMap.put(loopStatement, loopEnd);
        if (loopStatement.hasInitStatement()) {
            loopStatement.getInitStatement().accept(this);
        }
        methodVisitor.visitLabel(loopStart);
        loopStatement.getConditionStatement().accept(this);
        asmBooleanValue();
        methodVisitor.visitJumpInsn(IFEQ, loopEnd);
        loopStatement.getBlock().accept(this);
        if (loopStatement.hasPostStatement()) {
            loopStatement.getPostStatement().accept(this);
        }
        methodVisitor.visitJumpInsn(GOTO, loopStart);
        methodVisitor.visitLabel(loopEnd);
    }

    @Override
    public void visitLoopBreakFlowStatement(LoopBreakFlowStatement loopBreakFlowStatement) {
        Label jumpTarget;
        if (LoopBreakFlowStatement.Type.BREAK.equals(loopBreakFlowStatement.getType())) {
            jumpTarget = context.loopEndMap.get(loopBreakFlowStatement.getEnclosingLoop());
        } else {
            jumpTarget = context.loopStartMap.get(loopBreakFlowStatement.getEnclosingLoop());
        }
        methodVisitor.visitLdcInsn(0);
        methodVisitor.visitJumpInsn(IFEQ, jumpTarget);
        // NOP + ATHROW invalid frames if the GOTO is followed by an else branch code...
        // methodVisitor.visitJumpInsn(GOTO, jumpTarget);
    }

    @Override
    public void visitCollectionLiteral(CollectionLiteral collectionLiteral) {
        // TODO generate bytecode for collections
        switch (collectionLiteral.getType()) {
        case tuple:
            createTuple(collectionLiteral);
            break;
        case array:
            createArray(collectionLiteral);
            break;
        case list:
            createList(collectionLiteral);
            break;
        case vector:
            createVector(collectionLiteral);
            break;
        case set:
            createSet(collectionLiteral);
            break;
        case map:
            createMap(collectionLiteral);
            break;
        default:
            throw new UnsupportedOperationException(
                    "Can't handle collections of type " + collectionLiteral.getType() + " yet");
        }
    }

    private void createMap(CollectionLiteral collectionLiteral) {
        methodVisitor.visitTypeInsn(NEW, "java/util/LinkedHashMap");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/LinkedHashMap", "<init>", "()V");
        for (ExpressionStatement expression : collectionLiteral.getExpressions()) {
            methodVisitor.visitInsn(DUP);
            expression.accept(this);
            methodVisitor.visitTypeInsn(CHECKCAST, "gololang/Tuple");
            methodVisitor.visitInsn(DUP);
            loadInteger(methodVisitor, 0);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "gololang/Tuple", "get", "(I)Ljava/lang/Object;");
            methodVisitor.visitInsn(SWAP);
            loadInteger(methodVisitor, 1);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "gololang/Tuple", "get", "(I)Ljava/lang/Object;");
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/util/LinkedHashMap", "put",
                    "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
            methodVisitor.visitInsn(POP);
        }
    }

    private void createSet(CollectionLiteral collectionLiteral) {
        methodVisitor.visitTypeInsn(NEW, "java/util/LinkedHashSet");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/LinkedHashSet", "<init>", "()V");
        for (ExpressionStatement expression : collectionLiteral.getExpressions()) {
            methodVisitor.visitInsn(DUP);
            expression.accept(this);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/util/LinkedHashSet", "add", "(Ljava/lang/Object;)Z");
            methodVisitor.visitInsn(POP);
        }
    }

    private void createVector(CollectionLiteral collectionLiteral) {
        methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");
        methodVisitor.visitInsn(DUP);
        loadInteger(methodVisitor, collectionLiteral.getExpressions().size());
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "(I)V");
        for (ExpressionStatement expression : collectionLiteral.getExpressions()) {
            methodVisitor.visitInsn(DUP);
            expression.accept(this);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/util/ArrayList", "add", "(Ljava/lang/Object;)Z");
            methodVisitor.visitInsn(POP);
        }
    }

    private void createList(CollectionLiteral collectionLiteral) {
        methodVisitor.visitTypeInsn(NEW, "java/util/LinkedList");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/LinkedList", "<init>", "()V");
        for (ExpressionStatement expression : collectionLiteral.getExpressions()) {
            methodVisitor.visitInsn(DUP);
            expression.accept(this);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/util/LinkedList", "add", "(Ljava/lang/Object;)Z");
            methodVisitor.visitInsn(POP);
        }
    }

    private void createArray(CollectionLiteral collectionLiteral) {
        loadInteger(methodVisitor, collectionLiteral.getExpressions().size());
        methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
        int i = 0;
        for (ExpressionStatement expression : collectionLiteral.getExpressions()) {
            methodVisitor.visitInsn(DUP);
            loadInteger(methodVisitor, i);
            expression.accept(this);
            methodVisitor.visitInsn(AASTORE);
            i = i + 1;
        }
    }

    private void createTuple(CollectionLiteral collectionLiteral) {
        methodVisitor.visitTypeInsn(NEW, "gololang/Tuple");
        methodVisitor.visitInsn(DUP);
        createArray(collectionLiteral);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "gololang/Tuple", "<init>", "([Ljava/lang/Object;)V");
    }

    @Override
    public void visitTryCatchFinally(TryCatchFinally tryCatchFinally) {
        Label tryStart = new Label();
        Label tryEnd = new Label();
        Label catchStart = new Label();
        Label catchEnd = new Label();

        Label rethrowStart = null;
        Label rethrowEnd = null;
        if (tryCatchFinally.isTryCatchFinally()) {
            rethrowStart = new Label();
            rethrowEnd = new Label();
        }

        methodVisitor.visitLabel(tryStart);
        tryCatchFinally.getTryBlock().accept(this);
        if (tryCatchFinally.isTryCatch() || tryCatchFinally.isTryCatchFinally()) {
            methodVisitor.visitJumpInsn(GOTO, catchEnd);
        }
        methodVisitor.visitTryCatchBlock(tryStart, tryEnd, catchStart, null);
        methodVisitor.visitLabel(tryEnd);

        if (tryCatchFinally.isTryFinally()) {
            tryCatchFinally.getFinallyBlock().accept(this);
            methodVisitor.visitJumpInsn(GOTO, catchEnd);
        }

        if (tryCatchFinally.isTryCatchFinally()) {
            methodVisitor.visitTryCatchBlock(catchStart, catchEnd, rethrowStart, null);
        }

        methodVisitor.visitLabel(catchStart);
        if (tryCatchFinally.isTryCatch() || tryCatchFinally.isTryCatchFinally()) {
            Block catchBlock = tryCatchFinally.getCatchBlock();
            int exceptionRefIndex = catchBlock.getReferenceTable().get(tryCatchFinally.getExceptionId()).getIndex();
            methodVisitor.visitVarInsn(ASTORE, exceptionRefIndex);
            tryCatchFinally.getCatchBlock().accept(this);
        } else {
            tryCatchFinally.getFinallyBlock().accept(this);
            methodVisitor.visitInsn(ATHROW);
        }
        methodVisitor.visitLabel(catchEnd);

        if (tryCatchFinally.isTryCatchFinally()) {
            tryCatchFinally.getFinallyBlock().accept(this);
            methodVisitor.visitJumpInsn(GOTO, rethrowEnd);
            methodVisitor.visitLabel(rethrowStart);
            tryCatchFinally.getFinallyBlock().accept(this);
            methodVisitor.visitInsn(ATHROW);
            methodVisitor.visitLabel(rethrowEnd);
        }
    }

    @Override
    public void visitClosureReference(ClosureReference closureReference) {
        GoloFunction target = closureReference.getTarget();
        boolean isVarArgs = target.isVarargs();
        int arity = (isVarArgs) ? target.getArity() - 1 : target.getArity();
        methodVisitor.visitInvokeDynamicInsn(target.getName(),
                methodType(MethodHandle.class).toMethodDescriptorString(), CLOSUREREF_HANDLE, klass,
                (Integer) arity, (Boolean) isVarArgs);
        final int syntheticCount = closureReference.getTarget().getSyntheticParameterCount();
        if (syntheticCount > 0) {
            ReferenceTable table = context.referenceTableStack.peek();
            String[] refs = closureReference.getCapturedReferenceNames().toArray(new String[syntheticCount]);
            loadInteger(methodVisitor, 0);
            loadInteger(methodVisitor, syntheticCount);
            methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
            for (int i = 0; i < syntheticCount; i++) {
                methodVisitor.visitInsn(DUP);
                loadInteger(methodVisitor, i);
                methodVisitor.visitVarInsn(ALOAD, table.get(refs[i]).getIndex());
                methodVisitor.visitInsn(AASTORE);
            }
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "insertArguments",
                    "(Ljava/lang/invoke/MethodHandle;I[Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;");
            if (isVarArgs) {
                methodVisitor.visitLdcInsn(Type.getType(Object[].class));
                methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "asVarargsCollector",
                        "(Ljava/lang/Class;)Ljava/lang/invoke/MethodHandle;");
            }
        }
    }

    @Override
    public void visitBinaryOperation(BinaryOperation binaryOperation) {
        OperatorType operatorType = binaryOperation.getType();
        if (AND.equals(operatorType)) {
            andOperator(binaryOperation);
        } else if (OR.equals(operatorType)) {
            orOperator(binaryOperation);
        } else {
            genericBinaryOperator(binaryOperation, operatorType);
        }
    }

    private void genericBinaryOperator(BinaryOperation binaryOperation, OperatorType operatorType) {
        binaryOperation.getLeftExpression().accept(this);
        binaryOperation.getRightExpression().accept(this);
        if (!isMethodCall(binaryOperation)) {
            String name = operatorType.name().toLowerCase();
            methodVisitor.visitInvokeDynamicInsn(name, goloFunctionSignature(2), OPERATOR_HANDLE, (Integer) 2);
        }
    }

    private void orOperator(BinaryOperation binaryOperation) {
        Label exitLabel = new Label();
        Label trueLabel = new Label();
        binaryOperation.getLeftExpression().accept(this);
        asmBooleanValue();
        methodVisitor.visitJumpInsn(IFNE, trueLabel);
        binaryOperation.getRightExpression().accept(this);
        asmBooleanValue();
        methodVisitor.visitJumpInsn(IFNE, trueLabel);
        asmFalseObject();
        methodVisitor.visitJumpInsn(GOTO, exitLabel);
        methodVisitor.visitLabel(trueLabel);
        asmTrueObject();
        methodVisitor.visitLabel(exitLabel);
    }

    private void andOperator(BinaryOperation binaryOperation) {
        Label exitLabel = new Label();
        Label falseLabel = new Label();
        binaryOperation.getLeftExpression().accept(this);
        asmBooleanValue();
        methodVisitor.visitJumpInsn(IFEQ, falseLabel);
        binaryOperation.getRightExpression().accept(this);
        asmBooleanValue();
        methodVisitor.visitJumpInsn(IFEQ, falseLabel);
        asmTrueObject();
        methodVisitor.visitJumpInsn(GOTO, exitLabel);
        methodVisitor.visitLabel(falseLabel);
        asmFalseObject();
        methodVisitor.visitLabel(exitLabel);
    }

    private void asmFalseObject() {
        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;");
    }

    private void asmTrueObject() {
        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;");
    }

    private void asmBooleanValue() {
        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
    }

    @Override
    public void visitUnaryOperation(UnaryOperation unaryOperation) {
        String name = unaryOperation.getType().name().toLowerCase();
        unaryOperation.getExpressionStatement().accept(this);
        methodVisitor.visitInvokeDynamicInsn(name, goloFunctionSignature(1), OPERATOR_HANDLE, (Integer) 1);
    }
}