From 4278d34ca5af04cdcbaadca4d19a35e43d338cdd Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sat, 30 Sep 2023 14:52:42 -0600 Subject: [PATCH] Add example --- boa_cli/src/main.rs | 8 ++ boa_engine/src/lib.rs | 3 +- boa_examples/src/bin/modules.rs | 2 +- boa_examples/src/bin/synthetic.rs | 180 ++++++++++++++++++++++++++++++ boa_runtime/src/console/mod.rs | 1 + 5 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 boa_examples/src/bin/synthetic.rs diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 3c313e4ffde..8a6c35e5df1 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -61,6 +61,14 @@ )] #![allow(clippy::option_if_let_else, clippy::redundant_pub_crate)] +#[cfg(all( + target_arch = "x86_64", + target_os = "linux", + target_env = "gnu", + feature = "dhat" +))] +use jemallocator as _; + use boa_ast as _; mod debug; diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index 809b87f6b28..467f9418e5e 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -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; } diff --git a/boa_examples/src/bin/modules.rs b/boa_examples/src/bin/modules.rs index 21612741b83..642ea5299a6 100644 --- a/boa_examples/src/bin/modules.rs +++ b/boa_examples/src/bin/modules.rs @@ -33,7 +33,7 @@ fn main() -> Result<(), Box> { // 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 diff --git a/boa_examples/src/bin/synthetic.rs b/boa_examples/src/bin/synthetic.rs new file mode 100644 index 00000000000..0702c568f80 --- /dev/null +++ b/boa_examples/src/bin/synthetic.rs @@ -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> { + // 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, + ) +} diff --git a/boa_runtime/src/console/mod.rs b/boa_runtime/src/console/mod.rs index 8a58bf359d0..bb5f72a9c5f 100644 --- a/boa_runtime/src/console/mod.rs +++ b/boa_runtime/src/console/mod.rs @@ -135,6 +135,7 @@ impl Console { pub const NAME: &'static str = "console"; /// Initializes the `console` built-in object. + #[allow(clippy::too_many_lines)] pub fn init(context: &mut Context<'_>) -> JsObject { fn console_method( f: fn(&JsValue, &[JsValue], &Console, &mut Context<'_>) -> JsResult,