@devaloop/devalang 0.0.1-alpha.15 → 0.0.1-alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/.devalang +2 -0
  2. package/.github/workflows/ci.yml +92 -0
  3. package/Cargo.toml +60 -58
  4. package/README.md +1 -1
  5. package/docs/CHANGELOG.md +34 -1
  6. package/docs/CONTRIBUTING.md +101 -1
  7. package/docs/ROADMAP.md +1 -1
  8. package/docs/TODO.md +1 -1
  9. package/examples/automation.deva +1 -3
  10. package/examples/bank.deva +4 -4
  11. package/examples/events.deva +12 -0
  12. package/examples/function.deva +4 -4
  13. package/examples/index.deva +3 -5
  14. package/examples/loop.deva +5 -11
  15. package/examples/pattern.deva +8 -0
  16. package/examples/plugin.deva +12 -11
  17. package/examples/variables.deva +1 -1
  18. package/out-tsc/bin/index.js +51 -7
  19. package/out-tsc/index.js +3 -1
  20. package/out-tsc/scripts/postbuild.js +9 -10
  21. package/out-tsc/scripts/postinstall.js +49 -0
  22. package/package.json +12 -4
  23. package/project-version.json +3 -3
  24. package/rust/cli/bank.rs +462 -455
  25. package/rust/cli/build.rs +252 -199
  26. package/rust/cli/check.rs +221 -180
  27. package/rust/cli/driver.rs +297 -292
  28. package/rust/cli/generator.rs +1 -0
  29. package/rust/cli/init.rs +87 -79
  30. package/rust/cli/install.rs +35 -32
  31. package/rust/cli/login.rs +127 -134
  32. package/rust/cli/mod.rs +13 -11
  33. package/rust/cli/play.rs +1123 -218
  34. package/rust/cli/telemetry.rs +19 -0
  35. package/rust/cli/template.rs +69 -57
  36. package/rust/cli/update.rs +6 -4
  37. package/rust/common/api.rs +5 -5
  38. package/rust/common/mod.rs +3 -3
  39. package/rust/config/driver.rs +118 -94
  40. package/rust/config/loader.rs +165 -156
  41. package/rust/config/mod.rs +4 -2
  42. package/rust/config/settings.rs +91 -0
  43. package/rust/config/stats.rs +257 -0
  44. package/rust/core/audio/engine.rs +696 -659
  45. package/rust/core/audio/evaluator.rs +263 -132
  46. package/rust/core/audio/interpreter/arrow_call.rs +198 -187
  47. package/rust/core/audio/interpreter/call.rs +98 -95
  48. package/rust/core/audio/interpreter/condition.rs +70 -71
  49. package/rust/core/audio/interpreter/driver.rs +487 -231
  50. package/rust/core/audio/interpreter/function.rs +26 -21
  51. package/rust/core/audio/interpreter/let_.rs +38 -26
  52. package/rust/core/audio/interpreter/load.rs +18 -18
  53. package/rust/core/audio/interpreter/loop_.rs +113 -106
  54. package/rust/core/audio/interpreter/mod.rs +14 -14
  55. package/rust/core/audio/interpreter/sleep.rs +27 -28
  56. package/rust/core/audio/interpreter/spawn.rs +105 -102
  57. package/rust/core/audio/interpreter/tempo.rs +19 -16
  58. package/rust/core/audio/interpreter/trigger.rs +239 -210
  59. package/rust/core/audio/loader/mod.rs +1 -1
  60. package/rust/core/audio/loader/trigger.rs +100 -94
  61. package/rust/core/audio/mod.rs +7 -7
  62. package/rust/core/audio/player.rs +64 -64
  63. package/rust/core/audio/renderer.rs +56 -53
  64. package/rust/core/audio/special/easing.rs +189 -120
  65. package/rust/core/audio/special/env.rs +43 -41
  66. package/rust/core/audio/special/math.rs +102 -92
  67. package/rust/core/audio/special/mod.rs +9 -9
  68. package/rust/core/audio/special/modulator.rs +143 -120
  69. package/rust/core/builder/mod.rs +80 -85
  70. package/rust/core/debugger/lexer.rs +27 -27
  71. package/rust/core/debugger/mod.rs +24 -23
  72. package/rust/core/debugger/module.rs +55 -47
  73. package/rust/core/debugger/preprocessor.rs +27 -27
  74. package/rust/core/debugger/store.rs +40 -39
  75. package/rust/core/error/mod.rs +80 -69
  76. package/rust/core/lexer/handler/arrow.rs +82 -82
  77. package/rust/core/lexer/handler/at.rs +21 -21
  78. package/rust/core/lexer/handler/brace.rs +41 -41
  79. package/rust/core/lexer/handler/colon.rs +21 -21
  80. package/rust/core/lexer/handler/comment.rs +30 -30
  81. package/rust/core/lexer/handler/dot.rs +21 -21
  82. package/rust/core/lexer/handler/driver.rs +337 -292
  83. package/rust/core/lexer/handler/identifier.rs +46 -43
  84. package/rust/core/lexer/handler/indent.rs +66 -66
  85. package/rust/core/lexer/handler/mod.rs +16 -16
  86. package/rust/core/lexer/handler/newline.rs +23 -23
  87. package/rust/core/lexer/handler/number.rs +31 -31
  88. package/rust/core/lexer/handler/operator.rs +46 -46
  89. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  90. package/rust/core/lexer/handler/slash.rs +21 -21
  91. package/rust/core/lexer/handler/string.rs +63 -63
  92. package/rust/core/lexer/mod.rs +54 -51
  93. package/rust/core/lexer/token.rs +97 -94
  94. package/rust/core/mod.rs +11 -11
  95. package/rust/core/parser/driver.rs +513 -490
  96. package/rust/core/parser/handler/arrow_call.rs +233 -227
  97. package/rust/core/parser/handler/at.rs +245 -162
  98. package/rust/core/parser/handler/bank.rs +94 -69
  99. package/rust/core/parser/handler/condition.rs +80 -74
  100. package/rust/core/parser/handler/dot.rs +143 -135
  101. package/rust/core/parser/handler/identifier/automate.rs +257 -194
  102. package/rust/core/parser/handler/identifier/call.rs +91 -88
  103. package/rust/core/parser/handler/identifier/emit.rs +66 -0
  104. package/rust/core/parser/handler/identifier/function.rs +100 -91
  105. package/rust/core/parser/handler/identifier/group.rs +85 -75
  106. package/rust/core/parser/handler/identifier/let_.rs +158 -143
  107. package/rust/core/parser/handler/identifier/mod.rs +54 -56
  108. package/rust/core/parser/handler/identifier/on.rs +98 -0
  109. package/rust/core/parser/handler/identifier/print.rs +52 -29
  110. package/rust/core/parser/handler/identifier/sleep.rs +36 -33
  111. package/rust/core/parser/handler/identifier/spawn.rs +91 -88
  112. package/rust/core/parser/handler/identifier/synth.rs +65 -63
  113. package/rust/core/parser/handler/loop_.rs +170 -89
  114. package/rust/core/parser/handler/mod.rs +8 -8
  115. package/rust/core/parser/handler/tempo.rs +53 -47
  116. package/rust/core/parser/mod.rs +4 -4
  117. package/rust/core/parser/statement.rs +142 -113
  118. package/rust/core/plugin/loader.rs +123 -48
  119. package/rust/core/plugin/mod.rs +2 -1
  120. package/rust/core/plugin/runner.rs +296 -0
  121. package/rust/core/preprocessor/loader.rs +515 -326
  122. package/rust/core/preprocessor/mod.rs +4 -4
  123. package/rust/core/preprocessor/module.rs +60 -58
  124. package/rust/core/preprocessor/processor.rs +99 -101
  125. package/rust/core/preprocessor/resolver/bank.rs +51 -48
  126. package/rust/core/preprocessor/resolver/call.rs +100 -101
  127. package/rust/core/preprocessor/resolver/condition.rs +97 -97
  128. package/rust/core/preprocessor/resolver/driver.rs +310 -280
  129. package/rust/core/preprocessor/resolver/function.rs +69 -68
  130. package/rust/core/preprocessor/resolver/group.rs +96 -91
  131. package/rust/core/preprocessor/resolver/let_.rs +32 -28
  132. package/rust/core/preprocessor/resolver/loop_.rs +320 -121
  133. package/rust/core/preprocessor/resolver/mod.rs +15 -15
  134. package/rust/core/preprocessor/resolver/spawn.rs +76 -73
  135. package/rust/core/preprocessor/resolver/synth.rs +56 -50
  136. package/rust/core/preprocessor/resolver/tempo.rs +50 -49
  137. package/rust/core/preprocessor/resolver/trigger.rs +113 -115
  138. package/rust/core/preprocessor/resolver/value.rs +81 -81
  139. package/rust/core/shared/duration.rs +9 -9
  140. package/rust/core/shared/mod.rs +3 -3
  141. package/rust/core/shared/value.rs +35 -32
  142. package/rust/core/store/function.rs +34 -34
  143. package/rust/core/store/global.rs +55 -38
  144. package/rust/core/store/mod.rs +5 -5
  145. package/rust/core/store/variable.rs +37 -34
  146. package/rust/core/utils/mod.rs +2 -2
  147. package/rust/core/utils/path.rs +37 -31
  148. package/rust/core/utils/validation.rs +35 -36
  149. package/rust/installer/addon.rs +84 -80
  150. package/rust/installer/bank.rs +62 -65
  151. package/rust/installer/mod.rs +5 -5
  152. package/rust/installer/plugin.rs +54 -55
  153. package/rust/installer/utils.rs +56 -56
  154. package/rust/lib.rs +156 -164
  155. package/rust/main.rs +250 -144
  156. package/rust/utils/error.rs +200 -51
  157. package/rust/utils/file.rs +38 -35
  158. package/rust/utils/first_usage.rs +76 -0
  159. package/rust/utils/logger.rs +195 -143
  160. package/rust/utils/mod.rs +9 -7
  161. package/rust/utils/signature.rs +19 -17
  162. package/rust/utils/spinner.rs +22 -19
  163. package/rust/utils/telemetry.rs +292 -0
  164. package/rust/utils/watcher.rs +34 -33
  165. package/templates/minimal/README.md +97 -121
  166. package/templates/welcome/README.md +97 -121
  167. package/typescript/bin/index.ts +19 -5
  168. package/typescript/index.ts +3 -1
  169. package/typescript/scripts/postbuild.ts +10 -6
  170. package/typescript/scripts/postinstall.ts +56 -0
  171. package/typescript/scripts/version/bump.ts +0 -1
  172. package/typescript/scripts/version/index.ts +0 -1
  173. package/out-tsc/bin/devalang.exe +0 -0
