feat: add dependency

This commit is contained in:
2023-01-20 22:36:19 +08:00
parent 68e8d103b4
commit cf8e579f27
644 changed files with 150099 additions and 14 deletions

View File

@@ -0,0 +1,19 @@
[package]
name = "boa_examples"
description = "Usage examples of the Boa JavaScript engine."
publish = false
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
boa_engine = { workspace = true, features = ["console"] }
boa_ast.workspace = true
boa_interner.workspace = true
boa_gc.workspace = true
boa_parser.workspace = true

View File

@@ -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;
},
};

View File

@@ -0,0 +1,8 @@
//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));

View File

@@ -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);

View File

@@ -0,0 +1 @@
console.log("Hello World from JS file!");

View File

@@ -0,0 +1,151 @@
// NOTE: this example requires the `console` feature to run correctly.
use boa_engine::{
builtins::JsArgs,
class::{Class, ClassBuilder},
error::JsNativeError,
property::Attribute,
Context, JsResult, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
// We create a new struct that is going to represent a person.
//
// We derive `Debug`, `Trace` and `Finalize`, it automatically implements `NativeObject`
// so we can pass it as an object in Javascript.
//
// The fields of the struct are not accessible by Javascript unless we create accessors for them.
/// Represents a `Person` object.
#[derive(Debug, Trace, Finalize)]
struct Person {
/// The name of the person.
name: JsString,
/// The age of the person.
age: u32,
}
// Here we implement a static method for Person that matches the `NativeFunction` signature.
//
// NOTE: The function does not have to be implemented inside Person, it can be a free function,
// or any function that matches the required signature.
impl Person {
/// Says hello if `this` is a `Person`
fn say_hello(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
// We check if this is an object.
if let Some(object) = this.as_object() {
// If it is we downcast the type to type `Person`.
if let Some(person) = object.downcast_ref::<Person>() {
// and print a message to stdout.
println!(
"Hello my name is {}, I'm {} years old",
person.name.to_std_string_escaped(),
person.age // Here we can access the native rust fields of the struct.
);
return Ok(JsValue::undefined());
}
}
// If `this` was not an object or the type of `this` was not a native object `Person`,
// we throw a `TypeError`.
Err(JsNativeError::typ()
.with_message("'this' is not a Person object")
.into())
}
}
impl Class for Person {
// We set the binding name of this function to `"Person"`.
// It does not have to be `"Person"`, it can be any string.
const NAME: &'static str = "Person";
// We set the length to `2` since we accept 2 arguments in the constructor.
//
// This is the same as `Object.length`.
// NOTE: The default value of `LENGTH` is `0`.
const LENGTH: usize = 2;
// This is what is called when we construct a `Person` with the expression `new Person()`.
fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> {
// We get the first argument. If it is unavailable we default to `undefined`,
// and then we call `to_string()`.
//
// This is equivalent to `String(arg)`.
let name = args.get_or_undefined(0).to_string(context)?;
// We get the second argument. If it is unavailable we default to `undefined`,
// and then we call `to_u32`.
//
// This is equivalent to `arg | 0`.
let age = args.get(1).cloned().unwrap_or_default().to_u32(context)?;
// We construct a new native struct `Person`
let person = Person { name, age };
Ok(person) // and we return it.
}
/// Here is where the class is initialized.
fn init(class: &mut ClassBuilder) -> JsResult<()> {
// We add a inheritable method `sayHello` with `0` arguments of length.
//
// This function is added to the `Person` prototype.
class.method("sayHello", 0, Self::say_hello);
// We add a static method `is` using a closure, but it must be convertible
// to a NativeFunction.
// This means it must not contain state, or the code won't compile.
//
// This function is added to the `Person` class.
class.static_method("is", 1, |_this, args, _ctx| {
if let Some(arg) = args.get(0) {
if let Some(object) = arg.as_object() {
// We check if the type of `args[0]` is `Person`
if object.is::<Person>() {
return Ok(true.into()); // and return `true` if it is.
}
}
}
Ok(false.into()) // Otherwise we return `false`.
});
// We add an `"inheritedProperty"` property to the prototype of `Person` with
// a value of `10` and default attribute flags `READONLY`, `NON_ENUMERABLE` and `PERMANENT`.
class.property("inheritedProperty", 10, Attribute::default());
// Finally, we add a `"staticProperty"` property to `Person` with a value
// of `"Im a static property"` and attribute flags `WRITABLE`, `ENUMERABLE` and `PERMANENT`.
class.static_property(
"staticProperty",
"Im a static property",
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::PERMANENT,
);
Ok(())
}
}
fn main() {
// First we need to create a Javascript context.
let mut context = Context::default();
// Then we need to register the global class `Person` inside `context`.
context.register_global_class::<Person>().unwrap();
// Having done all of that, we can execute Javascript code with `eval`,
// and access the `Person` class defined in Rust!
context
.eval(
r"
let person = new Person('John', 19);
person.sayHello();
if (Person.is(person)) {
console.log('person is a Person class instance.');
}
if (!Person.is('Hello')) {
console.log('\'Hello\' string is not a Person class instance.');
}
console.log(Person.staticProperty);
console.log(person.inheritedProperty);
console.log(Person.prototype.inheritedProperty === person.inheritedProperty);
",
)
.unwrap();
}

View File

@@ -0,0 +1,194 @@
// This example goes into the details on how to pass closures as functions inside Rust and call them
// from Javascript.
use std::cell::{Cell, RefCell};
use boa_engine::{
js_string,
native_function::NativeFunction,
object::{builtins::JsArray, FunctionObjectBuilder, JsObject},
property::{Attribute, PropertyDescriptor},
string::utf16,
Context, JsError, JsNativeError, JsString, JsValue,
};
use boa_gc::{Finalize, GcCell, Trace};
fn main() -> Result<(), JsError> {
// We create a new `Context` to create a new Javascript executor.
let mut context = Context::default();
// We make some operations in Rust that return a `Copy` value that we want to pass to a Javascript
// function.
let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1;
// We register a global closure function that has the name 'closure' with length 0.
context.register_global_callable(
"closure",
0,
NativeFunction::from_copy_closure(move |_, _, _| {
println!("Called `closure`");
// `variable` is captured from the main function.
println!("variable = {variable}");
println!();
// We return the moved variable as a `JsValue`.
Ok(JsValue::new(variable))
}),
);
assert_eq!(context.eval("closure()")?, 255.into());
// We have created a closure with moved variables and executed that closure
// inside Javascript!
// This struct is passed to a closure as a capture.
#[derive(Debug, Clone, Trace, Finalize)]
struct BigStruct {
greeting: JsString,
object: JsObject,
}
// We create a new `JsObject` with some data
let object = JsObject::with_object_proto(&mut context);
object.define_property_or_throw(
"name",
PropertyDescriptor::builder()
.value("Boa dev")
.writable(false)
.enumerable(false)
.configurable(false),
&mut context,
)?;
// Now, we execute some operations that return a `Clone` type
let clone_variable = BigStruct {
greeting: JsString::from("Hello!"),
object,
};
// We can use `FunctionBuilder` to define a closure with additional captures and custom property
// attributes.
let js_function = FunctionObjectBuilder::new(
&mut context,
NativeFunction::from_copy_closure_with_captures(
|_, _, captures, context| {
let mut captures = captures.borrow_mut();
let BigStruct { greeting, object } = &mut *captures;
println!("Called `createMessage`");
// We obtain the `name` property of `captures.object`
let name = object.get("name", context)?;
// We create a new message from our captured variable.
let message = js_string!(
utf16!("message from `"),
&name.to_string(context)?,
utf16!("`: "),
greeting
);
// We can also mutate the moved data inside the closure.
captures.greeting = js_string!(greeting, utf16!(" Hello!"));
println!("{}", message.to_std_string_escaped());
println!();
// We convert `message` into `JsValue` to be able to return it.
Ok(message.into())
},
// Here is where we move `clone_variable` into the closure.
GcCell::new(clone_variable),
),
)
// And here we assign `createMessage` to the `name` property of the closure.
.name("createMessage")
// By default all `FunctionBuilder`s set the `length` property to `0` and
// the `constructable` property to `false`.
.build();
// We bind the newly constructed closure as a global property in Javascript.
context.register_global_property(
// We set the key to access the function the same as its name for
// consistency, but it may be different if needed.
"createMessage",
// We pass `js_function` as a property value.
js_function,
// We assign to the "createMessage" property the desired attributes.
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
assert_eq!(
context.eval("createMessage()")?,
"message from `Boa dev`: Hello!".into()
);
// The data mutates between calls
assert_eq!(
context.eval("createMessage(); createMessage();")?,
"message from `Boa dev`: Hello! Hello! Hello!".into()
);
// We have moved `Clone` variables into a closure and executed that closure
// inside Javascript!
// ADVANCED
// If we can ensure the captured variables are not traceable by the garbage collector,
// we can pass any static closure easily.
let index = Cell::new(0i32);
let numbers = RefCell::new(Vec::new());
// We register a global closure that is not `Copy`.
context.register_global_callable(
"enumerate",
0,
// Note that it is required to use `unsafe` code, since the compiler cannot verify that the
// types captured by the closure are not traceable.
unsafe {
NativeFunction::from_closure(move |_, _, context| {
println!("Called `enumerate`");
// `index` is captured from the main function.
println!("index = {}", index.get());
println!();
numbers.borrow_mut().push(index.get());
index.set(index.get() + 1);
// We return the moved variable as a `JsValue`.
Ok(
JsArray::from_iter(
numbers.borrow().iter().cloned().map(JsValue::from),
context,
)
.into(),
)
})
},
);
// First call should return the array `[0]`.
let result = context.eval("enumerate()")?;
let object = result
.as_object()
.cloned()
.ok_or_else(|| JsNativeError::typ().with_message("not an array!"))?;
let array = JsArray::from_object(object)?;
assert_eq!(array.get(0, &mut context)?, JsValue::from(0i32));
assert_eq!(array.get(1, &mut context)?, JsValue::undefined());
// First call should return the array `[0, 1]`.
let result = context.eval("enumerate()")?;
let object = result
.as_object()
.cloned()
.ok_or_else(|| JsNativeError::typ().with_message("not an array!"))?;
let array = JsArray::from_object(object)?;
assert_eq!(array.get(0, &mut context)?, JsValue::from(0i32));
assert_eq!(array.get(1, &mut context)?, JsValue::from(1i32));
assert_eq!(array.get(2, &mut context)?, JsValue::undefined());
// We have moved non-traceable variables into a closure and executed that closure inside Javascript!
Ok(())
}

View File

@@ -0,0 +1,83 @@
// This example demonstrates how to use visitors to modify an AST. Namely, the visitors shown here
// are used to swap the operands of commutable arithmetic operations. For an example which simply
// inspects the AST without modifying it, see symbol_visitor.rs.
use boa_ast::{
expression::operator::{
binary::{ArithmeticOp, BinaryOp},
Binary,
},
visitor::{VisitWith, VisitorMut},
Expression,
};
use boa_engine::Context;
use boa_interner::ToInternedString;
use boa_parser::Parser;
use core::ops::ControlFlow;
use std::{convert::Infallible, fs::File, io::BufReader};
/// Visitor which, when applied to a binary expression, will swap the operands. Use in other
/// circumstances is undefined.
#[derive(Default)]
struct OpExchanger<'ast> {
lhs: Option<&'ast mut Expression>,
}
impl<'ast> VisitorMut<'ast> for OpExchanger<'ast> {
type BreakTy = ();
fn visit_expression_mut(&mut self, node: &'ast mut Expression) -> ControlFlow<Self::BreakTy> {
if let Some(lhs) = self.lhs.take() {
core::mem::swap(lhs, node);
ControlFlow::Break(())
} else {
self.lhs = Some(node);
// we do not traverse into the expression; we are only to be used with a binary op
ControlFlow::Continue(())
}
}
}
/// Visitor which walks the AST and swaps the operands of commutable arithmetic binary expressions.
#[derive(Default)]
struct CommutorVisitor {}
impl<'ast> VisitorMut<'ast> for CommutorVisitor {
type BreakTy = Infallible;
fn visit_binary_mut(&mut self, node: &'ast mut Binary) -> ControlFlow<Self::BreakTy> {
if let BinaryOp::Arithmetic(op) = node.op() {
match op {
ArithmeticOp::Add | ArithmeticOp::Mul => {
// set up the exchanger and swap lhs and rhs
let mut exchanger = OpExchanger::default();
assert!(matches!(
exchanger.visit_binary_mut(node),
ControlFlow::Break(_)
));
}
_ => {}
}
}
// traverse further in; there may nested binary operations
node.visit_with_mut(self)
}
}
fn main() {
let mut parser = Parser::new(BufReader::new(
File::open("boa_examples/scripts/calc.js").unwrap(),
));
let mut ctx = Context::default();
let mut statements = parser.parse_all(ctx.interner_mut()).unwrap();
let mut visitor = CommutorVisitor::default();
assert!(matches!(
visitor.visit_statement_list_mut(&mut statements),
ControlFlow::Continue(_)
));
println!("{}", statements.to_interned_string(ctx.interner()));
}

