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

+35 -17
View File
@@ -146,7 +146,7 @@ around built-in function `outer`. It adds the specified value to the function ca
like capturing lambdas in Java, but unlike java captured variables don't need to be final. Scarpet will just
attach their new values at the time of the function definition, even if they change later. Most value will be
copied, but mutable values, like maps or lists, allow to keep the 'state' with the function, allowing them to
have memory and act like objects so to speak. . Check `outer(var)` for details.
have memory and act like objects so to speak. Check `outer(var)` for details.
## Code delivery, line indicators
@@ -767,22 +767,21 @@ from other threads, just because the only possibility of a deadlock in this case
bad code, not the internal world access behaviour. Some things tough like player or entity manipulation, can be
effectively parallelized.
### `task(function, ?args...., ?executor)`
### `task(function, ... args)`, `task_thread(executor, function, ... args)`
Creates and runs a parallel task, returning the handle to the task object. Task will return the return value of the
function when its completed, or will return `null` immediately if task is still in progress, so grabbing a value of
a task object is non-blocking. Function can be either function value, or function lambda, or a name of an existing
defined function. In case function needs arguments to be called with, they should be supplied after the function
name, or value. Optional `executor` identifier is to place the task in a specific queue identified by this value.
The default thread value is the `null` thread. There is no limits on number of parallel tasks for any executor,
so using different queues is solely for synchronization purposes. Also, since `task` function knows how many extra
arguments it requires, the use of tailing optional parameter for the custom executor thread pool is not ambiguous.
name, or value. `executor` identifier in `task_thread`, places the task in a specific queue identified by this value.
The default thread value is the `null` thread. There are no limits on number of parallel tasks for any executor,
so using different queues is solely for synchronization purposes.
<pre>
task( _() -> print('Hello Other World') ) => Runs print command on a separate thread
foo(a, b) -> print(a+b); task('foo',2,2) => Uses existing function definition to start a task
task('foo',3,5,'temp'); => runs function foo with a different thread executor, identified as 'temp'
a = 3; task( _(outer(a), b) -> foo(a,b), 5, 'temp')
task_thread('temp', 'foo',3,5); => runs function foo with a different thread executor, identified as 'temp'
a = 3; task_thread('temp', _(outer(a), b) -> foo(a,b), 5)
=> Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter.
</pre>
@@ -1274,10 +1273,11 @@ call creates new scope for variables inside `expr`, so all non-global variables
scope. All parameters are passed by value to the new scope, including lists and other containers, however their
copy will be shallow.
The function returns its name as a string, which means it can be used to call it later with the `call` function
The function returns itself as a first class object, which means it can be used to call it later with the `call` function
Using `_` as the function name creates anonymous function, so each time `_` function is defined, it will be given
a unique name, which you can pass somewhere else to get this function `call`ed.
a unique name, which you can pass somewhere else to get this function `call`ed. Anonymous functions can only be called
by their value and `call` method.
<pre>
a(lst) -> lst+=1; list = l(1,2,3); a(list); a(list); list // => [1,2,3]
@@ -1294,7 +1294,7 @@ arguments to a tuple which is used by map constructor as a key-value pair:
</pre>
This means that it is not possible to define literally a set of inline function, however a set of functions can still
be created by adding elements to an empty set, and building it this way.
be created by adding elements to an empty set, and building it this way. That's a tradeoff for having a cool map initializer.
### `outer(arg)`
@@ -1319,6 +1319,23 @@ a(lst) -> lst+=1; list = l(1,2,3); list=a(list); list=a(list); list // => [1,2,
Ability to combine more statements into one expression, with functions, passing parameters, and global and outer
scoping allow to organize even larger scripts
### `Operator ...`
Defines a function argument to represent a variable length argument list of whatever arguments are left
from the argument call list, also known as a varargs. There can be only one defined vararg argument in a function signature.
Technically, it doesn't matter where is it, but it looks best at the butt side of it.
<pre>
foo(a, b, c) -> ... # fixed argument function, call foo(1, 2, 3)
foo(a, b, ... c) -> ... # c is now representing the variable argument part
foo(1, 2) # a:1, b:2, c:[]
foo(1, 2, 3) # a:1, b:2, c:[3]
foo(1, 2, 3, 4) # a:1, b:2, c:[3, 4]
foo(... x) -> ... # all arguments for foo are included in the list
</pre>
### `import(module_name, symbols ...)`
Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use
@@ -2155,13 +2172,13 @@ structures and features. List of available options and names that you can use de
with minecraft 1.16.1 and below or 1.16.2 and above since in 1.16.2 Mojang has added JSON support for worldgen features
meaning that since 1.16.2 - they have official names that can be used by datapacks and scarpet
### Previous Structure Names, including variants (MC 1.16.1 and below)
### Previous Structure Names, including variants (as of MC 1.16.3)
* `'monument'`: Ocean Monument. Generates at fixed Y coordinate, surrounds itself with water.
* `'fortress'`: Nether Fortress. Altitude varies, but its bounded by the code.
* `'mansion'`: Woodland Mansion
* `'jungle_temple'`: Jungle Temple
* `'desert_temple'`: Desert Temple. Generates at fixed Y altitude.
* `'end_city'`: End City with Shulkers
* `'endcity'`: End City with Shulkers (in 1.16.1- as `'end_city`)
* `'igloo'`: Igloo
* `'shipwreck'`: Shipwreck
* `'shipwreck2'`: Shipwreck, beached
@@ -2185,7 +2202,7 @@ meaning that since 1.16.2 - they have official names that can be used by datapac
* `'bastion_remnant_treasure'`: Treasure room version of a piglin bastion (1.16)
* `'bastion_remnant_bridge'` : Bridge version of a piglin bastion (1.16)
### Feature Names (mc1.16.1-)
### Feature Names (as of mc1.16.1)
* `'oak'`
* `'oak_beehive'`: oak with a hive (1.15+).
@@ -2249,13 +2266,13 @@ meaning that since 1.16.2 - they have official names that can be used by datapac
* `'twisting_vines'` (1.16)
* `'basalt_pillar'` (1.16)
### Standard Structures (1.16.2+)
### Standard Structures (as of 1.16.2+)
`'bastion_remnant'`, `'buried_treasure'`, `'desert_pyramid'`, `'endcity'`, `'fortress'`, `'igloo'`,
`'jungle_pyramid'`, `'mansion'`, `'mineshaft'`, `'monument'`, `'nether_fossil'`, `'ocean_ruin'`,
`'pillager_outpost'`, `'ruined_portal'`, `'shipwreck'`, `'stronghold'`, `'swamp_hut'`, `'village'`
### Structure Variants (1.16.2+)
### Structure Variants (as of 1.16.2+)
`'bastion_remnant'`, `'buried_treasure'`, `'desert_pyramid'`, `'end_city'`, `'fortress'`, `'igloo'`,
`'jungle_pyramid'`, `'mansion'`, `'mineshaft'`, `'mineshaft_mesa'`, `'monument'`, `'nether_fossil'`,
@@ -2264,7 +2281,7 @@ meaning that since 1.16.2 - they have official names that can be used by datapac
`'ruined_portal_swamp'`, `'shipwreck'`, `'shipwreck_beached'`, `'stronghold'`, `'swamp_hut'`,
`'village_desert'`, `'village_plains'`, `'village_savanna'`, `'village_snovy'`, `'village_taiga'`
### World Generation Features (1.16.2+)
### World Generation Features (as of 1.16.2+)
`'acacia'`, `'bamboo'`, `'bamboo_light'`, `'bamboo_vegetation'`, `'basalt_blobs'`, `'basalt_pillar'`,
`'birch'`, `'birch_bees_0002'`, `'birch_bees_002'`, `'birch_bees_005'`, `'birch_other'`, `'birch_tall'`,
@@ -3397,6 +3414,7 @@ __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) /
__on_player_interacts_with_block(player, hand, block, face, hitvec) //right click on a block resulted in activation of said block
__on_player_places_block(player, item_tuple, hand, block) // player have just placed the block.
__on_player_interacts_with_entity(player, entity, hand)
__on_player_collides_with_entity(player, entity)
__on_player_chooses_recipe(player, recipe, full_stack)
__on_player_switches_slot(player, from, to)
__on_player_swaps_hands(player)
@@ -63,10 +63,11 @@ call creates new scope for variables inside `expr`, so all non-global variables
scope. All parameters are passed by value to the new scope, including lists and other containers, however their
copy will be shallow.
The function returns its name as a string, which means it can be used to call it later with the `call` function
The function returns itself as a first class object, which means it can be used to call it later with the `call` function
Using `_` as the function name creates anonymous function, so each time `_` function is defined, it will be given
a unique name, which you can pass somewhere else to get this function `call`ed.
a unique name, which you can pass somewhere else to get this function `call`ed. Anonymous functions can only be called
by their value and `call` method.
<pre>
a(lst) -> lst+=1; list = l(1,2,3); a(list); a(list); list // => [1,2,3]
@@ -83,7 +84,7 @@ arguments to a tuple which is used by map constructor as a key-value pair:
</pre>
This means that it is not possible to define literally a set of inline function, however a set of functions can still
be created by adding elements to an empty set, and building it this way.
be created by adding elements to an empty set, and building it this way. That's a tradeoff for having a cool map initializer.
### `outer(arg)`
@@ -108,6 +109,23 @@ a(lst) -> lst+=1; list = l(1,2,3); list=a(list); list=a(list); list // => [1,2,
Ability to combine more statements into one expression, with functions, passing parameters, and global and outer
scoping allow to organize even larger scripts
### `Operator ...`
Defines a function argument to represent a variable length argument list of whatever arguments are left
from the argument call list, also known as a varargs. There can be only one defined vararg argument in a function signature.
Technically, it doesn't matter where is it, but it looks best at the butt side of it.
<pre>
foo(a, b, c) -> ... # fixed argument function, call foo(1, 2, 3)
foo(a, b, ... c) -> ... # c is now representing the variable argument part
foo(1, 2) # a:1, b:2, c:[]
foo(1, 2, 3) # a:1, b:2, c:[3]
foo(1, 2, 3, 4) # a:1, b:2, c:[3, 4]
foo(... x) -> ... # all arguments for foo are included in the list
</pre>
### `import(module_name, symbols ...)`
Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use
+1 -1
View File
@@ -146,7 +146,7 @@ around built-in function `outer`. It adds the specified value to the function ca
like capturing lambdas in Java, but unlike java captured variables don't need to be final. Scarpet will just
attach their new values at the time of the function definition, even if they change later. Most value will be
copied, but mutable values, like maps or lists, allow to keep the 'state' with the function, allowing them to
have memory and act like objects so to speak. . Check `outer(var)` for details.
have memory and act like objects so to speak. Check `outer(var)` for details.
## Code delivery, line indicators
@@ -87,6 +87,7 @@ task_completed
task_count
task_dock
task_join
task_thread
task_value
throw
time
@@ -13,7 +13,7 @@
<Keywords name="Numbers, suffix1"></Keywords>
<Keywords name="Numbers, suffix2"></Keywords>
<Keywords name="Numbers, range"></Keywords>
<Keywords name="Operators1">; + - * / % ^ &amp;&amp; || &gt; &gt;= &lt; &lt;= == != = += -&gt; &lt;&gt; ! ~ , :</Keywords>
<Keywords name="Operators1">; + - * / % ^ &amp;&amp; || &gt; &gt;= &lt; &lt;= == != = += -&gt; &lt;&gt; ! ~ , : ...</Keywords>
<Keywords name="Operators2"></Keywords>
<Keywords name="Folders in code1, open">(</Keywords>
<Keywords name="Folders in code1, middle"></Keywords>
@@ -36,7 +36,7 @@
print return exit throw try
task task_count task_value task_join task_completed synchronize task_dock
task task_thread task_count task_value task_join task_completed synchronize task_dock
system_variable_get system_variable_set
@@ -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);
}
}
}