mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
5.7 KiB
154 lines
5.7 KiB
// 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 preson. |
|
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(); |
|
}
|
|
|