-
-
Notifications
You must be signed in to change notification settings - Fork 400
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
191 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
// This example implements a synthetic Rust module that is exposed to JS code. | ||
// This mirrors the `modules.rs` example but uses synthetic modules instead. | ||
|
||
use std::path::PathBuf; | ||
use std::{error::Error, path::Path}; | ||
|
||
use boa_engine::builtins::promise::PromiseState; | ||
use boa_engine::module::{ModuleLoader, SimpleModuleLoader, SyntheticModuleInitializer}; | ||
use boa_engine::object::FunctionObjectBuilder; | ||
use boa_engine::{ | ||
js_string, Context, JsArgs, JsError, JsNativeError, JsValue, Module, NativeFunction, Source, | ||
}; | ||
|
||
fn main() -> Result<(), Box<dyn Error>> { | ||
// A simple module that we want to compile from Rust code. | ||
const MODULE_SRC: &str = r#" | ||
import { pyth } from "./trig.mjs"; | ||
import * as ops from "./operations.mjs"; | ||
export let result = pyth(3, 4); | ||
export function mix(a, b) { | ||
return ops.sum(ops.mult(a, ops.sub(b, a)), 10); | ||
} | ||
"#; | ||
|
||
// This can be overriden with any custom implementation of `ModuleLoader`. | ||
let loader = &SimpleModuleLoader::new("./boa_examples/scripts/modules")?; | ||
let dyn_loader: &dyn ModuleLoader = loader; | ||
|
||
// Just need to cast to a `ModuleLoader` before passing it to the builder. | ||
let context = &mut Context::builder().module_loader(dyn_loader).build()?; | ||
|
||
// Now, create the synthetic module and insert it into the loader. | ||
let operations = create_operations_module(context); | ||
loader.insert( | ||
PathBuf::from("./boa_examples/scripts/modules") | ||
.canonicalize()? | ||
.join("operations.mjs"), | ||
operations, | ||
); | ||
|
||
let source = Source::from_reader(MODULE_SRC.as_bytes(), Some(Path::new("./main.mjs"))); | ||
|
||
// Can also pass a `Some(realm)` if you need to execute the module in another realm. | ||
let module = Module::parse(source, None, context)?; | ||
|
||
// Don't forget to insert the parsed module into the loader itself, since the root module | ||
// is not automatically inserted by the `ModuleLoader::load_imported_module` impl. | ||
// | ||
// Simulate as if the "fake" module is located in the modules root, just to ensure that | ||
// the loader won't double load in case someone tries to import "./main.mjs". | ||
loader.insert( | ||
Path::new("./boa_examples/scripts/modules") | ||
.canonicalize()? | ||
.join("main.mjs"), | ||
module.clone(), | ||
); | ||
|
||
// This uses the utility function to load, link and evaluate a module without having to deal | ||
// with callbacks. For an example demonstrating the whole lifecycle of a module, see | ||
// `modules.rs` | ||
let promise_result = module.load_link_evaluate(context)?; | ||
|
||
// Very important to push forward the job queue after queueing promises. | ||
context.run_jobs(); | ||
|
||
// Checking if the final promise didn't return an error. | ||
match promise_result.state()? { | ||
PromiseState::Pending => return Err("module didn't execute!".into()), | ||
PromiseState::Fulfilled(v) => { | ||
assert_eq!(v, JsValue::undefined()) | ||
} | ||
PromiseState::Rejected(err) => { | ||
return Err(JsError::from_opaque(err).try_native(context)?.into()) | ||
} | ||
} | ||
|
||
// We can access the full namespace of the module with all its exports. | ||
let namespace = module.namespace(context); | ||
let result = namespace.get(js_string!("result"), context)?; | ||
|
||
println!("result = {}", result.display()); | ||
|
||
assert_eq!( | ||
namespace.get(js_string!("result"), context)?, | ||
JsValue::from(5) | ||
); | ||
|
||
let mix = namespace | ||
.get(js_string!("mix"), context)? | ||
.as_callable() | ||
.cloned() | ||
.ok_or_else(|| JsNativeError::typ().with_message("mix export wasn't a function!"))?; | ||
let result = mix.call(&JsValue::undefined(), &[5.into(), 10.into()], context)?; | ||
|
||
println!("mix(5, 10) = {}", result.display()); | ||
|
||
assert_eq!(result, 35.into()); | ||
|
||
Ok(()) | ||
} | ||
|
||
// Creates the synthetic equivalent to the `./modules/operations.mjs` file. | ||
fn create_operations_module(context: &mut Context<'_>) -> Module { | ||
// We first create the function objects that will be exported by the module. More | ||
// on that below. | ||
let sum = FunctionObjectBuilder::new( | ||
context.realm(), | ||
NativeFunction::from_fn_ptr(|_, args, ctx| { | ||
args.get_or_undefined(0).add(args.get_or_undefined(1), ctx) | ||
}), | ||
) | ||
.length(2) | ||
.name(js_string!("sum")) | ||
.build(); | ||
let sub = FunctionObjectBuilder::new( | ||
context.realm(), | ||
NativeFunction::from_fn_ptr(|_, args, ctx| { | ||
args.get_or_undefined(0).sub(args.get_or_undefined(1), ctx) | ||
}), | ||
) | ||
.length(2) | ||
.name(js_string!("sub")) | ||
.build(); | ||
let mult = FunctionObjectBuilder::new( | ||
context.realm(), | ||
NativeFunction::from_fn_ptr(|_, args, ctx| { | ||
args.get_or_undefined(0).mul(args.get_or_undefined(1), ctx) | ||
}), | ||
) | ||
.length(2) | ||
.name(js_string!("mult")) | ||
.build(); | ||
let div = FunctionObjectBuilder::new( | ||
context.realm(), | ||
NativeFunction::from_fn_ptr(|_, args, ctx| { | ||
args.get_or_undefined(0).div(args.get_or_undefined(1), ctx) | ||
}), | ||
) | ||
.length(2) | ||
.name(js_string!("div")) | ||
.build(); | ||
let sqrt = FunctionObjectBuilder::new( | ||
context.realm(), | ||
NativeFunction::from_fn_ptr(|_, args, ctx| { | ||
let a = args.get_or_undefined(0).to_number(ctx)?; | ||
Ok(JsValue::from(a.sqrt())) | ||
}), | ||
) | ||
.length(1) | ||
.name(js_string!("sqrt")) | ||
.build(); | ||
|
||
Module::synthetic( | ||
// Make sure to list all exports beforehand. | ||
&[ | ||
js_string!("sum"), | ||
js_string!("sub"), | ||
js_string!("mult"), | ||
js_string!("div"), | ||
js_string!("sqrt"), | ||
], | ||
// The initializer is evaluated every time a module imports this synthetic module, | ||
// so we avoid creating duplicate objects by capturing and cloning them instead. | ||
SyntheticModuleInitializer::from_copy_closure_with_captures( | ||
|module, fns, context| { | ||
println!("Running initializer!"); | ||
module.set_export(&js_string!("sum"), fns.0.clone().into(), context)?; | ||
module.set_export(&js_string!("sub"), fns.1.clone().into(), context)?; | ||
module.set_export(&js_string!("mult"), fns.2.clone().into(), context)?; | ||
module.set_export(&js_string!("div"), fns.3.clone().into(), context)?; | ||
module.set_export(&js_string!("sqrt"), fns.4.clone().into(), context)?; | ||
Ok(()) | ||
}, | ||
(sum, sub, mult, div, sqrt), | ||
), | ||
None, | ||
context, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters