1use crate::core::preprocessor::resolver::driver::{
2 resolve_all_modules, resolve_and_flatten_all_modules,
3};
4use crate::core::utils::path::resolve_relative_path;
5use crate::{
6 config::loader::load_config,
7 core::{
8 error::ErrorHandler,
9 lexer::{Lexer, token::Token},
10 parser::{
11 driver::Parser,
12 statement::{Statement, StatementKind},
13 },
14 plugin::loader::load_plugin,
15 preprocessor::{module::Module, processor::process_modules},
16 shared::{bank::BankFile, value::Value},
17 store::global::GlobalStore,
18 utils::path::normalize_path,
19 },
20 utils::logger::Logger,
21};
22use std::{collections::HashMap, path::Path};
23
24pub struct ModuleLoader {
25 pub entry: String,
26 pub output: String,
27 pub base_dir: String,
28}
29
30impl ModuleLoader {
31 pub fn new(entry: &str, output: &str) -> Self {
32 let base_dir = Path::new(entry)
33 .parent()
34 .unwrap_or(Path::new(""))
35 .to_string_lossy()
36 .replace('\\', "/");
37
38 Self {
39 entry: entry.to_string(),
40 output: output.to_string(),
41 base_dir: base_dir,
42 }
43 }
44
45 pub fn from_raw_source(
46 entry_path: &str,
47 output_path: &str,
48 content: &str,
49 global_store: &mut GlobalStore,
50 ) -> Self {
51 let normalized_entry_path = normalize_path(entry_path);
52
53 let mut module = Module::new(&entry_path);
54 module.content = content.to_string();
55
56 global_store.insert_module(normalized_entry_path.to_string(), module);
57
58 Self {
59 entry: normalized_entry_path.to_string(),
60 output: output_path.to_string(),
61 base_dir: "".to_string(),
62 }
63 }
64
65 pub fn extract_statements_map(
66 &self,
67 global_store: &GlobalStore,
68 ) -> HashMap<String, Vec<Statement>> {
69 global_store
70 .modules
71 .iter()
72 .map(|(path, module)| (path.clone(), module.statements.clone()))
73 .collect()
74 }
75
76 pub fn load_single_module(&self, global_store: &mut GlobalStore) -> Result<Module, String> {
77 let mut module = global_store
78 .modules
79 .remove(&self.entry)
80 .ok_or_else(|| format!("Module not found in store for path: {}", self.entry))?;
81
82 let lexer = Lexer::new();
84 let tokens = lexer
85 .lex_from_source(&module.content)
86 .map_err(|e| format!("Lexer failed: {}", e))?;
87
88 module.tokens = tokens.clone();
89
90 let mut parser = Parser::new();
92 parser.set_current_module(self.entry.clone());
93 let statements = parser.parse_tokens(tokens, global_store);
94 module.statements = statements;
95
96 if let Err(e) = self.inject_bank_triggers(&mut module, "808", None) {
98 return Err(format!("Failed to inject bank triggers: {}", e));
99 }
100
101 for (plugin_name, alias) in self.extract_plugin_uses(&module.statements) {
102 self.load_plugin_and_register(&mut module, &plugin_name, &alias, global_store);
103 }
104
105 global_store
106 .modules
107 .insert(self.entry.clone(), module.clone());
108
109 let mut error_handler = ErrorHandler::new();
111 error_handler.detect_from_statements(&mut parser, &module.statements);
112
113 Ok(module)
114 }
115
116 pub fn load_wasm_module(&self, global_store: &mut GlobalStore) -> Result<(), String> {
117 let module = {
119 let module_ref = global_store
120 .modules
121 .get(&self.entry)
122 .ok_or_else(|| format!("❌ Module not found for path: {}", self.entry))?;
123
124 Module::from_existing(&self.entry, module_ref.content.clone())
125 };
126
127 let lexer = Lexer::new();
129 let tokens = lexer
130 .lex_from_source(&module.content)
131 .map_err(|e| format!("Lexer failed: {}", e))?;
132
133 let mut parser = Parser::new();
135 parser.set_current_module(self.entry.clone());
136
137 let statements = parser.parse_tokens(tokens.clone(), global_store);
138
139 let mut updated_module = module;
140 updated_module.tokens = tokens;
141 updated_module.statements = statements;
142
143 if let Err(e) = self.inject_bank_triggers(&mut updated_module, "808", None) {
145 return Err(format!("Failed to inject bank triggers: {}", e));
146 }
147
148 for (plugin_name, alias) in self.extract_plugin_uses(&updated_module.statements) {
149 self.load_plugin_and_register(&mut updated_module, &plugin_name, &alias, global_store);
150 }
151
152 let mut error_handler = ErrorHandler::new();
154 error_handler.detect_from_statements(&mut parser, &updated_module.statements);
155
156 global_store
158 .modules
159 .insert(self.entry.clone(), updated_module);
160
161 Ok(())
162 }
163
164 #[cfg(feature = "cli")]
165 pub fn load_all_modules(
166 &self,
167 global_store: &mut GlobalStore,
168 ) -> (HashMap<String, Vec<Token>>, HashMap<String, Vec<Statement>>) {
169 let tokens_by_module = self.load_module_recursively(&self.entry, global_store);
171
172 process_modules(self, global_store);
174 resolve_all_modules(self, global_store);
175
176 let statements_by_module = resolve_and_flatten_all_modules(global_store);
178
179 (tokens_by_module, statements_by_module)
180 }
181
182 #[cfg(feature = "cli")]
183 fn load_module_recursively(
184 &self,
185 raw_path: &str,
186 global_store: &mut GlobalStore,
187 ) -> HashMap<String, Vec<Token>> {
188 let path = normalize_path(raw_path);
189
190 if global_store.modules.contains_key(&path) {
192 return HashMap::new();
193 }
194
195 let lexer = Lexer::new();
196 let tokens = lexer.lex_tokens(&path);
197
198 let mut parser = Parser::new();
199 parser.set_current_module(path.clone());
200
201 let statements = parser.parse_tokens(tokens.clone(), global_store);
202
203 let mut module = Module::new(&path);
205 module.tokens = tokens.clone();
206 module.statements = statements.clone();
207
208 for (bank_name, alias_opt) in self.extract_bank_decls(&statements) {
210 if let Err(e) = self.inject_bank_triggers(&mut module, &bank_name, alias_opt) {
211 eprintln!("Failed to inject bank triggers for '{}': {}", bank_name, e);
212 }
213 }
214
215 for (plugin_name, alias) in self.extract_plugin_uses(&statements) {
216 self.load_plugin_and_register(&mut module, &plugin_name, &alias, global_store);
217 }
218
219 global_store
221 .variables
222 .variables
223 .extend(module.variable_table.variables.clone());
224 global_store
225 .functions
226 .functions
227 .extend(module.function_table.functions.clone());
228
229 global_store.insert_module(path.clone(), module);
231
232 self.load_module_imports(&path, global_store);
234
235 let mut error_handler = ErrorHandler::new();
237 if let Some(current_module) = global_store.modules.get(&path) {
238 error_handler.detect_from_statements(&mut parser, ¤t_module.statements);
239 } else {
240 error_handler.detect_from_statements(&mut parser, &statements);
241 }
242
243 if error_handler.has_errors() {
244 let logger = Logger::new();
245 for error in error_handler.get_errors() {
246 let trace = format!("{}:{}:{}", path, error.line, error.column);
247 logger.log_error_with_stacktrace(&error.message, &trace);
248 }
249 }
250
251 global_store
253 .modules
254 .iter()
255 .map(|(p, m)| (p.clone(), m.tokens.clone()))
256 .collect()
257 }
258
259 #[cfg(feature = "cli")]
260 fn load_module_imports(&self, path: &String, global_store: &mut GlobalStore) {
261 let import_paths: Vec<String> = {
262 let current_module = match global_store.modules.get(path) {
263 Some(module) => module,
264 None => {
265 eprintln!(
266 "[warn] Cannot resolve imports: module '{}' not found in store",
267 path
268 );
269 return;
270 }
271 };
272
273 current_module
274 .statements
275 .iter()
276 .filter_map(|stmt| {
277 if let StatementKind::Import { source, .. } = &stmt.kind {
278 Some(source.clone())
279 } else {
280 None
281 }
282 })
283 .collect()
284 };
285
286 for import_path in import_paths {
287 let resolved = resolve_relative_path(path, &import_path);
288 self.load_module_recursively(&resolved, global_store);
289 }
290 }
291
292 pub fn inject_bank_triggers(
293 &self,
294 module: &mut Module,
295 bank_name: &str,
296 alias_override: Option<String>,
297 ) -> Result<Module, String> {
298 let default_alias = bank_name.split('.').last().unwrap_or(bank_name).to_string();
299 let alias_ref = alias_override.as_deref().unwrap_or(&default_alias);
300
301 let bank_path = Path::new("./.deva/bank").join(bank_name);
302 let bank_toml_path = bank_path.join("bank.toml");
303
304 if !bank_toml_path.exists() {
305 return Ok(module.clone());
306 }
307
308 let content = std::fs::read_to_string(&bank_toml_path)
309 .map_err(|e| format!("Failed to read '{}': {}", bank_toml_path.display(), e))?;
310
311 let parsed_bankfile: BankFile = toml::from_str(&content)
312 .map_err(|e| format!("Failed to parse '{}': {}", bank_toml_path.display(), e))?;
313
314 let mut bank_map = HashMap::new();
315
316 for bank_trigger in parsed_bankfile.triggers.unwrap_or_default() {
317 let trigger_name = bank_trigger.name.clone().replace("./", "");
318 let bank_trigger_path = format!("devalang://bank/{}/{}", bank_name, trigger_name);
319
320 bank_map.insert(
321 bank_trigger.name.clone(),
322 Value::String(bank_trigger_path.clone()),
323 );
324
325 if module.variable_table.variables.contains_key(alias_ref) {
326 eprintln!(
327 "⚠️ Trigger '{}' already defined in module '{}', skipping injection.",
328 alias_ref, module.path
329 );
330 continue;
331 }
332
333 module.variable_table.set(
334 format!("{}.{}", alias_ref, bank_trigger.name),
335 Value::String(bank_trigger_path.clone()),
336 );
337 }
338
339 module
341 .variable_table
342 .set(alias_ref.to_string(), Value::Map(bank_map));
343
344 Ok(module.clone())
345 }
346
347 fn extract_bank_decls(&self, statements: &[Statement]) -> Vec<(String, Option<String>)> {
348 let mut banks = Vec::new();
349
350 for stmt in statements {
351 if let StatementKind::Bank { alias } = &stmt.kind {
352 let name_opt = match &stmt.value {
353 Value::String(s) => Some(s.clone()),
354 Value::Identifier(s) => Some(s.clone()),
355 Value::Number(n) => Some(n.to_string()),
356 _ => None,
357 };
358 if let Some(name) = name_opt {
359 banks.push((name, alias.clone()));
360 }
361 }
362 }
363
364 banks
365 }
366
367 fn extract_plugin_uses(&self, statements: &[Statement]) -> Vec<(String, String)> {
368 let mut plugins = Vec::new();
369
370 for stmt in statements {
371 if let StatementKind::Use { name, alias } = &stmt.kind {
372 let alias_name = alias
373 .clone()
374 .unwrap_or_else(|| name.split('.').last().unwrap_or(name).to_string());
375 plugins.push((name.clone(), alias_name));
376 }
377 }
378
379 plugins
380 }
381
382 fn load_plugin_and_register(
383 &self,
384 module: &mut Module,
385 plugin_name: &str,
386 alias: &str,
387 global_store: &mut GlobalStore,
388 ) {
389 let mut parts = plugin_name.split('.');
391 let author = match parts.next() {
392 Some(a) if !a.is_empty() => a,
393 _ => {
394 eprintln!("Invalid plugin name '{}': missing author", plugin_name);
395 return;
396 }
397 };
398 let name = match parts.next() {
399 Some(n) if !n.is_empty() => n,
400 _ => {
401 eprintln!("Invalid plugin name '{}': missing name", plugin_name);
402 return;
403 }
404 };
405 if parts.next().is_some() {
406 eprintln!(
407 "Invalid plugin name '{}': expected <author>.<name>",
408 plugin_name
409 );
410 return;
411 }
412
413 let expected_uri = format!("devalang://plugin/{}.{}", author, name);
416
417 let root = Path::new("./.deva");
419 let plugin_dir_preferred = root.join("plugin").join(format!("{}.{}", author, name));
420 let toml_path_preferred = plugin_dir_preferred.join("plugin.toml");
421 let plugin_dir_fallback = root.join("plugin").join(author).join(name);
422 let toml_path_fallback = plugin_dir_fallback.join("plugin.toml");
423 let exists_locally = toml_path_preferred.exists() || toml_path_fallback.exists();
424
425 if exists_locally {
426 let cfg_opt = load_config(None);
428 let mut declared = false;
429 if let Some(cfg) = cfg_opt {
430 if let Some(list) = cfg.plugins {
431 declared = list.iter().any(|p| p.path == expected_uri);
432 }
433 }
434 if !declared {
435 module.statements.push(Statement {
437 kind: StatementKind::Error {
438 message: "plugin present in local files but missing in .devalang config"
439 .to_string(),
440 },
441 value: Value::Null,
442 indent: 0,
443 line: 0,
444 column: 0,
445 });
446 return;
447 }
448 }
449
450 match load_plugin(author, name) {
451 Ok((info, wasm)) => {
452 let uri = format!("devalang://plugin/{}.{}", author, name);
453 global_store
454 .plugins
455 .insert(format!("{}:{}", author, name), (info, wasm));
456 module
458 .variable_table
459 .set(alias.to_string(), Value::String(uri.clone()));
460 if let Some((plugin_info, _)) =
461 global_store.plugins.get(&format!("{}:{}", author, name))
462 {
463 for exp in &plugin_info.exports {
464 match exp.kind.as_str() {
465 "number" => {
466 if let Some(toml::Value::String(s)) = &exp.default {
467 if let Ok(n) = s.parse::<f32>() {
468 module.variable_table.set(
469 format!("{}.{}", alias, exp.name),
470 Value::Number(n),
471 );
472 }
473 } else if let Some(toml::Value::Integer(i)) = &exp.default {
474 module.variable_table.set(
475 format!("{}.{}", alias, exp.name),
476 Value::Number(*i as f32),
477 );
478 } else if let Some(toml::Value::Float(f)) = &exp.default {
479 module.variable_table.set(
480 format!("{}.{}", alias, exp.name),
481 Value::Number(*f as f32),
482 );
483 }
484 }
485 "string" => {
486 if let Some(toml::Value::String(s)) = &exp.default {
487 module.variable_table.set(
488 format!("{}.{}", alias, exp.name),
489 Value::String(s.clone()),
490 );
491 }
492 }
493 "bool" => {
494 if let Some(toml::Value::Boolean(b)) = &exp.default {
495 module
496 .variable_table
497 .set(format!("{}.{}", alias, exp.name), Value::Boolean(*b));
498 }
499 }
500 "synth" => {
501 module.variable_table.set(
503 format!("{}.{}", alias, exp.name),
504 Value::String(format!("{}.{}", alias, exp.name)),
505 );
506 }
507 _ => {}
508 }
509 }
510 }
511 }
512 Err(e) => eprintln!("Failed to load plugin {}: {}", plugin_name, e),
513 }
514 }
515}