Browse Source

Implement Classes (#1976)

This Pull Request fixes/closes #337.

It changes the following:

- Implement class declaration parsing.
- Implement class expression parsing.
- Implement class execution.

There are still some features like `super` missing and there are some early errors that are not implemented yet. But I think it makes sense to merge this, as we can branch out the missing features from here.
pull/2014/head
raskad 3 years ago
parent
commit
223966981c
  1. 451
      boa_engine/src/bytecompiler.rs
  2. 19
      boa_engine/src/object/jsobject.rs
  3. 71
      boa_engine/src/object/mod.rs
  4. 367
      boa_engine/src/syntax/ast/node/declaration/class_decl/mod.rs
  5. 104
      boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs
  6. 1
      boa_engine/src/syntax/ast/node/declaration/mod.rs
  7. 63
      boa_engine/src/syntax/ast/node/field/get_private_field/mod.rs
  8. 1
      boa_engine/src/syntax/ast/node/field/mod.rs
  9. 21
      boa_engine/src/syntax/ast/node/mod.rs
  10. 27
      boa_engine/src/syntax/ast/node/object/mod.rs
  11. 5
      boa_engine/src/syntax/ast/node/operator/assign/mod.rs
  12. 56
      boa_engine/src/syntax/ast/node/parameters.rs
  13. 2
      boa_engine/src/syntax/lexer/cursor.rs
  14. 6
      boa_engine/src/syntax/lexer/identifier.rs
  15. 5
      boa_engine/src/syntax/lexer/mod.rs
  16. 79
      boa_engine/src/syntax/lexer/private_identifier.rs
  17. 30
      boa_engine/src/syntax/lexer/token.rs
  18. 78
      boa_engine/src/syntax/parser/cursor/mod.rs
  19. 5
      boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs
  20. 1
      boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs
  21. 26
      boa_engine/src/syntax/parser/expression/assignment/mod.rs
  22. 77
      boa_engine/src/syntax/parser/expression/assignment/yield.rs
  23. 6
      boa_engine/src/syntax/parser/expression/left_hand_side/member.rs
  24. 4
      boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs
  25. 6
      boa_engine/src/syntax/parser/expression/mod.rs
  26. 81
      boa_engine/src/syntax/parser/expression/primary/class_expression/mod.rs
  27. 22
      boa_engine/src/syntax/parser/expression/primary/mod.rs
  28. 602
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  29. 2
      boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs
  30. 18
      boa_engine/src/syntax/parser/expression/unary.rs
  31. 22
      boa_engine/src/syntax/parser/expression/update.rs
  32. 94
      boa_engine/src/syntax/parser/function/mod.rs
  33. 20
      boa_engine/src/syntax/parser/function/tests.rs
  34. 1200
      boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs
  35. 9
      boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs
  36. 2
      boa_engine/src/syntax/parser/statement/declaration/lexical.rs
  37. 5
      boa_engine/src/syntax/parser/statement/declaration/mod.rs
  38. 22
      boa_engine/src/syntax/parser/statement/mod.rs
  39. 4
      boa_engine/src/syntax/parser/statement/variable/mod.rs
  40. 1
      boa_engine/src/syntax/parser/tests.rs
  41. 43
      boa_engine/src/vm/code_block.rs
  42. 260
      boa_engine/src/vm/mod.rs
  43. 133
      boa_engine/src/vm/opcode.rs
  44. 42
      boa_interner/src/lib.rs
  45. 10
      boa_interner/src/tests.rs

451
boa_engine/src/bytecompiler.rs

@ -3,12 +3,15 @@ use crate::{
environments::BindingLocator,
syntax::ast::{
node::{
declaration::{BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPattern},
declaration::{
class_decl::ClassElement, BindingPatternTypeArray, BindingPatternTypeObject,
DeclarationPattern,
},
iteration::IterableLoopInitializer,
object::{MethodDefinition, PropertyDefinition, PropertyName},
operator::assign::AssignTarget,
template::TemplateElement,
Declaration, GetConstField, GetField,
Class, Declaration, GetConstField, GetField,
},
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
Const, Node,
@ -836,6 +839,7 @@ impl<'b> ByteCompiler<'b> {
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.compile_stmt(node, true)?;
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
@ -851,6 +855,7 @@ impl<'b> ByteCompiler<'b> {
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::SetPropertyGetterByValue);
}
@ -864,6 +869,7 @@ impl<'b> ByteCompiler<'b> {
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::SetPropertySetterByValue);
}
@ -877,6 +883,7 @@ impl<'b> ByteCompiler<'b> {
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
@ -890,6 +897,7 @@ impl<'b> ByteCompiler<'b> {
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
@ -908,6 +916,7 @@ impl<'b> ByteCompiler<'b> {
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
@ -938,6 +947,16 @@ impl<'b> ByteCompiler<'b> {
Some(assign.rhs()),
use_expr,
)?,
AssignTarget::GetPrivateField(node) => {
self.compile_expr(assign.rhs(), true)?;
if use_expr {
self.emit_opcode(Opcode::Dup);
}
self.compile_expr(node.obj(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(node.field());
self.emit(Opcode::SetPrivateValue, &[index]);
}
AssignTarget::GetConstField(node) => {
self.access_set(Access::ByName { node }, Some(assign.rhs()), use_expr)?;
}
@ -956,6 +975,11 @@ impl<'b> ByteCompiler<'b> {
let access = Access::ByName { node };
self.access_get(access, use_expr)?;
}
Node::GetPrivateField(node) => {
let index = self.get_or_insert_name(node.field());
self.compile_expr(node.obj(), true)?;
self.emit(Opcode::GetPrivateField, &[index]);
}
Node::GetField(node) => {
let access = Access::ByValue { node };
self.access_get(access, use_expr)?;
@ -1108,6 +1132,7 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]);
}
Node::ClassExpr(class) => self.class(class, true)?,
_ => unreachable!(),
}
Ok(())
@ -1758,6 +1783,7 @@ impl<'b> ByteCompiler<'b> {
Node::AsyncFunctionDecl(_) | Node::AsyncGeneratorDecl(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
Node::ClassDecl(class) => self.class(class, false)?,
Node::Empty => {}
expr => self.compile_expr(expr, use_expr)?,
}
@ -1812,7 +1838,7 @@ impl<'b> ByteCompiler<'b> {
};
let strict = body.strict() || self.code_block.strict;
let length = parameters.parameters.len() as u32;
let length = parameters.length();
let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict, true);
if let FunctionKind::Arrow = kind {
@ -2299,6 +2325,10 @@ impl<'b> ByteCompiler<'b> {
}
}
}
Node::ClassDecl(decl) => {
self.context
.create_mutable_binding(decl.name(), false, false)?;
}
Node::FunctionDecl(decl) => {
let ident = decl.name();
if ident == Sym::ARGUMENTS {
@ -2346,4 +2376,419 @@ impl<'b> ByteCompiler<'b> {
}
Ok(has_identifier_argument)
}
/// This function compiles a class declaration or expression.
///
/// The compilation of a class declaration and expression is mostly equal.
/// A class declaration binds the resulting class object to it's identifier.
/// A class expression leaves the resulting class object on the stack for following operations.
fn class(&mut self, class: &Class, expression: bool) -> JsResult<()> {
let mut code = CodeBlock::new(class.name(), 0, true, true);
code.computed_field_names = Some(gc::GcCell::new(vec![]));
let mut compiler = ByteCompiler {
code_block: code,
literals_map: FxHashMap::default(),
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
jump_info: Vec::new(),
context: self.context,
};
compiler.context.push_compile_time_environment(true);
for element in class.elements() {
match element {
ClassElement::FieldDefinition(name, field) => {
compiler.emit_opcode(Opcode::This);
match name {
PropertyName::Literal(name) => {
if let Some(node) = field {
compiler.compile_stmt(node, true)?;
} else {
compiler.emit_opcode(Opcode::PushUndefined);
}
compiler.emit_opcode(Opcode::Swap);
let index = compiler.get_or_insert_name(*name);
compiler.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(_) => {
compiler.emit_opcode(Opcode::Swap);
compiler.emit_opcode(Opcode::ToPropertyKey);
if let Some(node) = field {
compiler.compile_stmt(node, true)?;
} else {
compiler.emit_opcode(Opcode::PushUndefined);
}
compiler.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
}
}
ClassElement::PrivateFieldDefinition(name, field) => {
compiler.emit_opcode(Opcode::This);
if let Some(node) = field {
compiler.compile_stmt(node, true)?;
} else {
compiler.emit_opcode(Opcode::PushUndefined);
}
let index = compiler.get_or_insert_name(*name);
compiler.emit(Opcode::SetPrivateValue, &[index]);
}
_ => {}
}
}
if let Some(expr) = class.constructor() {
compiler.code_block.length = expr.parameters().length();
compiler.code_block.params = expr.parameters().clone();
compiler
.context
.create_mutable_binding(Sym::ARGUMENTS, false, true)?;
compiler.code_block.arguments_binding = Some(
compiler
.context
.initialize_mutable_binding(Sym::ARGUMENTS, false),
);
for parameter in expr.parameters().parameters.iter() {
if parameter.is_rest_param() {
compiler.emit_opcode(Opcode::RestParameterInit);
}
match parameter.declaration() {
Declaration::Identifier { ident, .. } => {
compiler
.context
.create_mutable_binding(ident.sym(), false, true)?;
if let Some(init) = parameter.declaration().init() {
let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true)?;
compiler.patch_jump(skip);
}
compiler.emit_binding(BindingOpcode::InitArg, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
compiler
.context
.create_mutable_binding(ident, false, true)?;
}
compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?;
}
}
}
if !expr.parameters().has_rest_parameter() {
compiler.emit_opcode(Opcode::RestParameterPop);
}
let env_label = if expr.parameters().has_expressions() {
compiler.code_block.num_bindings = compiler.context.get_binding_number();
compiler.context.push_compile_time_environment(true);
Some(compiler.jump_with_custom_opcode(Opcode::PushFunctionEnvironment))
} else {
None
};
compiler.create_declarations(expr.body().items())?;
compiler.compile_statement_list(expr.body().items(), false)?;
if let Some(env_label) = env_label {
let num_bindings = compiler
.context
.pop_compile_time_environment()
.num_bindings();
compiler.patch_jump_with_target(env_label, num_bindings as u32);
compiler.context.pop_compile_time_environment();
} else {
compiler.code_block.num_bindings = compiler
.context
.pop_compile_time_environment()
.num_bindings();
}
} else {
compiler.code_block.num_bindings = compiler
.context
.pop_compile_time_environment()
.num_bindings();
}
compiler.emit_opcode(Opcode::PushUndefined);
compiler.emit_opcode(Opcode::Return);
let code = Gc::new(compiler.finish());
let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]);
for element in class.elements() {
match element {
ClassElement::StaticMethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match method_definition {
MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineClassGetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineClassGetterByValue);
}
},
MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineClassSetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineClassSetterByValue);
}
},
MethodDefinition::Ordinary(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
}
}
ClassElement::PrivateStaticMethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match method_definition {
MethodDefinition::Get(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateGetter, &[index]);
}
MethodDefinition::Set(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateSetter, &[index]);
}
MethodDefinition::Ordinary(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateValue, &[index]);
}
MethodDefinition::Generator(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateValue, &[index]);
}
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
}
}
ClassElement::FieldDefinition(PropertyName::Computed(name_node), _) => {
self.emit_opcode(Opcode::Dup);
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::PushClassComputedFieldName);
}
ClassElement::StaticFieldDefinition(name, field) => {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
if let Some(node) = field {
self.compile_stmt(node, true)?;
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
if let Some(node) = field {
self.compile_stmt(node, true)?;
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
}
}
ClassElement::PrivateStaticFieldDefinition(name, field) => {
self.emit_opcode(Opcode::Dup);
if let Some(node) = field {
self.compile_stmt(node, true)?;
} else {
self.emit_opcode(Opcode::PushUndefined);
}
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateValue, &[index]);
}
ClassElement::StaticBlock(statement_list) => {
self.emit_opcode(Opcode::Dup);
let mut compiler = ByteCompiler::new(Sym::EMPTY_STRING, true, self.context);
compiler.context.push_compile_time_environment(true);
compiler.create_declarations(statement_list.items())?;
compiler.compile_statement_list(statement_list.items(), false)?;
compiler.code_block.num_bindings = compiler
.context
.pop_compile_time_environment()
.num_bindings();
let code = Gc::new(compiler.finish());
let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]);
self.emit(Opcode::Call, &[0]);
}
ClassElement::MethodDefinition(..)
| ClassElement::PrivateMethodDefinition(..)
| ClassElement::PrivateFieldDefinition(..)
| ClassElement::FieldDefinition(..) => {}
}
}
self.emit_opcode(Opcode::Dup);
if let Some(node) = class.super_ref() {
self.compile_expr(node, true)?;
self.emit_opcode(Opcode::PushClassPrototype);
} else {
self.emit_opcode(Opcode::PushEmptyObject);
}
for element in class.elements() {
match element {
ClassElement::MethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match method_definition {
MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineClassGetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineClassGetterByValue);
}
},
MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineClassSetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineClassSetterByValue);
}
},
MethodDefinition::Ordinary(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
}
}
ClassElement::PrivateMethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match method_definition {
MethodDefinition::Get(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateGetter, &[index]);
}
MethodDefinition::Set(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateSetter, &[index]);
}
MethodDefinition::Ordinary(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateValue, &[index]);
}
MethodDefinition::Generator(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateValue, &[index]);
}
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
}
}
ClassElement::PrivateFieldDefinition(..)
| ClassElement::StaticFieldDefinition(..)
| ClassElement::PrivateStaticFieldDefinition(..)
| ClassElement::StaticMethodDefinition(..)
| ClassElement::PrivateStaticMethodDefinition(..)
| ClassElement::StaticBlock(..)
| ClassElement::FieldDefinition(..) => {}
}
}
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(Sym::PROTOTYPE);
self.emit(Opcode::SetPropertyByName, &[index]);
if !expression {
self.emit_binding(BindingOpcode::InitVar, class.name());
}
Ok(())
}
}