View File

@@ -0,0 +1,120 @@
// This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{
native_function::NativeFunction,
object::{builtins::JsArray, FunctionObjectBuilder},
string::utf16,
Context, JsResult, JsValue,
};
fn main() -> JsResult<()> {
// We create a new `Context` to create a new Javascript executor.
let context = &mut Context::default();
// Create an empty array.
let array = JsArray::new(context);
assert!(array.is_empty(context)?);
array.push("Hello, world", context)?; // [ "Hello, world" ]
array.push(true, context)?; // [ "Hello, world", true ]
assert!(!array.is_empty(context)?);
assert_eq!(array.pop(context)?, JsValue::new(true)); // [ "Hello, world" ]
assert_eq!(array.pop(context)?, JsValue::new("Hello, world")); // [ ]
assert_eq!(array.pop(context)?, JsValue::undefined()); // [ ]
array.push(1, context)?; // [ 1 ]
assert_eq!(array.pop(context)?, JsValue::new(1)); // [ ]
assert_eq!(array.pop(context)?, JsValue::undefined()); // [ ]
array.push_items(
&[
JsValue::new(10),
JsValue::new(11),
JsValue::new(12),
JsValue::new(13),
JsValue::new(14),
],
context,
)?; // [ 10, 11, 12, 13, 14 ]
array.reverse(context)?; // [ 14, 13, 12, 11, 10 ]
assert_eq!(array.index_of(12, None, context)?, Some(2));
// We can also use JsObject method `.get()` through the Deref trait.
let element = array.get(2, context)?; // array[ 0 ]
assert_eq!(element, JsValue::new(12));
// Or we can use the `.at(index)` method.
assert_eq!(array.at(0, context)?, JsValue::new(14)); // first element
assert_eq!(array.at(-1, context)?, JsValue::new(10)); // last element
// Join the array with an optional separator (default ",").
let joined_array = array.join(None, context)?;
assert_eq!(&joined_array, utf16!("14,13,12,11,10"));
array.fill(false, Some(1), Some(4), context)?;
let joined_array = array.join(Some("::".into()), context)?;
assert_eq!(&joined_array, utf16!("14::false::false::false::10"));
let filter_callback = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(|_this, args, _context| {
Ok(args.get(0).cloned().unwrap_or_default().is_number().into())
}),
)
.build();
let map_callback = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(|_this, args, context| {
args.get(0)
.cloned()
.unwrap_or_default()
.pow(&JsValue::new(2), context)
}),
)
.build();
let mut data = Vec::new();
for i in 1..=5 {
data.push(JsValue::new(i));
}
let another_array = JsArray::from_iter(data, context); // [ 1, 2, 3, 4, 5]
let chained_array = array // [ 14, false, false, false, 10 ]
.filter(filter_callback, None, context)? // [ 14, 10 ]
.map(map_callback, None, context)? // [ 196, 100 ]
.sort(None, context)? // [ 100, 196 ]
.concat(&[another_array.into()], context)? // [ 100, 196, 1, 2, 3, 4, 5 ]
.slice(Some(1), Some(5), context)?; // [ 196, 1, 2, 3 ]
assert_eq!(&chained_array.join(None, context)?, utf16!("196,1,2,3"));
let reduce_callback = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(|_this, args, context| {
let accumulator = args.get(0).cloned().unwrap_or_default();
let value = args.get(1).cloned().unwrap_or_default();
accumulator.add(&value, context)
}),
)
.build();
assert_eq!(
chained_array.reduce(reduce_callback, Some(JsValue::new(0)), context)?,
JsValue::new(202)
);
context
.global_object()
.clone()
.set("myArray", array, true, context)?;
Ok(())
}

