@devaloop/devalang 0.0.1-alpha.14 → 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 (90) hide show
  1. package/.devalang +8 -8
  2. package/Cargo.toml +2 -2
  3. package/README.md +31 -14
  4. package/docs/CHANGELOG.md +59 -0
  5. package/docs/ROADMAP.md +1 -1
  6. package/examples/automation.deva +44 -0
  7. package/examples/index.deva +41 -25
  8. package/examples/plugin.deva +15 -0
  9. package/out-tsc/bin/devalang.exe +0 -0
  10. package/package.json +1 -1
  11. package/project-version.json +3 -3
  12. package/rust/cli/bank.rs +14 -15
  13. package/rust/cli/check.rs +1 -1
  14. package/rust/cli/play.rs +1 -1
  15. package/rust/cli/update.rs +1 -1
  16. package/rust/common/api.rs +3 -6
  17. package/rust/common/cdn.rs +3 -6
  18. package/rust/common/sso.rs +3 -6
  19. package/rust/core/audio/engine.rs +215 -74
  20. package/rust/core/audio/evaluator.rs +101 -0
  21. package/rust/core/audio/interpreter/arrow_call.rs +27 -1
  22. package/rust/core/audio/interpreter/automate.rs +18 -0
  23. package/rust/core/audio/interpreter/call.rs +2 -2
  24. package/rust/core/audio/interpreter/condition.rs +3 -3
  25. package/rust/core/audio/interpreter/driver.rs +49 -16
  26. package/rust/core/audio/interpreter/let_.rs +14 -7
  27. package/rust/core/audio/interpreter/loop_.rs +39 -6
  28. package/rust/core/audio/interpreter/mod.rs +2 -1
  29. package/rust/core/audio/interpreter/sleep.rs +2 -4
  30. package/rust/core/audio/interpreter/spawn.rs +2 -2
  31. package/rust/core/audio/loader/trigger.rs +2 -5
  32. package/rust/core/audio/mod.rs +2 -1
  33. package/rust/core/audio/renderer.rs +1 -1
  34. package/rust/core/audio/special/easing.rs +120 -0
  35. package/rust/core/audio/special/env.rs +41 -0
  36. package/rust/core/audio/special/math.rs +92 -0
  37. package/rust/core/audio/special/mod.rs +9 -0
  38. package/rust/core/audio/special/modulator.rs +120 -0
  39. package/rust/core/debugger/store.rs +1 -1
  40. package/rust/core/error/mod.rs +4 -1
  41. package/rust/core/lexer/handler/arrow.rs +60 -9
  42. package/rust/core/lexer/handler/at.rs +4 -4
  43. package/rust/core/lexer/handler/brace.rs +8 -8
  44. package/rust/core/lexer/handler/colon.rs +4 -4
  45. package/rust/core/lexer/handler/comment.rs +2 -2
  46. package/rust/core/lexer/handler/dot.rs +4 -4
  47. package/rust/core/lexer/handler/driver.rs +42 -13
  48. package/rust/core/lexer/handler/identifier.rs +5 -4
  49. package/rust/core/lexer/handler/newline.rs +1 -1
  50. package/rust/core/lexer/handler/number.rs +3 -3
  51. package/rust/core/lexer/handler/operator.rs +3 -1
  52. package/rust/core/lexer/handler/parenthesis.rs +8 -8
  53. package/rust/core/lexer/handler/slash.rs +5 -5
  54. package/rust/core/lexer/handler/string.rs +1 -1
  55. package/rust/core/lexer/mod.rs +1 -1
  56. package/rust/core/lexer/token.rs +3 -0
  57. package/rust/core/parser/driver.rs +94 -12
  58. package/rust/core/parser/handler/arrow_call.rs +105 -89
  59. package/rust/core/parser/handler/at.rs +1 -1
  60. package/rust/core/parser/handler/dot.rs +3 -3
  61. package/rust/core/parser/handler/identifier/automate.rs +194 -0
  62. package/rust/core/parser/handler/identifier/function.rs +2 -3
  63. package/rust/core/parser/handler/identifier/let_.rs +16 -0
  64. package/rust/core/parser/handler/identifier/mod.rs +14 -10
  65. package/rust/core/parser/handler/identifier/print.rs +29 -0
  66. package/rust/core/parser/handler/identifier/sleep.rs +1 -1
  67. package/rust/core/parser/handler/identifier/synth.rs +7 -9
  68. package/rust/core/parser/handler/loop_.rs +60 -43
  69. package/rust/core/parser/statement.rs +5 -0
  70. package/rust/core/preprocessor/loader.rs +1 -1
  71. package/rust/core/preprocessor/processor.rs +4 -4
  72. package/rust/core/preprocessor/resolver/bank.rs +1 -2
  73. package/rust/core/preprocessor/resolver/call.rs +19 -18
  74. package/rust/core/preprocessor/resolver/driver.rs +7 -5
  75. package/rust/core/preprocessor/resolver/function.rs +3 -13
  76. package/rust/core/preprocessor/resolver/loop_.rs +31 -1
  77. package/rust/core/preprocessor/resolver/spawn.rs +3 -22
  78. package/rust/core/preprocessor/resolver/tempo.rs +1 -1
  79. package/rust/core/preprocessor/resolver/trigger.rs +2 -3
  80. package/rust/core/preprocessor/resolver/value.rs +6 -12
  81. package/rust/core/shared/bank.rs +1 -1
  82. package/rust/core/utils/path.rs +1 -1
  83. package/rust/core/utils/validation.rs +0 -1
  84. package/rust/installer/bank.rs +1 -1
  85. package/rust/installer/plugin.rs +2 -2
  86. package/rust/main.rs +0 -1
  87. package/rust/utils/error.rs +51 -0
  88. package/rust/utils/logger.rs +4 -0
  89. package/rust/utils/mod.rs +1 -44
  90. package/rust/utils/spinner.rs +1 -1
