Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Imports and the scriptable event handler use case combined with REPL #934

Open
tripokey opened this issue Nov 18, 2024 · 14 comments
Open

Imports and the scriptable event handler use case combined with REPL #934

tripokey opened this issue Nov 18, 2024 · 14 comments
Labels

Comments

@tripokey
Copy link

I have an issue with an application that implements the scriptable event handler use case and also provides a REPL interface:

The book recommends that imports be placed at the top of the script: https://rhai.rs/book/language/modules/import.html#admonition-place-import-statements-at-the-top

REPL implementations typically use AST::clear_statements() to prevent statements from being executed twice.

After clear_statements has been called any imports at top level will be gone from the AST causing event handler functions that depends on the global imports to fail.

One approach that I tried to circumvent this is to add the loaded script into a global module similar to how the Rhai REPL does things. However it seems that script functions in a global module cannot be directly accessed from rust side preventing me from calling them using Engine::call_fn().

Am I missing something or is this use-case simply not supported unless the imports are placed inside the event handler functions?

@schungx
Copy link
Collaborator

schungx commented Nov 19, 2024

However it seems that script functions in a global module cannot be directly accessed from rust side preventing me from calling them using Engine::call_fn().

I'm pretty sure you can call the scripted functions in global... That's how the Rhai REPL does it...

If all your imports are static then just load them once into the engine. If they are volatile, then yes you'd want to keep the loaded modules that you imported before...

The Rhai REPL currently forces you to reimport the modules on every run.

@tripokey
Copy link
Author

Given the following changes to the Rhai REPL:

diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs
index 6a1215c7..3a0d161f 100644
--- a/src/bin/rhai-repl.rs
+++ b/src/bin/rhai-repl.rs
@@ -286,6 +286,10 @@ mod sample_functions {
         *x += y + (z.len() as INT);
         println!("{x} {y} {z}");
     }