View File

@@ -0,0 +1,70 @@
// This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{
object::builtins::{JsArrayBuffer, JsDataView, JsUint32Array, JsUint8Array},
property::Attribute,
Context, JsResult, JsValue,
};
fn main() -> JsResult<()> {
// We create a new `Context` to create a new Javascript executor.
let context = &mut Context::default();
// This create an array buffer of byte length 4
let array_buffer = JsArrayBuffer::new(4, context)?;
// We can now create an typed array to access the data.
let uint32_typed_array = JsUint32Array::from_array_buffer(array_buffer, context)?;
let value = 0x12345678u32;
uint32_typed_array.set(0_u64, value, true, context)?;
assert_eq!(uint32_typed_array.get(0_u64, context)?, JsValue::new(value));
// We can also create array buffers from a user defined block of data.
//
// NOTE: The block data will not be cloned.
let blob_of_data: Vec<u8> = (0..=255).collect();
let array_buffer = JsArrayBuffer::from_byte_block(blob_of_data, context)?;
// This the byte length of the new array buffer will be the length of block of data.
let byte_length = array_buffer.byte_length(context);
assert_eq!(byte_length, 256);
// We can now create an typed array to access the data.
let uint8_typed_array = JsUint8Array::from_array_buffer(array_buffer.clone(), context)?;
for i in 0..byte_length {
assert_eq!(uint8_typed_array.get(i, context)?, JsValue::new(i));
}
// We can create a Dataview from a JsArrayBuffer
let dataview = JsDataView::from_js_array_buffer(&array_buffer, None, Some(100_u64), context)?;
let dataview_length = dataview.byte_length(context)?;
assert_eq!(dataview_length, 100);
let second_byte = dataview.get_uint8(2, true, context)?;
assert_eq!(second_byte, 2_u8);
// We can also register it as a global property
context.register_global_property(
"myArrayBuffer",
array_buffer,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
);
// We can also take the inner data from a JsArrayBuffer
let data_block: Vec<u8> = (0..5).collect();
let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
let internal_buffer = array_buffer.take()?;
assert_eq!(internal_buffer, (0..5).collect::<Vec<u8>>());
let detached_err = array_buffer.take();
assert!(detached_err.is_err());
Ok(())
}

