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