From 768c44cc1481c14a8bb45fabca2be252784d672c Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 7 Oct 2019 09:30:25 -0300 Subject: [PATCH] equality issues and docs --- .../java/carpet/script/CarpetEventServer.java | 4 +- .../java/carpet/script/CarpetExpression.java | 28 +- src/main/java/carpet/script/Expression.java | 820 +++++++++--------- .../script/value/ContainerValueInterface.java | 2 +- .../java/carpet/script/value/EntityValue.java | 4 +- .../carpet/script/value/LazyListValue.java | 2 +- .../java/carpet/script/value/ListValue.java | 4 +- .../java/carpet/script/value/MapValue.java | 4 +- .../script/value/NBTSerializableValue.java | 14 +- .../java/carpet/script/value/NullValue.java | 2 +- .../carpet/script/value/NumericValue.java | 4 +- src/main/java/carpet/script/value/Value.java | 10 + 12 files changed, 469 insertions(+), 429 deletions(-) diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index c68e9960..b1f0016c 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -327,7 +327,7 @@ public class CarpetEventServer } }, - PLAYER_RELEASED_ITEM("player_released_item",new CallbackList(3)) + PLAYER_RELEASED_ITEM("player_releases_item",new CallbackList(3)) { @Override public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) @@ -343,7 +343,7 @@ public class CarpetEventServer }, player::getCommandSource); } }, - PLAYER_FINISHED_USING_ITEM("player_finished_using_item",new CallbackList(3)) + PLAYER_FINISHED_USING_ITEM("player_finishes_using_item",new CallbackList(3)) { public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) { diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index dacd3d24..db6a5a70 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -1542,9 +1542,9 @@ public class CarpetExpression * that corresponds to the path. For specification of path attribute, consult * vanilla /data get entity command.

*

Note that calls to nbt are considerably more expensive comparing to other - * calls in Minecraft API, and should only be used when there is no other option.A returned value is of type nbt, + * calls in Minecraft API, and should only be used when there is no other option. Returned value is of type nbt, * which can be further manipulated with nbt type objects via get, put, has, delete, so try to use API calls - * first

+ * first for that.

*

Entity Modification

*

Like with entity querying, entity modifications happen through one function. Most position and movements * modifications don't work currently on players as their position is controlled by clients.

@@ -3080,22 +3080,25 @@ public class CarpetExpression *

*

Event list

*

Here is a list of events that can be handled by scarpet. This list includes prefixes required by modules to - * autoload them, but you can add any function to any event if it had required parameters:

+ * autoload them, but you can add any function to any event if it accepts required number of parameters:

*
      * __on_tick()         // can access blocks and entities in the overworld
      * __on_tick_nether()  // can access blocks and entities in the nether
      * __on_tick_ender()   // can access blocks and entities in the end
+     *
      * // player specific callbacks
+     * __on_player_uses_item(player, item_tuple, hand)  // right click action
+     * __on_player_releases_item(player, item_tuple, hand)  // client action (e.g. bow)
+     * __on_player_finishes_using_item(player, item_tuple, hand))  // server action (e.g. food), called item is from before it is used.
+     * __on_player_clicks_block(player, block, face)  // left click attack on a block
+     * __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec)
+     * __on_player_breaks_block(player, block) // called after block is broken (the caller receives previous blockstate)
+     * __on_player_interacts_with_entity(player, entity, hand)
+     * __on_player_attacks_entity(player, entity)
+     * __on_player_rides(player, forward, strafe, jumping, sneaking)
      * __on_player_jumps(player)
      * __on_player_deploys_elytra(player)
      * __on_player_wakes_up(player)
-     * __on_player_rides(player, forward, strafe, jumping, sneaking)
-     * __on_player_uses_item(player, item_tuple, hand)
-     * __on_player_clicks_block(player, block, face)
-     * __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec)
-     * __on_player_breaks_block(player, block)
-     * __on_player_interacts_with_entity(player, entity, hand)
-     * __on_player_attacks_entity(player, entity)
      * __on_player_starts_sneaking(player)
      * __on_player_stops_sneaking(player)
      * __on_player_starts_sprinting(player)
@@ -3103,7 +3106,10 @@ public class CarpetExpression
      * 
*

/script event command

*

used to display current events and bounded functions. use add_to ro register new event, or remove_from - * to unbind a specific function from an event.

+ * to unbind a specific function from an event. Function to be bounded to an event needs to have the same + * number of parameters as the action is attempting to bind to (see list above). All calls in modules + * loaded via /script load that have functions listed above will be automatically bounded and unbounded when + * script is unloaded.

* */ public void gameEventsSystem() diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index e1d3ce48..44fb59a3 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -366,29 +366,6 @@ public class Expression implements Cloneable private Map functions = new HashMap<>(); Set getFunctionNames() {return functions.keySet();} - //static final Map globalFunctions = new HashMap<>(); - - //static final Map globalVariables = new HashMap<>(); - - /*static - { - setGlobals(); - } - static void setGlobals() - { - globalVariables.put("euler", (c, t) -> euler); - globalVariables.put("pi", (c, t) -> PI); - globalVariables.put("null", (c, t) -> Value.NULL); - globalVariables.put("true", (c, t) -> Value.TRUE); - globalVariables.put("false", (c, t) -> Value.FALSE); - - //special variables for second order functions so we don't need to check them all the time - globalVariables.put("_", (c, t) -> Value.ZERO); - globalVariables.put("_i", (c, t) -> Value.ZERO); - globalVariables.put("_a", (c, t) -> Value.ZERO); - } - */ - @Override protected Expression clone() throws CloneNotSupportedException { @@ -563,6 +540,7 @@ public class Expression implements Cloneable return new ExpressionException(e, token, "Your math is wrong, "+exc.getMessage()); if (exc instanceof ExpressionException) return exc; + // unexpected really - should be caught earlier and converted to InternalExpressionException exc.printStackTrace(); return new ExpressionException(e, token, "Error while evaluating expression: "+exc); } @@ -1062,24 +1040,76 @@ public class Expression implements Cloneable * has to deal with