View File

@@ -0,0 +1,78 @@
use boa_engine::{object::builtins::JsDate, Context, JsResult, JsValue};
fn main() -> JsResult<()> {
let context = &mut Context::default();
let date = JsDate::new(context);
// 823230245000.0
JsDate::utc(
&[
JsValue::new(96),
JsValue::new(1),
JsValue::new(2),
JsValue::new(3),
JsValue::new(4),
JsValue::new(5),
],
context,
)?;
// reference date: 2022-07-16T06:27:32.087241439
// sets day of the month to 24
date.set_date(24, context)?;
// 2022-07-24T06:27:11.567
// sets date to 1st of January 2000
date.set_full_year(&[2000.into(), 0.into(), 1.into()], context)?;
// 2000-01-01T06:26:53.984
// sets time to 10H:10M:10S:10mS
date.set_hours(&[23.into(), 23.into(), 23.into(), 23.into()], context)?;
// Is 2000-01-01T17:53:23.023
// Should be 2000-01-01T23:23:23.023
// sets milliseconds to 999
date.set_milliseconds(999, context)?;
// 2000-01-01T17:40:10.999
// sets time to 12M:12S:12ms
date.set_minutes(&[12.into(), 12.into(), 12.into()], context)?;
// Is 2000-01-01T17:42:12.012
// Should be 2000-01-01T17:12:12:012
// sets month to 9 and day to 9
date.set_month(&[9.into(), 9.into()], context)?;
// 2000-10-09T04:42:12.012
// set seconds to 59 and ms to 59
date.set_seconds(&[59.into(), 59.into()], context)?;
// 2000-10-09T04:42:59.059
assert_eq!(
date.to_json(context)?,
JsValue::from("2000-10-09T17:42:59.059Z")
);
assert_eq!(
date.to_date_string(context)?,
JsValue::from("Mon Oct 09 2000")
);
assert_eq!(
date.to_iso_string(context)?,
JsValue::from("2000-10-09T17:42:59.059Z")
);
assert_eq!(
date.to_time_string(context)?,
JsValue::from("23:12:59 GMT+0530")
);
assert_eq!(
date.to_string(context)?,
JsValue::from("Mon Oct 09 2000 23:12:59 GMT+0530")
);
Ok(())
}

