Package clojure.lang

Source Code of clojure.lang.Compiler$BodyExpr

/**
*   Copyright (c) Rich Hickey. All rights reserved.
*   The use and distribution terms for this software are covered by the
*   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
*   which can be found in the file epl-v10.html at the root of this distribution.
*   By using this software in any fashion, you are agreeing to be bound by
*    the terms of this license.
*   You must not remove this notice, or any other, from this software.
**/

/* rich Aug 21, 2007 */

package clojure.lang;

//*

import clojure.asm.*;
import clojure.asm.commons.GeneratorAdapter;
import clojure.asm.commons.Method;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

//*/
/*

import org.objectweb.asm.*;
import org.objectweb.asm.commons.Method;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.util.TraceClassVisitor;
import org.objectweb.asm.util.CheckClassAdapter;
//*/

public class Compiler implements Opcodes{

static final Symbol DEF = Symbol.intern("def");
static final Symbol LOOP = Symbol.intern("loop*");
static final Symbol RECUR = Symbol.intern("recur");
static final Symbol IF = Symbol.intern("if");
static final Symbol LET = Symbol.intern("let*");
static final Symbol LETFN = Symbol.intern("letfn*");
static final Symbol DO = Symbol.intern("do");
static final Symbol FN = Symbol.intern("fn*");
static final Symbol FNONCE = (Symbol) Symbol.intern("fn*").withMeta(RT.map(Keyword.intern(null, "once"), RT.T));
static final Symbol QUOTE = Symbol.intern("quote");
static final Symbol THE_VAR = Symbol.intern("var");
static final Symbol DOT = Symbol.intern(".");
static final Symbol ASSIGN = Symbol.intern("set!");
//static final Symbol TRY_FINALLY = Symbol.intern("try-finally");
static final Symbol TRY = Symbol.intern("try");
static final Symbol CATCH = Symbol.intern("catch");
static final Symbol FINALLY = Symbol.intern("finally");
static final Symbol THROW = Symbol.intern("throw");
static final Symbol MONITOR_ENTER = Symbol.intern("monitor-enter");
static final Symbol MONITOR_EXIT = Symbol.intern("monitor-exit");
static final Symbol IMPORT = Symbol.intern("clojure.core", "import*");
//static final Symbol INSTANCE = Symbol.intern("instance?");
static final Symbol DEFTYPE = Symbol.intern("deftype*");
static final Symbol CASE = Symbol.intern("case*");

//static final Symbol THISFN = Symbol.intern("thisfn");
static final Symbol CLASS = Symbol.intern("Class");
static final Symbol NEW = Symbol.intern("new");
static final Symbol THIS = Symbol.intern("this");
static final Symbol REIFY = Symbol.intern("reify*");
//static final Symbol UNQUOTE = Symbol.intern("unquote");
//static final Symbol UNQUOTE_SPLICING = Symbol.intern("unquote-splicing");
//static final Symbol SYNTAX_QUOTE = Symbol.intern("clojure.core", "syntax-quote");
static final Symbol LIST = Symbol.intern("clojure.core", "list");
static final Symbol HASHMAP = Symbol.intern("clojure.core", "hash-map");
static final Symbol VECTOR = Symbol.intern("clojure.core", "vector");
static final Symbol IDENTITY = Symbol.intern("clojure.core", "identity");

static final Symbol _AMP_ = Symbol.intern("&");
static final Symbol ISEQ = Symbol.intern("clojure.lang.ISeq");

static final Keyword inlineKey = Keyword.intern(null, "inline");
static final Keyword inlineAritiesKey = Keyword.intern(null, "inline-arities");
static final Keyword staticKey = Keyword.intern(null, "static");
static final Keyword arglistsKey = Keyword.intern(null, "arglists");
static final Symbol INVOKE_STATIC = Symbol.intern("invokeStatic");

static final Keyword volatileKey = Keyword.intern(null, "volatile");
static final Keyword implementsKey = Keyword.intern(null, "implements");
static final String COMPILE_STUB_PREFIX = "compile__stub";

static final Keyword protocolKey = Keyword.intern(null, "protocol");
static final Keyword onKey = Keyword.intern(null, "on");
static Keyword dynamicKey = Keyword.intern("dynamic");

static final Symbol NS = Symbol.intern("ns");
static final Symbol IN_NS = Symbol.intern("in-ns");

//static final Symbol IMPORT = Symbol.intern("import");
//static final Symbol USE = Symbol.intern("use");

//static final Symbol IFN = Symbol.intern("clojure.lang", "IFn");

static final public IPersistentMap specials = PersistentHashMap.create(
    DEF, new DefExpr.Parser(),
    LOOP, new LetExpr.Parser(),
    RECUR, new RecurExpr.Parser(),
    IF, new IfExpr.Parser(),
    CASE, new CaseExpr.Parser(),
    LET, new LetExpr.Parser(),
    LETFN, new LetFnExpr.Parser(),
    DO, new BodyExpr.Parser(),
    FN, null,
    QUOTE, new ConstantExpr.Parser(),
    THE_VAR, new TheVarExpr.Parser(),
    IMPORT, new ImportExpr.Parser(),
    DOT, new HostExpr.Parser(),
    ASSIGN, new AssignExpr.Parser(),
    DEFTYPE, new NewInstanceExpr.DeftypeParser(),
    REIFY, new NewInstanceExpr.ReifyParser(),
//    TRY_FINALLY, new TryFinallyExpr.Parser(),
TRY, new TryExpr.Parser(),
THROW, new ThrowExpr.Parser(),
MONITOR_ENTER, new MonitorEnterExpr.Parser(),
MONITOR_EXIT, new MonitorExitExpr.Parser(),
//    INSTANCE, new InstanceExpr.Parser(),
//    IDENTICAL, new IdenticalExpr.Parser(),
//THISFN, null,
CATCH, null,
FINALLY, null,
//    CLASS, new ClassExpr.Parser(),
NEW, new NewExpr.Parser(),
//    UNQUOTE, null,
//    UNQUOTE_SPLICING, null,
//    SYNTAX_QUOTE, null,
_AMP_, null
);

private static final int MAX_POSITIONAL_ARITY = 20;
private static final Type OBJECT_TYPE;
private static final Type KEYWORD_TYPE = Type.getType(Keyword.class);
private static final Type VAR_TYPE = Type.getType(Var.class);
private static final Type SYMBOL_TYPE = Type.getType(Symbol.class);
//private static final Type NUM_TYPE = Type.getType(Num.class);
private static final Type IFN_TYPE = Type.getType(IFn.class);
private static final Type AFUNCTION_TYPE = Type.getType(AFunction.class);
private static final Type RT_TYPE = Type.getType(RT.class);
private static final Type NUMBERS_TYPE = Type.getType(Numbers.class);
final static Type CLASS_TYPE = Type.getType(Class.class);
final static Type NS_TYPE = Type.getType(Namespace.class);
final static Type UTIL_TYPE = Type.getType(Util.class);
final static Type REFLECTOR_TYPE = Type.getType(Reflector.class);
final static Type THROWABLE_TYPE = Type.getType(Throwable.class);
final static Type BOOLEAN_OBJECT_TYPE = Type.getType(Boolean.class);
final static Type IPERSISTENTMAP_TYPE = Type.getType(IPersistentMap.class);
final static Type IOBJ_TYPE = Type.getType(IObj.class);

private static final Type[][] ARG_TYPES;
//private static final Type[] EXCEPTION_TYPES = {Type.getType(Exception.class)};
private static final Type[] EXCEPTION_TYPES = {};

static
  {
  OBJECT_TYPE = Type.getType(Object.class);
  ARG_TYPES = new Type[MAX_POSITIONAL_ARITY + 2][];
  for(int i = 0; i <= MAX_POSITIONAL_ARITY; ++i)
    {
    Type[] a = new Type[i];
    for(int j = 0; j < i; j++)
      a[j] = OBJECT_TYPE;
    ARG_TYPES[i] = a;
    }
  Type[] a = new Type[MAX_POSITIONAL_ARITY + 1];
  for(int j = 0; j < MAX_POSITIONAL_ARITY; j++)
    a[j] = OBJECT_TYPE;
  a[MAX_POSITIONAL_ARITY] = Type.getType("[Ljava/lang/Object;");
  ARG_TYPES[MAX_POSITIONAL_ARITY + 1] = a;


  }


//symbol->localbinding
static final public Var LOCAL_ENV = Var.create(null).setDynamic();

//vector<localbinding>
static final public Var LOOP_LOCALS = Var.create().setDynamic();

//Label
static final public Var LOOP_LABEL = Var.create().setDynamic();

//vector<object>
static final public Var CONSTANTS = Var.create().setDynamic();

//IdentityHashMap
static final public Var CONSTANT_IDS = Var.create().setDynamic();

//vector<keyword>
static final public Var KEYWORD_CALLSITES = Var.create().setDynamic();

//vector<var>
static final public Var PROTOCOL_CALLSITES = Var.create().setDynamic();

//set<var>
static final public Var VAR_CALLSITES = Var.create().setDynamic();

//keyword->constid
static final public Var KEYWORDS = Var.create().setDynamic();

//var->constid
static final public Var VARS = Var.create().setDynamic();

//FnFrame
static final public Var METHOD = Var.create(null).setDynamic();

//null or not
static final public Var IN_CATCH_FINALLY = Var.create(null).setDynamic();

static final public Var NO_RECUR = Var.create(null).setDynamic();

//DynamicClassLoader
static final public Var LOADER = Var.create().setDynamic();

//String
static final public Var SOURCE = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
                                            Symbol.intern("*source-path*"), "NO_SOURCE_FILE").setDynamic();

//String
static final public Var SOURCE_PATH = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
                                                 Symbol.intern("*file*"), "NO_SOURCE_PATH").setDynamic();

//String
static final public Var COMPILE_PATH = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
                                                  Symbol.intern("*compile-path*"), null).setDynamic();
//boolean
static final public Var COMPILE_FILES = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
                                                   Symbol.intern("*compile-files*"), Boolean.FALSE).setDynamic();

static final public Var INSTANCE = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
                                            Symbol.intern("instance?"));

static final public Var ADD_ANNOTATIONS = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
                                            Symbol.intern("add-annotations"));

static final public Keyword disableLocalsClearingKey = Keyword.intern("disable-locals-clearing");
static final public Keyword elideMetaKey = Keyword.intern("elide-meta");

static final public Var COMPILER_OPTIONS;

static public Object getCompilerOption(Keyword k){
  return RT.get(COMPILER_OPTIONS.deref(),k);
}

    static
    {
        Object compilerOptions = null;

        for (Map.Entry e : System.getProperties().entrySet())
        {
            String name = (String) e.getKey();
            String v = (String) e.getValue();
            if (name.startsWith("clojure.compiler."))
            {
                compilerOptions = RT.assoc(compilerOptions,
                        RT.keyword(null, name.substring(1 + name.lastIndexOf('.'))),
                        RT.readString(v));
            }
        }

        COMPILER_OPTIONS = Var.intern(Namespace.findOrCreate(Symbol.intern("clojure.core")),
                Symbol.intern("*compiler-options*"), compilerOptions).setDynamic();
    }

    static Object elideMeta(Object m){
        Collection<Object> elides = (Collection<Object>) getCompilerOption(elideMetaKey);
        if(elides != null)
            {
            for(Object k : elides)
                {
//                System.out.println("Eliding:" + k + " : " + RT.get(m, k));
                m = RT.dissoc(m, k);
                }
//            System.out.println("Remaining: " + RT.keys(m));
            }
        return m;
    }

//Integer
static final public Var LINE = Var.create(0).setDynamic();
static final public Var COLUMN = Var.create(0).setDynamic();

static int lineDeref(){
  return ((Number)LINE.deref()).intValue();
}

static int columnDeref(){
  return ((Number)COLUMN.deref()).intValue();
}

//Integer
static final public Var LINE_BEFORE = Var.create(0).setDynamic();
static final public Var COLUMN_BEFORE = Var.create(0).setDynamic();
static final public Var LINE_AFTER = Var.create(0).setDynamic();
static final public Var COLUMN_AFTER = Var.create(0).setDynamic();

//Integer
static final public Var NEXT_LOCAL_NUM = Var.create(0).setDynamic();

//Integer
static final public Var RET_LOCAL_NUM = Var.create().setDynamic();


static final public Var COMPILE_STUB_SYM = Var.create(null).setDynamic();
static final public Var COMPILE_STUB_CLASS = Var.create(null).setDynamic();


//PathNode chain
static final public Var CLEAR_PATH = Var.create(null).setDynamic();

//tail of PathNode chain
static final public Var CLEAR_ROOT = Var.create(null).setDynamic();

//LocalBinding -> Set<LocalBindingExpr>
static final public Var CLEAR_SITES = Var.create(null).setDynamic();

    public enum C{
  STATEMENT,  //value ignored
  EXPRESSION, //value required
  RETURN,      //tail position relative to enclosing recur frame
  EVAL
}

private class Recur {};
static final public Class RECUR_CLASS = Recur.class;
   
interface Expr{
  Object eval() ;

  void emit(C context, ObjExpr objx, GeneratorAdapter gen);

  boolean hasJavaClass() ;

  Class getJavaClass() ;
}

public static abstract class UntypedExpr implements Expr{

  public Class getJavaClass(){
    throw new IllegalArgumentException("Has no Java class");
  }

  public boolean hasJavaClass(){
    return false;
  }
}

interface IParser{
  Expr parse(C context, Object form) ;
}

static boolean isSpecial(Object sym){
  return specials.containsKey(sym);
}

static Symbol resolveSymbol(Symbol sym){
  //already qualified or classname?
  if(sym.name.indexOf('.') > 0)
    return sym;
  if(sym.ns != null)
    {
    Namespace ns = namespaceFor(sym);
    if(ns == null || ns.name.name == sym.ns)
      return sym;
    return Symbol.intern(ns.name.name, sym.name);
    }
  Object o = currentNS().getMapping(sym);
  if(o == null)
    return Symbol.intern(currentNS().name.name, sym.name);
  else if(o instanceof Class)
    return Symbol.intern(null, ((Class) o).getName());
  else if(o instanceof Var)
      {
      Var v = (Var) o;
      return Symbol.intern(v.ns.name.name, v.sym.name);
      }
  return null;

}

static class DefExpr implements Expr{
  public final Var var;
  public final Expr init;
  public final Expr meta;
  public final boolean initProvided;
  public final boolean isDynamic;
  public final String source;
  public final int line;
  public final int column;
  final static Method bindRootMethod = Method.getMethod("void bindRoot(Object)");
  final static Method setTagMethod = Method.getMethod("void setTag(clojure.lang.Symbol)");
  final static Method setMetaMethod = Method.getMethod("void setMeta(clojure.lang.IPersistentMap)");
  final static Method setDynamicMethod = Method.getMethod("clojure.lang.Var setDynamic(boolean)");
  final static Method symintern = Method.getMethod("clojure.lang.Symbol intern(String, String)");

  public DefExpr(String source, int line, int column, Var var, Expr init, Expr meta, boolean initProvided, boolean isDynamic){
    this.source = source;
    this.line = line;
    this.column = column;
    this.var = var;
    this.init = init;
    this.meta = meta;
    this.isDynamic = isDynamic;
    this.initProvided = initProvided;
  }

    private boolean includesExplicitMetadata(MapExpr expr) {
        for(int i=0; i < expr.keyvals.count(); i += 2)
            {
                Keyword k  = ((KeywordExpr) expr.keyvals.nth(i)).k;
                if ((k != RT.FILE_KEY) &&
                    (k != RT.DECLARED_KEY) &&
                    (k != RT.LINE_KEY) &&
                    (k != RT.COLUMN_KEY))
                    return true;
            }
        return false;
    }

    public Object eval() {
    try
      {
      if(initProvided)
        {
//      if(init instanceof FnExpr && ((FnExpr) init).closes.count()==0)
//        var.bindRoot(new FnLoaderThunk((FnExpr) init,var));
//      else
        var.bindRoot(init.eval());
        }
      if(meta != null)
        {
                IPersistentMap metaMap = (IPersistentMap) meta.eval();
                if (initProvided || true)//includesExplicitMetadata((MapExpr) meta))
            var.setMeta((IPersistentMap) meta.eval());
        }
      return var.setDynamic(isDynamic);
      }
    catch(Throwable e)
      {
      if(!(e instanceof CompilerException))
        throw new CompilerException(source, line, column, e);
      else
        throw (CompilerException) e;
      }
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    objx.emitVar(gen, var);
    if(isDynamic)
      {
      gen.push(isDynamic);
      gen.invokeVirtual(VAR_TYPE, setDynamicMethod);
      }
    if(meta != null)
      {
            if (initProvided || true)//includesExplicitMetadata((MapExpr) meta))
                {
                gen.dup();
                meta.emit(C.EXPRESSION, objx, gen);
                gen.checkCast(IPERSISTENTMAP_TYPE);
                gen.invokeVirtual(VAR_TYPE, setMetaMethod);
                }
      }
    if(initProvided)
      {
      gen.dup();
      if(init instanceof FnExpr)
        {
        ((FnExpr)init).emitForDefn(objx, gen);
        }
      else
        init.emit(C.EXPRESSION, objx, gen);
      gen.invokeVirtual(VAR_TYPE, bindRootMethod);
      }

    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass(){
    return true;
  }

  public Class getJavaClass(){
    return Var.class;
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object form) {
      //(def x) or (def x initexpr) or (def x "docstring" initexpr)
      String docstring = null;
      if(RT.count(form) == 4 && (RT.third(form) instanceof String)) {
        docstring = (String) RT.third(form);
        form = RT.list(RT.first(form), RT.second(form), RT.fourth(form));
      }
      if(RT.count(form) > 3)
        throw Util.runtimeException("Too many arguments to def");
      else if(RT.count(form) < 2)
        throw Util.runtimeException("Too few arguments to def");
      else if(!(RT.second(form) instanceof Symbol))
          throw Util.runtimeException("First argument to def must be a Symbol");
      Symbol sym = (Symbol) RT.second(form);
      Var v = lookupVar(sym, true);
      if(v == null)
        throw Util.runtimeException("Can't refer to qualified var that doesn't exist");
      if(!v.ns.equals(currentNS()))
        {
        if(sym.ns == null)
          {
          v = currentNS().intern(sym);
          registerVar(v);
          }
//          throw Util.runtimeException("Name conflict, can't def " + sym + " because namespace: " + currentNS().name +
//                              " refers to:" + v);
        else
          throw Util.runtimeException("Can't create defs outside of current ns");
        }
      IPersistentMap mm = sym.meta();
      boolean isDynamic = RT.booleanCast(RT.get(mm,dynamicKey));
      if(isDynamic)
         v.setDynamic();
            if(!isDynamic && sym.name.startsWith("*") && sym.name.endsWith("*") && sym.name.length() > 2)
                {
                RT.errPrintWriter().format("Warning: %1$s not declared dynamic and thus is not dynamically rebindable, "
                                          +"but its name suggests otherwise. Please either indicate ^:dynamic %1$s or change the name. (%2$s:%3$d)\n",
                                           sym, SOURCE_PATH.get(), LINE.get());
                }
      if(RT.booleanCast(RT.get(mm, arglistsKey)))
        {
        IPersistentMap vm = v.meta();
        //vm = (IPersistentMap) RT.assoc(vm,staticKey,RT.T);
        //drop quote
        vm = (IPersistentMap) RT.assoc(vm,arglistsKey,RT.second(mm.valAt(arglistsKey)));
        v.setMeta(vm);
        }
            Object source_path = SOURCE_PATH.get();
            source_path = source_path == null ? "NO_SOURCE_FILE" : source_path;
            mm = (IPersistentMap) RT.assoc(mm, RT.LINE_KEY, LINE.get()).assoc(RT.COLUMN_KEY, COLUMN.get()).assoc(RT.FILE_KEY, source_path);
      if (docstring != null)
        mm = (IPersistentMap) RT.assoc(mm, RT.DOC_KEY, docstring);
//      mm = mm.without(RT.DOC_KEY)
//          .without(Keyword.intern(null, "arglists"))
//          .without(RT.FILE_KEY)
//          .without(RT.LINE_KEY)
//          .without(RT.COLUMN_KEY)
//          .without(Keyword.intern(null, "ns"))
//          .without(Keyword.intern(null, "name"))
//          .without(Keyword.intern(null, "added"))
//          .without(Keyword.intern(null, "static"));
            mm = (IPersistentMap) elideMeta(mm);
      Expr meta = mm.count()==0 ? null:analyze(context == C.EVAL ? context : C.EXPRESSION, mm);
      return new DefExpr((String) SOURCE.deref(), lineDeref(), columnDeref(),
                         v, analyze(context == C.EVAL ? context : C.EXPRESSION, RT.third(form), v.sym.name),
                         meta, RT.count(form) == 3, isDynamic);
    }
  }
}

public static class AssignExpr implements Expr{
  public final AssignableExpr target;
  public final Expr val;

  public AssignExpr(AssignableExpr target, Expr val){
    this.target = target;
    this.val = val;
  }

  public Object eval() {
    return target.evalAssign(val);
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    target.emitAssign(context, objx, gen, val);
  }

  public boolean hasJavaClass() {
    return val.hasJavaClass();
  }

  public Class getJavaClass() {
    return val.getJavaClass();
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object frm) {
      ISeq form = (ISeq) frm;
      if(RT.length(form) != 3)
        throw new IllegalArgumentException("Malformed assignment, expecting (set! target val)");
      Expr target = analyze(C.EXPRESSION, RT.second(form));
      if(!(target instanceof AssignableExpr))
        throw new IllegalArgumentException("Invalid assignment target");
      return new AssignExpr((AssignableExpr) target, analyze(C.EXPRESSION, RT.third(form)));
    }
  }
}

public static class VarExpr implements Expr, AssignableExpr{
  public final Var var;
  public final Object tag;
  final static Method getMethod = Method.getMethod("Object get()");
  final static Method setMethod = Method.getMethod("Object set(Object)");

  public VarExpr(Var var, Symbol tag){
    this.var = var;
    this.tag = tag != null ? tag : var.getTag();
  }

  public Object eval() {
    return var.deref();
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    objx.emitVarValue(gen,var);
    if(context == C.STATEMENT)
      {
      gen.pop();
      }
  }

  public boolean hasJavaClass(){
    return tag != null;
  }

  public Class getJavaClass() {
    return HostExpr.tagToClass(tag);
  }

  public Object evalAssign(Expr val) {
    return var.set(val.eval());
  }

  public void emitAssign(C context, ObjExpr objx, GeneratorAdapter gen,
                         Expr val){
    objx.emitVar(gen, var);
    val.emit(C.EXPRESSION, objx, gen);
    gen.invokeVirtual(VAR_TYPE, setMethod);
    if(context == C.STATEMENT)
      gen.pop();
  }
}

public static class TheVarExpr implements Expr{
  public final Var var;

  public TheVarExpr(Var var){
    this.var = var;
  }

  public Object eval() {
    return var;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    objx.emitVar(gen, var);
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass(){
    return true;
  }

  public Class getJavaClass() {
    return Var.class;
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object form) {
      Symbol sym = (Symbol) RT.second(form);
      Var v = lookupVar(sym, false);
      if(v != null)
        return new TheVarExpr(v);
      throw Util.runtimeException("Unable to resolve var: " + sym + " in this context");
    }
  }
}

public static class KeywordExpr extends LiteralExpr{
  public final Keyword k;

  public KeywordExpr(Keyword k){
    this.k = k;
  }

  Object val(){
    return k;
  }

  public Object eval() {
    return k;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    objx.emitKeyword(gen, k);
    if(context == C.STATEMENT)
      gen.pop();

  }

  public boolean hasJavaClass(){
    return true;
  }

  public Class getJavaClass() {
    return Keyword.class;
  }
}

public static class ImportExpr implements Expr{
  public final String c;
  final static Method forNameMethod = Method.getMethod("Class classForNameNonLoading(String)");
  final static Method importClassMethod = Method.getMethod("Class importClass(Class)");
  final static Method derefMethod = Method.getMethod("Object deref()");

  public ImportExpr(String c){
    this.c = c;
  }

  public Object eval() {
    Namespace ns = (Namespace) RT.CURRENT_NS.deref();
    ns.importClass(RT.classForNameNonLoading(c));
    return null;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.getStatic(RT_TYPE,"CURRENT_NS",VAR_TYPE);
    gen.invokeVirtual(VAR_TYPE, derefMethod);
    gen.checkCast(NS_TYPE);
    gen.push(c);
    gen.invokeStatic(RT_TYPE, forNameMethod);
    gen.invokeVirtual(NS_TYPE, importClassMethod);
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass(){
    return false;
  }

  public Class getJavaClass() {
    throw new IllegalArgumentException("ImportExpr has no Java class");
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object form) {
      return new ImportExpr((String) RT.second(form));
    }
  }
}

public static abstract class LiteralExpr implements Expr{
  abstract Object val();

  public Object eval(){
    return val();
  }
}

static interface AssignableExpr{
  Object evalAssign(Expr val) ;

  void emitAssign(C context, ObjExpr objx, GeneratorAdapter gen, Expr val);
}

static public interface MaybePrimitiveExpr extends Expr{
  public boolean canEmitPrimitive();
  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen);
}

static public abstract class HostExpr implements Expr, MaybePrimitiveExpr{
  final static Type BOOLEAN_TYPE = Type.getType(Boolean.class);
  final static Type CHAR_TYPE = Type.getType(Character.class);
  final static Type INTEGER_TYPE = Type.getType(Integer.class);
  final static Type LONG_TYPE = Type.getType(Long.class);
  final static Type FLOAT_TYPE = Type.getType(Float.class);
  final static Type DOUBLE_TYPE = Type.getType(Double.class);
  final static Type SHORT_TYPE = Type.getType(Short.class);
  final static Type BYTE_TYPE = Type.getType(Byte.class);
  final static Type NUMBER_TYPE = Type.getType(Number.class);

  final static Method charValueMethod = Method.getMethod("char charValue()");
  final static Method booleanValueMethod = Method.getMethod("boolean booleanValue()");

  final static Method charValueOfMethod = Method.getMethod("Character valueOf(char)");
  final static Method intValueOfMethod = Method.getMethod("Integer valueOf(int)");
  final static Method longValueOfMethod = Method.getMethod("Long valueOf(long)");
  final static Method floatValueOfMethod = Method.getMethod("Float valueOf(float)");
  final static Method doubleValueOfMethod = Method.getMethod("Double valueOf(double)");
  final static Method shortValueOfMethod = Method.getMethod("Short valueOf(short)");
  final static Method byteValueOfMethod = Method.getMethod("Byte valueOf(byte)");

  final static Method intValueMethod = Method.getMethod("int intValue()");
  final static Method longValueMethod = Method.getMethod("long longValue()");
  final static Method floatValueMethod = Method.getMethod("float floatValue()");
  final static Method doubleValueMethod = Method.getMethod("double doubleValue()");
  final static Method byteValueMethod = Method.getMethod("byte byteValue()");
  final static Method shortValueMethod = Method.getMethod("short shortValue()");

  final static Method fromIntMethod = Method.getMethod("clojure.lang.Num from(int)");
  final static Method fromLongMethod = Method.getMethod("clojure.lang.Num from(long)");
  final static Method fromDoubleMethod = Method.getMethod("clojure.lang.Num from(double)");


  //*
  public static void emitBoxReturn(ObjExpr objx, GeneratorAdapter gen, Class returnType){
    if(returnType.isPrimitive())
      {
      if(returnType == boolean.class)
        {
        Label falseLabel = gen.newLabel();
        Label endLabel = gen.newLabel();
        gen.ifZCmp(GeneratorAdapter.EQ, falseLabel);
        gen.getStatic(BOOLEAN_OBJECT_TYPE, "TRUE", BOOLEAN_OBJECT_TYPE);
        gen.goTo(endLabel);
        gen.mark(falseLabel);
        gen.getStatic(BOOLEAN_OBJECT_TYPE, "FALSE", BOOLEAN_OBJECT_TYPE);
//        NIL_EXPR.emit(C.EXPRESSION, fn, gen);
        gen.mark(endLabel);
        }
      else if(returnType == void.class)
        {
        NIL_EXPR.emit(C.EXPRESSION, objx, gen);
        }
      else if(returnType == char.class)
          {
          gen.invokeStatic(CHAR_TYPE, charValueOfMethod);
          }
        else
          {
          if(returnType == int.class)
            {
            gen.invokeStatic(INTEGER_TYPE, intValueOfMethod);
//            gen.visitInsn(I2L);
//            gen.invokeStatic(NUMBERS_TYPE, Method.getMethod("Number num(long)"));
            }
          else if(returnType == float.class)
            {
            gen.invokeStatic(FLOAT_TYPE, floatValueOfMethod);

//            gen.visitInsn(F2D);
//            gen.invokeStatic(DOUBLE_TYPE, doubleValueOfMethod);
            }
          else if(returnType == double.class)
              gen.invokeStatic(DOUBLE_TYPE, doubleValueOfMethod);
          else if(returnType == long.class)
              gen.invokeStatic(NUMBERS_TYPE, Method.getMethod("Number num(long)"));
          else if(returnType == byte.class)
              gen.invokeStatic(BYTE_TYPE, byteValueOfMethod);
          else if(returnType == short.class)
              gen.invokeStatic(SHORT_TYPE, shortValueOfMethod);
          }
      }
  }

  //*/
  public static void emitUnboxArg(ObjExpr objx, GeneratorAdapter gen, Class paramType){
    if(paramType.isPrimitive())
      {
      if(paramType == boolean.class)
        {
        gen.checkCast(BOOLEAN_TYPE);
        gen.invokeVirtual(BOOLEAN_TYPE, booleanValueMethod);
//        Label falseLabel = gen.newLabel();
//        Label endLabel = gen.newLabel();
//        gen.ifNull(falseLabel);
//        gen.push(1);
//        gen.goTo(endLabel);
//        gen.mark(falseLabel);
//        gen.push(0);
//        gen.mark(endLabel);
        }
      else if(paramType == char.class)
        {
        gen.checkCast(CHAR_TYPE);
        gen.invokeVirtual(CHAR_TYPE, charValueMethod);
        }
      else
        {
        Method m = null;
        gen.checkCast(NUMBER_TYPE);
        if(RT.booleanCast(RT.UNCHECKED_MATH.deref()))
          {
          if(paramType == int.class)
            m = Method.getMethod("int uncheckedIntCast(Object)");
          else if(paramType == float.class)
            m = Method.getMethod("float uncheckedFloatCast(Object)");
          else if(paramType == double.class)
            m = Method.getMethod("double uncheckedDoubleCast(Object)");
          else if(paramType == long.class)
            m = Method.getMethod("long uncheckedLongCast(Object)");
          else if(paramType == byte.class)
            m = Method.getMethod("byte uncheckedByteCast(Object)");
          else if(paramType == short.class)
            m = Method.getMethod("short uncheckedShortCast(Object)");         
          }
        else
          {
          if(paramType == int.class)
            m = Method.getMethod("int intCast(Object)");
          else if(paramType == float.class)
            m = Method.getMethod("float floatCast(Object)");
          else if(paramType == double.class)
            m = Method.getMethod("double doubleCast(Object)");
          else if(paramType == long.class)
            m = Method.getMethod("long longCast(Object)");
          else if(paramType == byte.class)
            m = Method.getMethod("byte byteCast(Object)");
          else if(paramType == short.class)
            m = Method.getMethod("short shortCast(Object)");
          }
        gen.invokeStatic(RT_TYPE, m);
        }
      }
    else
      {
      gen.checkCast(Type.getType(paramType));
      }
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object frm) {
      ISeq form = (ISeq) frm;
      //(. x fieldname-sym) or
      //(. x 0-ary-method)
      // (. x methodname-sym args+)
      // (. x (methodname-sym args?))
      if(RT.length(form) < 3)
        throw new IllegalArgumentException("Malformed member expression, expecting (. target member ...)");
      //determine static or instance
      //static target must be symbol, either fully.qualified.Classname or Classname that has been imported
      int line = lineDeref();
      int column = columnDeref();
      String source = (String) SOURCE.deref();
      Class c = maybeClass(RT.second(form), false);
      //at this point c will be non-null if static
      Expr instance = null;
      if(c == null)
        instance = analyze(context == C.EVAL ? context : C.EXPRESSION, RT.second(form));

      boolean maybeField = RT.length(form) == 3 && (RT.third(form) instanceof Symbol);

      if(maybeField && !(((Symbol)RT.third(form)).name.charAt(0) == '-'))
        {
        Symbol sym = (Symbol) RT.third(form);
        if(c != null)
          maybeField = Reflector.getMethods(c, 0, munge(sym.name), true).size() == 0;
        else if(instance != null && instance.hasJavaClass() && instance.getJavaClass() != null)
          maybeField = Reflector.getMethods(instance.getJavaClass(), 0, munge(sym.name), false).size() == 0;
        }

      if(maybeField)    //field
        {
        Symbol sym = (((Symbol)RT.third(form)).name.charAt(0) == '-') ?
          Symbol.intern(((Symbol)RT.third(form)).name.substring(1))
            :(Symbol) RT.third(form);
        Symbol tag = tagOf(form);
        if(c != null) {
          return new StaticFieldExpr(line, column, c, munge(sym.name), tag);
        } else
          return new InstanceFieldExpr(line, column, instance, munge(sym.name), tag, (((Symbol)RT.third(form)).name.charAt(0) == '-'));
        }
      else
        {
        ISeq call = (ISeq) ((RT.third(form) instanceof ISeq) ? RT.third(form) : RT.next(RT.next(form)));
        if(!(RT.first(call) instanceof Symbol))
          throw new IllegalArgumentException("Malformed member expression");
        Symbol sym = (Symbol) RT.first(call);
        Symbol tag = tagOf(form);
        PersistentVector args = PersistentVector.EMPTY;
        for(ISeq s = RT.next(call); s != null; s = s.next())
          args = args.cons(analyze(context == C.EVAL ? context : C.EXPRESSION, s.first()));
        if(c != null)
          return new StaticMethodExpr(source, line, column, tag, c, munge(sym.name), args);
        else
          return new InstanceMethodExpr(source, line, column, tag, instance, munge(sym.name), args);
        }
    }
  }

  private static Class maybeClass(Object form, boolean stringOk) {
    if(form instanceof Class)
      return (Class) form;
    Class c = null;
    if(form instanceof Symbol)
      {
      Symbol sym = (Symbol) form;
      if(sym.ns == null) //if ns-qualified can't be classname
        {
        if(Util.equals(sym,COMPILE_STUB_SYM.get()))
          return (Class) COMPILE_STUB_CLASS.get();
        if(sym.name.indexOf('.') > 0 || sym.name.charAt(0) == '[')
          c = RT.classForName(sym.name);
        else
          {
          Object o = currentNS().getMapping(sym);
          if(o instanceof Class)
            c = (Class) o;
          else
            {
            try{
            c = RT.classForName(sym.name);
            }
            catch(Exception e){
              // aargh
              // leave c set to null -> return null
            }
            }
          }
        }
      }
    else if(stringOk && form instanceof String)
      c = RT.classForName((String) form);
    return c;
  }

  /*
   private static String maybeClassName(Object form, boolean stringOk){
     String className = null;
     if(form instanceof Symbol)
       {
       Symbol sym = (Symbol) form;
       if(sym.ns == null) //if ns-qualified can't be classname
         {
         if(sym.name.indexOf('.') > 0 || sym.name.charAt(0) == '[')
           className = sym.name;
         else
           {
           IPersistentMap imports = (IPersistentMap) ((Var) RT.NS_IMPORTS.get()).get();
           className = (String) imports.valAt(sym);
           }
         }
       }
     else if(stringOk && form instanceof String)
       className = (String) form;
     return className;
   }
*/
  static Class tagToClass(Object tag) {
    Class c = maybeClass(tag, true);
    if(tag instanceof Symbol)
      {
      Symbol sym = (Symbol) tag;
      if(sym.ns == null) //if ns-qualified can't be classname
        {
        if(sym.name.equals("objects"))
          c = Object[].class;
        else if(sym.name.equals("ints"))
          c = int[].class;
        else if(sym.name.equals("longs"))
          c = long[].class;
        else if(sym.name.equals("floats"))
          c = float[].class;
        else if(sym.name.equals("doubles"))
          c = double[].class;
        else if(sym.name.equals("chars"))
          c = char[].class;
        else if(sym.name.equals("shorts"))
          c = short[].class;
        else if(sym.name.equals("bytes"))
          c = byte[].class;
        else if(sym.name.equals("booleans"))
          c = boolean[].class;
        else if(sym.name.equals("int"))
          c = Integer.TYPE;
        else if(sym.name.equals("long"))
          c = Long.TYPE;
        else if(sym.name.equals("float"))
          c = Float.TYPE;
        else if(sym.name.equals("double"))
          c = Double.TYPE;
        else if(sym.name.equals("char"))
          c = Character.TYPE;
        else if(sym.name.equals("short"))
          c = Short.TYPE;
        else if(sym.name.equals("byte"))
          c = Byte.TYPE;
        else if(sym.name.equals("boolean"))
          c = Boolean.TYPE;
        }
      }
    if(c != null)
      return c;
    throw new IllegalArgumentException("Unable to resolve classname: " + tag);
  }
}

