package org.rascalmpl.library.lang.oil;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.rascalmpl.value.IBool;
import org.rascalmpl.value.IConstructor;
import org.rascalmpl.value.IInteger;
import org.rascalmpl.value.IList;
import org.rascalmpl.value.IString;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.IValueFactory;
public class Interpreter {
private final IValueFactory VF;
private final ClassLoader classLoader = Interpreter.class.getClassLoader();
private HashMap<String, Class<?>> classStore = new HashMap<>();
private HashMap<IConstructor, Class<?>> expTypes = new HashMap<>();
private HashMap<String, Object> letStore = new HashMap<>();
public Interpreter(IValueFactory VF) {
this.VF = VF;
}
public IString interpret(IList exps) {
Object result = null;
for (IValue c : exps) {
result = internalInterpret(null, (IConstructor) c);
}
return VF.string(result.toString());
}
public IString interpret(IConstructor exp) {
return VF.string(internalInterpret(null, exp).toString());
}
private Object internalInterpret(Object object, IConstructor exp) {
Class<?> clazz = null;
String className = "";
if (exp.has("class")) {
className = ((IString) exp.get("class")).getValue();
clazz = classStore.get(className);
if (clazz == null) {
try {
clazz = classLoader.loadClass(className);
classStore.put(className, clazz);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class " + className + " not found");
}
}
}
List<Object> arguments = new ArrayList<Object>();
List<Class<?>> argumentTypes = new ArrayList<Class<?>>();
if (exp.has("arguments")) {
for (IValue argumentType: (IList) exp.get("arguments")) {
arguments.add(internalInterpret(null, (IConstructor) argumentType));
argumentTypes.add(expTypes.get((IConstructor) argumentType));
}
}
switch (exp.getName()) {
case "let": {
letStore.put(((IString) exp.get("key")).getValue(), internalInterpret(null, (IConstructor) exp.get("val")));
return null;
}
case "access": {
String fieldName = ((IString) exp.get("field")).getValue();
try {
Field field = clazz.getField(fieldName);
object = field.get(object);
expTypes.put(exp, field.getType());
return object;
} catch (NoSuchFieldException e) {
throw new RuntimeException("Field " + fieldName + " of class " + className + " not found");
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument of " + fieldName + " of class " + className);
} catch (IllegalAccessException e) {
throw new RuntimeException("Illegal access on " + fieldName + " of class " + className);
}
}
case "atom": {
IValue val = exp.get(0);
if (val.getType().isInteger()) {
expTypes.put(exp, int.class);
return ((IInteger) val).intValue();
} else if (val.getType().isBool()) {
expTypes.put(exp, boolean.class);
return ((IBool) val).getValue();
}
expTypes.put(exp, String.class);
return ((IString) val).getValue();
}
case "use": {
Object o = internalInterpret(letStore.get(((IString) exp.get("key")).getValue()), (IConstructor) exp.get("val"));
expTypes.put(exp, expTypes.get((IConstructor) exp.get("val")));
return o;
}
case "new": {
try {
Constructor<?>[] constructors = clazz.getConstructors();
Constructor<?> constructor = null;
for(Constructor<?> c : constructors) {
if (isRequiredMethod(argumentTypes, c.getParameterTypes())) {
constructor = c;
break;
}
}
object = constructor.newInstance(arguments.toArray(new Object[arguments.size()]));
expTypes.put(exp, object.getClass());
return object;
} catch (IllegalAccessException e) {
throw new RuntimeException("Illegal access on constructor of class" + className);
} catch (InvocationTargetException e) {
throw new RuntimeException("InvocationTargetException on constructor of class" + className);
} catch (InstantiationException e) {
throw new RuntimeException("InstantiationException on constructor of class" + className);
}
}
case "call": {
String methodName = ((IString) exp.get("method")).getValue();
try {
Method[] methods = clazz.getMethods();
Method method = null;
for(Method m : methods) {
if (isRequiredMethod(argumentTypes, m.getParameterTypes()) && m.getName().equals(methodName)) {
method = m;
break;
}
}
object = method.invoke(object, arguments.toArray(new Object[arguments.size()]));
for (IValue extraCalls : (IList) exp.get("calls")) {
internalInterpret(object, (IConstructor) extraCalls);
}
expTypes.put(exp, method.getReturnType());
return object;
} catch (IllegalAccessException e) {
throw new RuntimeException("Illegal access on method " + methodName + " of class" + className);
} catch (InvocationTargetException e) {
throw new RuntimeException("InvocationTargetException on method " + methodName + " of class" + className);
}
}
default: {
throw new RuntimeException("Not supported");
}
}
}
private boolean isRequiredMethod(List<Class<?>> argumentTypes, Class<?>[] paramTypes) {
boolean matches = false;
if (paramTypes.length == argumentTypes.size()) {
for (int i = 0; i < paramTypes.length; i++) {
if (!paramTypes[i].isAssignableFrom(argumentTypes.get(i))) {
return false;
}
}
matches = true;
}
return matches;
}
}