diff --git a/README.md b/README.md index d075fc8f..3a1e8166 100644 --- a/README.md +++ b/README.md @@ -420,6 +420,7 @@ not the subset of ValueScript that has actually been implemented. - Enforcing `const` - Temporal dead zones - Local imports + - Including the many various import and export patterns - Tree shaking - Copy-on-write optimizations - utf8 strings (_not_ JS's utf16 strings) @@ -480,8 +481,6 @@ not the subset of ValueScript that has actually been implemented. - Rest params - Async functions - TypeScript namespaces -- `export * from` - - (`export { name } from` _does_ work) - `import.meta` - Dynamic imports - Unusual JS things like passing unintended types to standard functions diff --git a/inputs/passing/imports/exportStar.ts b/inputs/passing/imports/exportStar.ts new file mode 100644 index 00000000..50afd5ed --- /dev/null +++ b/inputs/passing/imports/exportStar.ts @@ -0,0 +1,14 @@ +//! test_output(["a","b (local)","this is the foo function","this is the bar function","baz",42]) + +import * as lotsOfThings from "./stuff/lotsOfThings.ts"; + +export default () => { + return [ + lotsOfThings.a(), + lotsOfThings.b(), + lotsOfThings.foo(), + lotsOfThings.bar(), + lotsOfThings.baz(), + lotsOfThings.x, + ]; +}; diff --git a/inputs/passing/importBar.ts b/inputs/passing/imports/importBar.ts similarity index 72% rename from inputs/passing/importBar.ts rename to inputs/passing/imports/importBar.ts index 202d0ab3..c537412f 100644 --- a/inputs/passing/importBar.ts +++ b/inputs/passing/imports/importBar.ts @@ -1,6 +1,6 @@ //! test_output(["this is the bar function","this is the bar function"]) -import { bar, barExported } from "./helpers/bar.ts"; +import { bar, barExported } from "./stuff/bar.ts"; export default function () { return [bar(), barExported()]; diff --git a/inputs/passing/importFoo.ts b/inputs/passing/imports/importFoo.ts similarity index 72% rename from inputs/passing/importFoo.ts rename to inputs/passing/imports/importFoo.ts index 47ebc970..a0441ace 100644 --- a/inputs/passing/importFoo.ts +++ b/inputs/passing/imports/importFoo.ts @@ -1,6 +1,6 @@ //! test_output("this is the foo function") -import foo from "./helpers/foo.ts"; +import foo from "./stuff/foo.ts"; export default function () { return foo(); diff --git a/inputs/passing/importFooAndBar.ts b/inputs/passing/imports/importFooAndBar.ts similarity index 71% rename from inputs/passing/importFooAndBar.ts rename to inputs/passing/imports/importFooAndBar.ts index c8993693..b7c9a951 100644 --- a/inputs/passing/importFooAndBar.ts +++ b/inputs/passing/imports/importFooAndBar.ts @@ -1,6 +1,6 @@ //! test_output(["this is the foo function","this is the bar function"]) -import { bar, foo } from "./helpers/fooAndBar.ts"; +import { bar, foo } from "./stuff/fooAndBar.ts"; export default function () { return [foo(), bar()]; diff --git a/inputs/passing/importFoobar.ts b/inputs/passing/imports/importFoobar.ts similarity index 77% rename from inputs/passing/importFoobar.ts rename to inputs/passing/imports/importFoobar.ts index 822167c0..f8dae28b 100644 --- a/inputs/passing/importFoobar.ts +++ b/inputs/passing/imports/importFoobar.ts @@ -1,6 +1,6 @@ //! test_output(["this is the foo function","this is the bar function"]) -import foobar from "./helpers/foobar.ts"; +import foobar from "./stuff/foobar.ts"; export default function () { return [foobar.foo(), foobar.bar()]; diff --git a/inputs/passing/importType.ts b/inputs/passing/imports/importType.ts similarity index 68% rename from inputs/passing/importType.ts rename to inputs/passing/imports/importType.ts index 6103446b..c48f43be 100644 --- a/inputs/passing/importType.ts +++ b/inputs/passing/imports/importType.ts @@ -1,6 +1,6 @@ //! test_output("done") -import { type Type } from "./helpers/Type.ts"; +import { type Type } from "./stuff/Type.ts"; export default function main() { const msg: Type = "done"; diff --git a/inputs/passing/helpers/Type.ts b/inputs/passing/imports/stuff/Type.ts similarity index 100% rename from inputs/passing/helpers/Type.ts rename to inputs/passing/imports/stuff/Type.ts diff --git a/inputs/passing/helpers/bar.ts b/inputs/passing/imports/stuff/bar.ts similarity index 100% rename from inputs/passing/helpers/bar.ts rename to inputs/passing/imports/stuff/bar.ts diff --git a/inputs/passing/imports/stuff/circularA.ts b/inputs/passing/imports/stuff/circularA.ts new file mode 100644 index 00000000..9b16f487 --- /dev/null +++ b/inputs/passing/imports/stuff/circularA.ts @@ -0,0 +1,5 @@ +export * from "./circularB.ts"; + +export function a() { + return "a"; +} diff --git a/inputs/passing/imports/stuff/circularB.ts b/inputs/passing/imports/stuff/circularB.ts new file mode 100644 index 00000000..bad71e8a --- /dev/null +++ b/inputs/passing/imports/stuff/circularB.ts @@ -0,0 +1,5 @@ +export * from "./circularA.ts"; + +export function b() { + return "b"; +} diff --git a/inputs/passing/helpers/foo.ts b/inputs/passing/imports/stuff/foo.ts similarity index 100% rename from inputs/passing/helpers/foo.ts rename to inputs/passing/imports/stuff/foo.ts diff --git a/inputs/passing/helpers/fooAndBar.ts b/inputs/passing/imports/stuff/fooAndBar.ts similarity index 100% rename from inputs/passing/helpers/fooAndBar.ts rename to inputs/passing/imports/stuff/fooAndBar.ts diff --git a/inputs/passing/helpers/foobar.ts b/inputs/passing/imports/stuff/foobar.ts similarity index 100% rename from inputs/passing/helpers/foobar.ts rename to inputs/passing/imports/stuff/foobar.ts diff --git a/inputs/passing/imports/stuff/lotsOfThings.ts b/inputs/passing/imports/stuff/lotsOfThings.ts new file mode 100644 index 00000000..6d68c47d --- /dev/null +++ b/inputs/passing/imports/stuff/lotsOfThings.ts @@ -0,0 +1,15 @@ +export * from "./fooAndBar.ts"; +export * from "./circularA.ts"; +export * from "./circularB.ts"; + +export const x = 42; + +export function baz() { + return "baz"; +} + +// Conflicts with `b` in circularB.ts, but locals have precedence. +// (Conflicts between multiple export*s produce errors.) +export function b() { + return "b (local)"; +} diff --git a/valuescript_compiler/src/gather_modules.rs b/valuescript_compiler/src/gather_modules.rs index 092c047d..abfdd626 100644 --- a/valuescript_compiler/src/gather_modules.rs +++ b/valuescript_compiler/src/gather_modules.rs @@ -78,6 +78,7 @@ where }; let mut compiler_output = compile_module(&file_contents); + // println!("{}: {}", dependency.path, compiler_output.module); gm.diagnostics .entry(dependency.path.clone()) diff --git a/valuescript_compiler/src/import_pattern.rs b/valuescript_compiler/src/import_pattern.rs index fc2132af..89af848b 100644 --- a/valuescript_compiler/src/import_pattern.rs +++ b/valuescript_compiler/src/import_pattern.rs @@ -6,6 +6,7 @@ pub struct ImportPattern { pub kind: ImportKind, } +#[derive(PartialEq, Eq)] pub enum ImportKind { Default, Star, diff --git a/valuescript_compiler/src/link_module.rs b/valuescript_compiler/src/link_module.rs index 1beae249..c0345077 100644 --- a/valuescript_compiler/src/link_module.rs +++ b/valuescript_compiler/src/link_module.rs @@ -1,6 +1,9 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::mem::swap; -use crate::asm::{Definition, DefinitionContent, ExportStar, FnLine, Instruction, Pointer, Value}; +use crate::asm::{ + Definition, DefinitionContent, ExportStar, FnLine, Instruction, Object, Pointer, Value, +}; use crate::gather_modules::PathAndModule; use crate::import_pattern::{ImportKind, ImportPattern}; use crate::name_allocator::NameAllocator; @@ -174,7 +177,14 @@ fn link_import_patterns( included_modules: &HashMap, diagnostics: &mut Vec, ) { - for definition in &mut module.definitions { + module.export_star = ExportStar { + includes: vec![], + local: flatten_export_star(&module.export_star, &*module, included_modules, diagnostics), + }; + + let mut new_definitions = HashMap::::new(); + + for definition in &module.definitions { let import_pattern = match ImportPattern::decode(definition) { Some(import_pattern) => import_pattern, None => continue, @@ -185,24 +195,21 @@ fn link_import_patterns( path: import_pattern.path.clone(), }; - let (default, namespace) = match included_modules.get(&resolved_path) { + let (default, export_star) = match included_modules.get(&resolved_path) { Some(el) => el, None => continue, }; + let export_star = flatten_export_star(export_star, module, included_modules, diagnostics); + let new_definition = Definition { - pointer: import_pattern.pointer, + pointer: import_pattern.pointer.clone(), content: match import_pattern.kind { ImportKind::Default => DefinitionContent::Value(default.clone()), - ImportKind::Star => { - // TODO: namespace.includes - DefinitionContent::Value(Value::Object(Box::new(namespace.local.clone()))) - } - ImportKind::Name(name) => match namespace.local.try_resolve_key(&name) { + ImportKind::Star => DefinitionContent::Value(Value::Object(Box::new(export_star.clone()))), + ImportKind::Name(name) => match export_star.try_resolve_key(&name) { Some(value) => DefinitionContent::Value(value.clone()), None => { - // TODO: namespace.includes - diagnostics.push(Diagnostic { level: DiagnosticLevel::Error, message: format!( @@ -218,6 +225,124 @@ fn link_import_patterns( }, }; - *definition = new_definition; + new_definitions.insert(import_pattern.pointer, new_definition); + } + + for definition in &mut module.definitions { + if let Some(new_definition) = new_definitions.get_mut(&definition.pointer) { + swap(definition, new_definition); + } + } +} + +fn flatten_export_star( + export_star: &ExportStar, + module: &Module, + included_modules: &HashMap, + diagnostics: &mut Vec, +) -> Object { + let mut include_pointers_to_process = export_star.includes.clone(); + let mut include_map = BTreeMap::::new(); + let mut processed_includes = HashSet::::new(); + + let mut i = 0; + + while i < include_pointers_to_process.len() { + let include_p = include_pointers_to_process[i].clone(); + i += 1; + let mut matched = false; + + for defn in &module.definitions { + if defn.pointer == include_p { + matched = true; + + let ip = match ImportPattern::decode(defn) { + Some(ip) => ip, + None => { + diagnostics.push(Diagnostic::internal_error( + swc_common::DUMMY_SP, + "Expected import pattern", + )); + + break; + } + }; + + if ip.kind != ImportKind::Star { + diagnostics.push(Diagnostic::internal_error( + swc_common::DUMMY_SP, + "Expected import star pattern", + )); + + break; + } + + let path = ResolvedPath::from(ip.path); + + let inserted = processed_includes.insert(path.clone()); + + if !inserted { + break; + } + + let matched_export_star = match included_modules.get(&path) { + Some((_, es)) => es, + None => { + diagnostics.push(Diagnostic::internal_error( + swc_common::DUMMY_SP, + "Missing module", + )); + + break; + } + }; + + for (k, v) in &matched_export_star.local.properties { + let k_string = match k { + Value::String(k_string) => k_string.clone(), + _ => { + diagnostics.push(Diagnostic::internal_error( + swc_common::DUMMY_SP, + "Expected exported name to be a string", + )); + + continue; + } + }; + + let old_value = include_map.insert(k_string.clone(), v.clone()); + + if old_value.is_some() { + diagnostics.push(Diagnostic::error( + swc_common::DUMMY_SP, + &format!("Conflicting export {}", k_string), + )); + } + } + + include_pointers_to_process.append(&mut matched_export_star.includes.clone()); + + break; + } + } + + if !matched { + diagnostics.push(Diagnostic::internal_error( + swc_common::DUMMY_SP, + "Failed to match export* pointer", + )); + } + } + + let mut obj = Object::default(); + + for (key, value) in include_map { + obj.properties.push((Value::String(key), value)); } + + obj + .properties + .append(&mut export_star.local.properties.clone()); + + obj } diff --git a/valuescript_compiler/src/module_compiler.rs b/valuescript_compiler/src/module_compiler.rs index 89580e44..2e554ffe 100644 --- a/valuescript_compiler/src/module_compiler.rs +++ b/valuescript_compiler/src/module_compiler.rs @@ -176,7 +176,7 @@ impl ModuleCompiler { ExportNamed(en) => self.compile_named_export(en), ExportDefaultDecl(edd) => self.compile_export_default_decl(edd), ExportDefaultExpr(ede) => self.module.export_default = self.static_ec().expr(&ede.expr), - ExportAll(_) => self.todo(module_decl.span(), "ExportAll declaration"), + ExportAll(ea) => self.compile_export_all(ea), TsImportEquals(_) => self.not_supported(module_decl.span(), "TsImportEquals declaration"), TsExportAssignment(_) => { self.not_supported(module_decl.span(), "TsExportAssignment declaration") @@ -559,6 +559,22 @@ impl ModuleCompiler { } } + fn compile_export_all(&mut self, ea: &swc_ecma_ast::ExportAll) { + let defn = self.allocate_defn(&format!("{}_export_star", ident_from_str(&ea.src.value))); + + self.module.definitions.push(Definition { + pointer: defn.clone(), + content: DefinitionContent::Lazy(Lazy { + body: vec![FnLine::Instruction(Instruction::ImportStar( + Value::String(ea.src.value.to_string()), + Register::return_(), + ))], + }), + }); + + self.module.export_star.includes.push(defn); + } + fn compile_import(&mut self, import: &swc_ecma_ast::ImportDecl) { if import.type_only { return; diff --git a/website/src/playground/files/root/lib/mod.ts b/website/src/playground/files/root/lib/mod.ts index bea25da6..d1b4d042 100644 --- a/website/src/playground/files/root/lib/mod.ts +++ b/website/src/playground/files/root/lib/mod.ts @@ -1,6 +1,4 @@ export { default as BinaryTree } from "./BinaryTree.ts"; -export { type NotNullish } from "./util.ts"; -export { factorize, factorizeAsPowers, isPrime, primes } from "./primes.ts"; +export * from "./util.ts"; +export * from "./primes.ts"; export { default as Range } from "./Range.ts"; - -// Note: `export *` would be used here, but it isn't implemented yet.