+
+    pub fn test3(x: INT, y: INT) {
+        println!("{x} {y}");
+    }
 }

 fn main() {
@@ -596,6 +600,15 @@ fn main() {

         // Throw away all the statements, leaving only the functions
         main_ast.clear_statements();
+
+        engine
+            .call_fn::<()>(
+                &mut scope,
+                &main_ast,
+                "test3",
+                (1 as rhai::INT, 2 as rhai::INT),
+            )
+            .expect("Failed...");
     }

     rl.save_history(HISTORY_FILE)

The functions in the global module seems to be available from the script, but they do not seem to be directly callable from Rust:

Rhai REPL tool (version 1.20.0)
===============================
help       => print this help
quit, exit => quit
keys       => print list of key bindings
history    => print lines history
!!         => repeat the last history line
!<#>       => repeat a particular history line
!<text>    => repeat the last history line starting with some text
!?<text>   => repeat the last history line containing some text
scope      => print all variables in the scope
strict     => toggle on/off Strict Variables Mode
optimize   => toggle on/off script optimization
ast        => print the last AST (optimized)
astu       => print the last raw, un-optimized AST

press Ctrl-Enter or end a line with `\`
to continue to the next line.

repl> test3(1, 3);
1 3
thread 'main' panicked at src/bin/rhai-repl.rs:611:14:
Failed...: ErrorFunctionNotFound("test3", none)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@schungx
Copy link
Collaborator

schungx commented Nov 19, 2024

You cannot call a native Rust function via call_fn. The idea is that it is much easier to just call the Rust version instead of going through Rhai.

@tripokey
Copy link
Author

Ok, here is another example with the function defined in a Rhai script which still does not work:

Executing: cargo run --release --bin rhai-repl --features rustyline -- testing.rhai

testing.rhai:

fn testing() {
	print("Works");
}

Changes:

diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs
index 6a1215c7..6959e6b7 100644
--- a/src/bin/rhai-repl.rs
+++ b/src/bin/rhai-repl.rs
@@ -596,6 +596,10 @@ fn main() {

         // Throw away all the statements, leaving only the functions
         main_ast.clear_statements();
+
+        engine
+            .call_fn::<()>(&mut scope, &main_ast, "testing", ())
+            .expect("Failed...");
     }

     rl.save_history(HISTORY_FILE)

Output:

Rhai REPL tool (version 1.20.0)
===============================
Script 'testing.rhai' loaded.

help       => print this help
quit, exit => quit
keys       => print list of key bindings
history    => print lines history
!!         => repeat the last history line
!<#>       => repeat a particular history line
!<text>    => repeat the last history line starting with some text
!?<text>   => repeat the last history line containing some text
scope      => print all variables in the scope
strict     => toggle on/off Strict Variables Mode
optimize   => toggle on/off script optimization
ast        => print the last AST (optimized)
astu       => print the last raw, un-optimized AST

press Ctrl-Enter or end a line with `\`
to continue to the next line.

repl> testing();
Works
thread 'main' panicked at src/bin/rhai-repl.rs:602:14:
Failed...: ErrorFunctionNotFound("testing", none)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@schungx
Copy link
Collaborator

schungx commented Nov 19, 2024

That would be interesting. Can you put a testing(); inside the script to see if it works?

Also you can dump the function signatures to see if testing is properly seen...

@tripokey
Copy link
Author

Sure,

Updated script:

fn testing() {
	print("Works");
}

testing();

Executing: cargo run --release --bin rhai-repl --features rustyline --features metadata -- testing.rhai

Changes:

diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs
index 6a1215c7..6959e6b7 100644
--- a/src/bin/rhai-repl.rs
+++ b/src/bin/rhai-repl.rs
@@ -596,6 +596,10 @@ fn main() {

         // Throw away all the statements, leaving only the functions
         main_ast.clear_statements();
+
+        engine
+            .call_fn::<()>(&mut scope, &main_ast, "testing", ())
+            .expect("Failed...");
     }

     rl.save_history(HISTORY_FILE)

Output:

Rhai REPL tool (version 1.20.0)
===============================
Works
Script 'testing.rhai' loaded.

help       => print this help
quit, exit => quit
keys       => print list of key bindings
history    => print lines history
!!         => repeat the last history line
!<#>       => repeat a particular history line
!<text>    => repeat the last history line starting with some text
!?<text>   => repeat the last history line containing some text
scope      => print all variables in the scope
strict     => toggle on/off Strict Variables Mode
optimize   => toggle on/off script optimization
functions  => print all functions defined
json       => output all functions to `metadata.json`
ast        => print the last AST (optimized)
astu       => print the last raw, un-optimized AST

press Ctrl-Enter or end a line with `\`
to continue to the next line.

repl> functions
test(x: i64, y: i64) -> MyType
test(x: &mut i64, y: i64, z: string)
testing()

repl> testing()
Works
thread 'main' panicked at src/bin/rhai-repl.rs:602:14:
Failed...: ErrorFunctionNotFound("testing", none)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Conclusions:

  • Works is printed once when the module is evaluated.
  • Works is printed once more when testing(); is executed from the REPL.
  • testing() is listed when the functions is printed.
  • testing() is not available to Engine::call_fn.

@schungx
Copy link
Collaborator

schungx commented Nov 19, 2024

Can you use rhai-run instead of repl?

Because the function is not in the AST.

You can define testing in the REPL and it'll work, I'm pretty sure...

@tripokey
Copy link
Author

Yes, using rhai-run would work since it does not load the file into a global module (and the REPL will also work when the function is defined through the repl> interface).

The problem I'm having is combining the event-handler pattern with a REPL wich requires that i use AST::clear_statements() which in turn removes the top-level import statements.

I tried to work around this limitation by registering the source as a global module but then discovered that it was not possible to call any of the event handler functions defined in the registered global module.

@schungx
Copy link
Collaborator

schungx commented Nov 19, 2024

So it seems call_fn may not find functions loaded into global. I'll take a look at that.

@schungx schungx added the bug label Nov 19, 2024
@tripokey
Copy link
Author

Thank you 🙂

@schungx
Copy link
Collaborator

schungx commented Nov 21, 2024

I'm sorry it was misleading. call_fn only calls functions defined within the current script.

    /// Call a script function defined in an [`AST`] with multiple arguments.

Functions inside your scripts, however, I believe should be able to call your scripts loaded into global.

@tripokey
Copy link
Author

Yes, it would be great however if there was a way to call a function from rust side that first attempts to call the function in the given AST and if the function was not found then attempt to call the function in the registered global modules.

@schungx
Copy link
Collaborator

schungx commented Nov 21, 2024

Yes, it would be great however if there was a way to call a function from rust side that first attempts to call the function in the given AST and if the function was not found then attempt to call the function in the registered global modules.

I fear that may be a breaking change...

@tripokey
Copy link
Author

Engine::call_fn returns ErrorFunctionNotFound if the AST does not contain the function, couldn't an API be exposed that calls a function defined in a global module. Then it would be up to the user to do the extra scaffolding if call_fn returns FunctionNotFound but it would at least be possible and it would be a non breaking change?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants