@devaloop/devalang 0.0.1-beta.2 → 0.0.1-beta.3

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 (159) hide show
  1. package/Cargo.toml +84 -81
  2. package/README.md +3 -2
  3. package/docs/CHANGELOG.md +41 -0
  4. package/docs/ROADMAP.md +3 -3
  5. package/examples/chain.deva +19 -0
  6. package/examples/plugin.deva +10 -10
  7. package/examples/routing.deva +23 -0
  8. package/out-tsc/bin/project-version.json +6 -0
  9. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +8 -8
  10. package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
  11. package/out-tsc/scripts/version/copy-to-binary.js +79 -0
  12. package/package.json +23 -10
  13. package/project-version.json +3 -3
  14. package/rust/bindings/Cargo.toml +9 -0
  15. package/rust/bindings/src/lib.rs +86 -0
  16. package/rust/cli/addon/commands.rs +35 -0
  17. package/rust/cli/addon/download.rs +234 -0
  18. package/rust/cli/addon/install.rs +33 -0
  19. package/rust/cli/addon/list.rs +224 -0
  20. package/rust/cli/addon/metadata.rs +124 -0
  21. package/rust/cli/addon/mod.rs +8 -0
  22. package/rust/cli/addon/remove.rs +271 -0
  23. package/rust/cli/addon/update.rs +305 -0
  24. package/rust/cli/{install/addon.rs → addon/utils.rs} +109 -118
  25. package/rust/cli/build/commands.rs +153 -153
  26. package/rust/cli/build/process.rs +165 -165
  27. package/rust/cli/check/mod.rs +208 -208
  28. package/rust/cli/discover/commands.rs +275 -253
  29. package/rust/cli/discover/config.rs +109 -111
  30. package/rust/cli/discover/fs.rs +19 -19
  31. package/rust/cli/discover/install.rs +214 -103
  32. package/rust/cli/discover/metadata.rs +48 -48
  33. package/rust/cli/discover/mod.rs +5 -5
  34. package/rust/cli/me/commands.rs +52 -0
  35. package/rust/cli/me/mod.rs +1 -0
  36. package/rust/cli/mod.rs +12 -12
  37. package/rust/cli/parser.rs +30 -69
  38. package/rust/cli/play/commands.rs +375 -375
  39. package/rust/cli/play/process.rs +159 -159
  40. package/rust/core/audio/engine/driver.rs +19 -2
  41. package/rust/core/audio/engine/export.rs +169 -169
  42. package/rust/core/audio/engine/mod.rs +56 -56
  43. package/rust/core/audio/engine/notes/dsp.rs +88 -85
  44. package/rust/core/audio/engine/notes/mod.rs +53 -44
  45. package/rust/core/audio/engine/notes/params.rs +294 -294
  46. package/rust/core/audio/engine/sample/insert.rs +148 -47
  47. package/rust/core/audio/engine/sample/mod.rs +40 -40
  48. package/rust/core/audio/engine/sample/padding.rs +170 -170
  49. package/rust/core/audio/evaluator/condition.rs +61 -61
  50. package/rust/core/audio/evaluator/numeric.rs +152 -152
  51. package/rust/core/audio/evaluator/rhs.rs +16 -16
  52. package/rust/core/audio/evaluator/string_expr.rs +94 -94
  53. package/rust/core/audio/interpreter/driver.rs +574 -574
  54. package/rust/core/audio/interpreter/mod.rs +2 -2
  55. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +9 -5
  56. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -384
  57. package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
  58. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +1 -0
  59. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +66 -11
  60. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -3
  61. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -192
  62. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -24
  63. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -116
  64. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -97
  65. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -100
  66. package/rust/core/audio/interpreter/statements/automate.rs +16 -16
  67. package/rust/core/audio/interpreter/statements/call.rs +31 -1
  68. package/rust/core/audio/interpreter/statements/condition.rs +72 -72
  69. package/rust/core/audio/interpreter/statements/function.rs +24 -24
  70. package/rust/core/audio/interpreter/statements/let_.rs +36 -36
  71. package/rust/core/audio/interpreter/statements/load.rs +17 -17
  72. package/rust/core/audio/interpreter/statements/loop_.rs +115 -115
  73. package/rust/core/audio/interpreter/statements/spawn.rs +51 -2
  74. package/rust/core/audio/interpreter/statements/trigger.rs +242 -239
  75. package/rust/core/audio/loader/trigger.rs +98 -98
  76. package/rust/core/audio/player.rs +70 -70
  77. package/rust/core/audio/special/mod.rs +9 -9
  78. package/rust/core/builder/mod.rs +129 -129
  79. package/rust/core/debugger/lexer.rs +27 -27
  80. package/rust/core/debugger/logs.rs +52 -52
  81. package/rust/core/debugger/preprocessor.rs +27 -27
  82. package/rust/core/debugger/store.rs +38 -38
  83. package/rust/core/lexer/driver.rs +59 -59
  84. package/rust/core/lexer/handler/arrow.rs +82 -82
  85. package/rust/core/lexer/handler/at.rs +21 -21
  86. package/rust/core/lexer/handler/brace.rs +41 -41
  87. package/rust/core/lexer/handler/colon.rs +21 -21
  88. package/rust/core/lexer/handler/comment.rs +30 -30
  89. package/rust/core/lexer/handler/dot.rs +21 -21
  90. package/rust/core/lexer/handler/driver.rs +337 -337
  91. package/rust/core/lexer/handler/identifier.rs +47 -47
  92. package/rust/core/lexer/handler/indent.rs +66 -66
  93. package/rust/core/lexer/handler/mod.rs +15 -15
  94. package/rust/core/lexer/handler/newline.rs +23 -23
  95. package/rust/core/lexer/handler/number.rs +31 -31
  96. package/rust/core/lexer/handler/operator.rs +46 -46
  97. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  98. package/rust/core/lexer/handler/slash.rs +21 -21
  99. package/rust/core/lexer/handler/string.rs +63 -63
  100. package/rust/core/lexer/mod.rs +3 -3
  101. package/rust/core/mod.rs +9 -9
  102. package/rust/core/parser/driver/block.rs +111 -111
  103. package/rust/core/parser/driver/cursor.rs +82 -82
  104. package/rust/core/parser/driver/driver_impl.rs +21 -1
  105. package/rust/core/parser/driver/mod.rs +6 -6
  106. package/rust/core/parser/driver/parse_array.rs +120 -120
  107. package/rust/core/parser/driver/parse_map.rs +247 -223
  108. package/rust/core/parser/driver/parser.rs +160 -160
  109. package/rust/core/parser/handler/arrow_call.rs +65 -14
  110. package/rust/core/parser/handler/identifier/synth.rs +171 -135
  111. package/rust/core/parser/handler/mod.rs +9 -9
  112. package/rust/core/parser/handler/pattern.rs +24 -1
  113. package/rust/core/plugin/loader.rs +137 -137
  114. package/rust/core/plugin/mod.rs +2 -2
  115. package/rust/core/plugin/runner/non_wasm.rs +481 -297
  116. package/rust/core/plugin/runner/wasm32.rs +1 -0
  117. package/rust/core/preprocessor/loader/inject.rs +313 -278
  118. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -110
  119. package/rust/core/preprocessor/loader/mod.rs +235 -235
  120. package/rust/core/preprocessor/module.rs +55 -55
  121. package/rust/core/preprocessor/processor/handlers.rs +107 -107
  122. package/rust/core/preprocessor/resolver/bank.rs +49 -49
  123. package/rust/core/preprocessor/resolver/call.rs +124 -124
  124. package/rust/core/preprocessor/resolver/condition.rs +95 -95
  125. package/rust/core/preprocessor/resolver/driver.rs +324 -324
  126. package/rust/core/preprocessor/resolver/function.rs +69 -69
  127. package/rust/core/preprocessor/resolver/group.rs +122 -122
  128. package/rust/core/preprocessor/resolver/let_.rs +32 -32
  129. package/rust/core/preprocessor/resolver/loop_.rs +318 -318
  130. package/rust/core/preprocessor/resolver/mod.rs +16 -16
  131. package/rust/core/preprocessor/resolver/pattern.rs +95 -83
  132. package/rust/core/preprocessor/resolver/spawn.rs +99 -99
  133. package/rust/core/preprocessor/resolver/synth.rs +54 -54
  134. package/rust/core/preprocessor/resolver/tempo.rs +48 -48
  135. package/rust/core/preprocessor/resolver/trigger.rs +116 -116
  136. package/rust/core/preprocessor/resolver/value.rs +176 -176
  137. package/rust/core/store/global.rs +57 -57
  138. package/rust/lib.rs +323 -323
  139. package/rust/macros/Cargo.toml +14 -0
  140. package/rust/macros/src/lib.rs +52 -0
  141. package/rust/main.rs +311 -142
  142. package/rust/types/Cargo.toml +1 -1
  143. package/rust/types/src/addons.rs +3 -1
  144. package/rust/types/src/config.rs +1 -3
  145. package/rust/utils/Cargo.toml +5 -2
  146. package/rust/utils/src/file.rs +397 -14
  147. package/rust/utils/src/path.rs +31 -2
  148. package/rust/utils/src/version.rs +38 -7
  149. package/rust/web/auth.rs +5 -0
  150. package/rust/web/forge.rs +5 -0
  151. package/rust/web/mod.rs +5 -3
  152. package/typescript/scripts/version/copy-to-binary.ts +82 -0
  153. package/rust/cli/bank/api.rs +0 -122
  154. package/rust/cli/bank/commands.rs +0 -306
  155. package/rust/cli/bank/mod.rs +0 -29
  156. package/rust/cli/install/bank.rs +0 -72
  157. package/rust/cli/install/commands.rs +0 -35
  158. package/rust/cli/install/mod.rs +0 -4
  159. package/rust/cli/install/plugin.rs +0 -80