19
boa_engine/src/object/jsobject.rs

@ -10,6 +10,7 @@ use crate::{
Context, JsResult, JsValue,
};
use boa_gc::{self, Finalize, Gc, Trace};
use rustc_hash::FxHashMap;
use std::{
cell::RefCell,
collections::HashMap,
@ -51,13 +52,29 @@ impl JsObject {
/// internal slots from the `data` provided.
#[inline]
pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self {
let prototype: Option<Self> = prototype.into();
if let Some(prototype) = prototype {
let private = {
let prototype_b = prototype.borrow();
prototype_b.private_elements.clone()
};
Self::from_object(Object {
data,
prototype: Some(prototype),
extensible: true,
properties: PropertyMap::default(),
private_elements: private,
})
} else {
Self::from_object(Object {
data,
prototype: prototype.into(),
prototype: None,
extensible: true,
properties: PropertyMap::default(),
private_elements: FxHashMap::default(),
})
}
}
/// Immutably borrows the `Object`.
///

71
boa_engine/src/object/mod.rs

@ -45,6 +45,8 @@ use crate::{
Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_interner::Sym;
use rustc_hash::FxHashMap;
use std::{
any::Any,
fmt::{self, Debug, Display},
@ -106,6 +108,18 @@ pub struct Object {
prototype: JsPrototype,
/// Whether it can have new properties added to it.
extensible: bool,
/// The `[[PrivateElements]]` internal slot.
private_elements: FxHashMap<Sym, PrivateElement>,
}
/// The representation of private object elements.
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) enum PrivateElement {
Value(JsValue),
Accessor {
getter: Option<JsObject>,
setter: Option<JsObject>,
},
}
/// Defines the kind of an object and its internal methods
@ -459,6 +473,7 @@ impl Default for Object {
properties: PropertyMap::default(),
prototype: None,
extensible: true,
private_elements: FxHashMap::default(),
}
}
}
@ -1219,6 +1234,62 @@ impl Object {
pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option<PropertyDescriptor> {
self.properties.remove(key)
}
/// Get a private element.
#[inline]
pub(crate) fn get_private_element(&self, name: Sym) -> Option<&PrivateElement> {
self.private_elements.get(&name)
}
/// Set a private element.
#[inline]
pub(crate) fn set_private_element(&mut self, name: Sym, value: PrivateElement) {
self.private_elements.insert(name, value);
}
/// Set a private setter.
#[inline]
pub(crate) fn set_private_element_setter(&mut self, name: Sym, setter: JsObject) {
match self.private_elements.get_mut(&name) {
Some(PrivateElement::Accessor {
getter: _,
setter: s,
}) => {
*s = Some(setter);
}
_ => {
self.private_elements.insert(
name,
PrivateElement::Accessor {
getter: None,
setter: Some(setter),
},
);
}
}
}
/// Set a private getter.
#[inline]
pub(crate) fn set_private_element_getter(&mut self, name: Sym, getter: JsObject) {
match self.private_elements.get_mut(&name) {
Some(PrivateElement::Accessor {
getter: g,
setter: _,
}) => {
*g = Some(getter);
}
_ => {
self.private_elements.insert(
name,
PrivateElement::Accessor {
getter: Some(getter),
setter: None,
},
);
}
}
}
}
/// The functions binding.

367
boa_engine/src/syntax/ast/node/declaration/class_decl/mod.rs

@ -0,0 +1,367 @@
#[cfg(test)]
mod tests;
use crate::syntax::ast::node::{
declaration::{block_to_string, FunctionExpr},
join_nodes,
object::{MethodDefinition, PropertyName},
Node, StatementList,
};
use boa_gc::{Finalize, Trace};
use boa_interner::{Interner, Sym, ToInternedString};
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
/// The `class` declaration defines a class with the specified methods, fields, and optional constructor.
///
/// Classes can be used to create objects, which can also be created through literals (using `{}`).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-class-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Class {
name: Sym,
super_ref: Option<Box<Node>>,
constructor: Option<FunctionExpr>,
elements: Box<[ClassElement]>,
}
impl Class {
/// Creates a new class declaration.
pub(in crate::syntax) fn new<S, C, E>(
name: Sym,
super_ref: S,
constructor: C,
elements: E,
) -> Self
where
S: Into<Option<Box<Node>>>,
C: Into<Option<FunctionExpr>>,
E: Into<Box<[ClassElement]>>,
{
Self {
name,
super_ref: super_ref.into(),
constructor: constructor.into(),
elements: elements.into(),
}
}
/// Returns the name of the class.
pub(crate) fn name(&self) -> Sym {
self.name
}
/// Returns the super class ref of the class.
pub(crate) fn super_ref(&self) -> &Option<Box<Node>> {
&self.super_ref
}
/// Returns the constructor of the class.
pub(crate) fn constructor(&self) -> &Option<FunctionExpr> {
&self.constructor
}
/// Gets the list of all fields defined on the class.
pub(crate) fn elements(&self) -> &[ClassElement] {
&self.elements
}
/// Implements the display formatting with indentation.
pub(in crate::syntax::ast::node) fn to_indented_string(
&self,
interner: &Interner,
indent_n: usize,
) -> String {
if self.elements.is_empty() && self.constructor().is_none() {
return format!(
"class {}{} {{}}",
interner.resolve_expect(self.name),
if let Some(node) = &self.super_ref {
format!(" extends {}", node.to_interned_string(interner))
} else {
"".to_string()
}
);
}
let indentation = " ".repeat(indent_n + 1);
let mut buf = format!(
"class {}{} {{\n",
interner.resolve_expect(self.name),
if let Some(node) = &self.super_ref {
format!("extends {}", node.to_interned_string(interner))
} else {
"".to_string()
}
);
if let Some(expr) = &self.constructor {
buf.push_str(&format!(
"{indentation}constructor({}) {}\n",
join_nodes(interner, &expr.parameters().parameters),
block_to_string(expr.body(), interner, indent_n + 1)
));
}
for element in self.elements.iter() {
buf.push_str(&match element {
ClassElement::MethodDefinition(name, method) => {
format!(
"{indentation}{}{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
name.to_interned_string(interner),
match &method {
MethodDefinition::Get(node)
| MethodDefinition::Set(node)
| MethodDefinition::Ordinary(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::Generator(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::AsyncGenerator(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::Async(node) => {
join_nodes(interner, &node.parameters().parameters)
}
},
match &method {
MethodDefinition::Get(node)
| MethodDefinition::Set(node)
| MethodDefinition::Ordinary(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::Async(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::StaticMethodDefinition(name, method) => {
format!(
"{indentation}static {}{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
name.to_interned_string(interner),
match &method {
MethodDefinition::Get(node)
| MethodDefinition::Set(node)
| MethodDefinition::Ordinary(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::Generator(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::AsyncGenerator(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::Async(node) => {
join_nodes(interner, &node.parameters().parameters)
}
},
match &method {
MethodDefinition::Get(node)
| MethodDefinition::Set(node)
| MethodDefinition::Ordinary(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::Async(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::FieldDefinition(name, field) => match field {
Some(node) => {
format!(
"{indentation}{} = {};\n",
name.to_interned_string(interner),
node.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!("{indentation}{};\n", name.to_interned_string(interner),)
}
},
ClassElement::StaticFieldDefinition(name, field) => match field {
Some(node) => {
format!(
"{indentation}static {} = {};\n",
name.to_interned_string(interner),
node.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!(
"{indentation}static {};\n",
name.to_interned_string(interner),
)
}
},
ClassElement::PrivateMethodDefinition(name, method) => {
format!(
"{indentation}{}#{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
interner.resolve_expect(*name),
match &method {
MethodDefinition::Get(node)
| MethodDefinition::Set(node)
| MethodDefinition::Ordinary(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::Generator(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::AsyncGenerator(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::Async(node) => {
join_nodes(interner, &node.parameters().parameters)
}
},
match &method {
MethodDefinition::Get(node)
| MethodDefinition::Set(node)
| MethodDefinition::Ordinary(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::Async(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::PrivateStaticMethodDefinition(name, method) => {
format!(
"{indentation}static {}#{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
interner.resolve_expect(*name),
match &method {
MethodDefinition::Get(node)
| MethodDefinition::Set(node)
| MethodDefinition::Ordinary(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::Generator(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::AsyncGenerator(node) => {
join_nodes(interner, &node.parameters().parameters)
}
MethodDefinition::Async(node) => {
join_nodes(interner, &node.parameters().parameters)
}
},
match &method {
MethodDefinition::Get(node)
| MethodDefinition::Set(node)
| MethodDefinition::Ordinary(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
MethodDefinition::Async(node) => {
block_to_string(node.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::PrivateFieldDefinition(name, field) => match field {
Some(node) => {
format!(
"{indentation}#{} = {};\n",
interner.resolve_expect(*name),
node.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!("{indentation}#{};\n", interner.resolve_expect(*name),)
}
},
ClassElement::PrivateStaticFieldDefinition(name, field) => match field {
Some(node) => {
format!(
"{indentation}static #{} = {};\n",
interner.resolve_expect(*name),
node.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!("{indentation}static #{};\n", interner.resolve_expect(*name),)
}
},
ClassElement::StaticBlock(statement_list) => {
format!(
"{indentation}static {}\n",
block_to_string(statement_list, interner, indent_n + 1)
)
}
});
}
buf.push('}');
buf
}
}
impl ToInternedString for Class {
fn to_interned_string(&self, interner: &Interner) -> String {
self.to_indented_string(interner, 0)
}
}
/// Class element types.
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub enum ClassElement {
MethodDefinition(PropertyName, MethodDefinition),
StaticMethodDefinition(PropertyName, MethodDefinition),
FieldDefinition(PropertyName, Option<Node>),
StaticFieldDefinition(PropertyName, Option<Node>),
PrivateMethodDefinition(Sym, MethodDefinition),
PrivateStaticMethodDefinition(Sym, MethodDefinition),
PrivateFieldDefinition(Sym, Option<Node>),
PrivateStaticFieldDefinition(Sym, Option<Node>),
StaticBlock(StatementList),
}

104
boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs

@ -0,0 +1,104 @@
use crate::syntax::ast::node::test_formatting;
#[test]
fn class_declaration_empty() {
test_formatting(
r#"
class A {};
"#,
);
}
#[test]
fn class_declaration_empty_extends() {
test_formatting(
r#"
class A extends Object {};
"#,
);
}
#[test]
fn class_declaration_constructor() {
test_formatting(
r#"
class A {
constructor(a, b, c) {
this.value = a + b + c;
}
};
"#,
);
}
#[test]
fn class_declaration_elements() {
test_formatting(
r#"
class A {
a;
b = 1;
c() {}
d(a, b, c) {
return a + b + c;
}
set e(value) {}
get e() {}
};
"#,
);
}
#[test]
fn class_declaration_elements_private() {
test_formatting(
r#"
class A {
#a;
#b = 1;
#c() {}
#d(a, b, c) {
return a + b + c;
}
set #e(value) {}
get #e() {}
};
"#,
);
}
#[test]
fn class_declaration_elements_static() {
test_formatting(
r#"
class A {
static a;
static b = 1;
static c() {}
static d(a, b, c) {
return a + b + c;
}
static set e(value) {}
static get e() {}
};
"#,
);
}
#[test]
fn class_declaration_elements_private_static() {
test_formatting(
r#"
class A {
static #a;
static #b = 1;
static #c() {}
static #d(a, b, c) {
return a + b + c;
}
static set #e(value) {}
static get #e() {}
};
"#,
);
}

1
boa_engine/src/syntax/ast/node/declaration/mod.rs

@ -16,6 +16,7 @@ pub mod async_function_decl;
pub mod async_function_expr;
pub mod async_generator_decl;
pub mod async_generator_expr;
pub mod class_decl;
pub mod function_decl;
pub mod function_expr;
pub mod generator_decl;

63
boa_engine/src/syntax/ast/node/field/get_private_field/mod.rs

@ -0,0 +1,63 @@
use crate::syntax::ast::node::Node;
use boa_gc::{Finalize, Trace};
use boa_interner::{Interner, Sym, ToInternedString};
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
/// This property accessor provides access to an class object's private fields.
///
/// This expression can be described as ` MemberExpression.PrivateIdentifier`
/// Example: `this.#a`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MemberExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct GetPrivateField {
obj: Box<Node>,
field: Sym,
}
impl GetPrivateField {
/// Creates a `GetPrivateField` AST node.
pub fn new<V>(value: V, field: Sym) -> Self
where
V: Into<Node>,
{
Self {
obj: Box::new(value.into()),
field,
}
}
/// Gets the original object from where to get the field from.
pub fn obj(&self) -> &Node {
&self.obj
}
/// Gets the name of the field to retrieve.
pub fn field(&self) -> Sym {
self.field
}
}
impl ToInternedString for GetPrivateField {
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{}.#{}",
self.obj.to_interned_string(interner),
interner.resolve_expect(self.field)
)
}
}
impl From<GetPrivateField> for Node {
fn from(get_private_field: GetPrivateField) -> Self {
Self::GetPrivateField(get_private_field)
}
}

1
boa_engine/src/syntax/ast/node/field/mod.rs

@ -2,6 +2,7 @@
pub mod get_const_field;
pub mod get_field;
pub mod get_private_field;
pub use self::{get_const_field::GetConstField, get_field::GetField};

21
boa_engine/src/syntax/ast/node/mod.rs

@ -23,6 +23,7 @@ pub mod throw;
pub mod try_node;
pub mod r#yield;
use self::field::get_private_field::GetPrivateField;
pub use self::{
array::ArrayDecl,
await_expr::AwaitExpr,
@ -31,9 +32,9 @@ pub use self::{
conditional::{ConditionalOp, If},
declaration::{
async_generator_decl::AsyncGeneratorDecl, async_generator_expr::AsyncGeneratorExpr,
generator_decl::GeneratorDecl, generator_expr::GeneratorExpr, ArrowFunctionDecl,
AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, DeclarationPattern,
FunctionDecl, FunctionExpr,
class_decl::Class, generator_decl::GeneratorDecl, generator_expr::GeneratorExpr,
ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList,
DeclarationPattern, FunctionDecl, FunctionExpr,
},
field::{GetConstField, GetField},
identifier::Identifier,
@ -135,6 +136,9 @@ pub enum Node {
/// Provides access to an object types' constant properties. [More information](./declaration/struct.GetConstField.html).
GetConstField(GetConstField),
/// Provides access to an object types' private properties. [More information](./declaration/struct.GetPrivateField.html).
GetPrivateField(GetPrivateField),
/// Provides access to object fields. [More information](./declaration/struct.GetField.html).
GetField(GetField),
@ -226,6 +230,12 @@ pub enum Node {
/// A generator function expression node. [More information](./declaration/struct.GeneratorExpr.html).
GeneratorExpr(GeneratorExpr),
/// A class declaration. [More information](./declaration/struct.class_decl.Class.html).
ClassDecl(Class),
/// A class declaration. [More information](./declaration/struct.class_decl.Class.html).
ClassExpr(Class),
}
impl From<Const> for Node {
@ -296,6 +306,9 @@ impl Node {
Self::GetConstField(ref get_const_field) => {
get_const_field.to_interned_string(interner)
}
Self::GetPrivateField(ref get_private_field) => {
get_private_field.to_interned_string(interner)
}
Self::GetField(ref get_field) => get_field.to_interned_string(interner),
Self::WhileLoop(ref while_loop) => while_loop.to_indented_string(interner, indentation),
Self::DoWhileLoop(ref do_while) => do_while.to_indented_string(interner, indentation),
@ -326,6 +339,8 @@ impl Node {
Self::GeneratorExpr(ref expr) => expr.to_indented_string(interner, indentation),
Self::AsyncGeneratorExpr(ref expr) => expr.to_indented_string(interner, indentation),
Self::AsyncGeneratorDecl(ref decl) => decl.to_indented_string(interner, indentation),
Self::ClassDecl(ref decl) => decl.to_indented_string(interner, indentation),
Self::ClassExpr(ref expr) => expr.to_indented_string(interner, indentation),
}
}
}

27
boa_engine/src/syntax/ast/node/object/mod.rs

@ -1,8 +1,11 @@
//! Object node.
use crate::syntax::ast::node::{
declaration::block_to_string, join_nodes, AsyncFunctionExpr, AsyncGeneratorExpr, FunctionExpr,
GeneratorExpr, Node,
use crate::syntax::ast::{
node::{
declaration::block_to_string, join_nodes, AsyncFunctionExpr, AsyncGeneratorExpr,
FunctionExpr, GeneratorExpr, Node,
},
Const,
};
use boa_gc::{unsafe_empty_trace, Finalize, Trace};
use boa_interner::{Interner, Sym, ToInternedString};
@ -344,6 +347,24 @@ pub enum PropertyName {
Computed(Node),
}
impl PropertyName {
pub(in crate::syntax) fn literal(&self) -> Option<Sym> {
if let Self::Literal(sym) = self {
Some(*sym)
} else {
None
}
}
pub(in crate::syntax) fn prop_name(&self) -> Option<Sym> {
match self {
PropertyName::Literal(sym)
| PropertyName::Computed(Node::Const(Const::String(sym))) => Some(*sym),
PropertyName::Computed(_) => None,
}
}
}
impl ToInternedString for PropertyName {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {

5
boa_engine/src/syntax/ast/node/operator/assign/mod.rs

@ -3,6 +3,7 @@ use crate::syntax::ast::node::{
BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPatternArray,
DeclarationPatternObject,
},
field::get_private_field::GetPrivateField,
object::{PropertyDefinition, PropertyName},
ArrayDecl, DeclarationPattern, GetConstField, GetField, Identifier, Node, Object,
};
@ -80,6 +81,7 @@ impl From<Assign> for Node {
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub enum AssignTarget {
Identifier(Identifier),
GetPrivateField(GetPrivateField),
GetConstField(GetConstField),
GetField(GetField),
DeclarationPattern(DeclarationPattern),
@ -91,6 +93,7 @@ impl AssignTarget {
pub(crate) fn from_node(node: &Node) -> Option<Self> {
match node {
Node::Identifier(target) => Some(Self::Identifier(*target)),
Node::GetPrivateField(target) => Some(Self::GetPrivateField(target.clone())),
Node::GetConstField(target) => Some(Self::GetConstField(target.clone())),
Node::GetField(target) => Some(Self::GetField(target.clone())),
Node::Object(object) => {
@ -110,6 +113,7 @@ impl ToInternedString for AssignTarget {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
AssignTarget::Identifier(target) => target.to_interned_string(interner),
AssignTarget::GetPrivateField(target) => target.to_interned_string(interner),
AssignTarget::GetConstField(target) => target.to_interned_string(interner),
AssignTarget::GetField(target) => target.to_interned_string(interner),
AssignTarget::DeclarationPattern(target) => target.to_interned_string(interner),
@ -261,6 +265,7 @@ pub(crate) fn array_decl_to_declaration_pattern(array: &ArrayDecl) -> Option<Dec
pattern: pattern.clone(),
});
}
AssignTarget::GetPrivateField(_) => return None,
},
Node::ArrayDecl(array) => {
let pattern = array_decl_to_declaration_pattern(array)?;

56
boa_engine/src/syntax/ast/node/parameters.rs

@ -18,12 +18,36 @@ pub struct FormalParameterList {
pub(crate) parameters: Box<[FormalParameter]>,
#[unsafe_ignore_trace]
pub(crate) flags: FormalParameterListFlags,
pub(crate) length: u32,
}
impl FormalParameterList {
/// Creates a new formal parameter list.
pub(crate) fn new(parameters: Box<[FormalParameter]>, flags: FormalParameterListFlags) -> Self {
Self { parameters, flags }
pub(crate) fn new(
parameters: Box<[FormalParameter]>,
flags: FormalParameterListFlags,
length: u32,
) -> Self {
Self {
parameters,
flags,
length,
}
}
/// Creates a new empty formal parameter list.
pub(crate) fn empty() -> Self {
Self {
parameters: Box::new([]),
flags: FormalParameterListFlags::default(),
length: 0,
}
}
/// Returns the length of the parameter list.
/// Note that this is not equal to the length of the parameters slice.
pub(crate) fn length(&self) -> u32 {
self.length
}
/// Indicates if the parameter list is simple.
@ -55,6 +79,34 @@ impl FormalParameterList {
}
}
impl From<FormalParameter> for FormalParameterList {
fn from(parameter: FormalParameter) -> Self {
let mut flags = FormalParameterListFlags::default();
if parameter.is_rest_param() {
flags |= FormalParameterListFlags::HAS_REST_PARAMETER;
}
if parameter.init().is_some() {
flags |= FormalParameterListFlags::HAS_EXPRESSIONS;
}
if parameter.names().contains(&Sym::ARGUMENTS) {
flags |= FormalParameterListFlags::HAS_ARGUMENTS;
}
if parameter.is_rest_param() || parameter.init().is_some() || !parameter.is_identifier() {
flags.remove(FormalParameterListFlags::IS_SIMPLE);
}
let length = if parameter.is_rest_param() || parameter.init().is_some() {
0
} else {
1
};
Self {
parameters: Box::new([parameter]),
flags,
length,
}
}
}
bitflags! {
/// Flags for a [`FormalParameterList`].
#[allow(clippy::unsafe_derive_deserialize)]

2
boa_engine/src/syntax/lexer/cursor.rs

@ -33,11 +33,13 @@ impl<R> Cursor<R> {
}
#[inline]
/// Returns if strict mode is currently active.
pub(super) fn strict_mode(&self) -> bool {
self.strict_mode
}
#[inline]
/// Sets the current strict mode.
pub(super) fn set_strict_mode(&mut self, strict_mode: bool) {
self.strict_mode = strict_mode;
}

6
boa_engine/src/syntax/lexer/identifier.rs

@ -10,9 +10,7 @@ use boa_profiler::Profiler;
use boa_unicode::UnicodeProperties;
use std::{io::Read, str};
const STRICT_FORBIDDEN_IDENTIFIERS: [&str; 11] = [
"eval",
"arguments",
const STRICT_FORBIDDEN_IDENTIFIERS: [&str; 9] = [
"implements",
"interface",
"let",
@ -133,7 +131,7 @@ impl<R> Tokenizer<R> for Identifier {
impl Identifier {
#[inline]
fn take_identifier_name<R>(
pub(super) fn take_identifier_name<R>(
cursor: &mut Cursor<R>,
start_pos: Position,
init: char,

5
boa_engine/src/syntax/lexer/mod.rs

@ -20,6 +20,7 @@ pub mod error;
mod identifier;
mod number;
mod operator;
mod private_identifier;
pub mod regex;
mod spread;
mod string;
@ -35,6 +36,7 @@ use self::{
identifier::Identifier,
number::NumberLiteral,
operator::Operator,
private_identifier::PrivateIdentifier,
regex::RegexLiteral,
spread::SpreadLiteral,
string::StringLiteral,
@ -100,11 +102,13 @@ impl<R> Lexer<R> {
}
#[inline]
/// Returns if strict mode is currently active.
pub(super) fn strict_mode(&self) -> bool {
self.cursor.strict_mode()
}
#[inline]
/// Sets the current strict mode.
pub(super) fn set_strict_mode(&mut self, strict_mode: bool) {
self.cursor.set_strict_mode(strict_mode);
}
@ -265,6 +269,7 @@ impl<R> Lexer<R> {
Punctuator::CloseBracket.into(),
Span::new(start, self.cursor.pos()),
)),
'#' => PrivateIdentifier::new().lex(&mut self.cursor, start, interner),
'/' => self.lex_slash_token(start, interner),
'=' | '*' | '+' | '-' | '%' | '|' | '&' | '^' | '<' | '>' | '!' | '~' | '?' => {
Operator::new(next_ch as u8).lex(&mut self.cursor, start, interner)

79
boa_engine/src/syntax/lexer/private_identifier.rs

@ -0,0 +1,79 @@
//! This module implements lexing for private identifiers (#foo, #myvar, etc.) used in the JavaScript programing language.
use super::{identifier::Identifier, Cursor, Error, Tokenizer};
use crate::syntax::{
ast::{Position, Span},
lexer::{Token, TokenKind},
};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;
/// Private Identifier lexing.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-PrivateIdentifier
#[derive(Debug, Clone, Copy)]
pub(super) struct PrivateIdentifier;
impl PrivateIdentifier {
/// Creates a new private identifier lexer.
pub(super) fn new() -> Self {
Self
}
}
impl<R> Tokenizer<R> for PrivateIdentifier {
fn lex(
&mut self,
cursor: &mut Cursor<R>,
start_pos: Position,
interner: &mut Interner,
) -> Result<Token, Error>
where
R: Read,
{
let _timer = Profiler::global().start_event("PrivateIdentifier", "Lexing");
if let Some(next_ch) = cursor.next_char()? {
if let Ok(c) = char::try_from(next_ch) {
match c {
'\\' if cursor.peek()? == Some(b'u') => {
let (name, _) = Identifier::take_identifier_name(cursor, start_pos, c)?;
Ok(Token::new(
TokenKind::PrivateIdentifier(interner.get_or_intern(&name)),
Span::new(start_pos, cursor.pos()),
))
}
_ if Identifier::is_identifier_start(c as u32) => {
let (name, _) = Identifier::take_identifier_name(cursor, start_pos, c)?;
Ok(Token::new(
TokenKind::PrivateIdentifier(interner.get_or_intern(&name)),
Span::new(start_pos, cursor.pos()),
))
}
_ => Err(Error::syntax(
"Abrupt end: Expecting private identifier",
start_pos,
)),
}
} else {
Err(Error::syntax(
format!(
"unexpected utf-8 char '\\u{next_ch}' at line {}, column {}",
start_pos.line_number(),
start_pos.column_number()
),
start_pos,
))
}
} else {
Err(Error::syntax(
"Abrupt end: Expecting private identifier",
start_pos,
))
}
}
}

30
boa_engine/src/syntax/lexer/token.rs

@ -52,11 +52,6 @@ impl Token {
pub(crate) fn to_string(&self, interner: &Interner) -> String {
self.kind.to_string(interner)
}
/// Converts the token to a string interner symbol.
pub(crate) fn to_sym(&self, interner: &mut Interner) -> Sym {
self.kind.to_sym(interner)
}
}
/// Represents the type differenct types of numeric literals.
@ -107,6 +102,9 @@ pub enum TokenKind {
/// An identifier.
Identifier(Sym),
/// A private identifier.
PrivateIdentifier(Sym),
/// A keyword.
Keyword(Keyword),
@ -230,6 +228,7 @@ impl TokenKind {
Self::BooleanLiteral(val) => val.to_string(),
Self::EOF => "end of file".to_owned(),
Self::Identifier(ident) => interner.resolve_expect(ident).to_owned(),
Self::PrivateIdentifier(ident) => format!("#{}", interner.resolve_expect(ident)),
Self::Keyword(word) => word.to_string(),
Self::NullLiteral => "null".to_owned(),
Self::NumericLiteral(Numeric::Rational(num)) => num.to_string(),
@ -251,25 +250,4 @@ impl TokenKind {
Self::Comment => "comment".to_owned(),
}
}
/// Converts the token to a string interner symbol.
///
/// This is an optimization to avoid resolving + re-interning strings.
pub(crate) fn to_sym(&self, interner: &mut Interner) -> Sym {
match *self {
Self::BooleanLiteral(_)
| Self::NumericLiteral(_)
| Self::RegularExpressionLiteral(_, _) => {
interner.get_or_intern(&self.to_string(interner))
}
Self::EOF => interner.get_or_intern_static("end of file"),
Self::Identifier(sym) | Self::StringLiteral(sym) => sym,
Self::Keyword(word) => interner.get_or_intern_static(word.as_str()),
Self::NullLiteral => Sym::NULL,
Self::Punctuator(punc) => interner.get_or_intern_static(punc.as_str()),
Self::TemplateNoSubstitution(ts) | Self::TemplateMiddle(ts) => ts.as_raw(),
Self::LineTerminator => interner.get_or_intern_static("line terminator"),
Self::Comment => interner.get_or_intern_static("comment"),
}
}
}

78
boa_engine/src/syntax/parser/cursor/mod.rs

@ -1,13 +1,14 @@
//! Cursor implementation for the parser.
mod buffered_lexer;
use super::ParseError;
use super::{statement::PrivateElement, ParseError};
use crate::syntax::{
ast::{Position, Punctuator},
lexer::{InputElement, Lexer, Token, TokenKind},
};
use boa_interner::Interner;
use boa_interner::{Interner, Sym};
use buffered_lexer::BufferedLexer;
use rustc_hash::FxHashMap;
use std::io::Read;
/// The result of a peek for a semicolon.
@ -23,6 +24,12 @@ pub(super) enum SemicolonResult<'s> {
#[derive(Debug)]
pub(super) struct Cursor<R> {
buffered_lexer: BufferedLexer<R>,
/// Tracks the private identifiers used in code blocks.
private_environments_stack: Vec<FxHashMap<Sym, Position>>,
/// Tracks if the cursor is in a arrow function declaration.
arrow: bool,
}
impl<R> Cursor<R>
@ -34,6 +41,8 @@ where
pub(super) fn new(reader: R) -> Self {
Self {
buffered_lexer: Lexer::new(reader).into(),
private_environments_stack: Vec::new(),
arrow: false,
}
}
@ -84,6 +93,71 @@ where
self.buffered_lexer.set_strict_mode(strict_mode);
}
/// Returns if the cursor is currently in a arrow function declaration.
#[inline]
pub(super) fn arrow(&self) -> bool {
self.arrow
}
/// Set if the cursor is currently in a arrow function declaration.
#[inline]
pub(super) fn set_arrow(&mut self, arrow: bool) {
self.arrow = arrow;
}
/// Push a new private environment.
#[inline]
pub(super) fn push_private_environment(&mut self) {
let new = FxHashMap::default();
self.private_environments_stack.push(new);
}
/// Push a used private identifier.
#[inline]
pub(super) fn push_used_private_identifier(
&mut self,
identifier: Sym,
position: Position,
) -> Result<(), ParseError> {
if let Some(env) = self.private_environments_stack.last_mut() {
env.entry(identifier).or_insert(position);
Ok(())
} else {
Err(ParseError::general(
"private identifier declared outside of class",
position,
))
}
}
/// Pop the last private environment.
///
/// This function takes the private element names of the current class.
/// If a used private identifier is not declared, this throws a syntax error.
#[inline]
pub(super) fn pop_private_environment(
&mut self,
identifiers: &FxHashMap<Sym, PrivateElement>,
) -> Result<(), ParseError> {
let last = self
.private_environments_stack
.pop()
.expect("private environment must exist");
for (identifier, position) in &last {
if !identifiers.contains_key(identifier) {
if let Some(outer) = self.private_environments_stack.last_mut() {
outer.insert(*identifier, *position);
} else {
return Err(ParseError::general(
"private identifier must be declared",
*position,
));
}
}
}
Ok(())
}
/// Returns an error if the next token is not of kind `kind`.
#[inline]
pub(super) fn expect<K>(

5
boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs

@ -110,6 +110,7 @@ where
false,
)]),
flags,
1,
),
params_start_position,
)
@ -122,8 +123,10 @@ where
"arrow function",
interner,
)?;
let arrow = cursor.arrow();
cursor.set_arrow(true);
let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?;
cursor.set_arrow(arrow);
// Early Error: ArrowFormalParameters are UniqueFormalParameters.
if params.has_duplicates() {
return Err(ParseError::lex(LexError::Syntax(

1
boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs

@ -88,7 +88,6 @@ where
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult {
let _timer = Profiler::global().start_event("ExponentiationExpression", "Parsing");
if is_unary_expression(cursor, interner)? {
return UnaryExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner);

26
boa_engine/src/syntax/parser/expression/assignment/mod.rs

@ -87,7 +87,7 @@ where
{
type Output = Node;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult {
fn parse(mut self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult {
let _timer = Profiler::global().start_event("AssignmentExpression", "Parsing");
cursor.set_goal(InputElement::Div);
@ -194,6 +194,11 @@ where
cursor.set_goal(InputElement::Div);
let position = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let mut lhs = ConditionalExpression::new(
self.name,
self.allow_in,
@ -207,8 +212,27 @@ where
if let Some(tok) = cursor.peek(0, interner)?.cloned() {
match tok.kind() {
TokenKind::Punctuator(Punctuator::Assign) => {
if cursor.strict_mode() {
if let Node::Identifier(ident) = lhs {
if ident.sym() == Sym::ARGUMENTS {
return Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'arguments' in strict mode".into(),
position,
)));
} else if ident.sym() == Sym::EVAL {
return Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'eval' in strict mode".into(),
position,
)));
}
}
}
cursor.next(interner)?.expect("= token vanished");
if let Some(target) = AssignTarget::from_node(&lhs) {
if let AssignTarget::Identifier(ident) = target {
self.name = Some(ident.sym());
}
let expr = self.parse(cursor, interner)?;
lhs = Assign::new(target, expr).into();
} else {

77
boa_engine/src/syntax/parser/expression/assignment/yield.rs

@ -14,7 +14,7 @@ use crate::syntax::{
Keyword, Punctuator,
},
lexer::TokenKind,
parser::{cursor::SemicolonResult, AllowAwait, AllowIn, Cursor, ParseResult, TokenParser},
parser::{AllowAwait, AllowIn, Cursor, ParseError, ParseResult, TokenParser},
};
use boa_interner::Interner;
use boa_profiler::Profiler;
@ -63,34 +63,57 @@ where
interner,
)?;
let mut expr = None;
let mut delegate = false;
if let SemicolonResult::Found(_) = cursor.peek_semicolon(interner)? {
cursor.expect(
TokenKind::Punctuator(Punctuator::Semicolon),
"token disappeared",
interner,
)?;
} else if let Ok(next_token) =
cursor.peek_expect_no_lineterminator(0, "yield expression", interner)
{
if let TokenKind::Punctuator(Punctuator::Mul) = next_token.kind() {
cursor.expect(
TokenKind::Punctuator(Punctuator::Mul),
"token disappeared",
interner,
)?;
delegate = true;
}
expr = Some(
AssignmentExpression::new(None, self.allow_in, true, self.allow_await)
.parse(cursor, interner)?,
);
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
match token.kind() {
TokenKind::Punctuator(Punctuator::Mul) => {
cursor.next(interner)?.expect("token disappeared");
let expr = AssignmentExpression::new(None, self.allow_in, true, self.allow_await)
.parse(cursor, interner)?;
Ok(Node::Yield(Yield::new::<Node, Option<Node>>(
Some(expr),
true,
)))
}
TokenKind::Identifier(_)
| TokenKind::Punctuator(
Punctuator::OpenParen
| Punctuator::Add
| Punctuator::Sub
| Punctuator::Not
| Punctuator::Neg
| Punctuator::Inc
| Punctuator::Dec
| Punctuator::OpenBracket
| Punctuator::OpenBlock
| Punctuator::Div,
)
| TokenKind::Keyword(
Keyword::Yield
| Keyword::Await
| Keyword::Delete
| Keyword::Void
| Keyword::TypeOf
| Keyword::New
| Keyword::This
| Keyword::Function
| Keyword::Class
| Keyword::Async,
)
| TokenKind::BooleanLiteral(_)
| TokenKind::NullLiteral
| TokenKind::StringLiteral(_)
| TokenKind::TemplateNoSubstitution(_)
| TokenKind::NumericLiteral(_)
| TokenKind::RegularExpressionLiteral(_, _)
| TokenKind::TemplateMiddle(_) => {
let expr = AssignmentExpression::new(None, self.allow_in, true, self.allow_await)
.parse(cursor, interner)?;
Ok(Node::Yield(Yield::new::<Node, Option<Node>>(
expr, delegate,
Some(expr),
false,
)))
}
_ => Ok(Node::Yield(Yield::new::<Node, Option<Node>>(None, false))),
}
}
}

6
boa_engine/src/syntax/parser/expression/left_hand_side/member.rs

@ -9,7 +9,7 @@ use super::arguments::Arguments;
use crate::syntax::{
ast::{
node::{
field::{GetConstField, GetField},
field::{get_private_field::GetPrivateField, GetConstField, GetField},
Call, New, Node,
},
Keyword, Punctuator,
@ -114,6 +114,10 @@ where
TokenKind::NullLiteral => {
lhs = GetConstField::new(lhs, Keyword::Null.to_sym(interner)).into();
}
TokenKind::PrivateIdentifier(name) => {
cursor.push_used_private_identifier(*name, token.span().start())?;
lhs = GetPrivateField::new(lhs, *name).into();
}
_ => {
return Err(ParseError::expected(
["identifier".to_owned()],

4
boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs

@ -31,7 +31,7 @@ use std::io::Read;
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Left-hand-side_expressions
/// [spec]: https://tc39.es/ecma262/#prod-LeftHandSideExpression
#[derive(Debug, Clone, Copy)]
pub(super) struct LeftHandSideExpression {
pub(in crate::syntax::parser) struct LeftHandSideExpression {
name: Option<Sym>,
allow_yield: AllowYield,
allow_await: AllowAwait,
@ -39,7 +39,7 @@ pub(super) struct LeftHandSideExpression {
impl LeftHandSideExpression {
/// Creates a new `LeftHandSideExpression` parser.
pub(super) fn new<N, Y, A>(name: N, allow_yield: Y, allow_await: A) -> Self
pub(in crate::syntax::parser) fn new<N, Y, A>(name: N, allow_yield: Y, allow_await: A) -> Self
where
N: Into<Option<Sym>>,
Y: Into<AllowYield>,

6
boa_engine/src/syntax/parser/expression/mod.rs

@ -32,6 +32,12 @@ use std::io::Read;
pub(super) use self::{assignment::AssignmentExpression, primary::Initializer};
pub(in crate::syntax::parser) mod await_expr;
pub(in crate::syntax::parser) use {
left_hand_side::LeftHandSideExpression,
primary::object_initializer::{
AsyncGeneratorMethod, AsyncMethod, GeneratorMethod, PropertyName,
},
};
// For use in the expression! macro to allow for both Punctuator and Keyword parameters.
// Always returns false.

81
boa_engine/src/syntax/parser/expression/primary/class_expression/mod.rs

@ -0,0 +1,81 @@
use crate::syntax::{
ast::{Keyword, Node},
lexer::TokenKind,
parser::{
statement::{BindingIdentifier, ClassTail},
AllowAwait, AllowYield, Cursor, ParseError, TokenParser,
},
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use std::io::Read;
/// Class expression parsing.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassExpression
#[derive(Debug, Clone, Copy)]
pub(super) struct ClassExpression {
name: Option<Sym>,
allow_yield: AllowYield,
allow_await: AllowAwait,
}
impl ClassExpression {
/// Creates a new `ClassExpression` parser.
pub(in crate::syntax::parser) fn new<N, Y, A>(name: N, allow_yield: Y, allow_await: A) -> Self
where
N: Into<Option<Sym>>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
name: name.into(),
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for ClassExpression
where
R: Read,
{
type Output = Node;
fn parse(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("ClassExpression", "Parsing");
let strict = cursor.strict_mode();
cursor.set_strict_mode(true);
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let name = match token.kind() {
TokenKind::Identifier(_) | TokenKind::Keyword(Keyword::Yield | Keyword::Await) => {
BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?
}
_ => {
if let Some(name) = self.name {
name
} else {
return Err(ParseError::unexpected(
token.to_string(interner),
token.span(),
"expected class identifier",
));
}
}
};
cursor.set_strict_mode(strict);
Ok(Node::ClassExpr(
ClassTail::new(name, self.allow_yield, self.allow_await).parse(cursor, interner)?,
))
}
}

22
boa_engine/src/syntax/parser/expression/primary/mod.rs

@ -7,22 +7,25 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#Primary_expressions
//! [spec]: https://tc39.es/ecma262/#prod-PrimaryExpression
#[cfg(test)]
mod tests;
mod array_initializer;
mod async_function_expression;
mod async_generator_expression;
mod class_expression;
mod function_expression;
mod generator_expression;
mod object_initializer;
mod template;
#[cfg(test)]
mod tests;
pub(in crate::syntax::parser) mod object_initializer;
use self::{
array_initializer::ArrayLiteral, async_function_expression::AsyncFunctionExpression,
async_generator_expression::AsyncGeneratorExpression, function_expression::FunctionExpression,
generator_expression::GeneratorExpression, object_initializer::ObjectLiteral,
async_generator_expression::AsyncGeneratorExpression, class_expression::ClassExpression,
function_expression::FunctionExpression, generator_expression::GeneratorExpression,
object_initializer::ObjectLiteral,
};
use super::Expression;
use crate::syntax::{
ast::{
node::{Call, Identifier, New, Node},
@ -30,8 +33,8 @@ use crate::syntax::{
},
lexer::{token::Numeric, InputElement, TokenKind},
parser::{
expression::primary::template::TemplateLiteral, AllowAwait, AllowYield, Cursor, ParseError,
ParseResult, TokenParser,
expression::{primary::template::TemplateLiteral, Expression},
AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser,
},
};
use boa_interner::{Interner, Sym};
@ -98,6 +101,9 @@ where
.map(Node::from)
}
}
TokenKind::Keyword(Keyword::Class) => {
ClassExpression::new(self.name, false, false).parse(cursor, interner)
}
TokenKind::Keyword(Keyword::Async) => {
let mul_peek = cursor.peek(1, interner)?.ok_or(ParseError::AbruptEnd)?;
if mul_peek.kind() == &TokenKind::Punctuator(Punctuator::Mul) {

602
boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -17,12 +17,12 @@ use crate::syntax::{
AsyncFunctionExpr, AsyncGeneratorExpr, FormalParameterList, FunctionExpr,
GeneratorExpr, Identifier, Node, Object,
},
Keyword, Position, Punctuator,
Const, Keyword, Position, Punctuator,
},
lexer::{Error as LexError, TokenKind},
lexer::{token::Numeric, Error as LexError, TokenKind},
parser::{
expression::AssignmentExpression,
function::{FormalParameters, FunctionBody},
function::{FormalParameter, FormalParameters, FunctionBody, UniqueFormalParameters},
AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser,
},
};
@ -108,14 +108,14 @@ where
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
#[derive(Debug, Clone, Copy)]
struct PropertyDefinition {
pub(in crate::syntax::parser) struct PropertyDefinition {
allow_yield: AllowYield,
allow_await: AllowAwait,
}
impl PropertyDefinition {
/// Creates a new `PropertyDefinition` parser.
fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
@ -148,6 +148,18 @@ where
) {
let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;
let ident = match token.kind() {
TokenKind::Identifier(Sym::ARGUMENTS) if cursor.strict_mode() => {
return Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'arguments' in strict mode".into(),
token.span().start(),
)));
}
TokenKind::Identifier(Sym::EVAL) if cursor.strict_mode() => {
return Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'eval' in strict mode".into(),
token.span().start(),
)));
}
TokenKind::Identifier(ident) => Identifier::new(*ident),
TokenKind::Keyword(Keyword::Yield) if self.allow_yield.0 => {
// Early Error: It is a Syntax Error if this production has a [Yield] parameter and StringValue of Identifier is "yield".
@ -206,231 +218,34 @@ where
if cursor.next_if(Keyword::Async, interner)?.is_some() {
cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?;
let mul_check = cursor.next_if(Punctuator::Mul, interner)?;
let property_name =
PropertyName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
if mul_check.is_some() {
// MethodDefinition[?Yield, ?Await] -> AsyncGeneratorMethod[?Yield, ?Await]
let params_start_position = cursor
.expect(
Punctuator::OpenParen,
"async generator method definition",
interner,
)?
.span()
.start();
let params = FormalParameters::new(true, true).parse(cursor, interner)?;
cursor.expect(
Punctuator::CloseParen,
"async generator method definition",
interner,
)?;
// Early Error: UniqueFormalParameters : FormalParameters
// NOTE: does not appear to formally be in ECMAScript specs for method
if params.has_duplicates() {
return Err(ParseError::lex(LexError::Syntax(
"Duplicate parameter name not allowed in this context".into(),
params_start_position,
)));
}
cursor.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
"async generator method definition",
interner,
)?;
let body = FunctionBody::new(true, true).parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"async generator method definition",
interner,
)?;
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of UniqueFormalParameters is false.
if body.strict() && !params.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list"
.into(),
params_start_position,
)));
}
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
if let TokenKind::Punctuator(Punctuator::Mul) = token.kind() {
let (property_name, method) =
AsyncGeneratorMethod::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
return Ok(object::PropertyDefinition::method_definition(
MethodDefinition::AsyncGenerator(AsyncGeneratorExpr::new(None, params, body)),
method,
property_name,
));
}
// MethodDefinition[?Yield, ?Await] -> AsyncMethod[?Yield, ?Await]
let params_start_position = cursor
.expect(Punctuator::OpenParen, "async method definition", interner)?
.span()
.start();
let params = FormalParameters::new(false, true).parse(cursor, interner)?;
cursor.expect(Punctuator::CloseParen, "async method definition", interner)?;
// Early Error: UniqueFormalParameters : FormalParameters
// NOTE: does not appear to be in ECMAScript specs
if params.has_duplicates() {
return Err(ParseError::lex(LexError::Syntax(
"Duplicate parameter name not allowed in this context".into(),
params_start_position,
)));
}
cursor.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
"async method definition",
interner,
)?;
let body = FunctionBody::new(true, true).parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"async method definition",
interner,
)?;
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of UniqueFormalParameters is false.
if body.strict() && !params.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list"
.into(),
params_start_position,
)));
}
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
let (property_name, method) =
AsyncMethod::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
return Ok(object::PropertyDefinition::method_definition(
MethodDefinition::Async(AsyncFunctionExpr::new(None, params, body)),
method,
property_name,
));
}
// MethodDefinition[?Yield, ?Await] -> GeneratorMethod[?Yield, ?Await]
if cursor.next_if(Punctuator::Mul, interner)?.is_some() {
let property_name =
PropertyName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
let params_start_position = cursor
.expect(
Punctuator::OpenParen,
"generator method definition",
interner,
)?
.span()
.start();
let params = FormalParameters::new(false, false).parse(cursor, interner)?;
cursor.expect(
Punctuator::CloseParen,
"generator method definition",
interner,
)?;
// Early Error: UniqueFormalParameters : FormalParameters
// NOTE: does not appear to be in ECMAScript specs for GeneratorMethod
if params.has_duplicates() {
return Err(ParseError::lex(LexError::Syntax(
"Duplicate parameter name not allowed in this context".into(),
params_start_position,
)));
}
cursor.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
"generator method definition",
interner,
)?;
let body = FunctionBody::new(true, false).parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"generator method definition",
interner,
)?;
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of UniqueFormalParameters is false.
if body.strict() && !params.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list"
.into(),
params_start_position,
)));
}
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
if cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.kind()
== &TokenKind::Punctuator(Punctuator::Mul)
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
let (property_name, method) =
GeneratorMethod::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
return Ok(object::PropertyDefinition::method_definition(
MethodDefinition::Generator(GeneratorExpr::new(None, params, body)),
method,
property_name,
));
}
@ -502,18 +317,14 @@ where
)?
.span()
.end();
let params = FormalParameters::new(false, false).parse(cursor, interner)?;
let parameters: FormalParameterList = FormalParameter::new(false, false)
.parse(cursor, interner)?
.into();
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseParen),
"set method definition",
interner,
)?;
if params.parameters.len() != 1 {
return Err(ParseError::general(
"set method definition must have one parameter",
params_start_position,
));
}
cursor.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
@ -529,7 +340,7 @@ where
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of PropertySetParameterList is false.
if body.strict() && !params.is_simple() {
if body.strict() && !parameters.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list"
.into(),
@ -538,7 +349,7 @@ where
}
Ok(object::PropertyDefinition::method_definition(
MethodDefinition::Set(FunctionExpr::new(None, params, body)),
MethodDefinition::Set(FunctionExpr::new(None, parameters, body)),
property_name,
))
}
@ -605,14 +416,14 @@ where
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyName
#[derive(Debug, Clone)]
struct PropertyName {
pub(in crate::syntax::parser) struct PropertyName {
allow_yield: AllowYield,
allow_await: AllowAwait,
}
impl PropertyName {
/// Creates a new `PropertyName` parser.
fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
@ -637,20 +448,31 @@ where
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("PropertyName", "Parsing");
// ComputedPropertyName[?Yield, ?Await] -> [ AssignmentExpression[+In, ?Yield, ?Await] ]
if cursor.next_if(Punctuator::OpenBracket, interner)?.is_some() {
let node = AssignmentExpression::new(None, false, self.allow_yield, self.allow_await)
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let name = match token.kind() {
TokenKind::Punctuator(Punctuator::OpenBracket) => {
cursor.next(interner).expect("token disappeared");
let node =
AssignmentExpression::new(None, false, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
cursor.expect(Punctuator::CloseBracket, "expected token ']'", interner)?;
return Ok(node.into());
}
// LiteralPropertyName
Ok(cursor
.next(interner)?
.ok_or(ParseError::AbruptEnd)?
.to_sym(interner)
.into())
TokenKind::Identifier(name) => object::PropertyName::Literal(*name),
TokenKind::StringLiteral(name) => Node::Const(Const::from(*name)).into(),
TokenKind::NumericLiteral(num) => match num {
Numeric::Rational(num) => Node::Const(Const::from(*num)).into(),
Numeric::Integer(num) => Node::Const(Const::from(*num)).into(),
Numeric::BigInt(num) => Node::Const(Const::from(num.clone())).into(),
},
TokenKind::Keyword(word) => {
Node::Const(Const::from(interner.get_or_intern_static(word.as_str()))).into()
}
TokenKind::NullLiteral => Node::Const(Const::from(Sym::NULL)).into(),
_ => return Err(ParseError::AbruptEnd),
};
cursor.next(interner).expect("token disappeared");
Ok(name)
}
}
@ -705,3 +527,303 @@ where
.parse(cursor, interner)
}
}
/// `GeneratorMethod` parsing.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-GeneratorMethod
#[derive(Debug, Clone, Copy)]
pub(in crate::syntax::parser) struct GeneratorMethod {
allow_yield: AllowYield,
allow_await: AllowAwait,
}
impl GeneratorMethod {
/// Creates a new `GeneratorMethod` parser.
pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for GeneratorMethod
where
R: Read,
{
type Output = (object::PropertyName, MethodDefinition);
fn parse(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("GeneratorMethod", "Parsing");
cursor.expect(Punctuator::Mul, "generator method definition", interner)?;
let property_name =
PropertyName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
let params = UniqueFormalParameters::new(true, false).parse(cursor, interner)?;
let body_start = cursor
.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
"generator method definition",
interner,
)?
.span()
.start();
let body = FunctionBody::new(true, false).parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"generator method definition",
interner,
)?;
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of UniqueFormalParameters is false.
if body.strict() && !params.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list".into(),
body_start,
)));
}
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
Ok((
property_name,
MethodDefinition::Generator(GeneratorExpr::new(None, params, body)),
))
}
}
/// `AsyncGeneratorMethod` parsing.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorMethod
#[derive(Debug, Clone, Copy)]
pub(in crate::syntax::parser) struct AsyncGeneratorMethod {
allow_yield: AllowYield,
allow_await: AllowAwait,
}
impl AsyncGeneratorMethod {
/// Creates a new `AsyncGeneratorMethod` parser.
pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for AsyncGeneratorMethod
where
R: Read,
{
type Output = (object::PropertyName, MethodDefinition);
fn parse(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("AsyncGeneratorMethod", "Parsing");
cursor.expect(
Punctuator::Mul,
"async generator method definition",
interner,
)?;
let property_name =
PropertyName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
let params = UniqueFormalParameters::new(true, true).parse(cursor, interner)?;
let body_start = cursor
.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
"async generator method definition",
interner,
)?
.span()
.start();
let body = FunctionBody::new(true, true).parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"async generator method definition",
interner,
)?;
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of UniqueFormalParameters is false.
if body.strict() && !params.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list".into(),
body_start,
)));
}
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
Ok((
property_name,
MethodDefinition::AsyncGenerator(AsyncGeneratorExpr::new(None, params, body)),
))
}
}
/// `AsyncMethod` parsing.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncMethod
#[derive(Debug, Clone, Copy)]
pub(in crate::syntax::parser) struct AsyncMethod {
allow_yield: AllowYield,
allow_await: AllowAwait,
}
impl AsyncMethod {
/// Creates a new `AsyncMethod` parser.
pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for AsyncMethod
where
R: Read,
{
type Output = (object::PropertyName, MethodDefinition);
fn parse(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("AsyncMethod", "Parsing");
let property_name =
PropertyName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
let params = UniqueFormalParameters::new(false, true).parse(cursor, interner)?;
let body_start = cursor
.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
"async method definition",
interner,
)?
.span()
.start();
let body = FunctionBody::new(true, true).parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"async method definition",
interner,
)?;
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of UniqueFormalParameters is false.
if body.strict() && !params.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list".into(),
body_start,
)));
}
// Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also
// occurs in the LexicallyDeclaredNames of GeneratorBody.
{
let lexically_declared_names = body.lexically_declared_names(interner);
for param in params.parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names.contains(&param_name) {
return Err(ParseError::lex(LexError::Syntax(
format!(
"Redeclaration of formal parameter `{}`",
interner.resolve_expect(param_name)
)
.into(),
match cursor.peek(0, interner)? {
Some(token) => token.span().end(),
None => Position::new(1, 1),
},
)));
}
}
}
}
Ok((
property_name,
MethodDefinition::Async(AsyncFunctionExpr::new(None, params, body)),
))
}
}

2
boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs

@ -93,6 +93,7 @@ fn check_object_short_function_arguments() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![],
)),
@ -170,6 +171,7 @@ fn check_object_setter() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![],
)),

18
boa_engine/src/syntax/parser/expression/unary.rs

@ -67,16 +67,28 @@ where
let token_start = tok.span().start();
match tok.kind() {
TokenKind::Keyword(Keyword::Delete) => {
cursor.next(interner)?.expect("Delete keyword vanished"); // Consume the token.
cursor.next(interner)?.expect("Delete keyword vanished");
let position = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let val = self.parse(cursor, interner)?;
if cursor.strict_mode() {
if let Node::Identifier(_) = val {
match val {
Node::Identifier(_) if cursor.strict_mode() => {
return Err(ParseError::lex(LexError::Syntax(
"Delete <variable> statements not allowed in strict mode".into(),
token_start,
)));
}
Node::GetPrivateField(_) => {
return Err(ParseError::general(
"private fields can not be deleted",
position,
));
}
_ => {}
}
Ok(node::UnaryOp::new(UnaryOp::Delete, val).into())

22
boa_engine/src/syntax/parser/expression/update.rs

@ -83,8 +83,30 @@ where
_ => {}
}
let position = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let lhs = LeftHandSideExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
if cursor.strict_mode() {
if let Node::Identifier(ident) = lhs {
if ident.sym() == Sym::ARGUMENTS {
return Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'arguments' in strict mode".into(),
position,
)));
} else if ident.sym() == Sym::EVAL {
return Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'eval' in strict mode".into(),
position,
)));
}
}
}
let strict = cursor.strict_mode();
if let Some(tok) = cursor.peek(0, interner)? {
let token_start = tok.span().start();

94
boa_engine/src/syntax/parser/function/mod.rs

@ -72,12 +72,14 @@ where
let mut flags = FormalParameterListFlags::default();
let mut params = Vec::new();
let mut length = 0;
let next_token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
if next_token.kind() == &TokenKind::Punctuator(Punctuator::CloseParen) {
return Ok(FormalParameterList::new(
params.into_boxed_slice(),
FormalParameterListFlags::IS_SIMPLE,
flags,
length,
));
}
let start_position = next_token.span().start();
@ -94,8 +96,14 @@ where
FunctionRestParameter::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?
}
_ => FormalParameter::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?,
_ => {
let param = FormalParameter::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
if param.init().is_none() {
length += 1;
}
param
}
};
if next_param.is_rest_param() && next_param.init().is_some() {
@ -145,6 +153,14 @@ where
}
cursor.expect(Punctuator::Comma, "parameter list", interner)?;
if cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.kind()
== &TokenKind::Punctuator(Punctuator::CloseParen)
{
break;
}
}
// Early Error: It is a Syntax Error if IsSimpleParameterList of FormalParameterList is false
@ -157,8 +173,75 @@ where
start_position,
)));
}
Ok(FormalParameterList::new(
params.into_boxed_slice(),
flags,
length,
))
}
}
/// `UniqueFormalParameters` parsing.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-UniqueFormalParameters
#[derive(Debug, Clone, Copy)]
pub(in crate::syntax::parser) struct UniqueFormalParameters {
allow_yield: AllowYield,
allow_await: AllowAwait,
}
Ok(FormalParameterList::new(params.into_boxed_slice(), flags))
impl UniqueFormalParameters {
/// Creates a new `UniqueFormalParameters` parser.
pub(in crate::syntax::parser) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for UniqueFormalParameters
where
R: Read,
{
type Output = FormalParameterList;
fn parse(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
let params_start_position = cursor
.expect(
TokenKind::Punctuator(Punctuator::OpenParen),
"unique formal parameters",
interner,
)?
.span()
.end();
let params =
FormalParameters::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseParen),
"unique formal parameters",
interner,
)?;
// Early Error: UniqueFormalParameters : FormalParameters
if params.has_duplicates() {
return Err(ParseError::lex(LexError::Syntax(
"duplicate parameter name not allowed in unique formal parameters".into(),
params_start_position,
)));
}
Ok(params)
}
}
@ -381,7 +464,8 @@ where
pub(in crate::syntax::parser) type FunctionBody = FunctionStatementList;
/// The possible `TokenKind` which indicate the end of a function statement.
const FUNCTION_BREAK_TOKENS: [TokenKind; 1] = [TokenKind::Punctuator(Punctuator::CloseBlock)];
pub(in crate::syntax::parser) const FUNCTION_BREAK_TOKENS: [TokenKind; 1] =
[TokenKind::Punctuator(Punctuator::CloseBlock)];
/// A function statement list
///

20
boa_engine/src/syntax/parser/function/tests.rs

@ -22,6 +22,7 @@ fn check_basic() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()],
)
@ -51,6 +52,7 @@ fn check_duplicates_strict_off() {
]),
flags: FormalParameterListFlags::default()
.union(FormalParameterListFlags::HAS_DUPLICATES),
length: 2,
},
vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()],
)
@ -84,6 +86,7 @@ fn check_basic_semicolon_insertion() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()],
)
@ -106,6 +109,7 @@ fn check_empty_return() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![Return::new::<Node, Option<Node>, Option<_>>(None, None).into()],
)
@ -128,6 +132,7 @@ fn check_empty_return_semicolon_insertion() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![Return::new::<Node, Option<Node>, Option<_>>(None, None).into()],
)
@ -157,6 +162,7 @@ fn check_rest_operator() {
]),
flags: FormalParameterListFlags::empty()
.union(FormalParameterListFlags::HAS_REST_PARAMETER),
length: 1,
},
vec![],
)
@ -180,6 +186,7 @@ fn check_arrow_only_rest() {
)]),
flags: FormalParameterListFlags::empty()
.union(FormalParameterListFlags::HAS_REST_PARAMETER),
length: 0,
},
vec![],
)
@ -213,6 +220,7 @@ fn check_arrow_rest() {
]),
flags: FormalParameterListFlags::empty()
.union(FormalParameterListFlags::HAS_REST_PARAMETER),
length: 2,
},
vec![],
)
@ -241,6 +249,7 @@ fn check_arrow() {
),
]),
flags: FormalParameterListFlags::default(),
length: 2,
},
vec![Return::new(
BinOp::new(
@ -277,6 +286,7 @@ fn check_arrow_semicolon_insertion() {
),
]),
flags: FormalParameterListFlags::default(),
length: 2,
},
vec![Return::new(
BinOp::new(
@ -313,6 +323,7 @@ fn check_arrow_epty_return() {
),
]),
flags: FormalParameterListFlags::default(),
length: 2,
},
vec![Return::new::<Node, Option<_>, Option<_>>(None, None).into()],
)
@ -341,6 +352,7 @@ fn check_arrow_empty_return_semicolon_insertion() {
),
]),
flags: FormalParameterListFlags::default(),
length: 2,
},
vec![Return::new::<Node, Option<_>, Option<_>>(None, None).into()],
)
@ -369,6 +381,7 @@ fn check_arrow_assignment() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![Return::new::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),
@ -406,6 +419,7 @@ fn check_arrow_assignment_nobrackets() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![Return::new::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),
@ -443,6 +457,7 @@ fn check_arrow_assignment_noparenthesis() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![Return::new::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),
@ -480,6 +495,7 @@ fn check_arrow_assignment_noparenthesis_nobrackets() {
false,
)]),
flags: FormalParameterListFlags::default(),
length: 1,
},
vec![Return::new::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),
@ -526,6 +542,7 @@ fn check_arrow_assignment_2arg() {
),
]),
flags: FormalParameterListFlags::default(),
length: 2,
},
vec![Return::new::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),
@ -572,6 +589,7 @@ fn check_arrow_assignment_2arg_nobrackets() {
),
]),
flags: FormalParameterListFlags::default(),
length: 2,
},
vec![Return::new::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),
@ -625,6 +643,7 @@ fn check_arrow_assignment_3arg() {
),
]),
flags: FormalParameterListFlags::default(),
length: 3,
},
vec![Return::new::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),
@ -678,6 +697,7 @@ fn check_arrow_assignment_3arg_nobrackets() {
),
]),
flags: FormalParameterListFlags::default(),
length: 3,
},
vec![Return::new::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),

