Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
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

// 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.
Lexer string interning (#1758) This Pull Request is part of #279. It adds a string interner to Boa, which allows many types to not contain heap-allocated strings, and just contain a `NonZeroUsize` instead. This can move types to the stack (hopefully I'll be able to move `Token`, for example, maybe some `Node` types too. Note that the internet is for now only available in the lexer. Next steps (in this PR or future ones) would include also using interning in the parser, and finally in execution. The idea is that strings should be represented with a `Sym` until they are displayed. Talking about display. I have changed the `ParseError` type in order to not contain anything that could contain a `Sym` (basically tokens), which might be a bit faster, but what is important is that we don't depend on the interner when displaying errors. The issue I have now is in order to display tokens. This requires the interner if we want to know identifiers, for example. The issue here is that Rust doesn't allow using a `fmt::Formatter` (only in nightly), which is making my head hurt. Maybe someone of you can find a better way of doing this. Then, about `cursor.expect()`, this is the only place where we don't have the expected token type as a static string, so it's failing to compile. We have the option of changing the type definition of `ParseError` to contain an owned string, but maybe we can avoid this by having a `&'static str` come from a `TokenKind` with the default values, such as "identifier" for an identifier. I wanted for you to think about it and maybe we can just add that and avoid allocations there. Oh, and this depends on the VM-only branch, so that has to be merged before :) Another thing to check: should the interner be in its own module?
3 years ago
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();
}