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

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 (110) hide show
  1. package/.devalang +2 -3
  2. package/Cargo.toml +58 -54
  3. package/README.md +59 -27
  4. package/docs/CHANGELOG.md +99 -2
  5. package/docs/CONTRIBUTING.md +1 -0
  6. package/docs/ROADMAP.md +3 -3
  7. package/docs/TODO.md +5 -4
  8. package/examples/automation.deva +44 -0
  9. package/examples/bank.deva +2 -4
  10. package/examples/function.deva +15 -0
  11. package/examples/index.deva +41 -11
  12. package/examples/plugin.deva +15 -0
  13. package/out-tsc/bin/devalang.exe +0 -0
  14. package/package.json +6 -6
  15. package/project-version.json +3 -3
  16. package/rust/cli/bank.rs +16 -16
  17. package/rust/cli/build.rs +69 -30
  18. package/rust/cli/check.rs +46 -6
  19. package/rust/cli/driver.rs +40 -28
  20. package/rust/cli/install.rs +22 -7
  21. package/rust/cli/login.rs +134 -0
  22. package/rust/cli/mod.rs +2 -1
  23. package/rust/cli/play.rs +44 -19
  24. package/rust/cli/update.rs +1 -1
  25. package/rust/common/api.rs +5 -0
  26. package/rust/common/cdn.rs +3 -9
  27. package/rust/common/mod.rs +3 -1
  28. package/rust/common/sso.rs +5 -0
  29. package/rust/config/driver.rs +19 -1
  30. package/rust/config/loader.rs +56 -10
  31. package/rust/core/audio/engine.rs +314 -63
  32. package/rust/core/audio/evaluator.rs +101 -0
  33. package/rust/core/audio/interpreter/arrow_call.rs +60 -15
  34. package/rust/core/audio/interpreter/automate.rs +18 -0
  35. package/rust/core/audio/interpreter/call.rs +4 -4
  36. package/rust/core/audio/interpreter/condition.rs +3 -3
  37. package/rust/core/audio/interpreter/driver.rs +68 -30
  38. package/rust/core/audio/interpreter/let_.rs +14 -7
  39. package/rust/core/audio/interpreter/loop_.rs +39 -6
  40. package/rust/core/audio/interpreter/mod.rs +2 -1
  41. package/rust/core/audio/interpreter/sleep.rs +2 -4
  42. package/rust/core/audio/interpreter/spawn.rs +4 -4
  43. package/rust/core/audio/loader/trigger.rs +2 -5
  44. package/rust/core/audio/mod.rs +2 -1
  45. package/rust/core/audio/renderer.rs +1 -1
  46. package/rust/core/audio/special/easing.rs +120 -0
  47. package/rust/core/audio/special/env.rs +41 -0
  48. package/rust/core/audio/special/math.rs +92 -0
  49. package/rust/core/audio/special/mod.rs +9 -0
  50. package/rust/core/audio/special/modulator.rs +120 -0
  51. package/rust/core/builder/mod.rs +11 -6
  52. package/rust/core/debugger/store.rs +1 -1
  53. package/rust/core/error/mod.rs +4 -1
  54. package/rust/core/lexer/handler/arrow.rs +60 -9
  55. package/rust/core/lexer/handler/at.rs +4 -4
  56. package/rust/core/lexer/handler/brace.rs +8 -8
  57. package/rust/core/lexer/handler/colon.rs +4 -4
  58. package/rust/core/lexer/handler/comment.rs +2 -2
  59. package/rust/core/lexer/handler/dot.rs +4 -4
  60. package/rust/core/lexer/handler/driver.rs +42 -13
  61. package/rust/core/lexer/handler/identifier.rs +5 -4
  62. package/rust/core/lexer/handler/indent.rs +16 -2
  63. package/rust/core/lexer/handler/newline.rs +1 -1
  64. package/rust/core/lexer/handler/number.rs +3 -3
  65. package/rust/core/lexer/handler/operator.rs +3 -1
  66. package/rust/core/lexer/handler/parenthesis.rs +8 -8
  67. package/rust/core/lexer/handler/slash.rs +5 -5
  68. package/rust/core/lexer/handler/string.rs +1 -1
  69. package/rust/core/lexer/mod.rs +1 -1
  70. package/rust/core/lexer/token.rs +4 -0
  71. package/rust/core/mod.rs +2 -1
  72. package/rust/core/parser/driver.rs +134 -11
  73. package/rust/core/parser/handler/arrow_call.rs +141 -65
  74. package/rust/core/parser/handler/at.rs +1 -1
  75. package/rust/core/parser/handler/bank.rs +35 -7
  76. package/rust/core/parser/handler/dot.rs +43 -22
  77. package/rust/core/parser/handler/identifier/automate.rs +194 -0
  78. package/rust/core/parser/handler/identifier/function.rs +2 -3
  79. package/rust/core/parser/handler/identifier/let_.rs +16 -0
  80. package/rust/core/parser/handler/identifier/mod.rs +14 -10
  81. package/rust/core/parser/handler/identifier/print.rs +29 -0
  82. package/rust/core/parser/handler/identifier/sleep.rs +1 -1
  83. package/rust/core/parser/handler/identifier/synth.rs +7 -9
  84. package/rust/core/parser/handler/loop_.rs +60 -43
  85. package/rust/core/parser/statement.rs +5 -0
  86. package/rust/core/plugin/loader.rs +48 -0
  87. package/rust/core/plugin/mod.rs +1 -0
  88. package/rust/core/preprocessor/loader.rs +7 -5
  89. package/rust/core/preprocessor/processor.rs +4 -4
  90. package/rust/core/preprocessor/resolver/bank.rs +1 -2
  91. package/rust/core/preprocessor/resolver/call.rs +19 -18
  92. package/rust/core/preprocessor/resolver/driver.rs +7 -5
  93. package/rust/core/preprocessor/resolver/function.rs +3 -13
  94. package/rust/core/preprocessor/resolver/loop_.rs +31 -1
  95. package/rust/core/preprocessor/resolver/spawn.rs +3 -22
  96. package/rust/core/preprocessor/resolver/tempo.rs +1 -1
  97. package/rust/core/preprocessor/resolver/trigger.rs +2 -3
  98. package/rust/core/preprocessor/resolver/value.rs +6 -12
  99. package/rust/core/shared/bank.rs +1 -1
  100. package/rust/core/utils/path.rs +1 -1
  101. package/rust/core/utils/validation.rs +0 -1
  102. package/rust/installer/addon.rs +80 -0
  103. package/rust/installer/bank.rs +25 -15
  104. package/rust/installer/mod.rs +4 -1
  105. package/rust/installer/plugin.rs +55 -0
  106. package/rust/main.rs +32 -10
  107. package/rust/utils/error.rs +51 -0
  108. package/rust/utils/logger.rs +20 -0
  109. package/rust/utils/mod.rs +1 -44
  110. package/rust/utils/spinner.rs +3 -5
