fusabi_frontend/
modules.rs

1//! Module System for Fusabi Mini-F#
2//!
3//! This module implements the module system for organizing code in Fusabi,
4//! supporting module definitions, imports (open statements), and qualified names.
5//!
6//! # Features
7//!
8//! - Module definitions with nested modules
9//! - Open imports for bringing module bindings into scope
10//! - Qualified name resolution (e.g., Math.add)
11//! - Name conflict detection
12//! - Type environment tracking per module
13//!
14//! # Example
15//!
16//! ```fsharp
17//! module Math =
18//!     let add x y = x + y
19//!     let multiply x y = x * y
20//!
21//! open Math
22//!
23//! let result = add 5 10  // Uses Math.add via open
24//! let result2 = Math.multiply 3 4  // Qualified access
25//! ```
26
27use crate::ast::{DuTypeDef, Expr, RecordTypeDef};
28use crate::types::TypeEnv;
29use std::collections::HashMap;
30
31/// Module registry for name resolution
32///
33/// Maintains a registry of all modules and their exported bindings,
34/// enabling both qualified and unqualified (via open) name lookups.
35#[derive(Debug, Clone)]
36pub struct ModuleRegistry {
37    /// Map from module name to Module
38    modules: HashMap<String, Module>,
39}
40
41/// A compiled module with its bindings and type environment
42#[derive(Debug, Clone)]
43pub struct Module {
44    /// Module name
45    pub name: String,
46    /// Value bindings (functions and constants)
47    pub bindings: HashMap<String, Expr>,
48    /// Type definitions (records and discriminated unions)
49    pub types: HashMap<String, TypeDefinition>,
50    /// Type environment for this module
51    pub type_env: TypeEnv,
52}
53
54/// Type definition exported by a module
55#[derive(Debug, Clone)]
56pub enum TypeDefinition {
57    /// Record type definition
58    Record(RecordTypeDef),
59    /// Discriminated union type definition
60    Du(DuTypeDef),
61}
62
63impl ModuleRegistry {
64    /// Create a new empty module registry
65    pub fn new() -> Self {
66        ModuleRegistry {
67            modules: HashMap::new(),
68        }
69    }
70
71    /// Create a new module registry with standard library modules pre-registered
72    pub fn with_stdlib() -> Self {
73        let mut registry = Self::new();
74        registry.register_stdlib_modules();
75        registry
76    }
77
78    /// Register standard library modules
79    ///
80    /// This registers the standard library modules (List, String, Map, Option, etc.)
81    /// as placeholder modules. The actual implementations are provided by the VM
82    /// at runtime, but this allows the compiler to resolve qualified names and
83    /// imports without errors.
84    fn register_stdlib_modules(&mut self) {
85        // Helper function to create a placeholder variable that will be resolved at runtime
86        let make_global_ref =
87            |qualified_name: &str| -> Expr { Expr::Var(qualified_name.to_string()) };
88
89        // List module
90        let mut list_bindings = HashMap::new();
91        list_bindings.insert("length".to_string(), make_global_ref("List.length"));
92        list_bindings.insert("head".to_string(), make_global_ref("List.head"));
93        list_bindings.insert("tail".to_string(), make_global_ref("List.tail"));
94        list_bindings.insert("reverse".to_string(), make_global_ref("List.reverse"));
95        list_bindings.insert("isEmpty".to_string(), make_global_ref("List.isEmpty"));
96        list_bindings.insert("append".to_string(), make_global_ref("List.append"));
97        list_bindings.insert("concat".to_string(), make_global_ref("List.concat"));
98        list_bindings.insert("map".to_string(), make_global_ref("List.map"));
99        self.register_module("List".to_string(), list_bindings, HashMap::new());
100
101        // String module
102        let mut string_bindings = HashMap::new();
103        string_bindings.insert("length".to_string(), make_global_ref("String.length"));
104        string_bindings.insert("trim".to_string(), make_global_ref("String.trim"));
105        string_bindings.insert("toLower".to_string(), make_global_ref("String.toLower"));
106        string_bindings.insert("toUpper".to_string(), make_global_ref("String.toUpper"));
107        string_bindings.insert("split".to_string(), make_global_ref("String.split"));
108        string_bindings.insert("concat".to_string(), make_global_ref("String.concat"));
109        string_bindings.insert("contains".to_string(), make_global_ref("String.contains"));
110        string_bindings.insert(
111            "startsWith".to_string(),
112            make_global_ref("String.startsWith"),
113        );
114        string_bindings.insert("endsWith".to_string(), make_global_ref("String.endsWith"));
115        self.register_module("String".to_string(), string_bindings, HashMap::new());
116
117        // Map module
118        let mut map_bindings = HashMap::new();
119        map_bindings.insert("empty".to_string(), make_global_ref("Map.empty"));
120        map_bindings.insert("add".to_string(), make_global_ref("Map.add"));
121        map_bindings.insert("remove".to_string(), make_global_ref("Map.remove"));
122        map_bindings.insert("find".to_string(), make_global_ref("Map.find"));
123        map_bindings.insert("tryFind".to_string(), make_global_ref("Map.tryFind"));
124        map_bindings.insert(
125            "containsKey".to_string(),
126            make_global_ref("Map.containsKey"),
127        );
128        map_bindings.insert("isEmpty".to_string(), make_global_ref("Map.isEmpty"));
129        map_bindings.insert("count".to_string(), make_global_ref("Map.count"));
130        map_bindings.insert("ofList".to_string(), make_global_ref("Map.ofList"));
131        map_bindings.insert("toList".to_string(), make_global_ref("Map.toList"));
132        self.register_module("Map".to_string(), map_bindings, HashMap::new());
133
134        // Option module
135        let mut option_bindings = HashMap::new();
136        option_bindings.insert("isSome".to_string(), make_global_ref("Option.isSome"));
137        option_bindings.insert("isNone".to_string(), make_global_ref("Option.isNone"));
138        option_bindings.insert(
139            "defaultValue".to_string(),
140            make_global_ref("Option.defaultValue"),
141        );
142        option_bindings.insert(
143            "defaultWith".to_string(),
144            make_global_ref("Option.defaultWith"),
145        );
146        option_bindings.insert("map".to_string(), make_global_ref("Option.map"));
147        option_bindings.insert("bind".to_string(), make_global_ref("Option.bind"));
148        option_bindings.insert("iter".to_string(), make_global_ref("Option.iter"));
149        option_bindings.insert("map2".to_string(), make_global_ref("Option.map2"));
150        option_bindings.insert("orElse".to_string(), make_global_ref("Option.orElse"));
151        self.register_module("Option".to_string(), option_bindings, HashMap::new());
152
153        // System.Collections.Generic module (for compatibility)
154        // This is a common .NET namespace that F# developers might use
155        let mut system_collections_generic_bindings = HashMap::new();
156        // Map common .NET collection types to Fusabi equivalents
157        system_collections_generic_bindings.insert("List".to_string(), make_global_ref("List"));
158        system_collections_generic_bindings
159            .insert("Dictionary".to_string(), make_global_ref("Map"));
160        self.register_module(
161            "System.Collections.Generic".to_string(),
162            system_collections_generic_bindings,
163            HashMap::new(),
164        );
165
166        // Support for System.Collections as well
167        let mut system_collections_bindings = HashMap::new();
168        system_collections_bindings.insert(
169            "Generic".to_string(),
170            make_global_ref("System.Collections.Generic"),
171        );
172        self.register_module(
173            "System.Collections".to_string(),
174            system_collections_bindings,
175            HashMap::new(),
176        );
177
178        // System module (parent of System.Collections)
179        let mut system_bindings = HashMap::new();
180        system_bindings.insert(
181            "Collections".to_string(),
182            make_global_ref("System.Collections"),
183        );
184        self.register_module("System".to_string(), system_bindings, HashMap::new());
185    }
186
187    /// Register a module with its bindings and types
188    ///
189    /// # Arguments
190    ///
191    /// * `name` - Module name
192    /// * `bindings` - Map of value bindings (name -> expression)
193    /// * `types` - Map of type definitions (type name -> definition)
194    pub fn register_module(
195        &mut self,
196        name: String,
197        bindings: HashMap<String, Expr>,
198        types: HashMap<String, TypeDefinition>,
199    ) {
200        let module = Module {
201            name: name.clone(),
202            bindings,
203            types,
204            type_env: TypeEnv::new(),
205        };
206        self.modules.insert(name, module);
207    }
208
209    /// Resolve a qualified name (e.g., "Math.add")
210    ///
211    /// # Returns
212    ///
213    /// The expression bound to the qualified name, or None if not found.
214    pub fn resolve_qualified(&self, module_name: &str, binding_name: &str) -> Option<&Expr> {
215        self.modules
216            .get(module_name)
217            .and_then(|m| m.bindings.get(binding_name))
218    }
219
220    /// Get all bindings from a module (for "open" imports)
221    ///
222    /// # Returns
223    ///
224    /// A reference to all bindings in the module, or None if module not found.
225    pub fn get_module_bindings(&self, module_name: &str) -> Option<&HashMap<String, Expr>> {
226        self.modules.get(module_name).map(|m| &m.bindings)
227    }
228
229    /// Get all type definitions from a module
230    ///
231    /// # Returns
232    ///
233    /// A reference to all type definitions in the module, or None if module not found.
234    pub fn get_module_types(&self, module_name: &str) -> Option<&HashMap<String, TypeDefinition>> {
235        self.modules.get(module_name).map(|m| &m.types)
236    }
237
238    /// Check if a module exists
239    pub fn has_module(&self, name: &str) -> bool {
240        self.modules.contains_key(name)
241    }
242
243    /// Get a module by name
244    pub fn get_module(&self, name: &str) -> Option<&Module> {
245        self.modules.get(name)
246    }
247
248    /// List all registered module names
249    pub fn module_names(&self) -> Vec<&str> {
250        self.modules.keys().map(|s| s.as_str()).collect()
251    }
252}
253
254impl Default for ModuleRegistry {
255    fn default() -> Self {
256        Self::new()
257    }
258}
259
260/// Module path for nested modules
261///
262/// Represents a path like ["Geometry", "Point"] for accessing nested modules.
263#[derive(Debug, Clone, PartialEq, Eq)]
264pub struct ModulePath {
265    /// Path components (e.g., ["Math", "Geometry"])
266    pub components: Vec<String>,
267}
268
269impl ModulePath {
270    /// Create a new module path from components
271    pub fn new(components: Vec<String>) -> Self {
272        ModulePath { components }
273    }
274
275    /// Create a single-component path
276    pub fn single(name: String) -> Self {
277        ModulePath {
278            components: vec![name],
279        }
280    }
281
282    /// Get the full qualified name (e.g., "Math.Geometry")
283    pub fn qualified_name(&self) -> String {
284        self.components.join(".")
285    }
286
287    /// Get the last component (e.g., "Geometry" from "Math.Geometry")
288    pub fn last(&self) -> Option<&str> {
289        self.components.last().map(|s| s.as_str())
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use crate::ast::{Literal, TypeExpr};
297
298    #[test]
299    fn test_module_registry_new() {
300        let registry = ModuleRegistry::new();
301        assert_eq!(registry.module_names().len(), 0);
302    }
303
304    #[test]
305    fn test_module_registry_with_stdlib() {
306        let registry = ModuleRegistry::with_stdlib();
307
308        // Should have standard library modules
309        assert!(registry.has_module("List"));
310        assert!(registry.has_module("String"));
311        assert!(registry.has_module("Map"));
312        assert!(registry.has_module("Option"));
313        assert!(registry.has_module("System"));
314        assert!(registry.has_module("System.Collections"));
315        assert!(registry.has_module("System.Collections.Generic"));
316
317        // Should be able to resolve stdlib functions
318        assert!(registry.resolve_qualified("List", "length").is_some());
319        assert!(registry.resolve_qualified("String", "trim").is_some());
320        assert!(registry.resolve_qualified("Map", "empty").is_some());
321        assert!(registry.resolve_qualified("Option", "isSome").is_some());
322    }
323
324    #[test]
325    fn test_register_and_resolve_module() {
326        let mut registry = ModuleRegistry::new();
327
328        // Create a simple Math module
329        let mut bindings = HashMap::new();
330        bindings.insert(
331            "add".to_string(),
332            Expr::Lambda {
333                param: "x".to_string(),
334                body: Box::new(Expr::Lambda {
335                    param: "y".to_string(),
336                    body: Box::new(Expr::BinOp {
337                        op: crate::ast::BinOp::Add,
338                        left: Box::new(Expr::Var("x".to_string())),
339                        right: Box::new(Expr::Var("y".to_string())),
340                    }),
341                }),
342            },
343        );
344
345        registry.register_module("Math".to_string(), bindings, HashMap::new());
346
347        // Verify module exists
348        assert!(registry.has_module("Math"));
349        assert_eq!(registry.module_names().len(), 1);
350
351        // Resolve qualified name
352        let expr = registry.resolve_qualified("Math", "add");
353        assert!(expr.is_some());
354        assert!(expr.unwrap().is_lambda());
355    }
356
357    #[test]
358    fn test_get_module_bindings() {
359        let mut registry = ModuleRegistry::new();
360
361        let mut bindings = HashMap::new();
362        bindings.insert("x".to_string(), Expr::Lit(Literal::Int(42)));
363        bindings.insert("y".to_string(), Expr::Lit(Literal::Int(100)));
364
365        registry.register_module("Test".to_string(), bindings, HashMap::new());
366
367        let module_bindings = registry.get_module_bindings("Test");
368        assert!(module_bindings.is_some());
369        assert_eq!(module_bindings.unwrap().len(), 2);
370    }
371
372    #[test]
373    fn test_resolve_nonexistent_module() {
374        let registry = ModuleRegistry::new();
375        assert!(!registry.has_module("Nonexistent"));
376        assert!(registry.resolve_qualified("Nonexistent", "add").is_none());
377    }
378
379    #[test]
380    fn test_module_path() {
381        let path = ModulePath::new(vec!["Math".to_string(), "Geometry".to_string()]);
382        assert_eq!(path.qualified_name(), "Math.Geometry");
383        assert_eq!(path.last(), Some("Geometry"));
384
385        let single = ModulePath::single("Math".to_string());
386        assert_eq!(single.qualified_name(), "Math");
387        assert_eq!(single.last(), Some("Math"));
388    }
389
390    #[test]
391    fn test_module_with_types() {
392        let mut registry = ModuleRegistry::new();
393
394        let mut types = HashMap::new();
395        types.insert(
396            "Person".to_string(),
397            TypeDefinition::Record(RecordTypeDef {
398                name: "Person".to_string(),
399                fields: vec![
400                    ("name".to_string(), TypeExpr::Named("string".to_string())),
401                    ("age".to_string(), TypeExpr::Named("int".to_string())),
402                ],
403            }),
404        );
405
406        registry.register_module("Data".to_string(), HashMap::new(), types);
407
408        let module_types = registry.get_module_types("Data");
409        assert!(module_types.is_some());
410        assert_eq!(module_types.unwrap().len(), 1);
411        assert!(module_types.unwrap().contains_key("Person"));
412    }
413}