*

Operator Precedence

*

- * Here is the complete list of operators in scarpet including control flow operators. - * note, that commas and brackets are not operators, but behave like them: - *

- *
    - *
  • Match, Get~ .
  • - *
  • Unary + - !
  • - *
  • Exponent ^
  • - *
  • Multiplication * / %
  • - *
  • Addition + -
  • - *
  • Comparison == != > >= <= <
  • - *
  • Logical And&&
  • - *
  • Logical Or ||
  • - *
  • Assignment = += <>
  • - *
  • Definition ->
  • - *
  • Next statement;
  • - *
  • Comma ,
  • - *
  • Bracket ( )
  • - *
+ * Here is the complete list of operators in scarpet including control flow operators. + * Note, that commas and brackets are not technically operators, but part of the language, + * even if they look like them: + *

+ *
    + *
  • Match, Get~ .
  • + *
  • Unary + - !
  • + *
  • Exponent ^
  • + *
  • Multiplication * / %
  • + *
  • Addition + -
  • + *
  • Comparison == != > >= <= <
  • + *
  • Logical And&&
  • + *
  • Logical Or ||
  • + *
  • Assignment = += <>
  • + *
  • Definition ->
  • + *
  • Next statement;
  • + *
  • Comma ,
  • + *
  • Bracket ( )
  • + *
+ * + *

Get, Accessor Operator .

+ *

Operator version of the get(...) function to access elements of lists, maps, and potentially other + * containers (i.e. NBTs). It is important to distinguish from ~ operator, which is a matching operator, + * which is expected to perform some extra computations to retrieve the result, while . should be + * straightforward and immediate, and the source object should behave like a container and support full container + * API, meaning get(...), put(...), delete(...), + * and has(...) functions

+ * + *

Matching Operator ~

+ *

This operator should be understood as 'matches', 'contains', 'is_in', + * or 'find me some stuff about something else. For strings it matches the right operand as a regular + * expression to the left, returning the first match. This can be used to extract information from unparsed nbt's + * in a more convoluted way (use get(...) for more appropriate way of doing it). + * For lists it checks if an element is in the list, and returns the index of that element, + * or null if no such element was found, especially that the use of first(...) function will not + * return the index. Currently it doesn't have any special behaviour for numbers - it checks for existence of characters + * in string representation of the left operand with respect of the regular expression on the right hand side. + * string

+ *

in Minecraft API portion entity ~ feature is a shortcode for query(entity,feature) + * for queries that do not take any extra arguments.

+ *
+     * l(1,2,3) ~ 2  => 1
+     * l(1,2,3) ~ 4  => null
+     * 'foobar' ~ '.b'  => 'ob'
+     * player('*') ~ 'gnembon'  // null unless player gnembon is logged in (better to use player('gnembon') instead
+     * p ~ 'sneaking' // if p is an entity returns whether p is sneaking
+     * 
+ *

Or a longer example of an ineffective way to searching for a squid

+ *
+     * entities = entities_area('all',x,y,z,100,10,100);
+     * sid = entities ~ 'Squid';
+     * if(sid != null, run('execute as '+query(get(entities,sid),'id')+' run say I am here '+query(get(entities,sid),'pos') ) )
+     * 
+ *

Or an example to find if a player has specific enchantment on a held axe (either hand) and get its level + * (not using proper NBTs query support via get(...)):

+ *
+     * global_get_enchantment(p, ench) -> (
+     * $   for(l('main','offhand'),
+     * $      holds = query(p, 'holds', _);
+     * $      if( holds,
+     * $         l(what, count, nbt) = holds;
+     * $         if( what ~ '_axe' && nbt ~ ench,
+     * $            lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
+     * $         )
+     * $      )
+     * $   );
+     * $   lvl
+     * $);
+     * /script run global_get_enchantment(players(), 'sharpness')
+     * 
* *

Basic Arithmetic Operators + - * /

*

Allows to add the results of two expressions. If the operands resolve to numbers, the result is @@ -1145,52 +1175,7 @@ public class Expression implements Cloneable * null != false && run('kill gnembon') => 1 // gnembon dies, cheats allowed * * - *

Get, Accessor Operator .

- *

Operator version of the get(...) function to access elements of lists, nbts, and potentially other - * containers. It is important to distinguish from ~ operator, which is a matching operator, meaning it - * is expected to perform some extra computations to retrieve the result, while . should be - * straightforward and immediate.

- * - *

Matching Operator ~

- *

This operator should be understood as 'matches', 'contains' or 'is_in'. For strings it matches the right operand as a regular - * expression to the left one, returning the first match. This can be used to extract information from unparsed nbt's - * in a more efficient way. For lists it checks if an element is in the list, and returns the index of that element, - * or null if no such element was found, especially that the use of first function will not - * return the index. Currently it doesn't have any special behaviour for numbers - it checks for existence of characters - * in string representation of the left operand with respect of the regular expression on the right hand side. - * string

- *

in Minecraft API portion entity ~ feature is a shortcode for query(entity,feature) - * for queries that do not take any extra arguments.