@@ -37,6 +37,7 @@ impl WasmPluginRunner {
37
37
  _channels: i32,
38
38
  _params_num: &HashMap<String, f32>,
39
39
  _params_str: Option<&HashMap<String, String>>,
40
+ _exported_names: Option<&[String]>,
40
41
  ) -> Result<(), String> {
41
42
  Err("Wasm plugin rendering is not available in wasm builds".to_string())
42
43
  }
@@ -1,278 +1,313 @@
1
- use crate::core::parser::statement::Statement;
2
- use crate::core::plugin::loader::load_plugin;
3
- use crate::core::preprocessor::module::Module;
4
- use crate::core::store::global::GlobalStore;
5
- use devalang_types::Value;
6
- use std::{collections::HashMap, path::Path};
7
-
8
- pub fn inject_bank_triggers(
9
- module: &mut Module,
10
- bank_name: &str,
11
- alias_override: Option<String>,
12
- ) -> Result<(), String> {
13
- let default_alias = bank_name
14
- .split('.')
15
- .next_back()
16
- .unwrap_or(bank_name)
17
- .to_string();
18
- let alias_ref = alias_override.as_deref().unwrap_or(&default_alias);
19
-
20
- let bank_path = match devalang_utils::path::get_deva_dir() {
21
- Ok(dir) => dir.join("banks").join(bank_name),
22
- Err(_) => Path::new("./.deva").join("banks").join(bank_name),
23
- };
24
- let bank_toml_path = bank_path.join("bank.toml");
25
-
26
- if !bank_toml_path.exists() {
27
- return Ok(());
28
- }
29
-
30
- let content = std::fs::read_to_string(&bank_toml_path)
31
- .map_err(|e| format!("Failed to read '{}': {}", bank_toml_path.display(), e))?;
32
-
33
- let parsed_bankfile: devalang_types::BankFile = toml::from_str(&content)
34
- .map_err(|e| format!("Failed to parse '{}': {}", bank_toml_path.display(), e))?;
35
-
36
- let mut bank_map = HashMap::new();
37
-
38
- for bank_trigger in parsed_bankfile.triggers.unwrap_or_default() {
39
- let entity_ref = bank_trigger
40
- .path
41
- .clone()
42
- .replace("\\", "/")
43
- .replace("./", "");
44
- let bank_trigger_path = format!("devalang://bank/{}/{}", bank_name, entity_ref);
45
-
46
- bank_map.insert(
47
- bank_trigger.name.clone(),
48
- Value::String(bank_trigger_path.clone()),
49
- );
50
-
51
- if module.variable_table.variables.contains_key(alias_ref) {
52
- eprintln!(
53
- "⚠️ Trigger '{}' already defined in module '{}', skipping injection.",
54
- alias_ref, module.path
55
- );
56
- continue;
57
- }
58
-
59
- module.variable_table.set(
60
- format!("{}.{}", alias_ref, bank_trigger.name),
61
- Value::String(bank_trigger_path.clone()),
62
- );
63
- }
64
-
65
- module
66
- .variable_table
67
- .set(alias_ref.to_string(), Value::Map(bank_map));
68
-
69
- Ok(())
70
- }
71
-
72
- pub fn extract_bank_decls(statements: &[Statement]) -> Vec<(String, Option<String>)> {
73
- let mut banks = Vec::new();
74
-
75
- for stmt in statements {
76
- if let crate::core::parser::statement::StatementKind::Bank { alias } = &stmt.kind {
77
- let name_opt = match &stmt.value {
78
- Value::String(s) => Some(s.clone()),
79
- Value::Identifier(s) => Some(s.clone()),
80
- Value::Number(n) => Some(n.to_string()),
81
- _ => None,
82
- };
83
- if let Some(name) = name_opt {
84
- banks.push((name, alias.clone()));
85
- }
86
- }
87
- }
88
-
89
- banks
90
- }
91
-
92
- pub fn extract_plugin_uses(statements: &[Statement]) -> Vec<(String, String)> {
93
- let mut plugins = Vec::new();
94
-
95
- for stmt in statements {
96
- if let crate::core::parser::statement::StatementKind::Use { name, alias } = &stmt.kind {
97
- let alias_name = alias
98
- .clone()
99
- .unwrap_or_else(|| name.split('.').next_back().unwrap_or(name).to_string());
100
- plugins.push((name.clone(), alias_name));
101
- }
102
- }
103
-
104
- plugins
105
- }
106
-
107
- pub fn load_plugin_and_register(
108
- module: &mut Module,
109
- plugin_name: &str,
110
- alias: &str,
111
- global_store: &mut GlobalStore,
112
- ) {
113
- let mut parts = plugin_name.split('.');
114
- let author = match parts.next() {
115
- Some(a) if !a.is_empty() => a,
116
- _ => {
117
- eprintln!("Invalid plugin name '{}': missing author", plugin_name);
118
- return;
119
- }
120
- };
121
- let name = match parts.next() {
122
- Some(n) if !n.is_empty() => n,
123
- _ => {
124
- eprintln!("Invalid plugin name '{}': missing name", plugin_name);
125
- return;
126
- }
127
- };
128
- if parts.next().is_some() {
129
- eprintln!(
130
- "Invalid plugin name '{}': expected <author>.<name>",
131
- plugin_name
132
- );
133
- return;
134
- }
135
-
136
- let expected_uri = format!("devalang://plugin/{}.{}", author, name);
137
-
138
- let root = match devalang_utils::path::get_deva_dir() {
139
- Ok(dir) => dir,
140
- Err(_) => Path::new("./.deva").to_path_buf(),
141
- };
142
- let plugin_dir_preferred = root.join("plugins").join(format!("{}.{}", author, name));
143
- let toml_path_preferred = plugin_dir_preferred.join("plugin.toml");
144
- let plugin_dir_fallback = root.join("plugins").join(author).join(name);
145
- let toml_path_fallback = plugin_dir_fallback.join("plugin.toml");
146
- let exists_locally = toml_path_preferred.exists() || toml_path_fallback.exists();
147
-
148
- if exists_locally {
149
- let cfg_opt = crate::config::ops::load_config(None);
150
- let mut declared = false;
151
- if let Some(cfg) = cfg_opt {
152
- if let Some(list) = cfg.plugins {
153
- declared = list.iter().any(|p| p.path == expected_uri);
154
- }
155
- }
156
- if !declared {
157
- module
158
- .statements
159
- .push(crate::core::parser::statement::Statement {
160
- kind: crate::core::parser::statement::StatementKind::Error {
161
- message: "plugin present in local files but missing in .devalang config"
162
- .to_string(),
163
- },
164
- value: Value::Null,
165
- indent: 0,
166
- line: 0,
167
- column: 0,
168
- });
169
- return;
170
- }
171
- }
172
-
173
- match load_plugin(author, name) {
174
- Ok((info, wasm)) => {
175
- let uri = format!("devalang://plugin/{}.{}", author, name);
176
- global_store
177
- .plugins
178
- .insert(format!("{}:{}", author, name), (info, wasm));
179
- module
180
- .variable_table
181
- .set(alias.to_string(), Value::String(uri.clone()));
182
- global_store
183
- .variables
184
- .set(alias.to_string(), Value::String(uri.clone()));
185
-
186
- if let Some((plugin_info, _)) =
187
- global_store.plugins.get(&format!("{}:{}", author, name))
188
- {
189
- for exp in &plugin_info.exports {
190
- match exp.kind.as_str() {
191
- "number" => {
192
- if let Some(toml::Value::String(s)) = &exp.default {
193
- if let Ok(n) = s.parse::<f32>() {
194
- module
195
- .variable_table
196
- .set(format!("{}.{}", alias, exp.name), Value::Number(n));
197
- }
198
- } else if let Some(toml::Value::Integer(i)) = &exp.default {
199
- module.variable_table.set(
200
- format!("{}.{}", alias, exp.name),
201
- Value::Number(*i as f32),
202
- );
203
- } else if let Some(toml::Value::Float(f)) = &exp.default {
204
- module.variable_table.set(
205
- format!("{}.{}", alias, exp.name),
206
- Value::Number(*f as f32),
207
- );
208
- }
209
- }
210
- "string" => {
211
- if let Some(toml::Value::String(s)) = &exp.default {
212
- module.variable_table.set(
213
- format!("{}.{}", alias, exp.name),
214
- Value::String(s.clone()),
215
- );
216
- }
217
- }
218
- "bool" => {
219
- if let Some(toml::Value::Boolean(b)) = &exp.default {
220
- module
221
- .variable_table
222
- .set(format!("{}.{}", alias, exp.name), Value::Boolean(*b));
223
- }
224
- }
225
- "synth" => {
226
- module.variable_table.set(
227
- format!("{}.{}", alias, exp.name),
228
- Value::String(format!("{}.{}", alias, exp.name)),
229
- );
230
- }
231
- _ => {
232
- if let Some(def) = &exp.default {
233
- let val = match def {
234
- toml::Value::String(s) => Value::String(s.clone()),
235
- toml::Value::Integer(i) => Value::Number(*i as f32),
236
- toml::Value::Float(f) => Value::Number(*f as f32),
237
- toml::Value::Boolean(b) => Value::Boolean(*b),
238
- toml::Value::Array(arr) => Value::Array(
239
- arr.iter()
240
- .map(|v| match v {
241
- toml::Value::String(s) => Value::String(s.clone()),
242
- toml::Value::Integer(i) => Value::Number(*i as f32),
243
- toml::Value::Float(f) => Value::Number(*f as f32),
244
- toml::Value::Boolean(b) => Value::Boolean(*b),
245
- _ => Value::Null,
246
- })
247
- .collect(),
248
- ),
249
- toml::Value::Table(t) => {
250
- let mut m = std::collections::HashMap::new();
251
- for (k, v) in t.iter() {
252
- let vv = match v {
253
- toml::Value::String(s) => Value::String(s.clone()),
254
- toml::Value::Integer(i) => Value::Number(*i as f32),
255
- toml::Value::Float(f) => Value::Number(*f as f32),
256
- toml::Value::Boolean(b) => Value::Boolean(*b),
257
- _ => Value::Null,
258
- };
259
- m.insert(k.clone(), vv);
260
- }
261
- Value::Map(m)
262
- }
263
- _ => Value::Null,
264
- };
265
- if val != Value::Null {
266
- module
267
- .variable_table
268
- .set(format!("{}.{}", alias, exp.name), val);
269
- }
270
- }
271
- }
272
- }
273
- }
274
- }
275
- }
276
- Err(e) => eprintln!("Failed to load plugin {}: {}", plugin_name, e),
277
- }
278
- }
1
+ use crate::core::parser::statement::Statement;
2
+ use crate::core::plugin::loader::load_plugin;
3
+ use crate::core::preprocessor::module::Module;
4
+ use crate::core::store::global::GlobalStore;
5
+ use devalang_types::Value;
6
+ use std::{collections::HashMap, path::Path};
7
+
8
+ pub fn inject_bank_triggers(
9
+ module: &mut Module,
10
+ bank_name: &str,
11
+ alias_override: Option<String>,
12
+ ) -> Result<(), String> {
13
+ let default_alias = bank_name
14
+ .split('.')
15
+ .next_back()
16
+ .unwrap_or(bank_name)
17
+ .to_string();
18
+ let alias_ref = alias_override.as_deref().unwrap_or(&default_alias);
19
+
20
+ let root = match devalang_utils::path::get_deva_dir() {
21
+ Ok(dir) => dir,
22
+ Err(_) => Path::new("./.deva").to_path_buf(),
23
+ };
24
+
25
+ // Try both plural and singular folder names and both layouts (flat and nested)
26
+ let mut parsed_bankfile_opt: Option<devalang_types::BankFile> = None;
27
+ let sds = ["banks", "bank"];
28
+ for sd in &sds {
29
+ // candidate: .deva/<sd>/<bank_name>/bank.toml (flat dir name)
30
+ let candidate1 = root.join(sd).join(bank_name).join("bank.toml");
31
+ if candidate1.exists() {
32
+ let content = std::fs::read_to_string(&candidate1)
33
+ .map_err(|e| format!("Failed to read '{}': {}", candidate1.display(), e))?;
34
+ if let Ok(parsed) = toml::from_str::<devalang_types::BankFile>(&content) {
35
+ parsed_bankfile_opt = Some(parsed);
36
+ break;
37
+ }
38
+ }
39
+
40
+ // If bank_name uses dot notation, also try nested layout: .deva/<sd>/<publisher>/<name>/bank.toml
41
+ if bank_name.contains('.') {
42
+ let mut it = bank_name.splitn(2, '.');
43
+ let pubr = it.next().unwrap_or("");
44
+ let nm = it.next().unwrap_or("");
45
+ let candidate2 = root.join(sd).join(pubr).join(nm).join("bank.toml");
46
+ if candidate2.exists() {
47
+ let content = std::fs::read_to_string(&candidate2)
48
+ .map_err(|e| format!("Failed to read '{}': {}", candidate2.display(), e))?;
49
+ if let Ok(parsed) = toml::from_str::<devalang_types::BankFile>(&content) {
50
+ parsed_bankfile_opt = Some(parsed);
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ let parsed_bankfile = match parsed_bankfile_opt {
58
+ Some(p) => p,
59
+ None => return Ok(()),
60
+ };
61
+
62
+ let mut bank_map = HashMap::new();
63
+
64
+ for bank_trigger in parsed_bankfile.triggers.unwrap_or_default() {
65
+ let entity_ref = bank_trigger
66
+ .path
67
+ .clone()
68
+ .replace("\\", "/")
69
+ .replace("./", "");
70
+ let bank_trigger_path = format!("devalang://bank/{}/{}", bank_name, entity_ref);
71
+
72
+ bank_map.insert(
73
+ bank_trigger.name.clone(),
74
+ Value::String(bank_trigger_path.clone()),
75
+ );
76
+
77
+ if module.variable_table.variables.contains_key(alias_ref) {
78
+ eprintln!(
79
+ "⚠️ Trigger '{}' already defined in module '{}', skipping injection.",
80
+ alias_ref, module.path
81
+ );
82
+ continue;
83
+ }
84
+
85
+ module.variable_table.set(
86
+ format!("{}.{}", alias_ref, bank_trigger.name),
87
+ Value::String(bank_trigger_path.clone()),
88
+ );
89
+ }
90
+
91
+ module
92
+ .variable_table
93
+ .set(alias_ref.to_string(), Value::Map(bank_map));
94
+
95
+ Ok(())
96
+ }
97
+
98
+ pub fn extract_bank_decls(statements: &[Statement]) -> Vec<(String, Option<String>)> {
99
+ let mut banks = Vec::new();
100
+
101
+ for stmt in statements {
102
+ if let crate::core::parser::statement::StatementKind::Bank { alias } = &stmt.kind {
103
+ let name_opt = match &stmt.value {
104
+ Value::String(s) => Some(s.clone()),
105
+ Value::Identifier(s) => Some(s.clone()),
106
+ Value::Number(n) => Some(n.to_string()),
107
+ _ => None,
108
+ };
109
+ if let Some(name) = name_opt {
110
+ banks.push((name, alias.clone()));
111
+ }
112
+ }
113
+ }
114
+
115
+ banks
116
+ }
117
+
118
+ pub fn extract_plugin_uses(statements: &[Statement]) -> Vec<(String, String)> {
119
+ let mut plugins = Vec::new();
120
+
121
+ for stmt in statements {
122
+ if let crate::core::parser::statement::StatementKind::Use { name, alias } = &stmt.kind {
123
+ let alias_name = alias
124
+ .clone()
125
+ .unwrap_or_else(|| name.split('.').next_back().unwrap_or(name).to_string());
126
+ plugins.push((name.clone(), alias_name));
127
+ }
128
+ }
129
+
130
+ plugins
131
+ }
132
+
133
+ pub fn load_plugin_and_register(
134
+ module: &mut Module,
135
+ plugin_name: &str,
136
+ alias: &str,
137
+ global_store: &mut GlobalStore,
138
+ ) {
139
+ let mut parts = plugin_name.split('.');
140
+ let author = match parts.next() {
141
+ Some(a) if !a.is_empty() => a,
142
+ _ => {
143
+ eprintln!("Invalid plugin name '{}': missing author", plugin_name);
144
+ return;
145
+ }
146
+ };
147
+ let name = match parts.next() {
148
+ Some(n) if !n.is_empty() => n,
149
+ _ => {
150
+ eprintln!("Invalid plugin name '{}': missing name", plugin_name);
151
+ return;
152
+ }
153
+ };
154
+ if parts.next().is_some() {
155
+ eprintln!(
156
+ "Invalid plugin name '{}': expected <author>.<name>",
157
+ plugin_name
158
+ );
159
+ return;
160
+ }
161
+
162
+ let expected_uri = format!("devalang://plugin/{}/{}", author, name);
163
+
164
+ let root = match devalang_utils::path::get_deva_dir() {
165
+ Ok(dir) => dir,
166
+ Err(_) => Path::new("./.deva").to_path_buf(),
167
+ };
168
+ // Test both 'plugins' and 'plugin' folders
169
+ let mut exists_locally = false;
170
+ for sd in &["plugins", "plugin"] {
171
+ let plugin_dir_preferred = root.join(sd).join(format!("{}/{}", author, name));
172
+ let toml_path_preferred = plugin_dir_preferred.join("plugin.toml");
173
+ let plugin_dir_fallback = root.join(sd).join(author).join(name);
174
+ let toml_path_fallback = plugin_dir_fallback.join("plugin.toml");
175
+ if toml_path_preferred.exists() || toml_path_fallback.exists() {
176
+ exists_locally = true;
177
+ break;
178
+ }
179
+ }
180
+
181
+ if exists_locally {
182
+ let cfg_opt = crate::config::ops::load_config(None);
183
+ let mut declared = false;
184
+ if let Some(cfg) = cfg_opt {
185
+ if let Some(list) = cfg.plugins {
186
+ declared = list.iter().any(|p| p.path == expected_uri);
187
+ }
188
+ }
189
+ if !declared {
190
+ module
191
+ .statements
192
+ .push(crate::core::parser::statement::Statement {
193
+ kind: crate::core::parser::statement::StatementKind::Error {
194
+ message: "plugin present in local files but missing in .devalang config"
195
+ .to_string(),
196
+ },
197
+ value: Value::Null,
198
+ indent: 0,
199
+ line: 0,
200
+ column: 0,
201
+ });
202
+ return;
203
+ }
204
+ }
205
+
206
+ match load_plugin(author, name) {
207
+ Ok((info, wasm)) => {
208
+ // keep dotted form for config/expected URIs, but inject a slash form into variables
209
+ let _uri_dot = format!("devalang://plugin/{}.{}", author, name);
210
+ let uri_inject = format!("devalang://plugin/{}/{}", author, name);
211
+ global_store
212
+ .plugins
213
+ .insert(format!("{}:{}", author, name), (info, wasm));
214
+ module
215
+ .variable_table
216
+ .set(alias.to_string(), Value::String(uri_inject.clone()));
217
+ global_store
218
+ .variables
219
+ .set(alias.to_string(), Value::String(uri_inject.clone()));
220
+
221
+ if let Some((plugin_info, _)) =
222
+ global_store.plugins.get(&format!("{}:{}", author, name))
223
+ {
224
+ for exp in &plugin_info.exports {
225
+ match exp.kind.as_str() {
226
+ "number" => {
227
+ if let Some(toml::Value::String(s)) = &exp.default {
228
+ if let Ok(n) = s.parse::<f32>() {
229
+ module
230
+ .variable_table
231
+ .set(format!("{}.{}", alias, exp.name), Value::Number(n));
232
+ }
233
+ } else if let Some(toml::Value::Integer(i)) = &exp.default {
234
+ module.variable_table.set(
235
+ format!("{}.{}", alias, exp.name),
236
+ Value::Number(*i as f32),
237
+ );
238
+ } else if let Some(toml::Value::Float(f)) = &exp.default {
239
+ module.variable_table.set(
240
+ format!("{}.{}", alias, exp.name),
241
+ Value::Number(*f as f32),
242
+ );
243
+ }
244
+ }
245
+ "string" => {
246
+ if let Some(toml::Value::String(s)) = &exp.default {
247
+ module.variable_table.set(
248
+ format!("{}.{}", alias, exp.name),
249
+ Value::String(s.clone()),
250
+ );
251
+ }
252
+ }
253
+ "bool" => {
254
+ if let Some(toml::Value::Boolean(b)) = &exp.default {
255
+ module
256
+ .variable_table
257
+ .set(format!("{}.{}", alias, exp.name), Value::Boolean(*b));
258
+ }
259
+ }
260
+ "synth" => {
261
+ module.variable_table.set(
262
+ format!("{}.{}", alias, exp.name),
263
+ Value::String(format!("{}.{}", alias, exp.name)),
264
+ );
265
+ }
266
+ _ => {
267
+ if let Some(def) = &exp.default {
268
+ let val = match def {
269
+ toml::Value::String(s) => Value::String(s.clone()),
270
+ toml::Value::Integer(i) => Value::Number(*i as f32),
271
+ toml::Value::Float(f) => Value::Number(*f as f32),
272
+ toml::Value::Boolean(b) => Value::Boolean(*b),
273
+ toml::Value::Array(arr) => Value::Array(
274
+ arr.iter()
275
+ .map(|v| match v {
276
+ toml::Value::String(s) => Value::String(s.clone()),
277
+ toml::Value::Integer(i) => Value::Number(*i as f32),
278
+ toml::Value::Float(f) => Value::Number(*f as f32),
279
+ toml::Value::Boolean(b) => Value::Boolean(*b),
280
+ _ => Value::Null,
281
+ })
282
+ .collect(),
283
+ ),
284
+ toml::Value::Table(t) => {
285
+ let mut m = std::collections::HashMap::new();
286
+ for (k, v) in t.iter() {
287
+ let vv = match v {
288
+ toml::Value::String(s) => Value::String(s.clone()),
289
+ toml::Value::Integer(i) => Value::Number(*i as f32),
290
+ toml::Value::Float(f) => Value::Number(*f as f32),
291
+ toml::Value::Boolean(b) => Value::Boolean(*b),
292
+ _ => Value::Null,
293
+ };
294
+ m.insert(k.clone(), vv);
295
+ }
296
+ Value::Map(m)
297
+ }
298
+ _ => Value::Null,
299
+ };
300
+ if val != Value::Null {
301
+ module
302
+ .variable_table
303
+ .set(format!("{}.{}", alias, exp.name), val);
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ Err(e) => eprintln!("Failed to load plugin {}: {}", plugin_name, e),
312
+ }
313
+ }