From 59b752e6c9fdffdc58521cfd26528b38a0612cc8 Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 4 Dec 2020 22:02:01 -0400 Subject: [PATCH] better messaging on handling of unknown functions. fixes #606 --- docs/scarpet/api/Auxiliary.md | 3 ++ docs/scarpet/api/Entities.md | 4 ++ docs/scarpet/api/Events.md | 4 ++ .../language/FunctionsAndControlFlow.md | 53 ++++++++++++++++++- docs/scarpet/language/SystemFunctions.md | 3 ++ src/main/java/carpet/script/ScriptHost.java | 11 ++-- 6 files changed, 72 insertions(+), 6 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 6362b82d..af470d08 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -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 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)` Queries in-game statistics for certain values. Categories include: diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index e73c0697..844eedd7 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -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 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?)` Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index e64aad1f..b337090d 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -263,6 +263,10 @@ handle_event('tick', _() -> foo('tick happened')); // built-in event handle_event('tick', null) // nah, ima good, kthxbai +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?)` Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 2d6a8d00..3d0ad0db 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -135,7 +135,7 @@ 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 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. 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. @@ -144,9 +144,58 @@ issues, and list contents of libraries. 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. 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. +#### 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 not dereferenced in `lib` 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); +``` +But you can circumvent that issue by explictly 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 passing an explicit reference to the function, not 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 diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index 0959f243..1f3768e6 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -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. +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)` diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index b494c9de..d984673f 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -18,13 +18,10 @@ import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.function.Predicate; -import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class ScriptHost @@ -189,7 +186,13 @@ public abstract class ScriptHost public FunctionValue getAssertFunction(Module module, String 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; } private FunctionValue getFunction(Module module, String name)