Package loop

Source Code of loop.AsmCodeEmitter$SourceLocation

package loop;

import loop.ast.*;
import loop.ast.script.ArgDeclList;
import loop.ast.script.FunctionDecl;
import loop.ast.script.Unit;
import loop.runtime.Closure;
import loop.runtime.Scope;
import loop.runtime.regex.NamedPattern;
import org.objectweb.asm.*;
import org.objectweb.asm.util.TraceClassVisitor;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
* @author dhanji@gmail.com (Dhanji R. Prasanna)
*/
@SuppressWarnings({"FieldCanBeLocal"}) public class AsmCodeEmitter implements Opcodes {
  private static final boolean printBytecode = true|| System.getProperty("print_bytecode") != null;
  private static final AtomicInteger functionNameSequence = new AtomicInteger();

  private static final String IS_LIST_VAR_PREFIX = "__$isList_";
  private static final String RUNTIME_LIST_SIZE_VAR_PREFIX = "__$runtimeListSize_";
  private static final String RUNTIME_STR_LEN_PREFIX = "__$str_len_";
  private static final String IS_STRING_PREFIX = "__$isStr_";
  private static final String IS_READER_PREFIX = "__$isRdr_";
  private static final String WHERE_SCOPE_FN_PREFIX = "$wh$";

  private final Stack<Context> functionStack = new Stack<Context>();

  private final Scope scope;

  public static class SourceLocation implements Comparable<SourceLocation> {
    public final int line;
    public final int column;

    public SourceLocation(int line, int column) {
      this.line = line;
      this.column = column;
    }

    @Override
    public int compareTo(SourceLocation that) {
      return (this.line * 1000000 + this.column) - (that.line * 1000000 + that.column);
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      SourceLocation that = (SourceLocation) o;

      return column == that.column && line == that.line;

    }

    @Override
    public int hashCode() {
      return 31 * line + column;
    }
  }

  private final Map<Class<?>, Emitter> EMITTERS = new HashMap<Class<?>, Emitter>();

  AsmCodeEmitter(Scope scope) {
    this.scope = scope;

    EMITTERS.put(Call.class, callEmitter);
    EMITTERS.put(Dereference.class, dereferenceEmitter);
    EMITTERS.put(Computation.class, computationEmitter);
    EMITTERS.put(IntLiteral.class, intEmitter);
    EMITTERS.put(FloatLiteral.class, floatEmitter);
    EMITTERS.put(LongLiteral.class, longEmitter);
    EMITTERS.put(DoubleLiteral.class, doubleEmitter);
    EMITTERS.put(BigIntegerLiteral.class, bigIntegerEmitter);
    EMITTERS.put(BigDecimalLiteral.class, bigDecimalEmitter);
    EMITTERS.put(BooleanLiteral.class, booleanEmitter);
    EMITTERS.put(TypeLiteral.class, typeLiteralEmitter);
    EMITTERS.put(Variable.class, variableEmitter);
    EMITTERS.put(JavaLiteral.class, javaLiteralEmitter);
    EMITTERS.put(BinaryOp.class, binaryOpEmitter);
    EMITTERS.put(StringLiteral.class, stringLiteralEmitter);
    EMITTERS.put(RegexLiteral.class, regexLiteralEmitter);
    EMITTERS.put(Assignment.class, assignmentEmitter);
    EMITTERS.put(InlineMapDef.class, inlineMapEmitter);
    EMITTERS.put(InlineListDef.class, inlineListEmitter);
    EMITTERS.put(IndexIntoList.class, indexIntoListEmitter);
    EMITTERS.put(CallChain.class, callChainEmitter);
    EMITTERS.put(FunctionDecl.class, functionDeclEmitter);
    EMITTERS.put(ArgDeclList.class, argDeclEmitter);
    EMITTERS.put(PrivateField.class, privateFieldEmitter);
    EMITTERS.put(PatternRule.class, patternRuleEmitter);
    EMITTERS.put(TernaryIfExpression.class, ternaryExpressionEmitter);
    EMITTERS.put(TernaryUnlessExpression.class, ternaryExpressionEmitter);
    EMITTERS.put(Comprehension.class, comprehensionEmitter);
    EMITTERS.put(ConstructorCall.class, constructorCallEmitter);
    EMITTERS.put(ListRange.class, inlineListRangeEmitter);
  }

  private final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
  private final Stack<MethodVisitor> methodStack = new Stack<MethodVisitor>();

