From 334cfbef75bc3d5eec7e1dbd4f00ded67f68896a Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Sat, 28 Dec 2019 19:46:36 +0000 Subject: [PATCH] A basic new function has been implemented --- src/main/java/serene/simple/INamespace.java | 6 + src/main/java/serene/simple/Utils.java | 12 ++ .../java/serene/simple/builtin/NewFn.java | 158 ++++++++++++++++++ .../java/serene/simple/builtin/Reflector.java | 138 +++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 src/main/java/serene/simple/INamespace.java create mode 100644 src/main/java/serene/simple/Utils.java create mode 100644 src/main/java/serene/simple/builtin/NewFn.java create mode 100644 src/main/java/serene/simple/builtin/Reflector.java diff --git a/src/main/java/serene/simple/INamespace.java b/src/main/java/serene/simple/INamespace.java new file mode 100644 index 0000000..02f890e --- /dev/null +++ b/src/main/java/serene/simple/INamespace.java @@ -0,0 +1,6 @@ +package serene.simple; + +public interface INamespace { + public Object lookupSymbol(String symbolName); + public void insertSymbol(String symbolName, Object symbolValue); +} diff --git a/src/main/java/serene/simple/Utils.java b/src/main/java/serene/simple/Utils.java new file mode 100644 index 0000000..0acf7c5 --- /dev/null +++ b/src/main/java/serene/simple/Utils.java @@ -0,0 +1,12 @@ +package serene.simple; + + +public class Utils { + public static Class getClassOf(Object target) { + if (target instanceof Class) { + return (Class) target; + } + + return target.getClass(); + } +} diff --git a/src/main/java/serene/simple/builtin/NewFn.java b/src/main/java/serene/simple/builtin/NewFn.java new file mode 100644 index 0000000..d5bd9dc --- /dev/null +++ b/src/main/java/serene/simple/builtin/NewFn.java @@ -0,0 +1,158 @@ +package serene.simple.builtin; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import serene.simple.BaseScope; +import serene.simple.SereneException; +import static serene.simple.Utils.getClassOf; + +public class NewFn extends AFn { + public String fnName() { + return "new"; + }; + + public Object eval(BaseScope scope) throws SereneException { + List args = this.arguments(); + + if (args.size() == 0) { + throw new SereneException("Needs a class to instantiate."); + } + else if(args.size() == 1) { + return this.createObject((Class) args.get(0)); + } + + return this.createObject((Class) args.get(0), args.subList(1, args.size())); + } + + private Object createObject(Class c) { + return this.createObject(c, (List) new ArrayList()); + } + + + private Object createObject(Class c, List args) { + Constructor constructor = this.getConstructor(c, args); + //Object[] argvals = new Object[args.size()]; + Object[] argvals = (Object[]) args.toArray(); + + for(int i = 0; i < args.size(); i++) + + if(constructor != null) { + try { + return constructor.newInstance( + Reflector.boxArgs(constructor.getParameterTypes(), argvals)); + } + catch(Exception e) { + throw new SereneException( + String.format("Can't instantiate class '%s' because: %s", c.getName(), e.getMessage())); + + } + } + return Reflector.invokeConstructor(c, argvals); + } + + private Constructor getConstructor(Class c, List args) { + ArrayList> possibleInitFns = new ArrayList>(); + Constructor[] inits = c.getConstructors(); + ArrayList[]> params = new ArrayList[]>(); + ArrayList> rets = new ArrayList>(); + // Constructor index on the possibleInitFns List + int id = 0; + + for (int i = 0; i < inits.length; i++){ + Constructor initFn = inits[i]; + if(initFn.getParameterTypes().length == args.size()) + { + possibleInitFns.add(initFn); + params.add(initFn.getParameterTypes()); + rets.add(c); + } + } + + if (possibleInitFns.isEmpty()) { + throw new IllegalArgumentException("No matching constructor found for " + c); + } + + if (possibleInitFns.size() > 1) { + id = findMatchingConstructor(c.getName(), params, args, rets); + } + + return id == 0 ? null : (Constructor) possibleInitFns.get(id); + } + + public int findMatchingConstructor(String methodName, + ArrayList[]> paramlists, + List args, + 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; + int exact = 0; + + for (int p = 0; match && p < args.size(); ++p) { + Object arg = args.get(p); + Class aclass = getClassOf(arg); + Class pclass = paramlists.get(i)[p]; + + // TODO: handle null aclass here here + if (aclass == pclass) + exact++; + else + match = Reflector.paramArgTypeMatch(pclass, aclass); + } + + if (exact == args.size()) { + 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; + } + + 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]) { + if(!c1[i].isPrimitive() && c2[i].isPrimitive() + || + c2[i].isAssignableFrom(c1[i])) + better = true; + else + return false; + } + } + return better; + } +} diff --git a/src/main/java/serene/simple/builtin/Reflector.java b/src/main/java/serene/simple/builtin/Reflector.java new file mode 100644 index 0000000..6905cde --- /dev/null +++ b/src/main/java/serene/simple/builtin/Reflector.java @@ -0,0 +1,138 @@ +/** + * I have borrowed the foundations of this namespace from Clojure + * Which is published under EPL license. + */ + +package serene.simple.builtin; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.stream.Collectors; +import java.util.ArrayList; + +import serene.simple.SereneException; + +public class Reflector { + static Object boxArg(Class paramType, Object arg) { + if (!paramType.isPrimitive()) + return paramType.cast(arg); + else if (paramType == boolean.class) + return Boolean.class.cast(arg); + else if (paramType == char.class) + return Character.class.cast(arg); + else if (arg instanceof Number) { + Number n = (Number) arg; + if (paramType == int.class) + return n.intValue(); + else if (paramType == float.class) + return n.floatValue(); + else if (paramType == double.class) + return n.doubleValue(); + else if (paramType == long.class) + return n.longValue(); + else if (paramType == short.class) + return n.shortValue(); + else if (paramType == byte.class) + return n.byteValue(); + } + throw new IllegalArgumentException( + "Unexpected param type, expected: " + paramType + ", given: " + arg.getClass().getName()); + } + + + static Object[] boxArgs(Class[] params, Object[] args) { + if (params.length == 0) + return null; + Object[] ret = new Object[params.length]; + for (int i = 0; i < params.length; i++) { + Object arg = args[i]; + Class paramType = params[i]; + ret[i] = boxArg(paramType, arg); + } + return ret; + } + + public static Object invokeConstructor(Class c, Object[] args) { + try { + Constructor[] allctors = c.getConstructors(); + ArrayList ctors = new ArrayList(); + + for (int i = 0; i < allctors.length; i++) { + Constructor ctor = allctors[i]; + if (ctor.getParameterTypes().length == args.length) + ctors.add(ctor); + } + if (ctors.isEmpty()) { + throw new IllegalArgumentException("No matching ctor found" + " for " + c); + } else if (ctors.size() == 1) { + Constructor ctor = (Constructor) ctors.get(0); + return ctor.newInstance(boxArgs(ctor.getParameterTypes(), args)); + } else // overloaded w/same arity + { + for (Iterator iterator = ctors.iterator(); iterator.hasNext();) { + Constructor ctor = (Constructor) iterator.next(); + Class[] params = ctor.getParameterTypes(); + if (isCongruent(params, args)) { + Object[] boxedArgs = boxArgs(params, args); + return ctor.newInstance(boxedArgs); + } + } + throw new IllegalArgumentException("No matching ctor found" + " for " + c); + } + } catch (Exception e) { + throw new SereneException( + String.format("Can't intantiate '%s': %s", c.getName(), e.getMessage())); + } + } + + static public boolean paramArgTypeMatch(Class paramType, Class argType) { + if (argType == null) + return !paramType.isPrimitive(); + if (paramType == argType || paramType.isAssignableFrom(argType)) + return true; + if (paramType == int.class) + return argType == Integer.class || argType == long.class || argType == Long.class || argType == short.class + || argType == byte.class;// || argType == FixNum.class; + else if (paramType == float.class) + return argType == Float.class || argType == double.class; + else if (paramType == double.class) + return argType == Double.class || argType == float.class;// || argType == DoubleNum.class; + else if (paramType == long.class) + return argType == Long.class || argType == int.class || argType == short.class || argType == byte.class;// || + // argType + // == + // BigNum.class; + else if (paramType == char.class) + return argType == Character.class; + else if (paramType == short.class) + return argType == Short.class; + else if (paramType == byte.class) + return argType == Byte.class; + else if (paramType == boolean.class) + return argType == Boolean.class; + return false; + } + + static boolean isCongruent(Class[] params, Object[] args) { + boolean ret = false; + if (args == null) + return params.length == 0; + if (params.length == args.length) { + ret = true; + for (int i = 0; ret && i < params.length; i++) { + Object arg = args[i]; + Class argType = (arg == null) ? null : arg.getClass(); + Class paramType = params[i]; + ret = paramArgTypeMatch(paramType, argType); + } + } + return ret; + } + +}