package php.runtime.ext.support.compile;
import php.runtime.Memory;
import php.runtime.annotation.Runtime;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.exceptions.CriticalException;
import php.runtime.exceptions.support.ErrorType;
import php.runtime.memory.ArrayMemory;
import php.runtime.memory.support.MemoryUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
public class CompileFunction {
public String name;
public Method[] methods;
private Method methodVarArgs;
private int methodVarArgsCount;
private int minArgs = Integer.MAX_VALUE;
private int maxArgs = 0;
public CompileFunction(String name) {
this.name = name;
this.methods = new Method[5];
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CompileFunction)) return false;
CompileFunction that = (CompileFunction) o;
return name.equals(that.name);
}
public int getMinArgs() {
return minArgs;
}
public int getMaxArgs() {
return maxArgs;
}
public Method addMethod(java.lang.reflect.Method method){
return addMethod(method, false);
}
public void mergeFunction(CompileFunction function) {
if (methods.length < function.methods.length) {
methods = Arrays.copyOf(methods, function.methods.length);
}
for (int i = 0; i < function.methods.length; i++) {
if (function.methods[i] != null) {
methods[i] = function.methods[i];
}
}
if (function.methodVarArgs != null) {
methodVarArgs = function.methodVarArgs;
}
this.minArgs = Math.min(this.minArgs, function.minArgs);
this.maxArgs = Math.max(this.maxArgs, function.maxArgs);
}
public Method addMethod(java.lang.reflect.Method method, boolean asImmutable){
Annotation[][] paramAnnotations = method.getParameterAnnotations();
if (method.isVarArgs()){
if (methodVarArgs != null)
throw new IllegalArgumentException("Cannot add two var-args methods");
methodVarArgs = new Method(method, 0, asImmutable);
int count = 0;
Class<?>[] types = method.getParameterTypes();
for(int i = 0; i < types.length; i++){
Class<?> type = types[i];
if (type == Environment.class || type == TraceInfo.class)
continue;
if (type == Memory[].class)
continue;
boolean ignore = false;
for (Annotation el : paramAnnotations[i]){
if (el.annotationType().equals(Runtime.GetLocals.class)) {
if (type != ArrayMemory.class)
throw new RuntimeException("@Runtime.GetLocals: param type must be ArrayMemory");
ignore = true;
break;
}
}
if (!ignore) count++;
}
if (count < minArgs)
minArgs = count;
methodVarArgsCount = count;
maxArgs = Integer.MAX_VALUE;
if (methodVarArgsCount < methods.length && methods[methodVarArgsCount] != null)
throw new IllegalArgumentException("Method '"+ name +"' with " + methodVarArgsCount + " args already exists");
}
Class<?>[] types = method.getParameterTypes();
int count = 0;
for(int i = 0; i < types.length; i++){
Class<?> type = types[i];
if (type == Environment.class || type == TraceInfo.class)
continue;
boolean ignore = false;
for (Annotation el : paramAnnotations[i]){
if (el.annotationType().equals(Runtime.GetLocals.class)) {
if (type != ArrayMemory.class)
throw new RuntimeException("@Runtime.GetLocals: param type must be ArrayMemory");
ignore = true;
break;
}
}
if (!ignore) count++;
}
if (count < minArgs)
minArgs = count;
if (count > maxArgs)
maxArgs = count;
if (count >= methods.length){
Method[] newMethods = new Method[Math.max(methods.length * 2, count + 1)];
System.arraycopy(methods, 0, newMethods, 0, methods.length);
methods = newMethods;
}
if (methods[count] != null)
throw new IllegalArgumentException("Method " + name + " with " + count + " args already exists");
return methods[count] = createMethod(method, count, asImmutable);
}
@Override
public int hashCode() {
return name.hashCode();
}
public boolean delete(int paramCount) {
if (paramCount < minArgs)
return false;
Method method = null;
if (paramCount < methods.length && paramCount >= 0) {
methods[paramCount] = null;
return true;
}
if (methodVarArgsCount <= paramCount) {
methodVarArgs = null;
return true;
}
if (paramCount > maxArgs) {
for(int i = methods.length - 1; i >= 0; i--){
method = methods[i];
if (method != null) {
methods[i] = null;
return true;
}
}
}
return false;
}
public Method find(int paramCount) {
if (paramCount < minArgs)
return null;
Method method = null;
if (paramCount < methods.length && paramCount >= 0)
method = methods[paramCount];
if (method == null && methodVarArgsCount <= paramCount)
method = methodVarArgs;
if (method == null && paramCount > maxArgs) {
for(int i = methods.length - 1; i >= 0; i--){
method = methods[i];
if (method != null)
return method;
}
}
return method;
}
protected Method createMethod(java.lang.reflect.Method method, int count, boolean asImmutable) {
return new Method(method, count, asImmutable);
}
public static class Method {
public boolean isImmutable;
public final boolean isImmutableIgnoreRefs;
public final java.lang.reflect.Method method;
public final MemoryUtils.Converter<?>[] converters;
public final Annotation[][] parameterAnnotations;
public final Class<?>[] parameterTypes;
public final Class<?> resultType;
public final boolean[] references;
public final boolean[] mutableValues;
public final int argsCount;
public Method(java.lang.reflect.Method method, int argsCount, boolean _asImmutable) {
this.argsCount = argsCount;
this.method = method;
parameterTypes = method.getParameterTypes();
converters = new MemoryUtils.Converter[parameterTypes.length];
int i = 0;
for (Class<?> type : parameterTypes) {
converters[i++] = getConverterForArgument(type);
}
if (method.isVarArgs())
converters[converters.length - 1] = null;
parameterAnnotations = method.getParameterAnnotations();
resultType = method.getReturnType();
isImmutable = method.isAnnotationPresent(Runtime.Immutable.class) || _asImmutable;
if (isImmutable){
Runtime.Immutable annotation = method.getAnnotation(Runtime.Immutable.class);
isImmutableIgnoreRefs = annotation != null && annotation.ignoreRefs();
} else
isImmutableIgnoreRefs = false;
references = new boolean[parameterTypes.length];
mutableValues = new boolean[parameterTypes.length];
i = 0;
for (Class<?> type : parameterTypes){
for(Annotation annotation : parameterAnnotations[i]){
if (annotation.annotationType() == Runtime.Reference.class) {
references[i] = true;
if (!isImmutableIgnoreRefs)
isImmutable = false;
} else if (annotation.annotationType() == Runtime.MutableValue.class) {
mutableValues[i] = true;
}
}
i++;
}
if (resultType == void.class)
isImmutable = false;
}
protected MemoryUtils.Converter<?> getConverterForArgument(Class<?> type) {
return MemoryUtils.getConverter(type);
}
public boolean isPresentAnnotationOfParam(int index, Class<? extends Annotation> clazz){
assert index >= 0 && index < parameterAnnotations.length;
for (Annotation el : parameterAnnotations[index])
if (el.annotationType().equals(clazz))
return true;
return false;
}
public boolean isVarArg(){
return method.isVarArgs();
}
public Memory call(Environment env, Memory... arguments) {
Class<?>[] types = parameterTypes;
Object[] passed = new Object[ types.length ];
int i = 0;
int j = 0;
for(Class<?> clazz : types) {
boolean isRef = references[i];
boolean mutableValue = mutableValues[i];
MemoryUtils.Converter<?> converter = converters[i];
if (clazz == Memory.class) {
passed[i] = isRef ? arguments[j] : (mutableValue ? arguments[j].toImmutable() : arguments[j].toValue());
j++;
} else if (converter != null) {
passed[i] = converter.run(arguments[j]);
j++;
} else if (clazz == Environment.class) {
passed[i] = env;
} else if (clazz == TraceInfo.class) {
passed[i] = env.trace();
} else if (i == types.length - 1 && types[i] == Memory[].class){
Memory[] arg = new Memory[arguments.length - i + 1];
if (!isRef){
for(int k = 0; k < arg.length; k++)
arg[i] = arguments[i].toImmutable();
} else {
System.arraycopy(arguments, j, arg, 0, arg.length);
}
passed[i] = arg;
break;
} else {
env.error(env.trace(), ErrorType.E_CORE_ERROR, "Cannot call this method dynamically");
passed[i] = Memory.NULL;
}
i++;
}
try {
if (resultType == void.class){
method.invoke(null, passed);
return Memory.NULL;
} else
return MemoryUtils.valueOf(method.invoke(null, passed));
} catch (InvocationTargetException e){
return env.__throwException(e);
} catch (IllegalAccessException e) {
throw new CriticalException(e);
}
}
}
}