static abstract class FieldExpr extends HostExpr{
}

static class InstanceFieldExpr extends FieldExpr implements AssignableExpr{
  public final Expr target;
  public final Class targetClass;
  public final java.lang.reflect.Field field;
  public final String fieldName;
  public final int line;
  public final int column;
  public final Symbol tag;
  public final boolean requireField;
  final static Method invokeNoArgInstanceMember = Method.getMethod("Object invokeNoArgInstanceMember(Object,String,boolean)");
  final static Method setInstanceFieldMethod = Method.getMethod("Object setInstanceField(Object,String,Object)");


  public InstanceFieldExpr(int line, int column, Expr target, String fieldName, Symbol tag, boolean requireField) {
    this.target = target;
    this.targetClass = target.hasJavaClass() ? target.getJavaClass() : null;
    this.field = targetClass != null ? Reflector.getField(targetClass, fieldName, false) : null;
    this.fieldName = fieldName;
    this.line = line;
    this.column = column;
    this.tag = tag;
    this.requireField = requireField;
    if(field == null && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
      {
      if(targetClass == null)
        {
        RT.errPrintWriter()
          .format("Reflection warning, %s:%d:%d - reference to field %s can't be resolved.\n",
                  SOURCE_PATH.deref(), line, column, fieldName);
        }
      else
        {
        RT.errPrintWriter()
          .format("Reflection warning, %s:%d:%d - reference to field %s on %s can't be resolved.\n",
                  SOURCE_PATH.deref(), line, column, fieldName, targetClass.getName());
        }
      }
  }

  public Object eval() {
    return Reflector.invokeNoArgInstanceMember(target.eval(), fieldName, requireField);
  }

  public boolean canEmitPrimitive(){
    return targetClass != null && field != null &&
           Util.isPrimitive(field.getType());
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());
    if(targetClass != null && field != null)
      {
      target.emit(C.EXPRESSION, objx, gen);
      gen.checkCast(getType(targetClass));
      gen.getField(getType(targetClass), fieldName, Type.getType(field.getType()));
      }
    else
      throw new UnsupportedOperationException("Unboxed emit of unknown member");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());
    if(targetClass != null && field != null)
      {
      target.emit(C.EXPRESSION, objx, gen);
      gen.checkCast(getType(targetClass));
      gen.getField(getType(targetClass), fieldName, Type.getType(field.getType()));
      //if(context != C.STATEMENT)
      HostExpr.emitBoxReturn(objx, gen, field.getType());
      if(context == C.STATEMENT)
        {
        gen.pop();
        }
      }
    else
      {
      target.emit(C.EXPRESSION, objx, gen);
      gen.push(fieldName);
      gen.push(requireField);
      gen.invokeStatic(REFLECTOR_TYPE, invokeNoArgInstanceMember);
      if(context == C.STATEMENT)
        gen.pop();
      }
  }

  public boolean hasJavaClass() {
    return field != null || tag != null;
  }

  public Class getJavaClass() {
    return tag != null ? HostExpr.tagToClass(tag) : field.getType();
  }

  public Object evalAssign(Expr val) {
    return Reflector.setInstanceField(target.eval(), fieldName, val.eval());
  }

  public void emitAssign(C context, ObjExpr objx, GeneratorAdapter gen,
                         Expr val){
    gen.visitLineNumber(line, gen.mark());
    if(targetClass != null && field != null)
      {
      target.emit(C.EXPRESSION, objx, gen);
      gen.checkCast(Type.getType(targetClass));
      val.emit(C.EXPRESSION, objx, gen);
      gen.dupX1();
      HostExpr.emitUnboxArg(objx, gen, field.getType());
      gen.putField(Type.getType(targetClass), fieldName, Type.getType(field.getType()));
      }
    else
      {
      target.emit(C.EXPRESSION, objx, gen);
      gen.push(fieldName);
      val.emit(C.EXPRESSION, objx, gen);
      gen.invokeStatic(REFLECTOR_TYPE, setInstanceFieldMethod);
      }
    if(context == C.STATEMENT)
      gen.pop();
  }
}

static class StaticFieldExpr extends FieldExpr implements AssignableExpr{
  //final String className;
  public final String fieldName;
  public final Class c;
  public final java.lang.reflect.Field field;
  public final Symbol tag;
//  final static Method getStaticFieldMethod = Method.getMethod("Object getStaticField(String,String)");
//  final static Method setStaticFieldMethod = Method.getMethod("Object setStaticField(String,String,Object)");
  final int line;
  final int column;

  public StaticFieldExpr(int line, int column, Class c, String fieldName, Symbol tag) {
    //this.className = className;
    this.fieldName = fieldName;
    this.line = line;
    this.column = column;
    //c = Class.forName(className);
    this.c = c;
    try
      {
      field = c.getField(fieldName);
      }
    catch(NoSuchFieldException e)
      {
      throw Util.sneakyThrow(e);
      }
    this.tag = tag;
  }

  public Object eval() {
    return Reflector.getStaticField(c, fieldName);
  }

  public boolean canEmitPrimitive(){
    return Util.isPrimitive(field.getType());
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());
    gen.getStatic(Type.getType(c), fieldName, Type.getType(field.getType()));
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());

    gen.getStatic(Type.getType(c), fieldName, Type.getType(field.getType()));
    //if(context != C.STATEMENT)
    HostExpr.emitBoxReturn(objx, gen, field.getType());
    if(context == C.STATEMENT)
      {
      gen.pop();
      }
//    gen.push(className);
//    gen.push(fieldName);
//    gen.invokeStatic(REFLECTOR_TYPE, getStaticFieldMethod);
  }

  public boolean hasJavaClass(){
    return true;
  }

  public Class getJavaClass() {
    //Class c = Class.forName(className);
    //java.lang.reflect.Field field = c.getField(fieldName);
    return tag != null ? HostExpr.tagToClass(tag) : field.getType();
  }

  public Object evalAssign(Expr val) {
    return Reflector.setStaticField(c, fieldName, val.eval());
  }

  public void emitAssign(C context, ObjExpr objx, GeneratorAdapter gen,
                         Expr val){
    gen.visitLineNumber(line, gen.mark());
    val.emit(C.EXPRESSION, objx, gen);
    gen.dup();
    HostExpr.emitUnboxArg(objx, gen, field.getType());
    gen.putStatic(Type.getType(c), fieldName, Type.getType(field.getType()));
    if(context == C.STATEMENT)
      gen.pop();
  }


}

static Class maybePrimitiveType(Expr e){
  if(e instanceof MaybePrimitiveExpr && e.hasJavaClass() && ((MaybePrimitiveExpr)e).canEmitPrimitive())
    {
    Class c = e.getJavaClass();
    if(Util.isPrimitive(c))
      return c;
    }
  return null;
}

static Class maybeJavaClass(Collection<Expr> exprs){
    Class match = null;
    try
    {
    for (Expr e : exprs)
        {
        if (e instanceof ThrowExpr)
            continue;
        if (!e.hasJavaClass())
            return null;
        Class c = e.getJavaClass();
        if (match == null)
            match = c;
        else if (match != c)
            return null;
        }
    }
    catch(Exception e)
    {
        return null;
    }
    return match;
}


static abstract class MethodExpr extends HostExpr{
  static void emitArgsAsArray(IPersistentVector args, ObjExpr objx, GeneratorAdapter gen){
    gen.push(args.count());
    gen.newArray(OBJECT_TYPE);
    for(int i = 0; i < args.count(); i++)
      {
      gen.dup();
      gen.push(i);
      ((Expr) args.nth(i)).emit(C.EXPRESSION, objx, gen);
      gen.arrayStore(OBJECT_TYPE);
      }
  }

  public static void emitTypedArgs(ObjExpr objx, GeneratorAdapter gen, Class[] parameterTypes, IPersistentVector args){
    for(int i = 0; i < parameterTypes.length; i++)
      {
      Expr e = (Expr) args.nth(i);
      try
        {
        final Class primc = maybePrimitiveType(e);
        if(primc == parameterTypes[i])
          {
          final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e;
          pe.emitUnboxed(C.EXPRESSION, objx, gen);
          }
        else if(primc == int.class && parameterTypes[i] == long.class)
          {
          final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e;
          pe.emitUnboxed(C.EXPRESSION, objx, gen);
          gen.visitInsn(I2L);
          }
        else if(primc == long.class && parameterTypes[i] == int.class)
          {
          final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e;
          pe.emitUnboxed(C.EXPRESSION, objx, gen);
          if(RT.booleanCast(RT.UNCHECKED_MATH.deref()))
            gen.invokeStatic(RT_TYPE, Method.getMethod("int uncheckedIntCast(long)"));
          else
            gen.invokeStatic(RT_TYPE, Method.getMethod("int intCast(long)"));
          }
        else if(primc == float.class && parameterTypes[i] == double.class)
          {
          final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e;
          pe.emitUnboxed(C.EXPRESSION, objx, gen);
          gen.visitInsn(F2D);
          }
        else if(primc == double.class && parameterTypes[i] == float.class)
          {
          final MaybePrimitiveExpr pe = (MaybePrimitiveExpr) e;
          pe.emitUnboxed(C.EXPRESSION, objx, gen);
          gen.visitInsn(D2F);
          }
        else
          {
          e.emit(C.EXPRESSION, objx, gen);
          HostExpr.emitUnboxArg(objx, gen, parameterTypes[i]);
          }
        }
      catch(Exception e1)
        {
        e1.printStackTrace(RT.errPrintWriter());
        }

      }
  }
}

static class InstanceMethodExpr extends MethodExpr{
  public final Expr target;
  public final String methodName;
  public final IPersistentVector args;
  public final String source;
  public final int line;
  public final int column;
  public final Symbol tag;
  public final java.lang.reflect.Method method;

  final static Method invokeInstanceMethodMethod =
      Method.getMethod("Object invokeInstanceMethod(Object,String,Object[])");


  public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, String methodName, IPersistentVector args)
      {
    this.source = source;
    this.line = line;
    this.column = column;
    this.args = args;
    this.methodName = methodName;
    this.target = target;
    this.tag = tag;
    if(target.hasJavaClass() && target.getJavaClass() != null)
      {
      List methods = Reflector.getMethods(target.getJavaClass(), args.count(), methodName, false);
      if(methods.isEmpty())
        {
        method = null;
        if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
          {
          RT.errPrintWriter()
            .format("Reflection warning, %s:%d:%d - call to method %s on %s can't be resolved (no such method).\n",
              SOURCE_PATH.deref(), line, column, methodName, target.getJavaClass().getName());
          }
        }
      else
        {
        int methodidx = 0;
        if(methods.size() > 1)
          {
          ArrayList<Class[]> params = new ArrayList();
          ArrayList<Class> rets = new ArrayList();
          for(int i = 0; i < methods.size(); i++)
            {
            java.lang.reflect.Method m = (java.lang.reflect.Method) methods.get(i);
            params.add(m.getParameterTypes());
            rets.add(m.getReturnType());
            }
          methodidx = getMatchingParams(methodName, params, args, rets);
          }
        java.lang.reflect.Method m =
            (java.lang.reflect.Method) (methodidx >= 0 ? methods.get(methodidx) : null);
        if(m != null && !Modifier.isPublic(m.getDeclaringClass().getModifiers()))
          {
          //public method of non-public class, try to find it in hierarchy
          m = Reflector.getAsMethodOfPublicBase(m.getDeclaringClass(), m);
          }
        method = m;
        if(method == null && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
          {
          RT.errPrintWriter()
            .format("Reflection warning, %s:%d:%d - call to method %s on %s can't be resolved (argument types: %s).\n",
              SOURCE_PATH.deref(), line, column, methodName, target.getJavaClass().getName(), getTypeStringForArgs(args));
          }
        }
      }
    else
      {
      method = null;
      if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
        {
        RT.errPrintWriter()
          .format("Reflection warning, %s:%d:%d - call to method %s can't be resolved (target class is unknown).\n",
            SOURCE_PATH.deref(), line, column, methodName);
        }
      }
  }

  public Object eval() {
    try
      {
      Object targetval = target.eval();
      Object[] argvals = new Object[args.count()];
      for(int i = 0; i < args.count(); i++)
        argvals[i] = ((Expr) args.nth(i)).eval();
      if(method != null)
        {
        LinkedList ms = new LinkedList();
        ms.add(method);
        return Reflector.invokeMatchingMethod(methodName, ms, targetval, argvals);
        }
      return Reflector.invokeInstanceMethod(targetval, methodName, argvals);
      }
    catch(Throwable e)
      {
      if(!(e instanceof CompilerException))
        throw new CompilerException(source, line, column, e);
      else
        throw (CompilerException) e;
      }
  }

  public boolean canEmitPrimitive(){
    return method != null && Util.isPrimitive(method.getReturnType());
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());
    if(method != null)
      {
      Type type = Type.getType(method.getDeclaringClass());
      target.emit(C.EXPRESSION, objx, gen);
      //if(!method.getDeclaringClass().isInterface())
      gen.checkCast(type);
      MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args);
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method));
      if(method.getDeclaringClass().isInterface())
        gen.invokeInterface(type, m);
      else
        gen.invokeVirtual(type, m);
      }
    else
      throw new UnsupportedOperationException("Unboxed emit of unknown member");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());
    if(method != null)
      {
      Type type = Type.getType(method.getDeclaringClass());
      target.emit(C.EXPRESSION, objx, gen);
      //if(!method.getDeclaringClass().isInterface())
      gen.checkCast(type);
      MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args);
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method));
      if(method.getDeclaringClass().isInterface())
        gen.invokeInterface(type, m);
      else
        gen.invokeVirtual(type, m);
      //if(context != C.STATEMENT || method.getReturnType() == Void.TYPE)
      HostExpr.emitBoxReturn(objx, gen, method.getReturnType());
      }
    else
      {
      target.emit(C.EXPRESSION, objx, gen);
      gen.push(methodName);
      emitArgsAsArray(args, objx, gen);
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      gen.invokeStatic(REFLECTOR_TYPE, invokeInstanceMethodMethod);
      }
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass(){
    return method != null || tag != null;
  }

  public Class getJavaClass() {
    return tag != null ? HostExpr.tagToClass(tag) : method.getReturnType();
  }
}


static class StaticMethodExpr extends MethodExpr{
  //final String className;
  public final Class c;
  public final String methodName;
  public final IPersistentVector args;
  public final String source;
  public final int line;
  public final int column;
  public final java.lang.reflect.Method method;
  public final Symbol tag;
  final static Method forNameMethod = Method.getMethod("Class forName(String)");
  final static Method invokeStaticMethodMethod =
      Method.getMethod("Object invokeStaticMethod(Class,String,Object[])");
  final static Keyword warnOnBoxedKeyword = Keyword.intern("warn-on-boxed");

  public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, String methodName, IPersistentVector args)
      {
    this.c = c;
    this.methodName = methodName;
    this.args = args;
    this.source = source;
    this.line = line;
    this.column = column;
    this.tag = tag;

    List methods = Reflector.getMethods(c, args.count(), methodName, true);
    if(methods.isEmpty())
      throw new IllegalArgumentException("No matching method: " + methodName);

    int methodidx = 0;
    if(methods.size() > 1)
      {
      ArrayList<Class[]> params = new ArrayList();
      ArrayList<Class> rets = new ArrayList();
      for(int i = 0; i < methods.size(); i++)
        {
        java.lang.reflect.Method m = (java.lang.reflect.Method) methods.get(i);
        params.add(m.getParameterTypes());
        rets.add(m.getReturnType());
        }
      methodidx = getMatchingParams(methodName, params, args, rets);
      }
    method = (java.lang.reflect.Method) (methodidx >= 0 ? methods.get(methodidx) : null);
    if(method == null && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
      {
      RT.errPrintWriter()
        .format("Reflection warning, %s:%d:%d - call to static method %s on %s can't be resolved (argument types: %s).\n",
          SOURCE_PATH.deref(), line, column, methodName, c.getName(), getTypeStringForArgs(args));
      }
    if(method != null && warnOnBoxedKeyword.equals(RT.UNCHECKED_MATH.deref()) && isBoxedMath(method))
      {
      RT.errPrintWriter()
        .format("Boxed math warning, %s:%d:%d - call: %s.\n",
            SOURCE_PATH.deref(), line, column, method.toString());
      }
  }

  public static boolean isBoxedMath(java.lang.reflect.Method m) {
    Class c = m.getDeclaringClass();
    if(c.equals(Numbers.class))
      {
      WarnBoxedMath boxedMath = m.getAnnotation(WarnBoxedMath.class);
      if(boxedMath != null)
        return boxedMath.value();

      Class[] argTypes = m.getParameterTypes();
      for(Class argType : argTypes)
        if(argType.equals(Object.class) || argType.equals(Number.class))
          return true;
      }
    return false;
  }

  public Object eval() {
    try
      {
      Object[] argvals = new Object[args.count()];
      for(int i = 0; i < args.count(); i++)
        argvals[i] = ((Expr) args.nth(i)).eval();
      if(method != null)
        {
        LinkedList ms = new LinkedList();
        ms.add(method);
        return Reflector.invokeMatchingMethod(methodName, ms, null, argvals);
        }
      return Reflector.invokeStaticMethod(c, methodName, argvals);
      }
    catch(Throwable e)
      {
      if(!(e instanceof CompilerException))
        throw new CompilerException(source, line, column, e);
      else
        throw (CompilerException) e;
      }
  }

  public boolean canEmitPrimitive(){
    return method != null && Util.isPrimitive(method.getReturnType());
  }

  public boolean canEmitIntrinsicPredicate(){
    return method != null && RT.get(Intrinsics.preds, method.toString()) != null;
  }

  public void emitIntrinsicPredicate(C context, ObjExpr objx, GeneratorAdapter gen, Label falseLabel){
    gen.visitLineNumber(line, gen.mark());
    if(method != null)
      {
      MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args);
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      Object[] predOps = (Object[]) RT.get(Intrinsics.preds, method.toString());
      for(int i=0;i<predOps.length-1;i++)
        gen.visitInsn((Integer)predOps[i]);
      gen.visitJumpInsn((Integer)predOps[predOps.length-1],falseLabel);
      }
    else
      throw new UnsupportedOperationException("Unboxed emit of unknown member");
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());
    if(method != null)
      {
      MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args);
      //Type type = Type.getObjectType(className.replace('.', '/'));
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      Object ops = RT.get(Intrinsics.ops, method.toString());
      if(ops != null)
        {
        if(ops instanceof Object[])
          {
          for(Object op : (Object[])ops)
            gen.visitInsn((Integer) op);
          }
        else
          gen.visitInsn((Integer) ops);
        }
      else
        {
        Type type = Type.getType(c);
        Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method));
        gen.invokeStatic(type, m);
        }
      }
    else
      throw new UnsupportedOperationException("Unboxed emit of unknown member");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());
    if(method != null)
      {
      MethodExpr.emitTypedArgs(objx, gen, method.getParameterTypes(), args);
      //Type type = Type.getObjectType(className.replace('.', '/'));
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      Type type = Type.getType(c);
      Method m = new Method(methodName, Type.getReturnType(method), Type.getArgumentTypes(method));
      gen.invokeStatic(type, m);
      //if(context != C.STATEMENT || method.getReturnType() == Void.TYPE)
      Class retClass = method.getReturnType();
      if(context == C.STATEMENT)
        {
        if(retClass == long.class || retClass == double.class)
          gen.pop2();
        else if(retClass != void.class)
          gen.pop();
        }
      else
        {
        HostExpr.emitBoxReturn(objx, gen, method.getReturnType());
        }
      }
    else
      {
      gen.push(c.getName());
      gen.invokeStatic(CLASS_TYPE, forNameMethod);
      gen.push(methodName);
      emitArgsAsArray(args, objx, gen);
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      gen.invokeStatic(REFLECTOR_TYPE, invokeStaticMethodMethod);
      if(context == C.STATEMENT)
        gen.pop();
      }
  }

  public boolean hasJavaClass(){
    return method != null || tag != null;
  }

  public Class getJavaClass() {
    return tag != null ? HostExpr.tagToClass(tag) : method.getReturnType();
  }
}

static class UnresolvedVarExpr implements Expr{
  public final Symbol symbol;

  public UnresolvedVarExpr(Symbol symbol){
    this.symbol = symbol;
  }

  public boolean hasJavaClass(){
    return false;
  }

  public Class getJavaClass() {
    throw new IllegalArgumentException(
        "UnresolvedVarExpr has no Java class");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
  }

  public Object eval() {
    throw new IllegalArgumentException(
        "UnresolvedVarExpr cannot be evalled");
  }
}

static class NumberExpr extends LiteralExpr implements MaybePrimitiveExpr{
  final Number n;
  public final int id;

  public NumberExpr(Number n){
    this.n = n;
    this.id = registerConstant(n);
  }

  Object val(){
    return n;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    if(context != C.STATEMENT)
      {
      objx.emitConstant(gen, id);
//      emitUnboxed(context,objx,gen);
//      HostExpr.emitBoxReturn(objx,gen,getJavaClass());
      }
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass(){
    if(n instanceof Integer)
      return long.class;
    else if(n instanceof Double)
      return double.class;
    else if(n instanceof Long)
      return long.class;
    else
      throw new IllegalStateException("Unsupported Number type: " + n.getClass().getName());
  }

  public boolean canEmitPrimitive(){
    return true;
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    if(n instanceof Integer)
      gen.push(n.longValue());
    else if(n instanceof Double)
      gen.push(n.doubleValue());
    else if(n instanceof Long)
      gen.push(n.longValue());
  }

  static public Expr parse(Number form){
    if(form instanceof Integer
      || form instanceof Double
      || form instanceof Long)
      return new NumberExpr(form);
    else
      return new ConstantExpr(form);
  }
}

static class ConstantExpr extends LiteralExpr{
  //stuff quoted vals in classloader at compile time, pull out at runtime
  //this won't work for static compilation...
  public final Object v;
  public final int id;

  public ConstantExpr(Object v){
    this.v = v;
    this.id = registerConstant(v);
//    this.id = RT.nextID();
//    DynamicClassLoader loader = (DynamicClassLoader) LOADER.get();
//    loader.registerQuotedVal(id, v);
  }

  Object val(){
    return v;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    objx.emitConstant(gen, id);

    if(context == C.STATEMENT)
      {
      gen.pop();
//      gen.loadThis();
//      gen.invokeVirtual(OBJECT_TYPE, getClassMethod);
//      gen.invokeVirtual(CLASS_TYPE, getClassLoaderMethod);
//      gen.checkCast(DYNAMIC_CLASSLOADER_TYPE);
//      gen.push(id);
//      gen.invokeVirtual(DYNAMIC_CLASSLOADER_TYPE, getQuotedValMethod);
      }
  }

  public boolean hasJavaClass(){
    return Modifier.isPublic(v.getClass().getModifiers());
    //return false;
  }

  public Class getJavaClass() {
    if(v instanceof APersistentMap)
      return APersistentMap.class;
    else if (v instanceof APersistentSet)
      return APersistentSet.class;
    else if (v instanceof APersistentVector)
      return APersistentVector.class;
    else
      return v.getClass();
    //throw new IllegalArgumentException("Has no Java class");
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object form){
      Object v = RT.second(form);

      if(v == null)
        return NIL_EXPR;
      else if(v == Boolean.TRUE)
        return TRUE_EXPR;
      else if(v == Boolean.FALSE)
        return FALSE_EXPR;
      if(v instanceof Number)
        return NumberExpr.parse((Number)v);
      else if(v instanceof String)
        return new StringExpr((String) v);
      else if(v instanceof IPersistentCollection && ((IPersistentCollection) v).count() == 0)
        return new EmptyExpr(v);
      else
        return new ConstantExpr(v);
    }
  }
}

static class NilExpr extends LiteralExpr{
  Object val(){
    return null;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitInsn(Opcodes.ACONST_NULL);
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass(){
    return true;
  }

  public Class getJavaClass() {
    return null;
  }
}

final static NilExpr NIL_EXPR = new NilExpr();

static class BooleanExpr extends LiteralExpr{
  public final boolean val;


  public BooleanExpr(boolean val){
    this.val = val;
  }

  Object val(){
    return val ? RT.T : RT.F;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    if(val)
      gen.getStatic(BOOLEAN_OBJECT_TYPE, "TRUE", BOOLEAN_OBJECT_TYPE);
    else
      gen.getStatic(BOOLEAN_OBJECT_TYPE, "FALSE", BOOLEAN_OBJECT_TYPE);
    if(context == C.STATEMENT)
      {
      gen.pop();
      }
  }

  public boolean hasJavaClass(){
    return true;
  }

  public Class getJavaClass() {
    return Boolean.class;
  }
}

final static BooleanExpr TRUE_EXPR = new BooleanExpr(true);
final static BooleanExpr FALSE_EXPR = new BooleanExpr(false);

static class StringExpr extends LiteralExpr{
  public final String str;

  public StringExpr(String str){
    this.str = str;
  }

  Object val(){
    return str;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    if(context != C.STATEMENT)
      gen.push(str);
  }

  public boolean hasJavaClass(){
    return true;
  }

  public Class getJavaClass() {
    return String.class;
  }
}


static class MonitorEnterExpr extends UntypedExpr{
  final Expr target;

  public MonitorEnterExpr(Expr target){
    this.target = target;
  }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval monitor-enter");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    target.emit(C.EXPRESSION, objx, gen);
    gen.monitorEnter();
    NIL_EXPR.emit(context, objx, gen);
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object form) {
      return new MonitorEnterExpr(analyze(C.EXPRESSION, RT.second(form)));
    }
  }
}

static class MonitorExitExpr extends UntypedExpr{
  final Expr target;

  public MonitorExitExpr(Expr target){
    this.target = target;
  }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval monitor-exit");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    target.emit(C.EXPRESSION, objx, gen);
    gen.monitorExit();
    NIL_EXPR.emit(context, objx, gen);
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object form) {
      return new MonitorExitExpr(analyze(C.EXPRESSION, RT.second(form)));
    }
  }

}

public static class TryExpr implements Expr{
  public final Expr tryExpr;
  public final Expr finallyExpr;
  public final PersistentVector catchExprs;
  public final int retLocal;
  public final int finallyLocal;

  public static class CatchClause{
    //final String className;
    public final Class c;
    public final LocalBinding lb;
    public final Expr handler;
    Label label;
    Label endLabel;


    public CatchClause(Class c, LocalBinding lb, Expr handler){
      this.c = c;
      this.lb = lb;
      this.handler = handler;
    }
  }

  public TryExpr(Expr tryExpr, PersistentVector catchExprs, Expr finallyExpr, int retLocal, int finallyLocal){
    this.tryExpr = tryExpr;
    this.catchExprs = catchExprs;
    this.finallyExpr = finallyExpr;
    this.retLocal = retLocal;
    this.finallyLocal = finallyLocal;
  }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval try");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    Label startTry = gen.newLabel();
    Label endTry = gen.newLabel();
    Label end = gen.newLabel();
    Label ret = gen.newLabel();
    Label finallyLabel = gen.newLabel();
    for(int i = 0; i < catchExprs.count(); i++)
      {
      CatchClause clause = (CatchClause) catchExprs.nth(i);
      clause.label = gen.newLabel();
      clause.endLabel = gen.newLabel();
      }

    gen.mark(startTry);
    tryExpr.emit(context, objx, gen);
    if(context != C.STATEMENT)
      gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), retLocal);
    gen.mark(endTry);
    if(finallyExpr != null)
      finallyExpr.emit(C.STATEMENT, objx, gen);
    gen.goTo(ret);

    for(int i = 0; i < catchExprs.count(); i++)
      {
      CatchClause clause = (CatchClause) catchExprs.nth(i);
      gen.mark(clause.label);
      //exception should be on stack
      //put in clause local
      gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), clause.lb.idx);
      clause.handler.emit(context, objx, gen);
      if(context != C.STATEMENT)
        gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), retLocal);
      gen.mark(clause.endLabel);

      if(finallyExpr != null)
        finallyExpr.emit(C.STATEMENT, objx, gen);
      gen.goTo(ret);
      }
    if(finallyExpr != null)
      {
      gen.mark(finallyLabel);
      //exception should be on stack
      gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), finallyLocal);
      finallyExpr.emit(C.STATEMENT, objx, gen);
      gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ILOAD), finallyLocal);
      gen.throwException();
      }
    gen.mark(ret);
    if(context != C.STATEMENT)
      gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ILOAD), retLocal);
    gen.mark(end);
    for(int i = 0; i < catchExprs.count(); i++)
      {
      CatchClause clause = (CatchClause) catchExprs.nth(i);
      gen.visitTryCatchBlock(startTry, endTry, clause.label, clause.c.getName().replace('.', '/'));
      }
    if(finallyExpr != null)
      {
        gen.visitTryCatchBlock(startTry, endTry, finallyLabel, null);
        for(int i = 0; i < catchExprs.count(); i++)
          {
          CatchClause clause = (CatchClause) catchExprs.nth(i);
          gen.visitTryCatchBlock(clause.label, clause.endLabel, finallyLabel, null);
          }
      }
    for(int i = 0; i < catchExprs.count(); i++)
      {
      CatchClause clause = (CatchClause) catchExprs.nth(i);
      gen.visitLocalVariable(clause.lb.name, "Ljava/lang/Object;", null, clause.label, clause.endLabel,
                             clause.lb.idx);
      }
  }

  public boolean hasJavaClass() {
    return tryExpr.hasJavaClass();
  }

  public Class getJavaClass() {
    return tryExpr.getJavaClass();
  }

  static class Parser implements IParser{

    public Expr parse(C context, Object frm) {
      ISeq form = (ISeq) frm;
//      if(context == C.EVAL || context == C.EXPRESSION)
      if(context != C.RETURN)
        return analyze(context, RT.list(RT.list(FNONCE, PersistentVector.EMPTY, form)));

      //(try try-expr* catch-expr* finally-expr?)
      //catch-expr: (catch class sym expr*)
      //finally-expr: (finally expr*)

      PersistentVector body = PersistentVector.EMPTY;
      PersistentVector catches = PersistentVector.EMPTY;
            Expr bodyExpr = null;
      Expr finallyExpr = null;
      boolean caught = false;

      int retLocal = getAndIncLocalNum();
      int finallyLocal = getAndIncLocalNum();
      for(ISeq fs = form.next(); fs != null; fs = fs.next())
        {
        Object f = fs.first();
        Object op = (f instanceof ISeq) ? ((ISeq) f).first() : null;
        if(!Util.equals(op, CATCH) && !Util.equals(op, FINALLY))
          {
          if(caught)
                                            throw Util.runtimeException("Only catch or finally clause can follow catch in try expression");
          body = body.cons(f);
          }
        else
          {
                                            if(bodyExpr == null)
                                                try {
                                                    Var.pushThreadBindings(RT.map(NO_RECUR, true));
                                        bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body));
                                                } finally {
                                                    Var.popThreadBindings();
                                                }
          if(Util.equals(op, CATCH))
            {
            Class c = HostExpr.maybeClass(RT.second(f), false);
            if(c == null)
              throw new IllegalArgumentException("Unable to resolve classname: " + RT.second(f));
            if(!(RT.third(f) instanceof Symbol))
              throw new IllegalArgumentException(
                  "Bad binding form, expected symbol, got: " + RT.third(f));
            Symbol sym = (Symbol) RT.third(f);
            if(sym.getNamespace() != null)
              throw Util.runtimeException("Can't bind qualified name:" + sym);

            IPersistentMap dynamicBindings = RT.map(LOCAL_ENV, LOCAL_ENV.deref(),
                                                    NEXT_LOCAL_NUM, NEXT_LOCAL_NUM.deref(),
                                                    IN_CATCH_FINALLY, RT.T);
            try
              {
              Var.pushThreadBindings(dynamicBindings);
              LocalBinding lb = registerLocal(sym,
                                              (Symbol) (RT.second(f) instanceof Symbol ? RT.second(f)
                                                                                       : null),
                                              null,false);
              Expr handler = (new BodyExpr.Parser()).parse(C.EXPRESSION, RT.next(RT.next(RT.next(f))));
              catches = catches.cons(new CatchClause(c, lb, handler));
              }
            finally
              {
              Var.popThreadBindings();
              }
            caught = true;
            }
          else //finally
            {
            if(fs.next() != null)
              throw Util.runtimeException("finally clause must be last in try expression");
            try
              {
              Var.pushThreadBindings(RT.map(IN_CATCH_FINALLY, RT.T));
              finallyExpr = (new BodyExpr.Parser()).parse(C.STATEMENT, RT.next(f));
              }
            finally
              {
              Var.popThreadBindings();
              }
            }
          }
        }
                        if(bodyExpr == null) {
                            try
                                {
                                    Var.pushThreadBindings(RT.map(NO_RECUR, true));
            bodyExpr = (new BodyExpr.Parser()).parse(C.EXPRESSION, RT.seq(body));
                                }
                            finally
                                {
                                    Var.popThreadBindings();
                                }
                        }

      return new TryExpr(bodyExpr, catches, finallyExpr, retLocal,
                         finallyLocal);
    }
  }
}

