logicaffeine_compile/analysis/discovery.rs
1//! Discovery extensions with module imports.
2//!
3//! Provides [`discover_with_imports`] which uses the [`Loader`](crate::loader::Loader)
4//! for multi-file projects with namespace support.
5//!
6//! # Multi-File Type Discovery
7//!
8//! ```text
9//! main.md geometry.md
10//! ┌────────────────────────┐ ┌────────────────────────┐
11//! │ ## Abstract │ │ ## Definition │
12//! │ [Geometry](geometry.md)│───────▶│ A Point has x: Int, │
13//! └────────────────────────┘ │ y: Int. │
14//! └────────────────────────┘
15//! ↓
16//! TypeRegistry contains:
17//! - Geometry::Point { x: Int, y: Int }
18//! ```
19
20use std::path::Path;
21use crate::loader::Loader;
22use crate::{Interner, Lexer, mwe};
23use super::{TypeRegistry, TypeDef, DiscoveryPass, scan_dependencies};
24
25/// Recursive discovery with module imports.
26///
27/// Scans a LOGOS source file for:
28/// 1. Dependencies declared in the Abstract (Markdown links)
29/// 2. Type definitions in `## Definition` blocks
30///
31/// # Namespace Prefixing
32///
33/// Dependencies are loaded recursively, and their types are merged into
34/// the registry with namespace prefixes (e.g., `Geometry::Point`).
35///
36/// # Arguments
37///
38/// * `file_path` - Path to the source file
39/// * `source` - Source code content
40/// * `loader` - Module loader for resolving imports
41/// * `interner` - Symbol interner
42///
43/// # Example
44///
45/// ```no_run
46/// # use logicaffeine_compile::loader::Loader;
47/// # use logicaffeine_compile::analysis::discover_with_imports;
48/// # use logicaffeine_compile::Interner;
49/// # use std::path::{Path, PathBuf};
50/// # fn main() -> Result<(), String> {
51/// # let root_path = PathBuf::from(".");
52/// # let source = "";
53/// # let mut interner = Interner::new();
54/// let mut loader = Loader::new(root_path);
55/// let registry = discover_with_imports(
56/// Path::new("main.md"),
57/// source,
58/// &mut loader,
59/// &mut interner
60/// )?;
61/// // registry now contains types from main.md and all imports
62/// # Ok(())
63/// # }
64/// ```
65pub fn discover_with_imports(
66 file_path: &Path,
67 source: &str,
68 loader: &mut Loader,
69 interner: &mut Interner,
70) -> Result<TypeRegistry, String> {
71 let mut registry = TypeRegistry::with_primitives(interner);
72
73 // 1. Scan for dependencies in the abstract
74 let deps = scan_dependencies(source);
75
76 // 2. For each dependency, recursively discover types
77 for dep in deps {
78 let module_source = loader.resolve(file_path, &dep.uri)?;
79 let dep_content = module_source.content.clone();
80 let dep_path = module_source.path.clone();
81
82 // Recursively discover types in the dependency
83 let dep_registry = discover_with_imports(
84 &dep_path,
85 &dep_content,
86 loader,
87 interner
88 )?;
89
90 // Merge with namespace prefix
91 merge_registry(&mut registry, &dep.alias, dep_registry, interner);
92 }
93
94 // 3. Scan local definitions using existing DiscoveryPass
95 let mut lexer = Lexer::new(source, interner);
96 let tokens = lexer.tokenize();
97 let mwe_trie = mwe::build_mwe_trie();
98 let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, interner);
99
100 let mut discovery = DiscoveryPass::new(&tokens, interner);
101 let local_registry = discovery.run();
102
103 // Merge local types (without namespace prefix)
104 for (sym, def) in local_registry.iter_types() {
105 // Skip primitives (already in registry)
106 let name = interner.resolve(*sym);
107 if !["Int", "Nat", "Text", "Bool", "Real", "Unit"].contains(&name) {
108 registry.register(*sym, def.clone());
109 }
110 }
111
112 Ok(registry)
113}
114
115/// Merges types from a dependency registry into the main registry with namespace prefix.
116fn merge_registry(
117 main: &mut TypeRegistry,
118 namespace: &str,
119 dep: TypeRegistry,
120 interner: &mut Interner,
121) {
122 for (sym, def) in dep.iter_types() {
123 // Get original name
124 let orig_name = interner.resolve(*sym);
125
126 // Skip primitives
127 if ["Int", "Nat", "Text", "Bool", "Real", "Unit"].contains(&orig_name) {
128 continue;
129 }
130
131 // Create namespaced name: "Geometry::Point"
132 let namespaced = format!("{}::{}", namespace, orig_name);
133 let namespaced_sym = interner.intern(&namespaced);
134
135 // Clone the typedef with the new namespaced symbol
136 let namespaced_def = match def {
137 TypeDef::Struct { fields, generics, is_portable, is_shared } => TypeDef::Struct {
138 fields: fields.clone(),
139 generics: generics.clone(),
140 is_portable: *is_portable,
141 is_shared: *is_shared,
142 },
143 TypeDef::Enum { variants, generics, is_portable, is_shared } => TypeDef::Enum {
144 variants: variants.clone(),
145 generics: generics.clone(),
146 is_portable: *is_portable,
147 is_shared: *is_shared,
148 },
149 TypeDef::Alias { target } => TypeDef::Alias {
150 target: target.clone(),
151 },
152 TypeDef::Generic { param_count } => TypeDef::Generic {
153 param_count: *param_count,
154 },
155 TypeDef::Primitive => TypeDef::Primitive,
156 };
157
158 main.register(namespaced_sym, namespaced_def);
159 }
160}