/** * 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.Type; import clojure.asm.commons.GeneratorAdapter; import clojure.asm.commons.Method; import java.io.*; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.lang.reflect.Executable; import java.util.*; import java.util.concurrent.Callable; import java.util.regex.Pattern; import java.util.regex.Matcher; import java.util.stream.Collectors; //*/ /* 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 loadNs = Keyword.intern(null, "load-ns"); 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 Keyword redefKey = Keyword.intern(null, "redef"); 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); final static Type TUPLE_TYPE = Type.getType(Tuple.class); final static Method createTupleMethods[] = {Method.getMethod("clojure.lang.IPersistentVector create()"), Method.getMethod("clojure.lang.IPersistentVector create(Object)"), Method.getMethod("clojure.lang.IPersistentVector create(Object,Object)"), Method.getMethod("clojure.lang.IPersistentVector create(Object,Object,Object)"), Method.getMethod("clojure.lang.IPersistentVector create(Object,Object,Object,Object)"), Method.getMethod("clojure.lang.IPersistentVector create(Object,Object,Object,Object,Object)"), Method.getMethod("clojure.lang.IPersistentVector create(Object,Object,Object,Object,Object,Object)") }; 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 static final public Var LOOP_LOCALS = Var.create().setDynamic(); //Label static final public Var LOOP_LABEL = Var.create().setDynamic(); //vector static final public Var CONSTANTS = Var.create().setDynamic(); //IdentityHashMap static final public Var CONSTANT_IDS = Var.create().setDynamic(); //vector static final public Var KEYWORD_CALLSITES = Var.create(null).setDynamic(); //vector static final public Var PROTOCOL_CALLSITES = Var.create(null).setDynamic(); //set //static final public Var VAR_CALLSITES = Var.create(null).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 METHOD_RETURN_CONTEXT = 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 directLinkingKey = Keyword.intern("direct-linking"); 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 elides = (Collection) 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 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 boolean inTailCall(C context) { return (context == C.RETURN) && (METHOD_RETURN_CONTEXT.deref() != null) && (IN_CATCH_FINALLY.deref() == null); } 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 == null ? sym.ns == null : ns.name.name.equals(sym.ns))) { Class ac = HostExpr.maybeArrayClass(sym); if(ac != null) return Util.arrayTypeToSymbol(ac); 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(metaMap); } return var.setDynamic(isDynamic); } catch(Throwable e) { if(!(e instanceof CompilerException)) throw new CompilerException(source, line, column, Compiler.DEF, CompilerException.PHASE_EXECUTION, 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)"); Class jc; 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() { if (jc == null) jc = HostExpr.tagToClass(tag); return jc; } 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 { // System.out.println("NOT fnexpr for defn var: " + var + "init: " + init.getClass()); 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; boolean tailPosition = inTailCall(context); 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, tailPosition); else return new InstanceMethodExpr(source, line, column, tag, instance, null, munge(sym.name), args, tailPosition); } } } public 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.classForNameNonLoading(sym.name); else { Object o = currentNS().getMapping(sym); if(o instanceof Class) c = (Class) o; else if(LOCAL_ENV.deref() != null && ((java.util.Map)LOCAL_ENV.deref()).containsKey(form)) return null; else { try{ c = RT.classForNameNonLoading(sym.name); } catch(Exception e){ // aargh // leave c set to null -> return null } } } } } else if(stringOk && form instanceof String) c = RT.classForNameNonLoading((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; } */ public static Class maybeSpecialTag(Symbol sym) { Class c = primClass(sym); if (c != null) return c; else 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; return c; } static Class tagToClass(Object tag) { Class c = null; if(tag instanceof Symbol) { Symbol sym = (Symbol) tag; if(sym.ns == null) { c = maybeSpecialTag(sym); } if(c == null) c = HostExpr.maybeArrayClass(sym); } if(c == null) c = maybeClass(tag, true); if(c != null) return c; throw new IllegalArgumentException("Unable to resolve classname: " + tag); } public static boolean looksLikeArrayClass(Symbol sym) { return sym.ns != null && Util.isPosDigit(sym.name); } public static String buildArrayClassDescriptor(Symbol sym) { int dim = sym.name.charAt(0) - '0'; Symbol componentClassName = Symbol.intern(null, sym.ns); Class componentClass = primClass(componentClassName); if(componentClass == null) componentClass = maybeClass(componentClassName, false); if(componentClass == null) throw Util.sneakyThrow(new ClassNotFoundException("Unable to resolve component classname: " + componentClassName)); StringBuilder arrayDescriptor = new StringBuilder(); for(int i=0; i hintedSig; private final Symbol methodSymbol; private final String methodName; private final MethodKind kind; private final Class tagClass; private final StaticFieldExpr fieldOverload; private enum MethodKind { CTOR, INSTANCE, STATIC } public QualifiedMethodExpr(Class methodClass, Symbol sym) { this(methodClass, sym, null); } public QualifiedMethodExpr(Class methodClass, Symbol sym, StaticFieldExpr fieldOL) { c = methodClass; methodSymbol = sym; tagClass = tagOf(sym) != null ? HostExpr.tagToClass(tagOf(sym)) : AFn.class; hintedSig = tagsToClasses(paramTagsOf(sym)); if(sym.name.startsWith(".")) { kind = MethodKind.INSTANCE; methodName = sym.name.substring(1); } else if(sym.name.equals("new")) { kind = MethodKind.CTOR; methodName = sym.name; } else { kind = MethodKind.STATIC; methodName = sym.name; } fieldOverload = fieldOL; } private boolean preferOverloadedField() { return fieldOverload != null && paramTagsOf(methodSymbol) == null; } // Expr impl - invocation, convert to fn expr @Override public Object eval() { if(preferOverloadedField()) return fieldOverload.eval(); else return buildThunk(C.EVAL, this).eval(); } @Override public void emit(C context, ObjExpr objx, GeneratorAdapter gen) { if(preferOverloadedField()) fieldOverload.emit(context, objx, gen); else buildThunk(context, this).emit(context, objx, gen); } // Expr impl - method value, always an AFn @Override public boolean hasJavaClass() { return true; } @Override public Class getJavaClass() { return tagClass; } // TBD: caching/reuse of thunks private static FnExpr buildThunk(C context, QualifiedMethodExpr qmexpr) { // When qualified symbol has param-tags: // (fn invoke__Class_meth ([this? args*] (methodSymbol this? args*))) // When no param-tags: // (fn invoke__Class_meth ([this?] (methodSymbol this?)) // ([this? arg1] (methodSymbol this? arg1)) ...) IPersistentCollection form = PersistentVector.EMPTY; Symbol instanceParam = qmexpr.kind == MethodKind.INSTANCE ? THIS : null; String thunkName = "invoke__" + qmexpr.c.getSimpleName() + "_" + qmexpr.methodSymbol.name; Set arities = (qmexpr.hintedSig != null) ? PersistentHashSet.create(qmexpr.hintedSig.size()) : aritySet(qmexpr.c, qmexpr.methodName, qmexpr.kind); for(Object a : arities) { int arity = (int) a; IPersistentVector params = buildParams(instanceParam, arity); ISeq body = RT.listStar(qmexpr.methodSymbol, params.seq()); form = RT.conj(form, RT.list(params, body)); } ISeq thunkForm = RT.listStar(Symbol.intern("fn"), Symbol.intern(thunkName), RT.seq(form)); return (FnExpr) analyzeSeq(context, thunkForm, thunkName); } private static IPersistentVector buildParams(Symbol instanceParam, int arity) { IPersistentVector params = PersistentVector.EMPTY; if(instanceParam != null) params = params.cons(instanceParam); for(int i = 0; i(); List methods = methodsWithName(c, methodName, kind); for(Executable exec : methods) res.add(exec.getParameterCount()); return res; } public static List methodOverloads(Class c, String methodName, MethodKind kind) { final Executable[] methods = c.getMethods(); return Arrays.stream(methods) .filter(m -> m.getName().equals(methodName)) .filter(m -> { switch(kind) { case STATIC: return isStaticMethod(m); case INSTANCE: return isInstanceMethod(m); default: return false; } }) .collect(Collectors.toList()); } // Returns a list of methods or ctors matching the name and kind given. // Otherwise, will throw if the information provided results in no matches private static List methodsWithName(Class c, String methodName, MethodKind kind) { if (kind == MethodKind.CTOR) { List ctors = Arrays.asList(c.getConstructors()); if(ctors.isEmpty()) throw noMethodWithNameException(c, methodName, kind); return ctors; } List res = methodOverloads(c, methodName, kind); if(res.isEmpty()) throw noMethodWithNameException(c, methodName, kind); return res; } static Executable resolveHintedMethod(Class c, String methodName, MethodKind kind, List hintedSig) { List methods = methodsWithName(c, methodName, kind); final int arity = hintedSig.size(); List filteredMethods = methods.stream() .filter(m -> m.getParameterCount() == arity) .filter(m -> !m.isSynthetic()) // remove bridge/lambda methods .filter(m -> signatureMatches(hintedSig, m)) .collect(Collectors.toList()); if(filteredMethods.size() == 1) return filteredMethods.get(0); else throw paramTagsDontResolveException(c, methodName, hintedSig); } static IllegalArgumentException noMethodWithNameException(Class c, String methodName, MethodKind kind) { return new IllegalArgumentException("Error - no matches found for " + (kind != MethodKind.CTOR ? kind.toString().toLowerCase() + " " : "") + methodDescription(c, methodName)); } static IllegalArgumentException paramTagsDontResolveException(Class c, String methodName, List hintedSig) { IPersistentVector paramTags = PersistentVector.create(hintedSig.stream() .map(tag -> tag == null ? PARAM_TAG_ANY : tag) .collect(Collectors.toList())); return new IllegalArgumentException("Error - param-tags " + paramTags + " insufficient to resolve " + methodDescription(c, methodName)); } public static IllegalArgumentException instanceNoTargetException(QualifiedMethodExpr qmexpr) { return new IllegalArgumentException( "Malformed method expression, expecting (" + qmexpr.c.getName() + "/." + qmexpr.methodName + " target ...)"); } } final static Symbol PARAM_TAG_ANY = Symbol.intern(null, "_"); private static IPersistentVector paramTagsOf(Symbol sym){ Object paramTags = RT.get(RT.meta(sym), RT.PARAM_TAGS_KEY); if(paramTags != null && !(paramTags instanceof IPersistentVector)) throw new IllegalArgumentException("param-tags of symbol " + sym + " should be a vector."); return (IPersistentVector) paramTags; } // calls tagToClass on every element, unless it encounters _ which becomes null private static List tagsToClasses(IPersistentVector paramTags) { if(paramTags == null) return null; List sig = new ArrayList<>(); for (ISeq s = RT.seq(paramTags); s!=null; s = s.next()) { Object t = s.first(); if (t.equals(PARAM_TAG_ANY)) sig.add(null); else sig.add(HostExpr.tagToClass(t)); } return sig; } private static boolean signatureMatches(List sig, Executable method) { Class[] methodSig = method.getParameterTypes(); if(methodSig.length != sig.size()) return false; for (int i = 0; i < methodSig.length; i++) if (sig.get(i) != null && !sig.get(i).equals(methodSig[i])) return false; return true; }; static boolean isStaticMethod(Executable method) { return method instanceof java.lang.reflect.Method && Modifier.isStatic(method.getModifiers()); } static boolean isInstanceMethod(Executable method) { return method instanceof java.lang.reflect.Method && !Modifier.isStatic(method.getModifiers()); } static boolean isConstructor(Executable method) { return method instanceof Constructor; } private static void checkMethodArity(Executable method, int argCount) { if(method.getParameterCount() != argCount) throw new IllegalArgumentException("Invocation of " + methodDescription(method.getDeclaringClass(), (method instanceof Constructor) ? "new" : method.getName()) + " expected " + method.getParameterCount() + " arguments, but received " + argCount); } private static String methodDescription(Class c, String methodName) { boolean isCtor = c != null && methodName.equals("new"); String type = isCtor ? "constructor" : "method"; return type + (isCtor ? "" : " " + methodName) + " in class " + c.getName(); } 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)"); Class jc; 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){ if(targetClass != null && field != null) { target.emit(C.EXPRESSION, objx, gen); gen.visitLineNumber(line, gen.mark()); 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){ if(targetClass != null && field != null) { target.emit(C.EXPRESSION, objx, gen); gen.visitLineNumber(line, gen.mark()); 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.visitLineNumber(line, gen.mark()); 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() { if (jc == null) jc = tag != null ? HostExpr.tagToClass(tag) : field.getType(); return jc; } public Object evalAssign(Expr val) { return Reflector.setInstanceField(target.eval(), fieldName, val.eval()); } public void emitAssign(C context, ObjExpr objx, GeneratorAdapter gen, Expr val){ if(targetClass != null && field != null) { target.emit(C.EXPRESSION, objx, gen); gen.checkCast(getType(targetClass)); val.emit(C.EXPRESSION, objx, gen); gen.visitLineNumber(line, gen.mark()); gen.dupX1(); HostExpr.emitUnboxArg(objx, gen, field.getType()); gen.putField(getType(targetClass), fieldName, Type.getType(field.getType())); } else { target.emit(C.EXPRESSION, objx, gen); gen.push(fieldName); val.emit(C.EXPRESSION, objx, gen); gen.visitLineNumber(line, gen.mark()); 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; Class jc; 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) { for (java.lang.reflect.Method m: c.getMethods()) if (fieldName.equals(m.getName()) && (Modifier.isStatic(m.getModifiers()))) throw new IllegalArgumentException("No matching method " + fieldName + " found taking 0 args for " + c); 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); if (jc == null) jc =tag != null ? HostExpr.tagToClass(tag) : field.getType(); return jc; } public Object evalAssign(Expr val) { return Reflector.setStaticField(c, fieldName, val.eval()); } public void emitAssign(C context, ObjExpr objx, GeneratorAdapter gen, Expr val){ val.emit(C.EXPRESSION, objx, gen); gen.visitLineNumber(line, gen.mark()); 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 FISupport { private static final IPersistentSet AFN_FIS = RT.set(Callable.class, Runnable.class, Comparator.class); private static final IPersistentSet OBJECT_METHODS = RT.set("equals", "toString", "hashCode"); // Return FI method if: // 1) Target is a functional interface and not already implemented by AFn // 2) Target method matches one of our fn invoker methods (0 <= arity <= 10) static java.lang.reflect.Method maybeFIMethod(Class target) { if (target != null && target.isAnnotationPresent(FunctionalInterface.class) && !AFN_FIS.contains(target)) { java.lang.reflect.Method[] methods = target.getMethods(); for (java.lang.reflect.Method method : methods) { if (method.getParameterCount() >= 0 && method.getParameterCount() <= 10 && Modifier.isAbstract(method.getModifiers()) && !OBJECT_METHODS.contains(method.getName())) return method; } } return null; } // Invokers support only long, double, Object params; widen numerics private static Class toInvokerParamType(Class c) { if (c.equals(Byte.TYPE) || c.equals(Short.TYPE) || c.equals(Integer.TYPE) || c.equals(Long.TYPE)) { return Long.TYPE; } else if (c.equals(Float.TYPE) || c.equals(Double.TYPE)) { return Double.TYPE; } return Object.class; } /** * If targetClass is FI and has an adaptable functional method * Find fn invoker method matching adaptable method of FI * Emit bytecode for (expr is emitted): * if(expr instanceof IFn && !(expr instanceof FI)) * invokeDynamic(targetMethod, fnInvokerImplMethod) * Else emit nothing */ static boolean maybeEmitFIAdapter(ObjExpr objx, GeneratorAdapter gen, Expr expr, Class targetClass) { // Optimization: // if(expr instanceof QualifiedMethodExpr) // emitInvokeDynamic(targetMethod, QME method) // DON'T emit expr java.lang.reflect.Method targetMethod = maybeFIMethod(targetClass); if (targetMethod == null) return false; // compute fn invoker method int paramCount = targetMethod.getParameterCount(); Class[] invokerParams = new Class[paramCount + 1]; invokerParams[0] = IFn.class; // close over Ifn as first arg StringBuilder invokeMethodBuilder = new StringBuilder("invoke"); for (int i = 0; i < paramCount; i++) { // FnInvokers only has prims for first 2 args invokerParams[i + 1] = paramCount <= 2 ? toInvokerParamType(targetMethod.getParameterTypes()[i]) : Object.class; invokeMethodBuilder.append(FnInvokers.encodeInvokerType(invokerParams[i + 1])); } // FnInvokers has prim returns for <= 2 params, only Object for higher Class retType = targetMethod.getReturnType(); char invokerReturnCode = FnInvokers.encodeInvokerType(paramCount <= 2 ? retType : Object.class); invokeMethodBuilder.append(invokerReturnCode); String invokerMethodName = invokeMethodBuilder.toString(); // Emit adapter to fn invoker method Type samType = Type.getType(targetClass); Type ifnType = Type.getType(IFn.class); try { java.lang.reflect.Method fnInvokerMethod = FnInvokers.class.getMethod(invokerMethodName, invokerParams); // if not (expr instanceof IFn), go to end label expr.emit(C.EXPRESSION, objx, gen); gen.dup(); gen.instanceOf(ifnType); Label endLabel = gen.newLabel(); gen.ifZCmp(Opcodes.IFEQ, endLabel); // if (expr instanceof FI), go to end label gen.dup(); gen.instanceOf(samType); gen.ifZCmp(Opcodes.IFNE, endLabel); // else adapt fn invoker method as impl for target method emitInvokeDynamicAdapter(gen, targetClass, targetMethod, FnInvokers.class, fnInvokerMethod); // end - checkcast that we have the target FI type gen.mark(endLabel); gen.checkCast(samType); return true; } catch (NoSuchMethodException e) { throw Util.sneakyThrow(e); // should never happen } } // LambdaMetafactory.metafactory() method handle for lambda bootstrap private static final Handle LMF_HANDLE = new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false); /** * Emit an invokedynamic to adapt an implMethod to act as a targetMethod. * * implMethod may be a static method, a constructor, or an instance method. If it is an * instance method, the first argument is the invocation instance. * * The implMethod may close over objects on the stack - these are passed as the initial arguments * to implMethod. The trailing arguments must match the targetMethod arguments. * * See: https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html * @param gen ASM code generator, expects any closed-overs to be on the stack already * @param targetClass The target class * @param targetMethod The target method * @param implClass The impl class * @param implMethod The impl method that will be adapted, takes closed-overs + args of targetMethod */ static void emitInvokeDynamicAdapter(GeneratorAdapter gen, Class targetClass, java.lang.reflect.Method targetMethod, Class implClass, Executable implMethod) { // Impl method - takes closed overs (on stack now) + args (when called) Class[] implParams = implMethod.getParameterTypes(); Class retClass = isConstructor(implMethod) ? implClass : ((java.lang.reflect.Method) implMethod).getReturnType(); int opCode = isConstructor(implMethod) ? Opcodes.H_INVOKESPECIAL : (isStaticMethod(implMethod) ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKEVIRTUAL); Handle implHandle = new Handle(opCode, Type.getInternalName(implClass), implMethod.getName(), MethodType.methodType(retClass, implParams).toMethodDescriptorString(), false); // Adapter interface lambda-style: (closedOver*) -> targetType int implArgCount = implParams.length; if (isInstanceMethod(implMethod)) // instance is first "arg" implArgCount++; List lambdaParams = Arrays.asList(Arrays.copyOfRange(implParams, 0, implArgCount - targetMethod.getParameterCount())); MethodType lambdaSig = MethodType.methodType(targetClass, lambdaParams); Type targetType = Type.getType(targetMethod); gen.visitInvokeDynamicInsn( targetMethod.getName(), lambdaSig.toMethodDescriptorString(), // adapter signature, closedOvers -> target LMF_HANDLE, // bootstrap method handle: LambdaMetaFactory.metafactory() new Object[]{targetType, implHandle, targetType}); // arg types of bootstrap method } } static Class maybeJavaClass(Collection exprs){ Class match = null; try { for (Expr e : exprs) { if (e instanceof ThrowExpr) continue; if (!e.hasJavaClass()) return null; Class c = e.getJavaClass(); if (c == null) return null; 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 if(!FISupport.maybeEmitFIAdapter(objx, gen, e, parameterTypes[i])) { e.emit(C.EXPRESSION, objx, gen); HostExpr.emitUnboxArg(objx, gen, parameterTypes[i]); } } catch(Exception e1) { throw Util.sneakyThrow(e1); } } } } 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 boolean tailPosition; public final java.lang.reflect.Method method; public final Class qualifyingClass; Class jc; final static Method invokeInstanceMethodMethod = Method.getMethod("Object invokeInstanceMethod(Object,String,Object[])"); final static Method invokeInstanceMethodOfClassMethod = Method.getMethod("Object invokeInstanceMethodOfClass(Object,String,String,Object[])"); public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, Class qualifyingClass, String methodName, java.lang.reflect.Method resolvedMethod, IPersistentVector args, boolean tailPosition) { checkMethodArity(resolvedMethod, RT.count(args)); this.source = source; this.line = line; this.column = column; this.args = args; this.methodName = methodName; this.target = target; this.tag = tag; this.tailPosition = tailPosition; this.method = resolvedMethod; this.qualifyingClass = qualifyingClass; } public InstanceMethodExpr(String source, int line, int column, Symbol tag, Expr target, Class qualifyingClass, String methodName, IPersistentVector args, boolean tailPosition) { this.source = source; this.line = line; this.column = column; this.args = args; this.methodName = methodName; this.target = target; this.tag = tag; this.tailPosition = tailPosition; this.qualifyingClass = qualifyingClass; Class contextClass = qualifyingClass != null ? qualifyingClass : (target.hasJavaClass() ? target.getJavaClass() : null); if(contextClass != null) { List methods = Reflector.getMethods(contextClass, 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, contextClass.getName()); } } else { int methodidx = 0; if(methods.size() > 1) { ArrayList params = new ArrayList(); ArrayList 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, contextClass.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); } if(qualifyingClass != null) return Reflector.invokeInstanceMethodOfClass(targetval, qualifyingClass, methodName, argvals); else return Reflector.invokeInstanceMethod(targetval, methodName, argvals); } catch(Throwable e) { if(!(e instanceof CompilerException)) throw new CompilerException(source, line, column, null, CompilerException.PHASE_EXECUTION, e); else throw (CompilerException) e; } } public boolean canEmitPrimitive(){ return method != null && Util.isPrimitive(method.getReturnType()); } public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){ 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); gen.visitLineNumber(line, gen.mark()); if(tailPosition && !objx.canBeDirect) { ObjMethod method = (ObjMethod) METHOD.deref(); method.emitClearThis(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){ 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); gen.visitLineNumber(line, gen.mark()); 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); 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, retClass); } else { target.emit(C.EXPRESSION, objx, gen); if(qualifyingClass != null) gen.push(qualifyingClass.getName()); gen.push(methodName); emitArgsAsArray(args, objx, gen); gen.visitLineNumber(line, gen.mark()); if(context == C.RETURN) { ObjMethod method = (ObjMethod) METHOD.deref(); method.emitClearLocals(gen); } if(qualifyingClass != null) gen.invokeStatic(REFLECTOR_TYPE, invokeInstanceMethodOfClassMethod); else gen.invokeStatic(REFLECTOR_TYPE, invokeInstanceMethodMethod); if(context == C.STATEMENT) gen.pop(); } } public boolean hasJavaClass(){ return method != null || tag != null; } public Class getJavaClass() { if (jc == null) jc = retType((tag!=null)?HostExpr.tagToClass(tag):null, (method!=null)?method.getReturnType():null); return jc; } } 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; public final boolean tailPosition; final static Method forNameMethod = Method.getMethod("Class classForName(String)"); final static Method invokeStaticMethodMethod = Method.getMethod("Object invokeStaticMethod(Class,String,Object[])"); final static Keyword warnOnBoxedKeyword = Keyword.intern("warn-on-boxed"); Class jc; public StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, String methodName, java.lang.reflect.Method preferredMethod, IPersistentVector args, boolean tailPosition) { checkMethodArity(preferredMethod, RT.count(args)); this.c = c; this.methodName = methodName; this.args = args; this.source = source; this.line = line; this.column = column; this.tag = tag; this.tailPosition = tailPosition; this.method = preferredMethod; 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 StaticMethodExpr(String source, int line, int column, Symbol tag, Class c, String methodName, IPersistentVector args, boolean tailPosition) { this.c = c; this.methodName = methodName; this.args = args; this.source = source; this.line = line; this.column = column; this.tag = tag; this.tailPosition = tailPosition; List methods = Reflector.getMethods(c, args.count(), methodName, true); if(methods.isEmpty()) throw new IllegalArgumentException("No matching method " + methodName + " found taking " + args.count() + " args for " + c); int methodidx = 0; if(methods.size() > 1) { ArrayList params = new ArrayList(); ArrayList 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, null, CompilerException.PHASE_EXECUTION, 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 2) throw Util.runtimeException("Too many arguments to throw, throw expects a single Throwable instance"); 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() != null) ? arg.getJavaClass().getName() : "unknown"); } return sb.toString(); } static int getMatchingParams(String methodName, ArrayList paramlists, IPersistentVector argexprs, List 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)"); public NewExpr(Class c, Constructor preferredConstructor, IPersistentVector args, int line, int column) { checkMethodArity(preferredConstructor, RT.count(args)); this.args = args; this.c = c; this.ctor = preferredConstructor; } 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 params = new ArrayList(); ArrayList 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); gen.invokeConstructor(type, new Method("", Type.getConstructorDescriptor(ctor))); } else { gen.push(destubClassName(c.getName())); gen.invokeStatic(RT_TYPE, forNameMethod); MethodExpr.emitArgsAsArray(args, objx, 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 IVECTOR_TYPE = Type.getType(IPersistentVector.class); final static Type TUPLE_TYPE = Type.getType(Tuple.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 = PersistentArrayMap.EMPTY; for(int i=0;i 0 && params[params.length-1] == ISeq.class; break; } else if(argcount > params.length && params.length > 0 && params[params.length-1] == ISeq.class) { method = m; variadic = true; break; } } } if(method == null) return null; Class retClass = method.getReturnType(); Class[] paramClasses = method.getParameterTypes(); Type[] paramTypes = new Type[paramClasses.length]; for(int i = 0;i -1 && argcount >= restOffset)) return tagOf(sig); } return null; } // Callsites are only registered in a function context // In KEYWORD/PROTOCOL_CALLSITES, null indicates "do not register" static boolean shouldRegisterCallsites(Var callSiteVar) { return callSiteVar.deref() != null; } public InvokeExpr(String source, int line, int column, Symbol tag, Expr fexpr, IPersistentVector args, boolean tailPosition) { this.source = source; this.fexpr = fexpr; this.args = args; this.line = line; this.column = column; this.tailPosition = tailPosition; if(fexpr instanceof VarExpr) { Var fvar = ((VarExpr)fexpr).var; Var pvar = (Var)RT.get(fvar.meta(), protocolKey); if(pvar != null && shouldRegisterCallsites(PROTOCOL_CALLSITES)) { 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) { Var v = ((VarExpr) fexpr).var; Object arglists = RT.get(RT.meta(v), arglistsKey); Object sigTag = sigTag(args.count(),v); 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, null, CompilerException.PHASE_EXECUTION, e); else throw (CompilerException) e; } } public void emit(C context, ObjExpr objx, GeneratorAdapter gen){ if(isProtocol) { gen.visitLineNumber(line, gen.mark()); emitProto(context,objx,gen); } else { fexpr.emit(C.EXPRESSION, objx, gen); gen.visitLineNumber(line, gen.mark()); 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) { gen.checkCast(Type.getType(protocolOn)); 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); } gen.visitLineNumber(line, gen.mark()); if(tailPosition && !objx.canBeDirect) { ObjMethod method = (ObjMethod) METHOD.deref(); method.emitClearThis(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() { if (jc == null) jc = HostExpr.tagToClass(tag); return jc; } static public Expr parse(C context, ISeq form) { boolean tailPosition = inTailCall(context); 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(RT.booleanCast(getCompilerOption(directLinkingKey)) && fexpr instanceof VarExpr && context != C.EVAL) { Var v = ((VarExpr)fexpr).var; if(!v.isDynamic() && !RT.booleanCast(RT.get(v.meta(), redefKey, false))) { Symbol formtag = tagOf(form); Object arglists = RT.get(RT.meta(v), arglistsKey); int arity = RT.count(form.next()); Object sigtag = sigTag(arity, v); Object vtag = RT.get(RT.meta(v), RT.TAG_KEY); Expr ret = StaticInvokeExpr .parse(v, RT.next(form), formtag != null ? formtag : sigtag != null ? sigtag : vtag, tailPosition); if(ret != null) { // System.out.println("invoke direct: " + v); return ret; } // System.out.println("NOT direct: " + v); } } 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, ((IObj)RT.listStar(Symbol.intern(".invokePrim"), ((Symbol) form.first()).withMeta(RT.map(RT.TAG_KEY, Symbol.intern(primc))), form.next())).withMeta((IPersistentMap)RT.conj(RT.meta(v), RT.meta(form)))); break; } } } if(fexpr instanceof KeywordExpr && RT.count(form) == 2 && shouldRegisterCallsites(KEYWORD_CALLSITES)) { // 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())); } // Preserving the existing static field syntax that replaces a reference in parens with // the field itself rather than trying to invoke the value in the field. This is // an exception to the uniform Class/member qualification per CLJ-2806 ticket. if(fexpr instanceof StaticFieldExpr) { if(RT.count(args) == 0) return fexpr; else throw new IllegalArgumentException("No matching method " + ((StaticFieldExpr) fexpr).fieldName + " found taking " + RT.count(args) + " args for " + ((StaticFieldExpr) fexpr).c); } if(fexpr instanceof QualifiedMethodExpr) { QualifiedMethodExpr qmexpr = (QualifiedMethodExpr)fexpr; if (qmexpr.kind == QualifiedMethodExpr.MethodKind.INSTANCE && RT.count(args) == 0) throw QualifiedMethodExpr.instanceNoTargetException(qmexpr); return toHostExpr(qmexpr, (String) SOURCE.deref(), lineDeref(), columnDeref(), tagOf(form), tailPosition, args); } // 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, tailPosition); } private static Expr toHostExpr(QualifiedMethodExpr qmexpr, String source, int line, int column, Symbol tag, boolean tailPosition, IPersistentVector args) { if(qmexpr.hintedSig != null) { Executable method = QualifiedMethodExpr.resolveHintedMethod(qmexpr.c, qmexpr.methodName, qmexpr.kind, qmexpr.hintedSig); switch(qmexpr.kind) { case CTOR: return new NewExpr(qmexpr.c, (Constructor) method, args, line, column); case INSTANCE: return new InstanceMethodExpr(source, line, column, tag, (Expr) RT.first(args), qmexpr.c, munge(qmexpr.methodName), (java.lang.reflect.Method) method, PersistentVector.create(RT.next(args)), tailPosition); default: return new StaticMethodExpr(source, line, column, tag, qmexpr.c, munge(qmexpr.methodName), (java.lang.reflect.Method) method, args, tailPosition); } } else { switch(qmexpr.kind) { case CTOR: return new NewExpr(qmexpr.c, args, line, column); case INSTANCE: return new InstanceMethodExpr(source, line, column, tag, (Expr) RT.first(args), qmexpr.c, munge(qmexpr.methodName), PersistentVector.create(RT.next(args)), tailPosition); default: return new StaticMethodExpr(source, line, column, tag, qmexpr.c, munge(qmexpr.methodName), args, tailPosition); } } } } 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; private boolean hasEnclosingMethod; // String superName = null; Class jc; public FnExpr(Object tag){ super(tag); } public boolean hasJavaClass() { return true; } boolean supportsMeta(){ return hasMeta; } public Class getJavaClass() { if (jc == null) jc = tag != null ? HostExpr.tagToClass(tag) : AFunction.class; return jc; } 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)); Keyword retkey = Keyword.intern(null, "rettag"); Object rettag = RT.get(RT.meta(form), retkey); fn.src = form; ObjMethod enclosingMethod = (ObjMethod) METHOD.deref(); fn.hasEnclosingMethod = enclosingMethod != null; 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); 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 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; 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; boolean usesThis = false; for(ISeq s = RT.next(form); s != null; s = RT.next(s)) { FnMethod f = FnMethod.parse(fn, (ISeq) RT.first(s), rettag); if(f.usesThis) { // System.out.println(fn.name + " use this"); usesThis = true; } 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"); } fn.canBeDirect = !fn.hasEnclosingMethod && fn.closes.count() == 0 && !usesThis; 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); if(fn.canBeDirect){ for(FnMethod fm : (Collection)methods) { if(fm.locals != null) { for(LocalBinding lb : (Collection)RT.keys(fm.locals)) { if(lb.isArg) lb.idx -= 1; } } } } 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).without(retkey); 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 (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; IPersistentSet usedConstants = PersistentHashSet.EMPTY; int constantsID; int altCtorDrops = 0; IPersistentVector keywordCallsites; IPersistentVector protocolCallsites; //IPersistentSet varCallsites; boolean onceOnly = false; Object src; IPersistentMap opts = PersistentHashMap.EMPTY; final static Method voidctor = Method.getMethod("void ()"); protected IPersistentMap classMeta; protected boolean canBeDirect; 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 = classWriter(); // 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_8, 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); // for(int i=0;i", 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", Type.VOID_TYPE, altCtorTypes); ctorgen = new GeneratorAdapter(ACC_PUBLIC, alt, null, null, cv); ctorgen.visitCode(); ctorgen.loadThis(); ctorgen.loadArgs(); ctorgen.visitInsn(Opcodes.ACONST_NULL); //__meta ctorgen.visitInsn(Opcodes.ACONST_NULL); //__extmap ctorgen.visitInsn(Opcodes.ICONST_0); //__hash ctorgen.visitInsn(Opcodes.ICONST_0); //__hasheq ctorgen.invokeConstructor(objtype, new Method("", Type.VOID_TYPE, ctorTypes)); ctorgen.returnValue(); ctorgen.endMethod(); // alt ctor no __hash, __hasheq altCtorTypes = new Type[ctorTypes.length-2]; for(int i=0;i", Type.VOID_TYPE, altCtorTypes); ctorgen = new GeneratorAdapter(ACC_PUBLIC, alt, null, null, cv); ctorgen.visitCode(); ctorgen.loadThis(); ctorgen.loadArgs(); ctorgen.visitInsn(Opcodes.ICONST_0); //__hash ctorgen.visitInsn(Opcodes.ICONST_0); //__hasheq ctorgen.invokeConstructor(objtype, new Method("", 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", 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("", 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("", Type.VOID_TYPE, ctorTypes)); gen.returnValue(); gen.endMethod(); } emitStatics(cv); emitMethods(cv); //static fields for constants for(int i = 0; i < constants.count(); i++) { if(usedConstants.contains(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); } //static init for constants, keywords and vars GeneratorAdapter clinitgen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, Method.getMethod("void ()"), 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(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(RT_TYPE, Method.getMethod("Class classForName(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("", 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, munge(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) 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) { IPersistentVector args = (IPersistentVector) value; if(args.count() <= Tuple.MAX_SIZE) { for(int i = 0; i < args.count(); i++) { emitValue(args.nth(i), gen); } gen.invokeStatic(TUPLE_TYPE, createTupleMethods[args.count()]); } else { 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++) { if(usedConstants.contains(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().getDeclaredConstructor().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("", Type.VOID_TYPE, ctorTypes())); } if(context == C.STATEMENT) gen.pop(); } public boolean hasJavaClass() { return true; } Class jc; public Class getJavaClass() { if (jc == null) jc = (compiledClass != null) ? compiledClass : (tag != null) ? HostExpr.tagToClass(tag) : IFn.class; return jc; } 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 = canBeDirect ?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 = canBeDirect ?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){ usedConstants = (IPersistentSet) usedConstants.cons(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 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, Object rettag) { //([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 = new PathNode(PATHTYPE.PATH,null); method.clearRoot = pnode; 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_RETURN_CONTEXT, RT.T )); method.prim = primInterface(parms); if(method.prim != null) method.prim = method.prim.replace('.', '/'); if(rettag instanceof String) rettag = Symbol.intern(null, (String) rettag); if(!(rettag instanceof Symbol)) rettag = null; if(rettag != null) { String retstr = ((Symbol)rettag).getName(); if(!(retstr.equals("long") || retstr.equals("double"))) rettag = null; } method.retClass = tagClass(tagOf(parms)!=null?tagOf(parms):rettag); if(method.retClass.isPrimitive()){ if(!(method.retClass == double.class || method.retClass == long.class)) throw new IllegalArgumentException("Only long and double primitives are supported"); } else method.retClass = Object.class; //register 'this' as local 0 //registerLocal(THISFN, null, null); // if(!canBeDirect) // { if(objx.thisName != null) registerLocal(Symbol.intern(objx.thisName), null, null,false); else getAndIncLocalNum(); // } PSTATE state = PSTATE.REQ; PersistentVector argLocals = PersistentVector.EMPTY; ArrayList argtypes = new ArrayList(); ArrayList 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(canBeDirect) // 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() && !canBeDirect) // { // 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(canBeDirect) method.argtypes = argtypes.toArray(new Type[argtypes.size()]); method.argclasses = argclasses.toArray(new Class[argtypes.size()]); if(method.prim != null) { 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(fn.canBeDirect) { // System.out.println("emit static: " + fn.name); doEmitStatic(fn, cv); } else if(prim != null) { // System.out.println("emit prim: " + fn.name); doEmitPrim(fn, cv); } else { // System.out.println("emit normal: " + fn.name); doEmit(fn,cv); } } public void doEmitStatic(ObjExpr fn, ClassVisitor cv){ // System.out.println("emit static:" + fn.name); Type returnType = Type.getType(retClass); // if (retClass == double.class || retClass == long.class) // returnType = getReturnType(); // else returnType = OBJECT_TYPE; Method ms = new Method("invokeStatic", returnType, 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]); if(!argclasses[i].isPrimitive()) { gen.visitInsn(Opcodes.ACONST_NULL); gen.storeArg(i); } } Label callLabel = gen.mark(); gen.visitLineNumber(line, callLabel); gen.invokeStatic(objx.objtype, ms); if(Type.LONG_TYPE.equals(returnType) || Type.DOUBLE_TYPE.equals(returnType)) { gen.valueOf(returnType); } else { gen.box(returnType); } gen.returnValue(); //gen.visitMaxs(1, 1); gen.endMethod(); //generate primInvoke if prim if(prim != null) { if (retClass == double.class || retClass == long.class) returnType = getReturnType(); else returnType = OBJECT_TYPE; Method pm = new Method("invokePrim", returnType, argtypes); gen = new GeneratorAdapter(ACC_PUBLIC + ACC_FINAL, pm, null, //todo don't hardwire this EXCEPTION_TYPES, cv); gen.visitCode(); for(int i = 0; i < argtypes.length; i++) { gen.loadArg(i); if(!argclasses[i].isPrimitive()) { gen.visitInsn(Opcodes.ACONST_NULL); gen.storeArg(i); } } gen.invokeStatic(objx.objtype, ms); 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); Type targetReturnType = getReturnType(); if(Type.LONG_TYPE.equals(targetReturnType) || Type.DOUBLE_TYPE.equals(targetReturnType)) { gen.valueOf(targetReturnType); } else { gen.box(targetReturnType); } 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;ilocalbinding IPersistentMap locals = null; //num->localbinding IPersistentMap indexlocals = null; Expr body = null; ObjExpr objx; PersistentVector argLocals; int maxLocal = 0; int line; int column; boolean usesThis = false; PersistentHashSet localsUsedInCatchFinally = PersistentHashSet.EMPTY; protected IPersistentMap methodMeta; PathNode clearRoot; 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 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; } } ObjMethod method = ((ObjMethod) METHOD.deref()); boolean closedOver = method.objx.closes.containsKey(b); if(clearRoot == b.clearPathRoot || (closedOver && clearRoot == method.clearRoot)) { 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(); } Class jc; public Class getJavaClass() { if (jc == null) { if(tag != null) jc = HostExpr.tagToClass(tag); else jc = b.getJavaClass(); } return jc; } } 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), false); 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), false); else if(maybePrimitiveType(init) == float.class) init = new StaticMethodExpr("", 0, 0, null, RT.class, "doubleCast", RT.vector(init), false); } //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) { Object methodReturnContext = context == C.RETURN ? METHOD_RETURN_CONTEXT.deref() : null; Var.pushThreadBindings( RT.map(CLEAR_PATH, clearpath, CLEAR_ROOT, clearroot, NO_RECUR, null, METHOD_RETURN_CONTEXT, methodReturnContext)); } 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 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 { Class bindingClass = HostExpr.maybeClass(bi.binding.tag, true); if(!FISupport.maybeEmitFIAdapter(objx, gen, bi.init, bindingClass)) bi.init.emit(C.EXPRESSION, objx, gen); if (!bi.binding.used && bi.binding.canBeCleared) gen.pop(); else 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.canBeDirect ?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(); // In :once fn, recur to head invalidates :once ObjMethod method = (ObjMethod)METHOD.deref(); if(method.objx.onceOnly && method.clearRoot == CLEAR_ROOT.deref()) method.objx.onceOnly = false; 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) { Object mform = form; form = RT.seq(form); if(form == null) form = PersistentList.EMPTY; form = ((IObj)form).withMeta(RT.meta(mform)); } 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 && !(form instanceof IRecord) && !(form instanceof IType) && ((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 implements IExceptionInfo{ final public String source; final public int line; final public Object data; // Error keys final static public String ERR_NS = "clojure.error"; final static public Keyword ERR_SOURCE = Keyword.intern(ERR_NS, "source"); final static public Keyword ERR_LINE = Keyword.intern(ERR_NS, "line"); final static public Keyword ERR_COLUMN = Keyword.intern(ERR_NS, "column"); final static public Keyword ERR_PHASE = Keyword.intern(ERR_NS, "phase"); final static public Keyword ERR_SYMBOL = Keyword.intern(ERR_NS, "symbol"); // Compile error phases final static public Keyword PHASE_READ = Keyword.intern(null, "read-source"); final static public Keyword PHASE_MACRO_SYNTAX_CHECK = Keyword.intern(null, "macro-syntax-check"); final static public Keyword PHASE_MACROEXPANSION = Keyword.intern(null, "macroexpansion"); final static public Keyword PHASE_COMPILE_SYNTAX_CHECK = Keyword.intern(null, "compile-syntax-check"); final static public Keyword PHASE_COMPILATION = Keyword.intern(null, "compilation"); final static public Keyword PHASE_EXECUTION = Keyword.intern(null, "execution"); final static public Keyword SPEC_PROBLEMS = Keyword.intern("clojure.spec.alpha", "problems"); // Class compile exception public CompilerException(String source, int line, int column, Throwable cause){ this(source, line, column, null, cause); } public CompilerException(String source, int line, int column, Symbol sym, Throwable cause){ this(source, line, column, sym, PHASE_COMPILE_SYNTAX_CHECK, cause); } public CompilerException(String source, int line, int column, Symbol sym, Keyword phase, Throwable cause){ super(makeMsg(source, line, column, sym, phase, cause), cause); this.source = source; this.line = line; Associative m = RT.map(ERR_PHASE, phase, ERR_LINE, line, ERR_COLUMN, column); if(source != null) m = RT.assoc(m, ERR_SOURCE, source); if(sym != null) m = RT.assoc(m, ERR_SYMBOL, sym); this.data = m; } public IPersistentMap getData(){ return (IPersistentMap)data; } private static String verb(Keyword phase) { if(PHASE_READ.equals(phase)){ return "reading source"; } else if(PHASE_COMPILE_SYNTAX_CHECK.equals(phase) || PHASE_COMPILATION.equals(phase)){ return "compiling"; } else { return "macroexpanding"; } } public static String makeMsg(String source, int line, int column, Symbol sym, Keyword phase, Throwable cause){ return (PHASE_MACROEXPANSION.equals(phase) ? "Unexpected error " : "Syntax error ") + verb(phase) + " " + (sym != null ? sym + " " : "") + "at (" + (source != null && !source.equals("NO_SOURCE_PATH") ? (source + ":") : "") + line + ":" + column + ")."; } public String toString(){ Throwable cause = getCause(); if(cause != null) { if (cause instanceof IExceptionInfo) { IPersistentMap data = (IPersistentMap)((IExceptionInfo)cause).getData(); if(PHASE_MACRO_SYNTAX_CHECK.equals(data.valAt(ERR_PHASE)) && data.valAt(SPEC_PROBLEMS) != null) { return String.format("%s", getMessage()); } else { return String.format("%s%n%s", getMessage(), cause.getMessage()); } } } 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; } private static volatile Var MACRO_CHECK = null; private static volatile boolean MACRO_CHECK_LOADING = false; private static final Object MACRO_CHECK_LOCK = new Object(); private static Var ensureMacroCheck() throws ClassNotFoundException, IOException { if(MACRO_CHECK == null) { synchronized(MACRO_CHECK_LOCK) { if(MACRO_CHECK == null) { MACRO_CHECK_LOADING = true; RT.load("clojure/spec/alpha"); RT.load("clojure/core/specs/alpha"); MACRO_CHECK = Var.find(Symbol.intern("clojure.spec.alpha", "macroexpand-check")); MACRO_CHECK_LOADING = false; } } } return MACRO_CHECK; } public static void checkSpecs(Var v, ISeq form) { if(RT.CHECK_SPECS && !MACRO_CHECK_LOADING) { try { ensureMacroCheck().applyTo(RT.cons(v, RT.list(form.next()))); } catch(Exception e) { throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), v.toSymbol(), CompilerException.PHASE_MACRO_SYNTAX_CHECK, e); } } } 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) { checkSpecs(v, form); try { ISeq args = RT.cons(form, RT.cons(Compiler.LOCAL_ENV.get(), form.next())); return v.applyTo(args); } catch(ArityException e) { // hide the 2 extra params for a macro if(e.name.equals(munge(v.ns.name.name) + "$" + munge(v.sym.name))) { throw new ArityException(e.actual - 2, e.name); } else { throw e; } } catch(CompilerException e) { throw e; } catch(IllegalArgumentException | IllegalStateException | ExceptionInfo e) { throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), (op instanceof Symbol ? (Symbol) op : null), CompilerException.PHASE_MACRO_SYNTAX_CHECK, e); } catch(Throwable e) { throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), (op instanceof Symbol ? (Symbol) op : null), (e.getClass().equals(Exception.class) ? CompilerException.PHASE_MACRO_SYNTAX_CHECK : CompilerException.PHASE_MACROEXPANSION), e); } } else { if(op instanceof Symbol) { Symbol sym = (Symbol) op; String sname = sym.name; //(.substring s 2 5) => (. s substring 2 5) // ns == null ensures that Class/.instanceMethod isn't expanded to . form if(sym.name.charAt(0) == '.' && sym.ns == null) { 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 { //(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)); Object op = null; try { Object me = macroexpand1(form); if(me != form) return analyze(context, me, name); op = RT.first(form); if(op == null) throw new IllegalArgumentException("Can't call nil, form: " + form); 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) { Symbol s = (op != null && op instanceof Symbol) ? (Symbol)op : null; if(!(e instanceof CompilerException)) { throw new CompilerException((String) SOURCE_PATH.deref(), lineDeref(), columnDeref(), s, e); } else throw (CompilerException) e; } finally { Var.popThreadBindings(); } } // DEPRECATED 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 { IPersistentMap meta = RT.meta(form); Object line = (meta != null ? meta.valAt(RT.LINE_KEY, LINE.deref()) : LINE.deref()); Object column = (meta != null ? meta.valAt(RT.COLUMN_KEY, COLUMN.deref()) : COLUMN.deref()); IPersistentMap bindings = RT.mapUniqueKeys(LINE, line, COLUMN, column); if(meta != null) { Object eval_file = meta.valAt(RT.EVAL_FILE_KEY); if(eval_file != null) { bindings = bindings.assoc(SOURCE_PATH, eval_file); try { bindings = bindings.assoc(SOURCE, new File((String)eval_file).getName()); } catch (Throwable t) { } } } Var.pushThreadBindings(bindings); 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 ids = (IdentityHashMap) 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){ IPersistentVector keywordCallsites = (IPersistentVector) KEYWORD_CALLSITES.deref(); keywordCallsites = keywordCallsites.cons(keyword); KEYWORD_CALLSITES.set(keywordCallsites); return keywordCallsites.count()-1; } private static int registerProtocolCallsite(Var v){ 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 && !Util.isPosDigit(sym.name)) { Symbol nsSym = Symbol.intern(sym.ns); Class c = HostExpr.maybeClass(nsSym, false); if(c != null) { if(Reflector.getField(c, sym.name, true) != null) { List maybeOverloads = QualifiedMethodExpr.methodOverloads(c, sym.name, QualifiedMethodExpr.MethodKind.STATIC); if(maybeOverloads.isEmpty()) return new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag); else return new QualifiedMethodExpr(c, sym, new StaticFieldExpr(lineDeref(), columnDeref(), c, sym.name, tag)); } else return new QualifiedMethodExpr(c, sym); } } } //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) { Class ac = HostExpr.maybeArrayClass(sym); if(ac != null) return ac; 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 HostExpr.maybeArrayClass(sym); 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) == '[') { try { return RT.classForName(sym.name); } catch (Exception e) { if (e instanceof ClassNotFoundException) return null; else return Util.sneakyThrow(e); } } 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) { LocalBinding lb = (LocalBinding) RT.get(method.locals, b); if(lb == null) { method.objx.closes = (IPersistentMap) RT.assoc(method.objx.closes, b, b); closeOver(b, method.parent); } else { if(lb.idx == 0) method.usesThis = true; 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(); if(b.idx == 0) method.usesThis = true; 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"); } static void consumeWhitespaces(LineNumberingPushbackReader pushbackReader) { int ch = LispReader.read1(pushbackReader); while(LispReader.isWhitespace(ch)) ch = LispReader.read1(pushbackReader); LispReader.unread(pushbackReader, ch); } private static final Object OPTS_COND_ALLOWED = RT.mapUniqueKeys(LispReader.OPT_READ_COND, LispReader.COND_ALLOW); private static Object readerOpts(String sourceName) { if(sourceName != null && sourceName.endsWith(".cljc")) return OPTS_COND_ALLOWED; else return null; } 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); consumeWhitespaces(pushbackReader); 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() )); Object readerOpts = readerOpts(sourceName); try { for(Object r = LispReader.read(pushbackReader, false, EOF, false, readerOpts); r != EOF; r = LispReader.read(pushbackReader, false, EOF, false, readerOpts)) { consumeWhitespaces(pushbackReader); 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, null, CompilerException.PHASE_READ, e.getCause()); } catch(Throwable e) { if(!(e instanceof CompilerException)) throw new CompilerException(sourcePath, (Integer) LINE_BEFORE.deref(), (Integer) COLUMN_BEFORE.deref(), null, CompilerException.PHASE_EXECUTION, 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(); } 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(), KEYWORD_CALLSITES, null, PROTOCOL_CALLSITES, null, //VAR_CALLSITES, null, 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 = classWriter(); ClassVisitor cv = cw; cv.visit(V1_8, 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(); Object readerOpts = readerOpts(sourceName); for(Object r = LispReader.read(pushbackReader, false, EOF, false, readerOpts); r != EOF; r = LispReader.read(pushbackReader, false, EOF, false, readerOpts)) { 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++) { if(objx.usedConstants.contains(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 ()"), 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 mmap; Map> 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()).getName(); 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, opts); 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, null); IPersistentMap fmeta = RT.meta(frm); if(fmeta != null) fmeta = fmeta.without(RT.LINE_KEY).without(RT.COLUMN_KEY).without(RT.FILE_KEY); if (RT.count(fmeta) > 0) return new MetaExpr(ret, MapExpr.parse(context == C.EVAL ? context : C.EXPRESSION, fmeta)); else return ret; } } static ObjExpr build(IPersistentVector interfaceSyms, IPersistentVector fieldSyms, Symbol thisSym, String tagName, Symbol className, Symbol typeTag, ISeq methodForms, Object frm, IPersistentMap opts) { 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); ret.opts = opts; 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= 0 && (((Symbol)fieldSyms.nth(i)).name.equals("__meta") || ((Symbol)fieldSyms.nth(i)).name.equals("__extmap") || ((Symbol)fieldSyms.nth(i)).name.equals("__hash") || ((Symbol)fieldSyms.nth(i)).name.equals("__hasheq") );--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 = classWriter(); ClassVisitor cv = cw; cv.visit(V1_8, 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("", 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", Type.VOID_TYPE, altCtorTypes); ctorgen = new GeneratorAdapter(ACC_PUBLIC, alt, null, null, cv); ctorgen.visitCode(); ctorgen.loadThis(); ctorgen.loadArgs(); ctorgen.visitInsn(Opcodes.ACONST_NULL); //__meta ctorgen.visitInsn(Opcodes.ACONST_NULL); //__extmap ctorgen.visitInsn(Opcodes.ICONST_0); //__hash ctorgen.visitInsn(Opcodes.ICONST_0); //__hasheq ctorgen.invokeConstructor(Type.getObjectType(COMPILE_STUB_PREFIX + "/" + ret.internalName), new Method("", Type.VOID_TYPE, ctorTypes)); ctorgen.returnValue(); ctorgen.endMethod(); // alt ctor no __hash, __hasheq altCtorTypes = new Type[ctorTypes.length-2]; for(int i=0;i", Type.VOID_TYPE, altCtorTypes); ctorgen = new GeneratorAdapter(ACC_PUBLIC, alt, null, null, cv); ctorgen.visitCode(); ctorgen.loadThis(); ctorgen.loadArgs(); ctorgen.visitInsn(Opcodes.ICONST_0); //__hash ctorgen.visitInsn(Opcodes.ICONST_0); //__hasheq ctorgen.invokeConstructor(Type.getObjectType(COMPILE_STUB_PREFIX + "/" + ret.internalName), new Method("", 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 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("", 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); //__meta mv.visitVarInsn(ALOAD, 0); //__extmap mv.visitMethodInsn(INVOKESTATIC, "clojure/lang/RT", "seqOrElse", "(Ljava/lang/Object;)Ljava/lang/Object;"); mv.visitInsn(ICONST_0); //__hash mv.visitInsn(ICONST_0); //__hasheq mv.visitMethodInsn(INVOKESPECIAL, className, "", 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> 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 mm = new HashMap(); Map> covariants = new HashMap>(); 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 cvs = covariants.get(mk); if(cvs == null) { cvs = new HashSet(); 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, null); method.clearRoot = pnode; 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_RETURN_CONTEXT, RT.T )); //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 tests; public final HashMap thens; public final Keyword switchType; public final Keyword testType; public final Set 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 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 tests,HashMap thens, Keyword switchType, Keyword testType, Set 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 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 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 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))); IPersistentVector args = LazilyPersistentVector.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 tests = new TreeMap(); HashMap 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;} static public ClassWriter classWriter() { return new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES) { protected String getCommonSuperClass (final String type1, final String type2) { return "java/lang/Object"; // if (!(type1.equals("java/lang/Object") || type2.equals("java/lang/Object"))) { // RT.errPrintWriter() // .format("stack map frame \"%s\" and \"%s\" on %s:%d:%d \n", // type1, type2, // SOURCE_PATH.deref(), LINE.deref(), COLUMN.deref()); // } } }; } }