View File

@@ -0,0 +1,58 @@
use boa_engine::{
object::{builtins::JsArray, builtins::JsMap},
Context, JsResult, JsValue,
};
fn main() -> JsResult<()> {
// Create a `Context` for the Javascript executor.
let context = &mut Context::default();
// Create a new empty map.
let map = JsMap::new(context);
// Set a key-value for the map.
map.set("Key-1", "Value-1", context)?;
let map_check = map.has("Key-1", context)?;
assert_eq!(map_check, true.into()); // true
// Set a second key-value to the same map.
map.set(2, 4, context)?;
assert_eq!(map.get_size(context)?, 2.into()); //true
assert_eq!(map.get("Key-1", context)?, "Value-1".into());
assert_eq!(map.get(2, context)?, 4.into());
// Delete an entry with a provided key.
map.delete("Key-1", context)?;
assert_eq!(map.get_size(context)?, 1.into());
let deleted_key_one = map.get("Key-1", context)?;
assert_eq!(deleted_key_one, JsValue::undefined());
// Retrieve a MapIterator for all entries in the Map.
let entries = map.entries(context)?;
let _first_value = entries.next(context)?;
// Create a multidimensional array with key value pairs -> [[first-key, first-value], [second-key, second-value]]
let js_array = JsArray::new(context);
let vec_one = vec![JsValue::new("first-key"), JsValue::new("first-value")];
let vec_two = vec![JsValue::new("second-key"), JsValue::new("second-value")];
js_array.push(JsArray::from_iter(vec_one, context), context)?;
js_array.push(JsArray::from_iter(vec_two, context), context)?;
// Create a map from the JsArray using it's iterable property.
let iter_map = JsMap::from_js_iterable(&js_array.into(), context)?;
assert_eq!(iter_map.get("first-key", context)?, "first-value".into());
iter_map.set("third-key", "third-value", context)?;
assert_eq!(iter_map.get_size(context)?, JsValue::new(3));
Ok(())
}