//static class TryFinallyExpr implements Expr{
//  final Expr tryExpr;
//  final Expr finallyExpr;
//
//
//  public TryFinallyExpr(Expr tryExpr, Expr finallyExpr){
//    this.tryExpr = tryExpr;
//    this.finallyExpr = finallyExpr;
//  }
//
//  public Object eval() {
//    throw new UnsupportedOperationException("Can't eval try");
//  }
//
//  public void emit(C context, FnExpr fn, GeneratorAdapter gen){
//    Label startTry = gen.newLabel();
//    Label endTry = gen.newLabel();
//    Label end = gen.newLabel();
//    Label finallyLabel = gen.newLabel();
//    gen.visitTryCatchBlock(startTry, endTry, finallyLabel, null);
//    gen.mark(startTry);
//    tryExpr.emit(context, fn, gen);
//    gen.mark(endTry);
//    finallyExpr.emit(C.STATEMENT, fn, gen);
//    gen.goTo(end);
//    gen.mark(finallyLabel);
//    //exception should be on stack
//    finallyExpr.emit(C.STATEMENT, fn, gen);
//    gen.throwException();
//    gen.mark(end);
//  }
//
//  public boolean hasJavaClass() {
//    return tryExpr.hasJavaClass();
//  }
//
//  public Class getJavaClass() {
//    return tryExpr.getJavaClass();
//  }
//
//  static class Parser implements IParser{
//    public Expr parse(C context, Object frm) {
//      ISeq form = (ISeq) frm;
//      //(try-finally try-expr finally-expr)
//      if(form.count() != 3)
//        throw new IllegalArgumentException(
//            "Wrong number of arguments, expecting: (try-finally try-expr finally-expr) ");
//
//      if(context == C.EVAL || context == C.EXPRESSION)
//        return analyze(context, RT.list(RT.list(FN, PersistentVector.EMPTY, form)));
//
//      return new TryFinallyExpr(analyze(context, RT.second(form)),
//                                analyze(C.STATEMENT, RT.third(form)));
//    }
//  }
//}

static class ThrowExpr extends UntypedExpr{
  public final Expr excExpr;

  public ThrowExpr(Expr excExpr){
    this.excExpr = excExpr;
  }


  public Object eval() {
    throw Util.runtimeException("Can't eval throw");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    excExpr.emit(C.EXPRESSION, objx, gen);
    gen.checkCast(THROWABLE_TYPE);
    gen.throwException();
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object form) {
      if(context == C.EVAL)
        return analyze(context, RT.list(RT.list(FNONCE, PersistentVector.EMPTY, form)));
      return new ThrowExpr(analyze(C.EXPRESSION, RT.second(form)));
    }
  }
}


static public boolean subsumes(Class[] c1, Class[] c2){
  //presumes matching lengths
  Boolean better = false;
  for(int i = 0; i < c1.length; i++)
    {
    if(c1[i] != c2[i])// || c2[i].isPrimitive() && c1[i] == Object.class))
      {
      if(!c1[i].isPrimitive() && c2[i].isPrimitive()
         //|| Number.class.isAssignableFrom(c1[i]) && c2[i].isPrimitive()
         ||
         c2[i].isAssignableFrom(c1[i]))
        better = true;
      else
        return false;
      }
    }
  return better;
}

static String getTypeStringForArgs(IPersistentVector args){
  StringBuilder sb = new StringBuilder();
  for(int i = 0; i < args.count(); i++)
    {
    Expr arg = (Expr) args.nth(i);
    if (i > 0) sb.append(", ");
    sb.append(arg.hasJavaClass() ? arg.getJavaClass().getName() : "unknown");
    }
  return sb.toString();
}

static int getMatchingParams(String methodName, ArrayList<Class[]> paramlists, IPersistentVector argexprs,
                             List<Class> rets)
    {
  //presumes matching lengths
  int matchIdx = -1;
  boolean tied = false;
    boolean foundExact = false;
  for(int i = 0; i < paramlists.size(); i++)
    {
    boolean match = true;
    ISeq aseq = argexprs.seq();
    int exact = 0;
    for(int p = 0; match && p < argexprs.count() && aseq != null; ++p, aseq = aseq.next())
      {
      Expr arg = (Expr) aseq.first();
      Class aclass = arg.hasJavaClass() ? arg.getJavaClass() : Object.class;
      Class pclass = paramlists.get(i)[p];
      if(arg.hasJavaClass() && aclass == pclass)
        exact++;
      else
        match = Reflector.paramArgTypeMatch(pclass, aclass);
      }
    if(exact == argexprs.count())
            {
            if(!foundExact || matchIdx == -1 || rets.get(matchIdx).isAssignableFrom(rets.get(i)))
                matchIdx = i;
            tied = false;
            foundExact = true;
            }
    else if(match && !foundExact)
      {
      if(matchIdx == -1)
        matchIdx = i;
      else
        {
        if(subsumes(paramlists.get(i), paramlists.get(matchIdx)))
          {
          matchIdx = i;
          tied = false;
          }
        else if(Arrays.equals(paramlists.get(matchIdx), paramlists.get(i)))
          {
          if(rets.get(matchIdx).isAssignableFrom(rets.get(i)))
            matchIdx = i;
          }
        else if(!(subsumes(paramlists.get(matchIdx), paramlists.get(i))))
            tied = true;
        }
      }
    }
  if(tied)
    throw new IllegalArgumentException("More than one matching method found: " + methodName);

  return matchIdx;
}

public static class NewExpr implements Expr{
  public final IPersistentVector args;
  public final Constructor ctor;
  public final Class c;
  final static Method invokeConstructorMethod =
      Method.getMethod("Object invokeConstructor(Class,Object[])");
//  final static Method forNameMethod = Method.getMethod("Class classForName(String)");
  final static Method forNameMethod = Method.getMethod("Class forName(String)");


  public NewExpr(Class c, IPersistentVector args, int line, int column) {
    this.args = args;
    this.c = c;
    Constructor[] allctors = c.getConstructors();
    ArrayList ctors = new ArrayList();
    ArrayList<Class[]> params = new ArrayList();
    ArrayList<Class> rets = new ArrayList();
    for(int i = 0; i < allctors.length; i++)
      {
      Constructor ctor = allctors[i];
      if(ctor.getParameterTypes().length == args.count())
        {
        ctors.add(ctor);
        params.add(ctor.getParameterTypes());
        rets.add(c);
        }
      }
    if(ctors.isEmpty())
      throw new IllegalArgumentException("No matching ctor found for " + c);

    int ctoridx = 0;
    if(ctors.size() > 1)
      {
      ctoridx = getMatchingParams(c.getName(), params, args, rets);
      }

    this.ctor = ctoridx >= 0 ? (Constructor) ctors.get(ctoridx) : null;
    if(ctor == null && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
      {
      RT.errPrintWriter()
              .format("Reflection warning, %s:%d:%d - call to %s ctor can't be resolved.\n",
                      SOURCE_PATH.deref(), line, column, c.getName());
      }
  }

  public Object eval() {
    Object[] argvals = new Object[args.count()];
    for(int i = 0; i < args.count(); i++)
      argvals[i] = ((Expr) args.nth(i)).eval();
    if(this.ctor != null)
      {
      try
        {
        return ctor.newInstance(Reflector.boxArgs(ctor.getParameterTypes(), argvals));
        }
      catch(Exception e)
        {
        throw Util.sneakyThrow(e);
        }
      }
    return Reflector.invokeConstructor(c, argvals);
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    if(this.ctor != null)
      {
      Type type = getType(c);
      gen.newInstance(type);
      gen.dup();
      MethodExpr.emitTypedArgs(objx, gen, ctor.getParameterTypes(), args);
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      gen.invokeConstructor(type, new Method("<init>", Type.getConstructorDescriptor(ctor)));
      }
    else
      {
      gen.push(destubClassName(c.getName()));
      gen.invokeStatic(CLASS_TYPE, forNameMethod);
      MethodExpr.emitArgsAsArray(args, objx, gen);
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      gen.invokeStatic(REFLECTOR_TYPE, invokeConstructorMethod);
      }
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass(){
    return true;
  }

  public Class getJavaClass() {
    return c;
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object frm) {
      int line = lineDeref();
      int column = columnDeref();
      ISeq form = (ISeq) frm;
      //(new Classname args...)
      if(form.count() < 2)
        throw Util.runtimeException("wrong number of arguments, expecting: (new Classname args...)");
      Class c = HostExpr.maybeClass(RT.second(form), false);
      if(c == null)
        throw new IllegalArgumentException("Unable to resolve classname: " + RT.second(form));
      PersistentVector args = PersistentVector.EMPTY;
      for(ISeq s = RT.next(RT.next(form)); s != null; s = s.next())
        args = args.cons(analyze(context == C.EVAL ? context : C.EXPRESSION, s.first()));
      return new NewExpr(c, args, line, column);
    }
  }

}

public static class MetaExpr implements Expr{
  public final Expr expr;
  public final Expr meta;
  final static Type IOBJ_TYPE = Type.getType(IObj.class);
  final static Method withMetaMethod = Method.getMethod("clojure.lang.IObj withMeta(clojure.lang.IPersistentMap)");


  public MetaExpr(Expr expr, Expr meta){
    this.expr = expr;
    this.meta = meta;
  }

  public Object eval() {
    return ((IObj) expr.eval()).withMeta((IPersistentMap) meta.eval());
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    expr.emit(C.EXPRESSION, objx, gen);
    gen.checkCast(IOBJ_TYPE);
    meta.emit(C.EXPRESSION, objx, gen);
    gen.checkCast(IPERSISTENTMAP_TYPE);
    gen.invokeInterface(IOBJ_TYPE, withMetaMethod);
    if(context == C.STATEMENT)
      {
      gen.pop();
      }
  }

  public boolean hasJavaClass() {
    return expr.hasJavaClass();
  }

  public Class getJavaClass() {
    return expr.getJavaClass();
  }
}

public static class IfExpr implements Expr, MaybePrimitiveExpr{
  public final Expr testExpr;
  public final Expr thenExpr;
  public final Expr elseExpr;
  public final int line;
  public final int column;


  public IfExpr(int line, int column, Expr testExpr, Expr thenExpr, Expr elseExpr){
    this.testExpr = testExpr;
    this.thenExpr = thenExpr;
    this.elseExpr = elseExpr;
    this.line = line;
    this.column = column;
  }

  public Object eval() {
    Object t = testExpr.eval();
    if(t != null && t != Boolean.FALSE)
      return thenExpr.eval();
    return elseExpr.eval();
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    doEmit(context, objx, gen,false);
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    doEmit(context, objx, gen, true);
  }

  public void doEmit(C context, ObjExpr objx, GeneratorAdapter gen, boolean emitUnboxed){
    Label nullLabel = gen.newLabel();
    Label falseLabel = gen.newLabel();
    Label endLabel = gen.newLabel();

    gen.visitLineNumber(line, gen.mark());

    if(testExpr instanceof StaticMethodExpr && ((StaticMethodExpr)testExpr).canEmitIntrinsicPredicate())
      {
      ((StaticMethodExpr) testExpr).emitIntrinsicPredicate(C.EXPRESSION, objx, gen, falseLabel);
      }
    else if(maybePrimitiveType(testExpr) == boolean.class)
      {
      ((MaybePrimitiveExpr) testExpr).emitUnboxed(C.EXPRESSION, objx, gen);
      gen.ifZCmp(gen.EQ, falseLabel);
      }
    else
      {
      testExpr.emit(C.EXPRESSION, objx, gen);
      gen.dup();
      gen.ifNull(nullLabel);
      gen.getStatic(BOOLEAN_OBJECT_TYPE, "FALSE", BOOLEAN_OBJECT_TYPE);
      gen.visitJumpInsn(IF_ACMPEQ, falseLabel);
      }
    if(emitUnboxed)
      ((MaybePrimitiveExpr)thenExpr).emitUnboxed(context, objx, gen);
    else
      thenExpr.emit(context, objx, gen);
    gen.goTo(endLabel);
    gen.mark(nullLabel);
    gen.pop();
    gen.mark(falseLabel);
    if(emitUnboxed)
      ((MaybePrimitiveExpr)elseExpr).emitUnboxed(context, objx, gen);
    else
      elseExpr.emit(context, objx, gen);
    gen.mark(endLabel);
  }

  public boolean hasJavaClass() {
    return thenExpr.hasJavaClass()
           && elseExpr.hasJavaClass()
           &&
           (thenExpr.getJavaClass() == elseExpr.getJavaClass()
            || thenExpr.getJavaClass() == RECUR_CLASS
        || elseExpr.getJavaClass() == RECUR_CLASS           
            || (thenExpr.getJavaClass() == null && !elseExpr.getJavaClass().isPrimitive())
            || (elseExpr.getJavaClass() == null && !thenExpr.getJavaClass().isPrimitive()));
  }

  public boolean canEmitPrimitive(){
    try
      {
      return thenExpr instanceof MaybePrimitiveExpr
             && elseExpr instanceof MaybePrimitiveExpr
             && (thenExpr.getJavaClass() == elseExpr.getJavaClass()
                 || thenExpr.getJavaClass() == RECUR_CLASS
                 || elseExpr.getJavaClass() == RECUR_CLASS)
             && ((MaybePrimitiveExpr)thenExpr).canEmitPrimitive()
           && ((MaybePrimitiveExpr)elseExpr).canEmitPrimitive();
      }
    catch(Exception e)
      {
      return false;
      }
  }

  public Class getJavaClass() {
    Class thenClass = thenExpr.getJavaClass();
    if(thenClass != null && thenClass != RECUR_CLASS)
      return thenClass;
    return elseExpr.getJavaClass();
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object frm) {
      ISeq form = (ISeq) frm;
      //(if test then) or (if test then else)
      if(form.count() > 4)
        throw Util.runtimeException("Too many arguments to if");
      else if(form.count() < 3)
        throw Util.runtimeException("Too few arguments to if");
            PathNode branch = new PathNode(PATHTYPE.BRANCH, (PathNode) CLEAR_PATH.get());
            Expr testexpr = analyze(context == C.EVAL ? context : C.EXPRESSION, RT.second(form));
            Expr thenexpr, elseexpr;
            try {
                Var.pushThreadBindings(
                        RT.map(CLEAR_PATH, new PathNode(PATHTYPE.PATH,branch)));
                thenexpr = analyze(context, RT.third(form));
                }
            finally{
                Var.popThreadBindings();
                }
            try {
                Var.pushThreadBindings(
                        RT.map(CLEAR_PATH, new PathNode(PATHTYPE.PATH,branch)));
                elseexpr = analyze(context, RT.fourth(form));
                }
            finally{
                Var.popThreadBindings();
                }
      return new IfExpr(lineDeref(),
                              columnDeref(),
                        testexpr,
                        thenexpr,
                        elseexpr);
    }
  }
}

static final public IPersistentMap CHAR_MAP =
    PersistentHashMap.create('-', "_",
//                             '.', "_DOT_",
':', "_COLON_",
'+', "_PLUS_",
'>', "_GT_",
'<', "_LT_",
'=', "_EQ_",
'~', "_TILDE_",
'!', "_BANG_",
'@', "_CIRCA_",
'#', "_SHARP_",
'\'', "_SINGLEQUOTE_",
'"', "_DOUBLEQUOTE_",
'%', "_PERCENT_",
'^', "_CARET_",
'&', "_AMPERSAND_",
'*', "_STAR_",
'|', "_BAR_",
'{', "_LBRACE_",
'}', "_RBRACE_",
'[', "_LBRACK_",
']', "_RBRACK_",
'/', "_SLASH_",
'\\', "_BSLASH_",
'?', "_QMARK_");

static final public IPersistentMap DEMUNGE_MAP;
static final public Pattern DEMUNGE_PATTERN;

static {
  // DEMUNGE_MAP maps strings to characters in the opposite
  // direction that CHAR_MAP does, plus it maps "$" to '/'
  IPersistentMap m = RT.map("$", '/');
  for(ISeq s = RT.seq(CHAR_MAP); s != null; s = s.next())
    {
    IMapEntry e = (IMapEntry) s.first();
    Character origCh = (Character) e.key();
    String escapeStr = (String) e.val();
    m = m.assoc(escapeStr, origCh);
    }
  DEMUNGE_MAP = m;

  // DEMUNGE_PATTERN searches for the first of any occurrence of
  // the strings that are keys of DEMUNGE_MAP.
  // Note: Regex matching rules mean that #"_|_COLON_" "_COLON_"
       // returns "_", but #"_COLON_|_" "_COLON_" returns "_COLON_"
       // as desired.  Sorting string keys of DEMUNGE_MAP from longest to
       // shortest ensures correct matching behavior, even if some strings are
  // prefixes of others.
  Object[] mungeStrs = RT.toArray(RT.keys(m));
  Arrays.sort(mungeStrs, new Comparator() {
                public int compare(Object s1, Object s2) {
                    return ((String) s2).length() - ((String) s1).length();
                }});
  StringBuilder sb = new StringBuilder();
  boolean first = true;
  for(Object s : mungeStrs)
    {
    String escapeStr = (String) s;
    if (!first)
      sb.append("|");
    first = false;
    sb.append("\\Q");
    sb.append(escapeStr);
    sb.append("\\E");
    }
  DEMUNGE_PATTERN = Pattern.compile(sb.toString());
}

static public String munge(String name){
  StringBuilder sb = new StringBuilder();
  for(char c : name.toCharArray())
    {
    String sub = (String) CHAR_MAP.valAt(c);
    if(sub != null)
      sb.append(sub);
    else
      sb.append(c);
    }
  return sb.toString();
}

static public String demunge(String mungedName){
  StringBuilder sb = new StringBuilder();
  Matcher m = DEMUNGE_PATTERN.matcher(mungedName);
  int lastMatchEnd = 0;
  while (m.find())
    {
    int start = m.start();
    int end = m.end();
    // Keep everything before the match
    sb.append(mungedName.substring(lastMatchEnd, start));
    lastMatchEnd = end;
    // Replace the match with DEMUNGE_MAP result
    Character origCh = (Character) DEMUNGE_MAP.valAt(m.group());
    sb.append(origCh);
    }
  // Keep everything after the last match
  sb.append(mungedName.substring(lastMatchEnd));
  return sb.toString();
}

public static class EmptyExpr implements Expr{
  public final Object coll;
  final static Type HASHMAP_TYPE = Type.getType(PersistentArrayMap.class);
  final static Type HASHSET_TYPE = Type.getType(PersistentHashSet.class);
  final static Type VECTOR_TYPE = Type.getType(PersistentVector.class);
  final static Type LIST_TYPE = Type.getType(PersistentList.class);
  final static Type EMPTY_LIST_TYPE = Type.getType(PersistentList.EmptyList.class);


  public EmptyExpr(Object coll){
    this.coll = coll;
  }

  public Object eval() {
    return coll;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    if(coll instanceof IPersistentList)
      gen.getStatic(LIST_TYPE, "EMPTY", EMPTY_LIST_TYPE);
    else if(coll instanceof IPersistentVector)
      gen.getStatic(VECTOR_TYPE, "EMPTY", VECTOR_TYPE);
    else if(coll instanceof IPersistentMap)
        gen.getStatic(HASHMAP_TYPE, "EMPTY", HASHMAP_TYPE);
      else if(coll instanceof IPersistentSet)
          gen.getStatic(HASHSET_TYPE, "EMPTY", HASHSET_TYPE);
        else
          throw new UnsupportedOperationException("Unknown Collection type");
    if(context == C.STATEMENT)
      {
      gen.pop();
      }
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    if(coll instanceof IPersistentList)
      return IPersistentList.class;
    else if(coll instanceof IPersistentVector)
      return IPersistentVector.class;
    else if(coll instanceof IPersistentMap)
        return IPersistentMap.class;
      else if(coll instanceof IPersistentSet)
          return IPersistentSet.class;
        else
          throw new UnsupportedOperationException("Unknown Collection type");
  }
}

public static class ListExpr implements Expr{
  public final IPersistentVector args;
  final static Method arrayToListMethod = Method.getMethod("clojure.lang.ISeq arrayToList(Object[])");


  public ListExpr(IPersistentVector args){
    this.args = args;
  }

  public Object eval() {
    IPersistentVector ret = PersistentVector.EMPTY;
    for(int i = 0; i < args.count(); i++)
      ret = (IPersistentVector) ret.cons(((Expr) args.nth(i)).eval());
    return ret.seq();
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    MethodExpr.emitArgsAsArray(args, objx, gen);
    gen.invokeStatic(RT_TYPE, arrayToListMethod);
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    return IPersistentList.class;
  }

}

public static class MapExpr implements Expr{
  public final IPersistentVector keyvals;
  final static Method mapMethod = Method.getMethod("clojure.lang.IPersistentMap map(Object[])");
  final static Method mapUniqueKeysMethod = Method.getMethod("clojure.lang.IPersistentMap mapUniqueKeys(Object[])");


  public MapExpr(IPersistentVector keyvals){
    this.keyvals = keyvals;
  }

  public Object eval() {
    Object[] ret = new Object[keyvals.count()];
    for(int i = 0; i < keyvals.count(); i++)
      ret[i] = ((Expr) keyvals.nth(i)).eval();
    return RT.map(ret);
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    boolean allKeysConstant = true;
    boolean allConstantKeysUnique = true;
    IPersistentSet constantKeys = PersistentHashSet.EMPTY;
    for(int i = 0; i < keyvals.count(); i+=2)
      {
      Expr k = (Expr) keyvals.nth(i);
      if(k instanceof LiteralExpr)
        {
        Object kval = k.eval();
        if (constantKeys.contains(kval))
          allConstantKeysUnique = false;
        else
          constantKeys = (IPersistentSet)constantKeys.cons(kval);
        }
      else
        allKeysConstant = false;
      }
    MethodExpr.emitArgsAsArray(keyvals, objx, gen);
    if((allKeysConstant && allConstantKeysUnique) || (keyvals.count() <= 2))
      gen.invokeStatic(RT_TYPE, mapUniqueKeysMethod);
    else
      gen.invokeStatic(RT_TYPE, mapMethod);
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    return IPersistentMap.class;
  }


  static public Expr parse(C context, IPersistentMap form) {
    IPersistentVector keyvals = PersistentVector.EMPTY;
    boolean keysConstant = true;
    boolean valsConstant = true;
    boolean allConstantKeysUnique = true;
    IPersistentSet constantKeys = PersistentHashSet.EMPTY;
    for(ISeq s = RT.seq(form); s != null; s = s.next())
      {
      IMapEntry e = (IMapEntry) s.first();
      Expr k = analyze(context == C.EVAL ? context : C.EXPRESSION, e.key());
      Expr v = analyze(context == C.EVAL ? context : C.EXPRESSION, e.val());
      keyvals = (IPersistentVector) keyvals.cons(k);
      keyvals = (IPersistentVector) keyvals.cons(v);
      if(k instanceof LiteralExpr)
        {
        Object kval = k.eval();
        if (constantKeys.contains(kval))
          allConstantKeysUnique = false;
        else
          constantKeys = (IPersistentSet)constantKeys.cons(kval);
        }
      else
        keysConstant = false;
      if(!(v instanceof LiteralExpr))
        valsConstant = false;
      }

    Expr ret = new MapExpr(keyvals);
    if(form instanceof IObj && ((IObj) form).meta() != null)
      return new MetaExpr(ret, MapExpr
          .parse(context == C.EVAL ? context : C.EXPRESSION, ((IObj) form).meta()));
    else if(keysConstant)
      {
      // TBD: Add more detail to exception thrown below.
      if(!allConstantKeysUnique)
        throw new IllegalArgumentException("Duplicate constant keys in map");
      if(valsConstant)
        {
        IPersistentMap m = PersistentHashMap.EMPTY;
        for(int i=0;i<keyvals.length();i+= 2)
          {
          m = m.assoc(((LiteralExpr)keyvals.nth(i)).val(), ((LiteralExpr)keyvals.nth(i+1)).val());
          }
//        System.err.println("Constant: " + m);
        return new ConstantExpr(m);
        }
      else
        return ret;
      }
    else
      return ret;
  }
}

public static class SetExpr implements Expr{
  public final IPersistentVector keys;
  final static Method setMethod = Method.getMethod("clojure.lang.IPersistentSet set(Object[])");


  public SetExpr(IPersistentVector keys){
    this.keys = keys;
  }

  public Object eval() {
    Object[] ret = new Object[keys.count()];
    for(int i = 0; i < keys.count(); i++)
      ret[i] = ((Expr) keys.nth(i)).eval();
    return RT.set(ret);
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    MethodExpr.emitArgsAsArray(keys, objx, gen);
    gen.invokeStatic(RT_TYPE, setMethod);
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    return IPersistentSet.class;
  }


  static public Expr parse(C context, IPersistentSet form) {
    IPersistentVector keys = PersistentVector.EMPTY;
    boolean constant = true;

    for(ISeq s = RT.seq(form); s != null; s = s.next())
      {
      Object e = s.first();
      Expr expr = analyze(context == C.EVAL ? context : C.EXPRESSION, e);
      keys = (IPersistentVector) keys.cons(expr);
      if(!(expr instanceof LiteralExpr))
        constant = false;
      }
    Expr ret = new SetExpr(keys);
    if(form instanceof IObj && ((IObj) form).meta() != null)
      return new MetaExpr(ret, MapExpr
          .parse(context == C.EVAL ? context : C.EXPRESSION, ((IObj) form).meta()));
    else if(constant)
      {
      IPersistentSet set = PersistentHashSet.EMPTY;
      for(int i=0;i<keys.count();i++)
        {
        LiteralExpr ve = (LiteralExpr)keys.nth(i);
        set = (IPersistentSet)set.cons(ve.val());
        }
//      System.err.println("Constant: " + set);
      return new ConstantExpr(set);
      }
    else
      return ret;
  }
}

public static class VectorExpr implements Expr{
  public final IPersistentVector args;
  final static Method vectorMethod = Method.getMethod("clojure.lang.IPersistentVector vector(Object[])");


  public VectorExpr(IPersistentVector args){
    this.args = args;
  }

  public Object eval() {
    IPersistentVector ret = PersistentVector.EMPTY;
    for(int i = 0; i < args.count(); i++)
      ret = (IPersistentVector) ret.cons(((Expr) args.nth(i)).eval());
    return ret;
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    MethodExpr.emitArgsAsArray(args, objx, gen);
    gen.invokeStatic(RT_TYPE, vectorMethod);
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    return IPersistentVector.class;
  }

  static public Expr parse(C context, IPersistentVector form) {
    boolean constant = true;

    IPersistentVector args = PersistentVector.EMPTY;
    for(int i = 0; i < form.count(); i++)
      {
      Expr v = analyze(context == C.EVAL ? context : C.EXPRESSION, form.nth(i));
      args = (IPersistentVector) args.cons(v);
      if(!(v instanceof LiteralExpr))
        constant = false;
      }
    Expr ret = new VectorExpr(args);
    if(form instanceof IObj && ((IObj) form).meta() != null)
      return new MetaExpr(ret, MapExpr
          .parse(context == C.EVAL ? context : C.EXPRESSION, ((IObj) form).meta()));
    else if (constant)
      {
      PersistentVector rv = PersistentVector.EMPTY;
      for(int i =0;i<args.count();i++)
        {
        LiteralExpr ve = (LiteralExpr)args.nth(i);
        rv = rv.cons(ve.val());
        }
//      System.err.println("Constant: " + rv);
      return new ConstantExpr(rv);
      }
    else
      return ret;
  }

}

static class KeywordInvokeExpr implements Expr{
  public final KeywordExpr kw;
  public final Object tag;
  public final Expr target;
  public final int line;
  public final int column;
  public final int siteIndex;
  public final String source;
  static Type ILOOKUP_TYPE = Type.getType(ILookup.class);

  public KeywordInvokeExpr(String source, int line, int column, Symbol tag, KeywordExpr kw, Expr target){
    this.source = source;
    this.kw = kw;
    this.target = target;
    this.line = line;
    this.column = column;
    this.tag = tag;
    this.siteIndex = registerKeywordCallsite(kw.k);
  }

  public Object eval() {
    try
      {
      return kw.k.invoke(target.eval());
      }
    catch(Throwable e)
      {
      if(!(e instanceof CompilerException))
        throw new CompilerException(source, line, column, e);
      else
        throw (CompilerException) e;
      }
  }

    public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
        Label endLabel = gen.newLabel();
        Label faultLabel = gen.newLabel();

        gen.visitLineNumber(line, gen.mark());
        gen.getStatic(objx.objtype, objx.thunkNameStatic(siteIndex),ObjExpr.ILOOKUP_THUNK_TYPE);
        gen.dup()//thunk, thunk
        target.emit(C.EXPRESSION, objx, gen); //thunk,thunk,target
        gen.dupX2();                          //target,thunk,thunk,target
        gen.invokeInterface(ObjExpr.ILOOKUP_THUNK_TYPE, Method.getMethod("Object get(Object)")); //target,thunk,result
        gen.dupX2();                          //result,target,thunk,result
        gen.visitJumpInsn(IF_ACMPEQ, faultLabel); //result,target
        gen.pop();                                //result
        gen.goTo(endLabel);

        gen.mark(faultLabel);    //result,target
        gen.swap();              //target,result
        gen.pop();               //target
      gen.dup();               //target,target
        gen.getStatic(objx.objtype, objx.siteNameStatic(siteIndex),ObjExpr.KEYWORD_LOOKUPSITE_TYPE)//target,target,site
        gen.swap();              //target,site,target
        gen.invokeInterface(ObjExpr.ILOOKUP_SITE_TYPE,
                            Method.getMethod("clojure.lang.ILookupThunk fault(Object)"));    //target,new-thunk
      gen.dup();   //target,new-thunk,new-thunk
      gen.putStatic(objx.objtype, objx.thunkNameStatic(siteIndex),ObjExpr.ILOOKUP_THUNK_TYPE)//target,new-thunk
      gen.swap();              //new-thunk,target
      gen.invokeInterface(ObjExpr.ILOOKUP_THUNK_TYPE, Method.getMethod("Object get(Object)")); //result

        gen.mark(endLabel);
        if(context == C.STATEMENT)
            gen.pop();
    }

  public boolean hasJavaClass() {
    return tag != null;
  }

  public Class getJavaClass() {
    return HostExpr.tagToClass(tag);
  }

}
//static class KeywordSiteInvokeExpr implements Expr{
//  public final Expr site;
//  public final Object tag;
//  public final Expr target;
//  public final int line;
//  public final int column;
//  public final String source;
//
//  public KeywordSiteInvokeExpr(String source, int line, int column, Symbol tag, Expr site, Expr target){
//    this.source = source;
//    this.site = site;
//    this.target = target;
//    this.line = line;
//    this.column = column;
//    this.tag = tag;
//  }
//
//  public Object eval() {
//    try
//      {
//      KeywordCallSite s = (KeywordCallSite) site.eval();
//      return s.thunk.invoke(s,target.eval());
//      }
//    catch(Throwable e)
//      {
//      if(!(e instanceof CompilerException))
//        throw new CompilerException(source, line, column, e);
//      else
//        throw (CompilerException) e;
//      }
//  }
//
//  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
//    gen.visitLineNumber(line, gen.mark());
//    site.emit(C.EXPRESSION, objx, gen);
//    gen.dup();
//    gen.getField(Type.getType(KeywordCallSite.class),"thunk",IFN_TYPE);
//    gen.swap();
//    target.emit(C.EXPRESSION, objx, gen);
//
//    gen.invokeInterface(IFN_TYPE, new Method("invoke", OBJECT_TYPE, ARG_TYPES[2]));
//    if(context == C.STATEMENT)
//      gen.pop();
//  }
//
//  public boolean hasJavaClass() {
//    return tag != null;
//  }
//
//  public Class getJavaClass() {
//    return HostExpr.tagToClass(tag);
//  }
//
//}

public static class InstanceOfExpr implements Expr, MaybePrimitiveExpr{
  Expr expr;
  Class c;

  public InstanceOfExpr(Class c, Expr expr){
    this.expr = expr;
    this.c = c;
  }

  public Object eval() {
    if(c.isInstance(expr.eval()))
      return RT.T;
    return RT.F;
  }

  public boolean canEmitPrimitive(){
    return true;
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    expr.emit(C.EXPRESSION, objx, gen);
    gen.instanceOf(getType(c));
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    emitUnboxed(context,objx,gen);
    HostExpr.emitBoxReturn(objx,gen,Boolean.TYPE);
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    return Boolean.TYPE;
  }

}

static class StaticInvokeExpr implements Expr, MaybePrimitiveExpr{
  public final Type target;
  public final Class retClass;
  public final Class[] paramclasses;
  public final Type[] paramtypes;
  public final IPersistentVector args;
  public final boolean variadic;
  public final Symbol tag;

