From 223966981c112477678f42f47fcf9458171fb6c0 Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Thu, 7 Apr 2022 07:10:32 +0000 Subject: [PATCH] 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. --- boa_engine/src/bytecompiler.rs | 451 ++++++- boa_engine/src/object/jsobject.rs | 29 +- boa_engine/src/object/mod.rs | 71 + .../ast/node/declaration/class_decl/mod.rs | 367 +++++ .../ast/node/declaration/class_decl/tests.rs | 104 ++ .../src/syntax/ast/node/declaration/mod.rs | 1 + .../ast/node/field/get_private_field/mod.rs | 63 + boa_engine/src/syntax/ast/node/field/mod.rs | 1 + boa_engine/src/syntax/ast/node/mod.rs | 21 +- boa_engine/src/syntax/ast/node/object/mod.rs | 27 +- .../syntax/ast/node/operator/assign/mod.rs | 5 + boa_engine/src/syntax/ast/node/parameters.rs | 56 +- boa_engine/src/syntax/lexer/cursor.rs | 2 + boa_engine/src/syntax/lexer/identifier.rs | 6 +- boa_engine/src/syntax/lexer/mod.rs | 5 + .../src/syntax/lexer/private_identifier.rs | 79 ++ boa_engine/src/syntax/lexer/token.rs | 30 +- boa_engine/src/syntax/parser/cursor/mod.rs | 78 +- .../expression/assignment/arrow_function.rs | 5 +- .../expression/assignment/exponentiation.rs | 1 - .../parser/expression/assignment/mod.rs | 26 +- .../parser/expression/assignment/yield.rs | 79 +- .../expression/left_hand_side/member.rs | 6 +- .../parser/expression/left_hand_side/mod.rs | 4 +- .../src/syntax/parser/expression/mod.rs | 6 + .../primary/class_expression/mod.rs | 81 ++ .../syntax/parser/expression/primary/mod.rs | 22 +- .../primary/object_initializer/mod.rs | 612 +++++---- .../primary/object_initializer/tests.rs | 2 + .../src/syntax/parser/expression/unary.rs | 18 +- .../src/syntax/parser/expression/update.rs | 22 + boa_engine/src/syntax/parser/function/mod.rs | 94 +- .../src/syntax/parser/function/tests.rs | 20 + .../declaration/hoistable/class_decl/mod.rs | 1200 +++++++++++++++++ .../statement/declaration/hoistable/mod.rs | 9 +- .../parser/statement/declaration/lexical.rs | 2 - .../parser/statement/declaration/mod.rs | 5 +- boa_engine/src/syntax/parser/statement/mod.rs | 22 +- .../syntax/parser/statement/variable/mod.rs | 4 +- boa_engine/src/syntax/parser/tests.rs | 1 + boa_engine/src/vm/code_block.rs | 43 +- boa_engine/src/vm/mod.rs | 260 +++- boa_engine/src/vm/opcode.rs | 133 ++ boa_interner/src/lib.rs | 42 +- boa_interner/src/tests.rs | 10 + 45 files changed, 3752 insertions(+), 373 deletions(-) create mode 100644 boa_engine/src/syntax/ast/node/declaration/class_decl/mod.rs create mode 100644 boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs create mode 100644 boa_engine/src/syntax/ast/node/field/get_private_field/mod.rs create mode 100644 boa_engine/src/syntax/lexer/private_identifier.rs create mode 100644 boa_engine/src/syntax/parser/expression/primary/class_expression/mod.rs create mode 100644 boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index 88ee542b21..c3c5a41337 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/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(()) + } } diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 3d6ddf6cfd..cf75a8b3a0 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/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,12 +52,28 @@ impl JsObject { /// internal slots from the `data` provided. #[inline] pub fn from_proto_and_data>>(prototype: O, data: ObjectData) -> Self { - Self::from_object(Object { - data, - prototype: prototype.into(), - extensible: true, - properties: PropertyMap::default(), - }) + let prototype: Option = 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: None, + extensible: true, + properties: PropertyMap::default(), + private_elements: FxHashMap::default(), + }) + } } /// Immutably borrows the `Object`. diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 0a2a992a66..9b33d425c4 100644 --- a/boa_engine/src/object/mod.rs +++ b/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, +} + +/// The representation of private object elements. +#[derive(Clone, Debug, Trace, Finalize)] +pub(crate) enum PrivateElement { + Value(JsValue), + Accessor { + getter: Option, + setter: Option, + }, } /// 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 { 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. diff --git a/boa_engine/src/syntax/ast/node/declaration/class_decl/mod.rs b/boa_engine/src/syntax/ast/node/declaration/class_decl/mod.rs new file mode 100644 index 0000000000..ec12bbafa1 --- /dev/null +++ b/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>, + constructor: Option, + elements: Box<[ClassElement]>, +} + +impl Class { + /// Creates a new class declaration. + pub(in crate::syntax) fn new( + name: Sym, + super_ref: S, + constructor: C, + elements: E, + ) -> Self + where + S: Into>>, + C: Into>, + E: Into>, + { + 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> { + &self.super_ref + } + + /// Returns the constructor of the class. + pub(crate) fn constructor(&self) -> &Option { + &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), + StaticFieldDefinition(PropertyName, Option), + PrivateMethodDefinition(Sym, MethodDefinition), + PrivateStaticMethodDefinition(Sym, MethodDefinition), + PrivateFieldDefinition(Sym, Option), + PrivateStaticFieldDefinition(Sym, Option), + StaticBlock(StatementList), +} diff --git a/boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs b/boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs new file mode 100644 index 0000000000..e9ff4dd7d1 --- /dev/null +++ b/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() {} + }; + "#, + ); +} diff --git a/boa_engine/src/syntax/ast/node/declaration/mod.rs b/boa_engine/src/syntax/ast/node/declaration/mod.rs index bb26ef93ec..37c98eb03d 100644 --- a/boa_engine/src/syntax/ast/node/declaration/mod.rs +++ b/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; diff --git a/boa_engine/src/syntax/ast/node/field/get_private_field/mod.rs b/boa_engine/src/syntax/ast/node/field/get_private_field/mod.rs new file mode 100644 index 0000000000..9059912860 --- /dev/null +++ b/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, + field: Sym, +} + +impl GetPrivateField { + /// Creates a `GetPrivateField` AST node. + pub fn new(value: V, field: Sym) -> Self + where + V: Into, + { + 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 for Node { + fn from(get_private_field: GetPrivateField) -> Self { + Self::GetPrivateField(get_private_field) + } +} diff --git a/boa_engine/src/syntax/ast/node/field/mod.rs b/boa_engine/src/syntax/ast/node/field/mod.rs index 54455ff5df..a42a8d0103 100644 --- a/boa_engine/src/syntax/ast/node/field/mod.rs +++ b/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}; diff --git a/boa_engine/src/syntax/ast/node/mod.rs b/boa_engine/src/syntax/ast/node/mod.rs index 8d36bd5d8d..f4eadd0551 100644 --- a/boa_engine/src/syntax/ast/node/mod.rs +++ b/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 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), } } } diff --git a/boa_engine/src/syntax/ast/node/object/mod.rs b/boa_engine/src/syntax/ast/node/object/mod.rs index f5dc0a3022..a26c77ec51 100644 --- a/boa_engine/src/syntax/ast/node/object/mod.rs +++ b/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 { + if let Self::Literal(sym) = self { + Some(*sym) + } else { + None + } + } + + pub(in crate::syntax) fn prop_name(&self) -> Option { + 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 { diff --git a/boa_engine/src/syntax/ast/node/operator/assign/mod.rs b/boa_engine/src/syntax/ast/node/operator/assign/mod.rs index 5ff427f18f..bbe875968c 100644 --- a/boa_engine/src/syntax/ast/node/operator/assign/mod.rs +++ b/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 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 { 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 return None, }, Node::ArrayDecl(array) => { let pattern = array_decl_to_declaration_pattern(array)?; diff --git a/boa_engine/src/syntax/ast/node/parameters.rs b/boa_engine/src/syntax/ast/node/parameters.rs index 40fb50a41b..604f388c5e 100644 --- a/boa_engine/src/syntax/ast/node/parameters.rs +++ b/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 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)] diff --git a/boa_engine/src/syntax/lexer/cursor.rs b/boa_engine/src/syntax/lexer/cursor.rs index 5784d11252..0b38fd8713 100644 --- a/boa_engine/src/syntax/lexer/cursor.rs +++ b/boa_engine/src/syntax/lexer/cursor.rs @@ -33,11 +33,13 @@ impl Cursor { } #[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; } diff --git a/boa_engine/src/syntax/lexer/identifier.rs b/boa_engine/src/syntax/lexer/identifier.rs index 6e0210d1b6..c114f6ed60 100644 --- a/boa_engine/src/syntax/lexer/identifier.rs +++ b/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 Tokenizer for Identifier { impl Identifier { #[inline] - fn take_identifier_name( + pub(super) fn take_identifier_name( cursor: &mut Cursor, start_pos: Position, init: char, diff --git a/boa_engine/src/syntax/lexer/mod.rs b/boa_engine/src/syntax/lexer/mod.rs index d804b2d526..87f7a12903 100644 --- a/boa_engine/src/syntax/lexer/mod.rs +++ b/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 Lexer { } #[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 Lexer { 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) diff --git a/boa_engine/src/syntax/lexer/private_identifier.rs b/boa_engine/src/syntax/lexer/private_identifier.rs new file mode 100644 index 0000000000..d3ef44cbcb --- /dev/null +++ b/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 Tokenizer for PrivateIdentifier { + fn lex( + &mut self, + cursor: &mut Cursor, + start_pos: Position, + interner: &mut Interner, + ) -> Result + 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, + )) + } + } +} diff --git a/boa_engine/src/syntax/lexer/token.rs b/boa_engine/src/syntax/lexer/token.rs index 0277db48e9..c08f9667bb 100644 --- a/boa_engine/src/syntax/lexer/token.rs +++ b/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"), - } - } } diff --git a/boa_engine/src/syntax/parser/cursor/mod.rs b/boa_engine/src/syntax/parser/cursor/mod.rs index 2ec0514cdf..c3edc5db44 100644 --- a/boa_engine/src/syntax/parser/cursor/mod.rs +++ b/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 { buffered_lexer: BufferedLexer, + + /// Tracks the private identifiers used in code blocks. + private_environments_stack: Vec>, + + /// Tracks if the cursor is in a arrow function declaration. + arrow: bool, } impl Cursor @@ -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, + ) -> 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( diff --git a/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs b/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs index 9e111fbf1a..7d021aa376 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/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( diff --git a/boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs b/boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs index e403bf91ec..d543074ede 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs @@ -88,7 +88,6 @@ where fn parse(self, cursor: &mut Cursor, 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); diff --git a/boa_engine/src/syntax/parser/expression/assignment/mod.rs b/boa_engine/src/syntax/parser/expression/assignment/mod.rs index e52ec4ae9b..4a82a93571 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/mod.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/mod.rs @@ -87,7 +87,7 @@ where { type Output = Node; - fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + fn parse(mut self, cursor: &mut Cursor, 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 { diff --git a/boa_engine/src/syntax/parser/expression/assignment/yield.rs b/boa_engine/src/syntax/parser/expression/assignment/yield.rs index 4ddf3f9770..c9b1b3dc77 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/yield.rs +++ b/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; + 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::>( + 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::>( + Some(expr), + false, + ))) } - expr = Some( - AssignmentExpression::new(None, self.allow_in, true, self.allow_await) - .parse(cursor, interner)?, - ); + _ => Ok(Node::Yield(Yield::new::>(None, false))), } - - Ok(Node::Yield(Yield::new::>( - expr, delegate, - ))) } } diff --git a/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs b/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs index c184dcd79d..cd4cfde062 100644 --- a/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs +++ b/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()], diff --git a/boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs b/boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs index 418d9de370..4bfa37e501 100644 --- a/boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs +++ b/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, 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(name: N, allow_yield: Y, allow_await: A) -> Self + pub(in crate::syntax::parser) fn new(name: N, allow_yield: Y, allow_await: A) -> Self where N: Into>, Y: Into, diff --git a/boa_engine/src/syntax/parser/expression/mod.rs b/boa_engine/src/syntax/parser/expression/mod.rs index 0faf3accd0..f9e87d94cc 100644 --- a/boa_engine/src/syntax/parser/expression/mod.rs +++ b/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. diff --git a/boa_engine/src/syntax/parser/expression/primary/class_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/class_expression/mod.rs new file mode 100644 index 0000000000..d5395d2d15 --- /dev/null +++ b/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, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ClassExpression { + /// Creates a new `ClassExpression` parser. + pub(in crate::syntax::parser) fn new(name: N, allow_yield: Y, allow_await: A) -> Self + where + N: Into>, + Y: Into, + A: Into, + { + Self { + name: name.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ClassExpression +where + R: Read, +{ + type Output = Node; + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + 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)?, + )) + } +} diff --git a/boa_engine/src/syntax/parser/expression/primary/mod.rs b/boa_engine/src/syntax/parser/expression/primary/mod.rs index 3a1257970e..6a0e2cad8a 100644 --- a/boa_engine/src/syntax/parser/expression/primary/mod.rs +++ b/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) { diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs index 387ccf040c..7245a9775a 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/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(allow_yield: Y, allow_await: A) -> Self + pub(in crate::syntax::parser) fn new(allow_yield: Y, allow_await: A) -> Self where Y: Into, A: Into, @@ -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(¶m_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(¶m_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. - { - 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(¶m_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), - }, - ))); - } - } - } - } - + if cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .kind() + == &TokenKind::Punctuator(Punctuator::Mul) + { + 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(allow_yield: Y, allow_await: A) -> Self + pub(in crate::syntax::parser) fn new(allow_yield: Y, allow_await: A) -> Self where Y: Into, A: Into, @@ -637,20 +448,31 @@ where ) -> Result { 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) - .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()) + 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()); + } + 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(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for GeneratorMethod +where + R: Read, +{ + type Output = (object::PropertyName, MethodDefinition); + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + 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(¶m_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(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for AsyncGeneratorMethod +where + R: Read, +{ + type Output = (object::PropertyName, MethodDefinition); + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + 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(¶m_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(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for AsyncMethod +where + R: Read, +{ + type Output = (object::PropertyName, MethodDefinition); + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + 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(¶m_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)), + )) + } +} diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs index f9f39bf493..6c6e7edc6a 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/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![], )), diff --git a/boa_engine/src/syntax/parser/expression/unary.rs b/boa_engine/src/syntax/parser/expression/unary.rs index d500813ce7..ba79c3260b 100644 --- a/boa_engine/src/syntax/parser/expression/unary.rs +++ b/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 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()) diff --git a/boa_engine/src/syntax/parser/expression/update.rs b/boa_engine/src/syntax/parser/expression/update.rs index f510c720c1..67d48202e2 100644 --- a/boa_engine/src/syntax/parser/expression/update.rs +++ b/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(); diff --git a/boa_engine/src/syntax/parser/function/mod.rs b/boa_engine/src/syntax/parser/function/mod.rs index 20a7a2b9d4..fbfce13e7f 100644 --- a/boa_engine/src/syntax/parser/function/mod.rs +++ b/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(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for UniqueFormalParameters +where + R: Read, +{ + type Output = FormalParameterList; + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + 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 /// diff --git a/boa_engine/src/syntax/parser/function/tests.rs b/boa_engine/src/syntax/parser/function/tests.rs index 8ed7475d0e..6d6abc18ac 100644 --- a/boa_engine/src/syntax/parser/function/tests.rs +++ b/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::, Option<_>>(None, None).into()], ) @@ -128,6 +132,7 @@ fn check_empty_return_semicolon_insertion() { false, )]), flags: FormalParameterListFlags::default(), + length: 1, }, vec![Return::new::, 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::, Option<_>>(None, None).into()], ) @@ -341,6 +352,7 @@ fn check_arrow_empty_return_semicolon_insertion() { ), ]), flags: FormalParameterListFlags::default(), + length: 2, }, vec![Return::new::, Option<_>>(None, None).into()], ) @@ -369,6 +381,7 @@ fn check_arrow_assignment() { false, )]), flags: FormalParameterListFlags::default(), + length: 1, }, vec![Return::new::, 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::, 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::, 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::, 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::, 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::, 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::, 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::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs new file mode 100644 index 0000000000..920ed1af0e --- /dev/null +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -0,0 +1,1200 @@ +use crate::syntax::{ + ast::{ + node::{ + self, + declaration::class_decl::ClassElement as ClassElementNode, + object::{MethodDefinition, PropertyName::Literal}, + Class, FormalParameterList, FunctionExpr, + }, + Keyword, Punctuator, + }, + lexer::{Error as LexError, TokenKind}, + parser::{ + expression::{ + AssignmentExpression, AsyncGeneratorMethod, AsyncMethod, GeneratorMethod, + LeftHandSideExpression, PropertyName, + }, + function::{ + FormalParameter, FormalParameters, FunctionBody, UniqueFormalParameters, + FUNCTION_BREAK_TOKENS, + }, + statement::{BindingIdentifier, StatementList}, + AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, + }, +}; +use boa_interner::{Interner, Sym}; +use node::Node; +use rustc_hash::FxHashMap; +use std::io::Read; + +/// Class declaration parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class +/// [spec]: https://tc39.es/ecma262/#prod-ClassDeclaration +#[derive(Debug, Clone, Copy)] +pub(super) struct ClassDeclaration { + allow_yield: AllowYield, + allow_await: AllowAwait, + is_default: AllowDefault, +} + +impl ClassDeclaration { + /// Creates a new `ClassDeclaration` parser. + pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self + where + Y: Into, + A: Into, + D: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + is_default: is_default.into(), + } + } +} + +impl TokenParser for ClassDeclaration +where + R: Read, +{ + type Output = Node; + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + cursor.expect(Keyword::Class, "class declaration", interner)?; + 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 self.is_default.0 => Sym::DEFAULT, + _ => { + return Err(ParseError::unexpected( + token.to_string(interner), + token.span(), + "expected class identifier", + )) + } + }; + cursor.set_strict_mode(strict); + + Ok(Node::ClassDecl( + ClassTail::new(name, self.allow_yield, self.allow_await).parse(cursor, interner)?, + )) + } +} + +/// Class Tail parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ClassTail +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct ClassTail { + name: Sym, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ClassTail { + /// Creates a new `ClassTail` parser. + pub(in crate::syntax::parser) fn new(name: Sym, allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + name, + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ClassTail +where + R: Read, +{ + type Output = Class; + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + let super_ref = if token.kind() == &TokenKind::Keyword(Keyword::Extends) { + Some(Box::new( + ClassHeritage::new(self.allow_yield, self.allow_await).parse(cursor, interner)?, + )) + } else { + None + }; + + cursor.expect(Punctuator::OpenBlock, "class tail", interner)?; + + // Temporarily disable strict mode because "strict" may be parsed as a keyword. + let strict = cursor.strict_mode(); + cursor.set_strict_mode(false); + let is_close_block = cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .kind() + == &TokenKind::Punctuator(Punctuator::CloseBlock); + cursor.set_strict_mode(strict); + + if is_close_block { + cursor.next(interner).expect("token disappeared"); + Ok(Class::new(self.name, super_ref, None, vec![])) + } else { + let (constructor, elements) = + ClassBody::new(self.name, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.expect(Punctuator::CloseBlock, "class tail", interner)?; + Ok(Class::new(self.name, super_ref, constructor, elements)) + } + } +} + +/// `ClassHeritage` parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ClassHeritage +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct ClassHeritage { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ClassHeritage { + /// Creates a new `ClassHeritage` parser. + pub(in crate::syntax::parser) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ClassHeritage +where + R: Read, +{ + type Output = Node; + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + cursor.expect( + TokenKind::Keyword(Keyword::Extends), + "class heritage", + interner, + )?; + + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let lhs = LeftHandSideExpression::new(None, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.set_strict_mode(strict); + + Ok(lhs) + } +} + +/// `ClassBody` parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ClassBody +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct ClassBody { + name: Sym, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ClassBody { + /// Creates a new `ClassBody` parser. + pub(in crate::syntax::parser) fn new(name: Sym, allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + name, + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ClassBody +where + R: Read, +{ + type Output = (Option, Vec); + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + cursor.push_private_environment(); + + let mut constructor = None; + let mut elements = Vec::new(); + let mut private_elements_names = FxHashMap::default(); + + // The identifier "static" is forbidden in strict mode but used as a keyword in classes. + // Because of this, strict mode has to temporarily be disabled while parsing class field names. + let strict = cursor.strict_mode(); + cursor.set_strict_mode(false); + loop { + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + let position = token.span().start(); + match token.kind() { + TokenKind::Punctuator(Punctuator::CloseBlock) => break, + _ => match ClassElement::new(self.name, self.allow_yield, self.allow_await) + .parse(cursor, interner)? + { + (Some(_), None) if constructor.is_some() => { + return Err(ParseError::general( + "a class may only have one constructor", + position, + )); + } + (Some(c), None) => { + constructor = Some(c); + } + (None, Some(element)) => { + match &element { + ClassElementNode::PrivateMethodDefinition(name, method) => match method + { + MethodDefinition::Get(_) => { + match private_elements_names.get(name) { + Some(PrivateElement::Setter) => { + private_elements_names + .insert(*name, PrivateElement::Value); + } + Some(_) => { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + None => { + private_elements_names + .insert(*name, PrivateElement::Getter); + } + } + } + MethodDefinition::Set(_) => { + match private_elements_names.get(name) { + Some(PrivateElement::Getter) => { + private_elements_names + .insert(*name, PrivateElement::Value); + } + Some(_) => { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + None => { + private_elements_names + .insert(*name, PrivateElement::Setter); + } + } + } + _ => { + if private_elements_names + .insert(*name, PrivateElement::Value) + .is_some() + { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + } + }, + ClassElementNode::PrivateStaticMethodDefinition(name, method) => { + match method { + MethodDefinition::Get(_) => { + match private_elements_names.get(name) { + Some(PrivateElement::StaticSetter) => { + private_elements_names + .insert(*name, PrivateElement::StaticValue); + } + Some(_) => { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + None => { + private_elements_names + .insert(*name, PrivateElement::StaticGetter); + } + } + } + MethodDefinition::Set(_) => { + match private_elements_names.get(name) { + Some(PrivateElement::StaticGetter) => { + private_elements_names + .insert(*name, PrivateElement::StaticValue); + } + Some(_) => { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + None => { + private_elements_names + .insert(*name, PrivateElement::StaticSetter); + } + } + } + _ => { + if private_elements_names + .insert(*name, PrivateElement::StaticValue) + .is_some() + { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + } + } + } + ClassElementNode::PrivateFieldDefinition(name, _) => { + if private_elements_names + .insert(*name, PrivateElement::Value) + .is_some() + { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + } + ClassElementNode::PrivateStaticFieldDefinition(name, _) => { + if private_elements_names + .insert(*name, PrivateElement::StaticValue) + .is_some() + { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + } + _ => {} + } + elements.push(element); + } + _ => {} + }, + } + } + + cursor.set_strict_mode(strict); + cursor.pop_private_environment(&private_elements_names)?; + + Ok((constructor, elements)) + } +} + +/// Representation of private object elements. +#[derive(Debug, PartialEq)] +pub(in crate::syntax) enum PrivateElement { + Value, + Getter, + Setter, + StaticValue, + StaticSetter, + StaticGetter, +} + +/// `ClassElement` parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ClassElement +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct ClassElement { + name: Sym, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ClassElement { + /// Creates a new `ClassElement` parser. + pub(in crate::syntax::parser) fn new(name: Sym, allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + name, + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ClassElement +where + R: Read, +{ + type Output = (Option, Option); + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + let r#static = match token.kind() { + TokenKind::Punctuator(Punctuator::Semicolon) => { + cursor.next(interner).expect("token disappeared"); + return Ok((None, None)); + } + TokenKind::Identifier(Sym::STATIC) => { + let token = cursor.peek(1, interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Identifier(_) + | TokenKind::StringLiteral(_) + | TokenKind::NumericLiteral(_) + | TokenKind::Keyword(_) + | TokenKind::NullLiteral + | TokenKind::PrivateIdentifier(_) + | TokenKind::Punctuator( + Punctuator::OpenBracket + | Punctuator::Mul + | Punctuator::OpenBlock + | Punctuator::Semicolon, + ) => { + cursor.next(interner).expect("token disappeared"); + true + } + _ => false, + } + } + _ => false, + }; + + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + let element = match token.kind() { + TokenKind::Identifier(Sym::CONSTRUCTOR) if !r#static => { + cursor.next(interner).expect("token disappeared"); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + + cursor.expect(Punctuator::OpenParen, "class constructor", interner)?; + let parameters = FormalParameters::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.expect(Punctuator::CloseParen, "class constructor", interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "class constructor", + interner, + )?; + let body = FunctionBody::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "class constructor", + interner, + )?; + cursor.set_strict_mode(strict); + + return Ok((Some(FunctionExpr::new(self.name, parameters, body)), None)); + } + TokenKind::Punctuator(Punctuator::Semicolon) if r#static => { + cursor.next(interner).expect("token disappeared"); + ClassElementNode::FieldDefinition(Sym::STATIC.into(), None) + } + TokenKind::Punctuator(Punctuator::OpenBlock) if r#static => { + cursor.next(interner).expect("token disappeared"); + let statement_list = if cursor + .next_if(TokenKind::Punctuator(Punctuator::CloseBlock), interner)? + .is_some() + { + node::StatementList::from(vec![]) + } else { + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let statement_list = + StatementList::new(false, true, false, true, &FUNCTION_BREAK_TOKENS) + .parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "class definition", + interner, + )?; + cursor.set_strict_mode(strict); + statement_list + }; + ClassElementNode::StaticBlock(statement_list) + } + TokenKind::Punctuator(Punctuator::Mul) => { + let token = cursor.peek(1, interner)?.ok_or(ParseError::AbruptEnd)?; + let name_position = token.span().start(); + if let TokenKind::Identifier(Sym::CONSTRUCTOR) = token.kind() { + return Err(ParseError::general( + "class constructor may not be a generator method", + token.span().start(), + )); + } + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let (property_name, method) = + GeneratorMethod::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.set_strict_mode(strict); + if r#static { + if let Some(name) = property_name.prop_name() { + if name == Sym::PROTOTYPE { + return Err(ParseError::general( + "class may not have static method definitions named 'prototype'", + name_position, + )); + } + } + ClassElementNode::StaticMethodDefinition(property_name, method) + } else { + ClassElementNode::MethodDefinition(property_name, method) + } + } + TokenKind::Keyword(Keyword::Async) => { + cursor.next(interner).expect("token disappeared"); + cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?; + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Punctuator(Punctuator::Mul) => { + let token = cursor.peek(1, interner)?.ok_or(ParseError::AbruptEnd)?; + let name_position = token.span().start(); + if let TokenKind::Identifier(Sym::CONSTRUCTOR) = token.kind() { + return Err(ParseError::general( + "class constructor may not be a generator method", + token.span().start(), + )); + } + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let (property_name, method) = + AsyncGeneratorMethod::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.set_strict_mode(strict); + if r#static { + if let Some(name) = property_name.prop_name() { + if name == Sym::PROTOTYPE { + return Err(ParseError::general( + "class may not have static method definitions named 'prototype'", + name_position, + )); + } + } + ClassElementNode::StaticMethodDefinition(property_name, method) + } else { + ClassElementNode::MethodDefinition(property_name, method) + } + } + TokenKind::Identifier(Sym::CONSTRUCTOR) => { + return Err(ParseError::general( + "class constructor may not be an async method", + token.span().start(), + )) + } + _ => { + let name_position = token.span().start(); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let (property_name, method) = + AsyncMethod::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.set_strict_mode(strict); + if r#static { + if let Some(name) = property_name.prop_name() { + if name == Sym::PROTOTYPE { + return Err(ParseError::general( + "class may not have static method definitions named 'prototype'", + name_position, + )); + } + } + ClassElementNode::StaticMethodDefinition(property_name, method) + } else { + ClassElementNode::MethodDefinition(property_name, method) + } + } + } + } + TokenKind::Identifier(Sym::GET) => { + cursor.next(interner).expect("token disappeared"); + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + cursor.next(interner).expect("token disappeared"); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let rhs = AssignmentExpression::new( + Sym::GET, + true, + self.allow_yield, + self.allow_await, + ) + .parse(cursor, interner)?; + cursor.expect_semicolon("expected semicolon", interner)?; + cursor.set_strict_mode(strict); + if r#static { + ClassElementNode::StaticFieldDefinition(Literal(Sym::GET), Some(rhs)) + } else { + ClassElementNode::FieldDefinition(Literal(Sym::GET), Some(rhs)) + } + } + TokenKind::Punctuator(Punctuator::OpenParen) => { + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let params = + UniqueFormalParameters::new(false, false).parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "method definition", + interner, + )?; + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + let token = cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "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(), + token.span().start(), + ))); + } + cursor.set_strict_mode(strict); + let method = + MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)); + if r#static { + ClassElementNode::StaticMethodDefinition(Literal(Sym::GET), method) + } else { + ClassElementNode::MethodDefinition(Literal(Sym::GET), method) + } + } + TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => { + return Err(ParseError::general( + "class constructor may not be a private method", + token.span().start(), + )) + } + TokenKind::PrivateIdentifier(name) => { + let name = *name; + cursor.next(interner).expect("token disappeared"); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let params = + UniqueFormalParameters::new(false, false).parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "method definition", + interner, + )?; + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + let token = cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "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(), + token.span().start(), + ))); + } + cursor.set_strict_mode(strict); + let method = MethodDefinition::Get(FunctionExpr::new(None, params, body)); + if r#static { + ClassElementNode::PrivateStaticMethodDefinition(name, method) + } else { + ClassElementNode::PrivateMethodDefinition(name, method) + } + } + TokenKind::Identifier(Sym::CONSTRUCTOR) => { + return Err(ParseError::general( + "class constructor may not be a getter method", + token.span().start(), + )) + } + TokenKind::Identifier(_) + | TokenKind::StringLiteral(_) + | TokenKind::NumericLiteral(_) + | TokenKind::Keyword(_) + | TokenKind::NullLiteral + | TokenKind::Punctuator(Punctuator::OpenBracket) => { + let name_position = token.span().start(); + let name = PropertyName::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenParen), + "class getter", + interner, + )?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseParen), + "class getter", + interner, + )?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "class getter", + interner, + )?; + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + cursor.set_strict_mode(strict); + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "class getter", + interner, + )?; + + let method = MethodDefinition::Get(FunctionExpr::new( + None, + FormalParameterList::empty(), + body, + )); + if r#static { + if let Some(name) = name.prop_name() { + if name == Sym::PROTOTYPE { + return Err(ParseError::general( + "class may not have static method definitions named 'prototype'", + name_position, + )); + } + } + ClassElementNode::StaticMethodDefinition(name, method) + } else { + ClassElementNode::MethodDefinition(name, method) + } + } + _ => { + cursor.expect_semicolon("expected semicolon", interner)?; + if r#static { + ClassElementNode::StaticFieldDefinition(Literal(Sym::GET), None) + } else { + ClassElementNode::FieldDefinition(Literal(Sym::GET), None) + } + } + } + } + TokenKind::Identifier(Sym::SET) => { + cursor.next(interner).expect("token disappeared"); + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + cursor.next(interner).expect("token disappeared"); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let rhs = AssignmentExpression::new( + Sym::SET, + true, + self.allow_yield, + self.allow_await, + ) + .parse(cursor, interner)?; + cursor.expect_semicolon("expected semicolon", interner)?; + cursor.set_strict_mode(strict); + if r#static { + ClassElementNode::StaticFieldDefinition(Literal(Sym::SET), Some(rhs)) + } else { + ClassElementNode::FieldDefinition(Literal(Sym::SET), Some(rhs)) + } + } + TokenKind::Punctuator(Punctuator::OpenParen) => { + cursor.next(interner).expect("token disappeared"); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let parameters: FormalParameterList = FormalParameter::new(false, false) + .parse(cursor, interner)? + .into(); + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseParen), + "class setter method definition", + interner, + )?; + + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "method definition", + interner, + )?; + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + let token = cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "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() && !parameters.is_simple() { + return Err(ParseError::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list" + .into(), + token.span().start(), + ))); + } + cursor.set_strict_mode(strict); + let method = + MethodDefinition::Ordinary(FunctionExpr::new(None, parameters, body)); + if r#static { + ClassElementNode::StaticMethodDefinition(Literal(Sym::SET), method) + } else { + ClassElementNode::MethodDefinition(Literal(Sym::SET), method) + } + } + TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => { + return Err(ParseError::general( + "class constructor may not be a private method", + token.span().start(), + )) + } + TokenKind::PrivateIdentifier(name) => { + let name = *name; + cursor.next(interner).expect("token disappeared"); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let params = + UniqueFormalParameters::new(false, false).parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "method definition", + interner, + )?; + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + let token = cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "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(), + token.span().start(), + ))); + } + cursor.set_strict_mode(strict); + let method = MethodDefinition::Set(FunctionExpr::new(None, params, body)); + if r#static { + ClassElementNode::PrivateStaticMethodDefinition(name, method) + } else { + ClassElementNode::PrivateMethodDefinition(name, method) + } + } + TokenKind::Identifier(Sym::CONSTRUCTOR) => { + return Err(ParseError::general( + "class constructor may not be a setter method", + token.span().start(), + )) + } + TokenKind::Identifier(_) + | TokenKind::StringLiteral(_) + | TokenKind::NumericLiteral(_) + | TokenKind::Keyword(_) + | TokenKind::NullLiteral + | TokenKind::Punctuator(Punctuator::OpenBracket) => { + let name_position = token.span().start(); + let name = PropertyName::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let params = + UniqueFormalParameters::new(false, false).parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "method definition", + interner, + )?; + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + let token = cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "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(), + token.span().start(), + ))); + } + cursor.set_strict_mode(strict); + let method = MethodDefinition::Set(FunctionExpr::new(None, params, body)); + if r#static { + if let Some(name) = name.prop_name() { + if name == Sym::PROTOTYPE { + return Err(ParseError::general( + "class may not have static method definitions named 'prototype'", + name_position, + )); + } + } + ClassElementNode::StaticMethodDefinition(name, method) + } else { + ClassElementNode::MethodDefinition(name, method) + } + } + _ => { + cursor.expect_semicolon("expected semicolon", interner)?; + if r#static { + ClassElementNode::StaticFieldDefinition(Literal(Sym::SET), None) + } else { + ClassElementNode::FieldDefinition(Literal(Sym::SET), None) + } + } + } + } + TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => { + return Err(ParseError::general( + "class constructor may not be a private method", + token.span().start(), + )) + } + TokenKind::PrivateIdentifier(name) => { + let name = *name; + cursor.next(interner).expect("token disappeared"); + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + cursor.next(interner).expect("token disappeared"); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let rhs = AssignmentExpression::new( + name, + true, + self.allow_yield, + self.allow_await, + ) + .parse(cursor, interner)?; + cursor.expect_semicolon("expected semicolon", interner)?; + cursor.set_strict_mode(strict); + if r#static { + ClassElementNode::PrivateStaticFieldDefinition(name, Some(rhs)) + } else { + ClassElementNode::PrivateFieldDefinition(name, Some(rhs)) + } + } + TokenKind::Punctuator(Punctuator::OpenParen) => { + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let params = + UniqueFormalParameters::new(false, false).parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "method definition", + interner, + )?; + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + let token = cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "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(), + token.span().start(), + ))); + } + let method = + MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)); + cursor.set_strict_mode(strict); + if r#static { + ClassElementNode::PrivateStaticMethodDefinition(name, method) + } else { + ClassElementNode::PrivateMethodDefinition(name, method) + } + } + _ => { + cursor.expect_semicolon("expected semicolon", interner)?; + if r#static { + ClassElementNode::PrivateStaticFieldDefinition(name, None) + } else { + ClassElementNode::PrivateFieldDefinition(name, None) + } + } + } + } + TokenKind::Identifier(_) + | TokenKind::StringLiteral(_) + | TokenKind::NumericLiteral(_) + | TokenKind::Keyword(_) + | TokenKind::NullLiteral + | TokenKind::Punctuator(Punctuator::OpenBracket) => { + let name_position = token.span().start(); + let name = PropertyName::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Punctuator(Punctuator::Assign) => { + if let Some(name) = name.prop_name() { + if r#static { + if [Sym::CONSTRUCTOR, Sym::PROTOTYPE].contains(&name) { + return Err(ParseError::general( + "class may not have static field definitions named 'constructor' or 'prototype'", + name_position, + )); + } + } else if name == Sym::CONSTRUCTOR { + return Err(ParseError::general( + "class may not have field definitions named 'constructor'", + name_position, + )); + } + } + cursor.next(interner).expect("token disappeared"); + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let rhs = AssignmentExpression::new( + name.literal(), + true, + self.allow_yield, + self.allow_await, + ) + .parse(cursor, interner)?; + cursor.expect_semicolon("expected semicolon", interner)?; + cursor.set_strict_mode(strict); + if r#static { + ClassElementNode::StaticFieldDefinition(name, Some(rhs)) + } else { + ClassElementNode::FieldDefinition(name, Some(rhs)) + } + } + TokenKind::Punctuator(Punctuator::OpenParen) => { + if let Some(name) = name.prop_name() { + if r#static && name == Sym::PROTOTYPE { + return Err(ParseError::general( + "class may not have static method definitions named 'prototype'", + name_position, + )); + } + } + let strict = cursor.strict_mode(); + cursor.set_strict_mode(true); + let params = + UniqueFormalParameters::new(false, false).parse(cursor, interner)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "method definition", + interner, + )?; + let body = FunctionBody::new(false, false).parse(cursor, interner)?; + let token = cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "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(), + token.span().start(), + ))); + } + let method = + MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)); + cursor.set_strict_mode(strict); + if r#static { + ClassElementNode::StaticMethodDefinition(name, method) + } else { + ClassElementNode::MethodDefinition(name, method) + } + } + _ => { + if let Some(name) = name.prop_name() { + if r#static { + if [Sym::CONSTRUCTOR, Sym::PROTOTYPE].contains(&name) { + return Err(ParseError::general( + "class may not have static field definitions named 'constructor' or 'prototype'", + name_position, + )); + } + } else if name == Sym::CONSTRUCTOR { + return Err(ParseError::general( + "class may not have field definitions named 'constructor'", + name_position, + )); + } + } + cursor.expect_semicolon("expected semicolon", interner)?; + if r#static { + ClassElementNode::StaticFieldDefinition(name, None) + } else { + ClassElementNode::FieldDefinition(name, None) + } + } + } + } + _ => { + return Err(ParseError::general( + "unexpected token", + token.span().start(), + )) + } + }; + + Ok((None, Some(element))) + } +} diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs index a641c3247d..c948fd63b4 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs +++ b/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), } } diff --git a/boa_engine/src/syntax/parser/statement/declaration/lexical.rs b/boa_engine/src/syntax/parser/statement/declaration/lexical.rs index ba126e1529..f8bc55e55d 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/lexical.rs +++ b/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)) } } diff --git a/boa_engine/src/syntax/parser/statement/declaration/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/mod.rs index da85466f68..e42195c2b9 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/mod.rs +++ b/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) } diff --git a/boa_engine/src/syntax/parser/statement/mod.rs b/boa_engine/src/syntax/parser/statement/mod.rs index 6f967ffd73..a3d81b68a8 100644 --- a/boa_engine/src/syntax/parser/statement/mod.rs +++ b/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, diff --git a/boa_engine/src/syntax/parser/statement/variable/mod.rs b/boa_engine/src/syntax/parser/statement/variable/mod.rs index 802a2e2b8c..f8d932b496 100644 --- a/boa_engine/src/syntax/parser/statement/variable/mod.rs +++ b/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)) } } diff --git a/boa_engine/src/syntax/parser/tests.rs b/boa_engine/src/syntax/parser/tests.rs index 672872f653..71d09226ac 100644 --- a/boa_engine/src/syntax/parser/tests.rs +++ b/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()], ) diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index d7bf07dcdd..fc6574a069 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/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, + + /// 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>>, } 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::(*pc); *pc += size_of::(); @@ -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 @@ -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, diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 2eb81b32a8..d01399221d 100644 --- a/boa_engine/src/vm/mod.rs +++ b/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::(); - 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::(); + 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::(); 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::(); + 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::(); let object = self.vm.pop(); @@ -769,6 +868,29 @@ impl Context { self, )?; } + Opcode::DefineClassSetterByName => { + let index = self.vm.read::(); + 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::(); + 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::(); + 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::(); + 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::(); + 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::(); 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); diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode.rs index 03d30cbbe8..4bce1bc343 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/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", } } } diff --git a/boa_interner/src/lib.rs b/boa_interner/src/lib.rs index 09af26fe3a..11cf49b3eb 100644 --- a/boa_interner/src/lib.rs +++ b/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", "
", "raw", + "static", + "prototype", + "constructor", + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", ]; } diff --git a/boa_interner/src/tests.rs b/boa_interner/src/tests.rs index 859a92a524..3df0fd194f 100644 --- a/boa_interner/src/tests.rs +++ b/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]