Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature native class objects (NativeObject and Class traits) #627

Merged
merged 7 commits into from
Sep 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
150 changes: 150 additions & 0 deletions boa/examples/classes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use boa::{
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved
builtins::{
object::{Class, ClassBuilder},
property::Attribute,
value::Value,
},
exec::Interpreter,
forward_val,
realm::Realm,
Result,
};

use 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 an object in JavaScript.
//
// The fields of the sturct are not accesable by JavaScript unless accessors are created for them.
/// This Represents a Person.
#[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` signiture.
//
// NOTE: The function does not have to be implemented of Person it can be a free function,
// or any function that matches that signature.
impl Person {
/// This function says hello
fn say_hello(this: &Value, _: &[Value], ctx: &mut Interpreter) -> Result<Value> {
// 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>() {
// we print the 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 Person struct.
);
return Ok(Value::undefined());
}
}
// If `this` was not an object or the type was not an native object `Person`,
// we throw a `TypeError`.
ctx.throw_type_error("'this' is not a Person object")
}
}

impl Class for Person {
// we set the binging name of this function to be `"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: If this is not defiend that the default is `0`.
const LENGTH: usize = 2;

// This is what is called when we do `new Person()`
fn constructor(_this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Self> {
// we get the first arguemnt of undefined if the first one is unavalable and call `to_string`.
//
// This is equivalent to `String(arg)`.
let name = args.get(0).cloned().unwrap_or_default().to_string(ctx)?;
// we get the second arguemnt of undefined if the first one is unavalable and call `to_u32`.
//
// This is equivalent to `arg | 0`.
let age = args.get(1).cloned().unwrap_or_default().to_u32(ctx)?;

// we construct the the native struct `Person`
let person = Person {
name: name.to_string(),
age,
};

Ok(person) // and we return it.
}

/// This is where the object is intitialized.
fn init(class: &mut ClassBuilder) -> Result<()> {
// we add a inheritable method `sayHello` with length `0` the amount of args it takes.
//
// This function is added to `Person.prototype.sayHello()`
class.method("sayHello", 0, Self::say_hello);
// we add a static mathod `is`, and here we use a closure, but it must be converible
// to a NativeFunction. it must not contain state, if it does it will give a compilation error.
//
// This function is added to `Person.is()`
class.static_method("is", 1, |_this, args, _ctx| {
if let Some(arg) = args.get(0) {
if let Some(object) = arg.as_object() {
if object.is::<Person>() {
// we check if the object type is `Person`
return Ok(true.into()); // return `true`.
}
}
}
Ok(false.into()) // otherwise `false`.
});

// Add a inherited property with the value `10`, with deafault attribute.
// (`READONLY, NON_ENUMERABLE, PERMANENT).
class.property("inheritedProperty", 10, Attribute::default());

// Add a static property with the value `"Im a static property"`, with deafault attribute.
// (`WRITABLE, ENUMERABLE, PERMANENT`).
class.static_property(
"staticProperty",
"Im a static property",
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::PERMANENT,
);

Ok(())
}
}

fn main() {
let realm = Realm::create();
let mut context = Interpreter::new(realm);

// we register the global class `Person`.
context.register_global_class::<Person>().unwrap();

forward_val(
&mut context,
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();
}
2 changes: 1 addition & 1 deletion boa/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ bitflags! {
}

impl FunctionFlags {
fn from_parameters(callable: bool, constructable: bool) -> Self {
pub(crate) fn from_parameters(callable: bool, constructable: bool) -> Self {
let mut flags = Self::default();

if callable {
Expand Down
15 changes: 8 additions & 7 deletions boa/src/builtins/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,19 @@ impl Json {
holder: &mut Value,
key: &PropertyKey,
) -> Result<Value> {
let mut value = holder.get_field(key.clone());
let value = holder.get_field(key.clone());

let obj = value.as_object().as_deref().cloned();
if let Some(obj) = obj {
for key in obj.keys() {
let v = Self::walk(reviver, ctx, &mut value, &key);
if let Value::Object(ref object) = value {
let keys: Vec<_> = object.borrow().keys().collect();

for key in keys {
let v = Self::walk(reviver, ctx, &mut value.clone(), &key);
match v {
Ok(v) if !v.is_undefined() => {
value.set_field(key.clone(), v);
value.set_field(key, v);
}
Ok(_) => {
value.remove_property(key.clone());
value.remove_property(key);
}
Err(_v) => {}
}
Expand Down
78 changes: 39 additions & 39 deletions boa/src/builtins/object/internal_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,45 +282,45 @@ impl Object {
})
}

/// `Object.setPropertyOf(obj, prototype)`
///
/// This method sets the prototype (i.e., the internal `[[Prototype]]` property)
/// of a specified object to another object or `null`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
pub fn set_prototype_of(&mut self, val: Value) -> bool {
debug_assert!(val.is_object() || val.is_null());
let current = self.prototype.clone();
if same_value(&current, &val) {
return true;
}
if !self.is_extensible() {
return false;
}
let mut p = val.clone();
let mut done = false;
while !done {
if p.is_null() {
done = true
} else if same_value(&Value::from(self.clone()), &p) {
return false;
} else {
let prototype = p
.as_object()
.expect("prototype should be null or object")
.prototype
.clone();
p = prototype;
}
}
self.prototype = val;
true
}
// /// `Object.setPropertyOf(obj, prototype)`
// ///
// /// This method sets the prototype (i.e., the internal `[[Prototype]]` property)
// /// of a specified object to another object or `null`.
// ///
// /// More information:
// /// - [ECMAScript reference][spec]
// /// - [MDN documentation][mdn]
// ///
// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
// pub fn set_prototype_of(&mut self, val: Value) -> bool {
// debug_assert!(val.is_object() || val.is_null());
// let current = self.prototype.clone();
// if same_value(&current, &val) {
// return true;
// }
// if !self.is_extensible() {
// return false;
// }
// let mut p = val.clone();
// let mut done = false;
// while !done {
// if p.is_null() {
// done = true
// } else if same_value(&Value::from(self.clone()), &p) {
// return false;
// } else {
// let prototype = p
// .as_object()
// .expect("prototype should be null or object")
// .prototype
// .clone();
// p = prototype;
// }
// }
// self.prototype = val;
// true
// }
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved

/// Returns either the prototype or null
///
Expand Down