Files
fabric-carpet/docs/scarpet/language/Operators.md
T

11 KiB

Operators

There is a number of operators you can use inside the expressions. Those could be considered generic type operators that apply to most data types. They also follow standard operator precedence, i.e. 2+2*2 is understood as 2+(2*2), not (2+2)*2, otherwise they are applied from left to right, i.e. 2+4-3 is interpreted as (2+4)-3, which in case of numbers doesn't matter, but since scarpet allows for mixing all value types the associativity would matter, and may lead to unintended effects:

Important operator is function definition -> operator. It will be covered in User Defined Functions and Program Control Flow

'123'+4-2 => ('123'+4)-2 => '1234'-2 => '134'
'123'+(4-2) => '123'+2 => '1232'
3*'foo' => 'foofoofoo'
1357-5 => 1352
1357-'5' => 137
3*'foo'-'o' => 'fff'
[1,3,5]+7 => [8,10,12]

As you can see, values can behave differently when mixed with other types in the same expression. In case values are of the same types, the result tends to be obvious, but Scarpet tries to make sense of whatever it 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 technically operators, but part of the language, even if they look like them:

  • Match, Get ~ :
  • Unary + - ! ...
  • Exponent ^
  • Multiplication * / %
  • Addition + -
  • Comparison > >= <= <
  • Equality == !=
  • 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

For certain operators and functions (get, put, delete, has, =, +=) objects can use : annotated fields as l-values, meaning construct like foo:0 = 5, would act like put(foo, 0, 5), rather than get(foo, 0) = 5, which would result in an error.

TODO: add more information about l-value behaviour.

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:

  • null if there is no match
  • matched phrase if no grouping is applied
  • matched element if one group is applied
  • list of matches if more than one grouping is applied

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.

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

[1,2,3] ~ 2  => 1
[1,2,3] ~ 4  => null

'foobar' ~ 'baz'  => null
'foobar' ~ '.b'  => 'ob'
'foobar' ~ '(.)b'  => 'o'
'foobar' ~ '((.)b)'  => ['ob', 'o']
'foobar' ~ '((.)(b))'  => ['ob', 'o', 'b']
'foobar' ~ '(?:(.)(?:b))'  => 'o'

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(['main','offhand'],
$      holds = query(p, 'holds', _);
$      if( holds,
$         [what, count, nbt] = holds;
$         if( what ~ '_axe' && nbt ~ ench,
$            lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
$         )
$      )
$   );
$   lvl
$);
/script run global_get_enchantment(player(), 'sharpness')

Basic Arithmetic Operators + - * /

Allows to add the results of two expressions. If the operands resolve to numbers, the result is arithmetic operation. In case of strings, adding or subtracting from a string results in string concatenation and removal of substrings from that string. Multiplication of strings and numbers results in repeating the string N times and division results in taking the first k'th part of the string, so that str*n/n ~ str

In case first operand is a list, either it results in a new list with all elements modified one by one with the other operand, or if the operand is a list with the same number of items - element-wise addition/subtraction. This prioritize treating lists as value containers to lists treated as vectors.

Addition with maps ({} or m()) results in a new map with keys from both maps added, if both operands are maps, adding elements of the right argument to the keys, of left map, or just adding the right value as a new key in the output map.

Examples:

