Merge pull request #609 from gnembon/module_imports

better messaging on handling of unknown functions. fixes #606
This commit is contained in:
gnembon authored and GitHub committed 2020-12-04 23:22:02 -04:00
commit c5a23ad7e2
6 files changed
+71 -6

No files matched your search

+3
View File
@@ -444,6 +444,9 @@ algorithm has taken in to account last time mobs spawned.
Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end
of the tick, and they will run in order they were scheduled. of the tick, and they will run in order they were scheduled.
In case you want to schedule a function that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `statistic(player, category, entry)` ### `statistic(player, category, entry)`
Queries in-game statistics for certain values. Categories include: Queries in-game statistics for certain values. Categories include:
+4
View File
@@ -746,6 +746,10 @@ Required arguments: `entity, amount, source, attacking_entity`
It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error
when you attach an inapplicable event to it. when you attach an inapplicable event to it.
In case you want to pass an event handler that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)` ### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)`
Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle
+4
View File
@@ -263,6 +263,10 @@ handle_event('tick', _() -> foo('tick happened')); // built-in event
handle_event('tick', null) // nah, ima good, kthxbai handle_event('tick', null) // nah, ima good, kthxbai
</pre> </pre>
In case you want to pass an event handler that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `signal_event(event, target_player?, ... args?)` ### `signal_event(event, target_player?, ... args?)`
Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list
@@ -135,7 +135,7 @@ foo(... x) -> ... # all arguments for foo are included in the list
### `import(module_name, symbols ...)` ### `import(module_name, symbols ...)`
Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use
them in the current app. This include other symbols imported by these modules. Scarpet supports cicular dependencies, 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. 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 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. issues, and list contents of libraries.
@@ -144,9 +144,57 @@ issues, and list contents of libraries.
calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly 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 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. And since function definitions return the 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. 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 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 creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for
programmers with their own lambda arguments programmers with their own lambda arguments
+3
View File
@@ -122,6 +122,9 @@ 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. => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter.
</pre> </pre>
In case you want to create a task based on a function that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)` ### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)`
+7 -4
View File
@@ -18,13 +18,10 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public abstract class ScriptHost public abstract class ScriptHost
@@ -189,7 +186,13 @@ public abstract class ScriptHost
public FunctionValue getAssertFunction(Module module, String name) public FunctionValue getAssertFunction(Module module, String name)
{ {
FunctionValue ret = getFunction(module, name); FunctionValue ret = getFunction(module, name);
if (ret == null) throw new InternalExpressionException("Function "+name+" is not defined yet"); if (ret == null)
{
if (module == main)
throw new InternalExpressionException("Function '"+name+"' is not defined yet");
else
throw new InternalExpressionException("Function '"+name+"' is not defined nor visible by its name in the imported module '"+module.getName()+"'");
}
return ret; return ret;
} }
private FunctionValue getFunction(Module module, String name) private FunctionValue getFunction(Module module, String name)