Skip to content

Commit

Permalink
Add example
Browse files Browse the repository at this point in the history
  • Loading branch information
jedel1043 committed Oct 3, 2023
1 parent d8ba9e3 commit 0094907
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 4 deletions.
1 change: 0 additions & 1 deletion boa_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
clippy::nursery,
)]
#![allow(
unused_crate_dependencies,
clippy::option_if_let_else,
clippy::redundant_pub_crate
)]
Expand Down
3 changes: 1 addition & 2 deletions boa_engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,7 @@ pub trait JsArgs {
/// `args.get(n).cloned().unwrap_or_default()` or
/// `args.get(n).unwrap_or(&undefined)`.
///
/// This returns a reference for efficiency, in case you only need to call methods of `JsValue`,
/// so try to minimize calling `clone`.
/// This returns a reference for efficiency, in case you only need to call methods of `JsValue`.
fn get_or_undefined(&self, index: usize) -> &JsValue;
}

Expand Down
2 changes: 1 addition & 1 deletion boa_examples/src/bin/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// 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
// 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
Expand Down
180 changes: 180 additions & 0 deletions boa_examples/src/bin/synthetic.rs
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,
)
}

0 comments on commit 0094907

Please sign in to comment.