@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
@@ -14,13 +14,41 @@ pub fn parse_bank_token(parser: &mut Parser, _global_store: &mut GlobalStore) ->
14
14
 
15
15
  let bank_value = if let Some(token) = parser.peek_clone() {
16
16
  match token.kind {
17
- TokenKind::Identifier => {
18
- parser.advance();
19
- Value::Identifier(token.lexeme.clone())
20
- }
21
- TokenKind::Number => {
22
- parser.advance();
23
- Value::Number(token.lexeme.parse::<f32>().unwrap_or(0.0))
17
+ TokenKind::Identifier | TokenKind::Number => {
18
+ parser.advance(); // consume identifier or number
19
+
20
+ let mut value = token.lexeme.clone();
21
+
22
+ // Support namespaced banks: <author>.<bank_name>
23
+ if let Some(next) = parser.peek_clone() {
24
+ if next.kind == TokenKind::Dot {
25
+ parser.advance(); // consume '.'
26
+ if let Some(last) = parser.peek_clone() {
27
+ match last.kind {
28
+ TokenKind::Identifier | TokenKind::Number => {
29
+ parser.advance();
30
+ value = format!("{}.{}", value, last.lexeme);
31
+ Value::String(value)
32
+ }
33
+ _ => Value::Unknown,
34
+ }
35
+ } else {
36
+ Value::Unknown
37
+ }
38
+ } else {
39
+ match token.kind {
40
+ TokenKind::Identifier => Value::Identifier(value),
41
+ TokenKind::Number => Value::Number(value.parse::<f32>().unwrap_or(0.0)),
42
+ _ => Value::Unknown,
43
+ }
44
+ }
45
+ } else {
46
+ match token.kind {
47
+ TokenKind::Identifier => Value::Identifier(value),
48
+ TokenKind::Number => Value::Number(value.parse::<f32>().unwrap_or(0.0)),
49
+ _ => Value::Unknown,
50
+ }
51
+ }
24
52
  }
25
53
  _ => Value::Unknown,
26
54
  }
@@ -16,13 +16,23 @@ pub fn parse_dot_token(
16
16
 
17
17
  // Parse a single entity (namespace-friendly, stops at newline)
18
18
  let mut parts = Vec::new();
19
+ let current_line = dot_token.line;
19
20
 
20
21
  while let Some(token) = parser.peek_clone() {
22
+ // Never cross a newline
23
+ if token.line != current_line {
24
+ break;
25
+ }
21
26
  match token.kind {
22
27
  TokenKind::Identifier | TokenKind::Number => {
23
28
  parts.push(token.lexeme.clone());
24
29
  parser.advance();
25
- if parser.peek_kind() != Some(TokenKind::Dot) {
30
+ // The separator must be a '.' on the same line, otherwise stop
31
+ if let Some(next) = parser.peek_clone() {
32
+ if next.line != current_line || next.kind != TokenKind::Dot {
33
+ break;
34
+ }
35
+ } else {
26
36
  break;
27
37
  }
28
38
  }
@@ -51,42 +61,53 @@ pub fn parse_dot_token(
51
61
  let mut value = Value::Null;
52
62
 
53
63
  if let Some(token) = parser.peek_clone() {
54
- match token.kind {
64
+ // Duration and effects map are only valid on the same line
65
+ if token.line == current_line {
66
+ match token.kind {
55
67
  TokenKind::Number => {
56
68
  let numerator = token.lexeme.clone();
57
69
  parser.advance();
58
- if let Some(TokenKind::Slash) = parser.peek_kind() {
59
- parser.advance();
60
- if let Some(denominator_token) = parser.peek_clone() {
61
- if denominator_token.kind == TokenKind::Number {
62
- let denominator = denominator_token.lexeme.clone();
63
- parser.advance();
64
- duration = Duration::Beat(format!("{}/{}", numerator, denominator));
70
+ if let Some(peek) = parser.peek_clone() {
71
+ if peek.line == current_line {
72
+ if let Some(TokenKind::Slash) = parser.peek_kind() {
73
+ parser.advance();
74
+ if let Some(denominator_token) = parser.peek_clone() {
75
+ if denominator_token.line == current_line && denominator_token.kind == TokenKind::Number {
76
+ let denominator = denominator_token.lexeme.clone();
77
+ parser.advance();
78
+ duration = Duration::Beat(format!("{}/{}", numerator, denominator));
79
+ }
80
+ }
81
+ } else {
82
+ duration = parse_duration(numerator);
83
+ }
84
+ } else {
85
+ duration = parse_duration(numerator);
65
86
  }
87
+ } else {
88
+ duration = parse_duration(numerator);
66
89
  }
67
- } else {
68
- duration = parse_duration(numerator);
69
- }
70
- if let Some(next) = parser.peek_clone() {
71
- if next.kind == TokenKind::LBrace {
72
- value = parser.parse_map_value().unwrap_or(Value::Null);
90
+ if let Some(next) = parser.peek_clone() {
91
+ if next.line == current_line && next.kind == TokenKind::LBrace {
92
+ value = parser.parse_map_value().unwrap_or(Value::Null);
93
+ }
73
94
  }
74
- }
75
95
  }
76
96
  TokenKind::Identifier => {
77
97
  let id = token.lexeme.clone();
78
98
  parser.advance();
79
99
  duration = parse_duration(id);
80
- if let Some(next) = parser.peek_clone() {
81
- if next.kind == TokenKind::LBrace {
82
- value = parser.parse_map_value().unwrap_or(Value::Null);
100
+ if let Some(next) = parser.peek_clone() {
101
+ if next.line == current_line && next.kind == TokenKind::LBrace {
102
+ value = parser.parse_map_value().unwrap_or(Value::Null);
103
+ }
83
104
  }
84
- }
85
105
  }
86
106
  TokenKind::LBrace => {
87
- value = parser.parse_map_value().unwrap_or(Value::Null);
107
+ value = parser.parse_map_value().unwrap_or(Value::Null);
108
+ }
109
+ _ => {}
88
110
  }
89
- _ => {}
90
111
  }
91
112
  }
92
113
 
@@ -0,0 +1,194 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::core::{
4
+ lexer::token::{Token, TokenKind},
5
+ parser::{driver::Parser, statement::{Statement, StatementKind}},
6
+ shared::value::Value,
7
+ store::global::GlobalStore,
8
+ };
9
+
10
+ // Grammar:
11
+ // automate <identifier>:
12
+ // param <name> { <percent>% = <number> ... }
13
+ // Produces StatementKind::Automate with value map:
14
+ // { target: Identifier, params: Map<paramName, Map<percent, Number>> }
15
+ pub fn parse_automate_token(
16
+ parser: &mut Parser,
17
+ current_token: Token,
18
+ _global_store: &mut GlobalStore,
19
+ ) -> Statement {
20
+ parser.advance(); // consume 'automate'
21
+
22
+ // Expect target identifier
23
+ let Some(target_token) = parser.peek_clone() else {
24
+ return Statement::error(current_token, "Expected target after 'automate'".to_string());
25
+ };
26
+
27
+ if target_token.kind != TokenKind::Identifier && target_token.kind != TokenKind::String {
28
+ return Statement::error(target_token, "Expected valid target after 'automate'".to_string());
29
+ }
30
+ parser.advance(); // consume target
31
+
32
+ // Expect ':'
33
+ let Some(colon_token) = parser.peek_clone() else {
34
+ return Statement::error(target_token, "Expected ':' after automate target".to_string());
35
+ };
36
+ if colon_token.kind != TokenKind::Colon {
37
+ return Statement::error(colon_token, "Expected ':' after automate target".to_string());
38
+ }
39
+ parser.advance(); // consume ':'
40
+
41
+ let base_indent = current_token.indent;
42
+
43
+ // Collect tokens inside block (indented > base_indent)
44
+ let mut index = parser.token_index;
45
+ let mut tokens_inside = Vec::new();
46
+ while index < parser.tokens.len() {
47
+ let tok = parser.tokens[index].clone();
48
+ if tok.indent <= base_indent && tok.kind != TokenKind::Newline {
49
+ break;
50
+ }
51
+ tokens_inside.push(tok);
52
+ index += 1;
53
+ }
54
+ parser.token_index = index;
55
+
56
+ // Now parse block manually to capture 'param' entries without reusing general parser kinds
57
+ let mut local = Parser {
58
+ resolve_modules: parser.resolve_modules,
59
+ tokens: tokens_inside,
60
+ token_index: 0,
61
+ current_module: parser.current_module.clone(),
62
+ previous: None,
63
+ };
64
+
65
+ let mut params: HashMap<String, Value> = HashMap::new();
66
+
67
+ while let Some(tok) = local.peek_clone() {
68
+ match tok.kind {
69
+ TokenKind::Identifier if tok.lexeme == "param" => {
70
+ local.advance(); // consume 'param'
71
+ // param name
72
+ let Some(name_tok) = local.peek_clone() else {
73
+ return Statement::error(tok, "Expected parameter name after 'param'".to_string());
74
+ };
75
+ if name_tok.kind != TokenKind::Identifier && name_tok.kind != TokenKind::String {
76
+ return Statement::error(name_tok, "Expected valid parameter name".to_string());
77
+ }
78
+ local.advance(); // consume name
79
+
80
+ // Expect '{'
81
+ if !local.match_token(TokenKind::LBrace) {
82
+ return Statement::error(name_tok, "Expected '{' to start parameter block".to_string());
83
+ }
84
+
85
+ // Collect entries like: 0% = 0.0
86
+ let mut envelope: HashMap<String, Value> = HashMap::new();
87
+ while let Some(inner) = local.peek_clone() {
88
+ if inner.kind == TokenKind::RBrace { local.advance(); break; }
89
+ // Skip formatting tokens inside the param block
90
+ if matches!(inner.kind, TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent | TokenKind::Comma) {
91
+ local.advance();
92
+ continue;
93
+ }
94
+
95
+ // Read percentage token: could be number followed by '%' as Dot or Identifier? '%' not defined.
96
+ // Our lexer has no Percent token, so accept either Number or Identifier containing e.g. '0%'.
97
+ let percent_token = inner.clone();
98
+ local.advance();
99
+
100
+ let percent_key = percent_token.lexeme.clone();
101
+
102
+ // Expect '='
103
+ // Skip any stray formatting between key and '='
104
+ while let Some(t) = local.peek_kind() {
105
+ if matches!(t, TokenKind::Indent | TokenKind::Dedent | TokenKind::Newline) { local.advance(); continue; }
106
+ break;
107
+ }
108
+ if !local.match_token(TokenKind::Equals) {
109
+ return Statement::error(percent_token, "Expected '=' in param entry".to_string());
110
+ }
111
+
112
+ // Read value (number or identifier)
113
+ // Skip formatting before value
114
+ while let Some(t) = local.peek_kind() {
115
+ if matches!(t, TokenKind::Indent | TokenKind::Dedent | TokenKind::Newline) { local.advance(); continue; }
116
+ break;
117
+ }
118
+
119
+ let value = if let Some(vtok) = local.peek_clone() {
120
+ match vtok.kind {
121
+ // Handle negative numbers where '-' is lexed as Arrow
122
+ TokenKind::Arrow => {
123
+ // Check if next token is a number
124
+ let mut num_str = String::from("-");
125
+ local.advance(); // consume '-'
126
+ if let Some(ntok) = local.peek_clone() {
127
+ if ntok.kind == TokenKind::Number {
128
+ num_str.push_str(&ntok.lexeme);
129
+ local.advance(); // consume number
130
+ if let Some(dot) = local.peek_clone() {
131
+ if dot.kind == TokenKind::Dot {
132
+ local.advance();
133
+ if let Some(frac) = local.peek_clone() {
134
+ if frac.kind == TokenKind::Number {
135
+ num_str.push('.');
136
+ num_str.push_str(&frac.lexeme);
137
+ local.advance();
138
+ }
139
+ }
140
+ }
141
+ }
142
+ Value::Number(num_str.parse::<f32>().unwrap_or(0.0))
143
+ } else {
144
+ Value::Unknown
145
+ }
146
+ } else {
147
+ Value::Unknown
148
+ }
149
+ }
150
+ TokenKind::Number => {
151
+ // Possibly a float with dot
152
+ let mut number_str = vtok.lexeme.clone();
153
+ local.advance();
154
+ if let Some(dot) = local.peek_clone() {
155
+ if dot.kind == TokenKind::Dot {
156
+ local.advance();
157
+ if let Some(frac) = local.peek_clone() {
158
+ if frac.kind == TokenKind::Number {
159
+ number_str.push('.');
160
+ number_str.push_str(&frac.lexeme);
161
+ local.advance();
162
+ }
163
+ }
164
+ }
165
+ }
166
+ Value::Number(number_str.parse::<f32>().unwrap_or(0.0))
167
+ }
168
+ TokenKind::Identifier => { local.advance(); Value::Identifier(vtok.lexeme.clone()) }
169
+ TokenKind::String => { local.advance(); Value::String(vtok.lexeme.clone()) }
170
+ _ => { local.advance(); Value::Unknown }
171
+ }
172
+ } else { Value::Null };
173
+
174
+ envelope.insert(percent_key, value);
175
+ }
176
+
177
+ params.insert(name_tok.lexeme.clone(), Value::Map(envelope));
178
+ }
179
+ _ => { local.advance(); }
180
+ }
181
+ }
182
+
183
+ let mut value_map = HashMap::new();
184
+ value_map.insert("target".to_string(), Value::String(target_token.lexeme.clone()));
185
+ value_map.insert("params".to_string(), Value::Map(params));
186
+
187
+ Statement {
188
+ kind: StatementKind::Automate { target: target_token.lexeme.clone() },
189
+ value: Value::Map(value_map),
190
+ indent: current_token.indent,
191
+ line: current_token.line,
192
+ column: current_token.column,
193
+ }
194
+ }
@@ -29,7 +29,6 @@ pub fn parse_function_token(parser: &mut Parser, global_store: &mut GlobalStore)
29
29
  parser.advance(); // consume function name
30
30
 
31
31
  let mut parameters = Vec::new();
32
- let mut body = Vec::new();
33
32
 
34
33
  // Expect '('
35
34
  if parser.peek_kind() != Some(TokenKind::LParen) {
@@ -68,8 +67,8 @@ pub fn parse_function_token(parser: &mut Parser, global_store: &mut GlobalStore)
68
67
  body_tokens.push(parser.advance().unwrap().clone());
69
68
  }
70
69
 
71
- // arse those tokens into block statements
72
- body = parser.parse_block(body_tokens.clone(), global_store);
70
+ // Parse those tokens into block statements
71
+ let body = parser.parse_block(body_tokens.clone(), global_store);
73
72
 
74
73
  // Skip Dedent if present
75
74
  if let Some(tok) = parser.peek() {
@@ -33,6 +33,22 @@ pub fn parse_let_token(
33
33
  return Statement::error(current_token, "Expected '=' after identifier".to_string());
34
34
  }
35
35
 
36
+ // If RHS begins with '$' or contains expression tokens ('+', '-', '*', '/', '(', '['),
37
+ // collect the rest of the line as a raw expression string.
38
+ if let Some(tok) = parser.peek_clone() {
39
+ let line = tok.line;
40
+ if tok.lexeme.starts_with('$') || matches!(tok.kind, TokenKind::Identifier | TokenKind::Number | TokenKind::LParen | TokenKind::LBracket) {
41
+ // Collect tokens until end of the current line
42
+ let collected = parser.collect_until(|t| t.line != line || matches!(t.kind, TokenKind::Newline | TokenKind::EOF));
43
+ let mut text = String::new();
44
+ for t in collected.iter() {
45
+ if matches!(t.kind, TokenKind::Newline | TokenKind::EOF) { break; }
46
+ text.push_str(&t.lexeme);
47
+ }
48
+ return Statement { kind: StatementKind::Let { name: identifier }, value: Value::String(text.trim().to_string()), indent: current_token.indent, line: current_token.line, column: current_token.column };
49
+ }
50
+ }
51
+
36
52
  let value = match parser.peek_clone() {
37
53
  Some(token) if token.kind == TokenKind::Dot => {
38
54
  let dot_stmt = parse_dot_token(parser, global_store);
@@ -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
  }