@@ -5,21 +5,23 @@ pub mod spawn;
5
5
  pub mod sleep;
6
6
  pub mod synth;
7
7
  pub mod function;
8
+ pub mod automate;
9
+ pub mod print;
8
10
 
9
11
  use crate::core::{
10
12
  parser::{
11
13
  driver::Parser,
12
- handler::{
13
- identifier::{
14
- call::parse_call_token,
15
- group::parse_group_token,
16
- let_::parse_let_token,
17
- sleep::parse_sleep_token,
18
- spawn::parse_spawn_token,
19
- synth::parse_synth_token
20
- },
14
+ handler::identifier::{
15
+ automate::parse_automate_token,
16
+ call::parse_call_token,
17
+ group::parse_group_token,
18
+ let_::parse_let_token,
19
+ print::parse_print_token,
20
+ sleep::parse_sleep_token,
21
+ spawn::parse_spawn_token,
22
+ synth::parse_synth_token,
21
23
  },
22
- statement::Statement,
24
+ statement::{ Statement },
23
25
  },
24
26
  store::global::GlobalStore,
25
27
  };
@@ -39,6 +41,8 @@ pub fn parse_identifier_token(parser: &mut Parser, global_store: &mut GlobalStor
39
41
  "spawn" => parse_spawn_token(parser, current_token_clone, global_store),
40
42
  "sleep" => parse_sleep_token(parser, current_token_clone, global_store),
41
43
  "synth" => parse_synth_token(parser, current_token_clone, global_store),
44
+ "automate" => parse_automate_token(parser, current_token_clone, global_store),
45
+ "print" => parse_print_token(parser, current_token_clone, global_store),
42
46
  _ => {
43
47
  parser.advance(); // consume identifier
44
48
 
@@ -0,0 +1,29 @@
1
+ use crate::core::{
2
+ lexer::token::{ Token, TokenKind },
3
+ parser::{ driver::Parser, statement::{ Statement, StatementKind } },
4
+ store::global::GlobalStore,
5
+ };
6
+
7
+ pub fn parse_print_token(
8
+ parser: &mut Parser,
9
+ current_token: Token,
10
+ _global_store: &mut GlobalStore
11
+ ) -> Statement {
12
+ // consume 'print'
13
+ parser.advance();
14
+
15
+ let collected = parser.collect_until(|t| matches!(t.kind, TokenKind::Newline | TokenKind::EOF));
16
+ // If single identifier, store as Identifier; else store as String of concatenated lexemes
17
+ let value = if collected.len() == 1 && collected[0].kind == TokenKind::Identifier {
18
+ crate::core::shared::value::Value::Identifier(collected[0].lexeme.clone())
19
+ } else {
20
+ let mut text = String::new();
21
+ for t in collected.iter() {
22
+ if matches!(t.kind, TokenKind::Newline | TokenKind::EOF) { break; }
23
+ text.push_str(&t.lexeme);
24
+ }
25
+ crate::core::shared::value::Value::String(text.trim().to_string())
26
+ };
27
+
28
+ Statement { kind: StatementKind::Print, value, indent: current_token.indent, line: current_token.line, column: current_token.column }
29
+ }
@@ -8,7 +8,7 @@ use crate::core::{
8
8
  pub fn parse_sleep_token(
9
9
  parser: &mut Parser,
10
10
  current_token: Token,
11
- global_store: &mut GlobalStore
11
+ _global_store: &mut GlobalStore
12
12
  ) -> Statement {
13
13
  parser.advance(); // consume "sleep"
14
14
 
@@ -1,7 +1,7 @@
1
1
  use std::collections::HashMap;
2
2
 
3
3
  use crate::core::{
4
- lexer::token::{ Token, TokenKind },
4
+ lexer::token::Token,
5
5
  parser::{ driver::Parser, statement::{ Statement, StatementKind } },
6
6
  shared::value::Value,
7
7
  store::global::GlobalStore,
@@ -9,8 +9,8 @@ use crate::core::{
9
9
 
10
10
  pub fn parse_synth_token(
11
11
  parser: &mut Parser,
12
- current_token: Token,
13
- global_store: &mut GlobalStore
12
+ _current_token: Token,
13
+ _global_store: &mut GlobalStore
14
14
  ) -> Statement {
15
15
  parser.advance(); // consume 'synth'
16
16
 
@@ -28,19 +28,17 @@ pub fn parse_synth_token(
28
28
  parser.advance(); // consume identifier
29
29
 
30
30
  // Expect synth optional parameters map
31
- let mut parameters = HashMap::new();
32
-
33
- if let Some(params) = parser.parse_map_value() {
31
+ let parameters = if let Some(params) = parser.parse_map_value() {
34
32
  // If parameters are provided, we expect a map
35
33
  if let Value::Map(map) = params {
36
- parameters = map;
34
+ map
37
35
  } else {
38
36
  return Statement::error(synth_token, "Expected a map for synth parameters".to_string());
39
37
  }
40
38
  } else {
41
39
  // If no parameters are provided, we can still create the statement with an empty map
42
- parameters = HashMap::new();
43
- }
40
+ HashMap::new()
41
+ };
44
42
 
45
43
  Statement {
46
44
  kind: StatementKind::Synth,
@@ -1,5 +1,3 @@
1
- use std::collections::HashMap;
2
-
3
1
  use crate::core::{
4
2
  lexer::{ token::TokenKind },
5
3
  parser::{ statement::{ Statement, StatementKind }, driver::Parser },
@@ -8,65 +6,84 @@ use crate::core::{
8
6
  };
9
7
 
10
8
  pub fn parse_loop_token(parser: &mut Parser, global_store: &mut GlobalStore) -> Statement {
11
- parser.advance(); // consume 'loop'
9
+ parser.advance(); // consume 'loop' or 'for' (aliased in lexer)
12
10
  let Some(loop_token) = parser.previous_clone() else {
13
11
  return Statement::unknown();
14
12
  };
15
13
 
16
- let Some(iterator_token) = parser.peek_clone() else {
17
- return Statement::error(loop_token, "Expected number or identifier after 'loop'".to_string());
14
+ // Support two forms:
15
+ // 1) loop <count>:
16
+ // 2) for <ident> in [a,b,c]:
17
+
18
+ // Peek next to decide
19
+ let Some(next_token) = parser.peek_clone() else {
20
+ return Statement::error(loop_token, "Expected iterator after loop/for".to_string());
18
21
  };
19
22
 
20
- let iterator_value = match iterator_token.kind {
21
- TokenKind::Number => {
22
- let val = iterator_token.lexeme.parse::<f32>().unwrap_or(1.0);
23
- parser.advance();
24
- Value::Number(val)
25
- }
26
- TokenKind::Identifier => {
27
- let val = iterator_token.lexeme.clone();
28
- parser.advance();
29
- Value::Identifier(val)
23
+ // Try to detect 'for <ident> in [array]:' form
24
+ let mut foreach_ident: Option<String> = None;
25
+ if let TokenKind::Identifier = next_token.kind {
26
+ // Could be either count identifier (old form) or foreach variable
27
+ // Look ahead for 'in'
28
+ let name = next_token.lexeme.clone();
29
+ // don't consume yet; we'll branch
30
+ if let Some(t2) = parser.peek_nth(1) {
31
+ if t2.kind == TokenKind::Identifier && t2.lexeme == "in" {
32
+ // foreach form
33
+ foreach_ident = Some(name);
34
+ // consume ident and 'in'
35
+ parser.advance();
36
+ parser.advance();
37
+ }
30
38
  }
31
- _ => {
32
- return Statement::error(
33
- iterator_token.clone(),
34
- "Expected a number or identifier as loop count".to_string()
35
- );
39
+ }
40
+
41
+ if let Some(var_name) = foreach_ident {
42
+ // Expect array literal
43
+ let array_val = if let Some(v) = parser.parse_array_value() { v } else {
44
+ return Statement::error(loop_token, "Expected array literal after 'in'".to_string());
45
+ };
46
+
47
+ // Expect ':'
48
+ if !parser.match_token(TokenKind::Colon) {
49
+ return Statement::error(loop_token, "Expected ':' after foreach header".to_string());
36
50
  }
51
+
52
+ let tokens = parser.collect_until(|t| t.kind == TokenKind::Dedent || t.kind == TokenKind::EOF);
53
+ let loop_body = parser.parse_block(tokens.clone(), global_store);
54
+ if let Some(token) = parser.peek() { if token.kind == TokenKind::Dedent { parser.advance(); } }
55
+
56
+ let mut value_map = std::collections::HashMap::new();
57
+ value_map.insert("foreach".to_string(), Value::Identifier(var_name));
58
+ value_map.insert("array".to_string(), array_val);
59
+ value_map.insert("body".to_string(), Value::Block(loop_body.clone()));
60
+
61
+ return Statement { kind: StatementKind::Loop, value: Value::Map(value_map), indent: loop_token.indent, line: loop_token.line, column: loop_token.column };
62
+ }
63
+
64
+ // Fallback to legacy: loop <count>:
65
+ let Some(iterator_token) = parser.peek_clone() else {
66
+ return Statement::error(loop_token, "Expected number or identifier after 'loop'".to_string());
37
67
  };
38
68
 
39
- // Expect colon
40
- let Some(colon_token) = parser.peek_clone() else {
41
- return Statement::error(iterator_token.clone(), "Expected ':' after loop count".to_string());
69
+ let iterator_value = match iterator_token.kind {
70
+ TokenKind::Number => { let val = iterator_token.lexeme.parse::<f32>().unwrap_or(1.0); parser.advance(); Value::Number(val) }
71
+ TokenKind::Identifier => { let val = iterator_token.lexeme.clone(); parser.advance(); Value::Identifier(val) }
72
+ _ => { return Statement::error(iterator_token.clone(), "Expected a number or identifier as loop count".to_string()); }
42
73
  };
43
74
 
44
- if colon_token.kind != TokenKind::Colon {
45
- let message = format!("Expected ':' after loop count, got {:?}", colon_token.kind);
46
- return Statement::error(colon_token.clone(), message);
75
+ if !parser.match_token(TokenKind::Colon) {
76
+ let message = format!("Expected ':' after loop count, got {:?}", parser.peek_kind());
77
+ return Statement::error(loop_token.clone(), message);
47
78
  }
48
79
 
49
- parser.advance(); // consume ':'
50
-
51
- // Collect body
52
80
  let tokens = parser.collect_until(|t| t.kind == TokenKind::Dedent || t.kind == TokenKind::EOF);
53
81
  let loop_body = parser.parse_block(tokens.clone(), global_store);
82
+ if let Some(token) = parser.peek() { if token.kind == TokenKind::Dedent { parser.advance(); } }
54
83
 
55
- if let Some(token) = parser.peek() {
56
- if token.kind == TokenKind::Dedent {
57
- parser.advance();
58
- }
59
- }
60
-
61
- let mut value_map = HashMap::new();
84
+ let mut value_map = std::collections::HashMap::new();
62
85
  value_map.insert("iterator".to_string(), iterator_value);
63
86
  value_map.insert("body".to_string(), Value::Block(loop_body.clone()));
64
87
 
65
- Statement {
66
- kind: StatementKind::Loop,
67
- value: Value::Map(value_map),
68
- indent: loop_token.indent,
69
- line: loop_token.line,
70
- column: loop_token.column,
71
- }
88
+ Statement { kind: StatementKind::Loop, value: Value::Map(value_map), indent: loop_token.indent, line: loop_token.line, column: loop_token.column }
72
89
  }
@@ -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,
@@ -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
@@ -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
 
@@ -59,7 +59,7 @@ pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
59
59
  format!("Failed to extract: {}", e)
60
60
  )?;
61
61
 
62
- add_bank_to_config(&mut config, &extract_path, &dependency_path);
62
+ add_bank_to_config(&mut config, &extract_path, dependency_path);
63
63
 
64
64
  Ok(())
65
65
  }