View File

@@ -0,0 +1,21 @@
use boa_engine::{object::builtins::JsRegExp, Context, JsResult};
fn main() -> JsResult<()> {
let context = &mut Context::default();
let regexp = JsRegExp::new("foo", "gi", context)?;
let test_result = regexp.test("football", context)?;
assert!(test_result);
let flags = regexp.flags(context)?;
assert_eq!(flags, String::from("gi"));
let src = regexp.source(context)?;
assert_eq!(src, String::from("foo"));
let to_string = regexp.to_string(context)?;
assert_eq!(to_string, String::from("/foo/gi"));
Ok(())
}

View File

@@ -0,0 +1,53 @@
// This example shows how to manipulate a Javascript Set using Rust code.
#![allow(clippy::bool_assert_comparison)]
use boa_engine::{object::builtins::JsSet, Context, JsError, JsValue};
fn main() -> Result<(), JsError> {
// New `Context` for a new Javascript executor.
let context = &mut Context::default();
// Create an empty set.
let set = JsSet::new(context);
assert_eq!(set.size()?, 0);
set.add(5, context)?;
assert_eq!(set.size()?, 1);
set.add(10, context)?;
assert_eq!(set.size()?, 2);
set.clear(context)?;
assert_eq!(set.size()?, 0);
set.add("one", context)?;
set.add("two", context)?;
set.add("three", context)?;
assert!(set.has("one", context)?);
assert_eq!(set.has("One", context)?, false);
set.delete("two", context)?;
assert_eq!(set.has("two", context)?, false);
set.clear(context)?;
assert_eq!(set.has("one", context)?, false);
assert_eq!(set.has("three", context)?, false);
assert_eq!(set.size()?, 0);
// Add a slice into a set;
set.add_items(
&[JsValue::new(1), JsValue::new(2), JsValue::new(3)],
context,
)?;
// Will return 1, as one slice was added.
assert_eq!(set.size()?, 1);
// Make a new set from a slice
let slice_set = JsSet::from_iter([JsValue::new(1), JsValue::new(2), JsValue::new(3)], context);
// Will return 3, as each element of slice was added into the set.
assert_eq!(slice_set.size()?, 3);
set.clear(context)?;
Ok(())
}