@@ -37,6 +37,7 @@ pub enum StatementKind {
37
37
  // ───── Core Instructions ─────
38
38
  Tempo,
39
39
  Bank,
40
+ Print,
40
41
  Load {
41
42
  source: String,
42
43
  alias: String,
@@ -44,6 +45,10 @@ pub enum StatementKind {
44
45
  Let {
45
46
  name: String,
46
47
  },
48
+ // Automation of parameters over time (percent-based envelopes)
49
+ Automate {
50
+ target: String,
51
+ },
47
52
  ArrowCall {
48
53
  target: String,
49
54
  method: String,
@@ -0,0 +1,48 @@
1
+ use std::path::Path;
2
+ use serde::Deserialize;
3
+
4
+ #[derive(Debug, Deserialize, Clone)]
5
+ pub struct PluginInfo {
6
+ pub name: String,
7
+ pub version: Option<String>,
8
+ pub description: Option<String>,
9
+ pub author: Option<String>,
10
+ }
11
+
12
+ #[derive(Debug, Deserialize, Clone)]
13
+ pub struct PluginFile {
14
+ pub plugin: PluginInfo,
15
+ }
16
+
17
+ pub fn load_plugin(name: &str) -> Result<(PluginInfo, Vec<u8>), String> {
18
+ let root = Path::new(env!("CARGO_MANIFEST_DIR"));
19
+ let plugin_dir = root.join(".deva").join("plugin").join(name);
20
+ let toml_path = plugin_dir.join("plugin.toml");
21
+ let wasm_path = plugin_dir.join(format!("{}_bg.wasm", name));
22
+
23
+ if !toml_path.exists() {
24
+ return Err(format!("❌ Plugin file not found: {}", toml_path.display()));
25
+ }
26
+ if !wasm_path.exists() {
27
+ return Err(format!("❌ Plugin wasm not found: {}", wasm_path.display()));
28
+ }
29
+
30
+ let toml_content = std::fs::read_to_string(&toml_path)
31
+ .map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
32
+ let plugin_file: PluginFile = toml::from_str(&toml_content)
33
+ .map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
34
+
35
+ let wasm_bytes = std::fs::read(&wasm_path)
36
+ .map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
37
+
38
+ Ok((plugin_file.plugin, wasm_bytes))
39
+ }
40
+
41
+ pub fn load_plugin_from_uri(uri: &str) -> Result<(PluginInfo, Vec<u8>), String> {
42
+ if !uri.starts_with("devalang://plugin/") {
43
+ return Err("Invalid plugin URI".into());
44
+ }
45
+
46
+ let name = uri.trim_start_matches("devalang://plugin/");
47
+ load_plugin(name)
48
+ }
@@ -0,0 +1 @@
1
+ pub mod loader;
@@ -188,7 +188,7 @@ impl ModuleLoader {
188
188
 
189
189
  // Inject triggers for each bank used in module
190
190
  for bank_name in self.extract_bank_names(&statements) {
191
- self.inject_bank_triggers(&mut module, &bank_name);
191
+ let _ = self.inject_bank_triggers(&mut module, &bank_name);
192
192
  }
193
193
 
194
194
  // Inject module variables and functions into global store
@@ -254,6 +254,8 @@ impl ModuleLoader {
254
254
  module: &mut Module,
255
255
  bank_name: &str
256
256
  ) -> Result<Module, String> {
257
+ let alias = bank_name.split('.').last().unwrap_or(bank_name);
258
+
257
259
  let bank_path = Path::new("./.deva/bank").join(bank_name);
258
260
  let bank_toml_path = bank_path.join("bank.toml");
259
261
 
@@ -277,23 +279,23 @@ impl ModuleLoader {
277
279
 
278
280
  bank_map.insert(bank_trigger.name.clone(), Value::String(bank_trigger_path.clone()));
279
281
 
280
- if module.variable_table.variables.contains_key(bank_name) {
282
+ if module.variable_table.variables.contains_key(alias) {
281
283
  eprintln!(
282
284
  "⚠️ Trigger '{}' already defined in module '{}', skipping injection.",
283
- bank_name,
285
+ alias,
284
286
  module.path
285
287
  );
286
288
  continue;
287
289
  }
288
290
 
289
291
  module.variable_table.set(
290
- format!("{}.{}", bank_name, bank_trigger.name),
292
+ format!("{}.{}", alias, bank_trigger.name),
291
293
  Value::String(bank_trigger_path.clone())
292
294
  );
293
295
  }
294
296
 
295
297
  // Inject the map under the bank name
296
- module.variable_table.set(bank_name.to_string(), Value::Map(bank_map));
298
+ module.variable_table.set(alias.to_string(), Value::Map(bank_map));
297
299
 
298
300
  Ok(module.clone())
299
301
  }
@@ -1,14 +1,14 @@
1
1
  use std::{ collections::HashMap, path::Path };
2
2
 
3
3
  use crate::core::{
4
- parser::{ driver::Parser, statement::StatementKind },
5
- preprocessor::{ loader::ModuleLoader, resolver::group },
4
+ parser::statement::StatementKind,
5
+ preprocessor::loader::ModuleLoader,
6
6
  shared::value::Value,
7
7
  store::global::GlobalStore,
8
8
  utils::path::{ normalize_path, resolve_relative_path },
9
9
  };
10
10
 
11
- pub fn process_modules(module_loader: &ModuleLoader, global_store: &mut GlobalStore) {
11
+ pub fn process_modules(_module_loader: &ModuleLoader, global_store: &mut GlobalStore) {
12
12
  for module in global_store.modules.values_mut() {
13
13
  for stmt in &module.statements {
14
14
  match &stmt.kind {
@@ -52,7 +52,7 @@ pub fn process_modules(module_loader: &ModuleLoader, global_store: &mut GlobalSt
52
52
  );
53
53
  }
54
54
 
55
- StatementKind::Export { names, source } => {
55
+ StatementKind::Export { names, source: _ } => {
56
56
  for name in names {
57
57
  if let Some(val) = module.variable_table.get(name) {
58
58
  module.export_table.add_export(name.clone(), val.clone());
@@ -11,12 +11,11 @@ use crate::{
11
11
  pub fn resolve_bank(
12
12
  stmt: &Statement,
13
13
  module: &Module,
14
- path: &str,
14
+ _path: &str,
15
15
  _global_store: &GlobalStore
16
16
  ) -> Statement {
17
17
  let mut new_stmt = stmt.clone();
18
18
  let logger = Logger::new();
19
-
20
19
  match &stmt.value {
21
20
  Value::Identifier(ident) => {
22
21
  if let Some(val) = module.variable_table.get(ident) {
@@ -1,7 +1,7 @@
1
1
  use crate::{
2
2
  core::{
3
3
  parser::statement::{ Statement, StatementKind },
4
- preprocessor::{ module::Module, resolver::driver::resolve_statement },
4
+ preprocessor::module::Module,
5
5
  shared::value::Value,
6
6
  store::global::GlobalStore,
7
7
  },
@@ -13,7 +13,7 @@ pub fn resolve_call(
13
13
  name: String,
14
14
  args: Vec<Value>,
15
15
  module: &Module,
16
- path: &str,
16
+ _path: &str,
17
17
  global_store: &mut GlobalStore
18
18
  ) -> Statement {
19
19
  let logger = Logger::new();
@@ -78,23 +78,24 @@ pub fn resolve_call(
78
78
  ..stmt.clone()
79
79
  }
80
80
  }
81
- _ => error_stmt(&logger, module, stmt, "Expected StatementKind::Call in resolve_call()"),
82
- }
83
- }
84
-
85
- fn get_group_body(stmt_box: &Statement) -> Vec<Statement> {
86
- if let Value::Block(body) = &stmt_box.value { body.clone() } else { vec![] }
87
- }
88
-
89
- fn error_stmt(logger: &Logger, module: &Module, stmt: &Statement, message: &str) -> Statement {
90
- let stacktrace = format!("{}:{}:{}", module.path, stmt.line, stmt.column);
91
- logger.log_message(LogLevel::Error, &format!("{message}\n → at {stacktrace}"));
81
+ _ => {
82
+ let stacktrace = format!("{}:{}:{}", module.path, stmt.line, stmt.column);
83
+ logger.log_message(
84
+ LogLevel::Error,
85
+ &format!(
86
+ "Expected StatementKind::Call in resolve_call()\n → at {stacktrace}"
87
+ )
88
+ );
92
89
 
93
- Statement {
94
- kind: StatementKind::Error {
95
- message: message.to_string(),
90
+ Statement {
91
+ kind: StatementKind::Error {
92
+ message: "Expected StatementKind::Call in resolve_call()".to_string(),
93
+ },
94
+ value: Value::Null,
95
+ ..stmt.clone()
96
+ }
96
97
  },
97
- value: Value::Null,
98
- ..stmt.clone()
99
98
  }
100
99
  }
100
+
101
+ // (removed unused helpers get_group_body, error_stmt)
@@ -25,7 +25,7 @@ use crate::{
25
25
  };
26
26
 
27
27
  pub fn resolve_all_modules(module_loader: &ModuleLoader, global_store: &mut GlobalStore) {
28
- for module in global_store.clone().modules.values_mut() {
28
+ for _module in global_store.clone().modules.values_mut() {
29
29
  resolve_imports(module_loader, global_store);
30
30
  }
31
31
  }
@@ -80,10 +80,12 @@ fn resolve_value(value: &Value, module: &Module, global_store: &mut GlobalStore)
80
80
  return resolve_value(&export_val, module, global_store);
81
81
  }
82
82
 
83
- eprintln!("⚠️ Unresolved identifier '{}'", name);
84
- Value::Null
83
+ // Leave unresolved identifiers as-is; they might be runtime-bound (e.g., foreach vars)
84
+ Value::Identifier(name.clone())
85
85
  }
86
86
 
87
+ Value::String(s) => Value::String(s.clone()),
88
+
87
89
  Value::Beat(beat_str) => {
88
90
  println!("[warn] '{:?}': unresolved beat '{}'", module.path, beat_str);
89
91
  Value::Beat(beat_str.clone())
@@ -118,7 +120,7 @@ fn find_export_value(name: &str, global_store: &GlobalStore) -> Option<Value> {
118
120
  None
119
121
  }
120
122
 
121
- pub fn resolve_imports(module_loader: &ModuleLoader, global_store: &mut GlobalStore) {
123
+ pub fn resolve_imports(_module_loader: &ModuleLoader, global_store: &mut GlobalStore) {
122
124
  for (module_path, module) in global_store.clone().modules.iter_mut() {
123
125
  for (name, source_path) in &module.import_table.imports {
124
126
  match source_path {
@@ -260,7 +262,7 @@ pub fn resolve_and_flatten_all_modules(
260
262
  resolved.push(resolved_stmt);
261
263
  }
262
264
 
263
- StatementKind::Function { name, parameters, body } => {
265
+ StatementKind::Function { name: _, parameters: _, body: _ } => {
264
266
  let resolved_function = resolve_function(&stmt, &module, &path, global_store);
265
267
  resolved.push(resolved_function);
266
268
  }
@@ -1,13 +1,12 @@
1
- use std::collections::HashMap;
2
1
 
3
2
  use crate::{
4
3
  core::{
5
4
  parser::statement::{ Statement, StatementKind },
6
5
  preprocessor::{ module::Module, resolver::driver::resolve_statement },
7
6
  shared::value::Value,
8
- store::{ function::FunctionDef, global::GlobalStore, variable::VariableTable },
7
+ store::{ function::FunctionDef, global::GlobalStore },
9
8
  },
10
- utils::logger::{ LogLevel, Logger },
9
+
11
10
  };
12
11
 
13
12
  pub fn resolve_function(
@@ -66,13 +65,4 @@ fn resolve_block_statements(
66
65
  .collect()
67
66
  }
68
67
 
69
- fn type_error(logger: &Logger, module: &Module, stmt: &Statement, message: String) -> Statement {
70
- let stacktrace = format!("{}:{}:{}", module.path, stmt.line, stmt.column);
71
- logger.log_error_with_stacktrace(&message, &stacktrace);
72
-
73
- Statement {
74
- kind: StatementKind::Error { message },
75
- value: Value::Null,
76
- ..stmt.clone()
77
- }
78
- }
68
+ // (removed unused helper type_error)
@@ -32,6 +32,36 @@ pub fn resolve_loop(
32
32
  resolved_map.insert(key.clone(), resolve_value(val, module, global_store));
33
33
  }
34
34
 
35
+ // Foreach form takes precedence if present
36
+ if let (Some(Value::Identifier(var_name)), Some(array_val)) = (resolved_map.get("foreach"), resolved_map.get("array")) {
37
+ // Resolve array elements
38
+ let resolved_array = match array_val {
39
+ Value::Array(items) => Value::Array(items.iter().map(|v| resolve_value(v, module, global_store)).collect()),
40
+ other => resolve_value(other, module, global_store),
41
+ };
42
+
43
+ let body_value = match resolved_map.get("body") {
44
+ Some(Value::Block(stmts)) => {
45
+ let resolved = stmts
46
+ .iter()
47
+ .map(|s| resolve_statement(s, module, path, global_store))
48
+ .collect();
49
+ Value::Block(resolved)
50
+ }
51
+ _ => {
52
+ error_value(&logger, module, stmt, "Invalid or missing loop body");
53
+ Value::Block(vec![])
54
+ }
55
+ };
56
+
57
+ let mut final_map = HashMap::new();
58
+ final_map.insert("foreach".to_string(), Value::Identifier(var_name.clone()));
59
+ final_map.insert("array".to_string(), resolved_array);
60
+ final_map.insert("body".to_string(), body_value);
61
+
62
+ return Statement { kind: StatementKind::Loop, value: Value::Map(final_map), ..stmt.clone() };
63
+ }
64
+
35
65
  let iterator_value = match resolved_map.get("iterator") {
36
66
  Some(Value::Number(n)) => Value::Number(*n),
37
67
  Some(other) => {
@@ -49,7 +79,7 @@ pub fn resolve_loop(
49
79
  }
50
80
  };
51
81
 
52
- let body_value = match resolved_map.remove("body") {
82
+ let body_value = match resolved_map.get("body") {
53
83
  Some(Value::Block(stmts)) => {
54
84
  let resolved = stmts
55
85
  .iter()
@@ -1,11 +1,7 @@
1
1
  use crate::{
2
2
  core::{
3
3
  parser::statement::{ Statement, StatementKind },
4
- preprocessor::{
5
- module::Module,
6
- resolver::driver::resolve_statement,
7
- resolver::value::resolve_value,
8
- },
4
+ preprocessor::module::Module,
9
5
  shared::value::Value,
10
6
  store::global::GlobalStore,
11
7
  },
@@ -17,7 +13,7 @@ pub fn resolve_spawn(
17
13
  name: String,
18
14
  args: Vec<Value>,
19
15
  module: &Module,
20
- path: &str,
16
+ _path: &str,
21
17
  global_store: &mut GlobalStore
22
18
  ) -> Statement {
23
19
  let logger = Logger::new();
@@ -74,19 +70,4 @@ pub fn resolve_spawn(
74
70
  }
75
71
  }
76
72
 
77
- fn get_group_body(stmt_box: &Statement) -> Vec<Statement> {
78
- if let Value::Block(body) = &stmt_box.value { body.clone() } else { vec![] }
79
- }
80
-
81
- fn error_stmt(logger: &Logger, module: &Module, stmt: &Statement, message: &str) -> Statement {
82
- let stacktrace = format!("{}:{}:{}", module.path, stmt.line, stmt.column);
83
- logger.log_message(LogLevel::Error, &format!("{message}\n → at {stacktrace}"));
84
-
85
- Statement {
86
- kind: StatementKind::Error {
87
- message: message.to_string(),
88
- },
89
- value: Value::Null,
90
- ..stmt.clone()
91
- }
92
- }
73
+ // (removed unused helpers get_group_body, error_stmt)
@@ -11,7 +11,7 @@ use crate::{
11
11
  pub fn resolve_tempo(
12
12
  stmt: &Statement,
13
13
  module: &Module,
14
- path: &str,
14
+ _path: &str,
15
15
  _global_store: &GlobalStore
16
16
  ) -> Statement {
17
17
  let mut new_stmt = stmt.clone();
@@ -14,7 +14,7 @@ pub fn resolve_trigger(
14
14
  stmt: &Statement,
15
15
  entity: &str,
16
16
  duration: &mut Duration,
17
- effects: Option<Value>,
17
+ _effects: Option<Value>,
18
18
  module: &Module,
19
19
  path: &str,
20
20
  global_store: &GlobalStore
@@ -22,7 +22,6 @@ pub fn resolve_trigger(
22
22
  let logger = Logger::new();
23
23
 
24
24
  let mut final_duration = duration.clone();
25
- let mut final_value = stmt.value.clone();
26
25
 
27
26
  // Duration resolution
28
27
  if let Duration::Identifier(ident) = duration {
@@ -43,7 +42,7 @@ pub fn resolve_trigger(
43
42
  }
44
43
 
45
44
  // Params value resolution
46
- final_value = match &stmt.value {
45
+ let final_value = match &stmt.value {
47
46
  Value::Identifier(ident) => {
48
47
  println!("Resolving identifier: {}", ident);
49
48
 
@@ -1,14 +1,10 @@
1
1
  use std::collections::HashMap;
2
2
 
3
- use crate::{
4
- core::{
5
- parser::statement::{ Statement, StatementKind },
3
+ use crate::core::{
6
4
  preprocessor::{ module::Module, resolver::driver::resolve_statement },
7
5
  shared::value::Value,
8
- store::{ global::GlobalStore, variable::VariableTable },
9
- },
10
- utils::logger::{ LogLevel, Logger },
11
- };
6
+ store::global::GlobalStore,
7
+ };
12
8
 
13
9
  fn find_export_value(name: &str, global_store: &GlobalStore) -> Option<Value> {
14
10
  for (_path, module) in &global_store.modules {
@@ -23,8 +19,7 @@ fn find_export_value(name: &str, global_store: &GlobalStore) -> Option<Value> {
23
19
  pub fn resolve_value(value: &Value, module: &Module, global_store: &mut GlobalStore) -> Value {
24
20
  match value {
25
21
  Value::String(s) => {
26
- println!("Resolving value: {}", s);
27
-
22
+ // Keep raw strings as-is; they may be runtime-evaluated (e.g., expressions)
28
23
  Value::String(s.clone())
29
24
  },
30
25
 
@@ -37,9 +32,8 @@ pub fn resolve_value(value: &Value, module: &Module, global_store: &mut GlobalSt
37
32
  return resolve_value(&export_val, module, global_store);
38
33
  }
39
34
 
40
- println!("⚠️ Unresolved identifier '{}'", name);
41
-
42
- Value::Null
35
+ // Leave unresolved identifiers as-is; may be runtime-bound (e.g., foreach variable)
36
+ Value::Identifier(name.clone())
43
37
  }
44
38
 
45
39
  Value::Map(map) => {
@@ -1,4 +1,4 @@
1
- use serde::{ Deserialize, Serialize };
1
+ use serde::Deserialize;
2
2
 
3
3
  #[derive(Debug, Deserialize)]
4
4
  pub struct BankInfo {
@@ -1,4 +1,4 @@
1
- use std::path::{ Component, Path, PathBuf };
1
+ use std::path::{ Path, PathBuf };
2
2
 
3
3
  pub fn find_entry_file(entry: &str) -> Option<String> {
4
4
  let path = Path::new(entry);
@@ -1,4 +1,3 @@
1
- use crate::core::{ preprocessor::module::Module, shared::value::Value, store::global::GlobalStore };
2
1
 
3
2
  // NOTE: Deprecated functions, kept for reference
4
3
 
@@ -0,0 +1,80 @@
1
+ use std::path::Path;
2
+ use crate::{
3
+ common::{ api::get_api_url },
4
+ installer::{ bank::install_bank, plugin::install_plugin },
5
+ };
6
+ use dirs::home_dir;
7
+
8
+ #[derive(Debug, Clone)]
9
+ pub enum AddonType {
10
+ Bank,
11
+ Plugin,
12
+ Preset,
13
+ }
14
+
15
+ pub async fn install_addon(
16
+ addon_type: AddonType,
17
+ name: &str,
18
+ target_dir: &Path
19
+ ) -> Result<(), String> {
20
+ match addon_type {
21
+ AddonType::Bank => install_bank(name, target_dir).await,
22
+ AddonType::Plugin => install_plugin(name, target_dir).await,
23
+ AddonType::Preset => Err("Preset installation not implemented".into()),
24
+ }
25
+ }
26
+
27
+ pub async fn ask_api_for_signed_url(addon_type: AddonType, slug: &str) -> Result<String, String> {
28
+ let api_url = get_api_url();
29
+
30
+ let mut stored_token_path = home_dir().unwrap();
31
+ stored_token_path.push(".devalang");
32
+ stored_token_path.push("session_token.json");
33
+
34
+ let stored_token = std::fs::read_to_string(&stored_token_path).unwrap_or_default();
35
+
36
+ let request_url = format!(
37
+ "{}/v1/assets/url?type={}&slug={}&token={}",
38
+ api_url,
39
+ match addon_type {
40
+ AddonType::Bank => "bank",
41
+ AddonType::Plugin => "plugin",
42
+ AddonType::Preset => "preset",
43
+ },
44
+ slug,
45
+ stored_token
46
+ );
47
+
48
+ let mut headers = reqwest::header::HeaderMap::new();
49
+
50
+ headers.insert("Authorization", format!("Bearer {}", stored_token).parse().unwrap());
51
+
52
+ let client: reqwest::Client = reqwest::Client
53
+ ::builder()
54
+ .default_headers(headers)
55
+ .build()
56
+ .map_err(|_| "Failed to build HTTP client".to_string())?;
57
+
58
+ let req = client
59
+ .get(&request_url)
60
+ .send().await
61
+ .map_err(|_| "Failed to receive response".to_string())?;
62
+
63
+ let response_body: serde_json::Value = req
64
+ .json().await
65
+ .map_err(|_| "Failed to read response body".to_string())?;
66
+
67
+ let signed_url: String = serde_json
68
+ ::from_value(
69
+ response_body
70
+ .get("payload")
71
+ .cloned()
72
+ .unwrap_or_default()
73
+ .get("url")
74
+ .cloned()
75
+ .unwrap_or_default()
76
+ )
77
+ .map_err(|_| "Failed to parse response body".to_string())?;
78
+
79
+ Ok(signed_url)
80
+ }
@@ -1,33 +1,39 @@
1
1
  use std::path::{ Path, PathBuf };
2
2
  use crate::{
3
- common::cdn::get_cdn_url,
4
3
  config::loader::{ add_bank_to_config, load_config },
5
- installer::utils::{ download_file, extract_archive },
4
+ installer::{
5
+ addon::{ ask_api_for_signed_url, AddonType },
6
+ utils::{ download_file, extract_archive },
7
+ },
8
+ utils::logger::{ LogLevel, Logger },
6
9
  };
7
10
 
8
11
  pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
9
- let cdn_url = get_cdn_url();
10
- let url = format!("{}/bank/{}", cdn_url, name);
12
+ let logger = Logger::new();
13
+
14
+ let signed_url = ask_api_for_signed_url(AddonType::Bank, name).await?;
11
15
 
12
16
  let bank_dir = target_dir.join("bank");
13
17
  let archive_path = PathBuf::from(format!("./.deva/tmp/{}.devabank", name));
14
18
  let extract_path = bank_dir.join(name);
15
19
 
20
+ download_file(&signed_url, &archive_path).await.map_err(|e|
21
+ format!("Failed to download: {}", e)
22
+ )?;
23
+
16
24
  if extract_path.exists() {
17
- println!(
18
- "Bank '{}' already exists at '{}'. Skipping install.",
19
- name,
20
- extract_path.display()
25
+ logger.log_message(
26
+ LogLevel::Warning,
27
+ &format!(
28
+ "Bank '{}' already exists at '{}'. Skipping install.",
29
+ name,
30
+ extract_path.display()
31
+ )
21
32
  );
33
+
22
34
  return Ok(());
23
35
  }
24
36
 
25
- download_file(&url, &archive_path).await.map_err(|e| format!("Failed to download: {}", e))?;
26
-
27
- extract_archive(&archive_path, &extract_path).await.map_err(|e|
28
- format!("Failed to extract: {}", e)
29
- )?;
30
-
31
37
  // Add the bank to the config
32
38
  let root_dir = target_dir
33
39
  .parent()
@@ -49,7 +55,11 @@ pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
49
55
 
50
56
  let dependency_path = &format!("devalang://bank/{}", name);
51
57
 
52
- add_bank_to_config(&mut config, &extract_path, &dependency_path);
58
+ extract_archive(&archive_path, &extract_path).await.map_err(|e|
59
+ format!("Failed to extract: {}", e)
60
+ )?;
61
+
62
+ add_bank_to_config(&mut config, &extract_path, dependency_path);
53
63
 
54
64
  Ok(())
55
65
  }
@@ -1,2 +1,5 @@
1
1
  pub mod bank;
2
- pub mod utils;
2
+ pub mod plugin;
3
+
4
+ pub mod utils;
5
+ pub mod addon;