devalang_core/core/preprocessor/loader/
mod.rs

1#[cfg(feature = "cli")]
2use crate::core::preprocessor::resolver::driver::{
3    resolve_all_modules, resolve_and_flatten_all_modules,
4};
5// resolve_relative_path moved to loader_helpers
6use crate::core::{
7    error::ErrorHandler,
8    lexer::{driver::Lexer, token::Token},
9    parser::{driver::parser::Parser, statement::Statement},
10    preprocessor::{module::Module, processor::handlers::process_modules},
11    store::global::GlobalStore,
12};
13use devalang_utils::path::normalize_path;
14use std::{collections::HashMap, path::Path};
15
16mod inject;
17mod loader_helpers;
18
19pub struct ModuleLoader {
20    pub entry: String,
21    pub output: String,
22    pub base_dir: String,
23}
24
25impl ModuleLoader {
26    pub fn new(entry: &str, output: &str) -> Self {
27        let base_dir = Path::new(entry)
28            .parent()
29            .unwrap_or(Path::new(""))
30            .to_string_lossy()
31            .replace('\\', "/");
32
33        Self {
34            entry: entry.to_string(),
35            output: output.to_string(),
36            base_dir,
37        }
38    }
39
40    pub fn from_raw_source(
41        entry_path: &str,
42        output_path: &str,
43        content: &str,
44        global_store: &mut GlobalStore,
45    ) -> Self {
46        let normalized_entry_path = normalize_path(entry_path);
47
48        let mut module = Module::new(entry_path);
49        module.content = content.to_string();
50
51        // Insert a module stub containing the provided content into the
52        // global store. This is used by the WASM APIs and tests which
53        // operate on in-memory sources instead of files on disk.
54        global_store.insert_module(normalized_entry_path.to_string(), module);
55
56        Self {
57            entry: normalized_entry_path.to_string(),
58            output: output_path.to_string(),
59            base_dir: "".to_string(),
60        }
61    }
62
63    pub fn extract_statements_map(
64        &self,
65        global_store: &GlobalStore,
66    ) -> HashMap<String, Vec<Statement>> {
67        global_store
68            .modules
69            .iter()
70            .map(|(path, module)| (path.clone(), module.statements.clone()))
71            .collect()
72    }
73
74    pub fn load_single_module(&self, global_store: &mut GlobalStore) -> Result<Module, String> {
75        let mut module = global_store
76            .modules
77            .remove(&self.entry)
78            .ok_or_else(|| format!("Module not found in store for path: {}", self.entry))?;
79
80        // SECTION Lexing the module content
81        let lexer = Lexer::new();
82        let tokens = lexer
83            .lex_from_source(&module.content)
84            .map_err(|e| format!("Lexer failed: {}", e))?;
85
86        module.tokens = tokens.clone();
87
88        // SECTION Parsing tokens into statements
89        let mut parser = Parser::new();
90        parser.set_current_module(self.entry.clone());
91        let statements = parser.parse_tokens(tokens, global_store);
92        module.statements = statements;
93
94        // SECTION Injecting bank triggers if any (legacy default for single-module run)
95        if let Err(e) = inject::inject_bank_triggers(&mut module, "808", None) {
96            return Err(format!("Failed to inject bank triggers: {}", e));
97        }
98
99        for (plugin_name, alias) in inject::extract_plugin_uses(&module.statements) {
100            inject::load_plugin_and_register(&mut module, &plugin_name, &alias, global_store);
101        }
102
103        global_store
104            .modules
105            .insert(self.entry.clone(), module.clone());
106
107        // SECTION Error handling
108        let mut error_handler = ErrorHandler::new();
109        error_handler.detect_from_statements(&mut parser, &module.statements);
110
111        Ok(module)
112    }
113
114    pub fn load_wasm_module(&self, global_store: &mut GlobalStore) -> Result<(), String> {
115        // Step one : Load the module from the global store
116        let module = {
117            let module_ref = global_store
118                .modules
119                .get(&self.entry)
120                .ok_or_else(|| format!("❌ Module not found for path: {}", self.entry))?;
121
122            Module::from_existing(&self.entry, module_ref.content.clone())
123        };
124
125        // Step two : lexing
126        let lexer = Lexer::new();
127        let tokens = lexer
128            .lex_from_source(&module.content)
129            .map_err(|e| format!("Lexer failed: {}", e))?;
130
131        // Step three : parsing
132        let mut parser = Parser::new();
133        parser.set_current_module(self.entry.clone());
134
135        let statements = parser.parse_tokens(tokens.clone(), global_store);
136
137        let mut updated_module = module;
138        updated_module.tokens = tokens;
139        updated_module.statements = statements;
140
141        // Step four : Injecting bank triggers if any
142        if let Err(e) = inject::inject_bank_triggers(&mut updated_module, "808", None) {
143            return Err(format!("Failed to inject bank triggers: {}", e));
144        }
145
146        // Insert the updated module into the global store before processing so
147        // process_modules can operate on it and populate variable_table, imports,
148        // and other derived structures.
149        global_store
150            .modules
151            .insert(self.entry.clone(), updated_module.clone());
152
153        // Process modules to populate module.variable_table, import/export tables,
154        // and other derived structures so runtime execution can resolve groups/synths.
155        process_modules(self, global_store);
156
157        for (plugin_name, alias) in inject::extract_plugin_uses(&updated_module.statements) {
158            inject::load_plugin_and_register(
159                &mut updated_module,
160                &plugin_name,
161                &alias,
162                global_store,
163            );
164        }
165
166        // Step four : error handling
167        let mut error_handler = ErrorHandler::new();
168        error_handler.detect_from_statements(&mut parser, &updated_module.statements);
169
170        // Final step : also expose module-level variables and functions into the global store
171        // so runtime evaluation (render_audio) can find group/synth definitions.
172        // Use the module instance that was actually processed by `process_modules`
173        // (it lives in `global_store.modules`) because `updated_module` is a local
174        // clone and won't contain the mutations applied by `process_modules`.
175        if let Some(stored_module) = global_store.modules.get(&self.entry) {
176            global_store
177                .variables
178                .variables
179                .extend(stored_module.variable_table.variables.clone());
180            global_store
181                .functions
182                .functions
183                .extend(stored_module.function_table.functions.clone());
184        } else {
185            // Fallback to the local updated_module if for any reason the module
186            // wasn't inserted into the store (defensive programming).
187            global_store
188                .variables
189                .variables
190                .extend(updated_module.variable_table.variables.clone());
191            global_store
192                .functions
193                .functions
194                .extend(updated_module.function_table.functions.clone());
195        }
196
197        Ok(())
198    }
199
200    #[cfg(feature = "cli")]
201    pub fn load_all_modules(
202        &self,
203        global_store: &mut GlobalStore,
204    ) -> (HashMap<String, Vec<Token>>, HashMap<String, Vec<Statement>>) {
205        // SECTION Load the entry module and its dependencies
206        let tokens_by_module = self.load_module_recursively(&self.entry, global_store);
207
208        // SECTION Process and resolve modules
209        process_modules(self, global_store);
210        resolve_all_modules(self, global_store);
211
212        // SECTION Flatten all modules to get statements (+ injects)
213        let statements_by_module = resolve_and_flatten_all_modules(global_store);
214
215        (tokens_by_module, statements_by_module)
216    }
217
218    #[cfg(feature = "cli")]
219    fn load_module_recursively(
220        &self,
221        raw_path: &str,
222        global_store: &mut GlobalStore,
223    ) -> HashMap<String, Vec<Token>> {
224        crate::core::preprocessor::loader::loader_helpers::load_module_recursively(
225            raw_path,
226            global_store,
227        )
228    }
229
230    #[cfg(feature = "cli")]
231    #[allow(dead_code)]
232    fn load_module_imports(&self, path: &String, global_store: &mut GlobalStore) {
233        crate::core::preprocessor::loader::loader_helpers::load_module_imports(path, global_store)
234    }
235}