  StaticInvokeExpr(Type target, Class retClass, Class[] paramclasses, Type[] paramtypes, boolean variadic,
                   IPersistentVector args,Symbol tag){
    this.target = target;
    this.retClass = retClass;
    this.paramclasses = paramclasses;
    this.paramtypes = paramtypes;
    this.args = args;
    this.variadic = variadic;
    this.tag = tag;
  }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval StaticInvokeExpr");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    emitUnboxed(context, objx, gen);
    if(context != C.STATEMENT)
      HostExpr.emitBoxReturn(objx,gen,retClass);
    if(context == C.STATEMENT)
      {
      if(retClass == long.class || retClass == double.class)
        gen.pop2();
      else
        gen.pop();
      }
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    return tag != null ? HostExpr.tagToClass(tag) : retClass;
  }

  public boolean canEmitPrimitive(){
    return retClass.isPrimitive();
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    Method ms = new Method("invokeStatic", getReturnType(), paramtypes);
    if(variadic)
      {
      for(int i = 0; i < paramclasses.length - 1; i++)
        {
        Expr e = (Expr) args.nth(i);
        if(maybePrimitiveType(e) == paramclasses[i])
          {
          ((MaybePrimitiveExpr) e).emitUnboxed(C.EXPRESSION, objx, gen);
          }
        else
          {
          e.emit(C.EXPRESSION, objx, gen);
          HostExpr.emitUnboxArg(objx, gen, paramclasses[i]);
          }
        }
      IPersistentVector restArgs = RT.subvec(args,paramclasses.length - 1,args.count());
      MethodExpr.emitArgsAsArray(restArgs,objx,gen);
      gen.invokeStatic(Type.getType(ArraySeq.class), Method.getMethod("clojure.lang.ArraySeq create(Object[])"));
      }
    else
      MethodExpr.emitTypedArgs(objx, gen, paramclasses, args);

    gen.invokeStatic(target, ms);
  }

  private Type getReturnType(){
    return Type.getType(retClass);
  }

  public static Expr parse(Var v, ISeq args, Symbol tag) {
    IPersistentCollection paramlists = (IPersistentCollection) RT.get(v.meta(), arglistsKey);
    if(paramlists == null)
      throw new IllegalStateException("Can't call static fn with no arglists: " + v);
    IPersistentVector paramlist = null;
    int argcount = RT.count(args);
    boolean variadic = false;
    for(ISeq aseq = RT.seq(paramlists); aseq != null; aseq = aseq.next())
      {
      if(!(aseq.first() instanceof IPersistentVector))
        throw new IllegalStateException("Expected vector arglist, had: " + aseq.first());
      IPersistentVector alist = (IPersistentVector) aseq.first();
      if(alist.count() > 1
         && alist.nth(alist.count() - 2).equals(_AMP_))
        {
        if(argcount >= alist.count() - 2)
          {
          paramlist = alist;
          variadic = true;
          }
        }
      else if(alist.count() == argcount)
        {
        paramlist = alist;
        variadic = false;
        break;
        }
      }

    if(paramlist == null)
      throw new IllegalArgumentException("Invalid arity - can't call: " + v + " with " + argcount + " args");

    Class retClass = tagClass(tagOf(paramlist));

    ArrayList<Class> paramClasses = new ArrayList();
    ArrayList<Type> paramTypes = new ArrayList();

    if(variadic)
      {
      for(int i = 0; i < paramlist.count()-2;i++)
        {
        Class pc = tagClass(tagOf(paramlist.nth(i)));
        paramClasses.add(pc);
        paramTypes.add(Type.getType(pc));
        }
      paramClasses.add(ISeq.class);
      paramTypes.add(Type.getType(ISeq.class));
      }
    else
      {
      for(int i = 0; i < argcount;i++)
        {
        Class pc = tagClass(tagOf(paramlist.nth(i)));
        paramClasses.add(pc);
        paramTypes.add(Type.getType(pc));
        }
      }

    String cname = v.ns.name.name.replace('.', '/').replace('-','_') + "$" + munge(v.sym.name);
    Type target = Type.getObjectType(cname);

    PersistentVector argv = PersistentVector.EMPTY;
    for(ISeq s = RT.seq(args); s != null; s = s.next())
      argv = argv.cons(analyze(C.EXPRESSION, s.first()));

    return new StaticInvokeExpr(target,retClass,paramClasses.toArray(new Class[paramClasses.size()]),
                                paramTypes.toArray(new Type[paramTypes.size()]),variadic, argv, tag);
  }
}

static class InvokeExpr implements Expr{
  public final Expr fexpr;
  public final Object tag;
  public final IPersistentVector args;
  public final int line;
  public final int column;
  public final String source;
  public boolean isProtocol = false;
  public boolean isDirect = false;
  public int siteIndex = -1;
  public Class protocolOn;
  public java.lang.reflect.Method onMethod;
  static Keyword onKey = Keyword.intern("on");
  static Keyword methodMapKey = Keyword.intern("method-map");

  public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args) {
    this.source = source;
    this.fexpr = fexpr;
    this.args = args;
    this.line = line;
    this.column = column;
    if(fexpr instanceof VarExpr)
      {
      Var fvar = ((VarExpr)fexpr).var;
      Var pvar =  (Var)RT.get(fvar.meta(), protocolKey);
      if(pvar != null && PROTOCOL_CALLSITES.isBound())
        {
        this.isProtocol = true;
        this.siteIndex = registerProtocolCallsite(((VarExpr)fexpr).var);
        Object pon = RT.get(pvar.get(), onKey);
        this.protocolOn = HostExpr.maybeClass(pon,false);
        if(this.protocolOn != null)
          {
          IPersistentMap mmap = (IPersistentMap) RT.get(pvar.get(), methodMapKey);
                    Keyword mmapVal = (Keyword) mmap.valAt(Keyword.intern(fvar.sym));
                    if (mmapVal == null) {
                        throw new IllegalArgumentException(
                              "No method of interface: " + protocolOn.getName() +
                              " found for function: " + fvar.sym + " of protocol: " + pvar.sym +
                              " (The protocol method may have been defined before and removed.)");
                    }
                    String mname = munge(mmapVal.sym.toString());
           List methods = Reflector.getMethods(protocolOn, args.count() - 1, mname, false);
          if(methods.size() != 1)
            throw new IllegalArgumentException(
                "No single method: " + mname + " of interface: " + protocolOn.getName() +
                " found for function: " + fvar.sym + " of protocol: " + pvar.sym);
          this.onMethod = (java.lang.reflect.Method) methods.get(0);
          }
        }
      }
   
    if (tag != null) {
        this.tag = tag;
    } else if (fexpr instanceof VarExpr) {
        Object arglists = RT.get(RT.meta(((VarExpr) fexpr).var), arglistsKey);
        Object sigTag = null;
        for(ISeq s = RT.seq(arglists); s != null; s = s.next()) {
                APersistentVector sig = (APersistentVector) s.first();
                int restOffset = sig.indexOf(_AMP_);
                if (args.count() == sig.count() || (restOffset > -1 && args.count() >= restOffset)) {
                    sigTag = tagOf(sig);
                    break;
                }
            }
       
        this.tag = sigTag == null ? ((VarExpr) fexpr).tag : sigTag;
    } else {
        this.tag = null;
    }
  }

  public Object eval() {
    try
      {
      IFn fn = (IFn) fexpr.eval();
      PersistentVector argvs = PersistentVector.EMPTY;
      for(int i = 0; i < args.count(); i++)
        argvs = argvs.cons(((Expr) args.nth(i)).eval());
      return fn.applyTo(RT.seq( Util.ret1(argvs, argvs = null) ));
      }
    catch(Throwable e)
      {
      if(!(e instanceof CompilerException))
        throw new CompilerException(source, line, column, e);
      else
        throw (CompilerException) e;
      }
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    gen.visitLineNumber(line, gen.mark());
    if(isProtocol)
      {
      emitProto(context,objx,gen);
      }

    else
      {
      fexpr.emit(C.EXPRESSION, objx, gen);
      gen.checkCast(IFN_TYPE);
      emitArgsAndCall(0, context,objx,gen);
      }
    if(context == C.STATEMENT)
      gen.pop();   
  }

  public void emitProto(C context, ObjExpr objx, GeneratorAdapter gen){
    Label onLabel = gen.newLabel();
    Label callLabel = gen.newLabel();
    Label endLabel = gen.newLabel();

    Var v = ((VarExpr)fexpr).var;

    Expr e = (Expr) args.nth(0);
    e.emit(C.EXPRESSION, objx, gen);
    gen.dup(); //target, target
    gen.invokeStatic(UTIL_TYPE,Method.getMethod("Class classOf(Object)")); //target,class
    gen.getStatic(objx.objtype, objx.cachedClassName(siteIndex),CLASS_TYPE); //target,class,cached-class
    gen.visitJumpInsn(IF_ACMPEQ, callLabel); //target
    if(protocolOn != null)
      {
      gen.dup(); //target, target     
      gen.instanceOf(Type.getType(protocolOn));
      gen.ifZCmp(GeneratorAdapter.NE, onLabel);
      }

    gen.dup(); //target, target
    gen.invokeStatic(UTIL_TYPE,Method.getMethod("Class classOf(Object)")); //target,class
    gen.putStatic(objx.objtype, objx.cachedClassName(siteIndex),CLASS_TYPE); //target

    gen.mark(callLabel); //target
    objx.emitVar(gen, v);
    gen.invokeVirtual(VAR_TYPE, Method.getMethod("Object getRawRoot()")); //target, proto-fn
    gen.swap();
    emitArgsAndCall(1, context,objx,gen);
    gen.goTo(endLabel);

    gen.mark(onLabel); //target
    if(protocolOn != null)
      {
      MethodExpr.emitTypedArgs(objx, gen, onMethod.getParameterTypes(), RT.subvec(args,1,args.count()));
      if(context == C.RETURN)
        {
        ObjMethod method = (ObjMethod) METHOD.deref();
        method.emitClearLocals(gen);
        }
      Method m = new Method(onMethod.getName(), Type.getReturnType(onMethod), Type.getArgumentTypes(onMethod));
      gen.invokeInterface(Type.getType(protocolOn), m);
      HostExpr.emitBoxReturn(objx, gen, onMethod.getReturnType());
      }
    gen.mark(endLabel);
  }

  void emitArgsAndCall(int firstArgToEmit, C context, ObjExpr objx, GeneratorAdapter gen){
    for(int i = firstArgToEmit; i < Math.min(MAX_POSITIONAL_ARITY, args.count()); i++)
      {
      Expr e = (Expr) args.nth(i);
      e.emit(C.EXPRESSION, objx, gen);
      }
    if(args.count() > MAX_POSITIONAL_ARITY)
      {
      PersistentVector restArgs = PersistentVector.EMPTY;
      for(int i = MAX_POSITIONAL_ARITY; i < args.count(); i++)
        {
        restArgs = restArgs.cons(args.nth(i));
        }
      MethodExpr.emitArgsAsArray(restArgs, objx, gen);
      }

    if(context == C.RETURN)
      {
      ObjMethod method = (ObjMethod) METHOD.deref();
      method.emitClearLocals(gen);
      }

    gen.invokeInterface(IFN_TYPE, new Method("invoke", OBJECT_TYPE, ARG_TYPES[Math.min(MAX_POSITIONAL_ARITY + 1,
                                                                                       args.count())]));
  }

  public boolean hasJavaClass() {
    return tag != null;
  }

  public Class getJavaClass() {
    return HostExpr.tagToClass(tag);
  }

  static public Expr parse(C context, ISeq form) {
    if(context != C.EVAL)
      context = C.EXPRESSION;
    Expr fexpr = analyze(context, form.first());
    if(fexpr instanceof VarExpr && ((VarExpr)fexpr).var.equals(INSTANCE) && RT.count(form) == 3)
      {
      Expr sexpr = analyze(C.EXPRESSION, RT.second(form));
      if(sexpr instanceof ConstantExpr)
        {
        Object val = ((ConstantExpr) sexpr).val();
        if(val instanceof Class)
          {
          return new InstanceOfExpr((Class) val, analyze(context, RT.third(form)));
          }
        }
      }

//    if(fexpr instanceof VarExpr && context != C.EVAL)
//      {
//      Var v = ((VarExpr)fexpr).var;
//      if(RT.booleanCast(RT.get(RT.meta(v),staticKey)))
//        {
//        return StaticInvokeExpr.parse(v, RT.next(form), tagOf(form));
//        }
//      }

    if(fexpr instanceof VarExpr && context != C.EVAL)
      {
      Var v = ((VarExpr)fexpr).var;
      Object arglists = RT.get(RT.meta(v), arglistsKey);
      int arity = RT.count(form.next());
      for(ISeq s = RT.seq(arglists); s != null; s = s.next())
        {
        IPersistentVector args = (IPersistentVector) s.first();
        if(args.count() == arity)
          {
          String primc = FnMethod.primInterface(args);
          if(primc != null)
            return analyze(context,
                           RT.listStar(Symbol.intern(".invokePrim"),
                                    ((Symbol) form.first()).withMeta(RT.map(RT.TAG_KEY, Symbol.intern(primc))),
                                    form.next()));
          break;
          }
        }
      }

    if(fexpr instanceof KeywordExpr && RT.count(form) == 2 && KEYWORD_CALLSITES.isBound())
      {
//      fexpr = new ConstantExpr(new KeywordCallSite(((KeywordExpr)fexpr).k));
      Expr target = analyze(context, RT.second(form));
      return new KeywordInvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form),
                                   (KeywordExpr) fexpr, target);
      }
    PersistentVector args = PersistentVector.EMPTY;
    for(ISeq s = RT.seq(form.next()); s != null; s = s.next())
      {
      args = args.cons(analyze(context, s.first()));
      }
//    if(args.count() > MAX_POSITIONAL_ARITY)
//      throw new IllegalArgumentException(
//          String.format("No more than %d args supported", MAX_POSITIONAL_ARITY));

    return new InvokeExpr((String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), fexpr, args);
  }
}

static class SourceDebugExtensionAttribute extends Attribute{
  public SourceDebugExtensionAttribute(){
    super("SourceDebugExtension");
  }

  void writeSMAP(ClassWriter cw, String smap){
    ByteVector bv = write(cw, null, -1, -1, -1);
    bv.putUTF8(smap);
  }
}

static public class FnExpr extends ObjExpr{
  final static Type aFnType = Type.getType(AFunction.class);
  final static Type restFnType = Type.getType(RestFn.class);
  //if there is a variadic overload (there can only be one) it is stored here
  FnMethod variadicMethod = null;
  IPersistentCollection methods;
  private boolean hasPrimSigs;
  private boolean hasMeta;
  //  String superName = null;

  public FnExpr(Object tag){
    super(tag);
  }

  public boolean hasJavaClass() {
    return true;
  }

  boolean supportsMeta(){
    return hasMeta;
  }

  public Class getJavaClass() {
    return tag != null ? HostExpr.tagToClass(tag) : AFunction.class;
  }

  protected void emitMethods(ClassVisitor cv){
    //override of invoke/doInvoke for each method
    for(ISeq s = RT.seq(methods); s != null; s = s.next())
      {
      ObjMethod method = (ObjMethod) s.first();
      method.emit(this, cv);
      }

    if(isVariadic())
      {
      GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC,
                                                  Method.getMethod("int getRequiredArity()"),
                                                  null,
                                                  null,
                                                  cv);
      gen.visitCode();
      gen.push(variadicMethod.reqParms.count());
      gen.returnValue();
      gen.endMethod();
      }
  }

  static Expr parse(C context, ISeq form, String name) {
    ISeq origForm = form;
    FnExpr fn = new FnExpr(tagOf(form));
    fn.src = form;
    ObjMethod enclosingMethod = (ObjMethod) METHOD.deref();
    if(((IMeta) form.first()).meta() != null)
      {
      fn.onceOnly = RT.booleanCast(RT.get(RT.meta(form.first()), Keyword.intern(null, "once")));
//      fn.superName = (String) RT.get(RT.meta(form.first()), Keyword.intern(null, "super-name"));
      }
    //fn.thisName = name;

    String basename = (enclosingMethod != null ?
                      enclosingMethod.objx.name
                      : (munge(currentNS().name.name))) + "$";

    Symbol nm = null;

    if(RT.second(form) instanceof Symbol) {
      nm = (Symbol) RT.second(form);
      if (name == null)
        name = nm.name + "__" + RT.nextID();
      else
        name += "__" + nm.name + "__" + RT.nextID();
    } else {
      if(name == null)
        name = "fn__" + RT.nextID();
      else if (enclosingMethod != null)
        name += "__" + RT.nextID();
    }

    String simpleName = munge(name).replace(".", "_DOT_");

    fn.name = basename + simpleName;
    fn.internalName = fn.name.replace('.', '/');
    fn.objtype = Type.getObjectType(fn.internalName);
    ArrayList<String> prims = new ArrayList();
    try
      {
      Var.pushThreadBindings(
          RT.mapUniqueKeys(CONSTANTS, PersistentVector.EMPTY,
                 CONSTANT_IDS, new IdentityHashMap(),
                 KEYWORDS, PersistentHashMap.EMPTY,
                 VARS, PersistentHashMap.EMPTY,
                 KEYWORD_CALLSITES, PersistentVector.EMPTY,
                 PROTOCOL_CALLSITES, PersistentVector.EMPTY,
                 VAR_CALLSITES, emptyVarCallSites(),
                                               NO_RECUR, null
          ));

      //arglist might be preceded by symbol naming this fn
      if(nm != null)
        {
        fn.thisName = nm.name;
        fn.isStatic = false; //RT.booleanCast(RT.get(nm.meta(), staticKey));
        form = RT.cons(FN, RT.next(RT.next(form)));
        }

      //now (fn [args] body...) or (fn ([args] body...) ([args2] body2...) ...)
      //turn former into latter
      if(RT.second(form) instanceof IPersistentVector)
        form = RT.list(FN, RT.next(form));
      fn.line = lineDeref();
      fn.column = columnDeref();
      FnMethod[] methodArray = new FnMethod[MAX_POSITIONAL_ARITY + 1];
      FnMethod variadicMethod = null;
      for(ISeq s = RT.next(form); s != null; s = RT.next(s))
        {
        FnMethod f = FnMethod.parse(fn, (ISeq) RT.first(s), fn.isStatic);
        if(f.isVariadic())
          {
          if(variadicMethod == null)
            variadicMethod = f;
          else
            throw Util.runtimeException("Can't have more than 1 variadic overload");
          }
        else if(methodArray[f.reqParms.count()] == null)
          methodArray[f.reqParms.count()] = f;
        else
          throw Util.runtimeException("Can't have 2 overloads with same arity");
        if(f.prim != null)
          prims.add(f.prim);
        }
      if(variadicMethod != null)
        {
        for(int i = variadicMethod.reqParms.count() + 1; i <= MAX_POSITIONAL_ARITY; i++)
          if(methodArray[i] != null)
            throw Util.runtimeException(
                "Can't have fixed arity function with more params than variadic function");
        }

      if(fn.isStatic && fn.closes.count() > 0)
        throw new IllegalArgumentException("static fns can't be closures");
      IPersistentCollection methods = null;
      for(int i = 0; i < methodArray.length; i++)
        if(methodArray[i] != null)
          methods = RT.conj(methods, methodArray[i]);
      if(variadicMethod != null)
        methods = RT.conj(methods, variadicMethod);

      fn.methods = methods;
      fn.variadicMethod = variadicMethod;
      fn.keywords = (IPersistentMap) KEYWORDS.deref();
      fn.vars = (IPersistentMap) VARS.deref();
      fn.constants = (PersistentVector) CONSTANTS.deref();
      fn.keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref();
      fn.protocolCallsites = (IPersistentVector) PROTOCOL_CALLSITES.deref();
      fn.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();

      fn.constantsID = RT.nextID();
//      DynamicClassLoader loader = (DynamicClassLoader) LOADER.get();
//      loader.registerConstants(fn.constantsID, fn.constants.toArray());
      }
    finally
      {
      Var.popThreadBindings();
      }
    fn.hasPrimSigs = prims.size() > 0;
    IPersistentMap fmeta = RT.meta(origForm);
    if(fmeta != null)
      fmeta = fmeta.without(RT.LINE_KEY).without(RT.COLUMN_KEY).without(RT.FILE_KEY);

    fn.hasMeta = RT.count(fmeta) > 0;

    try
      {
      fn.compile(fn.isVariadic() ? "clojure/lang/RestFn" : "clojure/lang/AFunction",
                 (prims.size() == 0)?
                  null
            :prims.toArray(new String[prims.size()]),
                  fn.onceOnly);
      }
    catch(IOException e)
      {
      throw Util.sneakyThrow(e);
      }
    fn.getCompiledClass();

    if(fn.supportsMeta())
      {
      //System.err.println(name + " supports meta");
      return new MetaExpr(fn, MapExpr
          .parse(context == C.EVAL ? context : C.EXPRESSION, fmeta));
      }
    else
      return fn;
  }

  public final ObjMethod variadicMethod(){
    return variadicMethod;
  }

  boolean isVariadic(){
    return variadicMethod != null;
  }

  public final IPersistentCollection methods(){
    return methods;
  }

  public void emitForDefn(ObjExpr objx, GeneratorAdapter gen){
//    if(!hasPrimSigs && closes.count() == 0)
//      {
//      Type thunkType = Type.getType(FnLoaderThunk.class);
////      presumes var on stack
//      gen.dup();
//      gen.newInstance(thunkType);
//      gen.dupX1();
//      gen.swap();
//      gen.push(internalName.replace('/','.'));
//      gen.invokeConstructor(thunkType,Method.getMethod("void <init>(clojure.lang.Var,String)"));
//      }
//    else
      emit(C.EXPRESSION,objx,gen);
  }
}

static public class ObjExpr implements Expr{
  static final String CONST_PREFIX = "const__";
  String name;
  //String simpleName;
  String internalName;
  String thisName;
  Type objtype;
  public final Object tag;
  //localbinding->itself
  IPersistentMap closes = PersistentHashMap.EMPTY;
    //localbndingexprs
    IPersistentVector closesExprs = PersistentVector.EMPTY;
  //symbols
  IPersistentSet volatiles = PersistentHashSet.EMPTY;

  //symbol->lb
  IPersistentMap fields = null;

  //hinted fields
  IPersistentVector hintedFields = PersistentVector.EMPTY;

  //Keyword->KeywordExpr
  IPersistentMap keywords = PersistentHashMap.EMPTY;
  IPersistentMap vars = PersistentHashMap.EMPTY;
  Class compiledClass;
  int line;
  int column;
  PersistentVector constants;
  int constantsID;
  int altCtorDrops = 0;

  IPersistentVector keywordCallsites;
  IPersistentVector protocolCallsites;
  IPersistentSet varCallsites;
  boolean onceOnly = false;

  Object src;

  final static Method voidctor = Method.getMethod("void <init>()");
  protected IPersistentMap classMeta;
  protected boolean isStatic;

  public final String name(){
    return name;
  }

//  public final String simpleName(){
//    return simpleName;
//  }

  public final String internalName(){
    return internalName;
  }

  public final String thisName(){
    return thisName;
  }

  public final Type objtype(){
    return objtype;
  }

  public final IPersistentMap closes(){
    return closes;
  }

  public final IPersistentMap keywords(){
    return keywords;
  }

  public final IPersistentMap vars(){
    return vars;
  }

  public final Class compiledClass(){
    return compiledClass;
  }

  public final int line(){
    return line;
  }

  public final int column(){
    return column;
  }

  public final PersistentVector constants(){
    return constants;
  }

  public final int constantsID(){
    return constantsID;
  }

  final static Method kwintern = Method.getMethod("clojure.lang.Keyword intern(String, String)");
  final static Method symintern = Method.getMethod("clojure.lang.Symbol intern(String)");
  final static Method varintern =
      Method.getMethod("clojure.lang.Var intern(clojure.lang.Symbol, clojure.lang.Symbol)");

  final static Type DYNAMIC_CLASSLOADER_TYPE = Type.getType(DynamicClassLoader.class);
  final static Method getClassMethod = Method.getMethod("Class getClass()");
  final static Method getClassLoaderMethod = Method.getMethod("ClassLoader getClassLoader()");
  final static Method getConstantsMethod = Method.getMethod("Object[] getConstants(int)");
  final static Method readStringMethod = Method.getMethod("Object readString(String)");

  final static Type ILOOKUP_SITE_TYPE = Type.getType(ILookupSite.class);
  final static Type ILOOKUP_THUNK_TYPE = Type.getType(ILookupThunk.class);
  final static Type KEYWORD_LOOKUPSITE_TYPE = Type.getType(KeywordLookupSite.class);

  private DynamicClassLoader loader;
  private byte[] bytecode;

  public ObjExpr(Object tag){
    this.tag = tag;
  }

  static String trimGenID(String name){
    int i = name.lastIndexOf("__");
    return i==-1?name:name.substring(0,i);
  }
 


  Type[] ctorTypes(){
    IPersistentVector tv = !supportsMeta()?PersistentVector.EMPTY:RT.vector(IPERSISTENTMAP_TYPE);
    for(ISeq s = RT.keys(closes); s != null; s = s.next())
      {
      LocalBinding lb = (LocalBinding) s.first();
      if(lb.getPrimitiveType() != null)
        tv = tv.cons(Type.getType(lb.getPrimitiveType()));
      else
        tv = tv.cons(OBJECT_TYPE);
      }
    Type[] ret = new Type[tv.count()];
    for(int i = 0; i < tv.count(); i++)
      ret[i] = (Type) tv.nth(i);
    return ret;
  }

  void compile(String superName, String[] interfaceNames, boolean oneTimeUse) throws IOException{
    //create bytecode for a class
    //with name current_ns.defname[$letname]+
    //anonymous fns get names fn__id
    //derived from AFn/RestFn
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//    ClassWriter cw = new ClassWriter(0);
    ClassVisitor cv = cw;
//    ClassVisitor cv = new TraceClassVisitor(new CheckClassAdapter(cw), new PrintWriter(System.out));
    //ClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out));
    cv.visit(V1_5, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, internalName, null,superName,interfaceNames);
//             superName != null ? superName :
//             (isVariadic() ? "clojure/lang/RestFn" : "clojure/lang/AFunction"), null);
    String source = (String) SOURCE.deref();
    int lineBefore = (Integer) LINE_BEFORE.deref();
    int lineAfter = (Integer) LINE_AFTER.deref() + 1;
    int columnBefore = (Integer) COLUMN_BEFORE.deref();
    int columnAfter = (Integer) COLUMN_AFTER.deref() + 1;

    if(source != null && SOURCE_PATH.deref() != null)
      {
      //cv.visitSource(source, null);
      String smap = "SMAP\n" +
                    ((source.lastIndexOf('.') > 0) ?
                     source.substring(0, source.lastIndexOf('.'))
                      :source)
                             //                      : simpleName)
                    + ".java\n" +
                    "Clojure\n" +
                    "*S Clojure\n" +
                    "*F\n" +
                    "+ 1 " + source + "\n" +
                    (String) SOURCE_PATH.deref() + "\n" +
                    "*L\n" +
                    String.format("%d#1,%d:%d\n", lineBefore, lineAfter - lineBefore, lineBefore) +
                    "*E";
      cv.visitSource(source, smap);
      }
    addAnnotation(cv, classMeta);
    //static fields for constants
    for(int i = 0; i < constants.count(); i++)
      {
      cv.visitField(ACC_PUBLIC + ACC_FINAL
                    + ACC_STATIC, constantName(i), constantType(i).getDescriptor(),
                    null, null);
      }

    //static fields for lookup sites
    for(int i = 0; i < keywordCallsites.count(); i++)
      {
      cv.visitField(ACC_FINAL
                    + ACC_STATIC, siteNameStatic(i), KEYWORD_LOOKUPSITE_TYPE.getDescriptor(),
                    null, null);
      cv.visitField(ACC_STATIC, thunkNameStatic(i), ILOOKUP_THUNK_TYPE.getDescriptor(),
                    null, null);
      }

//    for(int i=0;i<varCallsites.count();i++)
//      {
//      cv.visitField(ACC_PRIVATE + ACC_STATIC + ACC_FINAL
//          , varCallsiteName(i), IFN_TYPE.getDescriptor(), null, null);
//      }

    //static init for constants, keywords and vars
    GeneratorAdapter clinitgen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
                                                      Method.getMethod("void <clinit> ()"),
                                                      null,
                                                      null,
                                                      cv);
    clinitgen.visitCode();
    clinitgen.visitLineNumber(line, clinitgen.mark());

    if(constants.count() > 0)
      {
      emitConstants(clinitgen);
      }

    if(keywordCallsites.count() > 0)
      emitKeywordCallsites(clinitgen);

    /*
    for(int i=0;i<varCallsites.count();i++)
      {
      Label skipLabel = clinitgen.newLabel();
      Label endLabel = clinitgen.newLabel();
      Var var = (Var) varCallsites.nth(i);
      clinitgen.push(var.ns.name.toString());
      clinitgen.push(var.sym.toString());
      clinitgen.invokeStatic(RT_TYPE, Method.getMethod("clojure.lang.Var var(String,String)"));
      clinitgen.dup();
      clinitgen.invokeVirtual(VAR_TYPE,Method.getMethod("boolean hasRoot()"));
      clinitgen.ifZCmp(GeneratorAdapter.EQ,skipLabel);

      clinitgen.invokeVirtual(VAR_TYPE,Method.getMethod("Object getRoot()"));
            clinitgen.dup();
            clinitgen.instanceOf(AFUNCTION_TYPE);
            clinitgen.ifZCmp(GeneratorAdapter.EQ,skipLabel);
      clinitgen.checkCast(IFN_TYPE);
      clinitgen.putStatic(objtype, varCallsiteName(i), IFN_TYPE);
      clinitgen.goTo(endLabel);

      clinitgen.mark(skipLabel);
      clinitgen.pop();

      clinitgen.mark(endLabel);
      }
        */
    clinitgen.returnValue();

    clinitgen.endMethod();
    if(supportsMeta())
      {
      cv.visitField(ACC_FINAL, "__meta", IPERSISTENTMAP_TYPE.getDescriptor(), null, null);
      }
    //instance fields for closed-overs
    for(ISeq s = RT.keys(closes); s != null; s = s.next())
      {
      LocalBinding lb = (LocalBinding) s.first();
      if(isDeftype())
        {
        int access = isVolatile(lb) ? ACC_VOLATILE :
                     isMutable(lb) ? 0 :
                     (ACC_PUBLIC + ACC_FINAL);
        FieldVisitor fv;
        if(lb.getPrimitiveType() != null)
          fv = cv.visitField(access
              , lb.name, Type.getType(lb.getPrimitiveType()).getDescriptor(),
                  null, null);
        else
        //todo - when closed-overs are fields, use more specific types here and in ctor and emitLocal?
          fv = cv.visitField(access
              , lb.name, OBJECT_TYPE.getDescriptor(), null, null);
        addAnnotation(fv, RT.meta(lb.sym));
        }
      else
        {
        //todo - only enable this non-private+writability for letfns where we need it
        if(lb.getPrimitiveType() != null)
          cv.visitField(0 + (isVolatile(lb) ? ACC_VOLATILE : 0)
              , lb.name, Type.getType(lb.getPrimitiveType()).getDescriptor(),
                  null, null);
        else
          cv.visitField(0 //+ (oneTimeUse ? 0 : ACC_FINAL)
              , lb.name, OBJECT_TYPE.getDescriptor(), null, null);
        }
      }

    //static fields for callsites and thunks
    for(int i=0;i<protocolCallsites.count();i++)
      {
      cv.visitField(ACC_PRIVATE + ACC_STATIC, cachedClassName(i), CLASS_TYPE.getDescriptor(), null, null);
      }

     //ctor that takes closed-overs and inits base + fields
    Method m = new Method("<init>", Type.VOID_TYPE, ctorTypes());
    GeneratorAdapter ctorgen = new GeneratorAdapter(ACC_PUBLIC,
                                                    m,
                                                    null,
                                                    null,
                                                    cv);
    Label start = ctorgen.newLabel();
    Label end = ctorgen.newLabel();
    ctorgen.visitCode();
    ctorgen.visitLineNumber(line, ctorgen.mark());
    ctorgen.visitLabel(start);
    ctorgen.loadThis();
//    if(superName != null)
      ctorgen.invokeConstructor(Type.getObjectType(superName), voidctor);
//    else if(isVariadic()) //RestFn ctor takes reqArity arg
//      {
//      ctorgen.push(variadicMethod.reqParms.count());
//      ctorgen.invokeConstructor(restFnType, restfnctor);
//      }
//    else
//      ctorgen.invokeConstructor(aFnType, voidctor);

//    if(vars.count() > 0)
//      {
//      ctorgen.loadThis();
//      ctorgen.getStatic(VAR_TYPE,"rev",Type.INT_TYPE);
//      ctorgen.push(-1);
//      ctorgen.visitInsn(Opcodes.IADD);
//      ctorgen.putField(objtype, "__varrev__", Type.INT_TYPE);
//      }

    if(supportsMeta())
      {
      ctorgen.loadThis();
      ctorgen.visitVarInsn(IPERSISTENTMAP_TYPE.getOpcode(Opcodes.ILOAD), 1);
      ctorgen.putField(objtype, "__meta", IPERSISTENTMAP_TYPE);
      }