2+3 => 5
'foo'+3+2 => 'foo32'
'foo'+(3+2) => 'foo5'
3+2+'bar' => '5bar'
'foo'*3 => 'foofoofoo'
'foofoofoo' / 3 => 'foo'
'foofoofoo'-'o' => 'fff'
[1,2,3]+1  => [2,3,4]
b = [100,63,100]; b+[10,0,10]  => [110,63,110]
{'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}

Just Operators % ^

The modulo and exponent (power) operators work only if both operands are numbers

pi^pi%euler  => 1.124....
-9 % 4  => -1
9 % -4  => 0 ¯\_(ツ)_/¯ Java
-3 ^ 2  => 9
-3 ^ pi => // Error

Comparison Operators == != < > <= >=

Allows to compare the results of two expressions. For numbers it is considers arithmetic order of numbers, for strings - lexicographical, nulls are always 'less' than everything else, and lists check their elements - if the sizes are different, the size matters, otherwise, pairwise comparisons for each elements are performed. The same order rules than with all these operators are used with the default sortographical order as used by sort function. All of these are true:

null == null
null != false
0 == false
1 == true
null < 0
null < -1000
1000 < 'a'
'bar' < 'foo'
3 == 3.0

Logical Operators && ||

These operator compute respective boolean operation on the operands. What it important is that if calculating of the second operand is not necessary, it won't be evaluated, which means one can use them as conditional statements. In case of success returns first positive operand (||) or last one (&&).

true || false  => true
null || false => false
false || null => null
null != false || run('kill gnembon')  // gnembon survives
null != false && run('kill gnembon')  // when cheats not allowed
null != false && run('kill gnembon')  // gnembon dies, cheats allowed

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 handle list constructors with all bounded variables, and work then as list assignment operators. When += is used on a list, it extends that list of that element, and returns the list (old == new). scarpet doesn't support currently removal of items. Removal of items can be obtaine via filter command, and reassigning it fo the same variable. Both operations would require rewriting of the array anyways.

a = 5  => a == 5
[a,b,c] = [3,4,5] => a==3, b==4, c==5
[minx,maxx] = sort(xi,xj);  // minx assumes min(xi, xj) and maxx, max(xi, xj)
[a,b,c,d,e,f] = [range(6)]; [a,b,c] <> [d,e,f]; [a,b,c,d,e,f]  => [3,4,5,0,1,2]
a = [1,2,3]; a += 4  => [1,2,3,4]
a = [1,2,3,4]; a = filter(a,_!=2)  => [1,3,4]

Unary Operators - +

Require a number, flips the sign. One way to assert it's a number is by crashing the script. gg.

-4  => -4
+4  => 4
+'4'  // Error message

Negation Operator !

flips boolean condition of the expression. Equivalent of bool(expr)==false

!true  => false
!false  => true
!null  => true
!5  => false
!l() => true
!l(null) => false

Unpacking Operator ...

Unpacks elements of a list of an iterator into a sequence of arguments in a function making so that fun(...[1, 2, 3]) is identical with fun(1, 2, 3). For maps, it unpacks them to a list of key-value pairs.

In function signatures it identifies a vararg parameter.

fun(a, b, ... rest) -> [a, b, rest]; fun(1, 2, 3, 4)    => [1, 2, [3, 4]]

Effects of ... can be surprisingly lasting. It is kept through the use of variables and function calls.

fun(a, b, ... rest) -> [a, b, ... rest]; fun(1, 2, 3, 4)    => [1, 2, 3, 4]
args() -> ... [1, 2, 3]; sum(a, b, c) -> a+b+c; sum(args())   => 6
a = ... [1, 2, 3]; sum(a, b, c) -> a+b+c; sum(a)   => 6

Unpacking mechanics can be used for list and map constriction, not just for function calls.

[...range(5), pi, ...range(5,-1,-1)]   => [0, 1, 2, 3, 4, 3.14159265359, 5, 4, 3, 2, 1, 0]
{ ... map(range(5),  _  -> _*_ )}   => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
{...{1 -> 2, 3 -> 4}, ...{5 -> 6, 7 -> 8}}   => {1: 2, 3: 4, 5: 6, 7: 8}

Fine print: unpacking of argument lists happens just before functions are evaluated. This means that in some situations, for instance when an expression is expected (map(list, expr)), or a function should not evaluate some (most!) of its arguments (if(...)), unpacking cannot be used, and will be ignored, leaving ... list identical to list. Functions that don't honor unpacking mechanics, should have no use for it at the first place (i.e. have one, or very well-defined, and very specific parameters), so some caution (prior testing) is advised. Some of these multi-argument built-in functions are if, try, sort_key, system_variable_get, synchronize, sleep, in_dimension, all container functions (get, has, put, delete), and all loop functions (while, loop, map, filter, first, all, c_for, for andreduce).