variable number of arguments for user defined functions.

This commit is contained in:
gnembon committed 2020-09-22 04:38:08 -03:00
1 parent 7de132eb56
commit e8b2060fb3
15 files changed
+168 -68

No files matched your search

@@ -309,7 +309,12 @@ public class CarpetScriptHost extends ScriptHost
{
if (CarpetServer.scriptServer.stopAll)
return Value.NULL;
if (argv.size() != fun.getArguments().size())
try { // cause we can't throw checked exceptions in lambda. Left if be until need to handle these more gracefully
fun.assertArgsOk(argv.size(), (b) -> {
throw new InternalExpressionException("");
});
}
catch (InternalExpressionException ignored)
{
throw new InvalidCallbackException();
}
+2 -2
View File
@@ -339,7 +339,7 @@ public class Expression
}
});
}
public FunctionValue addContextFunction(Context context, String name, Expression expr, Tokenizer.Token token, List<String> arguments, List<String> outers, LazyValue code)
public FunctionValue addContextFunction(Context context, String name, Expression expr, Tokenizer.Token token, List<String> arguments, String varArgs, List<String> outers, LazyValue code)
{
if (functions.containsKey(name))
throw new ExpressionException(context, expr, token, "Function "+name+" would mask a built-in function");
@@ -358,7 +358,7 @@ public class Expression
}
if (contextValues.isEmpty()) contextValues = null;
FunctionValue result = new FunctionValue(expr, token, name, code, arguments, contextValues);
FunctionValue result = new FunctionValue(expr, token, name, code, arguments, varArgs, contextValues);
// do not store lambda definitions
if (!name.equals("_")) context.host.addUserDefinedFunction(context, module, name, result);
return result;
@@ -691,7 +691,7 @@ public class Auxiliary {
throw new InternalExpressionException("'schedule' should have at least 2 arguments, delay and call name");
long delay = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong();
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 1, true, true);
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 1, true);
CarpetServer.scriptServer.events.scheduleCall(
(CarpetContext) c,
@@ -1,6 +1,5 @@
package carpet.script.argument;
import carpet.script.CarpetContext;
import carpet.script.Context;
import carpet.script.LazyValue;
import carpet.script.bundled.Module;
@@ -22,7 +21,7 @@ public class FunctionArgument extends Argument
this.function = function;
this.args = args;
}
public static FunctionArgument findIn(Context c, Module module, List<LazyValue> params, int offset, boolean prematureEvaluation, boolean strict)
public static FunctionArgument findIn(Context c, Module module, List<LazyValue> params, int offset, boolean prematureEvaluation)
{
Value functionValue = params.get(offset).evalValue(c);
if (!(functionValue instanceof FunctionValue))
@@ -33,20 +32,22 @@ public class FunctionArgument extends Argument
FunctionValue fun = (FunctionValue)functionValue;
int argsize = fun.getArguments().size();
int extraargs = params.size() - argsize - offset - 1;
if (extraargs < 0 || (extraargs > 0 && strict))
if (extraargs < 0)
throw new InternalExpressionException("Function "+fun.getPrettyString()+" requires at least "+fun.getArguments().size()+" arguments");
if (extraargs > 0 && fun.getVarArgs()==null)
throw new InternalExpressionException("Function "+fun.getPrettyString()+" requires "+fun.getArguments().size()+" arguments");
List<LazyValue> lvargs = new ArrayList<>();
if (prematureEvaluation)
{
for (int i = 0; i < argsize; i++)
for (int i = offset+1, mx = params.size(); i < mx; i++)
{
Value arg = params.get(offset + 1 + i).evalValue(c);
Value arg = params.get(i).evalValue(c);
lvargs.add((cc, tt) -> arg);
}
}
else
{
for (int i = 0; i < argsize; i++) lvargs.add(params.get(offset + 1 + i));
for (int i = offset+1, mx = params.size(); i < mx; i++) lvargs.add(params.get(i));
}
return new FunctionArgument(fun, offset+1+argsize, lvargs);
}
@@ -8,7 +8,7 @@ import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ReturnStatement;
import carpet.script.value.FunctionSignatureValue;
import carpet.script.value.FunctionValue;
import carpet.script.value.GlobalValue;
import carpet.script.value.FunctionAnnotationValue;
import carpet.script.value.ListValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
@@ -43,7 +43,7 @@ public class Functions {
//lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes
if (t != Context.SIGNATURE) // just call the function
{
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, false, true);
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, false);
FunctionValue fun = functionArgument.function;
Value retval = fun.callInContext(expr, c, t, fun.getExpression(), fun.getToken(), functionArgument.args).evalValue(c);
return (cc, tt) -> retval; ///!!!! dono might need to store expr and token in statics? (e? t?)
@@ -52,6 +52,7 @@ public class Functions {
String name = lv.get(0).evalValue(c).getString();
List<String> args = new ArrayList<>();
List<String> globals = new ArrayList<>();
String varArgs = null;
for (int i = 1; i < lv.size(); i++)
{
Value v = lv.get(i).evalValue(c, Context.LOCALIZATION);
@@ -59,27 +60,44 @@ public class Functions {
{
throw new InternalExpressionException("Only variables can be used in function signature, not " + v.getString());
}
if (v instanceof GlobalValue)
if (v instanceof FunctionAnnotationValue)
{
globals.add(v.boundVariable);
if (((FunctionAnnotationValue) v).type == 0)
{
globals.add(v.boundVariable);
}
else
{
if (varArgs != null)
throw new InternalExpressionException("Variable argument identifier is already defined as "+varArgs+", trying to overwrite with "+v.boundVariable);
varArgs = v.boundVariable;
}
}
else
{
args.add(v.boundVariable);
}
}
Value retval = new FunctionSignatureValue(name, args, globals);
Value retval = new FunctionSignatureValue(name, args, varArgs, globals);
return (cc, tt) -> retval;
});
expression.addLazyFunction("outer", 1, (c, t, lv) -> {
expression.addLazyFunction("outer", 1, (c, t, lv) ->
{
if (t != Context.LOCALIZATION)
throw new InternalExpressionException("Outer scoping of variables is only possible in function signatures");
return (cc, tt) -> new GlobalValue(lv.get(0).evalValue(c));
return (cc, tt) -> new FunctionAnnotationValue(lv.get(0).evalValue(c), 0);
});
expression.addLazyUnaryOperator("...", Operators.precedence.get("def->..."), false, (c, t, lv) ->
{
if (t != Context.LOCALIZATION)
throw new InternalExpressionException("Outer scoping of variables is only possible in function signatures");
return (cc, tt) -> new FunctionAnnotationValue(lv.evalValue(c), 1);
});
//assigns const procedure to the lhs, returning its previous value
expression.addLazyBinaryOperatorWithDelegation("->", Operators.precedence.get("def->"), false, (c, type, e, t, lv1, lv2) ->
expression.addLazyBinaryOperatorWithDelegation("->", Operators.precedence.get("def->..."), false, (c, type, e, t, lv1, lv2) ->
{
if (type == Context.MAPDEF)
{
@@ -90,7 +108,7 @@ public class Functions {
if (!(v1 instanceof FunctionSignatureValue))
throw new InternalExpressionException("'->' operator requires a function signature on the LHS");
FunctionSignatureValue sign = (FunctionSignatureValue) v1;
Value result = expression.addContextFunction(c, sign.getName(), e, t, sign.getArgs(), sign.getGlobals(), lv2);
Value result = expression.addContextFunction(c, sign.getName(), e, t, sign.getArgs(), sign.getVarArgs(), sign.getGlobals(), lv2);
return (cc, tt) -> result;
});
@@ -29,7 +29,7 @@ public class Operators {
put("and&&", 5);
put("or||", 4);
put("assign=<>", 3);
put("def->", 2);
put("def->...", 2);
put("nextop;", 1);
}};
@@ -15,7 +15,7 @@ public class Threading
{
if (lv.size() == 0)
throw new InternalExpressionException("'task' requires at least function to call as a parameter");
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, true, true);
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, true);
ThreadValue thread = new ThreadValue(Value.NULL, functionArgument.function, expr, tok, c, functionArgument.args);
Thread.yield();
return (cc, tt) -> thread;
@@ -26,7 +26,7 @@ public class Threading
if (lv.size() < 2)
throw new InternalExpressionException("'task' requires at least function to call as a parameter");
Value queue = lv.get(0).evalValue(c);
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 1, true, true);
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 1, true);
ThreadValue thread = new ThreadValue(queue, functionArgument.function, expr, tok, c, functionArgument.args);
Thread.yield();
return (cc, tt) -> thread;
@@ -3,13 +3,17 @@ package carpet.script.value;
import carpet.script.exception.InternalExpressionException;
import net.minecraft.nbt.Tag;
public class GlobalValue extends Value
public class FunctionAnnotationValue extends Value
{
public GlobalValue(Value variable)
// 0 global
// 1 vararg
public int type;
public FunctionAnnotationValue(Value variable, int type)
{
if (variable.boundVariable == null)
throw new InternalExpressionException("You can only borrow variables from the outer scope");
this.boundVariable = variable.boundVariable;
this.type = type;
}
@Override
@@ -7,11 +7,13 @@ public class FunctionSignatureValue extends FrameworkValue
private String identifier;
private List<String> arguments;
private List<String> globals;
private String varArgs;
public FunctionSignatureValue(String name, List<String> args, List<String> globals)
public FunctionSignatureValue(String name, List<String> args, String varArgs, List<String> globals)
{
this.identifier = name;
this.arguments = args;
this.varArgs = varArgs;
this.globals = globals;
}
public String getName()
@@ -25,6 +27,5 @@ public class FunctionSignatureValue extends FrameworkValue
public List<String> getGlobals() {return globals;}
public String getVarArgs() { return varArgs;}
}
@@ -8,7 +8,6 @@ import carpet.script.Tokenizer;
import carpet.script.bundled.Module;
import carpet.script.exception.BreakStatement;
import carpet.script.exception.ContinueStatement;
import carpet.script.exception.ExitStatement;
import carpet.script.exception.ExpressionException;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ReturnStatement;
@@ -20,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class FunctionValue extends Value implements Fluff.ILazyFunction
@@ -30,27 +30,30 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
private LazyValue body;
private Map<String, LazyValue> outerState;
private List<String> args;
private String varArgs;
private static long variantCounter = 1;
private long variant;
private FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List<String> args)
private FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List<String> args, String varArgs)
{
this.expression = expression;
this.token = token;
this.name = name;
this.body = body;
this.args = args;
this.varArgs = varArgs;
this.outerState = null;
variant = 0L;
}
public FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List<String> args, Map<String, LazyValue> outerState)
public FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List<String> args, String varArgs, Map<String, LazyValue> outerState)
{
this.expression = expression;
this.token = token;
this.name = name;
this.body = body;
this.args = args;
this.varArgs = varArgs;
this.outerState = outerState;
variant = variantCounter++;
}
@@ -84,7 +87,7 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
@Override
protected Value clone()
{
FunctionValue ret = new FunctionValue(expression, token, name, body, args);
FunctionValue ret = new FunctionValue(expression, token, name, body, args, varArgs);
ret.outerState = this.outerState;
ret.variant = this.variant;
return ret;
@@ -146,7 +149,7 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
@Override
public boolean numParamsVaries()
{
return false;
return varArgs != null;
}
public LazyValue callInContext(Expression callingExpression, Context c, Integer type, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
@@ -175,23 +178,44 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
@Override
public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
{
if (args.size() != lazyParams.size()) // something that might be subject to change in the future
// in case we add variable arguments
{
throw new ExpressionException(c, e, t,
"Incorrect number of arguments for function "+name+
". Should be "+args.size()+", not "+lazyParams.size()+" like "+args
);
}
assertArgsOk(lazyParams.size(), (fixedArgs) ->{
if (fixedArgs) // wrong number of args for fixed args
{
throw new ExpressionException(c, e, t,
"Incorrect number of arguments for function "+name+
". Should be "+args.size()+", not "+lazyParams.size()+" like "+args
);
}
else // too few args for varargs
{
List<String> argList = new ArrayList<>(args);
argList.add("... "+varArgs);
throw new ExpressionException(c, e, t,
"Incorrect number of arguments for function "+name+
". Should be at least "+args.size()+", not "+lazyParams.size()+" like "+argList
);
}
});
Context newFrame = c.recreate();
if (outerState != null) outerState.forEach(newFrame::setVariable);
for (int i=0; i<args.size(); i++)
{
String arg = args.get(i);
Value val = lazyParams.get(i).evalValue(c).reboundedTo(arg);
Value val = lazyParams.get(i).evalValue(c).reboundedTo(arg); // todo check if we need to copy that
newFrame.setVariable(arg, (cc, tt) -> val);
}
if (varArgs != null)
{
List<Value> extraParams = new ArrayList<>();
for (int i = args.size(), mx = lazyParams.size(); i < mx; i++)
{
extraParams.add(lazyParams.get(i).evalValue(c).reboundedTo(null)); // copy by value I guess
}
Value rest = ListValue.wrap(extraParams).bindTo(varArgs); // didn't we just copied that?
newFrame.setVariable(varArgs, (cc, tt) -> rest);
}
Value retVal;
boolean rethrow = false;
try
@@ -211,10 +235,6 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
retVal = throwStatement.retval;
rethrow = true;
}
//catch (ArithmeticException | ExitStatement | ExpressionException | InternalExpressionException exc )
//{
// throw exc; // rethrow so could be contextualized if needed
//}
if (rethrow)
{
throw new ThrowStatement(retVal);
@@ -235,6 +255,7 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
{
return args;
}
public String getVarArgs() {return varArgs; }
@Override
public Tag toTag(boolean force)
@@ -242,4 +263,17 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
return StringTag.of(getString());
}
public void assertArgsOk(int size, Consumer<Boolean> feedback)
{
if (varArgs == null && args.size() != size) // wrong number of args for fixed args
{
feedback.accept(true);
}
else if (varArgs != null && args.size() > size) // too few args for varargs
{
feedback.accept(false);
}
}
}