summaryrefslogtreecommitdiff
path: root/src/jvm/clojure
diff options
context:
space:
mode:
authorfogus <mefogus@gmail.com>2011-05-10 08:18:20 -0400
committerStuart Halloway <stu@thinkrelevance.com>2011-05-13 13:18:28 -0400
commitac1e8ad9f182dc2e8a5254f3e4b7b77c0258353d (patch)
tree12c02f2ca444877c0a404372104b4e9b03411742 /src/jvm/clojure
parent914b77f25773a646d4706e179d427fce7bb745af (diff)
Changes to support defrecord and deftype literals. See CLJ-374
Signed-off-by: Stuart Halloway <stu@thinkrelevance.com>
Diffstat (limited to 'src/jvm/clojure')
-rw-r--r--src/jvm/clojure/lang/Compiler.java148
-rw-r--r--src/jvm/clojure/lang/IRecord.java14
-rw-r--r--src/jvm/clojure/lang/LispReader.java138
-rw-r--r--src/jvm/clojure/lang/RT.java4
-rw-r--r--src/jvm/clojure/lang/Reflector.java37
5 files changed, 315 insertions, 26 deletions
diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index bbaa0a12..e4ef205e 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -15,8 +15,15 @@ package clojure.lang;
//*
import clojure.asm.*;
-import clojure.asm.commons.Method;
import clojure.asm.commons.GeneratorAdapter;
+import clojure.asm.commons.Method;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.regex.Pattern;
+
//*/
/*
@@ -27,13 +34,6 @@ import org.objectweb.asm.util.TraceClassVisitor;
import org.objectweb.asm.util.CheckClassAdapter;
//*/
-import java.io.*;
-import java.lang.reflect.InvocationTargetException;
-import java.util.*;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Modifier;
-import java.util.regex.Pattern;
-
public class Compiler implements Opcodes{
static final Symbol DEF = Symbol.intern("def");
@@ -1723,6 +1723,7 @@ static class ConstantExpr extends LiteralExpr{
public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
objx.emitConstant(gen, id);
+
if(context == C.STATEMENT)
{
gen.pop();
@@ -3692,6 +3693,9 @@ static public class ObjExpr implements Expr{
//symbol->lb
IPersistentMap fields = null;
+ //hinted fields
+ IPersistentVector hintedFields = PersistentVector.EMPTY;
+
//Keyword->KeywordExpr
IPersistentMap keywords = PersistentHashMap.EMPTY;
IPersistentMap vars = PersistentHashMap.EMPTY;
@@ -4052,7 +4056,7 @@ static public class ObjExpr implements Expr{
if(supportsMeta())
{
- //ctor that takes closed-overs but not meta
+ //ctor that takes closed-overs but not meta
Type[] ctorTypes = ctorTypes();
Type[] noMetaCtorTypes = new Type[ctorTypes.length-1];
for(int i=1;i<ctorTypes.length;i++)
@@ -4119,7 +4123,8 @@ static public class ObjExpr implements Expr{
gen.returnValue();
gen.endMethod();
}
-
+
+ emitStatics(cv);
emitMethods(cv);
if(keywordCallsites.count() > 0)
@@ -4182,6 +4187,9 @@ static public class ObjExpr implements Expr{
}
}
+ protected void emitStatics(ClassVisitor gen){
+ }
+
protected void emitMethods(ClassVisitor gen){
}
@@ -4286,6 +4294,19 @@ static public class ObjExpr implements Expr{
gen.push(var.sym.toString());
gen.invokeStatic(RT_TYPE, Method.getMethod("clojure.lang.Var var(String,String)"));
}
+ else if(value instanceof IRecord)
+ {
+ Method createMethod = Method.getMethod(value.getClass().getName() + " create(clojure.lang.IPersistentMap)");
+ List entries = new ArrayList();
+ for(Map.Entry entry : (Set<Map.Entry>) ((Map) value).entrySet())
+ {
+ entries.add(entry.getKey());
+ entries.add(entry.getValue());
+ }
+ emitListAsObjectArray(entries, gen);
+ gen.invokeStatic(RT_TYPE, Method.getMethod("clojure.lang.IPersistentMap map(Object[])"));
+ gen.invokeStatic(getType(value.getClass()), createMethod);
+ }
else if(value instanceof IPersistentMap)
{
List entries = new ArrayList();
@@ -6111,6 +6132,8 @@ private static Expr analyze(C context, Object form, String name) {
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 IPersistentMap)
return MapExpr.parse(context, (IPersistentMap) form);
else if(form instanceof IPersistentSet)
@@ -7174,6 +7197,8 @@ static public class NewInstanceExpr extends ObjExpr{
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)*
@@ -7302,6 +7327,82 @@ static public class NewInstanceExpr extends ObjExpr{
return c.getName().replace('.', '/');
}
+ protected void emitStatics(ClassVisitor cv) {
+ if(this.isDeftype())
+ {
+ //getBasis()
+ Method meth = Method.getMethod("clojure.lang.IPersistentVector getBasis()");
+ GeneratorAdapter gen = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC,
+ meth,
+ null,
+ null,
+ cv);
+ emitValue(hintedFields, gen);
+ gen.returnValue();
+ gen.endMethod();
+
+ if (this.isDeftype() && this.fields.count() > this.hintedFields.count())
+ {
+ //create(IPersistentMap)
+ String className = name.replace('.', '/');
+ int i = 1;
+ int fieldCount = hintedFields.count();
+
+ MethodVisitor mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, "create", "(Lclojure/lang/IPersistentMap;)L"+className+";", null, null);
+ mv.visitCode();
+
+ for(ISeq s = RT.seq(hintedFields); s!=null; s=s.next(), i++)
+ {
+ String bName = ((Symbol)s.first()).name;
+ Class k = tagClass(tagOf(s.first()));
+
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitLdcInsn(bName);
+ mv.visitMethodInsn(INVOKESTATIC, "clojure/lang/Keyword", "intern", "(Ljava/lang/String;)Lclojure/lang/Keyword;");
+ mv.visitInsn(ACONST_NULL);
+ mv.visitMethodInsn(INVOKEINTERFACE, "clojure/lang/IPersistentMap", "valAt", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+ if(k.isPrimitive())
+ {
+ mv.visitTypeInsn(CHECKCAST, Type.getType(boxClass(k)).getInternalName());
+ }
+ mv.visitVarInsn(ASTORE, i);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitLdcInsn(bName);
+ mv.visitMethodInsn(INVOKESTATIC, "clojure/lang/Keyword", "intern", "(Ljava/lang/String;)Lclojure/lang/Keyword;");
+ mv.visitMethodInsn(INVOKEINTERFACE, "clojure/lang/IPersistentMap", "without", "(Ljava/lang/Object;)Lclojure/lang/IPersistentMap;");
+ mv.visitVarInsn(ASTORE, 0);
+ }
+
+ mv.visitTypeInsn(Opcodes.NEW, className);
+ mv.visitInsn(DUP);
+
+ Method ctor = new Method("<init>", Type.VOID_TYPE, ctorTypes());
+
+ if(hintedFields.count() > 0)
+ for(i=1; i<=fieldCount; i++)
+ {
+ mv.visitVarInsn(ALOAD, i);
+ Class k = tagClass(tagOf(hintedFields.nth(i-1)));
+ if(k.isPrimitive())
+ {
+ String b = Type.getType(boxClass(k)).getInternalName();
+ String p = Type.getType(k).getDescriptor();
+ String n = k.getName();
+
+ mv.visitMethodInsn(INVOKEVIRTUAL, b, n+"Value", "()"+p);
+ }
+ }
+
+ mv.visitInsn(ACONST_NULL);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESTATIC, "clojure/lang/RT", "seqOrElse", "(Ljava/lang/Object;)Ljava/lang/Object;");
+ mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", ctor.getDescriptor());
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(4+fieldCount, 1+fieldCount);
+ mv.visitEnd();
+ }
+ }
+ }
protected void emitMethods(ClassVisitor cv){
for(ISeq s = RT.seq(methods); s != null; s = s.next())
@@ -7695,6 +7796,33 @@ public static class NewInstanceMethod extends ObjMethod{
return c.isPrimitive()?c:Object.class;
}
+ static Class boxClass(Class p) {
+ if(!p.isPrimitive())
+ return p;
+
+ Class c = null;
+ Type t = Type.getType(p);
+
+ if(t == Type.INT_TYPE)
+ c = Integer.class;
+ else if(t == Type.LONG_TYPE)
+ c = Long.class;
+ else if(t == Type.FLOAT_TYPE)
+ c = Float.class;
+ else if(t == Type.DOUBLE_TYPE)
+ c = Double.class;
+ else if(t == Type.CHAR_TYPE)
+ c = Character.class;
+ else if(t == Type.SHORT_TYPE)
+ c = Short.class;
+ else if(t == Type.BYTE_TYPE)
+ c = Byte.class;
+ else if(t == Type.BOOLEAN_TYPE)
+ c = Boolean.class;
+
+ return c;
+ }
+
static public class MethodParamExpr implements Expr, MaybePrimitiveExpr{
final Class c;
diff --git a/src/jvm/clojure/lang/IRecord.java b/src/jvm/clojure/lang/IRecord.java
new file mode 100644
index 00000000..343da6c1
--- /dev/null
+++ b/src/jvm/clojure/lang/IRecord.java
@@ -0,0 +1,14 @@
+/**
+ * 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.
+ **/
+
+package clojure.lang;
+
+public interface IRecord {
+}
diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java
index 17b73e13..07ed6487 100644
--- a/src/jvm/clojure/lang/LispReader.java
+++ b/src/jvm/clojure/lang/LispReader.java
@@ -10,15 +10,31 @@
package clojure.lang;
-import java.io.*;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
+import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.Reader;
+import java.lang.Character;
+import java.lang.Class;
+import java.lang.Exception;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.Integer;
+import java.lang.Number;
+import java.lang.NumberFormatException;
+import java.lang.Object;
+import java.lang.RuntimeException;
+import java.lang.String;
+import java.lang.StringBuilder;
+import java.lang.Throwable;
+import java.lang.UnsupportedOperationException;
+import java.lang.reflect.Constructor;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.math.BigInteger;
-import java.math.BigDecimal;
-import java.lang.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class LispReader{
@@ -61,6 +77,7 @@ static final Symbol CLOJURE_SLASH = Symbol.intern("clojure.core","/");
static Var GENSYM_ENV = Var.create(null).setDynamic();
//sorted-map num->gensymbol
static Var ARG_ENV = Var.create(null).setDynamic();
+static IFn ctorReader = new CtorReader();
static
{
@@ -587,8 +604,17 @@ public static class DispatchReader extends AFn{
if(ch == -1)
throw Util.runtimeException("EOF while reading character");
IFn fn = dispatchMacros[ch];
- if(fn == null)
- throw Util.runtimeException(String.format("No dispatch macro for: %c", (char) ch));
+
+ // Try the ctor reader first
+ if(fn == null) {
+ unread((PushbackReader) reader, ch);
+ Object result = ctorReader.invoke(reader, ch);
+
+ if(result != null)
+ return result;
+ else
+ throw Util.runtimeException(String.format("No dispatch macro for: %c", (char) ch));
+ }
return fn.invoke(reader, ch);
}
}
@@ -946,6 +972,7 @@ public static class ListReader extends AFn{
}
+/*
static class CtorReader extends AFn{
static final Symbol cls = Symbol.intern("class");
@@ -974,8 +1001,8 @@ static class CtorReader extends AFn{
return Reflector.invokeConstructor(RT.classForName(s.name), args);
}
}
-
}
+*/
public static class EvalReader extends AFn{
public Object invoke(Object reader, Object eq) {
@@ -1115,6 +1142,99 @@ public static List readDelimitedList(char delim, PushbackReader r, boolean isRec
return a;
}
+public static class CtorReader extends AFn{
+ public Object invoke(Object reader, Object firstChar){
+ PushbackReader r = (PushbackReader) reader;
+
+ Object recordName = read(r, true, null, false);
+ Class recordClass = RT.classForName(recordName.toString());
+ int ch = read1(r);
+ char endch;
+ boolean shortForm = true;
+
+ // A defrecord ctor can take two forms. Check for map->R version first.
+ if(ch == '{')
+ {
+ endch = '}';
+ shortForm = false;
+ }
+ else if (ch == '[')
+ endch = ']';
+ else
+ throw Util.runtimeException("Unreadable constructor form starting with \"#" + recordName + (char) ch + "\"");
+
+ Object[] recordEntries = readDelimitedList(endch, r, true).toArray();
+ Object ret = null;
+ Constructor[] allctors = ((Class)recordClass).getConstructors();
+
+ if(shortForm)
+ {
+ boolean ctorFound = false;
+ for (Constructor ctor : allctors)
+ if(ctor.getParameterTypes().length == recordEntries.length)
+ ctorFound = true;
+
+ if(!ctorFound)
+ throw Util.runtimeException("Unexpected number of constructor arguments to " + recordClass.toString() + ": got " + recordEntries.length);
+
+ ret = Reflector.invokeConstructor(recordClass, RT.seqToArray(resolveEach(recordEntries)));
+ }
+ else
+ {
+ ret = Reflector.invokeStaticMethod(recordClass, "create", new Object[]{RT.map(RT.seqToArray(resolveEach(recordEntries)))});
+ }
+
+ return ret;
+ }
+
+ static public ISeq resolveEach(Object[] a) {
+ ISeq ret = null;
+ for(int i = a.length - 1; i >= 0; --i)
+ ret = (ISeq) RT.cons(resolve(a[i]), ret);
+ return ret;
+ }
+
+ static private Object resolve(Object o) {
+ if(o instanceof Symbol)
+ {
+ try
+ {
+ return RT.classForName(o.toString());
+ }
+ catch(Exception cfe)
+ {
+ throw new IllegalArgumentException("Constructor literal can only contain constants or statics. "
+ + o.toString()
+ + " does not name a known class.");
+ }
+ }
+ else if(o instanceof ISeq)
+ {
+ Symbol fs = (Symbol) RT.first(o);
+
+ if(fs == null && o == PersistentList.EMPTY)
+ {
+ return o;
+ }
+
+ throw new IllegalArgumentException("Constructor literal can only contain constants or statics. " + o.toString());
+ }
+ else if(o instanceof IPersistentCollection && ((IPersistentCollection) o).count() == 0 ||
+ o instanceof IPersistentCollection ||
+ o instanceof Number ||
+ o instanceof String ||
+ o instanceof Keyword ||
+ o instanceof Symbol ||
+ o == Boolean.TRUE ||
+ o == Boolean.FALSE ||
+ o == null) {
+ return o;
+ }
+ else
+ throw new IllegalArgumentException("Constructor literal can only contain constants or statics. " + o.toString());
+ }
+}
+
/*
public static void main(String[] args) throws Exception{
//RT.init();
diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java
index 21e81b3e..62923f67 100644
--- a/src/jvm/clojure/lang/RT.java
+++ b/src/jvm/clojure/lang/RT.java
@@ -488,6 +488,10 @@ static ISeq seqFrom(Object coll){
}
}
+static public Object seqOrElse(Object o) {
+ return seq(o) == null ? null : o;
+}
+
static public ISeq keys(Object coll){
return APersistentMap.KeySeq.create(seq(coll));
}
diff --git a/src/jvm/clojure/lang/Reflector.java b/src/jvm/clojure/lang/Reflector.java
index 74e58777..a57c32b2 100644
--- a/src/jvm/clojure/lang/Reflector.java
+++ b/src/jvm/clojure/lang/Reflector.java
@@ -12,11 +12,13 @@
package clojure.lang;
-import java.lang.reflect.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-import java.util.Arrays;
public class Reflector{
@@ -105,8 +107,7 @@ public static Method getAsMethodOfPublicBase(Class c, Method m){
{
for(Method im : iface.getMethods())
{
- if(im.getName().equals(m.getName())
- && Arrays.equals(m.getParameterTypes(), im.getParameterTypes()))
+ if(isMatch(im, m))
{
return im;
}
@@ -117,9 +118,7 @@ public static Method getAsMethodOfPublicBase(Class c, Method m){
return null;
for(Method scm : sc.getMethods())
{
- if(scm.getName().equals(m.getName())
- && Arrays.equals(m.getParameterTypes(), scm.getParameterTypes())
- && Modifier.isPublic(scm.getDeclaringClass().getModifiers()))
+ if(isMatch(scm, m))
{
return scm;
}
@@ -127,6 +126,30 @@ public static Method getAsMethodOfPublicBase(Class c, Method m){
return getAsMethodOfPublicBase(sc, m);
}
+public static boolean isMatch(Method lhs, Method rhs) {
+ if(!lhs.getName().equals(rhs.getName())
+ || !Modifier.isPublic(lhs.getDeclaringClass().getModifiers()))
+ {
+ return false;
+ }
+
+ Class[] types1 = lhs.getParameterTypes();
+ Class[] types2 = rhs.getParameterTypes();
+ if(types1.length != types2.length)
+ return false;
+
+ boolean match = true;
+ for (int i=0; i<types1.length; ++i)
+ {
+ if(!types1[i].isAssignableFrom(types2[i]))
+ {
+ match = false;
+ break;
+ }
+ }
+ return match;
+}
+
public static Object invokeConstructor(Class c, Object[] args) {
try
{