diff --git a/Cargo.lock b/Cargo.lock index 6103bf72c5d..35c14f3ff79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,13 @@ dependencies = [ "structopt", ] +[[package]] +name = "boa_examples" +version = "0.11.0" +dependencies = [ + "Boa", +] + [[package]] name = "boa_tester" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index 6dbd3e939c7..049e7daad8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "boa_wasm", "boa_tester", "boa_unicode", + "boa_examples", ] # The release profile, used for `cargo build --release`. diff --git a/boa_examples/Cargo.toml b/boa_examples/Cargo.toml new file mode 100644 index 00000000000..527eaba2744 --- /dev/null +++ b/boa_examples/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "boa_examples" +version = "0.11.0" +authors = ["boa-dev"] +repository = "https://github.com/boa-dev/boa" +license = "Unlicense/MIT" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +Boa = { path = "../boa" } diff --git a/boa_examples/scripts/calc.js b/boa_examples/scripts/calc.js new file mode 100644 index 00000000000..edaad6a64b4 --- /dev/null +++ b/boa_examples/scripts/calc.js @@ -0,0 +1,14 @@ +module.exports = { + add: function(a, b){ + return a + b; + }, + subtract: function(a, b){ + return a - b; + }, + multiply: function(a, b){ + return a * b; + }, + divide: function(a, b){ + return a/b; + } +} \ No newline at end of file diff --git a/boa_examples/scripts/calctest.js b/boa_examples/scripts/calctest.js new file mode 100644 index 00000000000..dcb4d7970d3 --- /dev/null +++ b/boa_examples/scripts/calctest.js @@ -0,0 +1,9 @@ + +//load module +let calc = require('./scripts/calc.js'); + +console.log('Using calc module'); +console.log('Add: ' + calc.add(3, 3)); +console.log('Subtract: ' + calc.subtract(3, 3)); +console.log('Multiply: ' + calc.multiply(3, 3)); +console.log('Divide: ' + calc.divide(3, 3)); \ No newline at end of file diff --git a/boa_examples/scripts/enhancedglobal.js b/boa_examples/scripts/enhancedglobal.js new file mode 100644 index 00000000000..8d53f694995 --- /dev/null +++ b/boa_examples/scripts/enhancedglobal.js @@ -0,0 +1,11 @@ +//access custom global variable +console.log("Custom global: " + customstring); + +//call a custom global function with arguments +console.log("Custom function: " + rusty_hello("Boa! Boa!")); + +//access a custom global object and call a member function of that object +let a = 5; +let b = 5; +let result = rusty_obj.add(a, b); +console.log("Custom object: Result from rusty_obj.add() : " + result); \ No newline at end of file diff --git a/boa_examples/scripts/helloworld.js b/boa_examples/scripts/helloworld.js new file mode 100644 index 00000000000..97c29079268 --- /dev/null +++ b/boa_examples/scripts/helloworld.js @@ -0,0 +1 @@ +console.log("Hello World from JS file!"); \ No newline at end of file diff --git a/boa_examples/src/enhancedglobal.rs b/boa_examples/src/enhancedglobal.rs new file mode 100644 index 00000000000..13c2e031150 --- /dev/null +++ b/boa_examples/src/enhancedglobal.rs @@ -0,0 +1,59 @@ +use std::{fs::read_to_string}; + +use boa::{ + exec::Interpreter, + forward, + realm::Realm, + builtins::value::Value, + builtins::value::ResultValue, + builtins::function::Function +}; + +pub fn run(){ + let js_file_path = "./scripts/enhancedglobal.js"; + let buffer = read_to_string(js_file_path); + + if buffer.is_err(){ + println!("Error: {}", buffer.unwrap_err()); + return; + } + + //Creating the execution context + let ctx = Realm::create(); + + //Adding a custom global variable + ctx.global_obj.set_field("customstring", "Hello! I am a custom global variable"); + + //Adding a custom global function + let rfn = Function::builtin(Vec::new(), rusty_hello); + ctx.global_obj.set_field("rusty_hello", Value::from_func(rfn)); + + //Adding s custom object + let gobj = Value::new_object(Some(&ctx.global_obj)); + let addfn = Function::builtin(Vec::new(), add); + gobj.set_field("add", Value::from_func(addfn)); + ctx.global_obj.set_field("rusty_obj", gobj); + + //Instantiating the engien with the execution context + let mut engine = Interpreter::new(ctx); + + //Loading, parsing and executing the JS code from the source file + let error_string = forward(&mut engine, &buffer.unwrap()); + if error_string != "undefined"{ + println!("Error parsing script: {}", error_string); + } +} + +//Custom function callable from JS +fn rusty_hello(_:&Value, args:&[Value], _:&mut Interpreter) -> ResultValue{ + let arg = args.get(0).unwrap(); + let val = format!("Hello from Rust! You passed {}", arg); + return ResultValue::from(Ok(Value::from(val))); +} + +//Function appended as property of a custom global object, callable from JS +fn add(_:&Value, args:&[Value], _engine:&mut Interpreter) -> ResultValue{ + let arg0 = args.get(0).unwrap(); + let arg1 = args.get(1).unwrap(); + return ResultValue::from(Ok(Value::from(arg0.to_integer() + arg1.to_integer()))); +} \ No newline at end of file diff --git a/boa_examples/src/loadfile.rs b/boa_examples/src/loadfile.rs new file mode 100644 index 00000000000..bead31613d5 --- /dev/null +++ b/boa_examples/src/loadfile.rs @@ -0,0 +1,38 @@ +use std::fs::read_to_string; + +use boa::{exec::Executable, parse, Context}; + +pub fn run() { + let js_file_path = "./scripts/helloworld.js"; + + match read_to_string(js_file_path) { + Ok(src) => { + // Instantiate the execution context + let mut context = Context::new(); + + // Parse the source code + let expr = match parse(src, false) { + Ok(res) => res, + Err(e) => { + // Pretty print the error + eprintln!( + "Uncaught {}", + context + .throw_syntax_error(e.to_string()) + .expect_err("interpreter.throw_syntax_error() did not return an error") + .display() + ); + + return; + } + }; + + // Execute the JS code read from the source file + match expr.run(&mut context) { + Ok(v) => println!("{}", v.display()), + Err(e) => eprintln!("Uncaught {}", e.display()), + } + } + Err(msg) => eprintln!("Error: {}", msg), + } +} diff --git a/boa_examples/src/loadstring.rs b/boa_examples/src/loadstring.rs new file mode 100644 index 00000000000..17e2d2ddd46 --- /dev/null +++ b/boa_examples/src/loadstring.rs @@ -0,0 +1,31 @@ +use boa::{exec::Executable, parse, Context}; + +pub fn run() { + let js_code = "console.log('Hello World from a JS code string!')"; + + // Instantiate the execution context + let mut context = Context::new(); + + // Parse the source code + let expr = match parse(js_code, false) { + Ok(res) => res, + Err(e) => { + // Pretty print the error + eprintln!( + "Uncaught {}", + context + .throw_syntax_error(e.to_string()) + .expect_err("interpreter.throw_syntax_error() did not return an error") + .display() + ); + + return; + } + }; + + // Execute the JS code read from the source file + match expr.run(&mut context) { + Ok(v) => println!("{}", v.display()), + Err(e) => eprintln!("Uncaught {}", e.display()), + } +} diff --git a/boa_examples/src/main.rs b/boa_examples/src/main.rs new file mode 100644 index 00000000000..bff68875582 --- /dev/null +++ b/boa_examples/src/main.rs @@ -0,0 +1,29 @@ +mod loadstring; +mod loadfile; +mod returnval; +mod enhancedglobal; +mod modulehandler; + +fn main() { + println!("\r\n"); + + //example that loads, parses and executs a JS code string + loadstring::run(); + println!("\r\n"); + + //example that loads, parses and executs JS code from a source file (./scripts/helloworld.js) + loadfile::run(); + println!("\r\n"); + + //example that loads, parses and executs JS code and uses the return value + returnval::run(); + println!("\r\n"); + + //example that enhances the global object with custom values, objects, functions + enhancedglobal::run(); + println!("\r\n"); + + //example that implements a custom module handler which mimics (require / module.exports) pattern + modulehandler::run(); + println!("\r\n"); +} diff --git a/boa_examples/src/modulehandler.rs b/boa_examples/src/modulehandler.rs new file mode 100644 index 00000000000..292854ff972 --- /dev/null +++ b/boa_examples/src/modulehandler.rs @@ -0,0 +1,66 @@ +use std::fs::read_to_string; + +use boa::{ + builtins::function::Function, builtins::value::ResultValue, builtins::value::Value, + exec::Interpreter, forward, realm::Realm, +}; + +pub fn run() { + let js_file_path = "./scripts/calctest.js"; + let buffer = read_to_string(js_file_path); + + if buffer.is_err() { + println!("Error: {}", buffer.unwrap_err()); + return; + } + + //Creating the execution context + let ctx = Realm::create(); + + //Adding custom implementation that mimics 'require' + let requirefn = Function::builtin(Vec::new(), require); + ctx.global_obj + .set_field("require", Value::from_func(requirefn)); + + //Addming custom object that mimics 'module.exports' + let moduleobj = Value::new_object(Some(&ctx.global_obj)); + moduleobj.set_field("exports", Value::from(" ")); + ctx.global_obj.set_field("module", moduleobj); + + //Instantiating the engine with the execution context + let mut engine = Interpreter::new(ctx); + + //Loading, parsing and executing the JS code from the source file + let error_string = forward(&mut engine, &buffer.unwrap()); + if error_string != "undefined" { + println!("Error parsing script: {}", error_string); + } +} + +//Custom implementation that mimics 'require' module loader +fn require(_: &Value, args: &[Value], engine: &mut Interpreter) -> ResultValue { + let arg = args.get(0).unwrap(); + + //BUG: Dev branch seems to be passing string arguments along with quotes + let libfile = arg.to_string().replace("\"", ""); + + //Read the module source file + println!("Loading: {}", libfile); + let buffer = read_to_string(libfile); + if buffer.is_err() { + println!("Error: {}", buffer.unwrap_err()); + return ResultValue::from(Ok(Value::from(-1))); + } else { + //Load and parse the module source + forward(engine, &buffer.unwrap()); + + //Access module.exports and return as ResultValue + let module_exports = engine + .realm + .global_obj + .get_field("module") + .get_field("exports"); + let return_value = ResultValue::from(Ok(Value::from(module_exports))); + return return_value; + } +}