1200
boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs

File diff suppressed because it is too large Load Diff

9
boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs

@ -13,9 +13,11 @@ mod async_generator_decl;
mod function_decl;
mod generator_decl;
pub(in crate::syntax) mod class_decl;
use self::{
async_function_decl::AsyncFunctionDeclaration, async_generator_decl::AsyncGeneratorDeclaration,
generator_decl::GeneratorDeclaration,
class_decl::ClassDeclaration, generator_decl::GeneratorDeclaration,
};
use crate::syntax::{
ast::node::{FormalParameterList, StatementList},
@ -101,6 +103,11 @@ where
.map(Node::from)
}
}
TokenKind::Keyword(Keyword::Class) => {
ClassDeclaration::new(false, false, self.is_default)
.parse(cursor, interner)
.map(Node::from)
}
_ => unreachable!("unknown token found: {:?}", tok),
}
}

2
boa_engine/src/syntax/parser/statement/declaration/lexical.rs

@ -307,7 +307,6 @@ where
Ok(Declaration::new_with_array_pattern(bindings, init))
}
_ => {
let ident = BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
@ -324,7 +323,6 @@ where
} else {
None
};
Ok(Declaration::new_with_identifier(ident, init))
}
}

5
boa_engine/src/syntax/parser/statement/declaration/mod.rs