    int a = supportsMeta()?2:1;
    for(ISeq s = RT.keys(closes); s != null; s = s.next(), ++a)
      {
      LocalBinding lb = (LocalBinding) s.first();
      ctorgen.loadThis();
      Class primc = lb.getPrimitiveType();
      if(primc != null)
        {
        ctorgen.visitVarInsn(Type.getType(primc).getOpcode(Opcodes.ILOAD), a);
        ctorgen.putField(objtype, lb.name, Type.getType(primc));
        if(primc == Long.TYPE || primc == Double.TYPE)
          ++a;
        }
      else
        {
        ctorgen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ILOAD), a);
        ctorgen.putField(objtype, lb.name, OBJECT_TYPE);
        }
            closesExprs = closesExprs.cons(new LocalBindingExpr(lb, null));
      }


    ctorgen.visitLabel(end);

    ctorgen.returnValue();

    ctorgen.endMethod();

    if(altCtorDrops > 0)
      {
          //ctor that takes closed-overs and inits base + fields
      Type[] ctorTypes = ctorTypes();
      Type[] altCtorTypes = new Type[ctorTypes.length-altCtorDrops];
      for(int i=0;i<altCtorTypes.length;i++)
        altCtorTypes[i] = ctorTypes[i];
      Method alt = new Method("<init>", Type.VOID_TYPE, altCtorTypes);
      ctorgen = new GeneratorAdapter(ACC_PUBLIC,
                              alt,
                              null,
                              null,
                              cv);
      ctorgen.visitCode();
      ctorgen.loadThis();
      ctorgen.loadArgs();
      for(int i=0;i<altCtorDrops;i++)
        ctorgen.visitInsn(Opcodes.ACONST_NULL);

      ctorgen.invokeConstructor(objtype, new Method("<init>", Type.VOID_TYPE, ctorTypes));

      ctorgen.returnValue();
      ctorgen.endMethod();
      }

    if(supportsMeta())
      {
      //ctor that takes closed-overs but not meta
      Type[] ctorTypes = ctorTypes();
      Type[] noMetaCtorTypes = new Type[ctorTypes.length-1];
      for(int i=1;i<ctorTypes.length;i++)
        noMetaCtorTypes[i-1] = ctorTypes[i];
      Method alt = new Method("<init>", Type.VOID_TYPE, noMetaCtorTypes);
      ctorgen = new GeneratorAdapter(ACC_PUBLIC,
                              alt,
                              null,
                              null,
                              cv);
      ctorgen.visitCode();
      ctorgen.loadThis();
      ctorgen.visitInsn(Opcodes.ACONST_NULL)//null meta
      ctorgen.loadArgs();
      ctorgen.invokeConstructor(objtype, new Method("<init>", Type.VOID_TYPE, ctorTypes));

      ctorgen.returnValue();
      ctorgen.endMethod();

      //meta()
      Method meth = Method.getMethod("clojure.lang.IPersistentMap meta()");

      GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC,
                        meth,
                        null,
                        null,
                        cv);
      gen.visitCode();
      gen.loadThis();
      gen.getField(objtype,"__meta",IPERSISTENTMAP_TYPE);

      gen.returnValue();
      gen.endMethod();

      //withMeta()
      meth = Method.getMethod("clojure.lang.IObj withMeta(clojure.lang.IPersistentMap)");

      gen = new GeneratorAdapter(ACC_PUBLIC,
                        meth,
                        null,
                        null,
                        cv);
      gen.visitCode();
      gen.newInstance(objtype);
      gen.dup();
      gen.loadArg(0);

      for(ISeq s = RT.keys(closes); s != null; s = s.next(), ++a)
        {
        LocalBinding lb = (LocalBinding) s.first();
        gen.loadThis();
        Class primc = lb.getPrimitiveType();
        if(primc != null)
          {
          gen.getField(objtype, lb.name, Type.getType(primc));
          }
        else
          {
          gen.getField(objtype, lb.name, OBJECT_TYPE);
          }
        }

      gen.invokeConstructor(objtype, new Method("<init>", Type.VOID_TYPE, ctorTypes));
      gen.returnValue();
      gen.endMethod();
      }

    emitStatics(cv);
    emitMethods(cv);

    if(keywordCallsites.count() > 0)
      {
      Method meth = Method.getMethod("void swapThunk(int,clojure.lang.ILookupThunk)");

      GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC,
                        meth,
                        null,
                        null,
                        cv);
      gen.visitCode();
      Label endLabel = gen.newLabel();

      Label[] labels = new Label[keywordCallsites.count()];
      for(int i = 0; i < keywordCallsites.count();i++)
        {
        labels[i] = gen.newLabel();
        }
      gen.loadArg(0);
      gen.visitTableSwitchInsn(0,keywordCallsites.count()-1,endLabel,labels);

      for(int i = 0; i < keywordCallsites.count();i++)
        {
        gen.mark(labels[i]);
//        gen.loadThis();
        gen.loadArg(1);
        gen.putStatic(objtype, thunkNameStatic(i),ILOOKUP_THUNK_TYPE);
        gen.goTo(endLabel);
        }

      gen.mark(endLabel);

      gen.returnValue();
      gen.endMethod();
      }
   
    //end of class
    cv.visitEnd();

    bytecode = cw.toByteArray();
    if(RT.booleanCast(COMPILE_FILES.deref()))
      writeClassFile(internalName, bytecode);
//    else
//      getCompiledClass();
  }

  private void emitKeywordCallsites(GeneratorAdapter clinitgen){
    for(int i=0;i<keywordCallsites.count();i++)
      {
      Keyword k = (Keyword) keywordCallsites.nth(i);
      clinitgen.newInstance(KEYWORD_LOOKUPSITE_TYPE);
      clinitgen.dup();
      emitValue(k,clinitgen);
      clinitgen.invokeConstructor(KEYWORD_LOOKUPSITE_TYPE,
                                  Method.getMethod("void <init>(clojure.lang.Keyword)"));
      clinitgen.dup();
      clinitgen.putStatic(objtype, siteNameStatic(i), KEYWORD_LOOKUPSITE_TYPE);
      clinitgen.putStatic(objtype, thunkNameStatic(i), ILOOKUP_THUNK_TYPE);
      }
  }

  protected void emitStatics(ClassVisitor gen){
  }

  protected void emitMethods(ClassVisitor gen){
  }

  void emitListAsObjectArray(Object value, GeneratorAdapter gen){
    gen.push(((List) value).size());
    gen.newArray(OBJECT_TYPE);
    int i = 0;
    for(Iterator it = ((List) value).iterator(); it.hasNext(); i++)
      {
      gen.dup();
      gen.push(i);
      emitValue(it.next(), gen);
      gen.arrayStore(OBJECT_TYPE);
      }
  }

  void emitValue(Object value, GeneratorAdapter gen){
    boolean partial = true;
    //System.out.println(value.getClass().toString());

    if(value == null)
      gen.visitInsn(Opcodes.ACONST_NULL);
    else if(value instanceof String)
      {
      gen.push((String) value);
      }
    else if(value instanceof Boolean)
      {
      if(((Boolean) value).booleanValue())
        gen.getStatic(BOOLEAN_OBJECT_TYPE, "TRUE", BOOLEAN_OBJECT_TYPE);
      else
        gen.getStatic(BOOLEAN_OBJECT_TYPE,"FALSE",BOOLEAN_OBJECT_TYPE);
      }
    else if(value instanceof Integer)
      {
      gen.push(((Integer) value).intValue());
      gen.invokeStatic(Type.getType(Integer.class), Method.getMethod("Integer valueOf(int)"));
      }
    else if(value instanceof Long)
      {
      gen.push(((Long) value).longValue());
      gen.invokeStatic(Type.getType(Long.class), Method.getMethod("Long valueOf(long)"));
      }
    else if(value instanceof Double)
        {
        gen.push(((Double) value).doubleValue());
        gen.invokeStatic(Type.getType(Double.class), Method.getMethod("Double valueOf(double)"));
        }
    else if(value instanceof Character)
        {
        gen.push(((Character) value).charValue());
        gen.invokeStatic(Type.getType(Character.class), Method.getMethod("Character valueOf(char)"));
        }
    else if(value instanceof Class)
      {
      Class cc = (Class)value;
      if(cc.isPrimitive())
        {
        Type bt;
        if ( cc == boolean.class ) bt = Type.getType(Boolean.class);
        else if ( cc == byte.class ) bt = Type.getType(Byte.class);
        else if ( cc == char.class ) bt = Type.getType(Character.class);
        else if ( cc == double.class ) bt = Type.getType(Double.class);
        else if ( cc == float.class ) bt = Type.getType(Float.class);
        else if ( cc == int.class ) bt = Type.getType(Integer.class);
        else if ( cc == long.class ) bt = Type.getType(Long.class);
        else if ( cc == short.class ) bt = Type.getType(Short.class);
        else throw Util.runtimeException(
            "Can't embed unknown primitive in code: " + value);
        gen.getStatic( bt, "TYPE", Type.getType(Class.class) );
        }
      else
        {
        gen.push(destubClassName(cc.getName()));
        gen.invokeStatic(Type.getType(Class.class), Method.getMethod("Class forName(String)"));
        }
      }
    else if(value instanceof Symbol)
      {
      gen.push(((Symbol) value).ns);
      gen.push(((Symbol) value).name);
      gen.invokeStatic(Type.getType(Symbol.class),
               Method.getMethod("clojure.lang.Symbol intern(String,String)"));
      }
    else if(value instanceof Keyword)
      {
      gen.push(((Keyword) value).sym.ns);
      gen.push(((Keyword) value).sym.name);
      gen.invokeStatic(RT_TYPE,
               Method.getMethod("clojure.lang.Keyword keyword(String,String)"));
      }
//            else if(value instanceof KeywordCallSite)
//                {
//                emitValue(((KeywordCallSite) value).k.sym, gen);
//                gen.invokeStatic(Type.getType(KeywordCallSite.class),
//                                 Method.getMethod("clojure.lang.KeywordCallSite create(clojure.lang.Symbol)"));
//                }
    else if(value instanceof Var)
      {
      Var var = (Var) value;
      gen.push(var.ns.name.toString());
      gen.push(var.sym.toString());
      gen.invokeStatic(RT_TYPE, Method.getMethod("clojure.lang.Var var(String,String)"));
      }
    else if(value instanceof IType)
      {
      Method ctor = new Method("<init>", Type.getConstructorDescriptor(value.getClass().getConstructors()[0]));
      gen.newInstance(Type.getType(value.getClass()));
      gen.dup();
      IPersistentVector fields = (IPersistentVector) Reflector.invokeStaticMethod(value.getClass(), "getBasis", new Object[]{});
      for(ISeq s = RT.seq(fields); s != null; s = s.next())
        {
        Symbol field = (Symbol) s.first();
        Class k = tagClass(tagOf(field));
        Object val = Reflector.getInstanceField(value, field.name);
        emitValue(val, gen);

        if(k.isPrimitive())
          {
          Type b = Type.getType(boxClass(k));
          String p = Type.getType(k).getDescriptor();
          String n = k.getName();

          gen.invokeVirtual(b, new Method(n+"Value", "()"+p));
          }
        }
      gen.invokeConstructor(Type.getType(value.getClass()), ctor);
      }
    else if(value instanceof IRecord)
      {
      Method createMethod = Method.getMethod(value.getClass().getName() + " create(clojure.lang.IPersistentMap)");
            emitValue(PersistentArrayMap.create((java.util.Map) value), gen);
      gen.invokeStatic(getType(value.getClass()), createMethod);
      }
    else if(value instanceof IPersistentMap)
      {
      List entries = new ArrayList();
      for(Map.Entry entry : (Set<Map.Entry>) ((Map) value).entrySet())
        {
        entries.add(entry.getKey());
        entries.add(entry.getValue());
        }
      emitListAsObjectArray(entries, gen);
      gen.invokeStatic(RT_TYPE,
               Method.getMethod("clojure.lang.IPersistentMap map(Object[])"));
      }
    else if(value instanceof IPersistentVector)
      {
      emitListAsObjectArray(value, gen);
      gen.invokeStatic(RT_TYPE, Method.getMethod(
          "clojure.lang.IPersistentVector vector(Object[])"));
      }
    else if(value instanceof PersistentHashSet)
      {
      ISeq vs = RT.seq(value);
      if(vs == null)
        gen.getStatic(Type.getType(PersistentHashSet.class),"EMPTY",Type.getType(PersistentHashSet.class));
      else
        {
        emitListAsObjectArray(vs, gen);
        gen.invokeStatic(Type.getType(PersistentHashSet.class), Method.getMethod(
          "clojure.lang.PersistentHashSet create(Object[])"));
        }
      }
    else if(value instanceof ISeq || value instanceof IPersistentList)
      {
      emitListAsObjectArray(value, gen);
      gen.invokeStatic(Type.getType(java.util.Arrays.class),
               Method.getMethod("java.util.List asList(Object[])"));
      gen.invokeStatic(Type.getType(PersistentList.class),
               Method.getMethod(
                   "clojure.lang.IPersistentList create(java.util.List)"));
      }
    else if(value instanceof Pattern)
      {
      emitValue(value.toString(), gen);
      gen.invokeStatic(Type.getType(Pattern.class),
               Method.getMethod("java.util.regex.Pattern compile(String)"));
      }
    else
      {
      String cs = null;
      try
        {
        cs = RT.printString(value);
//        System.out.println("WARNING SLOW CODE: " + Util.classOf(value) + " -> " + cs);
        }
      catch(Exception e)
        {
        throw Util.runtimeException(
            "Can't embed object in code, maybe print-dup not defined: " +
            value);
        }
      if(cs.length() == 0)
        throw Util.runtimeException(
            "Can't embed unreadable object in code: " + value);

      if(cs.startsWith("#<"))
        throw Util.runtimeException(
            "Can't embed unreadable object in code: " + cs);

      gen.push(cs);
      gen.invokeStatic(RT_TYPE, readStringMethod);
      partial = false;
      }

    if(partial)
      {
      if(value instanceof IObj && RT.count(((IObj) value).meta()) > 0)
        {
        gen.checkCast(IOBJ_TYPE);
                Object m = ((IObj) value).meta();
        emitValue(elideMeta(m), gen);
        gen.checkCast(IPERSISTENTMAP_TYPE);
        gen.invokeInterface(IOBJ_TYPE,
                            Method.getMethod("clojure.lang.IObj withMeta(clojure.lang.IPersistentMap)"));
        }
      }
  }


  void emitConstants(GeneratorAdapter clinitgen){
    try
      {
      Var.pushThreadBindings(RT.map(RT.PRINT_DUP, RT.T));

      for(int i = 0; i < constants.count(); i++)
        {
        emitValue(constants.nth(i), clinitgen);
        clinitgen.checkCast(constantType(i));
        clinitgen.putStatic(objtype, constantName(i), constantType(i));
        }
      }
    finally
      {
      Var.popThreadBindings();
      }
  }

  boolean isMutable(LocalBinding lb){
    return isVolatile(lb) ||
           RT.booleanCast(RT.contains(fields, lb.sym)) &&
           RT.booleanCast(RT.get(lb.sym.meta(), Keyword.intern("unsynchronized-mutable")));
  }

  boolean isVolatile(LocalBinding lb){
    return RT.booleanCast(RT.contains(fields, lb.sym)) &&
           RT.booleanCast(RT.get(lb.sym.meta(), Keyword.intern("volatile-mutable")));
  }

  boolean isDeftype(){
    return fields != null;
  }

  boolean supportsMeta(){
    return !isDeftype();
  }
  void emitClearCloses(GeneratorAdapter gen){
//    int a = 1;
//    for(ISeq s = RT.keys(closes); s != null; s = s.next(), ++a)
//      {
//      LocalBinding lb = (LocalBinding) s.first();
//      Class primc = lb.getPrimitiveType();
//      if(primc == null)
//        {
//        gen.loadThis();
//        gen.visitInsn(Opcodes.ACONST_NULL);
//        gen.putField(objtype, lb.name, OBJECT_TYPE);
//        }
//      }
  }

  synchronized Class getCompiledClass(){
    if(compiledClass == null)
//      if(RT.booleanCast(COMPILE_FILES.deref()))
//        compiledClass = RT.classForName(name);//loader.defineClass(name, bytecode);
//      else
        {
        loader = (DynamicClassLoader) LOADER.deref();
        compiledClass = loader.defineClass(name, bytecode, src);
        }
    return compiledClass;
  }

  public Object eval() {
    if(isDeftype())
      return null;
    try
      {
      return getCompiledClass().newInstance();
      }
    catch(Exception e)
      {
      throw Util.sneakyThrow(e);
      }
  }

  public void emitLetFnInits(GeneratorAdapter gen, ObjExpr objx, IPersistentSet letFnLocals){
    //objx arg is enclosing objx, not this
    gen.checkCast(objtype);

    for(ISeq s = RT.keys(closes); s != null; s = s.next())
      {
      LocalBinding lb = (LocalBinding) s.first();
      if(letFnLocals.contains(lb))
        {
        Class primc = lb.getPrimitiveType();
        gen.dup();
        if(primc != null)
          {
          objx.emitUnboxedLocal(gen, lb);
          gen.putField(objtype, lb.name, Type.getType(primc));
          }
        else
          {
          objx.emitLocal(gen, lb, false);
          gen.putField(objtype, lb.name, OBJECT_TYPE);
          }
        }
      }
    gen.pop();

  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    //emitting a Fn means constructing an instance, feeding closed-overs from enclosing scope, if any
    //objx arg is enclosing objx, not this
//    getCompiledClass();
    if(isDeftype())
      {
      gen.visitInsn(Opcodes.ACONST_NULL);
      }
    else
      {
      gen.newInstance(objtype);
      gen.dup();
      if(supportsMeta())
        gen.visitInsn(Opcodes.ACONST_NULL);
      for(ISeq s = RT.seq(closesExprs); s != null; s = s.next())
        {
                LocalBindingExpr lbe = (LocalBindingExpr) s.first();
        LocalBinding lb = lbe.b;
        if(lb.getPrimitiveType() != null)
          objx.emitUnboxedLocal(gen, lb);
        else
          objx.emitLocal(gen, lb, lbe.shouldClear);
        }
      gen.invokeConstructor(objtype, new Method("<init>", Type.VOID_TYPE, ctorTypes()));
      }
    if(context == C.STATEMENT)
      gen.pop();
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    return (compiledClass != null) ? compiledClass
      : (tag != null) ? HostExpr.tagToClass(tag)
      : IFn.class;
  }

  public void emitAssignLocal(GeneratorAdapter gen, LocalBinding lb,Expr val){
    if(!isMutable(lb))
      throw new IllegalArgumentException("Cannot assign to non-mutable: " + lb.name);
    Class primc = lb.getPrimitiveType();
    gen.loadThis();
    if(primc != null)
      {
      if(!(val instanceof MaybePrimitiveExpr && ((MaybePrimitiveExpr) val).canEmitPrimitive()))
        throw new IllegalArgumentException("Must assign primitive to primitive mutable: " + lb.name);
      MaybePrimitiveExpr me = (MaybePrimitiveExpr) val;
      me.emitUnboxed(C.EXPRESSION, this, gen);
      gen.putField(objtype, lb.name, Type.getType(primc));
      }
    else
      {
      val.emit(C.EXPRESSION, this, gen);
      gen.putField(objtype, lb.name, OBJECT_TYPE);
      }
  }

  private void emitLocal(GeneratorAdapter gen, LocalBinding lb, boolean clear){
    if(closes.containsKey(lb))
      {
      Class primc = lb.getPrimitiveType();
      gen.loadThis();
      if(primc != null)
        {
        gen.getField(objtype, lb.name, Type.getType(primc));
        HostExpr.emitBoxReturn(this, gen, primc);
        }
      else
        {
        gen.getField(objtype, lb.name, OBJECT_TYPE);
        if(onceOnly && clear && lb.canBeCleared)
          {
          gen.loadThis();
          gen.visitInsn(Opcodes.ACONST_NULL);
          gen.putField(objtype, lb.name, OBJECT_TYPE);
          }
        }
      }
    else
      {
      int argoff = isStatic?0:1;
      Class primc = lb.getPrimitiveType();
//            String rep = lb.sym.name + " " + lb.toString().substring(lb.toString().lastIndexOf('@'));
      if(lb.isArg)
        {
        gen.loadArg(lb.idx-argoff);
        if(primc != null)
          HostExpr.emitBoxReturn(this, gen, primc);
                else
                    {
                    if(clear && lb.canBeCleared)
                        {
//                        System.out.println("clear: " + rep);
                        gen.visitInsn(Opcodes.ACONST_NULL);
                        gen.storeArg(lb.idx - argoff);
                        }
                    else
                        {
//                        System.out.println("use: " + rep);
                        }
                    }    
        }
      else
        {
        if(primc != null)
          {
          gen.visitVarInsn(Type.getType(primc).getOpcode(Opcodes.ILOAD), lb.idx);
          HostExpr.emitBoxReturn(this, gen, primc);
          }
        else
                    {
          gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ILOAD), lb.idx);
                    if(clear && lb.canBeCleared)
                        {
//                        System.out.println("clear: " + rep);
                        gen.visitInsn(Opcodes.ACONST_NULL);
                        gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), lb.idx);
                        }
                    else
                        {
//                        System.out.println("use: " + rep);
                        }
                    }
        }
      }
  }

  private void emitUnboxedLocal(GeneratorAdapter gen, LocalBinding lb){
    int argoff = isStatic?0:1;
    Class primc = lb.getPrimitiveType();
    if(closes.containsKey(lb))
      {
      gen.loadThis();
      gen.getField(objtype, lb.name, Type.getType(primc));
      }
    else if(lb.isArg)
      gen.loadArg(lb.idx-argoff);
    else
      gen.visitVarInsn(Type.getType(primc).getOpcode(Opcodes.ILOAD), lb.idx);
  }

  public void emitVar(GeneratorAdapter gen, Var var){
    Integer i = (Integer) vars.valAt(var);
    emitConstant(gen, i);
    //gen.getStatic(fntype, munge(var.sym.toString()), VAR_TYPE);
  }

  final static Method varGetMethod = Method.getMethod("Object get()");
  final static Method varGetRawMethod = Method.getMethod("Object getRawRoot()");

  public void emitVarValue(GeneratorAdapter gen, Var v){
    Integer i = (Integer) vars.valAt(v);
    if(!v.isDynamic())
      {
      emitConstant(gen, i);
      gen.invokeVirtual(VAR_TYPE, varGetRawMethod);
      }
    else
      {
      emitConstant(gen, i);
      gen.invokeVirtual(VAR_TYPE, varGetMethod);
      }
  }

  public void emitKeyword(GeneratorAdapter gen, Keyword k){
    Integer i = (Integer) keywords.valAt(k);
    emitConstant(gen, i);
//    gen.getStatic(fntype, munge(k.sym.toString()), KEYWORD_TYPE);
  }

  public void emitConstant(GeneratorAdapter gen, int id){
    gen.getStatic(objtype, constantName(id), constantType(id));
  }


  String constantName(int id){
    return CONST_PREFIX + id;
  }

  String siteName(int n){
    return "__site__" + n;
  }

  String siteNameStatic(int n){
    return siteName(n) + "__";
  }

  String thunkName(int n){
    return "__thunk__" + n;
  }

  String cachedClassName(int n){
    return "__cached_class__" + n;
  }

  String cachedVarName(int n){
    return "__cached_var__" + n;
  }

  String varCallsiteName(int n){
    return "__var__callsite__" + n;
  }

  String thunkNameStatic(int n){
    return thunkName(n) + "__";
  }

  Type constantType(int id){
    Object o = constants.nth(id);
    Class c = clojure.lang.Util.classOf(o);
    if(c!= null && Modifier.isPublic(c.getModifiers()))
      {
      //can't emit derived fn types due to visibility
      if(LazySeq.class.isAssignableFrom(c))
        return Type.getType(ISeq.class);
      else if(c == Keyword.class)
        return Type.getType(Keyword.class);
//      else if(c == KeywordCallSite.class)
//        return Type.getType(KeywordCallSite.class);
      else if(RestFn.class.isAssignableFrom(c))
        return Type.getType(RestFn.class);
      else if(AFn.class.isAssignableFrom(c))
          return Type.getType(AFn.class);
        else if(c == Var.class)
            return Type.getType(Var.class);
          else if(c == String.class)
              return Type.getType(String.class);

//      return Type.getType(c);
      }
    return OBJECT_TYPE;
  }

}

enum PATHTYPE {
    PATH, BRANCH;
}

static class PathNode{
    final PATHTYPE type;
    final PathNode parent;

    PathNode(PATHTYPE type, PathNode parent) {
        this.type = type;
        this.parent = parent;
    }
}

static PathNode clearPathRoot(){
    return (PathNode) CLEAR_ROOT.get();
}
   
enum PSTATE{
  REQ, REST, DONE
}

public static class FnMethod extends ObjMethod{
  //localbinding->localbinding
  PersistentVector reqParms = PersistentVector.EMPTY;
  LocalBinding restParm = null;
  Type[] argtypes;
  Class[] argclasses;
  Class retClass;
  String prim ;

  public FnMethod(ObjExpr objx, ObjMethod parent){
    super(objx, parent);
  }

  static public char classChar(Object x){
    Class c = null;
    if(x instanceof Class)
      c = (Class) x;
    else if(x instanceof Symbol)
      c = primClass((Symbol) x);
    if(c == null || !c.isPrimitive())
      return 'O';
    if(c == long.class)
      return 'L';
    if(c == double.class)
      return 'D';
    throw new IllegalArgumentException("Only long and double primitives are supported");
  }

  static public String primInterface(IPersistentVector arglist) {
    StringBuilder sb = new StringBuilder();
    for(int i=0;i<arglist.count();i++)
      sb.append(classChar(tagOf(arglist.nth(i))));
    sb.append(classChar(tagOf(arglist)));
    String ret = sb.toString();
    boolean prim = ret.contains("L") || ret.contains("D");
    if(prim && arglist.count() > 4)
      throw new IllegalArgumentException("fns taking primitives support only 4 or fewer args");
    if(prim)
      return "clojure.lang.IFn$" + ret;
    return null;
  }

  static FnMethod parse(ObjExpr objx, ISeq form, boolean isStatic) {
    //([args] body...)
    IPersistentVector parms = (IPersistentVector) RT.first(form);
    ISeq body = RT.next(form);
    try
      {
      FnMethod method = new FnMethod(objx, (ObjMethod) METHOD.deref());
      method.line = lineDeref();
      method.column = columnDeref();
      //register as the current method and set up a new env frame
            PathNode pnode =  (PathNode) CLEAR_PATH.get();
      if(pnode == null)
        pnode = new PathNode(PATHTYPE.PATH,null);
      Var.pushThreadBindings(
          RT.mapUniqueKeys(
              METHOD, method,
              LOCAL_ENV, LOCAL_ENV.deref(),
              LOOP_LOCALS, null,
              NEXT_LOCAL_NUM, 0
                            ,CLEAR_PATH, pnode
                            ,CLEAR_ROOT, pnode
                            ,CLEAR_SITES, PersistentHashMap.EMPTY
                        ));

      method.prim = primInterface(parms);
      if(method.prim != null)
        method.prim = method.prim.replace('.', '/');

      method.retClass = tagClass(tagOf(parms));
      if(method.retClass.isPrimitive() && !(method.retClass == double.class || method.retClass == long.class))
        throw new IllegalArgumentException("Only long and double primitives are supported");

      //register 'this' as local 0
      //registerLocal(THISFN, null, null);
      if(!isStatic)
        {
        if(objx.thisName != null)
          registerLocal(Symbol.intern(objx.thisName), null, null,false);
        else
          getAndIncLocalNum();
        }
      PSTATE state = PSTATE.REQ;
      PersistentVector argLocals = PersistentVector.EMPTY;
      ArrayList<Type> argtypes = new ArrayList();
      ArrayList<Class> argclasses = new ArrayList();
      for(int i = 0; i < parms.count(); i++)
        {
        if(!(parms.nth(i) instanceof Symbol))
          throw new IllegalArgumentException("fn params must be Symbols");
        Symbol p = (Symbol) parms.nth(i);
        if(p.getNamespace() != null)
          throw Util.runtimeException("Can't use qualified name as parameter: " + p);
        if(p.equals(_AMP_))
          {
//          if(isStatic)
//            throw Util.runtimeException("Variadic fns cannot be static");
          if(state == PSTATE.REQ)
            state = PSTATE.REST;
          else
            throw Util.runtimeException("Invalid parameter list");
          }

        else
          {
          Class pc = primClass(tagClass(tagOf(p)));
//          if(pc.isPrimitive() && !isStatic)
//            {
//            pc = Object.class;
//            p = (Symbol) ((IObj) p).withMeta((IPersistentMap) RT.assoc(RT.meta(p), RT.TAG_KEY, null));
//            }
//            throw Util.runtimeException("Non-static fn can't have primitive parameter: " + p);
          if(pc.isPrimitive() && !(pc == double.class || pc == long.class))
            throw new IllegalArgumentException("Only long and double primitives are supported: " + p);

          if(state == PSTATE.REST && tagOf(p) != null)
            throw Util.runtimeException("& arg cannot have type hint");
          if(state == PSTATE.REST && method.prim != null)
            throw Util.runtimeException("fns taking primitives cannot be variadic");
                                 
          if(state == PSTATE.REST)
            pc = ISeq.class;
          argtypes.add(Type.getType(pc));
          argclasses.add(pc);
          LocalBinding lb = pc.isPrimitive() ?
                            registerLocal(p, null, new MethodParamExpr(pc), true)
                                     : registerLocal(p, state == PSTATE.REST ? ISEQ : tagOf(p), null, true);
          argLocals = argLocals.cons(lb);
          switch(state)
            {
            case REQ:
              method.reqParms = method.reqParms.cons(lb);
              break;
            case REST:
              method.restParm = lb;
              state = PSTATE.DONE;
              break;

            default:
              throw Util.runtimeException("Unexpected parameter");
            }
          }
        }
      if(method.reqParms.count() > MAX_POSITIONAL_ARITY)
        throw Util.runtimeException("Can't specify more than " + MAX_POSITIONAL_ARITY + " params");
      LOOP_LOCALS.set(argLocals);
      method.argLocals = argLocals;
//      if(isStatic)
      if(method.prim != null)
        {
        method.argtypes = argtypes.toArray(new Type[argtypes.size()]);
        method.argclasses = argclasses.toArray(new Class[argtypes.size()]);
        for(int i = 0; i < method.argclasses.length; i++)
          {
          if(method.argclasses[i] == long.class || method.argclasses[i] == double.class)
            getAndIncLocalNum();
          }
        }
      method.body = (new BodyExpr.Parser()).parse(C.RETURN, body);
      return method;
      }
    finally
      {
      Var.popThreadBindings();
      }
  }

  public void emit(ObjExpr fn, ClassVisitor cv){
    if(prim != null)
      doEmitPrim(fn, cv);
    else if(fn.isStatic)
      doEmitStatic(fn,cv);
    else
      doEmit(fn,cv);
  }

  public void doEmitStatic(ObjExpr fn, ClassVisitor cv){
    Method ms = new Method("invokeStatic", getReturnType(), argtypes);

    GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
                                                ms,
                                                null,
                                                //todo don't hardwire this
                                                EXCEPTION_TYPES,
                                                cv);
    gen.visitCode();
    Label loopLabel = gen.mark();
    gen.visitLineNumber(line, loopLabel);
    try
      {
      Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel, METHOD, this));
      emitBody(objx, gen, retClass, body);

      Label end = gen.mark();
      for(ISeq lbs = argLocals.seq(); lbs != null; lbs = lbs.next())
        {
        LocalBinding lb = (LocalBinding) lbs.first();
        gen.visitLocalVariable(lb.name, argtypes[lb.idx].getDescriptor(), null, loopLabel, end, lb.idx);
        }
      }
    finally
      {
      Var.popThreadBindings();
      }

    gen.returnValue();
    //gen.visitMaxs(1, 1);
    gen.endMethod();

  //generate the regular invoke, calling the static method
    Method m = new Method(getMethodName(), OBJECT_TYPE, getArgTypes());

    gen = new GeneratorAdapter(ACC_PUBLIC,
                               m,
                               null,
                               //todo don't hardwire this
                               EXCEPTION_TYPES,
                               cv);
    gen.visitCode();
    for(int i = 0; i < argtypes.length; i++)
      {
      gen.loadArg(i);
      HostExpr.emitUnboxArg(fn, gen, argclasses[i]);
      }
    gen.invokeStatic(objx.objtype, ms);
    gen.box(getReturnType());


    gen.returnValue();
    //gen.visitMaxs(1, 1);
    gen.endMethod();

  }

  public void doEmitPrim(ObjExpr fn, ClassVisitor cv){
    Type returnType;
    if (retClass == double.class || retClass == long.class)
      returnType = getReturnType();
    else returnType = OBJECT_TYPE;
    Method ms = new Method("invokePrim", returnType, argtypes);

    GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_FINAL,
                                                ms,
                                                null,
                                                //todo don't hardwire this
                                                EXCEPTION_TYPES,
                                                cv);
    gen.visitCode();

    Label loopLabel = gen.mark();
    gen.visitLineNumber(line, loopLabel);
    try
      {
      Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel, METHOD, this));
      emitBody(objx, gen, retClass, body);

      Label end = gen.mark();
      gen.visitLocalVariable("this", "Ljava/lang/Object;", null, loopLabel, end, 0);
      for(ISeq lbs = argLocals.seq(); lbs != null; lbs = lbs.next())
        {
        LocalBinding lb = (LocalBinding) lbs.first();
        gen.visitLocalVariable(lb.name, argtypes[lb.idx-1].getDescriptor(), null, loopLabel, end, lb.idx);
        }
      }
    finally
      {
      Var.popThreadBindings();
      }

    gen.returnValue();
    //gen.visitMaxs(1, 1);
    gen.endMethod();

  //generate the regular invoke, calling the prim method
    Method m = new Method(getMethodName(), OBJECT_TYPE, getArgTypes());

    gen = new GeneratorAdapter(ACC_PUBLIC,
                               m,
                               null,
                               //todo don't hardwire this
                               EXCEPTION_TYPES,
                               cv);
    gen.visitCode();
    gen.loadThis();
    for(int i = 0; i < argtypes.length; i++)
      {
      gen.loadArg(i);
      HostExpr.emitUnboxArg(fn, gen, argclasses[i]);
      }
    gen.invokeInterface(Type.getType("L"+prim+";"), ms);
    gen.box(getReturnType());


    gen.returnValue();
    //gen.visitMaxs(1, 1);
    gen.endMethod();

  }
  public void doEmit(ObjExpr fn, ClassVisitor cv){
    Method m = new Method(getMethodName(), getReturnType(), getArgTypes());

    GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC,
                                                m,
                                                null,
                                                //todo don't hardwire this
                                                EXCEPTION_TYPES,
                                                cv);
    gen.visitCode();

    Label loopLabel = gen.mark();
    gen.visitLineNumber(line, loopLabel);
    try
      {
      Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel, METHOD, this));

      body.emit(C.RETURN, fn, gen);
      Label end = gen.mark();

      gen.visitLocalVariable("this", "Ljava/lang/Object;", null, loopLabel, end, 0);
      for(ISeq lbs = argLocals.seq(); lbs != null; lbs = lbs.next())
        {
        LocalBinding lb = (LocalBinding) lbs.first();
        gen.visitLocalVariable(lb.name, "Ljava/lang/Object;", null, loopLabel, end, lb.idx);
        }
      }
    finally
      {
      Var.popThreadBindings();
      }

    gen.returnValue();
    //gen.visitMaxs(1, 1);
    gen.endMethod();
  }



  public final PersistentVector reqParms(){
    return reqParms;
  }

  public final LocalBinding restParm(){
    return restParm;
  }

  boolean isVariadic(){
    return restParm != null;
  }

  int numParams(){
    return reqParms.count() + (isVariadic() ? 1 : 0);
  }

  String getMethodName(){
    return isVariadic()?"doInvoke":"invoke";
  }

  Type getReturnType(){
    if(prim != null) //objx.isStatic)
      return Type.getType(retClass);
    return OBJECT_TYPE;
  }

  Type[] getArgTypes(){
    if(isVariadic() && reqParms.count() == MAX_POSITIONAL_ARITY)
      {
      Type[] ret = new Type[MAX_POSITIONAL_ARITY + 1];
      for(int i = 0;i<MAX_POSITIONAL_ARITY + 1;i++)
        ret[i] = OBJECT_TYPE;
      return ret;
      }
    return  ARG_TYPES[numParams()];
  }

  void emitClearLocals(GeneratorAdapter gen){
//    for(int i = 1; i < numParams() + 1; i++)
//      {
//      if(!localsUsedInCatchFinally.contains(i))
//        {
//        gen.visitInsn(Opcodes.ACONST_NULL);
//        gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), i);
//        }
//      }
//    for(int i = numParams() + 1; i < maxLocal + 1; i++)
//      {
//      if(!localsUsedInCatchFinally.contains(i))
//        {
//        LocalBinding b = (LocalBinding) RT.get(indexlocals, i);
//        if(b == null || maybePrimitiveType(b.init) == null)
//          {
//          gen.visitInsn(Opcodes.ACONST_NULL);
//          gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), i);
//          }
//        }
//      }
//    if(((FnExpr)objx).onceOnly)
//      {
//      objx.emitClearCloses(gen);
//      }
  }
}