View File

@@ -0,0 +1,50 @@
// This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{
native_function::NativeFunction,
object::{builtins::JsUint8Array, FunctionObjectBuilder},
property::Attribute,
Context, JsResult, JsValue,
};
fn main() -> JsResult<()> {
// We create a new `Context` to create a new Javascript executor.
let context = &mut Context::default();
let data: Vec<u8> = (0..=255).collect();
let array = JsUint8Array::from_iter(data, context)?;
assert_eq!(array.get(0, context)?, JsValue::new(0));
let mut sum = 0;
for i in 0..=255 {
assert_eq!(array.at(i, context)?, JsValue::new(i));
sum += i;
}
let callback = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(|_this, args, context| {
let accumulator = args.get(0).cloned().unwrap_or_default();
let value = args.get(1).cloned().unwrap_or_default();
accumulator.add(&value, context)
}),
)
.build();
assert_eq!(
array.reduce(callback, Some(JsValue::new(0)), context)?,
JsValue::new(sum)
);
context.register_global_property(
"myUint8Array",
array,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
);
Ok(())
}

View File

@@ -0,0 +1,31 @@
// This example shows how to load, parse and execute JS code from a source file
// (./scripts/helloworld.js)
use std::fs;
use boa_engine::Context;
fn main() {
let js_file_path = "./scripts/helloworld.js";
match fs::read(js_file_path) {
Ok(src) => {
// Instantiate the execution context
let mut context = Context::default();
// Parse the source code
match context.eval(src) {
Ok(res) => {
println!(
"{}",
res.to_string(&mut context).unwrap().to_std_string_escaped()
);
}
Err(e) => {
// Pretty print the error
eprintln!("Uncaught {e}");
}
};
}
Err(msg) => eprintln!("Error: {}", msg),
}
}

