package php.runtime.invoke;
import php.runtime.Memory;
import php.runtime.common.HintType;
import php.runtime.common.Messages;
import php.runtime.common.StringUtils;
import php.runtime.env.Environment;
import php.runtime.env.TraceInfo;
import php.runtime.exceptions.support.ErrorType;
import php.runtime.ext.core.classes.WrapJavaExceptions;
import php.runtime.lang.ForeachIterator;
import php.runtime.lang.Generator;
import php.runtime.memory.ArrayMemory;
import php.runtime.memory.ObjectMemory;
import php.runtime.memory.ReferenceMemory;
import php.runtime.memory.helper.VariadicMemory;
import php.runtime.reflection.ClassEntity;
import php.runtime.reflection.MethodEntity;
import php.runtime.reflection.ParameterEntity;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class InvokeArgumentHelper {
private static final String INVALID_TYPE_MESSAGE = "Only arrays and Traversables can be unpacked";
public static Memory[] makeArguments(Environment env, Memory[] args,
ParameterEntity[] parameters,
String originClassName, String originMethodName,
TraceInfo trace) {
if (parameters == null)
return args;
args = unpackArgs(env, trace, args, parameters);
Memory[] passed = args;
if ((args == null && parameters.length > 0) || (args != null && args.length < parameters.length)) {
passed = new Memory[parameters.length];
if (args != null && args.length > 0) {
System.arraycopy(args, 0, passed, 0, args.length);
}
}
int i = 0;
if (passed != null) {
boolean variadicMemoryExists = false;
for (ParameterEntity param : parameters) {
Memory arg = passed[i];
if (param.isVariadic()) {
ArrayMemory variadicArgs = new ArrayMemory();
int _i = i;
while (arg != null) {
if (arg instanceof VariadicMemory) {
variadicMemoryExists = true;
ForeachIterator iterator = arg.getNewIterator(env, param.isReference(), false);
if (iterator == null) {
env.warning(trace, INVALID_TYPE_MESSAGE);
} else {
makeVariadic(iterator, variadicArgs, param, env, trace, _i, originClassName, originMethodName);
}
} else {
if (variadicMemoryExists) {
env.error(trace, "Cannot use positional argument after argument unpacking");
}
if (!param.checkTypeHinting(env, arg)) {
invalidType(env, trace, param, _i + 1, arg, originClassName, originMethodName);
}
variadicArgs.add(makeValue(param, arg, env, trace));
}
i++;
if (i < passed.length) {
arg = passed[i];
} else {
break;
}
}
passed[_i] = variadicArgs;
break;
}
if (arg == null) {
Memory def = param.getDefaultValue();
if (def != null) {
if (!param.isReference()) {
passed[i] = param.isMutable() ? def.toImmutable(env, trace) : def;
} else {
passed[i] = new ReferenceMemory(param.isMutable() ? def.toImmutable(env, trace) : def);
}
} else {
if (param.getTypeClass() != null) {
invalidType(env, trace, param, i + 1, null, originClassName, originMethodName);
}
env.error(trace, ErrorType.E_ERROR,
Messages.ERR_MISSING_ARGUMENT, (i + 1) + " ($" + param.getName() + ")",
originMethodName == null ? originClassName : originClassName + "::" + originMethodName
);
passed[i] = param.isReference() ? new ReferenceMemory() : Memory.NULL;
}
} else {
if (param.isReference()) {
if (!arg.isReference() && !arg.isObject()) {
env.error(trace, ErrorType.E_ERROR, "Only variables can be passed by reference");
passed[i] = new ReferenceMemory(arg);
}
} else {
passed[i] = param.isMutable() ? arg.toImmutable() : arg.toValue();
}
}
if (!param.checkTypeHinting(env, passed[i])) {
invalidType(env, trace, param, i + 1, passed[i], originClassName, originMethodName);
}
i++;
}
if (!variadicMemoryExists) {
for (int j = parameters.length ; j < passed.length; j++) {
passed[j] = passed[j].toImmutable();
}
}
}
return passed;
}
public static Memory[] unpackArgs(Environment env, TraceInfo trace, Memory[] passed, ParameterEntity[] parameters) {
List<Memory> varPassed = null;
if (passed == null) {
return null;
}
int cnt = 0, paramCnt = 0;
ParameterEntity parameterEntity = null;
boolean variadicMemoryExists = false;
for (Memory arg : passed) {
if (arg instanceof VariadicMemory) {
variadicMemoryExists = true;
if (varPassed == null) {
varPassed = new ArrayList<Memory>();
for (int i = 0; i < cnt; i++) {
varPassed.add(passed[i]);
}
}
boolean isGenerator = arg.instanceOf(Generator.class);
ForeachIterator foreachIterator = arg.getNewIterator(env, !isGenerator, false);
if (foreachIterator == null || (!isGenerator && !arg.isTraversable())) {
env.warning(trace, INVALID_TYPE_MESSAGE);
} else {
boolean isRef;
while (foreachIterator.next()) {
if (parameters != null) {
if (parameterEntity == null || !parameterEntity.isVariadic()) {
parameterEntity = paramCnt < parameters.length ? parameters[paramCnt] : null;
}
}
isRef = parameterEntity != null && parameterEntity.isReference();
Memory value = foreachIterator.getValue();
varPassed.add(isRef ? value : value.toImmutable());
paramCnt++;
if (parameterEntity != null && !parameterEntity.isVariadic()) {
parameterEntity = null;
}
}
}
} else {
if (variadicMemoryExists) {
env.error(trace, "Cannot use positional argument after argument unpacking");
}
if (varPassed != null) {
varPassed.add(arg);
}
paramCnt++;
}
cnt++;
}
if (varPassed != null) {
passed = varPassed.toArray(new Memory[varPassed.size()]);
}
return passed;
}
public static void checkType(Environment env, TraceInfo trace, MethodEntity methodEntity, Memory... args) {
if (args == null) {
return;
}
ParameterEntity[] parameters = methodEntity.getParameters(args.length);
int i = 0;
for (Memory arg : args) {
if (i > parameters.length - 1) {
break;
}
if (!parameters[i].checkTypeHinting(env, arg)) {
invalidType(env, trace, parameters[i], i + 1, arg, methodEntity.getClazzName(), methodEntity.getName());
}
i++;
}
}
public static void invalidType(Environment env, TraceInfo trace, ParameterEntity param, int index, Memory passed,
String originClassName, String originMethodName) {
String given;
if (passed == null) {
given = "none";
} else if (passed.isObject()) {
given = "instance of " + passed.toValue(ObjectMemory.class).getReflection().getName();
} else {
given = passed.getRealType().toString();
}
String method = originMethodName == null ? originClassName : originClassName + "::" + originMethodName;
if (param.getTypeClass() == null) {
if (param.getTypeEnum() != null && param.getType() == HintType.ANY) {
Field[] fields = param.getTypeEnum().getFields();
String[] names = new String[fields.length];
for (int i = 0; i < names.length; i++) {
names[i] = fields[i].getName();
}
env.exception(param.getTrace(), WrapJavaExceptions.IllegalArgumentException.class,
"Argument %s passed to %s() must be a string belonging to the range [" + StringUtils.join(names, ", ") + "] as string, called in %s on line %d, position %d and defined",
index,
method,
trace.getFileName(),
trace.getStartLine() + 1,
trace.getStartPosition() + 1
);
} else if (param.getTypeHintingChecker() != null) {
env.error(
param.getTrace(),
ErrorType.E_RECOVERABLE_ERROR,
"Argument %s passed to %s() must be %s, called in %s on line %d, position %d and defined",
index,
method,
param.getTypeHintingChecker().getNeeded(env, passed),
trace.getFileName(),
trace.getStartLine() + 1,
trace.getStartPosition() + 1
);
} else {
env.error(
param.getTrace(),
ErrorType.E_RECOVERABLE_ERROR,
"Argument %s passed to %s() must be of the type %s, %s given, called in %s on line %d, position %d and defined",
index,
method,
param.getType().toString(), given,
trace.getFileName(),
trace.getStartLine() + 1,
trace.getStartPosition() + 1
);
}
} else {
ClassEntity need = env.fetchClass(param.getTypeClass(), false);
String what = "";
if (need == null || need.isClass()) {
what = "be an instance of";
} else if (need.isInterface()) {
what = "implement interface";
}
what = what + " " + param.getTypeClass();
env.error(
param.getTrace(),
ErrorType.E_RECOVERABLE_ERROR,
"Argument %s passed to %s() must %s, %s given, called in %s on line %d, position %d and defined",
index,
method,
what, given,
trace.getFileName(),
trace.getStartLine() + 1,
trace.getStartPosition() + 1
);
}
}
public static Memory makeValue(ParameterEntity param, Memory arg, Environment env, TraceInfo trace) {
if (param.isReference()) {
if (!arg.isReference() && !arg.isObject()) {
env.error(trace, ErrorType.E_ERROR, "Only variables can be passed by reference");
arg = new ReferenceMemory(arg);
}
} else {
arg = param.isMutable() ? arg.toImmutable() : arg.toValue();
}
return arg;
}
public static void makeVariadic(ForeachIterator iterator, ArrayMemory variadicArray, ParameterEntity param,
Environment env, TraceInfo trace, int index, String originClassName,
String originMethodName) {
while (iterator.next()) {
Memory arg = iterator.getValue();
if (!param.checkTypeHinting(env, arg)) {
invalidType(env, trace, param, index + 1, arg, originClassName, originMethodName);
}
variadicArray.add(makeValue(param, iterator.getValue(), env, trace));
}
}
}