From d763e0dd13cafc3bf8366dd4f246d4d364ee2e64 Mon Sep 17 00:00:00 2001 From: Andrew Morris Date: Fri, 7 Jul 2023 10:49:21 +1000 Subject: [PATCH] Implement enums --- inputs/passing/enum.ts | 7 ++ inputs/passing/helpers/Numbers.ts | 9 +++ .../src/compile_enum_value.rs | 73 +++++++++++++++++++ valuescript_compiler/src/function_compiler.rs | 26 ++++++- valuescript_compiler/src/lib.rs | 1 + valuescript_compiler/src/module_compiler.rs | 59 ++++++++++++++- valuescript_compiler/src/scope_analysis.rs | 29 +++++--- 7 files changed, 189 insertions(+), 15 deletions(-) create mode 100644 inputs/passing/enum.ts create mode 100644 inputs/passing/helpers/Numbers.ts create mode 100644 valuescript_compiler/src/compile_enum_value.rs diff --git a/inputs/passing/enum.ts b/inputs/passing/enum.ts new file mode 100644 index 0000000..864b465 --- /dev/null +++ b/inputs/passing/enum.ts @@ -0,0 +1,7 @@ +//! test_output(10) + +import Numbers from "./helpers/Numbers.ts"; + +export default function () { + return Numbers.One + Numbers.Two + Numbers.Three + Numbers.Four; +} diff --git a/inputs/passing/helpers/Numbers.ts b/inputs/passing/helpers/Numbers.ts new file mode 100644 index 0000000..011e1fa --- /dev/null +++ b/inputs/passing/helpers/Numbers.ts @@ -0,0 +1,9 @@ +enum Numbers { + Zero, + One, + Two, + Three, + Four, +} + +export default Numbers; diff --git a/valuescript_compiler/src/compile_enum_value.rs b/valuescript_compiler/src/compile_enum_value.rs new file mode 100644 index 0000000..b53d7c2 --- /dev/null +++ b/valuescript_compiler/src/compile_enum_value.rs @@ -0,0 +1,73 @@ +use swc_common::Spanned; + +use crate::{ + asm::{Number, Object, Value}, + static_eval_expr::static_eval_expr, + Diagnostic, DiagnosticLevel, +}; + +pub fn compile_enum_value( + ts_enum: &swc_ecma_ast::TsEnumDecl, + diagnostics: &mut Vec, +) -> Value { + let mut properties = Vec::<(Value, Value)>::new(); + let mut next_default_id: Option = Some(0.0); + + for member in &ts_enum.members { + let key = match &member.id { + swc_ecma_ast::TsEnumMemberId::Ident(ident) => ident.sym.to_string(), + swc_ecma_ast::TsEnumMemberId::Str(str) => str.value.to_string(), + }; + + let init_value = match &member.init { + Some(init) => match static_eval_expr(&init) { + Some(init_value) => match init_value { + Value::Number(Number(n)) => { + next_default_id = Some(n + 1.0); + Some(Value::Number(Number(n))) + } + Value::String(_) => Some(init_value), + _ => None, + }, + None => { + diagnostics.push(Diagnostic { + level: DiagnosticLevel::InternalError, + message: "TODO: Static eval failed".to_string(), + span: init.span(), + }); + + None + } + }, + None => None, + }; + + let value = match init_value { + Some(value) => value, + None => { + let id = match next_default_id { + Some(id) => id, + None => { + diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + message: format!("Missing required initializer"), + span: member.span, + }); + + 0.0 + } + }; + + let value = Value::Number(Number(id)); + next_default_id = Some(id + 1.0); + + value + } + }; + + properties.push((Value::String(key.clone()), value.clone())); + properties.push((value, Value::String(key))); + } + + Value::Object(Box::new(Object { properties })) +} diff --git a/valuescript_compiler/src/function_compiler.rs b/valuescript_compiler/src/function_compiler.rs index 0407a3d..b412f65 100644 --- a/valuescript_compiler/src/function_compiler.rs +++ b/valuescript_compiler/src/function_compiler.rs @@ -10,6 +10,7 @@ use crate::asm::{ Array, Builtin, Definition, DefinitionContent, FnLine, Function, Instruction, Label, Pointer, Register, Value, }; +use crate::compile_enum_value::compile_enum_value; use crate::diagnostic::{Diagnostic, DiagnosticLevel}; use crate::expression_compiler::CompiledExpression; use crate::expression_compiler::ExpressionCompiler; @@ -1279,7 +1280,30 @@ impl FunctionCompiler { Var(var_decl) => self.var_declaration(var_decl), TsInterface(interface_decl) => self.todo(interface_decl.span, "TsInterface declaration"), TsTypeAlias(_) => {} - TsEnum(ts_enum) => self.todo(ts_enum.span, "TsEnum declaration"), + TsEnum(ts_enum) => { + let pointer = match self + .scope_analysis + .lookup_value(&OwnerId::Module, &ts_enum.id) + { + Some(Value::Pointer(p)) => p, + _ => { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::InternalError, + message: format!("Pointer for {} should have been in scope", ts_enum.id.sym), + span: ts_enum.id.span, + }); + + return; + } + }; + + let enum_value = compile_enum_value(ts_enum, &mut self.diagnostics); + + self.definitions.push(Definition { + pointer, + content: DefinitionContent::Value(enum_value), + }); + } TsModule(ts_module) => self.todo(ts_module.span, "TsModule declaration"), }; } diff --git a/valuescript_compiler/src/lib.rs b/valuescript_compiler/src/lib.rs index 53622d5..f8b7c90 100644 --- a/valuescript_compiler/src/lib.rs +++ b/valuescript_compiler/src/lib.rs @@ -2,6 +2,7 @@ pub mod asm; mod assembler; pub mod assembly_parser; mod compile; +mod compile_enum_value; mod constants; mod diagnostic; mod expression_compiler; diff --git a/valuescript_compiler/src/module_compiler.rs b/valuescript_compiler/src/module_compiler.rs index 2267716..0de682d 100644 --- a/valuescript_compiler/src/module_compiler.rs +++ b/valuescript_compiler/src/module_compiler.rs @@ -11,6 +11,7 @@ use crate::asm::{ Class, Definition, DefinitionContent, FnLine, Function, Instruction, Lazy, Module, Object, Pointer, Register, Value, }; +use crate::compile_enum_value::compile_enum_value; use crate::diagnostic::{Diagnostic, DiagnosticLevel}; use crate::expression_compiler::{CompiledExpression, ExpressionCompiler}; use crate::function_compiler::{FunctionCompiler, Functionish}; @@ -199,7 +200,27 @@ impl ModuleCompiler { ExportDecl(ed) => self.compile_export_decl(ed), ExportNamed(en) => self.compile_named_export(en), ExportDefaultDecl(edd) => self.compile_export_default_decl(edd), - ExportDefaultExpr(_) => self.todo(module_decl.span(), "ExportDefaultExpr declaration"), + ExportDefaultExpr(ede) => { + let value = match &*ede.expr { + swc_ecma_ast::Expr::Ident(ident) => match self.scope_analysis.lookup(ident) { + Some(name) => Some(name.value.clone()), + None => None, + }, + expr => static_eval_expr(expr), + }; + + match value { + Some(value) => { + self.module.export_default = value; + } + None => { + self.todo( + module_decl.span(), + "Failed to evaluate export default expression", + ); + } + }; + } ExportAll(_) => self.todo(module_decl.span(), "ExportAll declaration"), TsImportEquals(_) => self.not_supported(module_decl.span(), "TsImportEquals declaration"), TsExportAssignment(_) => { @@ -326,7 +347,7 @@ impl ModuleCompiler { } TsInterface(_) => {} TsTypeAlias(_) => {} - TsEnum(ts_enum) => self.todo(ts_enum.span, "TsEnum declaration"), + TsEnum(ts_enum) => self.compile_enum_decl(false, ts_enum), TsModule(ts_module) => self.todo(ts_module.span, "TsModule declaration"), }; } @@ -366,6 +387,38 @@ impl ModuleCompiler { self.module.definitions.append(&mut fn_defns); } + fn compile_enum_decl(&mut self, export: bool, ts_enum: &swc_ecma_ast::TsEnumDecl) { + let pointer = match self + .scope_analysis + .lookup_value(&OwnerId::Module, &ts_enum.id) + { + Some(Value::Pointer(p)) => p, + _ => { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::InternalError, + message: format!("Pointer for {} should have been in scope", ts_enum.id.sym), + span: ts_enum.id.span, + }); + + return; + } + }; + + if export { + self.module.export_star.properties.push(( + Value::String(ts_enum.id.sym.to_string()), + Value::Pointer(pointer.clone()), + )); + } + + let enum_value = compile_enum_value(ts_enum, &mut self.diagnostics); + + self.module.definitions.push(Definition { + pointer, + content: DefinitionContent::Value(enum_value), + }); + } + fn compile_export_default_decl(&mut self, edd: &swc_ecma_ast::ExportDefaultDecl) { use swc_ecma_ast::DefaultDecl; @@ -432,7 +485,7 @@ impl ModuleCompiler { } Decl::TsInterface(_) => {} Decl::TsTypeAlias(_) => {} - Decl::TsEnum(ts_enum) => self.todo(ts_enum.span, "TsEnum declaration in export"), + Decl::TsEnum(ts_enum) => self.compile_enum_decl(true, ts_enum), Decl::TsModule(ts_module) => self.todo(ts_module.span, "TsModule declaration in export"), }; } diff --git a/valuescript_compiler/src/scope_analysis.rs b/valuescript_compiler/src/scope_analysis.rs index 51ffde9..e9e4bd5 100644 --- a/valuescript_compiler/src/scope_analysis.rs +++ b/valuescript_compiler/src/scope_analysis.rs @@ -33,6 +33,7 @@ pub enum NameType { Param, Function, Class, + Enum, Import, Builtin, Constant, @@ -187,6 +188,7 @@ impl ScopeAnalysis { effectively_const: match type_ { NameType::Var | NameType::Let | NameType::Param => false, NameType::Const + | NameType::Enum | NameType::Function | NameType::Class | NameType::Import @@ -434,12 +436,16 @@ impl ScopeAnalysis { } Decl::TsInterface(_) => {} Decl::TsTypeAlias(_) => {} - Decl::TsEnum(ts_enum) => { - self.diagnostics.push(Diagnostic { - level: DiagnosticLevel::InternalError, - message: "TODO: Implement TsEnum declarations".to_string(), - span: ts_enum.span, - }); + Decl::TsEnum(ts_enum) => 'b: { + if ts_enum.declare { + break 'b; + } + + for member in &ts_enum.members { + if let Some(init) = &member.init { + self.expr(scope, &init); + } + } } Decl::TsModule(ts_module) => { self.diagnostics.push(Diagnostic { @@ -510,8 +516,8 @@ impl ScopeAnalysis { } swc_ecma_ast::Decl::TsInterface(_) => {} swc_ecma_ast::Decl::TsTypeAlias(_) => {} - swc_ecma_ast::Decl::TsEnum(_) => { - // Diagnostic emitted after hoist processing + swc_ecma_ast::Decl::TsEnum(ts_enum) => { + self.insert_pointer_name(scope, NameType::Enum, &ts_enum.id); } swc_ecma_ast::Decl::TsModule(_) => { // Diagnostic emitted after hoist processing @@ -571,6 +577,9 @@ impl ScopeAnalysis { } } } + Decl::TsEnum(ts_enum) => { + self.insert_pointer_name(scope, NameType::Enum, &ts_enum.id); + } _ => {} }, Stmt::Block(block_stmt) => { @@ -687,9 +696,7 @@ impl ScopeAnalysis { } Decl::TsInterface(_) => {} Decl::TsTypeAlias(_) => {} - Decl::TsEnum(_) => { - // Diagnostic emitted after hoist processing - } + Decl::TsEnum(_) => {} Decl::TsModule(_) => { // Diagnostic emitted after hoist processing }