From e8b2060fb320d6024634a2df4608a5a5de4fe714 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 22 Sep 2020 04:38:08 -0300 Subject: [PATCH] variable number of arguments for user defined functions. --- docs/scarpet/Full.md | 52 +++++++++----- .../language/FunctionsAndControlFlow.md | 24 ++++++- docs/scarpet/language/Overview.md | 2 +- docs/scarpet/resources/editors/idea/1.txt | 1 + .../scarpet/resources/editors/npp/scarpet.xml | 4 +- .../java/carpet/script/CarpetScriptHost.java | 7 +- src/main/java/carpet/script/Expression.java | 4 +- .../java/carpet/script/api/Auxiliary.java | 2 +- .../script/argument/FunctionArgument.java | 13 ++-- .../carpet/script/language/Functions.java | 36 +++++++--- .../carpet/script/language/Operators.java | 2 +- .../carpet/script/language/Threading.java | 4 +- ...alue.java => FunctionAnnotationValue.java} | 8 ++- .../script/value/FunctionSignatureValue.java | 7 +- .../carpet/script/value/FunctionValue.java | 70 ++++++++++++++----- 15 files changed, 168 insertions(+), 68 deletions(-) rename src/main/java/carpet/script/value/{GlobalValue.java => FunctionAnnotationValue.java} (80%) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 5c0a4f76..e1330d1c 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -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.
 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.
 
@@ -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.
 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:
 
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. + +
+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
+
+    
+
+ ### `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) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 73d1de59..3fc9ded4 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -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.
 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:
 
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. + +
+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
+
+    
+
+ ### `import(module_name, symbols ...)` Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use diff --git a/docs/scarpet/language/Overview.md b/docs/scarpet/language/Overview.md index 0cacbd36..64c3d81d 100644 --- a/docs/scarpet/language/Overview.md +++ b/docs/scarpet/language/Overview.md @@ -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 diff --git a/docs/scarpet/resources/editors/idea/1.txt b/docs/scarpet/resources/editors/idea/1.txt index 8780d3f9..d69ced42 100644 --- a/docs/scarpet/resources/editors/idea/1.txt +++ b/docs/scarpet/resources/editors/idea/1.txt @@ -87,6 +87,7 @@ task_completed task_count task_dock task_join +task_thread task_value throw time diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index 202f0ca8..910d628c 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -13,7 +13,7 @@ - ; + - * / % ^ && || > >= < <= == != = += -> <> ! ~ , : + ; + - * / % ^ && || > >= < <= == != = += -> <> ! ~ , : ... ( @@ -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 diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 4c22c493..29c17140 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -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(); } diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 72310a9f..a042f822 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -339,7 +339,7 @@ public class Expression } }); } - public FunctionValue addContextFunction(Context context, String name, Expression expr, Tokenizer.Token token, List arguments, List outers, LazyValue code) + public FunctionValue addContextFunction(Context context, String name, Expression expr, Tokenizer.Token token, List arguments, String varArgs, List 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; diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index accb93a7..0b8141f2 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -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, diff --git a/src/main/java/carpet/script/argument/FunctionArgument.java b/src/main/java/carpet/script/argument/FunctionArgument.java index 7838a518..568d8411 100644 --- a/src/main/java/carpet/script/argument/FunctionArgument.java +++ b/src/main/java/carpet/script/argument/FunctionArgument.java @@ -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 params, int offset, boolean prematureEvaluation, boolean strict) + public static FunctionArgument findIn(Context c, Module module, List 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 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); } diff --git a/src/main/java/carpet/script/language/Functions.java b/src/main/java/carpet/script/language/Functions.java index 5afa634d..0f4907f6 100644 --- a/src/main/java/carpet/script/language/Functions.java +++ b/src/main/java/carpet/script/language/Functions.java @@ -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 args = new ArrayList<>(); List 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; }); diff --git a/src/main/java/carpet/script/language/Operators.java b/src/main/java/carpet/script/language/Operators.java index a5f5a146..906001ff 100644 --- a/src/main/java/carpet/script/language/Operators.java +++ b/src/main/java/carpet/script/language/Operators.java @@ -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); }}; diff --git a/src/main/java/carpet/script/language/Threading.java b/src/main/java/carpet/script/language/Threading.java index 781d093e..938c72ca 100644 --- a/src/main/java/carpet/script/language/Threading.java +++ b/src/main/java/carpet/script/language/Threading.java @@ -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; diff --git a/src/main/java/carpet/script/value/GlobalValue.java b/src/main/java/carpet/script/value/FunctionAnnotationValue.java similarity index 80% rename from src/main/java/carpet/script/value/GlobalValue.java rename to src/main/java/carpet/script/value/FunctionAnnotationValue.java index c38afb68..c15ab396 100644 --- a/src/main/java/carpet/script/value/GlobalValue.java +++ b/src/main/java/carpet/script/value/FunctionAnnotationValue.java @@ -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 diff --git a/src/main/java/carpet/script/value/FunctionSignatureValue.java b/src/main/java/carpet/script/value/FunctionSignatureValue.java index c5a4aa8b..e1c2f148 100644 --- a/src/main/java/carpet/script/value/FunctionSignatureValue.java +++ b/src/main/java/carpet/script/value/FunctionSignatureValue.java @@ -7,11 +7,13 @@ public class FunctionSignatureValue extends FrameworkValue private String identifier; private List arguments; private List globals; + private String varArgs; - public FunctionSignatureValue(String name, List args, List globals) + public FunctionSignatureValue(String name, List args, String varArgs, List 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 getGlobals() {return globals;} - - + public String getVarArgs() { return varArgs;} } diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java index 3762508c..88c2440a 100644 --- a/src/main/java/carpet/script/value/FunctionValue.java +++ b/src/main/java/carpet/script/value/FunctionValue.java @@ -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 outerState; private List args; + private String varArgs; private static long variantCounter = 1; private long variant; - private FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List args) + private FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List 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 args, Map outerState) + public FunctionValue(Expression expression, Tokenizer.Token token, String name, LazyValue body, List args, String varArgs, Map 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 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 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 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 val); } + if (varArgs != null) + { + List 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 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); + } + } }