Compile a Script (to AST)
To repeatedly evaluate a script, compile it first with Engine::compile
into an AST
(Abstract Syntax Tree) form.
Engine::eval_ast_XXX
and Engine::run_ast_XXX
evaluate a pre-compiled AST
.
// Compile to an AST and store it for later evaluations
let ast = engine.compile("40 + 2")?;
for _ in 0..42 {
let result: i64 = engine.eval_ast(&ast)?;
println!("Answer #{i}: {result}"); // prints 42
}
Advanced users who may want to manipulate an AST
, especially the functions contained within,
should see the section on Manage AST’s for more details.
Practical Use – Header Template Scripts
Sometimes it is desirable to include a standardized header template in a script that contains pre-defined functions, constants and imported modules.
// START OF THE HEADER TEMPLATE
// The following should run before every script...
import "hello" as h;
import "world" as w;
// Standard constants
const GLOBAL_CONSTANT = 42;
const SCALE_FACTOR = 1.2;
// Standard functions
fn foo(x, y) { ... }
fn bar() { ... }
fn baz() { ... }
// END OF THE HEADER TEMPLATE
// Everything below changes from run to run
foo(bar() + GLOBAL_CONSTANT, baz() * SCALE_FACTOR)
Option 1 – The easy way
Prepend the script header template onto independent scripts and run them as a whole.
Pros: Easy!
Cons: If the header template is long, work is duplicated every time to parse it.
let header_template = "..... // scripts... .....";
for index in 0..10000 {
let user_script = db.get_script(index);
// Just merge the two scripts...
let combined_script = format!("{header_template}\n{user_script}\n");
// Run away!
let result = engine.eval::<i64>(combined_script)?;
println!("{result}");
}
Option 2 – The hard way
Option 1 requires the script header template to be recompiled every time. This can be expensive if the header is very long.
This option compiles both the script header template and independent scripts as separate AST
’s
which are then joined together to form a combined AST
.
Pros: No need to recompile the header template!
Cons: More work…
let header_template = "..... // scripts... .....";
let mut template_ast = engine.compile(header_template)?;
// If you don't want to run the template, only keep the functions
// defined inside (e.g. closures), clear out the statements.
template_ast.clear_statements();
for index in 0..10000 {
let user_script = db.get_script(index);
let user_ast = engine.compile(user_script)?;
// Merge the two AST's
let combined_ast = template_ast + user_ast;
// Run away!
let result = engine.eval_ast::<i64>(combined_ast)?;
println!("{result}");
Option 3 – The not-so-hard way
Option 1 does repeated work, option 2 requires manipulating AST
’s…
This option makes the scripted functions (not imported modules nor constants
however) available globally by first making it a module (via Module::eval_ast_as_new
)
and then loading it into the Engine
via Engine::register_global_module
.
Pros: No need to recompile the header template!
Cons: No imported modules nor constants; if the header template is changed, a new
Engine
must be created.
let header_template = "..... // scripts... .....";
let template_ast = engine.compile(header_template)?;
let template_module = Module::eval_ast_as_new(Scope::new(), &template_ast, &engine)?;
engine.register_global_module(template_module.into());
for index in 0..10000 {
let user_script = db.get_script(index);
// Run away!
let result = engine.eval::<i64>(user_script)?;
println!("{result}");
}