abstract public static class ObjMethod{
  //when closures are defined inside other closures,
  //the closed over locals need to be propagated to the enclosing objx
  public final ObjMethod parent;
  //localbinding->localbinding
  IPersistentMap locals = null;
  //num->localbinding
  IPersistentMap indexlocals = null;
  Expr body = null;
  ObjExpr objx;
  PersistentVector argLocals;
  int maxLocal = 0;
  int line;
  int column;
  PersistentHashSet localsUsedInCatchFinally = PersistentHashSet.EMPTY;
  protected IPersistentMap methodMeta;


  public final IPersistentMap locals(){
    return locals;
  }

  public final Expr body(){
    return body;
  }

  public final ObjExpr objx(){
    return objx;
  }

  public final PersistentVector argLocals(){
    return argLocals;
  }

  public final int maxLocal(){
    return maxLocal;
  }

  public final int line(){
    return line;
  }

  public final int column(){
    return column;
  }

  public ObjMethod(ObjExpr objx, ObjMethod parent){
    this.parent = parent;
    this.objx = objx;
  }

  static void emitBody(ObjExpr objx, GeneratorAdapter gen, Class retClass, Expr body) {
      MaybePrimitiveExpr be = (MaybePrimitiveExpr) body;
      if(Util.isPrimitive(retClass) && be.canEmitPrimitive())
        {
        Class bc = maybePrimitiveType(be);
        if(bc == retClass)
          be.emitUnboxed(C.RETURN, objx, gen);
        else if(retClass == long.class && bc == int.class)
          {
          be.emitUnboxed(C.RETURN, objx, gen);
          gen.visitInsn(I2L);
          }
        else if(retClass == double.class && bc == float.class)
          {
          be.emitUnboxed(C.RETURN, objx, gen);
          gen.visitInsn(F2D);
          }
        else if(retClass == int.class && bc == long.class)
          {
          be.emitUnboxed(C.RETURN, objx, gen);
          gen.invokeStatic(RT_TYPE, Method.getMethod("int intCast(long)"));
          }
        else if(retClass == float.class && bc == double.class)
          {
          be.emitUnboxed(C.RETURN, objx, gen);
          gen.visitInsn(D2F);
          }
        else
          throw new IllegalArgumentException("Mismatched primitive return, expected: "
                                             + retClass + ", had: " + be.getJavaClass());
        }
      else
        {
        body.emit(C.RETURN, objx, gen);
        if(retClass == void.class)
          {
          gen.pop();
          }
        else
          gen.unbox(Type.getType(retClass));
        }
  }
  abstract int numParams();
  abstract String getMethodName();
  abstract Type getReturnType();
  abstract Type[] getArgTypes();

  public void emit(ObjExpr fn, ClassVisitor cv){
    Method m = new Method(getMethodName(), getReturnType(), getArgTypes());

    GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC,
                                                m,
                                                null,
                                                //todo don't hardwire this
                                                EXCEPTION_TYPES,
                                                cv);
    gen.visitCode();

    Label loopLabel = gen.mark();
    gen.visitLineNumber(line, loopLabel);
    try
      {
      Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel, METHOD, this));

      body.emit(C.RETURN, fn, gen);
      Label end = gen.mark();
      gen.visitLocalVariable("this", "Ljava/lang/Object;", null, loopLabel, end, 0);
      for(ISeq lbs = argLocals.seq(); lbs != null; lbs = lbs.next())
        {
        LocalBinding lb = (LocalBinding) lbs.first();
        gen.visitLocalVariable(lb.name, "Ljava/lang/Object;", null, loopLabel, end, lb.idx);
        }
      }
    finally
      {
      Var.popThreadBindings();
      }

    gen.returnValue();
    //gen.visitMaxs(1, 1);
    gen.endMethod();
  }

    void emitClearLocals(GeneratorAdapter gen){
    }
   
  void emitClearLocalsOld(GeneratorAdapter gen){
    for(int i=0;i<argLocals.count();i++)
      {
      LocalBinding lb = (LocalBinding) argLocals.nth(i);
      if(!localsUsedInCatchFinally.contains(lb.idx) && lb.getPrimitiveType() == null)
        {
        gen.visitInsn(Opcodes.ACONST_NULL);
        gen.storeArg(lb.idx - 1);       
        }

      }
//    for(int i = 1; i < numParams() + 1; i++)
//      {
//      if(!localsUsedInCatchFinally.contains(i))
//        {
//        gen.visitInsn(Opcodes.ACONST_NULL);
//        gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), i);
//        }
//      }
    for(int i = numParams() + 1; i < maxLocal + 1; i++)
      {
      if(!localsUsedInCatchFinally.contains(i))
        {
        LocalBinding b = (LocalBinding) RT.get(indexlocals, i);
        if(b == null || maybePrimitiveType(b.init) == null)
          {
          gen.visitInsn(Opcodes.ACONST_NULL);
          gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), i);
          }
        }
      }
  }
}

public static class LocalBinding{
  public final Symbol sym;
  public final Symbol tag;
  public Expr init;
  public final int idx;
  public final String name;
  public final boolean isArg;
    public final PathNode clearPathRoot;
  public boolean canBeCleared = !RT.booleanCast(getCompilerOption(disableLocalsClearingKey));
  public boolean recurMistmatch = false;

    public LocalBinding(int num, Symbol sym, Symbol tag, Expr init, boolean isArg,PathNode clearPathRoot)
                {
    if(maybePrimitiveType(init) != null && tag != null)
      throw new UnsupportedOperationException("Can't type hint a local with a primitive initializer");
    this.idx = num;
    this.sym = sym;
    this.tag = tag;
    this.init = init;
    this.isArg = isArg;
        this.clearPathRoot = clearPathRoot;
    name = munge(sym.name);
  }

  public boolean hasJavaClass() {
    if(init != null && init.hasJavaClass()
       && Util.isPrimitive(init.getJavaClass())
       && !(init instanceof MaybePrimitiveExpr))
      return false;
    return tag != null
           || (init != null && init.hasJavaClass());
  }

  public Class getJavaClass() {
    return tag != null ? HostExpr.tagToClass(tag)
                       : init.getJavaClass();
  }

  public Class getPrimitiveType(){
    return maybePrimitiveType(init);
  }
}

public static class LocalBindingExpr implements Expr, MaybePrimitiveExpr, AssignableExpr{
  public final LocalBinding b;
  public final Symbol tag;

    public final PathNode clearPath;
    public final PathNode clearRoot;
    public boolean shouldClear = false;


  public LocalBindingExpr(LocalBinding b, Symbol tag)
            {
    if(b.getPrimitiveType() != null && tag != null)
      throw new UnsupportedOperationException("Can't type hint a primitive local");
    this.b = b;
    this.tag = tag;

        this.clearPath = (PathNode)CLEAR_PATH.get();
        this.clearRoot = (PathNode)CLEAR_ROOT.get();
        IPersistentCollection sites = (IPersistentCollection) RT.get(CLEAR_SITES.get(),b);

        if(b.idx > 0)
            {
//            Object dummy;

            if(sites != null)
                {
                for(ISeq s = sites.seq();s!=null;s = s.next())
                    {
                    LocalBindingExpr o = (LocalBindingExpr) s.first();
                    PathNode common = commonPath(clearPath,o.clearPath);
                    if(common != null && common.type == PATHTYPE.PATH)
                        o.shouldClear = false;
//                    else
//                        dummy = null;
                    }
                }

            if(clearRoot == b.clearPathRoot)
                {
                this.shouldClear = true;
                sites = RT.conj(sites,this);
                CLEAR_SITES.set(RT.assoc(CLEAR_SITES.get(), b, sites));
                }
//            else
//                dummy = null;
            }
       }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval locals");
  }

  public boolean canEmitPrimitive(){
    return b.getPrimitiveType() != null;
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    objx.emitUnboxedLocal(gen, b);
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    if(context != C.STATEMENT)
      objx.emitLocal(gen, b, shouldClear);
  }

  public Object evalAssign(Expr val) {
    throw new UnsupportedOperationException("Can't eval locals");
  }

  public void emitAssign(C context, ObjExpr objx, GeneratorAdapter gen, Expr val){
    objx.emitAssignLocal(gen, b,val);
    if(context != C.STATEMENT)
      objx.emitLocal(gen, b, false);
  }

  public boolean hasJavaClass() {
    return tag != null || b.hasJavaClass();
  }

  public Class getJavaClass() {
    if(tag != null)
      return HostExpr.tagToClass(tag);
    return b.getJavaClass();
  }


}

public static class BodyExpr implements Expr, MaybePrimitiveExpr{
  PersistentVector exprs;

  public final PersistentVector exprs(){
    return exprs;
  }

  public BodyExpr(PersistentVector exprs){
    this.exprs = exprs;
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object frms) {
      ISeq forms = (ISeq) frms;
      if(Util.equals(RT.first(forms), DO))
        forms = RT.next(forms);
      PersistentVector exprs = PersistentVector.EMPTY;
      for(; forms != null; forms = forms.next())
        {
        Expr e = (context != C.EVAL &&
                  (context == C.STATEMENT || forms.next() != null)) ?
                 analyze(C.STATEMENT, forms.first())
                                                                    :
                 analyze(context, forms.first());
        exprs = exprs.cons(e);
        }
      if(exprs.count() == 0)
        exprs = exprs.cons(NIL_EXPR);
      return new BodyExpr(exprs);
    }
  }

  public Object eval() {
    Object ret = null;
    for(Object o : exprs)
      {
      Expr e = (Expr) o;
      ret = e.eval();
      }
    return ret;
  }

  public boolean canEmitPrimitive(){
    return lastExpr() instanceof MaybePrimitiveExpr && ((MaybePrimitiveExpr)lastExpr()).canEmitPrimitive();
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    for(int i = 0; i < exprs.count() - 1; i++)
      {
      Expr e = (Expr) exprs.nth(i);
      e.emit(C.STATEMENT, objx, gen);
      }
    MaybePrimitiveExpr last = (MaybePrimitiveExpr) exprs.nth(exprs.count() - 1);
    last.emitUnboxed(context, objx, gen);
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    for(int i = 0; i < exprs.count() - 1; i++)
      {
      Expr e = (Expr) exprs.nth(i);
      e.emit(C.STATEMENT, objx, gen);
      }
    Expr last = (Expr) exprs.nth(exprs.count() - 1);
    last.emit(context, objx, gen);
  }

  public boolean hasJavaClass() {
    return lastExpr().hasJavaClass();
  }

  public Class getJavaClass() {
    return lastExpr().getJavaClass();
  }

  private Expr lastExpr(){
    return (Expr) exprs.nth(exprs.count() - 1);
  }
}

public static class BindingInit{
  LocalBinding binding;
  Expr init;

  public final LocalBinding binding(){
    return binding;
  }

  public final Expr init(){
    return init;
  }

  public BindingInit(LocalBinding binding, Expr init){
    this.binding = binding;
    this.init = init;
  }
}

public static class LetFnExpr implements Expr{
  public final PersistentVector bindingInits;
  public final Expr body;

  public LetFnExpr(PersistentVector bindingInits, Expr body){
    this.bindingInits = bindingInits;
    this.body = body;
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object frm) {
      ISeq form = (ISeq) frm;
      //(letfns* [var (fn [args] body) ...] body...)
      if(!(RT.second(form) instanceof IPersistentVector))
        throw new IllegalArgumentException("Bad binding form, expected vector");

      IPersistentVector bindings = (IPersistentVector) RT.second(form);
      if((bindings.count() % 2) != 0)
        throw new IllegalArgumentException("Bad binding form, expected matched symbol expression pairs");

      ISeq body = RT.next(RT.next(form));

      if(context == C.EVAL)
        return analyze(context, RT.list(RT.list(FNONCE, PersistentVector.EMPTY, form)));

      IPersistentMap dynamicBindings = RT.map(LOCAL_ENV, LOCAL_ENV.deref(),
                                              NEXT_LOCAL_NUM, NEXT_LOCAL_NUM.deref());

      try
        {
        Var.pushThreadBindings(dynamicBindings);

        //pre-seed env (like Lisp labels)
        PersistentVector lbs = PersistentVector.EMPTY;
        for(int i = 0; i < bindings.count(); i += 2)
          {
          if(!(bindings.nth(i) instanceof Symbol))
            throw new IllegalArgumentException(
                "Bad binding form, expected symbol, got: " + bindings.nth(i));
          Symbol sym = (Symbol) bindings.nth(i);
          if(sym.getNamespace() != null)
            throw Util.runtimeException("Can't let qualified name: " + sym);
          LocalBinding lb = registerLocal(sym, tagOf(sym), null,false);
          lb.canBeCleared = false;
          lbs = lbs.cons(lb);
          }
        PersistentVector bindingInits = PersistentVector.EMPTY;
        for(int i = 0; i < bindings.count(); i += 2)
          {
          Symbol sym = (Symbol) bindings.nth(i);
          Expr init = analyze(C.EXPRESSION, bindings.nth(i + 1), sym.name);
          LocalBinding lb = (LocalBinding) lbs.nth(i / 2);
          lb.init = init;
          BindingInit bi = new BindingInit(lb, init);
          bindingInits = bindingInits.cons(bi);
          }
        return new LetFnExpr(bindingInits, (new BodyExpr.Parser()).parse(context, body));
        }
      finally
        {
        Var.popThreadBindings();
        }
    }
  }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval letfns");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    for(int i = 0; i < bindingInits.count(); i++)
      {
      BindingInit bi = (BindingInit) bindingInits.nth(i);
      gen.visitInsn(Opcodes.ACONST_NULL);
      gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), bi.binding.idx);
      }

    IPersistentSet lbset = PersistentHashSet.EMPTY;

    for(int i = 0; i < bindingInits.count(); i++)
      {
      BindingInit bi = (BindingInit) bindingInits.nth(i);
      lbset = (IPersistentSet) lbset.cons(bi.binding);
      bi.init.emit(C.EXPRESSION, objx, gen);
      gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), bi.binding.idx);
      }

    for(int i = 0; i < bindingInits.count(); i++)
      {
      BindingInit bi = (BindingInit) bindingInits.nth(i);
      ObjExpr fe = (ObjExpr) bi.init;
      gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ILOAD), bi.binding.idx);
      fe.emitLetFnInits(gen, objx, lbset);
      }

    Label loopLabel = gen.mark();

    body.emit(context, objx, gen);

    Label end = gen.mark();
//    gen.visitLocalVariable("this", "Ljava/lang/Object;", null, loopLabel, end, 0);
    for(ISeq bis = bindingInits.seq(); bis != null; bis = bis.next())
      {
      BindingInit bi = (BindingInit) bis.first();
      String lname = bi.binding.name;
      if(lname.endsWith("__auto__"))
        lname += RT.nextID();
      Class primc = maybePrimitiveType(bi.init);
      if(primc != null)
        gen.visitLocalVariable(lname, Type.getDescriptor(primc), null, loopLabel, end,
                               bi.binding.idx);
      else
        gen.visitLocalVariable(lname, "Ljava/lang/Object;", null, loopLabel, end, bi.binding.idx);
      }
  }

  public boolean hasJavaClass() {
    return body.hasJavaClass();
  }

  public Class getJavaClass() {
    return body.getJavaClass();
  }
}

public static class LetExpr implements Expr, MaybePrimitiveExpr{
  public final PersistentVector bindingInits;
  public final Expr body;
  public final boolean isLoop;

  public LetExpr(PersistentVector bindingInits, Expr body, boolean isLoop){
    this.bindingInits = bindingInits;
    this.body = body;
    this.isLoop = isLoop;
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object frm) {
      ISeq form = (ISeq) frm;
      //(let [var val var2 val2 ...] body...)
      boolean isLoop = RT.first(form).equals(LOOP);
      if(!(RT.second(form) instanceof IPersistentVector))
        throw new IllegalArgumentException("Bad binding form, expected vector");

      IPersistentVector bindings = (IPersistentVector) RT.second(form);
      if((bindings.count() % 2) != 0)
        throw new IllegalArgumentException("Bad binding form, expected matched symbol expression pairs");

      ISeq body = RT.next(RT.next(form));

      if(context == C.EVAL
         || (context == C.EXPRESSION && isLoop))
        return analyze(context, RT.list(RT.list(FNONCE, PersistentVector.EMPTY, form)));

      ObjMethod method = (ObjMethod) METHOD.deref();
      IPersistentMap backupMethodLocals = method.locals;
      IPersistentMap backupMethodIndexLocals = method.indexlocals;
      IPersistentVector recurMismatches = PersistentVector.EMPTY;
      for (int i = 0; i < bindings.count()/2; i++)
        {
        recurMismatches = recurMismatches.cons(RT.F);
        }

      //may repeat once for each binding with a mismatch, return breaks
      while(true){
        IPersistentMap dynamicBindings = RT.map(LOCAL_ENV, LOCAL_ENV.deref(),
                            NEXT_LOCAL_NUM, NEXT_LOCAL_NUM.deref());
        method.locals = backupMethodLocals;
        method.indexlocals = backupMethodIndexLocals;

        PathNode looproot = new PathNode(PATHTYPE.PATH, (PathNode) CLEAR_PATH.get());
        PathNode clearroot = new PathNode(PATHTYPE.PATH,looproot);
        PathNode clearpath = new PathNode(PATHTYPE.PATH,looproot);
        if(isLoop)
          dynamicBindings = dynamicBindings.assoc(LOOP_LOCALS, null);

        try
          {
          Var.pushThreadBindings(dynamicBindings);

          PersistentVector bindingInits = PersistentVector.EMPTY;
          PersistentVector loopLocals = PersistentVector.EMPTY;
          for(int i = 0; i < bindings.count(); i += 2)
            {
            if(!(bindings.nth(i) instanceof Symbol))
              throw new IllegalArgumentException(
                  "Bad binding form, expected symbol, got: " + bindings.nth(i));
            Symbol sym = (Symbol) bindings.nth(i);
            if(sym.getNamespace() != null)
              throw Util.runtimeException("Can't let qualified name: " + sym);
            Expr init = analyze(C.EXPRESSION, bindings.nth(i + 1), sym.name);
            if(isLoop)
              {
              if(recurMismatches != null && RT.booleanCast(recurMismatches.nth(i/2)))
                {
                init = new StaticMethodExpr("", 0, 0, null, RT.class, "box", RT.vector(init));
                if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
                  RT.errPrintWriter().println("Auto-boxing loop arg: " + sym);
                }
              else if(maybePrimitiveType(init) == int.class)
                init = new StaticMethodExpr("", 0, 0, null, RT.class, "longCast", RT.vector(init));
              else if(maybePrimitiveType(init) == float.class)
                init = new StaticMethodExpr("", 0, 0, null, RT.class, "doubleCast", RT.vector(init));
              }
            //sequential enhancement of env (like Lisp let*)
            try
              {
              if(isLoop)
                {
                              Var.pushThreadBindings(
                  RT.map(CLEAR_PATH, clearpath,
                                         CLEAR_ROOT, clearroot,
                                         NO_RECUR, null));

                }
              LocalBinding lb = registerLocal(sym, tagOf(sym), init,false);
              BindingInit bi = new BindingInit(lb, init);
              bindingInits = bindingInits.cons(bi);
              if(isLoop)
                loopLocals = loopLocals.cons(lb);
              }
            finally
              {
              if(isLoop)
                  Var.popThreadBindings();
              }
            }
          if(isLoop)
            LOOP_LOCALS.set(loopLocals);
          Expr bodyExpr;
          boolean moreMismatches = false;
          try {
            if(isLoop)
              {
                            Var.pushThreadBindings(
                RT.map(CLEAR_PATH, clearpath,
                                       CLEAR_ROOT, clearroot,
                                       NO_RECUR, null));
                                                      
              }
            bodyExpr = (new BodyExpr.Parser()).parse(isLoop ? C.RETURN : context, body);
            }
          finally{
            if(isLoop)
              {
                Var.popThreadBindings();
              for(int i = 0;i< loopLocals.count();i++)
                {
                LocalBinding lb = (LocalBinding) loopLocals.nth(i);
                if(lb.recurMistmatch)
                  {
                  recurMismatches = (IPersistentVector)recurMismatches.assoc(i, RT.T);
                  moreMismatches = true;
                  }
                }
              }
            }
          if(!moreMismatches)
            return new LetExpr(bindingInits, bodyExpr, isLoop);
          }
        finally
          {
          Var.popThreadBindings();
          }
      }
    }
  }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval let/loop");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    doEmit(context, objx, gen, false);
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    doEmit(context, objx, gen, true);
  }


  public void doEmit(C context, ObjExpr objx, GeneratorAdapter gen, boolean emitUnboxed){
    HashMap<BindingInit, Label> bindingLabels = new HashMap();
    for(int i = 0; i < bindingInits.count(); i++)
      {
      BindingInit bi = (BindingInit) bindingInits.nth(i);
      Class primc = maybePrimitiveType(bi.init);
      if(primc != null)
        {
        ((MaybePrimitiveExpr) bi.init).emitUnboxed(C.EXPRESSION, objx, gen);
        gen.visitVarInsn(Type.getType(primc).getOpcode(Opcodes.ISTORE), bi.binding.idx);
        }
      else
        {
        bi.init.emit(C.EXPRESSION, objx, gen);
        gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), bi.binding.idx);
        }
      bindingLabels.put(bi, gen.mark());
      }
    Label loopLabel = gen.mark();
    if(isLoop)
      {
      try
        {
        Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel));
        if(emitUnboxed)
          ((MaybePrimitiveExpr)body).emitUnboxed(context, objx, gen);
        else
          body.emit(context, objx, gen);
        }
      finally
        {
        Var.popThreadBindings();
        }
      }
    else
      {
      if(emitUnboxed)
        ((MaybePrimitiveExpr)body).emitUnboxed(context, objx, gen);
      else
        body.emit(context, objx, gen);
      }
    Label end = gen.mark();
//    gen.visitLocalVariable("this", "Ljava/lang/Object;", null, loopLabel, end, 0);
    for(ISeq bis = bindingInits.seq(); bis != null; bis = bis.next())
      {
      BindingInit bi = (BindingInit) bis.first();
      String lname = bi.binding.name;
      if(lname.endsWith("__auto__"))
        lname += RT.nextID();
      Class primc = maybePrimitiveType(bi.init);
      if(primc != null)
        gen.visitLocalVariable(lname, Type.getDescriptor(primc), null, bindingLabels.get(bi), end,
                               bi.binding.idx);
      else
        gen.visitLocalVariable(lname, "Ljava/lang/Object;", null, bindingLabels.get(bi), end, bi.binding.idx);
      }
  }

  public boolean hasJavaClass() {
    return body.hasJavaClass();
  }

  public Class getJavaClass() {
    return body.getJavaClass();
  }

  public boolean canEmitPrimitive(){
    return body instanceof MaybePrimitiveExpr && ((MaybePrimitiveExpr)body).canEmitPrimitive();
  }

}

public static class RecurExpr implements Expr, MaybePrimitiveExpr{
  public final IPersistentVector args;
  public final IPersistentVector loopLocals;
  final int line;
  final int column;
  final String source;


  public RecurExpr(IPersistentVector loopLocals, IPersistentVector args, int line, int column, String source){
    this.loopLocals = loopLocals;
    this.args = args;
    this.line = line;
    this.column = column;
    this.source = source;
  }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval recur");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    Label loopLabel = (Label) LOOP_LABEL.deref();
    if(loopLabel == null)
      throw new IllegalStateException();
    for(int i = 0; i < loopLocals.count(); i++)
      {
      LocalBinding lb = (LocalBinding) loopLocals.nth(i);
      Expr arg = (Expr) args.nth(i);
      if(lb.getPrimitiveType() != null)
        {
        Class primc = lb.getPrimitiveType();
        final Class pc = maybePrimitiveType(arg);
        if(pc == primc)
          ((MaybePrimitiveExpr) arg).emitUnboxed(C.EXPRESSION, objx, gen);
        else if(primc == long.class && pc == int.class)
          {
          ((MaybePrimitiveExpr) arg).emitUnboxed(C.EXPRESSION, objx, gen);
          gen.visitInsn(I2L);
          }
        else if(primc == double.class && pc == float.class)
          {
          ((MaybePrimitiveExpr) arg).emitUnboxed(C.EXPRESSION, objx, gen);
          gen.visitInsn(F2D);
          }
        else if(primc == int.class && pc == long.class)
          {
          ((MaybePrimitiveExpr) arg).emitUnboxed(C.EXPRESSION, objx, gen);
          gen.invokeStatic(RT_TYPE, Method.getMethod("int intCast(long)"));
          }
        else if(primc == float.class && pc == double.class)
          {
          ((MaybePrimitiveExpr) arg).emitUnboxed(C.EXPRESSION, objx, gen);
          gen.visitInsn(D2F);
          }
        else
          {
//          if(true)//RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
            throw new IllegalArgumentException
//            RT.errPrintWriter().println
              (//source + ":" + line +
               " recur arg for primitive local: " +
                                             lb.name + " is not matching primitive, had: " +
                              (arg.hasJavaClass() ? arg.getJavaClass().getName():"Object") +
                              ", needed: " +
                              primc.getName());
//          arg.emit(C.EXPRESSION, objx, gen);
//          HostExpr.emitUnboxArg(objx,gen,primc);
          }
        }
      else
        {
        arg.emit(C.EXPRESSION, objx, gen);
        }
      }

    for(int i = loopLocals.count() - 1; i >= 0; i--)
      {
      LocalBinding lb = (LocalBinding) loopLocals.nth(i);
      Class primc = lb.getPrimitiveType();
      if(lb.isArg)
        gen.storeArg(lb.idx-(objx.isStatic?0:1));
      else
        {
        if(primc != null)
          gen.visitVarInsn(Type.getType(primc).getOpcode(Opcodes.ISTORE), lb.idx);
        else
          gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), lb.idx);
        }
      }

    gen.goTo(loopLabel);
  }

  public boolean hasJavaClass() {
    return true;
  }

  public Class getJavaClass() {
    return RECUR_CLASS;
  }

  static class Parser implements IParser{
    public Expr parse(C context, Object frm) {
      int line = lineDeref();
      int column = columnDeref();
      String source = (String) SOURCE.deref();

      ISeq form = (ISeq) frm;
      IPersistentVector loopLocals = (IPersistentVector) LOOP_LOCALS.deref();
      if(context != C.RETURN || loopLocals == null)
        throw new UnsupportedOperationException("Can only recur from tail position");
                        if(NO_RECUR.deref() != null)
                            throw new UnsupportedOperationException("Cannot recur across try");
      PersistentVector args = PersistentVector.EMPTY;
      for(ISeq s = RT.seq(form.next()); s != null; s = s.next())
        {
        args = args.cons(analyze(C.EXPRESSION, s.first()));
        }
      if(args.count() != loopLocals.count())
        throw new IllegalArgumentException(
            String.format("Mismatched argument count to recur, expected: %d args, got: %d",
                          loopLocals.count(), args.count()));
      for(int i = 0;i< loopLocals.count();i++)
        {
        LocalBinding lb = (LocalBinding) loopLocals.nth(i);
        Class primc = lb.getPrimitiveType();
        if(primc != null)
          {
          boolean mismatch = false;
          final Class pc = maybePrimitiveType((Expr) args.nth(i));
          if(primc == long.class)
            {
            if(!(pc == long.class
              || pc == int.class
              || pc == short.class
              || pc == char.class
              || pc == byte.class))
              mismatch = true;
            }
          else if(primc == double.class)
            {
            if(!(pc == double.class
              || pc == float.class))
              mismatch = true;
            }
          if(mismatch)
            {
            lb.recurMistmatch = true;
            if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
              RT.errPrintWriter().println
                (source + ":" + line +
                 " recur arg for primitive local: " +
                                               lb.name + " is not matching primitive, had: " +
                              (pc != null ? pc.getName():"Object") +
                              ", needed: " +
                              primc.getName());
            }
          }
        }
      return new RecurExpr(loopLocals, args, line, column, source);
    }
  }

  public boolean canEmitPrimitive() {
    return true;
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen) {
    emit(context, objx, gen);
  }
}

private static LocalBinding registerLocal(Symbol sym, Symbol tag, Expr init, boolean isArg) {
  int num = getAndIncLocalNum();
  LocalBinding b = new LocalBinding(num, sym, tag, init, isArg, clearPathRoot());
  IPersistentMap localsMap = (IPersistentMap) LOCAL_ENV.deref();
  LOCAL_ENV.set(RT.assoc(localsMap, b.sym, b));
  ObjMethod method = (ObjMethod) METHOD.deref();
  method.locals = (IPersistentMap) RT.assoc(method.locals, b, b);
  method.indexlocals = (IPersistentMap) RT.assoc(method.indexlocals, num, b);
  return b;
}

private static int getAndIncLocalNum(){
  int num = ((Number) NEXT_LOCAL_NUM.deref()).intValue();
  ObjMethod m = (ObjMethod) METHOD.deref();
  if(num > m.maxLocal)
    m.maxLocal = num;
  NEXT_LOCAL_NUM.set(num + 1);
  return num;
}

public static Expr analyze(C context, Object form) {
  return analyze(context, form, null);
}

private static Expr analyze(C context, Object form, String name) {
  //todo symbol macro expansion?
  try
    {
    if(form instanceof LazySeq)
      {
      form = RT.seq(form);
      if(form == null)
        form = PersistentList.EMPTY;
      }
    if(form == null)
      return NIL_EXPR;
    else if(form == Boolean.TRUE)
      return TRUE_EXPR;
    else if(form == Boolean.FALSE)
        return FALSE_EXPR;
    Class fclass = form.getClass();
    if(fclass == Symbol.class)
      return analyzeSymbol((Symbol) form);
    else if(fclass == Keyword.class)
      return registerKeyword((Keyword) form);
    else if(form instanceof Number)
      return NumberExpr.parse((Number) form);
    else if(fclass == String.class)
        return new StringExpr(((String) form).intern());
//  else if(fclass == Character.class)
//    return new CharExpr((Character) form);
    else if(form instanceof IPersistentCollection && ((IPersistentCollection) form).count() == 0)
        {
        Expr ret = new EmptyExpr(form);
        if(RT.meta(form) != null)
          ret = new MetaExpr(ret, MapExpr
              .parse(context == C.EVAL ? context : C.EXPRESSION, ((IObj) form).meta()));
        return ret;
        }
    else if(form instanceof ISeq)
        return analyzeSeq(context, (ISeq) form, name);
    else if(form instanceof IPersistentVector)
        return VectorExpr.parse(context, (IPersistentVector) form);
    else if(form instanceof IRecord)
        return new ConstantExpr(form);
    else if(form instanceof IType)
        return new ConstantExpr(form);
    else if(form instanceof IPersistentMap)
        return MapExpr.parse(context, (IPersistentMap) form);
    else if(form instanceof IPersistentSet)
        return SetExpr.parse(context, (IPersistentSet) form);

//  else
    //throw new UnsupportedOperationException();
    return new ConstantExpr(form);
    }
  catch(Throwable e)
    {
    if(!(e instanceof CompilerException))
      throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), e);
    else
      throw (CompilerException) e;
    }
}

static public class CompilerException extends RuntimeException{
  final public String source;
 
  final public int line;

  public CompilerException(String source, int line, int column, Throwable cause){
    super(errorMsg(source, line, column, cause.toString()), cause);
    this.source = source;
    this.line = line;
  }

  public String toString(){
    return getMessage();
  }
}

static public Var isMacro(Object op) {
  //no local macros for now
  if(op instanceof Symbol && referenceLocal((Symbol) op) != null)
    return null;
  if(op instanceof Symbol || op instanceof Var)
    {
                Var v = (op instanceof Var) ? (Var) op : lookupVar((Symbol) op, false, false);
    if(v != null && v.isMacro())
      {
      if(v.ns != currentNS() && !v.isPublic())
        throw new IllegalStateException("var: " + v + " is not public");
      return v;
      }
    }
  return null;
}

static public IFn isInline(Object op, int arity) {
  //no local inlines for now
  if(op instanceof Symbol && referenceLocal((Symbol) op) != null)
    return null;
  if(op instanceof Symbol || op instanceof Var)
    {
    Var v = (op instanceof Var) ? (Var) op : lookupVar((Symbol) op, false);
    if(v != null)
      {
      if(v.ns != currentNS() && !v.isPublic())
        throw new IllegalStateException("var: " + v + " is not public");
      IFn ret = (IFn) RT.get(v.meta(), inlineKey);
      if(ret != null)
        {
        IFn arityPred = (IFn) RT.get(v.meta(), inlineAritiesKey);
        if(arityPred == null || RT.booleanCast(arityPred.invoke(arity)))
          return ret;
        }
      }
    }
  return null;
}

public static boolean namesStaticMember(Symbol sym){
  return sym.ns != null && namespaceFor(sym) == null;
}

public static Object preserveTag(ISeq src, Object dst) {
  Symbol tag = tagOf(src);
  if (tag != null && dst instanceof IObj) {
    IPersistentMap meta = RT.meta(dst);
    return ((IObj) dst).withMeta((IPersistentMap) RT.assoc(meta, RT.TAG_KEY, tag));
  }
  return dst;
}

public static Object macroexpand1(Object x) {
  if(x instanceof ISeq)
    {
    ISeq form = (ISeq) x;
    Object op = RT.first(form);
    if(isSpecial(op))
      return x;
    //macro expansion
    Var v = isMacro(op);
    if(v != null)
      {
        try
          {
            return v.applyTo(RT.cons(form,RT.cons(LOCAL_ENV.get(),form.next())));
          }
        catch(ArityException e)
          {
            // hide the 2 extra params for a macro
            throw new ArityException(e.actual - 2, e.name);
          }
        catch(Throwable e)
          {
            if(!(e instanceof CompilerException)) {
                Integer line = (Integer) LINE.deref();
                Integer column = (Integer) COLUMN.deref();
                String source = (String) SOURCE.deref();
                throw new CompilerException(source, line, column, e);
            } else
              throw (CompilerException) e;
          }
      }
    else
      {
      if(op instanceof Symbol)
        {
        Symbol sym = (Symbol) op;
        String sname = sym.name;
        //(.substring s 2 5) => (. s substring 2 5)
        if(sym.name.charAt(0) == '.')
          {
          if(RT.length(form) < 2)
            throw new IllegalArgumentException(
                "Malformed member expression, expecting (.member target ...)");
          Symbol meth = Symbol.intern(sname.substring(1));
          Object target = RT.second(form);
          if(HostExpr.maybeClass(target, false) != null)
            {
            target = ((IObj)RT.list(IDENTITY, target)).withMeta(RT.map(RT.TAG_KEY,CLASS));
            }
          return preserveTag(form, RT.listStar(DOT, target, meth, form.next().next()));
          }
        else if(namesStaticMember(sym))
          {
          Symbol target = Symbol.intern(sym.ns);
          Class c = HostExpr.maybeClass(target, false);
          if(c != null)
            {
            Symbol meth = Symbol.intern(sym.name);
            return preserveTag(form, RT.listStar(DOT, target, meth, form.next()));
            }
          }
        else
          {
          //(s.substring 2 5) => (. s substring 2 5)
          //also (package.class.name ...) (. package.class name ...)
          int idx = sname.lastIndexOf('.');
//          if(idx > 0 && idx < sname.length() - 1)
//            {
//            Symbol target = Symbol.intern(sname.substring(0, idx));
//            Symbol meth = Symbol.intern(sname.substring(idx + 1));
//            return RT.listStar(DOT, target, meth, form.rest());
//            }
          //(StringBuilder. "foo") => (new StringBuilder "foo") 
          //else
          if(idx == sname.length() - 1)
            return RT.listStar(NEW, Symbol.intern(sname.substring(0, idx)), form.next());
          }
        }
      }
    }
  return x;
}

static Object macroexpand(Object form) {
  Object exf = macroexpand1(form);
  if(exf != form)
    return macroexpand(exf);
  return form;
}

private static Expr analyzeSeq(C context, ISeq form, String name) {
  Object line = lineDeref();
  Object column = columnDeref();
  if(RT.meta(form) != null && RT.meta(form).containsKey(RT.LINE_KEY))
    line = RT.meta(form).valAt(RT.LINE_KEY);
  if(RT.meta(form) != null && RT.meta(form).containsKey(RT.COLUMN_KEY))
    column = RT.meta(form).valAt(RT.COLUMN_KEY);
  Var.pushThreadBindings(
      RT.map(LINE, line, COLUMN, column));
  try
    {
    Object me = macroexpand1(form);
    if(me != form)
      return analyze(context, me, name);

    Object op = RT.first(form);
    if(op == null)
      throw new IllegalArgumentException("Can't call nil");
    IFn inline = isInline(op, RT.count(RT.next(form)));
    if(inline != null)
      return analyze(context, preserveTag(form, inline.applyTo(RT.next(form))));
    IParser p;
    if(op.equals(FN))
      return FnExpr.parse(context, form, name);
    else if((p = (IParser) specials.valAt(op)) != null)
      return p.parse(context, form);
    else
      return InvokeExpr.parse(context, form);
    }
  catch(Throwable e)
    {
    if(!(e instanceof CompilerException))
      throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), e);
    else
      throw (CompilerException) e;
    }
  finally
    {
    Var.popThreadBindings();
    }
}

static String errorMsg(String source, int line, int column, String s){
  return String.format("%s, compiling:(%s:%d:%d)", s, source, line, column);
}

public static Object eval(Object form) {
  return eval(form, true);
}

public static Object eval(Object form, boolean freshLoader) {
  boolean createdLoader = false;
  if(true)//!LOADER.isBound())
    {
    Var.pushThreadBindings(RT.map(LOADER, RT.makeClassLoader()));
    createdLoader = true;
    }
  try
    {
    Object line = lineDeref();
    Object column = columnDeref();
    if(RT.meta(form) != null && RT.meta(form).containsKey(RT.LINE_KEY))
      line = RT.meta(form).valAt(RT.LINE_KEY);
    if(RT.meta(form) != null && RT.meta(form).containsKey(RT.COLUMN_KEY))
      column = RT.meta(form).valAt(RT.COLUMN_KEY);
    Var.pushThreadBindings(RT.map(LINE, line, COLUMN, column));
    try
      {
      form = macroexpand(form);
      if(form instanceof ISeq && Util.equals(RT.first(form), DO))
        {
        ISeq s = RT.next(form);
        for(; RT.next(s) != null; s = RT.next(s))
          eval(RT.first(s), false);
        return eval(RT.first(s), false);
        }
      else if((form instanceof IType) ||
          (form instanceof IPersistentCollection
          && !(RT.first(form) instanceof Symbol
            && ((Symbol) RT.first(form)).name.startsWith("def"))))
        {
        ObjExpr fexpr = (ObjExpr) analyze(C.EXPRESSION, RT.list(FN, PersistentVector.EMPTY, form),
                          "eval" + RT.nextID());
        IFn fn = (IFn) fexpr.eval();
        return fn.invoke();
        }
      else
        {
        Expr expr = analyze(C.EVAL, form);
        return expr.eval();
        }
      }
    finally
      {
      Var.popThreadBindings();
      }
    }

  finally
    {
    if(createdLoader)
      Var.popThreadBindings();
    }
}

private static int registerConstant(Object o){
  if(!CONSTANTS.isBound())
    return -1;
  PersistentVector v = (PersistentVector) CONSTANTS.deref();
  IdentityHashMap<Object,Integer> ids = (IdentityHashMap<Object,Integer>) CONSTANT_IDS.deref();
  Integer i = ids.get(o);
  if(i != null)
    return i;
  CONSTANTS.set(RT.conj(v, o));
  ids.put(o, v.count());
  return v.count();
}

private static KeywordExpr registerKeyword(Keyword keyword){
  if(!KEYWORDS.isBound())
    return new KeywordExpr(keyword);

  IPersistentMap keywordsMap = (IPersistentMap) KEYWORDS.deref();
  Object id = RT.get(keywordsMap, keyword);
  if(id == null)
    {
    KEYWORDS.set(RT.assoc(keywordsMap, keyword, registerConstant(keyword)));
    }
  return new KeywordExpr(keyword);
//  KeywordExpr ke = (KeywordExpr) RT.get(keywordsMap, keyword);
//  if(ke == null)
//    KEYWORDS.set(RT.assoc(keywordsMap, keyword, ke = new KeywordExpr(keyword)));
//  return ke;
}

private static int registerKeywordCallsite(Keyword keyword){
  if(!KEYWORD_CALLSITES.isBound())
    throw new IllegalAccessError("KEYWORD_CALLSITES is not bound");

  IPersistentVector keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref();

  keywordCallsites = keywordCallsites.cons(keyword);
  KEYWORD_CALLSITES.set(keywordCallsites);
  return keywordCallsites.count()-1;
}

private static int registerProtocolCallsite(Var v){
  if(!PROTOCOL_CALLSITES.isBound())
    throw new IllegalAccessError("PROTOCOL_CALLSITES is not bound");

  IPersistentVector protocolCallsites = (IPersistentVector) PROTOCOL_CALLSITES.deref();

  protocolCallsites = protocolCallsites.cons(v);
  PROTOCOL_CALLSITES.set(protocolCallsites);
  return protocolCallsites.count()-1;
}

private static void registerVarCallsite(Var v){
  if(!VAR_CALLSITES.isBound())
    throw new IllegalAccessError("VAR_CALLSITES is not bound");

  IPersistentCollection varCallsites = (IPersistentCollection) VAR_CALLSITES.deref();

  varCallsites = varCallsites.cons(v);
  VAR_CALLSITES.set(varCallsites);
//  return varCallsites.count()-1;
}

static ISeq fwdPath(PathNode p1){
    ISeq ret = null;
    for(;p1 != null;p1 = p1.parent)
        ret = RT.cons(p1,ret);
    return ret;
}

static PathNode commonPath(PathNode n1, PathNode n2){
    ISeq xp = fwdPath(n1);
    ISeq yp = fwdPath(n2);
    if(RT.first(xp) != RT.first(yp))
        return null;
    while(RT.second(xp) != null && RT.second(xp) == RT.second(yp))
        {
        xp = xp.next();
        yp = yp.next();
        }
    return (PathNode) RT.first(xp);
}

static void addAnnotation(Object visitor, IPersistentMap meta){
  if(meta != null && ADD_ANNOTATIONS.isBound())
     ADD_ANNOTATIONS.invoke(visitor, meta);
}

static void addParameterAnnotation(Object visitor, IPersistentMap meta, int i){
  if(meta != null && ADD_ANNOTATIONS.isBound())
     ADD_ANNOTATIONS.invoke(visitor, meta, i);
}

private static Expr analyzeSymbol(Symbol sym) {
  Symbol tag = tagOf(sym);
  if(sym.ns == null) //ns-qualified syms are always Vars
    {
    LocalBinding b = referenceLocal(sym);
    if(b != null)
            {
            return new LocalBindingExpr(b, tag);
            }
    }
  else
    {
    if(namespaceFor(sym) == null)
      {
      Symbol nsSym = Symbol.intern(sym.ns);
      Class c = HostExpr.maybeClass(nsSym, false);
      if(c != null)
        {
        if(Reflector.getField(c, sym.name, true) != null)
          return new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag);
        throw Util.runtimeException("Unable to find static field: " + sym.name + " in " + c);
        }
      }
    }
  //Var v = lookupVar(sym, false);
//  Var v = lookupVar(sym, false);
//  if(v != null)
//    return new VarExpr(v, tag);
  Object o = resolve(sym);
  if(o instanceof Var)
    {
    Var v = (Var) o;
    if(isMacro(v) != null)
      throw Util.runtimeException("Can't take value of a macro: " + v);
    if(RT.booleanCast(RT.get(v.meta(),RT.CONST_KEY)))
      return analyze(C.EXPRESSION, RT.list(QUOTE, v.get()));
    registerVar(v);
    return new VarExpr(v, tag);
    }
  else if(o instanceof Class)
    return new ConstantExpr(o);
  else if(o instanceof Symbol)
      return new UnresolvedVarExpr((Symbol) o);

  throw Util.runtimeException("Unable to resolve symbol: " + sym + " in this context");

}

static String destubClassName(String className){
  //skip over prefix + '.' or '/'
  if(className.startsWith(COMPILE_STUB_PREFIX))
    return className.substring(COMPILE_STUB_PREFIX.length()+1);
  return className;
}

static Type getType(Class c){
  String descriptor = Type.getType(c).getDescriptor();
  if(descriptor.startsWith("L"))
    descriptor = "L" + destubClassName(descriptor.substring(1));
  return Type.getType(descriptor);
}

static Object resolve(Symbol sym, boolean allowPrivate) {
  return resolveIn(currentNS(), sym, allowPrivate);
}

static Object resolve(Symbol sym) {
  return resolveIn(currentNS(), sym, false);
}

static Namespace namespaceFor(Symbol sym){
  return namespaceFor(currentNS(), sym);
}

static Namespace namespaceFor(Namespace inns, Symbol sym){
  //note, presumes non-nil sym.ns
  // first check against currentNS' aliases...
  Symbol nsSym = Symbol.intern(sym.ns);
  Namespace ns = inns.lookupAlias(nsSym);
  if(ns == null)
    {
    // ...otherwise check the Namespaces map.
    ns = Namespace.find(nsSym);
    }
  return ns;
}

static public Object resolveIn(Namespace n, Symbol sym, boolean allowPrivate) {
  //note - ns-qualified vars must already exist
  if(sym.ns != null)
    {
    Namespace ns = namespaceFor(n, sym);
    if(ns == null)
      throw Util.runtimeException("No such namespace: " + sym.ns);

    Var v = ns.findInternedVar(Symbol.intern(sym.name));
    if(v == null)
      throw Util.runtimeException("No such var: " + sym);
    else if(v.ns != currentNS() && !v.isPublic() && !allowPrivate)
      throw new IllegalStateException("var: " + sym + " is not public");
    return v;
    }
  else if(sym.name.indexOf('.') > 0 || sym.name.charAt(0) == '[')
    {
    return RT.classForName(sym.name);
    }
  else if(sym.equals(NS))
      return RT.NS_VAR;
  else if(sym.equals(IN_NS))
      return RT.IN_NS_VAR;
  else
    {
    if(Util.equals(sym, COMPILE_STUB_SYM.get()))
      return COMPILE_STUB_CLASS.get();
    Object o = n.getMapping(sym);
    if(o == null)
      {
      if(RT.booleanCast(RT.ALLOW_UNRESOLVED_VARS.deref()))
        {
        return sym;
        }
      else
        {
        throw Util.runtimeException("Unable to resolve symbol: " + sym + " in this context");
        }
      }
    return o;
    }
}


static public Object maybeResolveIn(Namespace n, Symbol sym) {
  //note - ns-qualified vars must already exist
  if(sym.ns != null)
    {
    Namespace ns = namespaceFor(n, sym);
    if(ns == null)
      return null;
    Var v = ns.findInternedVar(Symbol.intern(sym.name));
    if(v == null)
      return null;
    return v;
    }
  else if(sym.name.indexOf('.') > 0 && !sym.name.endsWith(".")
      || sym.name.charAt(0) == '[')
    {
    return RT.classForName(sym.name);
    }
  else if(sym.equals(NS))
      return RT.NS_VAR;
    else if(sym.equals(IN_NS))
        return RT.IN_NS_VAR;
      else
        {
        Object o = n.getMapping(sym);
        return o;
        }
}


static Var lookupVar(Symbol sym, boolean internNew, boolean registerMacro) {
  Var var = null;

  //note - ns-qualified vars in other namespaces must already exist
  if(sym.ns != null)
    {
    Namespace ns = namespaceFor(sym);
    if(ns == null)
      return null;
    //throw Util.runtimeException("No such namespace: " + sym.ns);
    Symbol name = Symbol.intern(sym.name);
    if(internNew && ns == currentNS())
      var = currentNS().intern(name);
    else
      var = ns.findInternedVar(name);
    }
  else if(sym.equals(NS))
    var = RT.NS_VAR;
  else if(sym.equals(IN_NS))
      var = RT.IN_NS_VAR;
    else
      {
      //is it mapped?
      Object o = currentNS().getMapping(sym);
      if(o == null)
        {
        //introduce a new var in the current ns
        if(internNew)
          var = currentNS().intern(Symbol.intern(sym.name));
          }
      else if(o instanceof Var)
        {
        var = (Var) o;
        }
      else
        {
        throw Util.runtimeException("Expecting var, but " + sym + " is mapped to " + o);
        }
      }
  if(var != null && (!var.isMacro() || registerMacro))
    registerVar(var);
  return var;
}
static Var lookupVar(Symbol sym, boolean internNew) {
    return lookupVar(sym, internNew, true);
}

private static void registerVar(Var var) {
  if(!VARS.isBound())
    return;
  IPersistentMap varsMap = (IPersistentMap) VARS.deref();
  Object id = RT.get(varsMap, var);
  if(id == null)
    {
    VARS.set(RT.assoc(varsMap, var, registerConstant(var)));
    }
//  if(varsMap != null && RT.get(varsMap, var) == null)
//    VARS.set(RT.assoc(varsMap, var, var));
}

static Namespace currentNS(){
  return (Namespace) RT.CURRENT_NS.deref();
}

static void closeOver(LocalBinding b, ObjMethod method){
  if(b != null && method != null)
    {
    if(RT.get(method.locals, b) == null)
      {
      method.objx.closes = (IPersistentMap) RT.assoc(method.objx.closes, b, b);
      closeOver(b, method.parent);
      }
    else if(IN_CATCH_FINALLY.deref() != null)
      {
      method.localsUsedInCatchFinally = (PersistentHashSet) method.localsUsedInCatchFinally.cons(b.idx);
      }
    }
}


static LocalBinding referenceLocal(Symbol sym) {
  if(!LOCAL_ENV.isBound())
    return null;
  LocalBinding b = (LocalBinding) RT.get(LOCAL_ENV.deref(), sym);
  if(b != null)
    {
    ObjMethod method = (ObjMethod) METHOD.deref();
    closeOver(b, method);
    }
  return b;
}

private static Symbol tagOf(Object o){
  Object tag = RT.get(RT.meta(o), RT.TAG_KEY);
  if(tag instanceof Symbol)
    return (Symbol) tag;
  else if(tag instanceof String)
    return Symbol.intern(null, (String) tag);
  return null;
}

public static Object loadFile(String file) throws IOException{
//  File fo = new File(file);
//  if(!fo.exists())
//    return null;

  FileInputStream f = new FileInputStream(file);
  try
    {
    return load(new InputStreamReader(f, RT.UTF8), new File(file).getAbsolutePath(), (new File(file)).getName());
    }
  finally
    {
    f.close();
    }
}

public static Object load(Reader rdr) {
  return load(rdr, null, "NO_SOURCE_FILE");
}

public static Object load(Reader rdr, String sourcePath, String sourceName) {
  Object EOF = new Object();
  Object ret = null;
  LineNumberingPushbackReader pushbackReader =
      (rdr instanceof LineNumberingPushbackReader) ? (LineNumberingPushbackReader) rdr :
      new LineNumberingPushbackReader(rdr);
  Var.pushThreadBindings(
      RT.mapUniqueKeys(LOADER, RT.makeClassLoader(),
             SOURCE_PATH, sourcePath,
             SOURCE, sourceName,
             METHOD, null,
             LOCAL_ENV, null,
          LOOP_LOCALS, null,
          NEXT_LOCAL_NUM, 0,
          RT.READEVAL, RT.T,
             RT.CURRENT_NS, RT.CURRENT_NS.deref(),
             LINE_BEFORE, pushbackReader.getLineNumber(),
             COLUMN_BEFORE, pushbackReader.getColumnNumber(),
             LINE_AFTER, pushbackReader.getLineNumber(),
             COLUMN_AFTER, pushbackReader.getColumnNumber()
             ,RT.UNCHECKED_MATH, RT.UNCHECKED_MATH.deref()
          ,RT.WARN_ON_REFLECTION, RT.WARN_ON_REFLECTION.deref()
             ,RT.DATA_READERS, RT.DATA_READERS.deref()
                        ));

  try
    {
    for(Object r = LispReader.read(pushbackReader, false, EOF, false); r != EOF;
        r = LispReader.read(pushbackReader, false, EOF, false))
      {
      LINE_AFTER.set(pushbackReader.getLineNumber());
      COLUMN_AFTER.set(pushbackReader.getColumnNumber());
      ret = eval(r,false);
      LINE_BEFORE.set(pushbackReader.getLineNumber());
      COLUMN_BEFORE.set(pushbackReader.getColumnNumber());
      }
    }
  catch(LispReader.ReaderException e)
    {
    throw new CompilerException(sourcePath, e.line, e.column, e.getCause());
    }
  catch(Throwable e)
    {
    if(!(e instanceof CompilerException))
      throw new CompilerException(sourcePath, (Integer) LINE_BEFORE.deref(), (Integer) COLUMN_BEFORE.deref(), e);
    else
      throw (CompilerException) e;
    }
  finally
    {
    Var.popThreadBindings();
    }
  return ret;
}

static public void writeClassFile(String internalName, byte[] bytecode) throws IOException{
  String genPath = (String) COMPILE_PATH.deref();
  if(genPath == null)
    throw Util.runtimeException("*compile-path* not set");
  String[] dirs = internalName.split("/");
  String p = genPath;
  for(int i = 0; i < dirs.length - 1; i++)
    {
    p += File.separator + dirs[i];
    (new File(p)).mkdir();
    }
  String path = genPath + File.separator + internalName + ".class";
  File cf = new File(path);
  cf.createNewFile();
  FileOutputStream cfs = new FileOutputStream(cf);
  try
    {
    cfs.write(bytecode);
    cfs.flush();
    cfs.getFD().sync();
    }
  finally
    {
    cfs.close();
    }
}

public static void pushNS(){
  Var.pushThreadBindings(PersistentHashMap.create(Var.intern(Symbol.intern("clojure.core"),
                                                             Symbol.intern("*ns*")).setDynamic(), null));
}

public static void pushNSandLoader(ClassLoader loader){
  Var.pushThreadBindings(RT.map(Var.intern(Symbol.intern("clojure.core"),
                                           Symbol.intern("*ns*")).setDynamic(),
                                null,
                                RT.FN_LOADER_VAR, loader,
                                RT.READEVAL, RT.T
                                ));
}

public static ILookupThunk getLookupThunk(Object target, Keyword k){
  return null//To change body of created methods use File | Settings | File Templates.
}

static void compile1(GeneratorAdapter gen, ObjExpr objx, Object form) {
  Object line = lineDeref();
  Object column = columnDeref();
  if(RT.meta(form) != null && RT.meta(form).containsKey(RT.LINE_KEY))
    line = RT.meta(form).valAt(RT.LINE_KEY);
  if(RT.meta(form) != null && RT.meta(form).containsKey(RT.COLUMN_KEY))
    column = RT.meta(form).valAt(RT.COLUMN_KEY);
  Var.pushThreadBindings(
      RT.map(LINE, line, COLUMN, column
             ,LOADER, RT.makeClassLoader()
      ));
  try
    {
    form = macroexpand(form);
    if(form instanceof ISeq && Util.equals(RT.first(form), DO))
      {
      for(ISeq s = RT.next(form); s != null; s = RT.next(s))
        {
        compile1(gen, objx, RT.first(s));
        }
      }
    else
      {
      Expr expr = analyze(C.EVAL, form);
      objx.keywords = (IPersistentMap) KEYWORDS.deref();
      objx.vars = (IPersistentMap) VARS.deref();
      objx.constants = (PersistentVector) CONSTANTS.deref();
      expr.emit(C.EXPRESSION, objx, gen);
      expr.eval();
      }
    }
  finally
    {
    Var.popThreadBindings();
    }
}

public static Object compile(Reader rdr, String sourcePath, String sourceName) throws IOException{
  if(COMPILE_PATH.deref() == null)
    throw Util.runtimeException("*compile-path* not set");

  Object EOF = new Object();
  Object ret = null;
  LineNumberingPushbackReader pushbackReader =
      (rdr instanceof LineNumberingPushbackReader) ? (LineNumberingPushbackReader) rdr :
      new LineNumberingPushbackReader(rdr);
  Var.pushThreadBindings(
      RT.mapUniqueKeys(SOURCE_PATH, sourcePath,
             SOURCE, sourceName,
             METHOD, null,
             LOCAL_ENV, null,
          LOOP_LOCALS, null,
          NEXT_LOCAL_NUM, 0,
          RT.READEVAL, RT.T,
          RT.CURRENT_NS, RT.CURRENT_NS.deref(),
             LINE_BEFORE, pushbackReader.getLineNumber(),
             COLUMN_BEFORE, pushbackReader.getColumnNumber(),
             LINE_AFTER, pushbackReader.getLineNumber(),
             COLUMN_AFTER, pushbackReader.getColumnNumber(),
             CONSTANTS, PersistentVector.EMPTY,
             CONSTANT_IDS, new IdentityHashMap(),
             KEYWORDS, PersistentHashMap.EMPTY,
             VARS, PersistentHashMap.EMPTY
          ,RT.UNCHECKED_MATH, RT.UNCHECKED_MATH.deref()
          ,RT.WARN_ON_REFLECTION, RT.WARN_ON_REFLECTION.deref()
          ,RT.DATA_READERS, RT.DATA_READERS.deref()
         //    ,LOADER, RT.makeClassLoader()
      ));

  try
    {
    //generate loader class
    ObjExpr objx = new ObjExpr(null);
    objx.internalName = sourcePath.replace(File.separator, "/").substring(0, sourcePath.lastIndexOf('.'))
                      + RT.LOADER_SUFFIX;

    objx.objtype = Type.getObjectType(objx.internalName);
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    ClassVisitor cv = cw;
    cv.visit(V1_5, ACC_PUBLIC + ACC_SUPER, objx.internalName, null, "java/lang/Object", null);

    //static load method
    GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
                                                Method.getMethod("void load ()"),
                                                null,
                                                null,
                                                cv);
    gen.visitCode();

    for(Object r = LispReader.read(pushbackReader, false, EOF, false); r != EOF;
        r = LispReader.read(pushbackReader, false, EOF, false))
      {
        LINE_AFTER.set(pushbackReader.getLineNumber());
        COLUMN_AFTER.set(pushbackReader.getColumnNumber());
        compile1(gen, objx, r);
        LINE_BEFORE.set(pushbackReader.getLineNumber());
        COLUMN_BEFORE.set(pushbackReader.getColumnNumber());
      }
    //end of load
    gen.returnValue();
    gen.endMethod();

    //static fields for constants
    for(int i = 0; i < objx.constants.count(); i++)
      {
      cv.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, objx.constantName(i), objx.constantType(i).getDescriptor(),
                    null, null);
      }

    final int INITS_PER = 100;
    int numInits =  objx.constants.count() / INITS_PER;
    if(objx.constants.count() % INITS_PER != 0)
      ++numInits;

    for(int n = 0;n<numInits;n++)
      {
      GeneratorAdapter clinitgen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
                                                        Method.getMethod("void __init" + n + "()"),
                                                        null,
                                                        null,
                                                        cv);
      clinitgen.visitCode();
      try
        {
        Var.pushThreadBindings(RT.map(RT.PRINT_DUP, RT.T));

        for(int i = n*INITS_PER; i < objx.constants.count() && i < (n+1)*INITS_PER; i++)
          {
          objx.emitValue(objx.constants.nth(i), clinitgen);
          clinitgen.checkCast(objx.constantType(i));
          clinitgen.putStatic(objx.objtype, objx.constantName(i), objx.constantType(i));
          }
        }
      finally
        {
        Var.popThreadBindings();
        }
      clinitgen.returnValue();
      clinitgen.endMethod();
      }

    //static init for constants, keywords and vars
    GeneratorAdapter clinitgen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
                                                      Method.getMethod("void <clinit> ()"),
                                                      null,
                                                      null,
                                                      cv);
    clinitgen.visitCode();
    Label startTry = clinitgen.newLabel();
    Label endTry = clinitgen.newLabel();
    Label end = clinitgen.newLabel();
    Label finallyLabel = clinitgen.newLabel();

//    if(objx.constants.count() > 0)
//      {
//      objx.emitConstants(clinitgen);
//      }
    for(int n = 0;n<numInits;n++)
      clinitgen.invokeStatic(objx.objtype, Method.getMethod("void __init" + n + "()"));

    clinitgen.push(objx.internalName.replace('/','.'));
    clinitgen.invokeStatic(CLASS_TYPE, Method.getMethod("Class forName(String)"));
    clinitgen.invokeVirtual(CLASS_TYPE,Method.getMethod("ClassLoader getClassLoader()"));
    clinitgen.invokeStatic(Type.getType(Compiler.class), Method.getMethod("void pushNSandLoader(ClassLoader)"));
    clinitgen.mark(startTry);
    clinitgen.invokeStatic(objx.objtype, Method.getMethod("void load()"));
    clinitgen.mark(endTry);
    clinitgen.invokeStatic(VAR_TYPE, Method.getMethod("void popThreadBindings()"));
    clinitgen.goTo(end);

    clinitgen.mark(finallyLabel);
    //exception should be on stack
    clinitgen.invokeStatic(VAR_TYPE, Method.getMethod("void popThreadBindings()"));
    clinitgen.throwException();
    clinitgen.mark(end);
    clinitgen.visitTryCatchBlock(startTry, endTry, finallyLabel, null);

    //end of static init
    clinitgen.returnValue();
    clinitgen.endMethod();

    //end of class
    cv.visitEnd();

    writeClassFile(objx.internalName, cw.toByteArray());
    }
  catch(LispReader.ReaderException e)
    {
    throw new CompilerException(sourcePath, e.line, e.column, e.getCause());
    }
  finally
    {
    Var.popThreadBindings();
    }
  return ret;
}


static public class NewInstanceExpr extends ObjExpr{
  //IPersistentMap optionsMap = PersistentArrayMap.EMPTY;
  IPersistentCollection methods;

  Map<IPersistentVector,java.lang.reflect.Method> mmap;
  Map<IPersistentVector,Set<Class>> covariants;

  public NewInstanceExpr(Object tag){
    super(tag);
  }

  static class DeftypeParser implements IParser{
    public Expr parse(C context, final Object frm) {
      ISeq rform = (ISeq) frm;
      //(deftype* tagname classname [fields] :implements [interfaces] :tag tagname methods*)
      rform = RT.next(rform);
      String tagname = ((Symbol) rform.first()).toString();
      rform = rform.next();
      Symbol classname = (Symbol) rform.first();
      rform = rform.next();
      IPersistentVector fields = (IPersistentVector) rform.first();
      rform = rform.next();
      IPersistentMap opts = PersistentHashMap.EMPTY;
      while(rform != null && rform.first() instanceof Keyword)
        {
        opts = opts.assoc(rform.first(), RT.second(rform));
        rform = rform.next().next();
        }

      ObjExpr ret = build((IPersistentVector)RT.get(opts,implementsKey,PersistentVector.EMPTY),fields,null,tagname, classname,
                   (Symbol) RT.get(opts,RT.TAG_KEY),rform, frm);
      return ret;
    }
  }

  static class ReifyParser implements IParser{
  public Expr parse(C context, Object frm) {
    //(reify this-name? [interfaces] (method-name [args] body)*)
    ISeq form = (ISeq) frm;
    ObjMethod enclosingMethod = (ObjMethod) METHOD.deref();
    String basename = enclosingMethod != null ?
                      (trimGenID(enclosingMethod.objx.name) + "$")
                     : (munge(currentNS().name.name) + "$");
    String simpleName = "reify__" + RT.nextID();
    String classname = basename + simpleName;

    ISeq rform = RT.next(form);

    IPersistentVector interfaces = ((IPersistentVector) RT.first(rform)).cons(Symbol.intern("clojure.lang.IObj"));


    rform = RT.next(rform);


    ObjExpr ret = build(interfaces, null, null, classname, Symbol.intern(classname), null, rform, frm);
    if(frm instanceof IObj && ((IObj) frm).meta() != null)
      return new MetaExpr(ret, MapExpr
          .parse(context == C.EVAL ? context : C.EXPRESSION, ((IObj) frm).meta()));
    else
      return ret;
  }
  }

  static ObjExpr build(IPersistentVector interfaceSyms, IPersistentVector fieldSyms, Symbol thisSym,
                       String tagName, Symbol className,
                    Symbol typeTag, ISeq methodForms, Object frm) {
    NewInstanceExpr ret = new NewInstanceExpr(null);

    ret.src = frm;
    ret.name = className.toString();
    ret.classMeta = RT.meta(className);
    ret.internalName = ret.name.replace('.', '/');
    ret.objtype = Type.getObjectType(ret.internalName);

    if(thisSym != null)
      ret.thisName = thisSym.name;

    if(fieldSyms != null)
      {
      IPersistentMap fmap = PersistentHashMap.EMPTY;
      Object[] closesvec = new Object[2 * fieldSyms.count()];
      for(int i=0;i<fieldSyms.count();i++)
        {
        Symbol sym = (Symbol) fieldSyms.nth(i);
        LocalBinding lb = new LocalBinding(-1, sym, null,
                                           new MethodParamExpr(tagClass(tagOf(sym))),false,null);
        fmap = fmap.assoc(sym, lb);
        closesvec[i*2] = lb;
        closesvec[i*2 + 1] = lb;
        }

      //todo - inject __meta et al into closes - when?
      //use array map to preserve ctor order
      ret.closes = new PersistentArrayMap(closesvec);
      ret.fields = fmap;
      for(int i=fieldSyms.count()-1;i >= 0 && (((Symbol)fieldSyms.nth(i)).name.equals("__meta") || ((Symbol)fieldSyms.nth(i)).name.equals("__extmap"));--i)
        ret.altCtorDrops++;
      }
    //todo - set up volatiles
//    ret.volatiles = PersistentHashSet.create(RT.seq(RT.get(ret.optionsMap, volatileKey)));

    PersistentVector interfaces = PersistentVector.EMPTY;
    for(ISeq s = RT.seq(interfaceSyms);s!=null;s = s.next())
      {
      Class c = (Class) resolve((Symbol) s.first());
      if(!c.isInterface())
        throw new IllegalArgumentException("only interfaces are supported, had: " + c.getName());
      interfaces = interfaces.cons(c);
      }
    Class superClass = Object.class;
    Map[] mc = gatherMethods(superClass,RT.seq(interfaces));
    Map overrideables = mc[0];
    Map covariants = mc[1];
    ret.mmap = overrideables;
    ret.covariants = covariants;
   
    String[] inames = interfaceNames(interfaces);

    Class stub = compileStub(slashname(superClass),ret, inames, frm);
    Symbol thistag = Symbol.intern(null,stub.getName());

    try
      {
      Var.pushThreadBindings(
          RT.mapUniqueKeys(CONSTANTS, PersistentVector.EMPTY,
                 CONSTANT_IDS, new IdentityHashMap(),
                 KEYWORDS, PersistentHashMap.EMPTY,
                 VARS, PersistentHashMap.EMPTY,
                 KEYWORD_CALLSITES, PersistentVector.EMPTY,
                 PROTOCOL_CALLSITES, PersistentVector.EMPTY,
                 VAR_CALLSITES, emptyVarCallSites(),
                                               NO_RECUR, null));
      if(ret.isDeftype())
        {
        Var.pushThreadBindings(RT.mapUniqueKeys(METHOD, null,
                                      LOCAL_ENV, ret.fields
            , COMPILE_STUB_SYM, Symbol.intern(null, tagName)
            , COMPILE_STUB_CLASS, stub));

        ret.hintedFields = RT.subvec(fieldSyms, 0, fieldSyms.count() - ret.altCtorDrops);
        }

      //now (methodname [args] body)*
      ret.line = lineDeref();
      ret.column = columnDeref();
      IPersistentCollection methods = null;
      for(ISeq s = methodForms; s != null; s = RT.next(s))
        {
        NewInstanceMethod m = NewInstanceMethod.parse(ret, (ISeq) RT.first(s),thistag, overrideables);
        methods = RT.conj(methods, m);
        }


      ret.methods = methods;
      ret.keywords = (IPersistentMap) KEYWORDS.deref();
      ret.vars = (IPersistentMap) VARS.deref();
      ret.constants = (PersistentVector) CONSTANTS.deref();
      ret.constantsID = RT.nextID();
      ret.keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref();
      ret.protocolCallsites = (IPersistentVector) PROTOCOL_CALLSITES.deref();
      ret.varCallsites = (IPersistentSet) VAR_CALLSITES.deref();
      }
    finally
      {
      if(ret.isDeftype())
        Var.popThreadBindings();
      Var.popThreadBindings();
      }

    try
      {
      ret.compile(slashname(superClass),inames,false);
      }
    catch(IOException e)
      {
      throw Util.sneakyThrow(e);
      }
    ret.getCompiledClass();
    return ret;
    }

