Skip to main content

pipa/runtime/
module.rs

1use crate::object::JSObject;
2use crate::runtime::atom::AtomTable;
3use crate::runtime::context::JSContext;
4use crate::util::FxHashMap;
5use crate::value::JSValue;
6use std::cell::RefCell;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ModuleState {
10    Unlinked,
11    Linking,
12    Linked,
13    Evaluating,
14    Evaluated,
15    Errored,
16}
17
18#[derive(Clone)]
19pub struct ModuleExport {
20    pub name: String,
21    pub value: JSValue,
22    pub mutable: bool,
23}
24
25#[derive(Clone)]
26pub struct ModuleImport {
27    pub module_specifier: String,
28    pub import_name: String,
29    pub local_name: String,
30}
31
32pub struct Module {
33    pub specifier: String,
34    pub state: ModuleState,
35    pub exports: FxHashMap<String, ModuleExport>,
36    pub imports: Vec<ModuleImport>,
37    pub namespace_object: Option<usize>,
38    pub source: String,
39    pub import_meta_object: Option<usize>,
40    pub error: Option<String>,
41}
42
43impl Module {
44    pub fn new(specifier: String, source: String) -> Self {
45        Module {
46            specifier,
47            state: ModuleState::Unlinked,
48            exports: FxHashMap::default(),
49            imports: Vec::new(),
50            namespace_object: None,
51            source,
52            import_meta_object: None,
53            error: None,
54        }
55    }
56
57    pub fn add_export(&mut self, name: String, value: JSValue, mutable: bool) {
58        self.exports.insert(
59            name.clone(),
60            ModuleExport {
61                name,
62                value,
63                mutable,
64            },
65        );
66    }
67
68    pub fn add_import(
69        &mut self,
70        module_specifier: String,
71        import_name: String,
72        local_name: String,
73    ) {
74        self.imports.push(ModuleImport {
75            module_specifier,
76            import_name,
77            local_name,
78        });
79    }
80
81    pub fn get_export(&self, name: &str) -> Option<&ModuleExport> {
82        self.exports.get(name)
83    }
84
85    pub fn get_export_mut(&mut self, name: &str) -> Option<&mut ModuleExport> {
86        self.exports.get_mut(name)
87    }
88
89    pub fn get_export_value(&self, name: &str) -> JSValue {
90        self.exports
91            .get(name)
92            .map(|e| e.value.clone())
93            .unwrap_or(JSValue::undefined())
94    }
95
96    pub fn set_export_value(&mut self, name: &str, value: JSValue) {
97        if let Some(export) = self.exports.get_mut(name) {
98            export.value = value;
99        }
100    }
101
102    pub fn create_namespace_object(&mut self, atom_table: &mut AtomTable) -> usize {
103        let mut ns_obj = JSObject::new();
104        for (name, export) in &self.exports {
105            let atom = atom_table.intern(name);
106            ns_obj.set(atom, export.value.clone());
107        }
108        let ptr = Box::into_raw(Box::new(ns_obj)) as usize;
109        self.namespace_object = Some(ptr);
110        ptr
111    }
112
113    pub fn get_or_create_namespace_object(&mut self, atom_table: &mut AtomTable) -> usize {
114        if let Some(ptr) = self.namespace_object {
115            ptr
116        } else {
117            self.create_namespace_object(atom_table)
118        }
119    }
120
121    pub fn has_dependencies(&self) -> bool {
122        !self.imports.is_empty()
123    }
124
125    pub fn get_dependencies(&self) -> Vec<String> {
126        self.imports
127            .iter()
128            .map(|imp| imp.module_specifier.clone())
129            .filter(|s| !s.is_empty())
130            .collect()
131    }
132}
133
134pub struct ModuleRegistry {
135    modules: FxHashMap<String, RefCell<Module>>,
136    resolving: Vec<String>,
137}
138
139impl ModuleRegistry {
140    pub fn new() -> Self {
141        ModuleRegistry {
142            modules: FxHashMap::default(),
143            resolving: Vec::new(),
144        }
145    }
146
147    pub fn register(&mut self, module: Module) {
148        self.modules
149            .insert(module.specifier.clone(), RefCell::new(module));
150    }
151
152    pub fn get(&self, specifier: &str) -> Option<&RefCell<Module>> {
153        self.modules.get(specifier)
154    }
155
156    pub fn get_mut(&mut self, specifier: &str) -> Option<&mut RefCell<Module>> {
157        self.modules.get_mut(specifier)
158    }
159
160    pub fn has(&self, specifier: &str) -> bool {
161        self.modules.contains_key(specifier)
162    }
163
164    pub fn begin_resolve(&mut self, specifier: &str) -> bool {
165        if self.resolving.contains(&specifier.to_string()) {
166            return false;
167        }
168        self.resolving.push(specifier.to_string());
169        true
170    }
171
172    pub fn end_resolve(&mut self, specifier: &str) {
173        self.resolving.retain(|s| s != specifier);
174    }
175
176    pub fn is_resolving(&self, specifier: &str) -> bool {
177        self.resolving.contains(&specifier.to_string())
178    }
179
180    pub fn is_state(&self, specifier: &str, state: ModuleState) -> bool {
181        self.modules
182            .get(specifier)
183            .map(|m| m.borrow().state == state)
184            .unwrap_or(false)
185    }
186
187    pub fn set_state(&mut self, specifier: &str, state: ModuleState) {
188        if let Some(m) = self.modules.get(specifier) {
189            m.borrow_mut().state = state;
190        }
191    }
192
193    pub fn get_all_specifiers(&self) -> Vec<String> {
194        self.modules.keys().cloned().collect()
195    }
196}
197
198impl Default for ModuleRegistry {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204pub fn resolve_specifier(specifier: &str, base_path: &str) -> String {
205    if specifier.starts_with("./") || specifier.starts_with("../") {
206        let base = std::path::Path::new(base_path);
207        let base_dir = base.parent().unwrap_or(std::path::Path::new("."));
208        let resolved = base_dir.join(specifier);
209        let canonical = std::fs::canonicalize(&resolved).unwrap_or(resolved);
210        canonical.to_string_lossy().to_string()
211    } else if specifier.starts_with('/') {
212        specifier.to_string()
213    } else {
214        let path = std::path::Path::new(".").join(specifier);
215        let canonical = std::fs::canonicalize(&path).unwrap_or(path);
216        canonical.to_string_lossy().to_string()
217    }
218}
219
220pub fn load_module_source(specifier: &str) -> Result<String, String> {
221    let path = if specifier.ends_with(".js") || specifier.ends_with(".mjs") {
222        specifier.to_string()
223    } else {
224        format!("{}.js", specifier)
225    };
226
227    std::fs::read_to_string(&path).map_err(|e| format!("Failed to load module '{}': {}", path, e))
228}
229
230pub fn load_and_evaluate_module(ctx: &mut JSContext, specifier: &str) -> Result<usize, String> {
231    use crate::compiler::ast::BlockStatement;
232    use crate::compiler::codegen::CodeGenerator;
233    use crate::compiler::parser::Parser;
234    use crate::compiler::peephole;
235    use crate::runtime::vm::VM;
236
237    let resolved = if specifier.starts_with("./")
238        || specifier.starts_with("../")
239        || specifier.starts_with('/')
240    {
241        if let Some(current) = ctx.get_current_module() {
242            resolve_specifier(specifier, current)
243        } else {
244            let cwd = std::env::current_dir()
245                .map(|p| p.to_string_lossy().to_string())
246                .unwrap_or_else(|_| ".".to_string());
247            resolve_specifier(specifier, &cwd)
248        }
249    } else {
250        specifier.to_string()
251    };
252
253    let already_evaluated = {
254        let registry = ctx.runtime().module_registry();
255        registry
256            .get(&resolved)
257            .map(|m| m.borrow().state == ModuleState::Evaluated)
258            .unwrap_or(false)
259    };
260
261    if already_evaluated {
262        let atom_table_ptr = ctx.atom_table_mut() as *mut _;
263        let ns_ptr = unsafe {
264            let registry = ctx.runtime_mut().module_registry_mut();
265            let module_cell = registry.get_mut(&resolved);
266            if let Some(module_cell) = module_cell {
267                module_cell
268                    .borrow_mut()
269                    .get_or_create_namespace_object(&mut *atom_table_ptr)
270            } else {
271                return Err(format!("Module {} not found", resolved));
272            }
273        };
274        return Ok(ns_ptr);
275    }
276
277    let is_manually_registered = {
278        let registry = ctx.runtime().module_registry();
279        registry
280            .get(&resolved)
281            .map(|m| {
282                let module = m.borrow();
283                !module.exports.is_empty()
284            })
285            .unwrap_or(false)
286    };
287
288    if is_manually_registered {
289        {
290            let registry = ctx.runtime_mut().module_registry_mut();
291            if let Some(module_cell) = registry.get_mut(&resolved) {
292                module_cell.borrow_mut().state = ModuleState::Evaluated;
293            }
294        }
295        let atom_table_ptr = ctx.atom_table_mut() as *mut _;
296        let ns_ptr = unsafe {
297            let registry = ctx.runtime_mut().module_registry_mut();
298            let module_cell = registry.get_mut(&resolved);
299            if let Some(module_cell) = module_cell {
300                module_cell
301                    .borrow_mut()
302                    .get_or_create_namespace_object(&mut *atom_table_ptr)
303            } else {
304                return Err(format!("Module {} not found", resolved));
305            }
306        };
307        return Ok(ns_ptr);
308    }
309
310    {
311        let registry = ctx.runtime().module_registry();
312        if !registry.has(&resolved) {
313            let source = load_module_source(&resolved)?;
314            let module = Module::new(resolved.clone(), source);
315            ctx.runtime_mut().module_registry_mut().register(module);
316        }
317    }
318
319    let source = {
320        let registry = ctx.runtime().module_registry();
321        if let Some(module_cell) = registry.get(&resolved) {
322            module_cell.borrow().source.clone()
323        } else {
324            return Err(format!("Module {} not found after registration", resolved));
325        }
326    };
327
328    let dependencies: Vec<String> = {
329        let registry = ctx.runtime().module_registry();
330        if let Some(module_cell) = registry.get(&resolved) {
331            module_cell.borrow().get_dependencies()
332        } else {
333            Vec::new()
334        }
335    };
336
337    {
338        let registry = ctx.runtime_mut().module_registry_mut();
339        if let Some(module_cell) = registry.get_mut(&resolved) {
340            module_cell.borrow_mut().state = ModuleState::Evaluating;
341        }
342    }
343
344    for dep_specifier in &dependencies {
345        let resolved_dep = if dep_specifier.starts_with("./") || dep_specifier.starts_with("../") {
346            resolve_specifier(dep_specifier, &resolved)
347        } else {
348            dep_specifier.clone()
349        };
350        load_and_evaluate_module(ctx, &resolved_dep)?;
351    }
352
353    let old_module = ctx.get_current_module().map(|s| s.to_string());
354    ctx.set_current_module(Some(resolved.clone()));
355
356    let rb = {
357        let ast = Parser::new(&source).parse()?;
358        let opt_level = ctx.get_compiler_opt_level();
359        let mut codegen = CodeGenerator::with_opt_level(opt_level);
360        let block = BlockStatement {
361            body: ast.body,
362            lines: ast.lines,
363        };
364        let (mut rb, _) = codegen.compile_script(&block, ctx)?;
365        peephole::optimize_with_level(&mut rb, opt_level);
366        rb
367    };
368
369    let result = {
370        let mut vm = VM::new();
371        vm.execute(ctx, &rb)
372    };
373
374    ctx.set_current_module(old_module);
375
376    match result {
377        Ok(_) => {
378            {
379                let registry = ctx.runtime_mut().module_registry_mut();
380                if let Some(module_cell) = registry.get_mut(&resolved) {
381                    module_cell.borrow_mut().state = ModuleState::Evaluated;
382                }
383            }
384            let ns_ptr = {
385                let atom_table_ptr = ctx.atom_table_mut() as *mut _;
386                let registry = ctx.runtime_mut().module_registry_mut();
387                if let Some(module_cell) = registry.get_mut(&resolved) {
388                    unsafe {
389                        module_cell
390                            .borrow_mut()
391                            .get_or_create_namespace_object(&mut *atom_table_ptr)
392                    }
393                } else {
394                    return Err(format!("Module {} not found after evaluation", resolved));
395                }
396            };
397            Ok(ns_ptr)
398        }
399        Err(e) => {
400            {
401                let registry = ctx.runtime_mut().module_registry_mut();
402                if let Some(module_cell) = registry.get_mut(&resolved) {
403                    let mut module = module_cell.borrow_mut();
404                    module.state = ModuleState::Errored;
405                    module.error = Some(e.clone());
406                }
407            }
408            Err(e)
409        }
410    }
411}