devalang_core/core/preprocessor/
loader.rs

1use std::{ collections::{ HashMap, HashSet }, path::Path };
2use crate::{
3    core::{
4        error::ErrorHandler,
5        lexer::{ token::Token, Lexer },
6        parser::{ driver::Parser, statement::{ Statement, StatementKind } },
7        preprocessor::{ module::Module, processor::process_modules },
8        shared::{ bank::BankFile, value::Value },
9        store::global::GlobalStore,
10        utils::path::normalize_path,
11    },
12    utils::logger::Logger,
13};
14use crate::core::preprocessor::resolver::driver::{
15    resolve_all_modules,
16    resolve_and_flatten_all_modules,
17};
18use crate::core::utils::path::resolve_relative_path;
19
20pub struct ModuleLoader {
21    pub entry: String,
22    pub output: String,
23    pub base_dir: String,
24}
25
26impl ModuleLoader {
27    pub fn new(entry: &str, output: &str) -> Self {
28        let base_dir = Path::new(entry)
29            .parent()
30            .unwrap_or(Path::new(""))
31            .to_string_lossy()
32            .replace('\\', "/");
33
34        Self {
35            entry: entry.to_string(),
36            output: output.to_string(),
37            base_dir: base_dir,
38        }
39    }
40
41    pub fn from_raw_source(
42        entry_path: &str,
43        output_path: &str,
44        content: &str,
45        global_store: &mut GlobalStore
46    ) -> Self {
47        let normalized_entry_path = normalize_path(entry_path);
48
49        let mut module = Module::new(&entry_path);
50        module.content = content.to_string();
51
52        global_store.insert_module(normalized_entry_path.to_string(), module);
53
54        Self {
55            entry: normalized_entry_path.to_string(),
56            output: output_path.to_string(),
57            base_dir: "".to_string(),
58        }
59    }
60
61    pub fn extract_statements_map(
62        &self,
63        global_store: &GlobalStore
64    ) -> HashMap<String, Vec<Statement>> {
65        global_store.modules
66            .iter()
67            .map(|(path, module)| (path.clone(), module.statements.clone()))
68            .collect()
69    }
70
71    pub fn load_single_module(&self, global_store: &mut GlobalStore) -> Result<Module, String> {
72        let mut module = global_store.modules
73            .remove(&self.entry)
74            .ok_or_else(|| format!("Module not found in store for path: {}", self.entry))?;
75
76        // SECTION Lexing the module content
77        let lexer = Lexer::new();
78        let tokens = lexer
79            .lex_from_source(&module.content)
80            .map_err(|e| format!("Lexer failed: {}", e))?;
81
82        module.tokens = tokens.clone();
83
84        // SECTION Parsing tokens into statements
85        let mut parser = Parser::new();
86        parser.set_current_module(self.entry.clone());
87        let statements = parser.parse_tokens(tokens, global_store);
88        module.statements = statements;
89
90        // SECTION Injecting bank triggers if any
91        if let Err(e) = self.inject_bank_triggers(&mut module, "808") {
92            return Err(format!("Failed to inject bank triggers: {}", e));
93        }
94
95        global_store.modules.insert(self.entry.clone(), module.clone());
96
97        // SECTION Error handling
98        let mut error_handler = ErrorHandler::new();
99        error_handler.detect_from_statements(&mut parser, &module.statements);
100
101        Ok(module)
102    }
103
104    pub fn load_wasm_module(&self, global_store: &mut GlobalStore) -> Result<(), String> {
105        // Step one : Load the module from the global store
106        let module = {
107            let module_ref = global_store.modules
108                .get(&self.entry)
109                .ok_or_else(|| format!("❌ Module not found for path: {}", self.entry))?;
110
111            Module::from_existing(&self.entry, module_ref.content.clone())
112        };
113
114        // Step two : lexing
115        let lexer = Lexer::new();
116        let tokens = lexer
117            .lex_from_source(&module.content)
118            .map_err(|e| format!("Lexer failed: {}", e))?;
119
120        // Step three : parsing
121        let mut parser = Parser::new();
122        parser.set_current_module(self.entry.clone());
123
124        let statements = parser.parse_tokens(tokens.clone(), global_store);
125
126        let mut updated_module = module;
127        updated_module.tokens = tokens;
128        updated_module.statements = statements;
129
130        // Step four : Injecting bank triggers if any
131        if let Err(e) = self.inject_bank_triggers(&mut updated_module, "808") {
132            return Err(format!("Failed to inject bank triggers: {}", e));
133        }
134
135        // Step four : error handling
136        let mut error_handler = ErrorHandler::new();
137        error_handler.detect_from_statements(&mut parser, &updated_module.statements);
138
139        // Final step : insert the updated module back into the global store
140        global_store.modules.insert(self.entry.clone(), updated_module);
141
142        Ok(())
143    }
144
145    #[cfg(feature = "cli")]
146    pub fn load_all_modules(
147        &self,
148        global_store: &mut GlobalStore
149    ) -> (HashMap<String, Vec<Token>>, HashMap<String, Vec<Statement>>) {
150        // SECTION Load the entry module and its dependencies
151        let tokens_by_module = self.load_module_recursively(&self.entry, global_store);
152
153        // SECTION Process and resolve modules
154        process_modules(self, global_store);
155        resolve_all_modules(self, global_store);
156
157        // SECTION Flatten all modules to get statements (+ injects)
158        let statements_by_module = resolve_and_flatten_all_modules(global_store);
159
160        (tokens_by_module, statements_by_module)
161    }
162
163    #[cfg(feature = "cli")]
164    fn load_module_recursively(
165        &self,
166        raw_path: &str,
167        global_store: &mut GlobalStore
168    ) -> HashMap<String, Vec<Token>> {
169        let path = normalize_path(raw_path);
170
171        // Check if already loaded
172        if global_store.modules.contains_key(&path) {
173            return HashMap::new();
174        }
175
176        let lexer = Lexer::new();
177        let tokens = lexer.lex_tokens(&path);
178
179        let mut parser = Parser::new();
180        parser.set_current_module(path.clone());
181
182        let statements = parser.parse_tokens(tokens.clone(), global_store);
183
184        // Insert module into store
185        let mut module = Module::new(&path);
186        module.tokens = tokens.clone();
187        module.statements = statements.clone();
188
189        // Inject triggers for each bank used in module
190        for bank_name in self.extract_bank_names(&statements) {
191            let _ = self.inject_bank_triggers(&mut module, &bank_name);
192        }
193
194        // Inject module variables and functions into global store
195        global_store.variables.variables.extend(module.variable_table.variables.clone());
196        global_store.functions.functions.extend(module.function_table.functions.clone());
197
198        // Inject the module into the global store
199        global_store.insert_module(path.clone(), module);
200
201        // Load dependencies
202        self.load_module_imports(&path, global_store);
203
204        // Error handling
205        let mut error_handler = ErrorHandler::new();
206        error_handler.detect_from_statements(&mut parser, &statements);
207
208        if error_handler.has_errors() {
209            let logger = Logger::new();
210            for error in error_handler.get_errors() {
211                let trace = format!("{}:{}:{}", path, error.line, error.column);
212                logger.log_error_with_stacktrace(&error.message, &trace);
213            }
214        }
215
216        // Return tokens per module
217        global_store.modules
218            .iter()
219            .map(|(p, m)| (p.clone(), m.tokens.clone()))
220            .collect()
221    }
222
223    #[cfg(feature = "cli")]
224    fn load_module_imports(&self, path: &String, global_store: &mut GlobalStore) {
225        let import_paths: Vec<String> = {
226            let current_module = match global_store.modules.get(path) {
227                Some(module) => module,
228                None => {
229                    eprintln!("[warn] Cannot resolve imports: module '{}' not found in store", path);
230                    return;
231                }
232            };
233
234            current_module.statements
235                .iter()
236                .filter_map(|stmt| {
237                    if let StatementKind::Import { source, .. } = &stmt.kind {
238                        Some(source.clone())
239                    } else {
240                        None
241                    }
242                })
243                .collect()
244        };
245
246        for import_path in import_paths {
247            let resolved = resolve_relative_path(path, &import_path);
248            self.load_module_recursively(&resolved, global_store);
249        }
250    }
251
252    pub fn inject_bank_triggers(
253        &self,
254        module: &mut Module,
255        bank_name: &str
256    ) -> Result<Module, String> {
257        let alias = bank_name.split('.').last().unwrap_or(bank_name);
258
259        let bank_path = Path::new("./.deva/bank").join(bank_name);
260        let bank_toml_path = bank_path.join("bank.toml");
261
262        if !bank_toml_path.exists() {
263            return Ok(module.clone());
264        }
265
266        let content = std::fs
267            ::read_to_string(&bank_toml_path)
268            .map_err(|e| format!("Failed to read '{}': {}", bank_toml_path.display(), e))?;
269
270        let parsed_bankfile: BankFile = toml
271            ::from_str(&content)
272            .map_err(|e| format!("Failed to parse '{}': {}", bank_toml_path.display(), e))?;
273
274        let mut bank_map = HashMap::new();
275
276        for bank_trigger in parsed_bankfile.triggers.unwrap_or_default() {
277            let trigger_name = bank_trigger.name.clone().replace("./", "");
278            let bank_trigger_path = format!("devalang://bank/{}/{}", bank_name, trigger_name);
279
280            bank_map.insert(bank_trigger.name.clone(), Value::String(bank_trigger_path.clone()));
281
282            if module.variable_table.variables.contains_key(alias) {
283                eprintln!(
284                    "⚠️ Trigger '{}' already defined in module '{}', skipping injection.",
285                    alias,
286                    module.path
287                );
288                continue;
289            }
290
291            module.variable_table.set(
292                format!("{}.{}", alias, bank_trigger.name),
293                Value::String(bank_trigger_path.clone())
294            );
295        }
296
297        // Inject the map under the bank name
298        module.variable_table.set(alias.to_string(), Value::Map(bank_map));
299
300        Ok(module.clone())
301    }
302
303    fn extract_bank_names(&self, statements: &[Statement]) -> HashSet<String> {
304        let mut banks = HashSet::new();
305
306        for stmt in statements {
307            match &stmt.kind {
308                // Extract only bank declarations
309                StatementKind::Bank => {
310                    if let Value::String(name) = &stmt.value {
311                        banks.insert(name.clone());
312                    }
313                    if let Value::Number(num) = &stmt.value {
314                        banks.insert(num.to_string());
315                    }
316                    if let Value::Identifier(name) = &stmt.value {
317                        banks.insert(name.clone());
318                    }
319                }
320                _ => {}
321            }
322        }
323
324        banks
325    }
326}