feat: works

This commit is contained in:
2022-07-17 10:11:20 +08:00
parent 4ba63b4c2e
commit 74a202f1ed
458 changed files with 125067 additions and 8 deletions

View File

@@ -0,0 +1,154 @@
// NOTE: this example requires the `console` feature to run correctly.
use boa_engine::{
class::{Class, ClassBuilder},
property::Attribute,
Context, JsResult, 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: String,
/// 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], context: &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,
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`.
context.throw_type_error("'this' is not a Person object")
}
}
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(0)
.cloned()
.unwrap_or_default()
.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: name.to_string(),
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,121 @@
// This example goes into the details on how to pass closures as functions
// inside Rust and call them from Javascript.
use boa_engine::{
object::{FunctionBuilder, JsObject},
property::{Attribute, PropertyDescriptor},
Context, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
fn main() -> Result<(), JsValue> {
// 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_closure("closure", 0, 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 = context.construct_object();
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.
let js_function = FunctionBuilder::closure_with_captures(
&mut context,
|_, _, captures, context| {
println!("Called `createMessage`");
// We obtain the `name` property of `captures.object`
let name = captures.object.get("name", context)?;
// We create a new message from our captured variable.
let message = JsString::concat_array(&[
"message from `",
name.to_string(context)?.as_str(),
"`: ",
captures.greeting.as_str(),
]);
// We can also mutate the moved data inside the closure.
captures.greeting = format!("{} Hello!", captures.greeting).into();
println!("{message}");
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.
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::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
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!
Ok(())
}

View File

@@ -0,0 +1,109 @@
// This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{
object::{FunctionBuilder, JsArray},
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, "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, "14::false::false::false::10");
let filter_callback = FunctionBuilder::native(context, |_this, args, _context| {
Ok(args.get(0).cloned().unwrap_or_default().is_number().into())
})
.build();
let map_callback = FunctionBuilder::native(context, |_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)?, "196,1,2,3");
let reduce_callback = FunctionBuilder::native(context, |_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,49 @@
// This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{
object::{JsArrayBuffer, 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, value, true, context)?;
assert_eq!(uint32_typed_array.get(0, 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 also register it as a global property
context.register_global_property(
"myArrayBuffer",
array_buffer,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
);
Ok(())
}

View File

@@ -0,0 +1,58 @@
use boa_engine::{
object::{JsArray, 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,53 @@
// This example shows how to manipulate a Javascript Set using Rust code.
#![allow(clippy::bool_assert_comparison)]
use boa_engine::{object::JsSet, Context, JsValue};
fn main() -> Result<(), JsValue> {
// 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(context)?, 0);
set.add(5, context)?;
assert_eq!(set.size(context)?, 1);
set.add(10, context)?;
assert_eq!(set.size(context)?, 2);
set.clear(context)?;
assert_eq!(set.size(context)?, 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(context)?, 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(context)?, 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(context)?, 3);
set.clear(context)?;
Ok(())
}

View File

@@ -0,0 +1,46 @@
// This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{
object::{FunctionBuilder, 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();
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 = FunctionBuilder::native(context, |_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,28 @@
// This example shows how to load, parse and execute JS code from a source file
// (./scripts/helloworld.js)
use std::fs::read_to_string;
use boa_engine::Context;
fn main() {
let js_file_path = "./scripts/helloworld.js";
match read_to_string(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());
}
Err(e) => {
// Pretty print the error
eprintln!("Uncaught {}", e.display());
}
};
}
Err(msg) => eprintln!("Error: {}", msg),
}
}

View File

@@ -0,0 +1,21 @@
// 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());
}
Err(e) => {
// Pretty print the error
eprintln!("Uncaught {}", e.display());
}
};
}

View File

@@ -0,0 +1,59 @@
// This example implements a custom module handler which mimics
// the require/module.exports pattern
use boa_engine::{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_function("require", 0, 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_string();
// 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)
}
}