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

259 lines
13 KiB
Markdown

# 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, ...), get(lvalue), ':' 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
In case to simplify the access with nested objects, you can add chain of addresses to the arguments of `get` rather
than calling it multiple times. `get(get(foo,a),b)` is equivalent to `get(foo, a, b)`, or `foo:a:b`.
<pre>
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
</pre>
### `has(container, address, ...), has(lvalue)`
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`, and is cheaper to run than `get`.
Like get, it can accept multiple addresses for chains in nested containers. In this case `has(foo:a:b)` is
equivalent to `has(get(foo,a), b)` or `has(foo, a, b)`
### `delete(container, address, ...), delete(lvalue)`
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, with 0 indicating that the original value was unaffected.
Like with the `get` and `has`, `delete` can accept chained addresses, as well as l-value container access, removing
the value from the leaf of the path provided, so `delete(foo, a, b)` is the
same as `delete(get(foo,a),b)` or `delete(foo:a:b)`
Returns true, if container was changed, false, if it was left unchanged, and null if operation was invalid.
### `put(container, address, value), put(container, address, value, mode), put(lvalue, value)`
<u>**Lists**</u>
Modifies the container by replacing the value under the address with the supplied `value`. For lists, a valid
index is required, but can be negative as well to indicate positions from the end of the list. If `null` is
supplied as the address, it always means - add to the end of the list.
There are three modes that lists can have items added to them:
* `replace`(default): Replaces item under given index(address). Doesn't change the size of the array
unless `null` address is used, which is an exception and then it appends to the end
* `insert`: Inserts given element at a specified index, shifting the rest of the array to make space for the item.
Note that index of -1 points to the last element of the list, thus inserting at that position and moving the previous
last element to the new last element position. To insert at the end, use `+=` operator, or `null` address in put
* `extend`: treats the supplied value as an iterable set of values to insert at a given index, extending the list
by this amount of items. Again use `null` address/index to point to the end of the list
Due to the extra mode parameter, there is no chaining for `put`, but you can still use l-value container access to
indicate container and address, so `put(foo, key, value)` is the same as `put(foo:key, value)` or `foo:key=value`
Returns true, if container got modified, false otherwise, and null if operation was invalid.
<u>**Maps**</u>
For maps there are no modes available (yet, seems there is no reason to). It replaces the value under the supplied
key (address), or sets it if not currently present.
<u>**NBT Tags**</u>
The address for nbt values is a valid nbt path that you would use with `/data` command, and tag is any tag that
would be applicable for a given insert operation. Note that to distinguish between proper types (like integer types,
you need to use command notation, i.e. regular ints is `123`, while byte size int would be `123b` and an explicit
string would be `"5"`, so it helps that scarpet uses single quotes in his strings. Unlike for lists and maps, it
returns the number of affected nodes, or 0 if none were affected.
There are three modes that NBT tags can have items added to them:
* `replace`(default): Replaces item under given path(address). Removes them first if possible, and then adds given
element to the supplied position. The target path can indicate compound tag keys, lists, or individual elements
of the lists.
* `<N>`: Index for list insertions. Inserts given element at a specified index, inside a list specified with the
path address. Fails if list is not specified. It behaves like `insert` mode for lists, i.e. it is not removing any
of the existing elements. Use `replace` to remove and replace existing element.
* `merge`: assumes that both path and replacement target are of compound type (dictionaries, maps, `{}` types),
and merges keys from `value` with the compound tag under the path
<pre>
a = l(1, 2, 3); put(a, 1, 4); a => [1, 4, 3]
a = l(1, 2, 3); put(a, null, 4); a => [1, 2, 3, 4]
a = l(1, 2, 3); put(a, 1, 4, 'insert'); a => [1, 4, 2, 3]
a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a => [1, 2, 3, 4, 5, 6]
a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a => [1, 4, 5, 6, 2, 3]
a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
a = m(1,2,3,4); put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null}
tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag => {BlockData:{Properties:[1,2,3,4]}}
tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag => {a:[{lvl:1},{lvl:1},{lvl:1}]}
tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
=> {a:[{lvl:[1,2,1,3]},{lvl:[3,2,1,1]},{lvl:[4,5,1,6]}]}
tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl[1]', 1); tag
=> {a:[{lvl:[1,1,3]},{lvl:[3,1,1]},{lvl:[4,1,6]}]}
</pre>
## List operations
### `[value, ...?]`,`[iterator]`,`l(value, ...?)`, `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 `[]` 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 `[]`.
Internally, `[elem, ...]`(list syntax) and `l(elem, ...)`(function syntax) are equivalent. `[]` is simply translated to
`l()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `[]`
not using different kinds of brackets and not being proper operators. This means that `l(]` and `[)` are also valid
although not recommended as they will make your code far less readable.
<pre>
l(1,2,'foo') <=> [1, 2, 'foo']
l() <=> [] (empty list)
[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2] = [3, 4] => Error: l is not a variable
[foo, bar] = [3, 4]; foo==3 && bar==4 => 1
[foo, bar, baz] = [2, 4, 6]; [min(foo, bar), baz] = [3, 5]; [foo, bar, baz] => [3, 4, 5]
</pre>
In the last example `[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
<pre>
join('-',range(10)) => 0-1-2-3-4-5-6-7-8-9
join('-','foo') => foo
join('-', 'foo', 'bar') => foo-bar
</pre>
### `split(delim?, expr)`
Splits a string under `expr` by `delim` which can be a regular expression. If no delimiter is specified, it splits
by characters.
<pre>
split('foo') => [f, o, o]
split('','foo') => [f, o, o]
split('.','foo.bar') => []
split('\\.','foo.bar') => [foo, bar]
</pre>
### `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
<pre>
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]
</pre>
### `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
<pre>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]
</pre>
### `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
<pre>
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]
</pre>
### `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
<pre>
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]
range(-0.3, 0.3, 0.1) => [-0.3, -0.2, -0.1, 0, 0.1, 0.2]
range(0.3, -0.3, -0.1) => [0.3, 0.2, 0.1, -0, -0.1, -0.2]
</pre>
## 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:
### `{values, ...}`,`{iterator}`,`{key -> value, ...}`,`m(values, ...)`, `m(iterator)`, `m(l(key, value), ...))`
creates and initializes a map with supplied keys, and values. If the arguments contains a flat list, these are all
treated as keys with no value, same goes with the iterator - creates a map that behaves like a set. If the
arguments is a list of lists, they have to have two elements each, and then first is a key, and second, a value
In map creation context (directly inside `{}` or `m{}` call), `->` operator acts like a pair constructor for simpler
syntax providing key value pairs, so the invocation to `{foo -> bar, baz -> quux}` is equivalent to
`{l(foo, bar), l(baz, quux)}`, which is equivalent to somewhat older, but more traditional functional form of
`m(l(foo, bar),l(baz, quuz))`.
Internally, `{?}`(list syntax) and `m(?)`(function syntax) are equivalent. `{}` is simply translated to
`m()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `{}`
not using different kinds of brackets and not being proper operators. This means that `m(}` and `{)` are also valid
although not recommended as they will make your code far less readable.
When converting map value to string, `':'` is used as a key-value separator due to tentative compatibility with NBT
notation, meaning in simpler cases maps can be converted to NBT parsable string by calling `str()`. This however
does not guarantee a parsable output. To properly convert to NBT value, use `encode_nbt()`.
<pre>
{1, 2, 'foo'} => {1: null, 2: null, foo: null}
m() <=> {} (empty map)
{range(10)} => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null}
m(l(1, 2), l(3, 4)) <=> {1 -> 2, 3 -> 4} => {1: 2, 3: 4}
reduce(range(10), put(_a, _, _*_); _a, {})
=> {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
</pre>
### `keys(map), values(map), pairs(map)`
Returns full lists of keys, values and key-value pairs (2-element lists) for all the entries in the map