View File

@@ -0,0 +1,24 @@
// This example loads, parses and executes a JS code string
use boa_engine::Context;
fn main() {
let js_code = "console.log('Hello World from a JS code string!')";
// Instantiate the execution context
let mut context = Context::default();
// Parse the source code
match context.eval(js_code) {
Ok(res) => {
println!(
"{}",
res.to_string(&mut context).unwrap().to_std_string_escaped()
);
}
Err(e) => {
// Pretty print the error
eprintln!("Uncaught {e}");
}
};
}

View File

@@ -0,0 +1,62 @@
// This example implements a custom module handler which mimics
// the require/module.exports pattern
use boa_engine::{
native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, JsResult,
JsValue,
};
use std::fs::read_to_string;
fn main() {
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 mut ctx = Context::default();
// Adding custom implementation that mimics 'require'
ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require));
// Adding custom object that mimics 'module.exports'
let moduleobj = JsObject::default();
moduleobj
.set("exports", JsValue::from(" "), false, &mut ctx)
.unwrap();
ctx.register_global_property("module", JsValue::from(moduleobj), Attribute::default());
// Instantiating the engine with the execution context
// Loading, parsing and executing the JS code from the source file
ctx.eval(&buffer.unwrap()).unwrap();
}
// Custom implementation that mimics the 'require' module loader
fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult<JsValue> {
let arg = args.get(0).unwrap();
// BUG: Dev branch seems to be passing string arguments along with quotes
let libfile = arg
.to_string(ctx)
.expect("Failed to convert to string")
.to_std_string_escaped();
// Read the module source file
println!("Loading: {}", libfile);
let buffer = read_to_string(libfile);
if let Err(..) = buffer {
println!("Error: {}", buffer.unwrap_err());
Ok(JsValue::Rational(-1.0))
} else {
// Load and parse the module source
ctx.eval(&buffer.unwrap()).unwrap();
// Access module.exports and return as ResultValue
let global_obj = ctx.global_object().to_owned();
let module = global_obj.get("module", ctx).unwrap();
module.as_object().unwrap().get("exports", ctx)
}
}

View File

@@ -0,0 +1,48 @@
// This example demonstrates how to use a visitor to perform simple operations over the Javascript
// AST, namely: finding all the `Sym`s present in a script. See commuter_visitor.rs for an example
// which mutates the AST.
use boa_ast::visitor::Visitor;
use boa_engine::Context;
use boa_interner::Sym;
use boa_parser::Parser;
use core::ops::ControlFlow;
use std::{collections::HashSet, convert::Infallible, fs::File, io::BufReader};
#[derive(Debug, Clone, Default)]
struct SymbolVisitor {
observed: HashSet<Sym>,
}
impl<'ast> Visitor<'ast> for SymbolVisitor {
type BreakTy = Infallible;
fn visit_sym(&mut self, node: &'ast Sym) -> ControlFlow<Self::BreakTy> {
self.observed.insert(*node);
ControlFlow::Continue(())
}
}
fn main() {
let mut parser = Parser::new(BufReader::new(
File::open("boa_examples/scripts/calc.js").unwrap(),
));
let mut ctx = Context::default();
let statements = parser.parse_all(ctx.interner_mut()).unwrap();
let mut visitor = SymbolVisitor::default();
assert!(matches!(
visitor.visit_statement_list(&statements),
ControlFlow::Continue(_)
));
println!(
"Observed {} unique strings/symbols:",
visitor.observed.len()
);
for sym in visitor.observed {
println!(" - {}", ctx.interner().resolve(sym).unwrap());
}
}