@ -22,6 +22,9 @@ use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;
pub(in crate::syntax::parser) use hoistable::class_decl::ClassTail;
pub(in crate::syntax) use hoistable::class_decl::PrivateElement;
/// Parses a declaration.
///
/// More information:
@ -64,7 +67,7 @@ where
let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
match tok.kind() {
TokenKind::Keyword(Keyword::Function | Keyword::Async) => {
TokenKind::Keyword(Keyword::Function | Keyword::Async | Keyword::Class) => {
HoistableDeclaration::new(self.allow_yield, self.allow_await, false)
.parse(cursor, interner)
}

22
boa_engine/src/syntax/parser/statement/mod.rs

@ -55,6 +55,9 @@ use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use std::{collections::HashSet, io::Read, vec};
pub(in crate::syntax::parser) use declaration::ClassTail;
pub(in crate::syntax) use declaration::PrivateElement;
/// Statement parsing.
///
/// This can be one of the following:
@ -465,7 +468,7 @@ where
let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
match *tok.kind() {
TokenKind::Keyword(Keyword::Function | Keyword::Async) => {
TokenKind::Keyword(Keyword::Function | Keyword::Async | Keyword::Class) => {
if strict_mode && self.in_block {
return Err(ParseError::lex(LexError::Syntax(
"Function declaration in blocks not allowed in strict mode".into(),
@ -536,6 +539,18 @@ where
let next_token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;
match next_token.kind() {
TokenKind::Identifier(Sym::ARGUMENTS) if cursor.strict_mode() => {
Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'arguments' in strict mode".into(),
next_token.span().start(),
)))
}
TokenKind::Identifier(Sym::EVAL) if cursor.strict_mode() => {
Err(ParseError::lex(LexError::Syntax(
"unexpected identifier 'eval' in strict mode".into(),
next_token.span().start(),
)))
}
TokenKind::Identifier(ref s) => Ok(*s),
TokenKind::Keyword(Keyword::Yield) if self.allow_yield.0 => {
// Early Error: It is a Syntax Error if this production has a [Yield] parameter and StringValue of Identifier is "yield".
@ -554,6 +569,7 @@ where
Ok(Sym::YIELD)
}
}
TokenKind::Keyword(Keyword::Await) if cursor.arrow() => Ok(Sym::AWAIT),
TokenKind::Keyword(Keyword::Await) if self.allow_await.0 => {
// Early Error: It is a Syntax Error if this production has an [Await] parameter and StringValue of Identifier is "await".
Err(ParseError::general(
@ -674,7 +690,7 @@ where
match peek_token.kind() {
TokenKind::Punctuator(Punctuator::Assign) => {
let init = Initializer::new(
None,
Some(property_name),
self.allow_in,
self.allow_yield,
self.allow_await,
@ -1085,7 +1101,7 @@ where
{
TokenKind::Punctuator(Punctuator::Assign) => {
let default_init = Initializer::new(
None,
Some(ident),
self.allow_in,
self.allow_yield,
self.allow_await,

4
boa_engine/src/syntax/parser/statement/variable/mod.rs

@ -20,7 +20,7 @@ use std::io::Read;
/// Variable statement parsing.
///
/// A varible statement contains the `var` keyword.
/// A variable statement contains the `var` keyword.
///
/// More information:
/// - [MDN documentation][mdn]
@ -232,7 +232,6 @@ where
Ok(Declaration::new_with_array_pattern(bindings, init))
}
_ => {
let ident = BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
@ -249,7 +248,6 @@ where
} else {
None
};
Ok(Declaration::new_with_identifier(ident, init))
}
}

1
boa_engine/src/syntax/parser/tests.rs

@ -414,6 +414,7 @@ fn spread_in_arrow_function() {
)]),
flags: FormalParameterListFlags::empty()
.union(FormalParameterListFlags::HAS_REST_PARAMETER),
length: 0,
},
vec![Identifier::from(b).into()],
)

43
boa_engine/src/vm/code_block.rs

@ -13,7 +13,7 @@ use crate::{
context::intrinsics::StandardConstructors,
environments::{BindingLocator, DeclarativeEnvironmentStack},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::PropertyDescriptor,
property::{PropertyDescriptor, PropertyKey},
syntax::ast::node::FormalParameterList,
vm::call_frame::GeneratorResumeKind,
vm::{call_frame::FinallyReturn, CallFrame, Opcode},
@ -96,6 +96,10 @@ pub struct CodeBlock {
/// The `arguments` binding location of the function, if set.
#[unsafe_ignore_trace]
pub(crate) arguments_binding: Option<BindingLocator>,
/// Similar to the `[[ClassFieldInitializerName]]` slot in the spec.
/// Holds class field names that are computed at class declaration time.
pub(crate) computed_field_names: Option<Cell<Vec<PropertyKey>>>,
}
impl CodeBlock {
@ -116,6 +120,7 @@ impl CodeBlock {
params: FormalParameterList::default(),
lexical_name_argument: false,
arguments_binding: None,
computed_field_names: None,
}
}
@ -235,8 +240,15 @@ impl CodeBlock {
Opcode::GetPropertyByName
| Opcode::SetPropertyByName
| Opcode::DefineOwnPropertyByName
| Opcode::DefineClassMethodByName
| Opcode::SetPropertyGetterByName
| Opcode::DefineClassGetterByName
| Opcode::SetPropertySetterByName
| Opcode::DefineClassSetterByName
| Opcode::SetPrivateValue
| Opcode::SetPrivateSetter
| Opcode::SetPrivateGetter
| Opcode::GetPrivateField
| Opcode::DeletePropertyByName => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
@ -258,6 +270,7 @@ impl CodeBlock {
| Opcode::PushFalse
| Opcode::PushUndefined
| Opcode::PushEmptyObject
| Opcode::PushClassPrototype
| Opcode::Add
| Opcode::Sub
| Opcode::Div
@ -293,9 +306,13 @@ impl CodeBlock {
| Opcode::GetPropertyByValue
| Opcode::SetPropertyByValue
| Opcode::DefineOwnPropertyByValue
| Opcode::DefineClassMethodByValue
| Opcode::SetPropertyGetterByValue
| Opcode::DefineClassGetterByValue
| Opcode::SetPropertySetterByValue
| Opcode::DefineClassSetterByValue
| Opcode::DeletePropertyByValue
| Opcode::ToPropertyKey
| Opcode::ToBoolean
| Opcode::Throw
| Opcode::TryEnd
@ -327,6 +344,7 @@ impl CodeBlock {
| Opcode::PopOnReturnSub
| Opcode::Yield
| Opcode::GeneratorNext
| Opcode::PushClassComputedFieldName
| Opcode::Nop => String::new(),
}
}
@ -864,7 +882,7 @@ impl JsObject {
} => {
std::mem::swap(&mut environments, &mut context.realm.environments);
let this: JsValue = {
let this = {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
@ -874,13 +892,22 @@ impl JsObject {
StandardConstructors::object,
context,
)?;
Self::from_proto_and_data(prototype, ObjectData::ordinary()).into()
let this = Self::from_proto_and_data(prototype, ObjectData::ordinary());
// Set computed class field names if they exist.
if let Some(fields) = &code.computed_field_names {
for key in fields.borrow().iter().rev() {
context.vm.push(key);
}
}
this
};
context
.realm
.environments
.push_function(code.num_bindings, this.clone());
.push_function(code.num_bindings, this.clone().into());
let mut arguments_in_parameter_names = false;
let mut is_simple_parameter_list = true;
@ -937,16 +964,10 @@ impl JsObject {
let param_count = code.params.parameters.len();
let this = if (!code.strict && !context.strict()) && this.is_null_or_undefined() {
context.global_object().clone().into()
} else {
this
};
context.vm.push_frame(CallFrame {
prev: None,
code,
this,
this: this.into(),
pc: 0,
catch: Vec::new(),
finally_return: FinallyReturn::None,

260
boa_engine/src/vm/mod.rs

@ -3,7 +3,8 @@
//! plus an interpreter to execute those instructions
use crate::{
builtins::{iterable::IteratorRecord, Array, ForInIterator, Number},
builtins::{function::Function, iterable::IteratorRecord, Array, ForInIterator, Number},
object::PrivateElement,
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
value::Numeric,
vm::{
@ -187,6 +188,22 @@ impl Context {
self.vm.push(value);
}
Opcode::PushEmptyObject => self.vm.push(self.construct_object()),
Opcode::PushClassPrototype => {
let superclass = self.vm.pop();
if superclass.is_null() {
self.vm.push(JsValue::Null);
}
if let Some(superclass) = superclass.as_constructor() {
let proto = superclass.get("prototype", self)?;
if !proto.is_object() && !proto.is_null() {
return self
.throw_type_error("superclass prototype must be an object or null");
}
self.vm.push(proto);
} else {
return self.throw_type_error("superclass must be a constructor");
}
}
Opcode::PushNewArray => {
let array = Array::array_create(0, None, self)
.expect("Array creation with 0 length should never fail");
@ -636,7 +653,6 @@ impl Context {
}
Opcode::DefineOwnPropertyByName => {
let index = self.vm.read::<u32>();
let object = self.vm.pop();
let value = self.vm.pop();
let object = if let Some(object) = object.as_object() {
@ -644,10 +660,8 @@ impl Context {
} else {
object.to_object(self)?
};
let name = self.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name);
object.__define_own_property__(
name.into(),
PropertyDescriptor::builder()
@ -659,6 +673,28 @@ impl Context {
self,
)?;
}
Opcode::DefineClassMethodByName => {
let index = self.vm.read::<u32>();
let object = self.vm.pop();
let value = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object.clone()
} else {
object.to_object(self)?
};
let name = self.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name);
object.__define_own_property__(
name.into(),
PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(false)
.configurable(true)
.build(),
self,
)?;
}
Opcode::SetPropertyByValue => {
let object = self.vm.pop();
let key = self.vm.pop();
@ -686,9 +722,7 @@ impl Context {
} else {
object.to_object(self)?
};
let key = key.to_property_key(self)?;
object.__define_own_property__(
key,
PropertyDescriptor::builder()
@ -700,12 +734,32 @@ impl Context {
self,
)?;
}
Opcode::DefineClassMethodByValue => {
let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object.clone()
} else {
object.to_object(self)?
};
let key = key.to_property_key(self)?;
object.__define_own_property__(
key,
PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(false)
.configurable(true)
.build(),
self,
)?;
}
Opcode::SetPropertyGetterByName => {
let index = self.vm.read::<u32>();
let object = self.vm.pop();
let value = self.vm.pop();
let object = object.to_object(self)?;
let name = self.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name).into();
let set = object
@ -724,6 +778,29 @@ impl Context {
self,
)?;
}
Opcode::DefineClassGetterByName => {
let index = self.vm.read::<u32>();
let object = self.vm.pop();
let value = self.vm.pop();
let object = object.to_object(self)?;
let name = self.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name).into();
let set = object
.__get_own_property__(&name, self)?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
object.__define_own_property__(
name,
PropertyDescriptor::builder()
.maybe_get(Some(value))
.maybe_set(set)
.enumerable(false)
.configurable(true)
.build(),
self,
)?;
}
Opcode::SetPropertyGetterByValue => {
let value = self.vm.pop();
let key = self.vm.pop();
@ -746,6 +823,28 @@ impl Context {
self,
)?;
}
Opcode::DefineClassGetterByValue => {
let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = object.to_object(self)?;
let name = key.to_property_key(self)?;
let set = object
.__get_own_property__(&name, self)?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
object.__define_own_property__(
name,
PropertyDescriptor::builder()
.maybe_get(Some(value))
.maybe_set(set)
.enumerable(false)
.configurable(true)
.build(),
self,
)?;
}
Opcode::SetPropertySetterByName => {
let index = self.vm.read::<u32>();
let object = self.vm.pop();
@ -769,6 +868,29 @@ impl Context {
self,
)?;
}
Opcode::DefineClassSetterByName => {
let index = self.vm.read::<u32>();
let object = self.vm.pop();
let value = self.vm.pop();
let object = object.to_object(self)?;
let name = self.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name).into();
let get = object
.__get_own_property__(&name, self)?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
object.__define_own_property__(
name,
PropertyDescriptor::builder()
.maybe_set(Some(value))
.maybe_get(get)
.enumerable(false)
.configurable(true)
.build(),
self,
)?;
}
Opcode::SetPropertySetterByValue => {
let value = self.vm.pop();
let key = self.vm.pop();
@ -791,6 +913,125 @@ impl Context {
self,
)?;
}
Opcode::DefineClassSetterByValue => {
let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = object.to_object(self)?;
let name = key.to_property_key(self)?;
let get = object
.__get_own_property__(&name, self)?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
object.__define_own_property__(
name,
PropertyDescriptor::builder()
.maybe_set(Some(value))
.maybe_get(get)
.enumerable(false)
.configurable(true)
.build(),
self,
)?;
}
Opcode::SetPrivateValue => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.names[index as usize];
let value = self.vm.pop();
let object = self.vm.pop();
if let Some(object) = object.as_object() {
let mut object_borrow_mut = object.borrow_mut();
if let Some(PrivateElement::Accessor {
getter: _,
setter: Some(setter),
}) = object_borrow_mut.get_private_element(name)
{
let setter = setter.clone();
drop(object_borrow_mut);
setter.call(&object.clone().into(), &[value], self)?;
} else {
object_borrow_mut.set_private_element(name, PrivateElement::Value(value));
}
} else {
return self.throw_type_error("cannot set private property on non-object");
}
}
Opcode::SetPrivateSetter => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.names[index as usize];
let value = self.vm.pop();
let value = value.as_callable().expect("setter must be callable");
let object = self.vm.pop();
if let Some(object) = object.as_object() {
let mut object_borrow_mut = object.borrow_mut();
object_borrow_mut.set_private_element_setter(name, value.clone());
} else {
return self.throw_type_error("cannot set private setter on non-object");
}
}
Opcode::SetPrivateGetter => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.names[index as usize];
let value = self.vm.pop();
let value = value.as_callable().expect("getter must be callable");
let object = self.vm.pop();
if let Some(object) = object.as_object() {
let mut object_borrow_mut = object.borrow_mut();
object_borrow_mut.set_private_element_getter(name, value.clone());
} else {
return self.throw_type_error("cannot set private getter on non-object");
}
}
Opcode::GetPrivateField => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.names[index as usize];
let value = self.vm.pop();
if let Some(object) = value.as_object() {
let object_borrow_mut = object.borrow();
if let Some(element) = object_borrow_mut.get_private_element(name) {
match element {
PrivateElement::Value(value) => self.vm.push(value),
PrivateElement::Accessor {
getter: Some(getter),
setter: _,
} => {
let value = getter.call(&value, &[], self)?;
self.vm.push(value);
}
PrivateElement::Accessor { .. } => {
return self.throw_type_error(
"private property was defined without a getter",
);
}
}
} else {
return self.throw_type_error("private property does not exist");
}
} else {
return self.throw_type_error("cannot read private property from non-object");
}
}
Opcode::PushClassComputedFieldName => {
let object = self.vm.pop();
let value = self.vm.pop();
let value = value.to_property_key(self)?;
let object_obj = object
.as_object()
.expect("can only add field to function object");
let object = object_obj.borrow();
let function = object
.as_function()
.expect("can only add field to function object");
if let Function::Ordinary { code, .. } = function {
let code_b = code
.computed_field_names
.as_ref()
.expect("class constructor must have fields");
let mut fields_mut = code_b.borrow_mut();
fields_mut.push(value);
}
}
Opcode::DeletePropertyByName => {
let index = self.vm.read::<u32>();
let key = self.vm.frame().code.names[index as usize];
@ -825,6 +1066,11 @@ impl Context {
object.copy_data_properties(&source, excluded_keys, self)?;
self.vm.push(value);
}
Opcode::ToPropertyKey => {
let value = self.vm.pop();
let key = value.to_property_key(self)?;
self.vm.push(key);
}
Opcode::Throw => {
let value = self.vm.pop();
return Err(value);

133
boa_engine/src/vm/opcode.rs

@ -131,6 +131,13 @@ pub enum Opcode {
/// Stack: **=>** `{}`
PushEmptyObject,
/// Get the prototype of a superclass and push it on the stack.
///
/// Operands:
///
/// Stack: superclass **=>** superclass.prototype
PushClassPrototype,
/// Push an empty array value on the stack.
///
/// Operands:
@ -508,6 +515,13 @@ pub enum Opcode {
/// Stack: value, object **=>**
DefineOwnPropertyByName,
/// Defines a class method by name.
///
/// Operands: name_index: `u32`
///
/// Stack: value, object **=>**
DefineClassMethodByName,
/// Sets a property by value of an object.
///
/// Like `object[key] = value`
@ -524,6 +538,13 @@ pub enum Opcode {
/// Stack: object, key, value **=>**
DefineOwnPropertyByValue,
/// Defines a class method by value.
///
/// Operands:
///
/// Stack: object, key, value **=>**
DefineClassMethodByValue,
/// Sets a getter property by name of an object.
///
/// Like `get name() value`
@ -533,6 +554,15 @@ pub enum Opcode {
/// Stack: value, object **=>**
SetPropertyGetterByName,
/// Defines a getter class method by name.
///
/// Like `get name() value`
///
/// Operands: name_index: `u32`
///
/// Stack: value, object **=>**
DefineClassGetterByName,
/// Sets a getter property by value of an object.
///
/// Like `get [key]() value`
@ -542,6 +572,15 @@ pub enum Opcode {
/// Stack: object, key, value **=>**
SetPropertyGetterByValue,
/// Defines a getter class method by value.
///
/// Like `get [key]() value`
///
/// Operands:
///
/// Stack: object, key, value **=>**
DefineClassGetterByValue,
/// Sets a setter property by name of an object.
///
/// Like `set name() value`
@ -551,6 +590,15 @@ pub enum Opcode {
/// Stack: value, object **=>**
SetPropertySetterByName,
/// Defines a setter class method by name.
///
/// Like `set name() value`
///
/// Operands: name_index: `u32`
///
/// Stack: value, object **=>**
DefineClassSetterByName,
/// Sets a setter property by value of an object.
///
/// Like `set [key]() value`
@ -560,6 +608,58 @@ pub enum Opcode {
/// Stack: object, key, value **=>**
SetPropertySetterByValue,
/// Defines a setter class method by value.
///
/// Like `set [key]() value`
///
/// Operands:
///
/// Stack: object, key, value **=>**
DefineClassSetterByValue,
/// Set a private property by name from an object.
///
/// Like `#name = value`
///
/// Operands: name_index: `u32`
///
/// Stack: object, value **=>**
SetPrivateValue,
/// Set a private setter property by name from an object.
///
/// Like `set #name() {}`
///
/// Operands: name_index: `u32`
///
/// Stack: object, value **=>**
SetPrivateSetter,
/// Set a private getter property by name from an object.
///
/// Like `get #name() {}`
///
/// Operands: name_index: `u32`
///
/// Stack: object, value **=>**
SetPrivateGetter,
/// Get a private property by name from an object an push it on the stack.
///
/// Like `object.#name`
///
/// Operands: name_index: `u32`
///
/// Stack: object **=>** value
GetPrivateField,
/// Push a computed class field name to a class constructor object.
///
/// Operands:
///
/// Stack: value, object **=>**
PushClassComputedFieldName,
/// Deletes a property by name of an object.
///
/// Like `delete object.key.`
@ -585,6 +685,13 @@ pub enum Opcode {
/// Stack: source, value, excluded_key_0 ... excluded_key_n **=>** value
CopyDataProperties,
/// Call ToPropertyKey on the value on the stack.
///
/// Operands:
///
/// Stack: value **=>** key
ToPropertyKey,
/// Unconditional jump to address.
///
/// Operands: address: `u32`
@ -956,6 +1063,7 @@ impl Opcode {
Opcode::PushUndefined => "PushUndefined",
Opcode::PushLiteral => "PushLiteral",
Opcode::PushEmptyObject => "PushEmptyObject",
Opcode::PushClassPrototype => "PushClassPrototype",
Opcode::PushNewArray => "PushNewArray",
Opcode::PushValueToArray => "PushValueToArray",
Opcode::PushElisionToArray => "PushElisionToArray",
@ -1008,15 +1116,27 @@ impl Opcode {
Opcode::GetPropertyByValue => "GetPropertyByValue",
Opcode::SetPropertyByName => "SetPropertyByName",
Opcode::DefineOwnPropertyByName => "DefineOwnPropertyByName",
Opcode::DefineClassMethodByName => "DefineClassMethodByName",
Opcode::SetPropertyByValue => "SetPropertyByValue",
Opcode::DefineOwnPropertyByValue => "DefineOwnPropertyByValue",
Opcode::DefineClassMethodByValue => "DefineClassMethodByValue",
Opcode::SetPropertyGetterByName => "SetPropertyGetterByName",
Opcode::DefineClassGetterByName => "DefineClassGetterByName",
Opcode::SetPropertyGetterByValue => "SetPropertyGetterByValue",
Opcode::DefineClassGetterByValue => "DefineClassGetterByValue",
Opcode::SetPropertySetterByName => "SetPropertySetterByName",
Opcode::DefineClassSetterByName => "DefineClassSetterByName",
Opcode::SetPropertySetterByValue => "SetPropertySetterByValue",
Opcode::DefineClassSetterByValue => "DefineClassSetterByValue",
Opcode::SetPrivateValue => "SetPrivateValue",
Opcode::SetPrivateSetter => "SetPrivateSetter",
Opcode::SetPrivateGetter => "SetPrivateGetter",
Opcode::GetPrivateField => "GetPrivateByName",
Opcode::PushClassComputedFieldName => "PushClassComputedFieldName",
Opcode::DeletePropertyByName => "DeletePropertyByName",
Opcode::DeletePropertyByValue => "DeletePropertyByValue",
Opcode::CopyDataProperties => "CopyDataProperties",
Opcode::ToPropertyKey => "ToPropertyKey",
Opcode::Jump => "Jump",
Opcode::JumpIfFalse => "JumpIfFalse",
Opcode::JumpIfNotUndefined => "JumpIfNotUndefined",
@ -1196,6 +1316,19 @@ impl Opcode {
Opcode::GeneratorNext => "INST - GeneratorNext",
Opcode::GeneratorNextDelegate => "INST - GeneratorNextDelegate",
Opcode::Nop => "INST - Nop",
Opcode::PushClassPrototype => "INST - PushClassPrototype",
Opcode::DefineClassMethodByName => "INST - DefineClassMethodByName",
Opcode::DefineClassMethodByValue => "INST - DefineClassMethodByValue",
Opcode::DefineClassGetterByName => "INST - DefineClassGetterByName",
Opcode::DefineClassGetterByValue => "INST - DefineClassGetterByValue",
Opcode::DefineClassSetterByName => "INST - DefineClassSetterByName",
Opcode::DefineClassSetterByValue => "INST - DefineClassSetterByValue",
Opcode::SetPrivateValue => "INST - SetPrivateValue",
Opcode::SetPrivateSetter => "INST - SetPrivateSetter",
Opcode::SetPrivateGetter => "INST - SetPrivateGetter",
Opcode::GetPrivateField => "INST - GetPrivateField",
Opcode::PushClassComputedFieldName => "INST - PushClassComputedFieldName",
Opcode::ToPropertyKey => "INST - ToPropertyKey",
}
}
}

42
boa_interner/src/lib.rs

@ -291,6 +291,36 @@ impl Sym {
/// Symbol for the `"raw"` string.
pub const RAW: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(12)) };
/// Symbol for the `"static"` string.
pub const STATIC: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(13)) };
/// Symbol for the `"prototype"` string.
pub const PROTOTYPE: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(14)) };
/// Symbol for the `"constructor"` string.
pub const CONSTRUCTOR: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(15)) };
/// Symbol for the `"implements"` string.
pub const IMPLEMENTS: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(16)) };
/// Symbol for the `"interface"` string.
pub const INTERFACE: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(17)) };
/// Symbol for the `"let"` string.
pub const LET: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(18)) };
/// Symbol for the `"package"` string.
pub const PACKAGE: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(19)) };
/// Symbol for the `"private"` string.
pub const PRIVATE: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(20)) };
/// Symbol for the `"protected"` string.
pub const PROTECTED: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(21)) };
/// Symbol for the `"public"` string.
pub const PUBLIC: Self = unsafe { Self::from_raw(NonZeroUsize::new_unchecked(22)) };
/// Creates a `Sym` from a raw `NonZeroUsize`.
const fn from_raw(value: NonZeroUsize) -> Self {
Self { value }
@ -341,7 +371,7 @@ impl Interner {
/// List of commonly used static strings.
///
/// Make sure that any string added as a `Sym` constant is also added here.
const STATIC_STRINGS: [&'static str; 12] = [
const STATIC_STRINGS: [&'static str; 22] = [
"",
"arguments",
"await",
@ -354,5 +384,15 @@ impl Interner {
"set",
"<main>",
"raw",
"static",
"prototype",
"constructor",
"implements",
"interface",
"let",
"package",
"private",
"protected",
"public",
];
}

10
boa_interner/src/tests.rs

@ -29,6 +29,16 @@ fn check_constants() {
assert_eq!(Sym::SET, sym_from_usize(10));
assert_eq!(Sym::MAIN, sym_from_usize(11));
assert_eq!(Sym::RAW, sym_from_usize(12));
assert_eq!(Sym::STATIC, sym_from_usize(13));
assert_eq!(Sym::PROTOTYPE, sym_from_usize(14));
assert_eq!(Sym::CONSTRUCTOR, sym_from_usize(15));
assert_eq!(Sym::IMPLEMENTS, sym_from_usize(16));
assert_eq!(Sym::INTERFACE, sym_from_usize(17));
assert_eq!(Sym::LET, sym_from_usize(18));
assert_eq!(Sym::PACKAGE, sym_from_usize(19));
assert_eq!(Sym::PRIVATE, sym_from_usize(20));
assert_eq!(Sym::PROTECTED, sym_from_usize(21));
assert_eq!(Sym::PUBLIC, sym_from_usize(22));
}
#[test]

Loading…
Cancel
Save