  /***
   * Current host interop uses reflection, which requires pre-existing classes
   * Work around this by:
   * Generate a stub class that has the same interfaces and fields as the class we are generating.
   * Use it as a type hint for this, and bind the simple name of the class to this stub (in resolve etc)
   * Unmunge the name (using a magic prefix) on any code gen for classes
   */
  static Class compileStub(String superName, NewInstanceExpr ret, String[] interfaceNames, Object frm){
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    ClassVisitor cv = cw;
    cv.visit(V1_5, ACC_PUBLIC + ACC_SUPER, COMPILE_STUB_PREFIX + "/" + ret.internalName,
             null,superName,interfaceNames);

    //instance fields for closed-overs
    for(ISeq s = RT.keys(ret.closes); s != null; s = s.next())
      {
      LocalBinding lb = (LocalBinding) s.first();
      int access = ACC_PUBLIC + (ret.isVolatile(lb) ? ACC_VOLATILE :
                                 ret.isMutable(lb) ? 0 :
                                 ACC_FINAL);
      if(lb.getPrimitiveType() != null)
        cv.visitField(access
            , lb.name, Type.getType(lb.getPrimitiveType()).getDescriptor(),
                null, null);
      else
      //todo - when closed-overs are fields, use more specific types here and in ctor and emitLocal?
        cv.visitField(access
            , lb.name, OBJECT_TYPE.getDescriptor(), null, null);
      }

    //ctor that takes closed-overs and does nothing
    Method m = new Method("<init>", Type.VOID_TYPE, ret.ctorTypes());
    GeneratorAdapter ctorgen = new GeneratorAdapter(ACC_PUBLIC,
                                                    m,
                                                    null,
                                                    null,
                                                    cv);
    ctorgen.visitCode();
    ctorgen.loadThis();
    ctorgen.invokeConstructor(Type.getObjectType(superName), voidctor);
    ctorgen.returnValue();
    ctorgen.endMethod();

    if(ret.altCtorDrops > 0)
      {
      Type[] ctorTypes = ret.ctorTypes();
      Type[] altCtorTypes = new Type[ctorTypes.length-ret.altCtorDrops];
      for(int i=0;i<altCtorTypes.length;i++)
        altCtorTypes[i] = ctorTypes[i];
      Method alt = new Method("<init>", Type.VOID_TYPE, altCtorTypes);
      ctorgen = new GeneratorAdapter(ACC_PUBLIC,
                              alt,
                              null,
                              null,
                              cv);
      ctorgen.visitCode();
      ctorgen.loadThis();
      ctorgen.loadArgs();
      for(int i=0;i<ret.altCtorDrops;i++)
        ctorgen.visitInsn(Opcodes.ACONST_NULL);

      ctorgen.invokeConstructor(Type.getObjectType(COMPILE_STUB_PREFIX + "/" + ret.internalName),
                                new Method("<init>", Type.VOID_TYPE, ctorTypes));

      ctorgen.returnValue();
      ctorgen.endMethod();
      }
    //end of class
    cv.visitEnd();

    byte[] bytecode = cw.toByteArray();
    DynamicClassLoader loader = (DynamicClassLoader) LOADER.deref();
    return loader.defineClass(COMPILE_STUB_PREFIX + "." + ret.name, bytecode, frm);
  }

  static String[] interfaceNames(IPersistentVector interfaces){
    int icnt = interfaces.count();
    String[] inames = icnt > 0 ? new String[icnt] : null;
    for(int i=0;i<icnt;i++)
      inames[i] = slashname((Class) interfaces.nth(i));
    return inames;
  }


  static String slashname(Class c){
    return c.getName().replace('.', '/');
  }

  protected void emitStatics(ClassVisitor cv) {
    if(this.isDeftype())
      {
      //getBasis()
      Method meth = Method.getMethod("clojure.lang.IPersistentVector getBasis()");
      GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
                        meth,
                        null,
                        null,
                        cv);
      emitValue(hintedFields, gen);
      gen.returnValue();
      gen.endMethod();

      if (this.isDeftype() && this.fields.count() > this.hintedFields.count())
        {
        //create(IPersistentMap)
        String className = name.replace('.', '/');
        int i = 1;
        int fieldCount = hintedFields.count();

        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, "create", "(Lclojure/lang/IPersistentMap;)L"+className+";", null, null);
        mv.visitCode();

        for(ISeq s = RT.seq(hintedFields); s!=null; s=s.next(), i++)
          {
          String bName = ((Symbol)s.first()).name;
          Class k = tagClass(tagOf(s.first()));

          mv.visitVarInsn(ALOAD, 0);
          mv.visitLdcInsn(bName);
          mv.visitMethodInsn(INVOKESTATIC, "clojure/lang/Keyword", "intern", "(Ljava/lang/String;)Lclojure/lang/Keyword;");
          mv.visitInsn(ACONST_NULL);
          mv.visitMethodInsn(INVOKEINTERFACE, "clojure/lang/IPersistentMap", "valAt", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
          if(k.isPrimitive())
            {
            mv.visitTypeInsn(CHECKCAST, Type.getType(boxClass(k)).getInternalName());
            }
          mv.visitVarInsn(ASTORE, i);
          mv.visitVarInsn(ALOAD, 0);
          mv.visitLdcInsn(bName);
          mv.visitMethodInsn(INVOKESTATIC, "clojure/lang/Keyword", "intern", "(Ljava/lang/String;)Lclojure/lang/Keyword;");
          mv.visitMethodInsn(INVOKEINTERFACE, "clojure/lang/IPersistentMap", "without", "(Ljava/lang/Object;)Lclojure/lang/IPersistentMap;");
          mv.visitVarInsn(ASTORE, 0);
          }

        mv.visitTypeInsn(Opcodes.NEW, className);
        mv.visitInsn(DUP);

        Method ctor = new Method("<init>", Type.VOID_TYPE, ctorTypes());

        if(hintedFields.count() > 0)
          for(i=1; i<=fieldCount; i++)
            {
            mv.visitVarInsn(ALOAD, i);
            Class k = tagClass(tagOf(hintedFields.nth(i-1)));
            if(k.isPrimitive())
              {
              String b = Type.getType(boxClass(k)).getInternalName();
              String p = Type.getType(k).getDescriptor();
              String n = k.getName();

              mv.visitMethodInsn(INVOKEVIRTUAL, b, n+"Value", "()"+p);
              }
            }

        mv.visitInsn(ACONST_NULL);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESTATIC, "clojure/lang/RT", "seqOrElse", "(Ljava/lang/Object;)Ljava/lang/Object;");
        mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", ctor.getDescriptor());
        mv.visitInsn(ARETURN);
        mv.visitMaxs(4+fieldCount, 1+fieldCount);
        mv.visitEnd();
        }
      }
  }

  protected void emitMethods(ClassVisitor cv){
    for(ISeq s = RT.seq(methods); s != null; s = s.next())
      {
      ObjMethod method = (ObjMethod) s.first();
      method.emit(this, cv);
      }
    //emit bridge methods
    for(Map.Entry<IPersistentVector,Set<Class>> e : covariants.entrySet())
      {
      java.lang.reflect.Method m = mmap.get(e.getKey());
      Class[] params = m.getParameterTypes();
      Type[] argTypes = new Type[params.length];

      for(int i = 0; i < params.length; i++)
        {
        argTypes[i] = Type.getType(params[i]);
        }

      Method target = new Method(m.getName(), Type.getType(m.getReturnType()), argTypes);

      for(Class retType : e.getValue())
        {
             Method meth = new Method(m.getName(), Type.getType(retType), argTypes);

        GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_BRIDGE,
                                                meth,
                                                null,
                                                //todo don't hardwire this
                                                EXCEPTION_TYPES,
                                                cv);
        gen.visitCode();
        gen.loadThis();
        gen.loadArgs();
        gen.invokeInterface(Type.getType(m.getDeclaringClass()),target);
        gen.returnValue();
        gen.endMethod();
        }
      }
  }

  static public IPersistentVector msig(java.lang.reflect.Method m){
    return RT.vector(m.getName(), RT.seq(m.getParameterTypes()),m.getReturnType());
  }

  static void considerMethod(java.lang.reflect.Method m, Map mm){
    IPersistentVector mk = msig(m);
    int mods = m.getModifiers();

    if(!(mm.containsKey(mk)
        || !(Modifier.isPublic(mods) || Modifier.isProtected(mods))
        || Modifier.isStatic(mods)
        || Modifier.isFinal(mods)))
      {
        mm.put(mk, m);
      }
  }

  static void gatherMethods(Class c, Map mm){
    for(; c != null; c = c.getSuperclass())
      {
      for(java.lang.reflect.Method m : c.getDeclaredMethods())
        considerMethod(m, mm);
      for(java.lang.reflect.Method m : c.getMethods())
        considerMethod(m, mm);
      }
  }

  static public Map[] gatherMethods(Class sc, ISeq interfaces){
    Map allm = new HashMap();
    gatherMethods(sc, allm);
    for(; interfaces != null; interfaces = interfaces.next())
      gatherMethods((Class) interfaces.first(), allm);

    Map<IPersistentVector,java.lang.reflect.Method> mm = new HashMap<IPersistentVector,java.lang.reflect.Method>();
    Map<IPersistentVector,Set<Class>> covariants = new HashMap<IPersistentVector,Set<Class>>();
    for(Object o : allm.entrySet())
      {
      Map.Entry e = (Map.Entry) o;
      IPersistentVector mk = (IPersistentVector) e.getKey();
      mk = (IPersistentVector) mk.pop();
      java.lang.reflect.Method m = (java.lang.reflect.Method) e.getValue();
      if(mm.containsKey(mk)) //covariant return
        {
        Set<Class> cvs = covariants.get(mk);
        if(cvs == null)
          {
          cvs = new HashSet<Class>();
          covariants.put(mk,cvs);
          }
        java.lang.reflect.Method om = mm.get(mk);
        if(om.getReturnType().isAssignableFrom(m.getReturnType()))
          {
          cvs.add(om.getReturnType());
          mm.put(mk, m);
          }
        else
          cvs.add(m.getReturnType());
        }
      else
        mm.put(mk, m);
      }
    return new Map[]{mm,covariants};
  }
}

public static class NewInstanceMethod extends ObjMethod{
  String name;
  Type[] argTypes;
  Type retType;
  Class retClass;
  Class[] exclasses;

  static Symbol dummyThis = Symbol.intern(null,"dummy_this_dlskjsdfower");
  private IPersistentVector parms;

  public NewInstanceMethod(ObjExpr objx, ObjMethod parent){
    super(objx, parent);
  }

  int numParams(){
    return argLocals.count();
  }

  String getMethodName(){
    return name;
  }

  Type getReturnType(){
    return retType;
  }

  Type[] getArgTypes(){
    return argTypes;
  }



  static public IPersistentVector msig(String name,Class[] paramTypes){
    return RT.vector(name,RT.seq(paramTypes));
  }

  static NewInstanceMethod parse(ObjExpr objx, ISeq form, Symbol thistag,
                                 Map overrideables) {
    //(methodname [this-name args*] body...)
    //this-name might be nil
    NewInstanceMethod method = new NewInstanceMethod(objx, (ObjMethod) METHOD.deref());
    Symbol dotname = (Symbol)RT.first(form);
    Symbol name = (Symbol) Symbol.intern(null,munge(dotname.name)).withMeta(RT.meta(dotname));
    IPersistentVector parms = (IPersistentVector) RT.second(form);
    if(parms.count() == 0)
      {
      throw new IllegalArgumentException("Must supply at least one argument for 'this' in: " + dotname);
      }
    Symbol thisName = (Symbol) parms.nth(0);
    parms = RT.subvec(parms,1,parms.count());
    ISeq body = RT.next(RT.next(form));
    try
      {
      method.line = lineDeref();
      method.column = columnDeref();
      //register as the current method and set up a new env frame
            PathNode pnode =  new PathNode(PATHTYPE.PATH, (PathNode) CLEAR_PATH.get());
      Var.pushThreadBindings(
          RT.mapUniqueKeys(
              METHOD, method,
              LOCAL_ENV, LOCAL_ENV.deref(),
              LOOP_LOCALS, null,
              NEXT_LOCAL_NUM, 0
                            ,CLEAR_PATH, pnode
                            ,CLEAR_ROOT, pnode
                            ,CLEAR_SITES, PersistentHashMap.EMPTY
                    ));

      //register 'this' as local 0
      if(thisName != null)
        registerLocal((thisName == null) ? dummyThis:thisName,thistag, null,false);
      else
        getAndIncLocalNum();

      PersistentVector argLocals = PersistentVector.EMPTY;
      method.retClass = tagClass(tagOf(name));
      method.argTypes = new Type[parms.count()];
      boolean hinted = tagOf(name) != null;
      Class[] pclasses = new Class[parms.count()];
      Symbol[] psyms = new Symbol[parms.count()];

      for(int i = 0; i < parms.count(); i++)
        {
        if(!(parms.nth(i) instanceof Symbol))
          throw new IllegalArgumentException("params must be Symbols");
        Symbol p = (Symbol) parms.nth(i);
        Object tag = tagOf(p);
        if(tag != null)
          hinted = true;
        if(p.getNamespace() != null)
          p = Symbol.intern(p.name);
        Class pclass = tagClass(tag);
        pclasses[i] = pclass;
        psyms[i] = p;
        }
      Map matches = findMethodsWithNameAndArity(name.name, parms.count(), overrideables);
      Object mk = msig(name.name, pclasses);
      java.lang.reflect.Method m = null;
      if(matches.size() > 0)
        {
        //multiple methods
        if(matches.size() > 1)
          {
          //must be hinted and match one method
          if(!hinted)
            throw new IllegalArgumentException("Must hint overloaded method: " + name.name);
          m = (java.lang.reflect.Method) matches.get(mk);
          if(m == null)
            throw new IllegalArgumentException("Can't find matching overloaded method: " + name.name);
          if(m.getReturnType() != method.retClass)
            throw new IllegalArgumentException("Mismatched return type: " + name.name +
            ", expected: " + m.getReturnType().getName()  + ", had: " + method.retClass.getName());
          }
        else  //one match
          {
          //if hinted, validate match,
          if(hinted)
            {
            m = (java.lang.reflect.Method) matches.get(mk);
            if(m == null)
              throw new IllegalArgumentException("Can't find matching method: " + name.name +
                                                 ", leave off hints for auto match.");
            if(m.getReturnType() != method.retClass)
              throw new IllegalArgumentException("Mismatched return type: " + name.name +
              ", expected: " + m.getReturnType().getName()  + ", had: " + method.retClass.getName());
            }
          else //adopt found method sig
            {
            m = (java.lang.reflect.Method) matches.values().iterator().next();
            method.retClass = m.getReturnType();
            pclasses = m.getParameterTypes();
            }
          }
        }
//      else if(findMethodsWithName(name.name,allmethods).size()>0)
//        throw new IllegalArgumentException("Can't override/overload method: " + name.name);
      else
        throw new IllegalArgumentException("Can't define method not in interfaces: " + name.name);

      //else
        //validate unque name+arity among additional methods

      method.retType = Type.getType(method.retClass);
      method.exclasses = m.getExceptionTypes();

      for(int i = 0; i < parms.count(); i++)
        {
        LocalBinding lb = registerLocal(psyms[i], null, new MethodParamExpr(pclasses[i]),true);
        argLocals = argLocals.assocN(i,lb);
        method.argTypes[i] = Type.getType(pclasses[i]);
        }
      for(int i = 0; i < parms.count(); i++)
        {
        if(pclasses[i] == long.class || pclasses[i] == double.class)
          getAndIncLocalNum();
        }
      LOOP_LOCALS.set(argLocals);
      method.name = name.name;
      method.methodMeta = RT.meta(name);
      method.parms = parms;
      method.argLocals = argLocals;
      method.body = (new BodyExpr.Parser()).parse(C.RETURN, body);
      return method;
      }
    finally
      {
      Var.popThreadBindings();
      }
  }

  private static Map findMethodsWithNameAndArity(String name, int arity, Map mm){
    Map ret = new HashMap();
    for(Object o : mm.entrySet())
      {
      Map.Entry e = (Map.Entry) o;
      java.lang.reflect.Method m = (java.lang.reflect.Method) e.getValue();
      if(name.equals(m.getName()) && m.getParameterTypes().length == arity)
        ret.put(e.getKey(), e.getValue());
      }
    return ret;
  }

  private static Map findMethodsWithName(String name, Map mm){
    Map ret = new HashMap();
    for(Object o : mm.entrySet())
      {
      Map.Entry e = (Map.Entry) o;
      java.lang.reflect.Method m = (java.lang.reflect.Method) e.getValue();
      if(name.equals(m.getName()))
        ret.put(e.getKey(), e.getValue());
      }
    return ret;
  }

  public void emit(ObjExpr obj, ClassVisitor cv){
    Method m = new Method(getMethodName(), getReturnType(), getArgTypes());

    Type[] extypes = null;
    if(exclasses.length > 0)
      {
      extypes = new Type[exclasses.length];
      for(int i=0;i<exclasses.length;i++)
        extypes[i] = Type.getType(exclasses[i]);
      }
    GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC,
                                                m,
                                                null,
                                                extypes,
                                                cv);
    addAnnotation(gen,methodMeta);
    for(int i = 0; i < parms.count(); i++)
      {
      IPersistentMap meta = RT.meta(parms.nth(i));
      addParameterAnnotation(gen, meta, i);
      }
    gen.visitCode();

    Label loopLabel = gen.mark();

    gen.visitLineNumber(line, loopLabel);
    try
      {
      Var.pushThreadBindings(RT.map(LOOP_LABEL, loopLabel, METHOD, this));

      emitBody(objx, gen, retClass, body);
      Label end = gen.mark();
      gen.visitLocalVariable("this", obj.objtype.getDescriptor(), null, loopLabel, end, 0);
      for(ISeq lbs = argLocals.seq(); lbs != null; lbs = lbs.next())
        {
        LocalBinding lb = (LocalBinding) lbs.first();
        gen.visitLocalVariable(lb.name, argTypes[lb.idx-1].getDescriptor(), null, loopLabel, end, lb.idx);
        }
      }
    finally
      {
      Var.popThreadBindings();
      }

    gen.returnValue();
    //gen.visitMaxs(1, 1);
    gen.endMethod();
  }
}

  static Class primClass(Symbol sym){
    if(sym == null)
      return null;
    Class c = null;
    if(sym.name.equals("int"))
      c = int.class;
    else if(sym.name.equals("long"))
      c = long.class;
    else if(sym.name.equals("float"))
      c = float.class;
    else if(sym.name.equals("double"))
      c = double.class;
    else if(sym.name.equals("char"))
      c = char.class;
    else if(sym.name.equals("short"))
      c = short.class;
    else if(sym.name.equals("byte"))
      c = byte.class;
    else if(sym.name.equals("boolean"))
      c = boolean.class;
    else if(sym.name.equals("void"))
      c = void.class;
    return c;
  }

  static Class tagClass(Object tag) {
    if(tag == null)
      return Object.class;
    Class c = null;
    if(tag instanceof Symbol)
      c = primClass((Symbol) tag);
    if(c == null)
      c = HostExpr.tagToClass(tag);
    return c;
  }

  static Class primClass(Class c){
    return c.isPrimitive()?c:Object.class;
  }

  static Class boxClass(Class p) {
    if(!p.isPrimitive())
      return p;

    Class c = null;

    if(p == Integer.TYPE)
      c = Integer.class;
    else if(p == Long.TYPE)
      c = Long.class;
    else if(p == Float.TYPE)
      c = Float.class;
    else if(p == Double.TYPE)
      c = Double.class;
    else if(p == Character.TYPE)
      c = Character.class;
    else if(p == Short.TYPE)
      c = Short.class;
    else if(p == Byte.TYPE)
      c = Byte.class;
    else if(p == Boolean.TYPE)
      c = Boolean.class;

    return c;
  }

static public class MethodParamExpr implements Expr, MaybePrimitiveExpr{
  final Class c;

  public MethodParamExpr(Class c){
    this.c = c;
  }

  public Object eval() {
    throw Util.runtimeException("Can't eval");
  }

  public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
    throw Util.runtimeException("Can't emit");
  }

  public boolean hasJavaClass() {
    return c != null;
  }

  public Class getJavaClass() {
    return c;
  }

  public boolean canEmitPrimitive(){
    return Util.isPrimitive(c);
  }

  public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
    throw Util.runtimeException("Can't emit");
  }
}

public static class CaseExpr implements Expr, MaybePrimitiveExpr{
  public final LocalBindingExpr expr;
  public final int shift, mask, low, high;
  public final Expr defaultExpr;
  public final SortedMap<Integer,Expr> tests;
  public final HashMap<Integer,Expr> thens;
  public final Keyword switchType;
  public final Keyword testType;
  public final Set<Integer> skipCheck;
  public final Class returnType;
  public final int line;
  public final int column;

  final static Type NUMBER_TYPE = Type.getType(Number.class);
  final static Method intValueMethod = Method.getMethod("int intValue()");

  final static Method hashMethod = Method.getMethod("int hash(Object)");
  final static Method hashCodeMethod = Method.getMethod("int hashCode()");
  final static Method equivMethod = Method.getMethod("boolean equiv(Object, Object)");
    final static Keyword compactKey = Keyword.intern(null, "compact");
    final static Keyword sparseKey = Keyword.intern(null, "sparse");
    final static Keyword hashIdentityKey = Keyword.intern(null, "hash-identity");
    final static Keyword hashEquivKey = Keyword.intern(null, "hash-equiv");
    final static Keyword intKey = Keyword.intern(null, "int");
  //(case* expr shift mask default map<minhash, [test then]> table-type test-type skip-check?)
  public CaseExpr(int line, int column, LocalBindingExpr expr, int shift, int mask, int low, int high, Expr defaultExpr,
          SortedMap<Integer,Expr> tests,HashMap<Integer,Expr> thens, Keyword switchType, Keyword testType, Set<Integer> skipCheck){
    this.expr = expr;
    this.shift = shift;
    this.mask = mask;
    this.low = low;
    this.high = high;
    this.defaultExpr = defaultExpr;
    this.tests = tests;
    this.thens = thens;
    this.line = line;
    this.column = column;
    if (switchType != compactKey && switchType != sparseKey)
        throw new IllegalArgumentException("Unexpected switch type: "+switchType);
    this.switchType = switchType;
        if (testType != intKey && testType != hashEquivKey && testType != hashIdentityKey)
            throw new IllegalArgumentException("Unexpected test type: "+switchType);
    this.testType = testType;
    this.skipCheck = skipCheck;
    Collection<Expr> returns = new ArrayList(thens.values());
    returns.add(defaultExpr);
    this.returnType = maybeJavaClass(returns);
        if(RT.count(skipCheck) > 0 && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
            {
            RT.errPrintWriter()
              .format("Performance warning, %s:%d:%d - hash collision of some case test constants; if selected, those entries will be tested sequentially.\n",
                      SOURCE_PATH.deref(), line, column);
            }
  }

  public boolean hasJavaClass(){
      return returnType != null;
  }

  public boolean canEmitPrimitive(){
  return Util.isPrimitive(returnType);
  }

  public Class getJavaClass(){
      return returnType;
  }

  public Object eval() {
    throw new UnsupportedOperationException("Can't eval case");
  }

    public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
        doEmit(context, objx, gen, false);
    }

    public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
        doEmit(context, objx, gen, true);
    }

  public void doEmit(C context, ObjExpr objx, GeneratorAdapter gen, boolean emitUnboxed){
    Label defaultLabel = gen.newLabel();
    Label endLabel = gen.newLabel();
    SortedMap<Integer,Label> labels = new TreeMap();

    for(Integer i : tests.keySet())
      {
      labels.put(i, gen.newLabel());
      }

        gen.visitLineNumber(line, gen.mark());

        Class primExprClass = maybePrimitiveType(expr);
        Type primExprType = primExprClass == null ? null : Type.getType(primExprClass);

        if (testType == intKey)
        emitExprForInts(objx, gen, primExprType, defaultLabel);
        else
            emitExprForHashes(objx, gen);

        if (switchType == sparseKey)
            {
            Label[] la = new Label[labels.size()];
            la = labels.values().toArray(la);
            int[] ints = Numbers.int_array(tests.keySet());
            gen.visitLookupSwitchInsn(defaultLabel, ints, la);
            }
        else
            {
            Label[] la = new Label[(high-low)+1];
            for(int i=low;i<=high;i++)
                {
                la[i-low] = labels.containsKey(i) ? labels.get(i) : defaultLabel;
                }
            gen.visitTableSwitchInsn(low, high, defaultLabel, la);
            }

    for(Integer i : labels.keySet())
      {
      gen.mark(labels.get(i));
      if (testType == intKey)
          emitThenForInts(objx, gen, primExprType, tests.get(i), thens.get(i), defaultLabel, emitUnboxed);
      else if (RT.contains(skipCheck, i) == RT.T)
          emitExpr(objx, gen, thens.get(i), emitUnboxed);
      else
          emitThenForHashes(objx, gen, tests.get(i), thens.get(i), defaultLabel, emitUnboxed);
      gen.goTo(endLabel);
      }

    gen.mark(defaultLabel);
    emitExpr(objx, gen, defaultExpr, emitUnboxed);
    gen.mark(endLabel);
    if(context == C.STATEMENT)
      gen.pop();
  }

  private boolean isShiftMasked(){
      return  mask != 0;
  }

  private void emitShiftMask(GeneratorAdapter gen){
      if (isShiftMasked())
          {
            gen.push(shift);
            gen.visitInsn(ISHR);
            gen.push(mask);
            gen.visitInsn(IAND);
          }
  }

    private void emitExprForInts(ObjExpr objx, GeneratorAdapter gen, Type exprType, Label defaultLabel){
        if (exprType == null)
            {
            if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
                {
                RT.errPrintWriter()
                  .format("Performance warning, %s:%d:%d - case has int tests, but tested expression is not primitive.\n",
                          SOURCE_PATH.deref(), line, column);
                }
            expr.emit(C.EXPRESSION, objx, gen);
            gen.instanceOf(NUMBER_TYPE);
            gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
            expr.emit(C.EXPRESSION, objx, gen);
            gen.checkCast(NUMBER_TYPE);
            gen.invokeVirtual(NUMBER_TYPE, intValueMethod);
            emitShiftMask(gen);
            }
        else if (exprType == Type.LONG_TYPE
                || exprType == Type.INT_TYPE
                || exprType == Type.SHORT_TYPE
                || exprType == Type.BYTE_TYPE)
            {
            expr.emitUnboxed(C.EXPRESSION, objx, gen);
            gen.cast(exprType, Type.INT_TYPE);
            emitShiftMask(gen);
            }
        else
            {
            gen.goTo(defaultLabel);
            }
    }

    private void emitThenForInts(ObjExpr objx, GeneratorAdapter gen, Type exprType, Expr test, Expr then, Label defaultLabel, boolean emitUnboxed){
        if (exprType == null)
            {
            expr.emit(C.EXPRESSION, objx, gen);
            test.emit(C.EXPRESSION, objx, gen);
            gen.invokeStatic(UTIL_TYPE, equivMethod);
            gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
            emitExpr(objx, gen, then, emitUnboxed);
            }
        else if (exprType == Type.LONG_TYPE)
            {
            ((NumberExpr)test).emitUnboxed(C.EXPRESSION, objx, gen);
            expr.emitUnboxed(C.EXPRESSION, objx, gen);
            gen.ifCmp(Type.LONG_TYPE, GeneratorAdapter.NE, defaultLabel);
            emitExpr(objx, gen, then, emitUnboxed);
            }
        else if (exprType == Type.INT_TYPE
                || exprType == Type.SHORT_TYPE
                || exprType == Type.BYTE_TYPE)
            {
            if (isShiftMasked())
                {
                ((NumberExpr)test).emitUnboxed(C.EXPRESSION, objx, gen);
                expr.emitUnboxed(C.EXPRESSION, objx, gen);
                gen.cast(exprType, Type.LONG_TYPE);
                gen.ifCmp(Type.LONG_TYPE, GeneratorAdapter.NE, defaultLabel);
                }
            // else direct match
            emitExpr(objx, gen, then, emitUnboxed);
            }
        else
            {
            gen.goTo(defaultLabel);
            }
    }

    private void emitExprForHashes(ObjExpr objx, GeneratorAdapter gen){
        expr.emit(C.EXPRESSION, objx, gen);
        gen.invokeStatic(UTIL_TYPE,hashMethod);
        emitShiftMask(gen);
    }

    private void emitThenForHashes(ObjExpr objx, GeneratorAdapter gen, Expr test, Expr then, Label defaultLabel, boolean emitUnboxed){
        expr.emit(C.EXPRESSION, objx, gen);
        test.emit(C.EXPRESSION, objx, gen);
        if(testType == hashIdentityKey)
            {
            gen.visitJumpInsn(IF_ACMPNE, defaultLabel);
            }
        else
            {
            gen.invokeStatic(UTIL_TYPE, equivMethod);
            gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
            }
        emitExpr(objx, gen, then, emitUnboxed);
    }

    private static void emitExpr(ObjExpr objx, GeneratorAdapter gen, Expr expr, boolean emitUnboxed){
        if (emitUnboxed && expr instanceof MaybePrimitiveExpr)
            ((MaybePrimitiveExpr)expr).emitUnboxed(C.EXPRESSION,objx,gen);
        else
            expr.emit(C.EXPRESSION,objx,gen);
    }


  static class Parser implements IParser{
    //(case* expr shift mask default map<minhash, [test then]> table-type test-type skip-check?)
    //prepared by case macro and presumed correct
    //case macro binds actual expr in let so expr is always a local,
    //no need to worry about multiple evaluation
    public Expr parse(C context, Object frm) {
      ISeq form = (ISeq) frm;
      if(context == C.EVAL)
        return analyze(context, RT.list(RT.list(FNONCE, PersistentVector.EMPTY, form)));
      PersistentVector args = PersistentVector.create(form.next());

      Object exprForm = args.nth(0);
      int shift = ((Number)args.nth(1)).intValue();
      int mask = ((Number)args.nth(2)).intValue();
      Object defaultForm = args.nth(3);
      Map caseMap = (Map)args.nth(4);
      Keyword switchType = ((Keyword)args.nth(5));
      Keyword testType = ((Keyword)args.nth(6));
      Set skipCheck = RT.count(args) < 8 ? null : (Set)args.nth(7);

            ISeq keys = RT.keys(caseMap);
            int low = ((Number)RT.first(keys)).intValue();
            int high = ((Number)RT.nth(keys, RT.count(keys)-1)).intValue();

            LocalBindingExpr testexpr = (LocalBindingExpr) analyze(C.EXPRESSION, exprForm);
      testexpr.shouldClear = false;

            SortedMap<Integer,Expr> tests = new TreeMap();
            HashMap<Integer,Expr> thens = new HashMap();

            PathNode branch = new PathNode(PATHTYPE.BRANCH, (PathNode) CLEAR_PATH.get());

      for(Object o : caseMap.entrySet())
        {
        Map.Entry e = (Map.Entry) o;
        Integer minhash = ((Number)e.getKey()).intValue();
                Object pair = e.getValue(); // [test-val then-expr]
                Expr testExpr = testType == intKey
                                    ? NumberExpr.parse(((Number)RT.first(pair)).intValue())
                                    : new ConstantExpr(RT.first(pair));
                tests.put(minhash, testExpr);

                Expr thenExpr;
                try {
                    Var.pushThreadBindings(
                            RT.map(CLEAR_PATH, new PathNode(PATHTYPE.PATH,branch)));
                    thenExpr = analyze(context, RT.second(pair));
                    }
                finally{
                    Var.popThreadBindings();
                    }
        thens.put(minhash, thenExpr);
        }
           
            Expr defaultExpr;
            try {
                Var.pushThreadBindings(
                        RT.map(CLEAR_PATH, new PathNode(PATHTYPE.PATH,branch)));
                defaultExpr = analyze(context, args.nth(3));
                }
            finally{
                Var.popThreadBindings();
                }

            int line = ((Number)LINE.deref()).intValue();
            int column = ((Number)COLUMN.deref()).intValue();
      return new CaseExpr(line, column, testexpr, shift, mask, low, high,
              defaultExpr, tests, thens, switchType, testType, skipCheck);
    }
  }
}

static IPersistentCollection emptyVarCallSites(){return PersistentHashSet.EMPTY;}

}
TOP

Related Classes of clojure.lang.Compiler$BodyExpr

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.