  public Class<?> write(Unit unit) {
    Thread.currentThread().setContextClassLoader(LoopClassLoader.CLASS_LOADER);

    // We always emit functions as static into a containing Java class.
    String javaClass = unit.name();

    String fileName = unit.getFileName();
    if (fileName != null) {
      if (!fileName.endsWith(".loop"))
        fileName += ".loop";
      classWriter.visitSource(fileName, null);
    }
    classWriter.visit(V1_6, ACC_PUBLIC, javaClass, null, "java/lang/Object", new String[0]);

    for (FunctionDecl functionDecl : unit.functions()) {
      emit(functionDecl);
    }

    // Emit any static initializer here.
    if (unit.initializer() != null)
      emitInitializerBlock(unit.initializer());

    classWriter.visitEnd();

    if (printBytecode) {
      try {
        new ClassReader(new ByteArrayInputStream(classWriter.toByteArray())).accept(
            new TraceClassVisitor(new PrintWriter(System.out)), ClassReader.SKIP_DEBUG);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    LoopClassLoader.CLASS_LOADER.put(javaClass, classWriter.toByteArray());
    try {
      return LoopClassLoader.CLASS_LOADER.findClass(javaClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  private void emitInitializerBlock(List<Node> exprs) {
    MethodVisitor initializer = classWriter.visitMethod(ACC_STATIC,
        "<clinit>",
        "()V",
        null,
        null);

    methodStack.push(initializer);
    Context context = new Context(FunctionDecl.STATIC_INITIALIZER);
    functionStack.push(context);
    scope.pushScope(context);
    for (Node expr : exprs) {
      emit(expr);
      initializer.visitInsn(POP);
    }

    initializer.visitInsn(RETURN);
    initializer.visitMaxs(1, 0);
    initializer.visitEnd();
    scope.popScope();
    functionStack.pop();
    methodStack.pop();
  }

  private void trackLineAndColumn(Node node) {
    Label line = new Label();
    methodStack.peek().visitLabel(line);
    methodStack.peek().visitLineNumber(node.sourceLine, line);
  }

  private void emitChildren(Node node) {
    for (Node child : node.children()) {
      emit(child);
    }
  }

  private void emitOnlyChild(Node node) {
    emit(node.children().get(0));
  }

  public void emit(Node node) {
    if (!EMITTERS.containsKey(node.getClass()))
      throw new RuntimeException("Missing emitter for " + node.getClass().getSimpleName());
    EMITTERS.get(node.getClass()).emitCode(node);
  }

  // -------------------------------------------------------------------
  // EMITTERS ----------------------------------------------------------
  // -------------------------------------------------------------------

  private final Emitter ternaryExpressionEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      boolean unless = node instanceof TernaryUnlessExpression;
      MethodVisitor methodVisitor = methodStack.peek();

      Label elseBranch = new Label();
      Label end = new Label();

      // If condition
      emit(node.children().get(0));
      methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");

      // Flip the clauses in an "unless" expression.
      if (unless)
        methodVisitor.visitJumpInsn(IFNE, elseBranch);
      else
        methodVisitor.visitJumpInsn(IFEQ, elseBranch);

      emit(node.children().get(1));
      methodVisitor.visitJumpInsn(GOTO, end);

      methodVisitor.visitLabel(elseBranch);
      emit(node.children().get(2));
      methodVisitor.visitLabel(end);
    }
  };

  private final Emitter computationEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      trackLineAndColumn(node);
      emitChildren(node);
    }
  };

  private final Emitter dereferenceEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      Dereference dereference = (Dereference) node;
      trackLineAndColumn(dereference);

      MethodVisitor methodVisitor = methodStack.peek();
      methodVisitor.visitLdcInsn(dereference.name());

      // Special form to call on a java type rather than lookup by class name.
      if (dereference.isJavaStatic()) {
        methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "getStatic",
            "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Object;");
      } else {
        // If JDK7, use invokedynamic instead for better performance.
        methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "dereference",
            "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;");
      }
    }
  };

  private final Emitter callEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      Call call = (Call) node;
      trackLineAndColumn(call);

      MethodVisitor methodVisitor = methodStack.peek();
      Context context = functionStack.peek();

      FunctionDecl resolvedFunction;
      if (call.namespace() != null)
        resolvedFunction = scope.resolveNamespacedFunction(call.name(), call.namespace());
      else
        resolvedFunction = call.callJava() ? null : scope.resolveFunctionOnStack(call.name());

      // All Loop functions are Java static.
      boolean isStatic = resolvedFunction != null, isClosure = false;

      // Is this a tail-recursive function?
      boolean isTailRecursive = call.isTailCall()
          && call.namespace() == null
          && !call.isJavaStatic()
          && context.thisFunction.equals(resolvedFunction);

      // The parse-tree knows if we are calling a java method statically.
      if (!isStatic)
        isStatic = call.isJavaStatic();

      // This is a special invocation so we emit it without the dot.
      String name;
      if (Closure.CALL_FORM.equals(call.name())) {
        name = "";

        isStatic = true;
        isClosure = true;
      } else if (isStatic && resolvedFunction != null) {
        name = normalizeMethodName(resolvedFunction.scopedName());
      } else
        name = normalizeMethodName(call.name());

      // No such function exists and it's not a method-call, so let's treat this as a closure call.
      if (resolvedFunction == null && !isClosure && !call.isJavaStatic() && isStatic) {
        name = "";

        isClosure = true;
        isStatic = true;
      }

      // Compute if we should "call as postfix method" (can be overridden with <- operator)
      List<Node> arguments = call.args().children();
      int callAsPostfixVar = -1;

      boolean callAsPostfix = false;
      int argSize = arguments.size();
      if (resolvedFunction != null && resolvedFunction.arguments() != null) {
        // The actual call is 1 less argument than the function takes, so this is a
        // "call-as-method" syntax.
        if (resolvedFunction.arguments().children().size() - argSize == 1) {
          callAsPostfix = true;
          callAsPostfixVar = context.localVarIndex(context.newLocalVariable());
          argSize++;

          // Save the top of the stack for use as the first argument.
          methodVisitor.visitVarInsn(ASTORE, callAsPostfixVar);
        }
      }

      // TAIL CALL ELIMINATION:
      // Store the call-args into the args of this function and short-circuit the call.
      if (isTailRecursive) {
        List<Node> children = call.args().children();
        for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
          Node arg = children.get(i);
          emit(arg);                                        // value

          // Store into the local vars representing the arguments to the recursive function.
          methodVisitor.visitVarInsn(ASTORE, i);
        }

        // If there's anything left, pop it off. If some expression results in void on stack
        // rather than null or 0, then we might be screwed.
        // Pattern matching functions are well behaved (i.e. not multiline, so leave them alone)
        if (!context.thisFunction.patternMatching && context.thisFunction.children().size() > 1) {
          for (int i = 1; i < context.thisFunction.children().size(); i++) {
            methodVisitor.visitInsn(POP);
          }
        }

        // Loop.
        methodVisitor.visitJumpInsn(GOTO, context.startOfFunction);

        return;
      }

      // push name of containing type if this is a static call.
      boolean isExternalFunction = resolvedFunction != null
          && resolvedFunction.moduleName != null
          && !scope.getModuleName().equals(resolvedFunction.moduleName);

      if (isStatic && !call.isJavaStatic()) {
        if (isClosure)
          methodVisitor.visitTypeInsn(CHECKCAST, "loop/runtime/Closure");

        if (!isExternalFunction)
          methodVisitor.visitLdcInsn(scope.getModuleName());
      }

      // push method name onto stack
      if (!isClosure) {
        // Emit the module name of the containing class for the resolved function, BUT only
        // if it is not in the same module as us.
        if (isExternalFunction) {
          methodVisitor.visitLdcInsn(resolvedFunction.moduleName);
        }

        methodVisitor.visitLdcInsn(name);
      }

      if (argSize > 0) {
        int arrayVar = context.localVarIndex(context.newLocalVariable());

        // push args as array.
        methodVisitor.visitIntInsn(BIPUSH, argSize);       // size of array
        methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
        methodVisitor.visitVarInsn(ASTORE, arrayVar);
        int i = 0;

        if (callAsPostfix) {
          methodVisitor.visitVarInsn(ALOAD, arrayVar);          // array
          methodVisitor.visitIntInsn(BIPUSH, i);                // index
          methodVisitor.visitVarInsn(ALOAD, callAsPostfixVar)// value
          methodVisitor.visitInsn(AASTORE);

          i++;
        }

        for (Node arg : arguments) {
          methodVisitor.visitVarInsn(ALOAD, arrayVar);      // array
          methodVisitor.visitIntInsn(BIPUSH, i);            // index
          emit(arg);                                        // value

          methodVisitor.visitInsn(AASTORE);
          i++;
        }

        // Load the array back in.
        methodVisitor.visitVarInsn(ALOAD, arrayVar);

        if (isStatic) {
          // If JDK7, use invokedynamic instead for better performance.
          if (isClosure)
            methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "callClosure",
                "(Lloop/runtime/Closure;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
          else {
            // Special form to call on a java type rather than lookup by class name.
            if (call.callJava()) {
              methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "callStatic",
                "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
            } else {
              methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "callStatic",
                  "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
            }
          }

        } else {
          // If JDK7, use invokedynamic instead for better performance.
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "call",
              "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
        }
      } else {
        if (isStatic) {
          // If JDK7, use invokedynamic instead for better performance.
          if (isClosure)
            methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "callClosure",
                "(Lloop/runtime/Closure;Ljava/lang/String;)Ljava/lang/Object;");
          else {
            // Special form to call on a java type rather than lookup by class name.
            if (call.callJava()) {
              methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "callStatic",
                  "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Object;");
            } else {
              methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "callStatic",
                  "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;");
            }
          }

        } else {
          // If JDK7, use invokedynamic instead for better performance.
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "call",
              "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;");
        }
      }
    }
  };

  private final Emitter constructorCallEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      ConstructorCall call = (ConstructorCall) node;
      MethodVisitor methodVisitor = methodStack.peek();
      Context context = functionStack.peek();

      // Try to resolve as an aliased dep first.
      ClassDecl classDecl = null;
      if (call.modulePart != null && call.modulePart.split("[.]").length == 1)
        classDecl = scope.resolveAliasedType(
            call.modulePart.substring(0, call.modulePart.length() - 1),
            call.name);

      // Resolve a loop type internally. Note that this makes dynamic linking
      // of Loop types impossible, but we CAN link Java binaries dynamically.
      if (classDecl == null)
        classDecl = scope.resolve(call.name, true);
      if (classDecl != null) {

        // Instatiate the loop object first. With the correct type
        int objectVar = context.localVarIndex(context.newLocalVariable());

        methodVisitor.visitTypeInsn(NEW, "loop/lang/LoopObject");
        methodVisitor.visitInsn(DUP);

        methodVisitor.visitTypeInsn(NEW, "loop/lang/LoopClass");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitLdcInsn(classDecl.name);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "loop/lang/LoopClass", "<init>",
            "(Ljava/lang/String;)V");

        methodVisitor.visitMethodInsn(INVOKESPECIAL, "loop/lang/LoopObject", "<init>",
            "(Lloop/lang/LoopClass;)V");
        methodVisitor.visitVarInsn(ASTORE, objectVar);


        Map<String, Node> fields = new HashMap<String, Node>();
        for (Node nodeAssign : classDecl.children()) {
          if (nodeAssign instanceof Assignment) {
            Assignment assignment = (Assignment) nodeAssign;
            fields.put(((Variable) assignment.lhs()).name, assignment.rhs());
          }
        }

        List<Node> children = call.args().children();
        if (!children.isEmpty() || !fields.isEmpty()) {

          // First emit named-args as overrides of defaults.
          for (Node child : children) {
            CallArguments.NamedArg arg = (CallArguments.NamedArg) child;

            methodVisitor.visitVarInsn(ALOAD, objectVar);
            methodVisitor.visitLdcInsn(arg.name);
            emit(arg.arg);
            methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put",
                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

            // Puts return crap which we need to discard.
            methodVisitor.visitInsn(POP);
          }

          // Now emit any remaining defaults.
          for (Map.Entry<String, Node> field : fields.entrySet()) {
            methodVisitor.visitVarInsn(ALOAD, objectVar);
            methodVisitor.visitLdcInsn(field.getKey());
            emit(field.getValue());
            methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put",
                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

            // Puts return crap which we need to discard.
            methodVisitor.visitInsn(POP);
          }
        }

        // Leave the object on the stack.
        methodVisitor.visitVarInsn(ALOAD, objectVar);

        // Should we freeze this object?
        if (classDecl.immutable) {
          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "loop/lang/LoopObject", "immutize",
              "()Lloop/lang/ImmutableLoopObject;");

          // Overwrite the old var in case there is a reference later on.
          methodVisitor.visitVarInsn(ASTORE, objectVar);
          methodVisitor.visitVarInsn(ALOAD, objectVar);
        }

      } else {
        // Emit Java constructor call.
        String javaType;
        if (call.modulePart != null)
          javaType = call.modulePart + call.name;
        else
          javaType = scope.resolveJavaType(call.name);

        boolean isNullary = call.args().children().isEmpty();
        if (!isNullary) {
          int arrayVar = context.localVarIndex(context.newLocalVariable());
          methodVisitor.visitIntInsn(BIPUSH, call.args().children().size());       // size of array
          methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
          methodVisitor.visitVarInsn(ASTORE, arrayVar);

          int i = 0;
          for (Node arg : call.args().children()) {
            methodVisitor.visitVarInsn(ALOAD, arrayVar);      // array
            methodVisitor.visitIntInsn(BIPUSH, i);            // index
            emit(arg);                                        // value

            methodVisitor.visitInsn(AASTORE);
            i++;
          }

          // push type and constructor arg array.
          methodVisitor.visitLdcInsn(javaType);
          methodVisitor.visitVarInsn(ALOAD, arrayVar);

          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "instantiate",
              "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
        } else {
          // Otherwise invoke the Java constructor directly. We don't need to resolve it.
          // This is an optimization.
          javaType = javaType.replace('.', '/');
          methodVisitor.visitTypeInsn(NEW, javaType);
          methodVisitor.visitInsn(DUP);
          methodVisitor.visitMethodInsn(INVOKESPECIAL, javaType, "<init>", "()V");
        }
      }
    }
  };

  private final Emitter binaryOpEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      BinaryOp binaryOp = (BinaryOp) node;
      emitOnlyChild(binaryOp);

      MethodVisitor methodVisitor = methodStack.peek();
      switch (binaryOp.operator.kind) {
        case PLUS:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "plus",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
          break;
        case MINUS:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "minus",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
          break;
        case STAR:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "multiply",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
          break;
        case DIVIDE:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "divide",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
          break;
        case MODULUS:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "remainder",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
          break;
        case LESSER:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "lesserThan",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
          break;
        case LEQ:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "lesserThanOrEqual",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
          break;
        case GREATER:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "greaterThan",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
          break;
        case GEQ:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations",
              "greaterThanOrEqual",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
          break;
        case EQUALS:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "equal",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
          break;
        case AND:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "and",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
//          methodVisitor.visitInsn(IAND);
          break;
        case OR:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "or",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
//          methodVisitor.visitInsn(IOR);
          break;
        case NOT:
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "notEqual",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
          break;
        case IN: // HACK for instanceof
          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "isInstance",
              "(Ljava/lang/Object;)Z");
          break;
        default:
          throw new UnsupportedOperationException(
              "Unsupported binary operator " + binaryOp.toSymbol());
      }
    }
  };

  private final Emitter assignmentEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      Assignment assignment = (Assignment) node;
      trackLineAndColumn(assignment);
      Context context = functionStack.peek();
      MethodVisitor methodVisitor = methodStack.peek();

      if (assignment.lhs() instanceof Variable) {
        int lhsVar = context.localVarIndex(context.newLocalVariable((Variable) assignment.lhs()));
        emit(assignment.rhs());

        // Leave the result of the assignment on the stack.
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitVarInsn(ASTORE, lhsVar);
      } else if (assignment.lhs() instanceof CallChain) {
        // this is a setter/put style assignment.
        CallChain lhs = (CallChain) assignment.lhs();
        List<Node> children = lhs.children();
        Node last = children.remove(children.size() - 1);

        if (last instanceof IndexIntoList) {
          // The object to assign into.
          emit(lhs);

          // The slot where this assignment will go.
          emit(((IndexIntoList) last).from());

          // The value to assign.
          emit(assignment.rhs());
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Collections", "store",
              "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

        } else if (last instanceof MemberAccess) {
          MemberAccess call = (MemberAccess) last;
          if (call instanceof Call)
            throw new RuntimeException("Cannot assign value to a function call");

          // The object to assign into.
          emit(lhs);

          // The slot where this assignment will go.
          methodVisitor.visitLdcInsn(call.name());

          // The value to assign.
          emit(assignment.rhs());
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Collections", "store",
              "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

        } else
          throw new RuntimeException("Can only assign a value to variable or object property");
      }
    }
  };

  private final Emitter variableEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      Variable var = (Variable) node;

      Context context = functionStack.peek();
      Integer index = context.argumentIndex.get(var.name);
      if (index == null)
        index = context.localVarIndex(var.name);

      if (index == null) {
        // It could be a function-reference. In which case emit it as a closure.
        FunctionDecl functionDecl = scope.resolveFunctionOnStack(var.name);
        if (functionDecl != null) {
          MethodVisitor methodVisitor = methodStack.peek();
          methodVisitor.visitTypeInsn(NEW, "loop/runtime/Closure");
          methodVisitor.visitInsn(DUP);
          methodVisitor.visitLdcInsn(functionDecl.moduleName);
          methodVisitor.visitLdcInsn(functionDecl.scopedName());
          methodVisitor.visitMethodInsn(INVOKESPECIAL, "loop/runtime/Closure", "<init>",
              "(Ljava/lang/String;Ljava/lang/String;)V");

        }
      } else
        methodStack.peek().visitVarInsn(ALOAD, index);
    }
  };

  private static String normalizeMethodName(String name) {
    return name.replaceFirst("@", "__");
  }

  private final Emitter intEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      IntLiteral intLiteral = (IntLiteral) node;

      // Emit int wrappers.
      MethodVisitor methodVisitor = methodStack.peek();
      methodVisitor.visitLdcInsn(intLiteral.value);
      methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf",
          "(I)Ljava/lang/Integer;");

    }
  };

  private final Emitter floatEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      FloatLiteral floatLiteral = (FloatLiteral) node;

      // Emit float wrappers.
      MethodVisitor methodVisitor = methodStack.peek();
      methodVisitor.visitLdcInsn(floatLiteral.value);
      methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf",
          "(F)Ljava/lang/Float;");
    }
  };

  private final Emitter longEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      LongLiteral longLiteral = (LongLiteral) node;

      // Emit long wrappers.
      MethodVisitor methodVisitor = methodStack.peek();
      methodVisitor.visitLdcInsn(longLiteral.value);
      methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf",
          "(J)Ljava/lang/Long;");
    }
  };

  private final Emitter doubleEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      DoubleLiteral doubleLiteral = (DoubleLiteral) node;

      // Emit double wrappers.
      MethodVisitor methodVisitor = methodStack.peek();
      methodVisitor.visitLdcInsn(doubleLiteral.value);
      methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf",
          "(D)Ljava/lang/Double;");
    }
  };

  private final Emitter bigIntegerEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      BigIntegerLiteral bigIntegerLiteral = (BigIntegerLiteral) node;

      // Emit bigint wrappers.
      MethodVisitor methodVisitor = methodStack.peek();
      methodVisitor.visitTypeInsn(NEW, "java/math/BigInteger");
      methodVisitor.visitInsn(DUP);
      methodVisitor.visitLdcInsn(bigIntegerLiteral.value);
      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/math/BigInteger", "<init>",
          "(Ljava/lang/String;)V");
    }
  };

  private final Emitter bigDecimalEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      BigDecimalLiteral bigDecimalLiteral = (BigDecimalLiteral) node;

      // Emit bigdecimal wrappers.
      MethodVisitor methodVisitor = methodStack.peek();
      methodVisitor.visitTypeInsn(NEW, "java/math/BigDecimal");
      methodVisitor.visitInsn(DUP);
      methodVisitor.visitLdcInsn(bigDecimalLiteral.value);
      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/math/BigDecimal", "<init>",
          "(Ljava/lang/String;)V");
    }
  };

  private final Emitter booleanEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      BooleanLiteral booleanLiteral = (BooleanLiteral) node;

      // Emit boolean wrappers.
      MethodVisitor methodVisitor = methodStack.peek();
      methodVisitor.visitIntInsn(BIPUSH, booleanLiteral.value ? 1 : 0);
      methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf",
          "(Z)Ljava/lang/Boolean;");

    }
  };

  private final Emitter typeLiteralEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      TypeLiteral type = (TypeLiteral) node;
      trackLineAndColumn(type);

      MethodVisitor methodVisitor = methodStack.peek();
      if (TypeLiteral.NOTHING.equals(type.name)) // Special-case the bottom type.
        methodVisitor.visitInsn(ACONST_NULL);
      else {
        ClassDecl classDecl = scope.resolve(type.name, true);

        if (classDecl != null)
          throw new UnsupportedOperationException(); // TODO reflection.

        String fqn = scope.resolveJavaType(type.name);
        if (fqn != null) {
          methodVisitor.visitLdcInsn(fqn);
          methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName",
              "(Ljava/lang/String;)Ljava/lang/Class;");
        }
      }
    }
  };

  private final Emitter javaLiteralEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      JavaLiteral java = (JavaLiteral) node;
      trackLineAndColumn(java);

      MethodVisitor methodVisitor = methodStack.peek();

      if (java.staticFieldAccess == null)
        methodVisitor.visitLdcInsn(java.value);
      else {
        methodVisitor.visitLdcInsn(java.value);
        methodVisitor.visitLdcInsn(java.staticFieldAccess);
        methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "getStatic",
            "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;");
      }
    }
  };

  private final Emitter stringLiteralEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      StringLiteral string = (StringLiteral) node;
      trackLineAndColumn(string);

      if (string.parts != null) {
        MethodVisitor methodVisitor = methodStack.peek();

        // Emit stringbuilder for string interpolation.
        methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");

        for (Node part : string.parts) {
          if (part instanceof StringLiteral)
            methodVisitor.visitLdcInsn(((StringLiteral) part).value);
          else
            emit(part);

          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
              "(Ljava/lang/Object;)Ljava/lang/StringBuilder;");
        }

        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
            "()Ljava/lang/String;");

      } else {
        methodStack.peek().visitLdcInsn(string.value.substring(1, string.value.length() - 1));
      }
    }
  };

  private final Emitter regexLiteralEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      throw new UnsupportedOperationException();
    }
  };

  private final Emitter functionDeclEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      FunctionDecl functionDecl = (FunctionDecl) node;
      String name = functionDecl.scopedName();
      boolean isClosure = functionDecl.isAnonymous();
      if (isClosure) {
        // Function is anonymous, generate a globally unique name for it.
        name = "$fn_" + functionNameSequence.incrementAndGet();
      }
      Context innerContext = new Context(functionDecl);

      // Before we emit the body of this method into the class scope, let's
      // see if this is closure, and if it is, emit it as a function reference.
      List<Variable> freeVariables;
      if (isClosure) {
        MethodVisitor currentVisitor = methodStack.peek();

        // Discover any free variables and save them to this closure.
        freeVariables = new ArrayList<Variable>();
        detectFreeVariables(functionDecl, functionDecl.arguments(), freeVariables);
        functionDecl.freeVariables = freeVariables;

        // Add them to the argument list of the function.
        for (Variable freeVariable : freeVariables) {
          functionDecl.arguments().add(new ArgDeclList.Argument(freeVariable.name, null));
        }

        currentVisitor.visitTypeInsn(NEW, "loop/runtime/Closure");
        currentVisitor.visitInsn(DUP);
        currentVisitor.visitLdcInsn(functionDecl.moduleName);
        currentVisitor.visitLdcInsn(name);

        if (!freeVariables.isEmpty()) {
          Context outerContext = functionStack.peek();
          int arrayIndex = outerContext.localVarIndex(outerContext.newLocalVariable());

          // push free variables as array.
          currentVisitor.visitIntInsn(BIPUSH, freeVariables.size());       // size of array
          currentVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
          currentVisitor.visitVarInsn(ASTORE, arrayIndex);
          int i = 0;
          for (Variable freeVariable : freeVariables) {
            currentVisitor.visitVarInsn(ALOAD, arrayIndex);    // array
            currentVisitor.visitIntInsn(BIPUSH, i);            // index
            emit(freeVariable);                                // value

            currentVisitor.visitInsn(AASTORE);
            i++;
          }

          // Load the array back in.
          currentVisitor.visitVarInsn(ALOAD, arrayIndex);
          currentVisitor.visitMethodInsn(INVOKESPECIAL, "loop/runtime/Closure", "<init>",
              "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V");
        } else
          currentVisitor.visitMethodInsn(INVOKESPECIAL, "loop/runtime/Closure", "<init>",
              "(Ljava/lang/String;Ljava/lang/String;)V");
      }


      //******* BEGIN FUNCTION SIGNATURE ********


      // Start writing this function in its own scope.
      StringBuilder args = new StringBuilder("(");
      List<Node> children = functionDecl.arguments().children();
      for (int functionArgIndex = 0, childrenSize = children.size(); functionArgIndex < childrenSize; functionArgIndex++) {
        Node arg = children.get(functionArgIndex);
        String argName = ((ArgDeclList.Argument) arg).name();
        innerContext.arguments.add(argName);
        innerContext.argumentIndex.put(argName, functionArgIndex);

        args.append("Ljava/lang/Object;");
      }
      args.append(")");
      functionStack.push(innerContext);
      scope.pushScope(innerContext);

      final MethodVisitor methodVisitor = classWriter.visitMethod(
          (functionDecl.isPrivate ? 0 /* default */ : ACC_PUBLIC) + ACC_STATIC,
          normalizeMethodName(name),
          args.append("Ljava/lang/Object;").toString(),
          null,
          null);
      methodStack.push(methodVisitor);
      trackLineAndColumn(functionDecl);

      methodVisitor.visitLabel(innerContext.startOfFunction);

      //******* BEGIN CELL TRANSACTION ********
      if (functionDecl.cell != null) {
        int thisIndex = innerContext.newLocalVariable("this");

        // Load the cell in a transactional wrapper into the "this" variable.
        methodVisitor.visitLdcInsn(functionDecl.cell);
        methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Cells", "beginTransaction",
            "(Ljava/lang/String;)Ljava/lang/Object;");
        methodVisitor.visitVarInsn(ASTORE, thisIndex);
      }

      //******* BEGIN WHERE BLOCK LOCALS ********

      // Emit static definitions in all parent where blocks.
      for (int i1 = 0, functionStackSize = functionStack.size(); i1 < functionStackSize - 1; i1++) {
        FunctionDecl parent = functionStack.get(i1).thisFunction;

        for (Node helper : parent.whereBlock()) {
          if (helper instanceof Assignment)
            emit(helper);
        }
      }

      // Emit locally-scoped helper functions and variables.
      for (Node helper : functionDecl.whereBlock()) {
        // Rewrite helper functions to be namespaced inside the parent function.
        if (helper instanceof FunctionDecl)
          scopeNestedFunction(functionDecl, innerContext, (FunctionDecl) helper);
        emit(helper);
      }

      //******* BEGIN PATTERN HELPER LOCALS ********

      // Set up some helper local vars to make it easier to pattern match certain types (lists).
      if (functionDecl.patternMatching) {
        boolean checkIfLists = false, checkIfString = false;
        for (Node child : functionDecl.children()) {
          PatternRule patternRule = (PatternRule) child;

          for (Node pattern : patternRule.patterns) {
            if (pattern instanceof ListDestructuringPattern ||
                pattern instanceof ListStructurePattern) {
              checkIfLists = true;
            } else if (pattern instanceof StringPattern) {
              checkIfString = true;
            }
          }
        }

        if (checkIfLists) {
          List<Node> children1 = functionDecl.arguments().children();
          for (int i = 0, children1Size = children1.size(); i < children1Size; i++) {
            int isList = innerContext.newLocalVariable(IS_LIST_VAR_PREFIX + i);
            int runtimeListSize = innerContext.newLocalVariable(RUNTIME_LIST_SIZE_VAR_PREFIX + i);

            methodVisitor.visitVarInsn(ALOAD, i);
            methodVisitor.visitTypeInsn(INSTANCEOF, "java/util/List");
            methodVisitor.visitIntInsn(ISTORE, isList);

            // Initialize register for local var.
            methodVisitor.visitIntInsn(BIPUSH, -1);
            methodVisitor.visitIntInsn(ISTORE, runtimeListSize);

            Label notAList = new Label();
            methodVisitor.visitIntInsn(ILOAD, isList);
            methodVisitor.visitJumpInsn(IFEQ, notAList);

            methodVisitor.visitVarInsn(ALOAD, i);
            methodVisitor.visitTypeInsn(CHECKCAST, "java/util/List");
            methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "size", "()I");
            methodVisitor.visitIntInsn(ISTORE, runtimeListSize);
            methodVisitor.visitLabel(notAList);
          }
        }
        if (checkIfString) {
          for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
            int isString = innerContext.newLocalVariable(IS_STRING_PREFIX + i);
            int isReader = innerContext.newLocalVariable(IS_READER_PREFIX + i);
            int runtimeStringLen = innerContext.newLocalVariable(RUNTIME_STR_LEN_PREFIX + i);

            // Initialize all local vars we're going to use.
            methodVisitor.visitIntInsn(BIPUSH, 0);
            methodVisitor.visitVarInsn(ISTORE, isString);
            methodVisitor.visitIntInsn(BIPUSH, 0);
            methodVisitor.visitVarInsn(ISTORE, isReader);
            methodVisitor.visitIntInsn(BIPUSH, -1);
            methodVisitor.visitVarInsn(ISTORE, runtimeStringLen);

            methodVisitor.visitVarInsn(ALOAD, i);
            methodVisitor.visitInsn(DUP);
            methodVisitor.visitTypeInsn(INSTANCEOF, "java/lang/String");
            methodVisitor.visitIntInsn(ISTORE, isString);
            methodVisitor.visitTypeInsn(INSTANCEOF, "java/io/Reader");
            methodVisitor.visitIntInsn(ISTORE, isReader);
            methodVisitor.visitIntInsn(BIPUSH, 1);

            Label skipStringChecks = new Label();
            methodVisitor.visitJumpInsn(IFNE, skipStringChecks);
            methodVisitor.visitVarInsn(ALOAD, i);
            methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "length", "()I");
            methodVisitor.visitIntInsn(ISTORE, runtimeStringLen);

            methodVisitor.visitLabel(skipStringChecks);
          }
        }
      }
      //******* BEGIN FUNCTION BODY ********

      //******* BEGIN TRY ********
      Map<String, Label> catchBlocks = new LinkedHashMap<String, Label>();
      FunctionDecl exceptionHandler = null;
      if (functionDecl.exceptionHandler != null) {
        exceptionHandler = scope.resolveFunctionOnStack(functionDecl.exceptionHandler);

        Label tryStart = new Label();

        // Determine exception types.
        Label firstCatchStart = null;
        for (String type : exceptionHandler.handledExceptions()) {
          Label catchStart = new Label();
          catchBlocks.put(type, catchStart);

          if (null == firstCatchStart)
            firstCatchStart = catchStart;

          // Resolve java exception type, against imported types.
          String resolvedType = scope.resolveJavaType(type);
          if (resolvedType != null)
            type = resolvedType;

          methodVisitor.visitTryCatchBlock(tryStart, firstCatchStart, catchStart, type.replace('.', '/'));
        }
        methodVisitor.visitLabel(tryStart);
      }

      //******* BEGIN INSTRUCTIONS ********


      emitChildren(node);

      if (functionDecl.patternMatching) {
        methodVisitor.visitLdcInsn("Non-exhaustive pattern rules in " + functionDecl.name());
        methodVisitor.visitInsn(DUP);   // This is necessary just to maintain stack height consistency. =/
        methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/Loop", "error", "(Ljava/lang/String;)V");
      }

      methodVisitor.visitLabel(innerContext.endOfFunction);
      methodVisitor.visitInsn(ARETURN);

      //******* END FUNCTION BODY ********

      if (functionDecl.exceptionHandler != null) {
        for (Map.Entry<String, Label> typeLabel : catchBlocks.entrySet()) {
          methodVisitor.visitLabel(typeLabel.getValue());

          // Emit call to handler.
          // TODO probably need to resolve this to the correct module.
          methodVisitor.visitMethodInsn(INVOKESTATIC, scope.getModuleName(),
              exceptionHandler.scopedName(), "(Ljava/lang/Object;)Ljava/lang/Object;");
          methodVisitor.visitInsn(ARETURN);
        }
      }

      methodVisitor.visitMaxs(0, 0);
      methodVisitor.visitEnd();

      methodStack.pop();
      functionStack.pop();
      scope.popScope();
    }
  };

  private void detectFreeVariables(Node top, ArgDeclList args, List<Variable> vars) {
    // Pre-order traversal.
    for (Node node : top.children()) {
      detectFreeVariables(node, args, vars);
    }

    if (top instanceof Variable) {
      Variable local = (Variable) top;

      boolean free = true;
      if (args != null)
        for (Node arg : args.children()) {
          ArgDeclList.Argument argument = (ArgDeclList.Argument) arg;

          if (argument.name().equals(local.name))
            free = false;
        }

      if (free)
        vars.add(local);

    } else if (top instanceof Call) {
      Call call = (Call) top;
      detectFreeVariables(call.args(), args, vars);
    }

  }

  private void scopeNestedFunction(FunctionDecl parent, Context context, FunctionDecl function) {
    String unscopedName = function.name();

    String newName;
    if (parent.name().startsWith(WHERE_SCOPE_FN_PREFIX))
      newName = parent.name() + '$' + unscopedName;
    else
      newName = WHERE_SCOPE_FN_PREFIX + parent.name() + '$' + unscopedName;

    // Apply the scoped name globally.
    function.scopedName(newName);
    context.newLocalFunction(unscopedName, function);
  }

  private final Emitter privateFieldEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      PrivateField privateField = (PrivateField) node;
      trackLineAndColumn(privateField);
      String name = privateField.name().substring(1); // Strip @

      methodStack.peek().visitLdcInsn(name);
    }
  };

  private final Emitter argDeclEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
    }
  };

  private final Emitter inlineListEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      InlineListDef inlineListDef = (InlineListDef) node;

      MethodVisitor methodVisitor = methodStack.peek();
      Context context = functionStack.peek();

      String listType = inlineListDef.isSet ? "java/util/HashSet" : "java/util/ArrayList";

      int listVar = context.localVarIndex(context.newLocalVariable());
      methodVisitor.visitTypeInsn(NEW, listType);
      methodVisitor.visitInsn(DUP);
      methodVisitor.visitMethodInsn(INVOKESPECIAL, listType, "<init>", "()V");
      methodVisitor.visitVarInsn(ASTORE, listVar);

      for (Node child : inlineListDef.children()) {
        methodVisitor.visitVarInsn(ALOAD, listVar);
        emit(child);

        methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Collection", "add",
            "(Ljava/lang/Object;)Z");
        methodVisitor.visitInsn(POP); // discard result of add()
      }

      methodVisitor.visitVarInsn(ALOAD, listVar);
    }
  };

  private final Emitter inlineListRangeEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      ListRange range = (ListRange) node;

      MethodVisitor methodVisitor = methodStack.peek();

      emit(range.from);
      emit(range.to);
      methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Caller", "range",
          "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
    }
  };

  private final Emitter inlineMapEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      InlineMapDef inlineMapDef = (InlineMapDef) node;

      MethodVisitor methodVisitor = methodStack.peek();
      Context context = functionStack.peek();

      String mapType = inlineMapDef.isTree ? "java/util/TreeMap" : "java/util/HashMap";

      int mapVar = context.localVarIndex(context.newLocalVariable());
      methodVisitor.visitTypeInsn(NEW, mapType);
      methodVisitor.visitInsn(DUP);
      methodVisitor.visitMethodInsn(INVOKESPECIAL, mapType, "<init>", "()V");
      methodVisitor.visitVarInsn(ASTORE, mapVar);

      for (Iterator<Node> iterator = inlineMapDef.children().iterator(); iterator.hasNext(); ) {
        Node key = iterator.next();
        methodVisitor.visitVarInsn(ALOAD, mapVar);
        emit(key);
        emit(iterator.next()); // value.

        methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put",
            "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        methodVisitor.visitInsn(POP);   // disard put() result
      }

      methodVisitor.visitVarInsn(ALOAD, mapVar);
    }
  };


  private final Emitter callChainEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      CallChain callChain = (CallChain) node;
      List<Node> children = callChain.children();

      for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
        Node child = children.get(i);

        if (i == 0
            && callChain.nullSafe
            && child instanceof Variable
            && childrenSize > 1)
          trackLineAndColumn(child);
        emit(child);
      }
    }
  };

  private final Emitter indexIntoListEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      IndexIntoList indexIntoList = (IndexIntoList) node;
      MethodVisitor methodVisitor = methodStack.peek();

      Node from = indexIntoList.from();
      if (from != null)
        emit(from);

      boolean hasTo = indexIntoList.to() != null;
      if (hasTo) {
        emit(indexIntoList.to());

        if (indexIntoList.isSlice() && from == null)
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Collections", "sliceTo",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        else
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Collections", "obtain",
              "(Ljava/lang/Object;Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Object;");
      } else {
        if (indexIntoList.isSlice())
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Collections", "sliceFrom",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        else
          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Collections", "obtain",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
      }
    }
  };

  private final Emitter comprehensionEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      Comprehension comprehension = (Comprehension) node;

      Context context = functionStack.peek();
      MethodVisitor methodVisitor = methodStack.peek();

      // First create our output list.
      int outVarIndex = context.localVarIndex(context.newLocalVariable());

      methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");
      methodVisitor.visitInsn(DUP);
      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V");
      methodVisitor.visitVarInsn(ASTORE, outVarIndex);


      // Now loop through the target variable.
      int iVarIndex = context.localVarIndex(context.newLocalVariable());

      // iterator = collection.iterator()
      emit(comprehension.inList());
      methodVisitor.visitTypeInsn(CHECKCAST, "java/util/Collection");
      methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Collection", "iterator",
          "()Ljava/util/Iterator;");
      methodVisitor.visitVarInsn(ASTORE, iVarIndex);

      Label start = new Label();
      Label end = new Label();
      // {
      // if !iterator.hasNext() jump to end
      methodVisitor.visitLabel(start);
      methodVisitor.visitVarInsn(ALOAD, iVarIndex);
      methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z");
      methodVisitor.visitJumpInsn(IFEQ, end);

      // var = iterator.next()
      methodVisitor.visitVarInsn(ALOAD, iVarIndex);
      methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next",
          "()Ljava/lang/Object;");
      int nextIndex = context.localVarIndex(context.newLocalVariable(comprehension.var()));
      methodVisitor.visitVarInsn(ASTORE, nextIndex);

      // if (filter_expression)
      if (comprehension.filter() != null) {
        emit(comprehension.filter());
        // Convert to primitive type.
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue",
            "()Z");
        methodVisitor.visitJumpInsn(IFEQ, start);
      }

      // Dump this value into the out list.
      methodVisitor.visitVarInsn(ALOAD, outVarIndex);
      methodVisitor.visitVarInsn(ALOAD, nextIndex);

      // Transform the value first using the projection expression.
      if (comprehension.projection() != null) {
        methodVisitor.visitInsn(POP);
        for (Node projection : comprehension.projection()) {
          emit(projection);
        }
      }

      methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add",
          "(Ljava/lang/Object;)Z");

      methodVisitor.visitInsn(POP);   // Discard result of add()
      methodVisitor.visitJumpInsn(GOTO, start);
      // }
      methodVisitor.visitLabel(end);

      // Finally.
      methodVisitor.visitVarInsn(ALOAD, outVarIndex);
    }
  };

  private final Emitter patternRuleEmitter = new Emitter() {
    @Override
    public void emitCode(Node node) {
      PatternRule rule = (PatternRule) node;
      Context context = functionStack.peek();
      MethodVisitor methodVisitor = methodStack.peek();

      if (context.arguments.isEmpty())
        throw new RuntimeException("Incorrect number of arguments for pattern matching");

      if (context.arguments.size() != rule.patterns.size())
        throw new RuntimeException("Incorrect number of pattern rules. Expected pattern rules for "
            + context.arguments + " but found " + rule.patterns.size() + " rule(s): "
            + LexprParser.stringify(rule.patterns));

      Label matchedClause = new Label();
      Label endOfClause = new Label();

      for (int i = 0, argumentsSize = context.arguments.size(); i < argumentsSize; i++) {

        Node pattern = rule.patterns.get(i);
        if (pattern instanceof ListDestructuringPattern) {
          emitListDestructuringPatternRule(rule, methodVisitor, context, endOfClause,
              i);
        } else if (pattern instanceof ListStructurePattern) {
          emitListStructurePatternRule(rule, methodVisitor, context, matchedClause, endOfClause, i);
        } else if (pattern instanceof StringLiteral
            || pattern instanceof PrivateField
            || pattern instanceof IntLiteral
            || pattern instanceof BooleanLiteral) {

          methodVisitor.visitVarInsn(ALOAD, i);
          emit(pattern);

          if (!(pattern instanceof BooleanLiteral))
            methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/Operations", "equal",
                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Boolean;");
          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");

          methodVisitor.visitJumpInsn(IFEQ, endOfClause);
        } else if (pattern instanceof TypeLiteral) {
          emitTypePatternRule(methodVisitor, matchedClause, endOfClause, i, (TypeLiteral) pattern);
        } else if (pattern instanceof RegexLiteral) {
          String regex = ((RegexLiteral) pattern).value;
          methodVisitor.visitLdcInsn(regex);

          // Discover named capturing groups if any.
          NamedPattern namedPattern = NamedPattern.compile(regex);
          List<String> groupNames = namedPattern.groupNames();
          for (String groupVarName : groupNames) {
            int varIndex = context.newLocalVariable(groupVarName);
            methodVisitor.visitInsn(ACONST_NULL);
            methodVisitor.visitVarInsn(ASTORE, varIndex);   // initialize part to null.
          }

          int matcherVar = context.localVarIndex(context.newLocalVariable());

          methodVisitor.visitMethodInsn(INVOKESTATIC, "loop/runtime/regex/NamedPattern", "compile",
              "(Ljava/lang/String;)Lloop/runtime/regex/NamedPattern;");

          methodVisitor.visitVarInsn(ALOAD, i);
          methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");
          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "loop/runtime/regex/NamedPattern", "matcher",
              "(Ljava/lang/CharSequence;)Lloop/runtime/regex/NamedMatcher;");

          methodVisitor.visitVarInsn(ASTORE, matcherVar);
          methodVisitor.visitVarInsn(ALOAD, matcherVar);
          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "loop/runtime/regex/NamedMatcher", "matches",
              "()Z");

          methodVisitor.visitJumpInsn(IFEQ, endOfClause);

          // Now extract the capturing group names.
          for (String groupNameVar : groupNames) {
            methodVisitor.visitVarInsn(ALOAD, matcherVar);
            methodVisitor.visitLdcInsn(groupNameVar);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "loop/runtime/regex/NamedMatcher", "group",
                "(Ljava/lang/String;)Ljava/lang/String;");
            methodVisitor.visitVarInsn(ASTORE, context.localVarIndex(groupNameVar));
          }

        } else if (pattern instanceof StringPattern) {
          emitStringPatternRule(rule, context, endOfClause, i);
        } else if (pattern instanceof MapPattern) {
          emitMapPatternRule(rule, context, matchedClause, endOfClause, i);
        } else if (pattern instanceof WildcardPattern) {
          // Always matches.
        }
      }

      methodVisitor.visitLabel(matchedClause);
      emitPatternClauses(rule);
      methodVisitor.visitJumpInsn(GOTO, context.endOfFunction);
      methodVisitor.visitLabel(endOfClause);
    }
  };

  private void emitTypePatternRule(MethodVisitor methodVisitor,
                                   Label matchedClause,
                                   Label endOfClause,
                                   int argIndex,
                                   TypeLiteral pattern) {
    String typeName;
    ClassDecl resolved = scope.resolve(pattern.name, true);
    if (resolved != null) {
      typeName = resolved.name;
    } else {
      typeName = scope.resolveJavaType(pattern.name);
    }

    methodVisitor.visitVarInsn(ALOAD, argIndex);
    methodVisitor.visitTypeInsn(INSTANCEOF, typeName.replace('.', '/'));
    methodVisitor.visitJumpInsn(IFEQ, endOfClause);
    methodVisitor.visitJumpInsn(GOTO, matchedClause);
  }

  private void emitPatternClauses(PatternRule rule) {
    if (rule.rhs != null) {
      emit(rule.rhs);
    } else
      emitGuards(rule);
  }

  private void emitGuards(PatternRule rule) {
    MethodVisitor methodVisitor = methodStack.peek();

    List<Node> children = rule.children();
    for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
      Node node = children.get(i);
      if (!(node instanceof Guard))
        throw new RuntimeException("Apparent pattern rule missing guards: "
            + LexprParser.stringify(rule));
      Guard guard = (Guard) node;
      Label endOfClause = new Label();
      Label matchedClause = new Label();

      // The "Otherwise" expression is a plain else.
      boolean notElse = !(guard.expression instanceof OtherwiseGuard);
      if (notElse) {
        emit(guard.expression);

        methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
        methodVisitor.visitJumpInsn(IFEQ, endOfClause);
      }

      methodVisitor.visitLabel(matchedClause);
      emit(guard.line);
      methodVisitor.visitJumpInsn(GOTO, functionStack.peek().endOfFunction);
      methodVisitor.visitLabel(endOfClause);

      if (i == childrenSize - 1 && notElse)
        methodVisitor.visitInsn(ACONST_NULL);
    }
  }

  private void emitMapPatternRule(PatternRule rule,
                                  Context context,
                                  Label matchedClause,
                                  Label endOfClause,
                                  int argIndex) {
    MapPattern pattern = (MapPattern) rule.patterns.get(argIndex);
    MethodVisitor methodVisitor = methodStack.peek();

    boolean hasType = false;
    List<Node> children = pattern.children();
    for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
      Node child = children.get(i);
      if (child instanceof TypeLiteral) {
        hasType = true;

        TypeLiteral typeLiteral = (TypeLiteral) child;
        String typeName;
        ClassDecl resolved = scope.resolve(typeLiteral.name, true);
        if (resolved != null) {
          typeName = resolved.name;
        } else {
          typeName = scope.resolveJavaType(typeLiteral.name);
        }

        methodVisitor.visitVarInsn(ALOAD, argIndex);
        methodVisitor.visitTypeInsn(INSTANCEOF, typeName.replace('.', '/'));
        continue;
      }

      // Guard the first destructuring pair if there is a type pattern.
      if (hasType && i == 1) {
        methodVisitor.visitJumpInsn(IFEQ, endOfClause);
      }

      DestructuringPair pair = (DestructuringPair) child;

      int destructuredVar = context.localVarIndex(context.newLocalVariable((Variable) pair.lhs));
      emit(pair.rhs);

      methodVisitor.visitVarInsn(ASTORE, destructuredVar);
      methodVisitor.visitVarInsn(ALOAD, destructuredVar);
      methodVisitor.visitJumpInsn(IFNULL, endOfClause);
    }
    methodVisitor.visitJumpInsn(GOTO, matchedClause);
  }

  private void emitStringPatternRule(PatternRule rule,
                                     Context context,
                                     Label endOfClause,
                                     int argIndex) {
    MethodVisitor methodVisitor = methodStack.peek();

    methodVisitor.visitVarInsn(ILOAD, context.localVarIndex(IS_STRING_PREFIX + argIndex));
    methodVisitor.visitJumpInsn(IFEQ, endOfClause);   // Not a string, so skip

    List<Node> children = rule.patterns.get(argIndex).children();
    int i = 0, childrenSize = children.size();

    boolean splittable = false;
    int lastIndex = context.localVarIndex(context.newLocalVariable());      // The last index of split (i.e. pattern delimiter).

    // Start from offset if the first bit is a constant (corner case)
    if (childrenSize > 0 && children.get(0) instanceof StringLiteral)
      methodVisitor.visitLdcInsn(((StringLiteral) children.get(0)).unquotedValue().length());
    else
      methodVisitor.visitIntInsn(BIPUSH, -1);

    methodVisitor.visitIntInsn(ISTORE, lastIndex);

    for (int j = 0; j < childrenSize; j++) {
      Node child = children.get(j);

      if (child instanceof Variable) {
        if (j < childrenSize - 1) {
          context.localVarIndex(context.newLocalVariable((Variable) child));

          Node next = children.get(j + 1);
          if (next instanceof StringLiteral) {
            // If the next node is a string literal, then we must split this
            // string across occurrences of the given literal.
            int thisIndex = context.localVarIndex(context.newLocalVariable());

            methodVisitor.visitVarInsn(ALOAD, argIndex);
            methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");
            emit(next);

            // If this is the second or greater pattern matcher, seek from the last location.
            if (splittable) {
              methodVisitor.visitIntInsn(ILOAD, lastIndex);
              methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "indexOf",
                  "(Ljava/lang/String;I)I");
            } else {
              methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "indexOf",
                  "(Ljava/lang/String;)I");
            }
            methodVisitor.visitIntInsn(ISTORE, thisIndex);

            methodVisitor.visitIntInsn(ILOAD, thisIndex);
            methodVisitor.visitIntInsn(BIPUSH, -1);
            methodVisitor.visitJumpInsn(IF_ICMPLE, endOfClause); // Jump out of this clause

            int matchedPieceVar = context.localVarIndex(context.newLocalVariable((Variable) child));

            methodVisitor.visitVarInsn(ALOAD, argIndex);
            methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");

            methodVisitor.visitIntInsn(ILOAD, lastIndex);

            Label startFromLastIndex = new Label();
            Label startFromZeroIndex = new Label();
            methodVisitor.visitIntInsn(BIPUSH, -1);
            methodVisitor.visitJumpInsn(IF_ICMPNE, startFromLastIndex);

            // Either start from 0 or lastindex of split.
            methodVisitor.visitIntInsn(BIPUSH, 0);
            methodVisitor.visitJumpInsn(GOTO, startFromZeroIndex);
            methodVisitor.visitLabel(startFromLastIndex);
            methodVisitor.visitIntInsn(ILOAD, lastIndex);
            methodVisitor.visitLabel(startFromZeroIndex);

            methodVisitor.visitIntInsn(ILOAD, thisIndex);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "substring",
                "(II)Ljava/lang/String;");

            // Save this piece into our variable.
            methodVisitor.visitVarInsn(ASTORE, matchedPieceVar);

            // Advance the index by the length of this match.
            methodVisitor.visitLdcInsn(((StringLiteral) next).unquotedValue().length());
            methodVisitor.visitIntInsn(ILOAD, thisIndex);
            methodVisitor.visitInsn(IADD);

            methodVisitor.visitIntInsn(ISTORE, lastIndex);

            splittable = true;
          } else {
            methodVisitor.visitVarInsn(ALOAD, argIndex);
            int matchedPieceVar = context.localVarIndex(context.newLocalVariable((Variable) child));
            methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");
            methodVisitor.visitLdcInsn(i);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C");
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf",
                "(C)Ljava/lang/Character;");
            methodVisitor.visitVarInsn(ASTORE, matchedPieceVar);
          }
        } else {
          int matchedPieceVar = context.localVarIndex(context.newLocalVariable((Variable) child));
          int strLen = context.localVarIndex(RUNTIME_STR_LEN_PREFIX + argIndex);

//          methodVisitor.visitVarInsn(ALOAD, argIndex);
          methodVisitor.visitIntInsn(ILOAD, strLen);
          methodVisitor.visitIntInsn(BIPUSH, 1);

          Label restOfString = new Label();
          Label assignToPiece = new Label();

          methodVisitor.visitJumpInsn(IF_ICMPNE, restOfString);
          methodVisitor.visitLdcInsn("");
          methodVisitor.visitJumpInsn(GOTO, assignToPiece);
          methodVisitor.visitLabel(restOfString);

          methodVisitor.visitVarInsn(ALOAD, argIndex);
          methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/String");

          methodVisitor.visitIntInsn(ILOAD, lastIndex);
          methodVisitor.visitIntInsn(BIPUSH, -1);

          Label restOfStringFromI = new Label();
          Label reduceString = new Label();
          methodVisitor.visitJumpInsn(IF_ICMPLE, restOfStringFromI);
          methodVisitor.visitIntInsn(ILOAD, lastIndex);
          methodVisitor.visitJumpInsn(GOTO, reduceString);
          methodVisitor.visitLabel(restOfStringFromI);
          methodVisitor.visitLdcInsn(i);
          methodVisitor.visitLabel(reduceString);
          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "substring",
              "(I)Ljava/lang/String;");

          methodVisitor.visitLabel(assignToPiece);
          methodVisitor.visitVarInsn(ASTORE, matchedPieceVar);
        }
        i++;
      }
    }
  }

  private void emitListStructurePatternRule(PatternRule rule,
                                            MethodVisitor methodVisitor,
                                            Context context,
                                            Label matchedClause,
                                            Label endOfClause,
                                            int argIndex) {
    ListStructurePattern listPattern = (ListStructurePattern) rule.patterns.get(argIndex);
    List<Node> children = listPattern.children();

    int runtimeListSizeVar = context.localVarIndex(RUNTIME_LIST_SIZE_VAR_PREFIX + argIndex);
    methodVisitor.visitIntInsn(ILOAD, runtimeListSizeVar);
    methodVisitor.visitIntInsn(BIPUSH, children.size());
    methodVisitor.visitJumpInsn(IF_ICMPNE, endOfClause);

    // Slice the list by terminals in the pattern list.
    for (int j = 0, childrenSize = children.size(); j < childrenSize; j++) {
      Node child = children.get(j);
      if (child instanceof Variable) {
        trackLineAndColumn(child);

        // Store into structure vars.
        int localVar = context.localVarIndex(context.newLocalVariable(((Variable) child)));

        methodVisitor.visitVarInsn(ALOAD, argIndex);
        methodVisitor.visitIntInsn(BIPUSH, j);      // We dont support matching >128 args ;)
        methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get",
            "(I)Ljava/lang/Object;");
        methodVisitor.visitVarInsn(ASTORE, localVar);
      }
    }

    methodVisitor.visitJumpInsn(GOTO, matchedClause);
  }

  private void emitListDestructuringPatternRule(PatternRule rule,
                                                MethodVisitor methodVisitor,
                                                Context context,
                                                Label endOfClause,
                                                int argIndex) {
    ListDestructuringPattern listPattern = (ListDestructuringPattern) rule.patterns.get(argIndex);
    Label noMatch = new Label();

    int runtimeListSizeVar = context.localVarIndex(RUNTIME_LIST_SIZE_VAR_PREFIX + argIndex);

    int size = listPattern.children().size();
    if (size == 0) {
      methodVisitor.visitIntInsn(ILOAD, runtimeListSizeVar);
//      methodVisitor.visitJumpInsn(IFEQ, matchedClause);
//      methodVisitor.visitJumpInsn(GOTO, endOfClause);
      methodVisitor.visitJumpInsn(IFNE, endOfClause);
    } else if (size == 1) {
      methodVisitor.visitIntInsn(ILOAD, runtimeListSizeVar);
      methodVisitor.visitIntInsn(BIPUSH, 1);
//      methodVisitor.visitJumpInsn(IFNE, matchedClause);
//      methodVisitor.visitJumpInsn(GOTO, endOfClause);
      methodVisitor.visitJumpInsn(IFNE, endOfClause);

    } else {
      // Slice the list by terminals in the pattern list.
      int i = 0;
      List<Node> children = listPattern.children();
      for (int j = 0, childrenSize = children.size(); j < childrenSize; j++) {
        Node child = children.get(j);
        if (child instanceof Variable) {
          trackLineAndColumn(child);
          int localVar = context.localVarIndex(context.newLocalVariable(((Variable) child)));

          if (j < childrenSize - 1) {
            methodVisitor.visitVarInsn(ALOAD, argIndex);
            methodVisitor.visitLdcInsn(i);
            methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get",
                "(I)Ljava/lang/Object;");
            methodVisitor.visitVarInsn(ASTORE, localVar);
          } else {
            methodVisitor.visitIntInsn(ILOAD, runtimeListSizeVar);
            methodVisitor.visitIntInsn(BIPUSH, 1);
            Label storeEmptyList = new Label();
            methodVisitor.visitJumpInsn(IF_ICMPEQ, storeEmptyList);

            // Otherwise store a slice of the list.
            methodVisitor.visitVarInsn(ALOAD, argIndex);
            methodVisitor.visitLdcInsn(i);
            methodVisitor.visitIntInsn(ILOAD, runtimeListSizeVar);
            methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "subList",
                "(II)Ljava/util/List;");
            methodVisitor.visitVarInsn(ASTORE, localVar);

            Label endOfStoreEmptyList = new Label();
            methodVisitor.visitJumpInsn(GOTO, endOfStoreEmptyList);

            methodVisitor.visitLabel(storeEmptyList);
            methodVisitor.visitFieldInsn(GETSTATIC, "java/util/Collections", "EMPTY_LIST",
                "Ljava/util/List;");
            methodVisitor.visitVarInsn(ASTORE, localVar);
            methodVisitor.visitLabel(endOfStoreEmptyList);
//            methodVisitor.visitJumpInsn(GOTO, matchedClause);

          }

          i++;
        }
      }
    }

    methodVisitor.visitLabel(noMatch);
  }
}
TOP

Related Classes of loop.AsmCodeEmitter$SourceLocation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.