From 7f3858cd2c48bace95e06ef4a1e0f1fdbfa3673c Mon Sep 17 00:00:00 2001 From: yossydev Date: Sat, 15 Mar 2025 10:34:22 +0900 Subject: [PATCH 1/3] feat: WIP enums implementation --- index.ts | 5 ++ .../ecmascript/scripts_and_modules/script.rs | 1 + .../scope_analysis.rs | 45 +++++++++----- .../src/engine/bytecode/bytecode_compiler.rs | 60 ++++++++++++++++++- nova_vm/src/engine/bytecode/vm.rs | 5 ++ 5 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 index.ts diff --git a/index.ts b/index.ts new file mode 100644 index 000000000..e7369ac24 --- /dev/null +++ b/index.ts @@ -0,0 +1,5 @@ +// const obj = { a: 1, b: "hello" }; + +enum Test { + A = 1 +} \ No newline at end of file diff --git a/nova_vm/src/ecmascript/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index 1277a9876..6b4ae889f 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -307,6 +307,7 @@ pub fn script_evaluation<'gc>( // a. Set result to Completion(Evaluation of script). // b. If result.[[Type]] is normal and result.[[Value]] is empty, then // i. Set result to NormalCompletion(undefined). + println!("bytecode: {:?}", bytecode); let result = Vm::execute(agent, bytecode, None, gc).into_js_result(); // SAFETY: The bytecode is not accessible by anyone and no one will try // to re-run it. diff --git a/nova_vm/src/ecmascript/syntax_directed_operations/scope_analysis.rs b/nova_vm/src/ecmascript/syntax_directed_operations/scope_analysis.rs index bd21a8100..639ebfb81 100644 --- a/nova_vm/src/ecmascript/syntax_directed_operations/scope_analysis.rs +++ b/nova_vm/src/ecmascript/syntax_directed_operations/scope_analysis.rs @@ -1107,15 +1107,18 @@ impl<'a> TopLevelLexicallyDeclaredNames<'a> for Statement<'a> { Statement::ImportDeclaration(decl) => decl.bound_names(f), Statement::ExportNamedDeclaration(decl) => decl.bound_names(f), #[cfg(feature = "typescript")] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => {} + Statement::TSTypeAliasDeclaration(_) + | Statement::TSInterfaceDeclaration(_) + | Statement::TSEnumDeclaration(_) => {} #[cfg(not(feature = "typescript"))] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => { + Statement::TSTypeAliasDeclaration(_) + | Statement::TSInterfaceDeclaration(_) + | Statement::TSEnumDeclaration(_) => { unreachable!() } // Note: No bounds names for export all and export default declarations. Statement::ExportAllDeclaration(_) | Statement::ExportDefaultDeclaration(_) => {} - Statement::TSEnumDeclaration(_) - | Statement::TSModuleDeclaration(_) + Statement::TSModuleDeclaration(_) | Statement::TSImportEqualsDeclaration(_) | Statement::TSExportAssignment(_) | Statement::TSNamespaceExportDeclaration(_) => unreachable!(), @@ -1189,13 +1192,16 @@ impl<'a> TopLevelLexicallyScopedDeclarations<'a> for Statement<'a> { } Statement::ClassDeclaration(decl) => f(LexicallyScopedDeclaration::Class(decl)), #[cfg(feature = "typescript")] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => {} + Statement::TSTypeAliasDeclaration(_) + | Statement::TSInterfaceDeclaration(_) + | Statement::TSEnumDeclaration(_) => {} #[cfg(not(feature = "typescript"))] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => { + Statement::TSTypeAliasDeclaration(_) + | Statement::TSInterfaceDeclaration(_) + | Statement::TSEnumDeclaration(_) => { unreachable!() } - Statement::TSEnumDeclaration(_) - | Statement::TSExportAssignment(_) + Statement::TSExportAssignment(_) | Statement::TSImportEqualsDeclaration(_) | Statement::TSModuleDeclaration(_) | Statement::TSNamespaceExportDeclaration(_) => unreachable!(), @@ -1275,13 +1281,16 @@ impl<'a> TopLevelVarDeclaredNames<'a> for Statement<'a> { | Statement::WhileStatement(_) | Statement::WithStatement(_) => self.var_declared_names(f), #[cfg(feature = "typescript")] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => {} + Statement::TSTypeAliasDeclaration(_) + | Statement::TSInterfaceDeclaration(_) + | Statement::TSEnumDeclaration(_) => {} #[cfg(not(feature = "typescript"))] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => { + Statement::TSTypeAliasDeclaration(_) + | Statement::TSInterfaceDeclaration(_) + | Statement::TSEnumDeclaration(_) => { unreachable!() } - Statement::TSEnumDeclaration(_) - | Statement::TSExportAssignment(_) + Statement::TSExportAssignment(_) | Statement::TSImportEqualsDeclaration(_) | Statement::TSModuleDeclaration(_) | Statement::TSNamespaceExportDeclaration(_) => unreachable!(), @@ -1404,13 +1413,17 @@ impl<'a> TopLevelVarScopedDeclarations<'a> for Statement<'a> { // 2. Return a new empty List. } #[cfg(feature = "typescript")] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => {} + Statement::TSTypeAliasDeclaration(_) + | Statement::TSInterfaceDeclaration(_) + | Statement::TSEnumDeclaration(_) => {} #[cfg(not(feature = "typescript"))] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => { + Statement::TSTypeAliasDeclaration(_) + | Statement::TSInterfaceDeclaration(_) + | Statement::TSEnumDeclaration(_) => { unreachable!() } - Statement::TSEnumDeclaration(_) - | Statement::TSExportAssignment(_) + + Statement::TSExportAssignment(_) | Statement::TSImportEqualsDeclaration(_) | Statement::TSModuleDeclaration(_) | Statement::TSNamespaceExportDeclaration(_) => unreachable!(), diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler.rs b/nova_vm/src/engine/bytecode/bytecode_compiler.rs index 4dd87db2d..c88f1edbd 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -76,6 +76,7 @@ pub(crate) struct CompileContext<'agent, 'gc, 'scope> { /// In a `(a?.b)?.()` chain the evaluation of `(a?.b)` must be considered a /// reference. is_call_optional_chain_this: bool, + current_value: Option>, } impl<'a, 'gc, 'scope> CompileContext<'a, 'gc, 'scope> { @@ -98,6 +99,7 @@ impl<'a, 'gc, 'scope> CompileContext<'a, 'gc, 'scope> { current_break: None, optional_chains: None, is_call_optional_chain_this: false, + current_value: None, } } @@ -356,8 +358,11 @@ impl<'a, 'gc, 'scope> CompileContext<'a, 'gc, 'scope> { instruction: Instruction, identifier: String<'gc>, ) { + println!("Debug: Adding identifier to instruction {:?}", instruction); + debug_assert_eq!(instruction.argument_count(), 1); - debug_assert!(instruction.has_identifier_index()); + debug_assert!(instruction.has_identifier_index()); // ここで panic する + self._push_instruction(instruction); let identifier = self.add_identifier(identifier); self.add_index(identifier); @@ -507,6 +512,7 @@ impl CompileEvaluation for ast::NumericLiteral<'_> { fn compile(&self, ctx: &mut CompileContext) { let constant = ctx.agent.heap.create(self.value); ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); + ctx.current_value = Some(constant.into_value()); } } @@ -2935,6 +2941,53 @@ impl CompileEvaluation for ast::ContinueStatement<'_> { } } +#[cfg(feature = "typescript")] +impl CompileEvaluation for ast::TSEnumDeclaration<'_> { + fn compile<'gc>(&self, ctx: &mut CompileContext<'_, 'gc, '_>) { + let is_const = self.r#const; + if is_const { + return; + } + ctx.add_instruction(Instruction::ObjectCreate); + let mut next_value = 0.0; + + for prop in self.members.iter() { + let key = match &prop.id { + ast::TSEnumMemberName::Identifier(ident) => { + String::from_str(ctx.agent, ident.name.as_str(), ctx.gc) + } + ast::TSEnumMemberName::String(string) => { + String::from_str(ctx.agent, string.value.as_str(), ctx.gc) + } + }; + + let mut needs_get_value = false; + + let value: Value<'gc>; + if let Some(expr) = &prop.initializer { + expr.compile(ctx); + println!("expr: {:?}", expr); + needs_get_value = is_reference(expr); + value = ctx + .current_value + .take() + .unwrap_or(Value::from_f64(ctx.agent, next_value, ctx.gc)); + } else { + value = Value::from_f64(ctx.agent, next_value, ctx.gc); + } + + ctx.add_instruction(Instruction::Load); + ctx.add_instruction_with_constant(Instruction::LoadConstant, key.into_value()); + ctx.add_instruction_with_constant(Instruction::LoadConstant, value); + + ctx.add_instruction(Instruction::GetValue); + + ctx.add_instruction(Instruction::ObjectDefineProperty); + } + + ctx.add_instruction(Instruction::Store); + } +} impl CompileEvaluation for ast::Statement<'_> { fn compile(&self, ctx: &mut CompileContext) { match self { @@ -2972,8 +3025,9 @@ impl CompileEvaluation for ast::Statement<'_> { Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => { unreachable!() } - Statement::TSEnumDeclaration(_) - | Statement::TSExportAssignment(_) + #[cfg(feature = "typescript")] + Statement::TSEnumDeclaration(x) => x.compile(ctx), + Statement::TSExportAssignment(_) | Statement::TSImportEqualsDeclaration(_) | Statement::TSModuleDeclaration(_) | Statement::TSNamespaceExportDeclaration(_) => unreachable!(), diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index daeb6d972..58209cfd4 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -337,6 +337,7 @@ impl<'a> Vm { let mut instr_count = 0u8; let instructions = executable.get_instructions(agent); + println!("instructions: {:?}", instructions); while let Some(instr) = get_instruction(instructions, &mut self.ip) { #[cfg(feature = "interleaved-gc")] if do_gc { @@ -415,6 +416,8 @@ impl<'a> Vm { if agent.options.print_internals { eprintln!("Executing instruction {:?}", instr.kind); } + // println!("vm.result: {:?}", vm.result); + println!("instr.kind: {:?}", instr.kind); match instr.kind { Instruction::ArrayCreate => { let result = @@ -602,6 +605,8 @@ impl<'a> Vm { gc.reborrow(), )?; let key = key.unbind().bind(gc.nogc()); + // println!("vm.stack: {:?}", vm.stack); + // println!("vm.result.take {:?}", vm.result); let value = vm.result.take().unwrap().bind(gc.nogc()); let object = vm.stack.last().unwrap().bind(gc.nogc()); let object = Object::try_from(object).unwrap(); From 7cad2ba61508a03995e603d00d27c21ddb353ee1 Mon Sep 17 00:00:00 2001 From: yossydev Date: Thu, 20 Mar 2025 22:57:56 +0900 Subject: [PATCH 2/3] wip --- index.ts | 23 ++++++-- .../ecmascript/scripts_and_modules/script.rs | 1 - .../src/engine/bytecode/bytecode_compiler.rs | 53 +++++++++++-------- nova_vm/src/engine/bytecode/vm.rs | 3 -- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/index.ts b/index.ts index e7369ac24..579018f4c 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,20 @@ -// const obj = { a: 1, b: "hello" }; +enum CompassDirection { + North, + East, + South, + West +} -enum Test { - A = 1 -} \ No newline at end of file +globalThis.__enumCheck = { + northValue: CompassDirection.North === 0, + eastValue: CompassDirection.East === 1, + southValue: CompassDirection.South === 2, + westValue: CompassDirection.West === 3, + + zeroName: CompassDirection[0] === "North", + oneName: CompassDirection[1] === "East", + twoName: CompassDirection[2] === "South", + threeName: CompassDirection[3] === "West", + + keys: Object.keys(CompassDirection) +}; diff --git a/nova_vm/src/ecmascript/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index 6b4ae889f..1277a9876 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -307,7 +307,6 @@ pub fn script_evaluation<'gc>( // a. Set result to Completion(Evaluation of script). // b. If result.[[Type]] is normal and result.[[Value]] is empty, then // i. Set result to NormalCompletion(undefined). - println!("bytecode: {:?}", bytecode); let result = Vm::execute(agent, bytecode, None, gc).into_js_result(); // SAFETY: The bytecode is not accessible by anyone and no one will try // to re-run it. diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler.rs b/nova_vm/src/engine/bytecode/bytecode_compiler.rs index c88f1edbd..b99117e06 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -358,10 +358,8 @@ impl<'a, 'gc, 'scope> CompileContext<'a, 'gc, 'scope> { instruction: Instruction, identifier: String<'gc>, ) { - println!("Debug: Adding identifier to instruction {:?}", instruction); - debug_assert_eq!(instruction.argument_count(), 1); - debug_assert!(instruction.has_identifier_index()); // ここで panic する + debug_assert!(instruction.has_identifier_index()); self._push_instruction(instruction); let identifier = self.add_identifier(identifier); @@ -2948,46 +2946,55 @@ impl CompileEvaluation for ast::TSEnumDeclaration<'_> { if is_const { return; } + let enum_name = self.id.name; + println!("{:?}", enum_name); + + // TODO: implement var Foo + ctx.add_instruction(Instruction::ObjectCreate); + ctx.add_instruction(Instruction::Store); + ctx.add_instruction(Instruction::LoadCopy); + ctx.add_instruction(Instruction::Load); let mut next_value = 0.0; - for prop in self.members.iter() { - let key = match &prop.id { - ast::TSEnumMemberName::Identifier(ident) => { - String::from_str(ctx.agent, ident.name.as_str(), ctx.gc) - } - ast::TSEnumMemberName::String(string) => { - String::from_str(ctx.agent, string.value.as_str(), ctx.gc) - } + let key_str = match &prop.id { + ast::TSEnumMemberName::Identifier(ident) => ident.name.as_str(), + ast::TSEnumMemberName::String(string) => string.value.as_str(), }; - - let mut needs_get_value = false; - + let key = String::from_str(ctx.agent, key_str, ctx.gc); let value: Value<'gc>; if let Some(expr) = &prop.initializer { expr.compile(ctx); - println!("expr: {:?}", expr); - needs_get_value = is_reference(expr); + if is_reference(expr) { + ctx.add_instruction(Instruction::GetValue); + } value = ctx .current_value .take() .unwrap_or(Value::from_f64(ctx.agent, next_value, ctx.gc)); + + if value.is_number() { + next_value += 1.0; + } else { + next_value += 1.0; + } } else { value = Value::from_f64(ctx.agent, next_value, ctx.gc); + next_value += 1.0; } - - ctx.add_instruction(Instruction::Load); ctx.add_instruction_with_constant(Instruction::LoadConstant, key.into_value()); - ctx.add_instruction_with_constant(Instruction::LoadConstant, value); - - ctx.add_instruction(Instruction::GetValue); - + ctx.add_instruction_with_constant(Instruction::StoreConstant, value); ctx.add_instruction(Instruction::ObjectDefineProperty); + if value.is_number() { + ctx.add_instruction_with_constant(Instruction::LoadConstant, value); + ctx.add_instruction_with_constant(Instruction::StoreConstant, key.into_value()); + ctx.add_instruction(Instruction::ObjectDefineProperty); + } } - ctx.add_instruction(Instruction::Store); } } + impl CompileEvaluation for ast::Statement<'_> { fn compile(&self, ctx: &mut CompileContext) { match self { diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 58209cfd4..561df2be2 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -337,7 +337,6 @@ impl<'a> Vm { let mut instr_count = 0u8; let instructions = executable.get_instructions(agent); - println!("instructions: {:?}", instructions); while let Some(instr) = get_instruction(instructions, &mut self.ip) { #[cfg(feature = "interleaved-gc")] if do_gc { @@ -416,8 +415,6 @@ impl<'a> Vm { if agent.options.print_internals { eprintln!("Executing instruction {:?}", instr.kind); } - // println!("vm.result: {:?}", vm.result); - println!("instr.kind: {:?}", instr.kind); match instr.kind { Instruction::ArrayCreate => { let result = From 2b3311a24c60e5c1cc81fac74cb079842022ab92 Mon Sep 17 00:00:00 2001 From: yossydev Date: Thu, 20 Mar 2025 22:58:34 +0900 Subject: [PATCH 3/3] wip --- nova_vm/src/engine/bytecode/vm.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 561df2be2..daeb6d972 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -602,8 +602,6 @@ impl<'a> Vm { gc.reborrow(), )?; let key = key.unbind().bind(gc.nogc()); - // println!("vm.stack: {:?}", vm.stack); - // println!("vm.result.take {:?}", vm.result); let value = vm.result.take().unwrap().bind(gc.nogc()); let object = vm.stack.last().unwrap().bind(gc.nogc()); let object = Object::try_from(object).unwrap();