# User-defined functions and program control flow ## Writing programs with more than 1 line ### `Operator ;` To effectively write programs that have more than one line, a programmer needs way to specify a sequence of commands that execute one after another. In `scarpet` this can be achieved with `;`. Its an operator, and by separating statements with semicolons. And since whitespaces and `$` (commandline visible newline separator) sign are all treats as whitespaces, how you layout your code doesn't matter, as long as it is readable to everyone involved.
expr; expr; expr; exprNotice that the last expression is not followed by a semicolon. Since instruction separation is functional in `scarpet`, and not barely an instruction delimiter, terminating the code with a dangling operator wouldn't be valid. Having said that, since many programming languages don't care about the number of op terminators programmers use, carpet preprocessor will remove all unnecessary semicolons from scripts when compiled. In general `expr; expr; expr; expr` is equivalent to `(((expr ; expr) ; expr) ; expr)`. Result of the evaluated expression is the same as the result of the second expression, but first expression is also evaluated for side-effects
expr1 ; expr2 => expr2 // with expr1 as a side-effect## Global variables All defined functions are compiled, stored persistently, and available globally within the app. Functions can only be undefined via call to `undef('fun')`, which would erase global entry for function `fun`. Since all variables have local scope inside each function, or each command script, global variables is a way to share the global state. Any variable that is used with a name that starts with `'global_'` will be stored and accessible globally, not only inside the current scope. If used directly in the chat window with the default app, it will persist across calls to `/script` function. Like functions, which are global, global variables can only be undefined via `undef`. For apps running in `'global'` scope - all players will share the same global variables and defined functions, and with `player` scope, each player hosts its own state for each app, so function and global_variables are distinct.
/script run a() -> global_list+=1; global_list = l(1,2,3); a(); a(); global_list // => [1, 2, 3, 1, 1] /script run a(); a(); global_list // => [1, 2, 3, 1, 1, 1, 1]### `Operator ->` `->` operator has two uses - as a function definition operator and key-value initializer for maps. To organize code better than a flat sequence of operations, one can define functions. Definition is correct if has the following form
fun(args, ...) -> exprWhere `fun(args, ...)` is a function signature indicating function name, number of arguments, and their names, and expr is an expression (can be complex) that is evaluated when `fun` is called. Names in the signature don't need to be used anywhere else, other occurrences of these names will be masked in this function scope. Function call creates new scope for variables inside `expr`, so all non-global variables are not visible from the caller 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 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. 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]In case the inner function wants to operate and modify larger objects, lists from the outer scope, but not global, it needs to use `outer` function in function signature. in map construction context (directly in `m()` or `{}`), the `->` operator has a different function by converting its arguments to a tuple which is used by map constructor as a key-value pair:
{ 'foo' -> 'bar' } => {l('foo', 'bar')}
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. That's a tradeoff for having a cool map initializer.
### `outer(arg)`
`outer` function can only be used in the function signature, and it will cause an error everywhere else. It
saves the value of that variable from the outer scope and allows its use in the inner scope. This is a similar
behaviour to using outer variables in lambda function definitions from Java, except here you have to specify
which variables you want to use, and borrow
This mechanism can be used to use static mutable objects without the need of using `global_...` variables
list = l(1,2,3); a(outer(list)) -> list+=1; a(); a(); list // => [1,2,3,1,1]The return value of a function is the value of the last expression. This as the same effect as using outer or global lists, but is more expensive
a(lst) -> lst+=1; list = l(1,2,3); list=a(list); list=a(list); list // => [1,2,3,1,1]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
them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies,
but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them.
Returns full list of available symbols that could be imported from this module, which can be used to debug import
issues, and list contents of libraries.
### `call(function, args.....)`
calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly
except you can use it with function value, or name instead. This means you can pass functions to other user defined
functions as arguments and call them with `call` internally. Since function definitions return the defined
function, they can be defined in place as anonymous functions.
#### Passing function references to other modules of your application
In case a function is defined by its name, Scarpet will attempt to resolve its definition in the given module and its imports,
meaning if the call is in a imported library, and not in the main module of your app, and that function is not visible from the
library perspective, but in the app, it won't be call-able. In case you pass a function name to a separate module in your app,
it should import back that method from the main module for visibility.
Check an example of a problematic code of a library that expects a function value as a passed argument and how it is called in
the parent app:
```
//app.sc
import('lib', 'callme');
foo(x) -> x*x;
test() -> callme('foo' , 5);
```
```
//lib.scl
callme(fun, arg) -> call(fun, arg);
```
In this case `'foo'` will fail to dereference in `lib` as it is not visible by name. In tightly coupled modules, where `lib` is just
a component of your `app` you can use circular import to acknowledge the symbol from the other module (pretty much like
imports in Java classes), and that solves the issue but makes the library dependent on the main app:
```
//lib.scl
import('app','foo');
callme(fun, arg) -> call(fun, arg);
```
You can circumvent that issue by explicitly dereferencing the local function where it is used as a lambda argument created
in the module in which the requested function is visible:
```
//app.sc
import('lib', 'callme');
foo(x) -> x*x;
test() -> callme(_(x) -> foo(x), 5);
```
```
//lib.scl
callme(fun, arg) -> call(fun, arg);
```
Or by passing an explicit reference to the function, instead of calling it by name:
```
//app.sc
import('lib', 'callme');
global_foohandler = (foo(x) -> x*x);
test() -> callme(global_foohandler, 5);
```
Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not
creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for
programmers with their own lambda arguments
my_map(list, function) -> map(list, call(function, _)); my_map(l(1,2,3), _(x) -> x*x); // => [1,4,9] profile_expr(my_map(l(1,2,3), _(x) -> x*x)); // => ~32000 sq(x) -> x*x; profile_expr(my_map(l(1,2,3), 'sq')); // => ~36000 sq = (_(x) -> x*x); profile_expr(my_map(l(1,2,3), sq)); // => ~36000 profile_expr(map(l(1,2,3), _*_)); // => ~80000## Control flow ### `return(expr?)` Sometimes its convenient to break the organized control flow, or it is not practical to pass the final result value of a function to the last statement, in this case a return statement can be used If no argument is provided - returns null value.
def() -> ( expr1; expr2; return(expr3); // function terminates returning expr3 expr4; // skipped expr5 // skipped )In general its cheaper to leave the last expression as a return value, rather than calling returns everywhere, but it would often lead to a messy code. ### `exit(expr?)` It terminates entire program passing `expr` as the result of the program execution, or null if omitted. ### `try(expr, id_filter?, catch_expr(_, _msg)?, id_filter2?, catch_expr2(_, _msg), ...)` `try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with `_` set to the id of the exception and `_msg` set to the exception message. If an `id_filter` is present, only exceptions with an id equal to that value will be caught, and any other exception will continue up the stack. You can specify multiple filters and their respective `catch_expr` to be able to catch different exceptions within the same code. When an exception occurs, the script will try to find an equality with every provided filter in order. Therefore, even if the caught exception matches multiple filters, only the first matching block will be executed. It is recommended to filter the exceptions to catch to be able to distinguish from an exception that is known that may happen from one that shouldn't. This mechanic accepts skipping catch expression - then try returns `null`. This mechanism allows to terminate large portion of a convoluted call stack and continue program execution. A `try` block will not only catch any exception whose id match the `id_filter`, but also any exception that has a parent with their id being that one, recursively. The `try` function allows you to catch some Scarpet exceptions for those cases when trying to get things like items, blocks, biomes or dimensions from registries, that may have been modified by datapacks, resourcepacks or other mods, or when an error that is out of the scope of the programmer occurs, such as problems when reading files. In this documentation, those are documented in at least most of the functions that throw them, and the current exception hierarchy is the following: - `exception`: This is the base exception. Every exception has this as its top-level parent - `value_exception`: This is the parent for any exception that occurs due to a provided value for something not being present in the game - `unknown_item`: Happens when a specified item doesn't exist - `unknown_block`: Happens when a specified block doesn't exist - `unknown_biome`: Happens when a specified biome doesn't exist - `unknown_sound`: Happens when a specified sound doesn't exist - `unknown_particle`: Happens when a specified particle doesn't exist - `unknown_poi_type`: Happens when a specified POI type doesn't exist - `unknown_dimension`: Happens when a specified dimension doesn't exist - `unknown_structure`: Happens when a specified structure doesn't exist - `unknown_criterion`: Happens when a specified scoreboard criterion doesn't exist - `file_read_exception`: This is the parent for any exception that occurs due to an error while reading a file, including appdata load - `nbt_read_exception`: Happens when there is an exception while reading an NBT file - `json_read_exception`: Happens when there is an exception while reading a JSON file - `user_defined_exception`: This is the parent for any exception thrown by the below `throw` function, except for those that explicitly declare their parent to be one of the above ### `throw(id?, message?, parent?)` Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will pass `null` as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is unhandled. By default, the parent of any exception thrown by this function is `user_defined_exception`. You can specify the `parent` to be either an already existing exception from the list above, in which case your exception will not inherit from `user_defined_exception`, or specify a custom one, in which case your exception will inherit from `exception` > `user_defined_exception` > `parent` > `id` ### `if(cond, expr, cond?, expr?, ..., default?)` If statement is a function that takes a number of conditions that are evaluated one after another and if any of them turns out true, its `expr` gets returned, otherwise, if all conditions fail, the return value is `default` expression, or `null` if default is skipped `if` function is equivalent to `if (cond) expr; else if (cond) expr; else default;` from Java, just in a functional form