Skip to main content

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}