@@ -1,326 +1,515 @@
1
- use std::{ collections::{ HashMap, HashSet }, path::Path };
2
- use 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
- };
14
- use crate::core::preprocessor::resolver::driver::{
15
- resolve_all_modules,
16
- resolve_and_flatten_all_modules,
17
- };
18
- use crate::core::utils::path::resolve_relative_path;
19
-
20
- pub struct ModuleLoader {
21
- pub entry: String,
22
- pub output: String,
23
- pub base_dir: String,
24
- }
25
-
26
- impl 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
- }
1
+ use crate::core::preprocessor::resolver::driver::{
2
+ resolve_all_modules, resolve_and_flatten_all_modules,
3
+ };
4
+ use crate::core::utils::path::resolve_relative_path;
5
+ use 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
+ };
22
+ use std::{collections::HashMap, path::Path};
23
+
24
+ pub struct ModuleLoader {
25
+ pub entry: String,
26
+ pub output: String,
27
+ pub base_dir: String,
28
+ }
29
+
30
+ impl 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
+ // SECTION Lexing the module content
83
+ 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
+ // SECTION Parsing tokens into statements
91
+ 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
+ // SECTION Injecting bank triggers if any (legacy default for single-module run)
97
+ 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
+ // SECTION Error handling
110
+ 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
+ // Step one : Load the module from the global store
118
+ 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
+ // Step two : lexing
128
+ let lexer = Lexer::new();
129
+ let tokens = lexer
130
+ .lex_from_source(&module.content)
131
+ .map_err(|e| format!("Lexer failed: {}", e))?;
132
+
133
+ // Step three : parsing
134
+ 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
+ // Step four : Injecting bank triggers if any
144
+ 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
+ // Step four : error handling
153
+ let mut error_handler = ErrorHandler::new();
154
+ error_handler.detect_from_statements(&mut parser, &updated_module.statements);
155
+
156
+ // Final step : insert the updated module back into the global store
157
+ 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
+ // SECTION Load the entry module and its dependencies
170
+ let tokens_by_module = self.load_module_recursively(&self.entry, global_store);
171
+
172
+ // SECTION Process and resolve modules
173
+ process_modules(self, global_store);
174
+ resolve_all_modules(self, global_store);
175
+
176
+ // SECTION Flatten all modules to get statements (+ injects)
177
+ 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
+ // Check if already loaded
191
+ 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
+ // Insert module into store
204
+ let mut module = Module::new(&path);
205
+ module.tokens = tokens.clone();
206
+ module.statements = statements.clone();
207
+
208
+ // Inject triggers for each bank used in module, respecting aliases
209
+ 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
+ // Inject module variables and functions into global store
220
+ 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
+ // Inject the module into the global store
230
+ global_store.insert_module(path.clone(), module);
231
+
232
+ // Load dependencies
233
+ self.load_module_imports(&path, global_store);
234
+
235
+ // Error handling (use the module now in the store to include injected errors)
236
+ 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, &current_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
+ // Return tokens per module
252
+ 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
+ // Inject the map under the bank name
340
+ 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
+ // plugin_name expected format: "author.name"
390
+ 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
+ // Enforce presence in .devalang config when plugin exists locally
414
+ // Build expected URI from author/name
415
+ let expected_uri = format!("devalang://plugin/{}.{}", author, name);
416
+
417
+ // Detect local presence (preferred and legacy layouts)
418
+ 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
+ // Load config and verify plugin is declared
427
+ 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
+ // Inject a single, clear error into the module so it is reported once by the error handler
436
+ 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
+ // Set alias to URI, and inject exported variables
457
+ 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
+ // Provide a discoverable marker: alias.<synthName> resolves to alias.synthName waveform string
502
+ 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
+ }