- *
-     * l(1,2,3) ~ 2  => 1
-     * l(1,2,3) ~ 4  => null
-     * 'foobar' ~ '.b'  => 'ob'
-     * player('*') ~ 'gnembon'  // null unless player gnembon is logged in (better to use player('gnembon') instead
-     * p ~ 'sneaking' // if p is an entity returns whether p is sneaking
-     * 
- *

Or a longer example of an ineffective way to searching for a squid

- *
-     * entities = entities_area('all',x,y,z,100,10,100);
-     * sid = entities ~ 'Squid';
-     * if(sid != null, run('execute as '+query(get(entities,sid),'id')+' run say I am here '+query(get(entities,sid),'pos') ) )
-     * 
- *

Or an example to find if a player has specific enchantment on a held axe (either hand) and get its level - * (despite obvious lack of support for json NBT's):

- *
-     * global_get_enchantment(p, ench) -> (
-     * $   for(l('main','offhand'),
-     * $      holds = query(p, 'holds', _);
-     * $      if( holds,
-     * $         l(what, count, nbt) = holds;
-     * $         if( what ~ '_axe' && nbt ~ ench,
-     * $            lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
-     * $         )
-     * $      )
-     * $   );
-     * $   lvl
-     * $);
-     * /script run global_get_enchantment(players(), 'sharpness')
-     * 
+ *

Assignment Operators = <> +=

*

A set of assignment operators. All require bounded variable on the LHS, <> requires * bounded arguments on the right hand side as well (bounded, meaning being variables). Additionally they can also @@ -1569,118 +1554,22 @@ public class Expression implements Cloneable } /** - *

Lists, loops, and higher order functions

+ *

Loops, and higher order functions

*
- *

Efficient use of these functions cangreatly simplify your programs and speed them up, as these functions - * will internalize most of the operations that need to be applied on multiple values at the same time

- *

Basic list operations

- *

l(values ...), l(iterator)

- *

Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all - * elements are variables, you coujld use it to return multiple results from one function call, if that function returns - * a list of results with the same size as the l call uses. In case there is only one argument and it is - * an iterator (vanilla expression specification has range, but Minecraft API implements - * a bunch of them, like diamond), it will convert it to a proper list. Iterators can only be used in - * high order functions, and are treated as empty lists, unless unrolled with l

+ *

Efficient use of these functions can greatly simplify your programs and speed them up, as these functions + * will internalize most of the operations that need to be applied on multiple values at the same time

+ *

Loops

+ * + *

for(list,expr(_,_i),exit(_,_i)?)

+ *

Evaluates expression over list of items from the list, and can optionally stop early if + * exit expression is specified and evaluates true for a given iteration. Supplies + * _(value) and _i(iteration number) to both expr and exit. + * Returns the number of times expr was successful

*
-     * l(1,2,'foo') => [1, 2, foo]
-     * l() => [] (empty list)
-     * l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-     * l(1, 2) = l(3, 4) => Error: l is not a variable
-     * l(foo, bar) = l(3,4); foo==3 && bar==4 => 1
-     * l(foo, bar, baz) = l(2, 4, 6); l(min(foo, bar), baz) = l(3, 5); l(foo, bar, baz)  => [3, 4, 5]
-     * 
- *

In the last example l(min(foo, bar), baz) creates a valid L-value, as min(foo, bar) finds the - * lower of the variables (in this case foo) creating a valid assignable L-list of [foo, baz], and these - * values will be assigned new values

- * - *

join(delim, list), join(delim, values ...)

- *

Returns a string that contains joined elements of the list, iterator, or all values, concatenated with delim delimiter

- *
-     *     join('-',range(10))  => 0-1-2-3-4-5-6-7-8-9
-     *     join('-','foo')  => foo
-     *     join('-', 'foo', 'bar')  => foo-bar
-     * 
- * - *

split(delim, expr)

- *

Splits a string undr expr by delim which can be a regular expression

- *
-     *     split('',foo)  => [f, o, o]
-     *     split('.','foo.bar')  => []
-     *     split('\\.','foo.bar')  => [foo, bar]
-     * 
- * - *

slice(expr, from, to?)

- *

extracts a substring, or sublist (based on the type of the result of the expression under expr with starting index - * of from, and ending at to if provided, or the end, if omitted

- *
-     *     slice(l(0,1,2,3,4,5), 1, 3)  => [1, 2, 3] 
-     *     slice('foobar', 0, 1)  => 'f'
-     *     slice('foobar', 3)  => 'bar'
-     *     slice(range(10), 3, 5)  => [3, 4, 5]
-     *     slice(range(10), 5)  => [5, 6, 7, 8, 9]
-     * 
- * - * - * - *

sort(list), sort(values ...)

- *

Sorts in the default sortographical order either all arguments, or a list if its the only argument. It returns a new - * sorted list, not affecting the list passed to the argument

- *
-     * sort(3,2,1)  => [1, 2, 3]
-     * sort('a',3,11,1)  => [1, 3, 11, 'a']
-     * list = l(4,3,2,1); sort(list)  => [1, 2, 3, 4]
-     * 
- * - *

sort_key(list, key_expr)

- *

Sorts a copy of the list in the order or keys as defined by the key_expr for each element

- *
-     *     sort_key([1,3,2],_)  => [1, 2, 3]
-     *     sort_key([1,3,2],-_)  => [3, 2, 1]
-     *     sort_key(l(range(10)),rand(1))  => [1, 0, 9, 6, 8, 2, 4, 5, 7, 3]
-     *     sort_key(l(range(20)),str(_))  => [0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9]
-     * 
- * - *

range(to), range(from, to), range(from, to, step)

- *

Creates a range of numbers from from, no greater/larger than to. - * The step parameter dictates not only the increment size, but also direction (can be negative). - * The returned value is not a proper list, just the iterator - * but if for whatever reason you need a proper list with all items evaluated, use l(range(to)). - * Primarily to be used in higher order functions

- *
-     *     range(10)  => [...]
-     *     l(range(10))  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-     *     map(range(10),_*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
-     *     reduce(range(10),_a+_, 0)  => 45
-     *     range(5,10)  => [5, 6, 7, 8, 9]
-     *     range(20, 10, -2)  => [20, 18, 16, 14, 12]
-     * 
- * - *

get(value, address), element(value, address)(deprecated)

- *

Returns the value at address element from the value. - * For lists it indicates an index, use negative numbers to reach elements from the end of the list. get - * call will always be able to find the index. In case there is few items, it will loop over

- *

[Minecraft specific usecase]: In case value is of nbt type, uses addess as the nbt path to query, - * returning null, if path is not found, one value if there was one match, or list of values if result is a list. - * Returned elements can be of numerical type, string texts, or another compound nbt tags

- *
-     *     get(l(range(10)), 5)  => 5
-     *     get(l(range(10)), -1)  => 9
-     *     get(l(range(10)), 10)  => 0
-     *     get(l(range(10)), 93)  => 3
-     *     get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health
-     * 
- * - *

put(list, index, values ...), put(list, null, values ...)

- *

Modifies the list by replacing values starting from index with values. - * use negative numbers to reach elements from the end of the list. put - * call will always be able to find the index. In case there is few items, it will loop over. In case end - * of the list is reached before values run out, list is extended to accomodate for more values. in case you - * want to append at the end of the list, use null as index. returns number of elements inserted.

- *
-     *     a = l(1, 2, 3); put(a, 1, 4); a  => [1, 4, 3]
-     *     a = l(1, 2, 3); put(a, null, 4, 5, 6); a  => [1, 2, 3, 4, 5, 6]
-     *     a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(element(a, 1), 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
+     *     check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
+     *        for(range(1000000,1100000),check_prime(_))  => 7216
      * 
+ *

From which we can learn that there is 7216 primes between 1M and 1.1M

*

while(cond, limit, expr)

*

Evaluates expression expr repeatedly until condition cond becomes false, * but not more than limit times. Returns the result of the last expr evaluation, @@ -1710,6 +1599,7 @@ public class Expression implements Cloneable * * // outputs: [0, 1, 2, 3, 5, 7, 11, 13, 17, 19] * + *

Higher Order Functions

*

map(list,expr(_,_i), exit(_,_i)?)

*

Converts a list of values, to another list where each value is result of an expression * v = expr(_, _i) where _ is passed as each element of the list, and _i is @@ -1754,17 +1644,6 @@ public class Expression implements Cloneable * // ditto, but requiring at most 7 bedrock block in the 18 blocks below them * * - * - *

for(list,expr(_,_i),exit(_,_i)?)

- *

Evaluates expression over list of items from the list, and can optionally stop early if - * exit expression is specified and evaluates true for a given iteration. Supplies - * _(value) and _i(iteration number) to both expr and exit. - * Returns the number of times expr was successful

- *
-     *     check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
-     *        for(range(1000000,1100000),check_prime(_))  => 7216
-     * 
- *

From which we can learn that there is 7216 primes between 1M and 1.1M

*

reduce(list,expr(_a,_,_i), initial)

*

Applies expr for each element of the list and saves the result in _a accumulator. * Consecutive calls to expr can access that value to apply more values. You also need to specify @@ -1775,196 +1654,9 @@ public class Expression implements Cloneable * *

*/ - public void ListsLoopsAndHigherOrderFunctions() + public void LoopsAndHigherOrderFunctions() { - addFunction("l", lv -> - { - if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) - return ListValue.wrap(((LazyListValue) lv.get(0)).unroll()); - return new ListValue.ListConstructorValue(lv); - }); - addFunction("join", (lv) -> - { - if (lv.size() < 2) - throw new InternalExpressionException("join takes at least 2 arguments"); - String delimiter = lv.get(0).getString(); - List toJoin; - if (lv.size()==2 && lv.get(1) instanceof LazyListValue) - { - toJoin = ((LazyListValue) lv.get(1)).unroll(); - - } - else if (lv.size() == 2 && lv.get(1) instanceof ListValue) - { - toJoin = new ArrayList<>(((ListValue)lv.get(1)).getItems()); - } - else - { - toJoin = lv.subList(1,lv.size()); - } - return new StringValue(toJoin.stream().map(Value::getString).collect(Collectors.joining(delimiter))); - }); - - addBinaryFunction("split", (d, v) -> { - String delimiter = d.getString(); - String hwat = v.getString(); - return ListValue.wrap(Arrays.stream(hwat.split(delimiter)).map(StringValue::new).collect(Collectors.toList())); - }); - - addFunction("slice", (lv) -> { - - if (lv.size() != 2 && lv.size() != 3) - throw new InternalExpressionException("slice takes 2 or 3 arguments"); - Value hwat = lv.get(0); - long from = NumericValue.asNumber(lv.get(1)).getLong(); - long to = -1; - if (lv.size()== 3) - to = NumericValue.asNumber(lv.get(2)).getLong(); - return hwat.slice(from, to); - }); - - addFunction("sort", (lv) -> - { - List toSort = lv; - if (lv.size()==1 && lv.get(0) instanceof ListValue) - { - toSort = new ArrayList<>(((ListValue)lv.get(0)).getItems()); - } - Collections.sort(toSort); - return ListValue.wrap(toSort); - }); - - addLazyFunction("sort_key", 2, (c, t, lv) -> //get working with iterators - { - Value v = lv.get(0).evalValue(c); - if (!(v instanceof ListValue)) - throw new InternalExpressionException("First argument for sort_key should be a List"); - LazyValue sortKey = lv.get(1); - //scoping - LazyValue __ = c.getVariable("_"); - - List toSort = new ArrayList<>(((ListValue) v).getItems()); - - Collections.sort(toSort,(v1, v2) -> { - c.setVariable("_",(cc, tt) -> v1); - Value ev1 = sortKey.evalValue(c); - c.setVariable("_",(cc, tt) -> v2); - Value ev2 = sortKey.evalValue(c); - return ev1.compareTo(ev2); - }); - //revering scope - c.setVariable("_", __); - return (cc, tt) -> ListValue.wrap(toSort); - }); - - addFunction("range", (lv) -> - { - long from = 0; - long to = 0; - long step = 1; - int argsize = lv.size(); - if (argsize == 0 || argsize > 3) - throw new InternalExpressionException("range accepts from 1 to 3 arguments, not "+argsize); - to = NumericValue.asNumber(lv.get(0)).getLong(); - if (lv.size() > 1) - { - from = to; - to = NumericValue.asNumber(lv.get(1)).getLong(); - if (lv.size() > 2) - { - step = NumericValue.asNumber(lv.get(2)).getLong(); - } - } - return LazyListValue.range(from, to, step); - }); - - addFunction("m", lv -> - { - if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) - return new MapValue(((LazyListValue) lv.get(0)).unroll()); - return new MapValue(lv); - }); - - addUnaryFunction("keys", v -> { - if (v instanceof MapValue) - return new ListValue(((MapValue) v).getMap().keySet()); - throw new InternalExpressionException("keys applies only to maps"); - }); - - addUnaryFunction("values", v -> { - if (v instanceof MapValue) - return new ListValue(((MapValue) v).getMap().values()); - throw new InternalExpressionException("keys applies only to maps"); - }); - - addUnaryFunction("pairs", v -> { - if (v instanceof MapValue) - return ListValue.wrap(((MapValue) v).getMap().entrySet().stream().map( - (p) -> ListValue.of(p.getKey(), p.getValue()) - ).collect(Collectors.toList())); - throw new InternalExpressionException("pairs applies only to maps"); - }); - - addBinaryFunction("get", (container, key) -> - { - if (container instanceof ContainerValueInterface) - return ((ContainerValueInterface) container).get(key); - throw new InternalExpressionException("Cannot access elements of a non-container"); - }); - - addBinaryFunction("has", (container, key) -> - { - if (container instanceof ContainerValueInterface) - return new NumericValue(((ContainerValueInterface) container).has(key)); - throw new InternalExpressionException("Cannot access elements of a non-container"); - }); - - //Deprecated, use "get" instead, or . operator - addBinaryFunction("element", (v1, v2) -> - { - if (!(v1 instanceof ListValue)) - throw new InternalExpressionException("First argument of get should be a list"); - List items = ((ListValue)v1).getItems(); - long index = NumericValue.asNumber(v2).getLong(); - int numitems = items.size(); - long range = abs(index)/numitems; - index += (range+2)*numitems; - index = index % numitems; - return items.get((int)index); - }); - - addFunction("put", (lv) -> - { - if(lv.size()<3) - { - throw new InternalExpressionException("put takes at least three arguments, a container, address, and values to insert at that index"); - } - Value container = lv.get(0); - if (!(container instanceof ContainerValueInterface)) - { - throw new InternalExpressionException("First argument of put should be a container: list, map or nbt"); - } - Value where = lv.get(1); - Value what = lv.get(2); - if (lv.size()>3) - { - return ((ContainerValueInterface) container).put(where, what, lv.get(3)); - } - else - { - return ((ContainerValueInterface) container).put(where, what); - } - }); - - addBinaryFunction("delete", (container, address) -> - { - if (container instanceof ContainerValueInterface) - { - return ((ContainerValueInterface) container).remove(address); - } - throw new InternalExpressionException("First argument of remove should be a container: list, map or nbt"); - }); //condition and expression will get a bound 'i' @@ -2282,6 +1974,341 @@ public class Expression implements Cloneable }); } + /** + *

Lists, Maps and API support for Containers

+ *
+ *

Scarpet supports basic container types: lists and maps (aka hashmaps, dicts etc..)

+ *

Container manipulation

+ *

Here is a list of operations that work on all types of containers: lists, maps, as well as other Minecraft + * specific modifyable containers, like NBTs

+ * + *

get(container, address), '.' operator

+ *

Returns the value at address element from the value. + * For lists it indicates an index, use negative numbers to reach elements from the end of the list. get + * call will always be able to find the index. In case there is few items, it will loop over

+ *

for maps, retrieves the value under the key specified in the address or null otherwise

+ *

[Minecraft specific usecase]: In case value is of nbt type, uses address as the + * nbt path to query, + * returning null, if path is not found, one value if there was one match, or list of values if result is a list. + * Returned elements can be of numerical type, string texts, or another compound nbt tags

+ *
+     *     get(l(range(10)), 5)  => 5
+     *     get(l(range(10)), -1)  => 9
+     *     get(l(range(10)), 10)  => 0
+     *     l(range(10)).93  => 3
+     *
+     *     get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead
+     *
+     *     get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar')  => 3
+     * 
+ * + *

has(container, address)

+ *

Similar to get, but returns boolean value indicating if the given index / key / path is in the + * container. Can be used to determine if get(...)==null means the element doesn't exist, or the stored value + * for this address is null.

+ * + *

delete(container, address)

+ *

Removes specific entry from the container. For the lists - removes the element and shrinks it. For maps, it + * removes the key from the map, and for nbt - removes content from a given path. For lists and maps returns previous + * entry at the address, for nbt's - number of removed objects.

+ * + * + *

put(list, index, values ...), put(list, null, values ...)

+ *

Modifies the list by replacing values starting from index with values. + * use negative numbers to reach elements from the end of the list. put + * call will always be able to find the index. In case there is few items, it will loop over. In case end + * of the list is reached before values run out, list is extended to accomodate for more values. in case you + * want to append at the end of the list, use null as index. returns number of elements inserted.

+ *
+     *     a = l(1, 2, 3); put(a, 1, 4); a  => [1, 4, 3]
+     *     a = l(1, 2, 3); put(a, null, 4, 5, 6); a  => [1, 2, 3, 4, 5, 6]
+     *     a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(element(a, 1), 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
+     * 
+ * + *

List operations

+ *

l(values ...), l(iterator)

+ *

Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all + * elements are variables, you coujld use it to return multiple results from one function call, if that function returns + * a list of results with the same size as the l call uses. In case there is only one argument and it is + * an iterator (vanilla expression specification has range, but Minecraft API implements + * a bunch of them, like diamond), it will convert it to a proper list. Iterators can only be used in + * high order functions, and are treated as empty lists, unless unrolled with l

+ *
+     * l(1,2,'foo') => [1, 2, foo]
+     * l() => [] (empty list)
+     * l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+     * l(1, 2) = l(3, 4) => Error: l is not a variable
+     * l(foo, bar) = l(3,4); foo==3 && bar==4 => 1
+     * l(foo, bar, baz) = l(2, 4, 6); l(min(foo, bar), baz) = l(3, 5); l(foo, bar, baz)  => [3, 4, 5]
+     * 
+ *

In the last example l(min(foo, bar), baz) creates a valid L-value, as min(foo, bar) finds the + * lower of the variables (in this case foo) creating a valid assignable L-list of [foo, baz], and these + * values will be assigned new values

+ * + *

join(delim, list), join(delim, values ...)

+ *

Returns a string that contains joined elements of the list, iterator, or all values, concatenated with delim delimiter

+ *
+     *     join('-',range(10))  => 0-1-2-3-4-5-6-7-8-9
+     *     join('-','foo')  => foo
+     *     join('-', 'foo', 'bar')  => foo-bar
+     * 
+ * + *

split(delim, expr)

+ *

Splits a string undr expr by delim which can be a regular expression

+ *
+     *     split('',foo)  => [f, o, o]
+     *     split('.','foo.bar')  => []
+     *     split('\\.','foo.bar')  => [foo, bar]
+     * 
+ * + *

slice(expr, from, to?)

+ *

extracts a substring, or sublist (based on the type of the result of the expression under expr with starting index + * of from, and ending at to if provided, or the end, if omitted

+ *
+     *     slice(l(0,1,2,3,4,5), 1, 3)  => [1, 2, 3]
+     *     slice('foobar', 0, 1)  => 'f'
+     *     slice('foobar', 3)  => 'bar'
+     *     slice(range(10), 3, 5)  => [3, 4, 5]
+     *     slice(range(10), 5)  => [5, 6, 7, 8, 9]
+     * 
+ * + *

sort(list), sort(values ...)

+ *

Sorts in the default sortographical order either all arguments, or a list if its the only argument. It returns a new + * sorted list, not affecting the list passed to the argument

+ *
+     * sort(3,2,1)  => [1, 2, 3]
+     * sort('a',3,11,1)  => [1, 3, 11, 'a']
+     * list = l(4,3,2,1); sort(list)  => [1, 2, 3, 4]
+     * 
+ * + *

sort_key(list, key_expr)

+ *

Sorts a copy of the list in the order or keys as defined by the key_expr for each element

+ *
+     *     sort_key([1,3,2],_)  => [1, 2, 3]
+     *     sort_key([1,3,2],-_)  => [3, 2, 1]
+     *     sort_key(l(range(10)),rand(1))  => [1, 0, 9, 6, 8, 2, 4, 5, 7, 3]
+     *     sort_key(l(range(20)),str(_))  => [0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9]
+     * 
+ * + *

range(to), range(from, to), range(from, to, step)

+ *

Creates a range of numbers from from, no greater/larger than to. + * The step parameter dictates not only the increment size, but also direction (can be negative). + * The returned value is not a proper list, just the iterator + * but if for whatever reason you need a proper list with all items evaluated, use l(range(to)). + * Primarily to be used in higher order functions

+ *
+     *     range(10)  => [...]
+     *     l(range(10))  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+     *     map(range(10),_*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
+     *     reduce(range(10),_a+_, 0)  => 45
+     *     range(5,10)  => [5, 6, 7, 8, 9]
+     *     range(20, 10, -2)  => [20, 18, 16, 14, 12]
+     * 
+ * + *

element(list, index)(deprecated)

+ *

Legacy support for older method that worked only on lists. Please use get(...) for equivalent + * support, or . operator. Also previous unique behaviours with put on lists has been + * removed to support all type of container types.

+ * + *

Map operations

+ *

Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with null + * values as sets. Apart from container access functions, (. , get, put, has, delete), + * the following functions:

+ *
+ */ + + void BasicDataStructures() + { + addFunction("l", lv -> + { + if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) + return ListValue.wrap(((LazyListValue) lv.get(0)).unroll()); + return new ListValue.ListConstructorValue(lv); + }); + + addFunction("join", (lv) -> + { + if (lv.size() < 2) + throw new InternalExpressionException("join takes at least 2 arguments"); + String delimiter = lv.get(0).getString(); + List toJoin; + if (lv.size()==2 && lv.get(1) instanceof LazyListValue) + { + toJoin = ((LazyListValue) lv.get(1)).unroll(); + + } + else if (lv.size() == 2 && lv.get(1) instanceof ListValue) + { + toJoin = new ArrayList<>(((ListValue)lv.get(1)).getItems()); + } + else + { + toJoin = lv.subList(1,lv.size()); + } + return new StringValue(toJoin.stream().map(Value::getString).collect(Collectors.joining(delimiter))); + }); + + addBinaryFunction("split", (d, v) -> { + String delimiter = d.getString(); + String hwat = v.getString(); + return ListValue.wrap(Arrays.stream(hwat.split(delimiter)).map(StringValue::new).collect(Collectors.toList())); + }); + + addFunction("slice", (lv) -> { + + if (lv.size() != 2 && lv.size() != 3) + throw new InternalExpressionException("slice takes 2 or 3 arguments"); + Value hwat = lv.get(0); + long from = NumericValue.asNumber(lv.get(1)).getLong(); + long to = -1; + if (lv.size()== 3) + to = NumericValue.asNumber(lv.get(2)).getLong(); + return hwat.slice(from, to); + }); + + addFunction("sort", (lv) -> + { + List toSort = lv; + if (lv.size()==1 && lv.get(0) instanceof ListValue) + { + toSort = new ArrayList<>(((ListValue)lv.get(0)).getItems()); + } + Collections.sort(toSort); + return ListValue.wrap(toSort); + }); + + addLazyFunction("sort_key", 2, (c, t, lv) -> //get working with iterators + { + Value v = lv.get(0).evalValue(c); + if (!(v instanceof ListValue)) + throw new InternalExpressionException("First argument for sort_key should be a List"); + LazyValue sortKey = lv.get(1); + //scoping + LazyValue __ = c.getVariable("_"); + + List toSort = new ArrayList<>(((ListValue) v).getItems()); + + Collections.sort(toSort,(v1, v2) -> { + c.setVariable("_",(cc, tt) -> v1); + Value ev1 = sortKey.evalValue(c); + c.setVariable("_",(cc, tt) -> v2); + Value ev2 = sortKey.evalValue(c); + return ev1.compareTo(ev2); + }); + //revering scope + c.setVariable("_", __); + return (cc, tt) -> ListValue.wrap(toSort); + }); + + addFunction("range", (lv) -> + { + long from = 0; + long to = 0; + long step = 1; + int argsize = lv.size(); + if (argsize == 0 || argsize > 3) + throw new InternalExpressionException("range accepts from 1 to 3 arguments, not "+argsize); + to = NumericValue.asNumber(lv.get(0)).getLong(); + if (lv.size() > 1) + { + from = to; + to = NumericValue.asNumber(lv.get(1)).getLong(); + if (lv.size() > 2) + { + step = NumericValue.asNumber(lv.get(2)).getLong(); + } + } + return LazyListValue.range(from, to, step); + }); + + addFunction("m", lv -> + { + if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) + return new MapValue(((LazyListValue) lv.get(0)).unroll()); + return new MapValue(lv); + }); + + addUnaryFunction("keys", v -> { + if (v instanceof MapValue) + return new ListValue(((MapValue) v).getMap().keySet()); + throw new InternalExpressionException("keys applies only to maps"); + }); + + addUnaryFunction("values", v -> { + if (v instanceof MapValue) + return new ListValue(((MapValue) v).getMap().values()); + throw new InternalExpressionException("keys applies only to maps"); + }); + + addUnaryFunction("pairs", v -> { + if (v instanceof MapValue) + return ListValue.wrap(((MapValue) v).getMap().entrySet().stream().map( + (p) -> ListValue.of(p.getKey(), p.getValue()) + ).collect(Collectors.toList())); + throw new InternalExpressionException("pairs applies only to maps"); + }); + + addBinaryFunction("get", (container, key) -> + { + if (container instanceof ContainerValueInterface) + return ((ContainerValueInterface) container).get(key); + throw new InternalExpressionException("Cannot access elements of a non-container"); + }); + + addBinaryFunction("has", (container, key) -> + { + if (container instanceof ContainerValueInterface) + return new NumericValue(((ContainerValueInterface) container).has(key)); + throw new InternalExpressionException("Cannot access elements of a non-container"); + }); + + //Deprecated, use "get" instead, or . operator + addBinaryFunction("element", (v1, v2) -> + { + if (!(v1 instanceof ListValue)) + throw new InternalExpressionException("First argument of get should be a list"); + List items = ((ListValue)v1).getItems(); + long index = NumericValue.asNumber(v2).getLong(); + int numitems = items.size(); + long range = abs(index)/numitems; + index += (range+2)*numitems; + index = index % numitems; + return items.get((int)index); + }); + + addFunction("put", (lv) -> + { + if(lv.size()<3) + { + throw new InternalExpressionException("put takes at least three arguments, a container, address, and values to insert at that index"); + } + Value container = lv.get(0); + if (!(container instanceof ContainerValueInterface)) + { + throw new InternalExpressionException("First argument of put should be a container: list, map or nbt"); + } + Value where = lv.get(1); + Value what = lv.get(2); + if (lv.size()>3) + { + return ((ContainerValueInterface) container).put(where, what, lv.get(3)); + } + else + { + return ((ContainerValueInterface) container).put(where, what); + } + }); + + addBinaryFunction("delete", (container, address) -> + { + if (container instanceof ContainerValueInterface) + { + return ((ContainerValueInterface) container).delete(address); + } + throw new InternalExpressionException("First argument of delete should be a container: list, map or nbt"); + }); + } + /** * * @@ -2695,7 +2722,8 @@ public class Expression implements Cloneable Operators(); ArithmeticOperations(); SystemFunctions(); - ListsLoopsAndHigherOrderFunctions(); + LoopsAndHigherOrderFunctions(); + BasicDataStructures(); } private List shuntingYard() diff --git a/src/main/java/carpet/script/value/ContainerValueInterface.java b/src/main/java/carpet/script/value/ContainerValueInterface.java index 09e54b28..3b62d327 100644 --- a/src/main/java/carpet/script/value/ContainerValueInterface.java +++ b/src/main/java/carpet/script/value/ContainerValueInterface.java @@ -9,5 +9,5 @@ public interface ContainerValueInterface } public Value get(Value where); public boolean has(Value where); - public Value remove(Value where); + public Value delete(Value where); } diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 9dfea2be..32ad09b3 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -101,13 +101,13 @@ public class EntityValue extends Value } @Override - public boolean equals(Value v) + public boolean equals(Object v) { if (v instanceof EntityValue) { return entity.getEntityId()==((EntityValue) v).entity.getEntityId(); } - return super.equals(v); + return super.equals((Value)v); } @Override diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java index 5035c018..4a22d0de 100644 --- a/src/main/java/carpet/script/value/LazyListValue.java +++ b/src/main/java/carpet/script/value/LazyListValue.java @@ -121,7 +121,7 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato } @Override - public boolean equals(final Value o) + public boolean equals(final Object o) { return false; } diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java index fce10b91..5c94c308 100644 --- a/src/main/java/carpet/script/value/ListValue.java +++ b/src/main/java/carpet/script/value/ListValue.java @@ -212,7 +212,7 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf } @Override - public boolean equals(final Value o) + public boolean equals(final Object o) { if (o instanceof ListValue) { @@ -346,7 +346,7 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf } @Override - public Value remove(Value where) + public Value delete(Value where) { long index = NumericValue.asNumber(where).getLong(); int numitems = items.size(); diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java index 4c782770..a6137e21 100644 --- a/src/main/java/carpet/script/value/MapValue.java +++ b/src/main/java/carpet/script/value/MapValue.java @@ -120,7 +120,7 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa } @Override - public boolean equals(final Value o) + public boolean equals(final Object o) { if (o instanceof MapValue) { @@ -177,7 +177,7 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa } @Override - public Value remove(Value where) + public Value delete(Value where) { return map.remove(where); } diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 5618c869..f326be97 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -202,11 +202,11 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } @Override - public boolean equals(final Value o) + public boolean equals(final Object o) { if (o instanceof NBTSerializableValue) return getTag().equals(((NBTSerializableValue) o).getTag()); - return super.equals(o); + return super.equals((Value)o); } @Override @@ -408,17 +408,13 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } @Override - public Value remove(Value where) + public Value delete(Value where) { NbtPathArgumentType.NbtPath path = cachePath(where.getString()); Tag tag = getTag().copy(); int removed = path.remove(tag); - if (removed > 0) - { - replaceTag(tag); - return Value.TRUE; - } - return Value.FALSE; + if (removed > 0) replaceTag(tag); + return new NumericValue(removed); } public static class InventoryLocator diff --git a/src/main/java/carpet/script/value/NullValue.java b/src/main/java/carpet/script/value/NullValue.java index 91994ccd..eeef2f0d 100644 --- a/src/main/java/carpet/script/value/NullValue.java +++ b/src/main/java/carpet/script/value/NullValue.java @@ -28,7 +28,7 @@ public class NullValue extends NumericValue // TODO check nonsingleton code public NullValue() {super(0.0D);} @Override - public boolean equals(final Value o) + public boolean equals(final Object o) { return o instanceof NullValue; } diff --git a/src/main/java/carpet/script/value/NumericValue.java b/src/main/java/carpet/script/value/NumericValue.java index 48e90372..77ca3ea7 100644 --- a/src/main/java/carpet/script/value/NumericValue.java +++ b/src/main/java/carpet/script/value/NumericValue.java @@ -125,7 +125,7 @@ public class NumericValue extends Value return getString().compareTo(o.getString()); } @Override - public boolean equals(Value o) + public boolean equals(Object o) { if (o instanceof NullValue) { @@ -133,7 +133,7 @@ public class NumericValue extends Value } if (o instanceof NumericValue) { - return !this.subtract(o).getBoolean(); + return !this.subtract((Value) o).getBoolean(); } return super.equals(o); } diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index eda60bb7..c585748d 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -115,6 +115,16 @@ public abstract class Value implements Comparable, Cloneable } return getString().compareTo(o.getString()); } + + @Override // for hashmap key access + public boolean equals(final Object o) + { + if (o instanceof Value) + return this.compareTo((Value) o)==0; + return false; + } + + // for general use public boolean equals(final Value o) { return this.compareTo(o)==0;