@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
@@ -16,10 +16,24 @@ pub fn handle_indent_lexer(
16
16
  *current_indent += 1;
17
17
  chars.next();
18
18
  col += 1;
19
+ tokens.push(Token {
20
+ kind: TokenKind::Whitespace,
21
+ lexeme: " ".to_string(),
22
+ line: *line,
23
+ column: col,
24
+ indent: *current_indent,
25
+ });
19
26
  } else if c == '\t' {
20
27
  *current_indent += 4;
21
28
  chars.next();
22
29
  col += 4;
30
+ tokens.push(Token {
31
+ kind: TokenKind::Whitespace,
32
+ lexeme: "\t".to_string(),
33
+ line: *line,
34
+ column: col,
35
+ indent: *current_indent,
36
+ });
23
37
  } else {
24
38
  break;
25
39
  }
@@ -32,7 +46,7 @@ pub fn handle_indent_lexer(
32
46
  indent_stack.push(*current_indent);
33
47
  tokens.push(Token {
34
48
  kind: TokenKind::Indent,
35
- lexeme: String::new(),
49
+ lexeme: String::from("<INDENT>"),
36
50
  line: *line,
37
51
  column: *column,
38
52
  indent: *current_indent,
@@ -42,7 +56,7 @@ pub fn handle_indent_lexer(
42
56
  indent_stack.pop();
43
57
  tokens.push(Token {
44
58
  kind: TokenKind::Dedent,
45
- lexeme: String::new(),
59
+ lexeme: String::from("<DEDENT>"),
46
60
  line: *line,
47
61
  column: *column,
48
62
  indent: *current_indent,
@@ -2,7 +2,7 @@ use crate::core::lexer::token::{ Token, TokenKind };
2
2
 
3
3
  pub fn handle_newline_lexer(
4
4
  ch: char,
5
- chars: &mut std::iter::Peekable<std::str::Chars>,
5
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  tokens: &mut Vec<Token>,
7
7
  line: &mut usize,
8
8
  column: &mut usize,
@@ -1,15 +1,15 @@
1
1
  use crate::core::lexer::token::{ Token, TokenKind };
2
2
 
3
3
  pub fn handle_number_lexer(
4
- char: char,
4
+ ch: char,
5
5
  chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
- let mut number = char.to_string();
12
+ let mut number = ch.to_string();
13
13
 
14
14
  while let Some(&c) = chars.peek() {
15
15
  if c.is_ascii_digit() {
@@ -4,7 +4,7 @@ pub fn handle_operator_lexer(
4
4
  ch: char,
5
5
  chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
@@ -16,6 +16,8 @@ pub fn handle_operator_lexer(
16
16
  ('!', Some('=')) => (TokenKind::NotEquals, 2),
17
17
  ('>', Some('=')) => (TokenKind::GreaterEqual, 2),
18
18
  ('<', Some('=')) => (TokenKind::LessEqual, 2),
19
+ ('+', _) => (TokenKind::Plus, 1),
20
+ ('*', _) => (TokenKind::Asterisk, 1),
19
21
  ('=', _) => (TokenKind::Equals, 1),
20
22
  ('>', _) => (TokenKind::Greater, 1),
21
23
  ('<', _) => (TokenKind::Less, 1),
@@ -1,17 +1,17 @@
1
1
  use crate::core::lexer::token::{ Token, TokenKind };
2
2
 
3
3
  pub fn handle_rparen_lexer(
4
- char: char,
5
- chars: &mut std::iter::Peekable<std::str::Chars>,
4
+ ch: char,
5
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
12
  tokens.push(Token {
13
13
  kind: TokenKind::RParen,
14
- lexeme: char.to_string(),
14
+ lexeme: ch.to_string(),
15
15
  line: *line,
16
16
  column: *column,
17
17
  indent: *current_indent,
@@ -21,17 +21,17 @@ pub fn handle_rparen_lexer(
21
21
  }
22
22
 
23
23
  pub fn handle_lparen_lexer(
24
- char: char,
25
- chars: &mut std::iter::Peekable<std::str::Chars>,
24
+ ch: char,
25
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
26
26
  current_indent: &mut usize,
27
- indent_stack: &mut Vec<usize>,
27
+ _indent_stack: &mut Vec<usize>,
28
28
  tokens: &mut Vec<Token>,
29
29
  line: &mut usize,
30
30
  column: &mut usize
31
31
  ) {
32
32
  tokens.push(Token {
33
33
  kind: TokenKind::LParen,
34
- lexeme: char.to_string(),
34
+ lexeme: ch.to_string(),
35
35
  line: *line,
36
36
  column: *column,
37
37
  indent: *current_indent,
@@ -1,21 +1,21 @@
1
1
  use crate::core::lexer::token::{ Token, TokenKind };
2
2
 
3
3
  pub fn handle_slash_lexer(
4
- char: char,
5
- chars: &mut std::iter::Peekable<std::str::Chars>,
4
+ ch: char,
5
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
- let mut slash = char.to_string();
12
+ let slash = ch.to_string();
13
13
 
14
14
  tokens.push(Token {
15
15
  kind: TokenKind::Slash,
16
16
  lexeme: slash,
17
17
  line: *line,
18
18
  column: *column,
19
- indent: *current_indent,
19
+ indent: *current_indent,
20
20
  });
21
21
  }
@@ -4,7 +4,7 @@ pub fn handle_string_lexer(
4
4
  ch: char,
5
5
  chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
@@ -2,7 +2,7 @@ pub mod handler;
2
2
  pub mod token;
3
3
 
4
4
  use std::fs;
5
- use std::path::{Path, PathBuf};
5
+ use std::path::Path;
6
6
  use crate::core::{
7
7
  lexer::{ handler::driver::handle_content_lexing, token::Token },
8
8
  utils::path::normalize_path,
@@ -51,6 +51,9 @@ pub enum TokenKind {
51
51
  Equals,
52
52
  Dot,
53
53
  Slash,
54
+ Plus,
55
+ Asterisk,
56
+ Minus,
54
57
 
55
58
  // ───── Operators ─────
56
59
  DoubleEquals,
@@ -84,6 +87,7 @@ pub enum TokenKind {
84
87
  ElseIf,
85
88
 
86
89
  // ───── Special / Internal ─────
90
+ Whitespace,
87
91
  Unknown,
88
92
  Error(String),
89
93
  EOF,
package/rust/core/mod.rs CHANGED
@@ -7,4 +7,5 @@ pub mod debugger;
7
7
  pub mod utils;
8
8
  pub mod builder;
9
9
  pub mod error;
10
- pub mod audio;
10
+ pub mod audio;
11
+ pub mod plugin;
@@ -7,7 +7,7 @@ use crate::core::{
7
7
  bank::parse_bank_token,
8
8
  condition::parse_condition_token,
9
9
  dot::parse_dot_token,
10
- identifier::{function::parse_function_token, parse_identifier_token},
10
+ identifier::{ function::parse_function_token, parse_identifier_token },
11
11
  loop_::parse_loop_token,
12
12
  tempo::parse_tempo_token,
13
13
  },
@@ -107,7 +107,12 @@ impl Parser {
107
107
  tokens: Vec<Token>,
108
108
  global_store: &mut GlobalStore
109
109
  ) -> Vec<Statement> {
110
- self.tokens = tokens;
110
+ // Filtrer uniquement les espaces, mais conserver les Newline car
111
+ // certaines constructions (ex: print ...) s'appuient sur la fin de ligne.
112
+ self.tokens = tokens
113
+ .into_iter()
114
+ .filter(|t| t.kind != TokenKind::Whitespace)
115
+ .collect();
111
116
  self.token_index = 0;
112
117
 
113
118
  let mut statements = Vec::new();
@@ -120,6 +125,11 @@ impl Parser {
120
125
  }
121
126
  };
122
127
 
128
+ if token.kind == TokenKind::Newline {
129
+ self.advance();
130
+ continue;
131
+ }
132
+
123
133
  let statement = match &token.kind {
124
134
  TokenKind::At => parse_at_token(self, global_store),
125
135
  TokenKind::Identifier => {
@@ -149,7 +159,6 @@ impl Parser {
149
159
  | TokenKind::LBrace
150
160
  | TokenKind::RBrace
151
161
  | TokenKind::Comma
152
- | TokenKind::Newline
153
162
  | TokenKind::Dedent
154
163
  | TokenKind::Indent => {
155
164
  self.advance();
@@ -189,17 +198,57 @@ impl Parser {
189
198
  let mut map = std::collections::HashMap::new();
190
199
 
191
200
  while !self.check_token(TokenKind::RBrace) && !self.is_eof() {
201
+ // Skip separators and formatting before the key
202
+ while
203
+ self.check_token(TokenKind::Newline) ||
204
+ self.check_token(TokenKind::Whitespace) ||
205
+ self.check_token(TokenKind::Indent) ||
206
+ self.check_token(TokenKind::Dedent) ||
207
+ self.check_token(TokenKind::Comma)
208
+ {
209
+ self.advance();
210
+ }
211
+
212
+ // Check if we are at the closing brace of the map
213
+ if self.check_token(TokenKind::RBrace) {
214
+ break;
215
+ }
216
+
192
217
  let key = if let Some(token) = self.advance() {
193
- token.lexeme.clone()
218
+ match token.kind {
219
+ | TokenKind::Whitespace
220
+ | TokenKind::Indent
221
+ | TokenKind::Dedent
222
+ | TokenKind::Newline => {
223
+ continue;
224
+ }
225
+ _ => token.lexeme.clone(),
226
+ }
194
227
  } else {
195
228
  break;
196
229
  };
197
230
 
231
+ // Skip newlines and whitespace before colon
232
+ while self.check_token(TokenKind::Newline) || self.check_token(TokenKind::Whitespace) {
233
+ self.advance();
234
+ }
235
+
198
236
  if !self.match_token(TokenKind::Colon) {
199
237
  println!("Expected ':' after map key '{}'", key);
200
238
  break;
201
239
  }
202
240
 
241
+ // Skip separators and formatting before value
242
+ while
243
+ self.check_token(TokenKind::Newline) ||
244
+ self.check_token(TokenKind::Whitespace) ||
245
+ self.check_token(TokenKind::Indent) ||
246
+ self.check_token(TokenKind::Dedent) ||
247
+ self.check_token(TokenKind::Comma)
248
+ {
249
+ self.advance();
250
+ }
251
+
203
252
  let value = if let Some(token) = self.peek_clone() {
204
253
  match token.kind {
205
254
  TokenKind::String => {
@@ -250,6 +299,11 @@ impl Parser {
250
299
  };
251
300
 
252
301
  map.insert(key, value);
302
+
303
+ // Optionally skip a trailing comma after the value
304
+ while self.check_token(TokenKind::Comma) || self.check_token(TokenKind::Whitespace) || self.check_token(TokenKind::Newline) {
305
+ self.advance();
306
+ }
253
307
  }
254
308
 
255
309
  if !self.match_token(TokenKind::RBrace) {
@@ -259,6 +313,79 @@ impl Parser {
259
313
  Some(Value::Map(map))
260
314
  }
261
315
 
316
+ // Parse an array value like [1, 2, 3] or ["a", b]
317
+ pub fn parse_array_value(&mut self) -> Option<Value> {
318
+ if !self.match_token(TokenKind::LBracket) {
319
+ return None;
320
+ }
321
+
322
+ let mut arr: Vec<Value> = Vec::new();
323
+
324
+ while !self.check_token(TokenKind::RBracket) && !self.is_eof() {
325
+ // Skip formatting tokens
326
+ while
327
+ self.check_token(TokenKind::Newline) ||
328
+ self.check_token(TokenKind::Whitespace) ||
329
+ self.check_token(TokenKind::Indent) ||
330
+ self.check_token(TokenKind::Dedent) ||
331
+ self.check_token(TokenKind::Comma)
332
+ {
333
+ self.advance();
334
+ }
335
+
336
+ if self.check_token(TokenKind::RBracket) {
337
+ break;
338
+ }
339
+
340
+ if let Some(token) = self.peek_clone() {
341
+ let value = match token.kind {
342
+ TokenKind::String => { self.advance(); Value::String(token.lexeme.clone()) }
343
+ TokenKind::Number => {
344
+ // Support simple decimals split as number '.' number
345
+ let mut number_str = token.lexeme.clone();
346
+ self.advance();
347
+ if let Some(dot) = self.peek_clone() {
348
+ if dot.kind == TokenKind::Dot {
349
+ if let Some(next) = self.peek_nth(1).cloned() {
350
+ if next.kind == TokenKind::Number {
351
+ self.advance(); // consume dot
352
+ self.advance(); // consume next number
353
+ number_str.push('.');
354
+ number_str.push_str(&next.lexeme);
355
+ }
356
+ }
357
+ }
358
+ }
359
+ Value::Number(number_str.parse::<f32>().unwrap_or(0.0))
360
+ }
361
+ TokenKind::Identifier => { self.advance(); Value::Identifier(token.lexeme.clone()) }
362
+ TokenKind::LBrace => {
363
+ // Allow inline maps inside arrays
364
+ if let Some(v) = self.parse_map_value() { v } else { Value::Null }
365
+ }
366
+ TokenKind::LBracket => {
367
+ // Nested arrays
368
+ if let Some(v) = self.parse_array_value() { v } else { Value::Null }
369
+ }
370
+ _ => { self.advance(); Value::Null }
371
+ };
372
+
373
+ // Only push non-null (retain alignment with permissive parsing)
374
+ if value != Value::Null { arr.push(value); }
375
+
376
+ // Optional trailing comma handled by the skipper at loop start
377
+ } else {
378
+ break;
379
+ }
380
+ }
381
+
382
+ if !self.match_token(TokenKind::RBracket) {
383
+ println!("Expected ']' at end of array");
384
+ }
385
+
386
+ Some(Value::Array(arr))
387
+ }
388
+
262
389
  pub fn peek(&self) -> Option<&Token> {
263
390
  self.tokens.get(self.token_index)
264
391
  }
@@ -292,19 +419,15 @@ impl Parser {
292
419
  pub fn collect_until<F>(&mut self, condition: F) -> Vec<Token> where F: Fn(&Token) -> bool {
293
420
  let mut collected = Vec::new();
294
421
  while let Some(token) = self.peek() {
295
- if token.kind == TokenKind::Newline || token.kind == TokenKind::Indent {
296
- self.advance(); // Skip newlines and indents
297
- continue;
298
- }
299
- if token.kind == TokenKind::EOF {
422
+ if condition(token) {
300
423
  break;
301
424
  }
302
- if condition(token) {
425
+ if token.kind == TokenKind::EOF {
303
426
  break;
304
427
  }
305
428
  collected.push(self.advance().unwrap().clone());
306
429
  }
307
-
430
+
308
431
  collected
309
432
  }
310
433
 
@@ -5,7 +5,104 @@ use crate::core::{
5
5
  store::global::GlobalStore,
6
6
  };
7
7
 
8
- pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) -> Statement {
8
+ fn parse_map_literal(parser: &mut Parser) -> Value {
9
+ // Assumes '{' has already been consumed by caller
10
+ let mut map = std::collections::HashMap::new();
11
+ loop {
12
+ let Some(inner_token) = parser.peek_clone() else { break; };
13
+
14
+ match inner_token.kind {
15
+ TokenKind::RBrace => {
16
+ parser.advance(); // consume '}'
17
+ break;
18
+ }
19
+ TokenKind::Newline | TokenKind::Comma => {
20
+ parser.advance();
21
+ continue;
22
+ }
23
+ _ => {}
24
+ }
25
+
26
+ // Key
27
+ parser.advance();
28
+ let key = inner_token.lexeme.clone();
29
+
30
+ // Expect ':'
31
+ if let Some(colon_token) = parser.peek_clone() {
32
+ if colon_token.kind == TokenKind::Colon {
33
+ parser.advance(); // consume ':'
34
+
35
+ // Value
36
+ if let Some(value_token) = parser.peek_clone() {
37
+ match value_token.kind {
38
+ TokenKind::LBrace => {
39
+ parser.advance(); // consume '{'
40
+ let nested = parse_map_literal(parser);
41
+ map.insert(key, nested);
42
+ }
43
+ TokenKind::Identifier => {
44
+ parser.advance();
45
+ let v = if value_token.lexeme == "true" {
46
+ Value::Boolean(true)
47
+ } else if value_token.lexeme == "false" {
48
+ Value::Boolean(false)
49
+ } else {
50
+ Value::Identifier(value_token.lexeme.clone())
51
+ };
52
+ map.insert(key, v);
53
+ }
54
+ TokenKind::String => {
55
+ parser.advance();
56
+ map.insert(key, Value::String(value_token.lexeme.clone()));
57
+ }
58
+ TokenKind::Number => {
59
+ parser.advance();
60
+ // Beat fraction support: NUMBER '/' NUMBER
61
+ if let Some(TokenKind::Slash) = parser.peek_kind() {
62
+ parser.advance(); // '/'
63
+ if let Some(den) = parser.peek_clone() {
64
+ if den.kind == TokenKind::Number {
65
+ parser.advance();
66
+ let beat = format!("{}/{}", value_token.lexeme, den.lexeme);
67
+ map.insert(key, Value::Beat(beat));
68
+ continue;
69
+ }
70
+ }
71
+ }
72
+ // Decimal support NUMBER '.' NUMBER
73
+ if let Some(next) = parser.peek_clone() {
74
+ if next.kind == TokenKind::Dot {
75
+ parser.advance(); // '.'
76
+ if let Some(after) = parser.peek_clone() {
77
+ if after.kind == TokenKind::Number {
78
+ parser.advance();
79
+ let combined = format!("{}.{}", value_token.lexeme, after.lexeme);
80
+ map.insert(key, Value::Number(combined.parse::<f32>().unwrap_or(0.0)));
81
+ continue;
82
+ }
83
+ }
84
+ }
85
+ }
86
+ map.insert(key, Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0)));
87
+ }
88
+ TokenKind::Boolean => {
89
+ parser.advance();
90
+ map.insert(key, Value::Boolean(value_token.lexeme.parse::<bool>().unwrap_or(false)));
91
+ }
92
+ _ => {
93
+ // Unknown value type, consume and store Unknown
94
+ parser.advance();
95
+ map.insert(key, Value::Unknown);
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ Value::Map(map)
103
+ }
104
+
105
+ pub fn parse_arrow_call(parser: &mut Parser, _global_store: &mut GlobalStore) -> Statement {
9
106
  let Some(target_token) = parser.peek_clone() else {
10
107
  return Statement::unknown();
11
108
  };
@@ -54,87 +151,66 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
54
151
  parser.advance(); // method
55
152
 
56
153
  let mut args = Vec::new();
154
+ let mut paren_depth = 0;
155
+ let mut map_depth = 0;
57
156
 
58
157
  while let Some(token) = parser.peek_clone() {
59
158
  if token.kind == TokenKind::Newline || token.kind == TokenKind::EOF {
60
159
  break;
61
160
  }
161
+ if token.kind == TokenKind::LParen {
162
+ paren_depth += 1;
163
+ }
164
+ if token.kind == TokenKind::RParen {
165
+ if paren_depth > 0 {
166
+ paren_depth -= 1;
167
+ parser.advance();
168
+ if paren_depth == 0 {
169
+ break;
170
+ }
171
+ continue;
172
+ } else {
173
+ break;
174
+ }
175
+ }
176
+ if token.kind == TokenKind::LBrace {
177
+ map_depth += 1;
178
+ }
179
+ if token.kind == TokenKind::RBrace {
180
+ if map_depth > 0 {
181
+ map_depth -= 1;
182
+ parser.advance();
183
+ if map_depth == 0 {
184
+ continue;
185
+ }
186
+ continue;
187
+ } else {
188
+ break;
189
+ }
190
+ }
62
191
 
63
192
  parser.advance();
64
193
 
65
- let value = match token.kind {
194
+ let value = match token.kind {
66
195
  TokenKind::Identifier => Value::Identifier(token.lexeme.clone()),
67
196
  TokenKind::String => Value::String(token.lexeme.clone()),
68
197
  TokenKind::Number => Value::Number(token.lexeme.parse::<f32>().unwrap_or(0.0)),
69
198
  TokenKind::LBrace => {
70
- // Handle map literal
71
- let mut map = std::collections::HashMap::new();
72
- while let Some(inner_token) = parser.peek_clone() {
73
- if inner_token.kind == TokenKind::RBrace {
74
- parser.advance(); // consume RBrace
75
- break;
76
- }
77
- if inner_token.kind == TokenKind::Newline || inner_token.kind == TokenKind::EOF {
78
- break;
79
- }
80
- parser.advance(); // consume key token
81
- let key = inner_token.lexeme.clone();
82
-
83
- if let Some(colon_token) = parser.peek_clone() {
84
- if colon_token.kind == TokenKind::Colon {
85
- parser.advance(); // consume colon
86
- if let Some(value_token) = parser.peek_clone() {
87
- parser.advance(); // consume value token
88
- let value = match value_token.kind {
89
- TokenKind::Identifier =>
90
- Value::Identifier(value_token.lexeme.clone()),
91
- TokenKind::String => Value::String(value_token.lexeme.clone()),
92
- TokenKind::Number => {
93
- if let Some(TokenKind::Slash) = parser.peek_kind() {
94
- parser.advance(); // consume slash
95
- if let Some(denominator_token) = parser.peek_clone() {
96
- if denominator_token.kind == TokenKind::Number {
97
- parser.advance(); // consume denominator
98
- let denominator =
99
- denominator_token.lexeme.clone();
100
- Value::Beat(
101
- format!(
102
- "{}/{}",
103
- value_token.lexeme,
104
- denominator
105
- )
106
- )
107
- } else {
108
- Value::Unknown
109
- }
110
- } else {
111
- Value::Unknown
112
- }
113
- } else {
114
- // Regular number without slash
115
- Value::Number(
116
- value_token.lexeme.parse::<f32>().unwrap_or(0.0)
117
- )
118
- }
119
- }
120
- TokenKind::Boolean =>
121
- Value::Boolean(
122
- value_token.lexeme.parse::<bool>().unwrap_or(false)
123
- ),
124
-
125
- _ => Value::Unknown,
126
- };
127
- map.insert(key, value);
128
- }
129
- }
130
- }
131
- }
132
- Value::Map(map)
199
+ // Handle map literal (supports nested maps)
200
+ let map_val = parse_map_literal(parser);
201
+ // We consumed the matching '}', so outer map_depth should be decremented
202
+ // if the caller tracks it.
203
+ map_val
133
204
  }
134
205
  _ => Value::Unknown,
135
206
  };
136
207
 
137
208
  args.push(value);
209
+
210
+ // Stop if we reach the end of the statement
211
+ if paren_depth == 0 && (token.kind == TokenKind::RParen || token.kind == TokenKind::RBrace) {
212
+ break;
213
+ }
138
214
  }
139
215
 
140
216
  Statement {
@@ -4,7 +4,7 @@ use crate::core::{
4
4
  shared::value::Value,
5
5
  store::global::GlobalStore,
6
6
  };
7
- pub fn parse_at_token(parser: &mut Parser, global_store: &mut GlobalStore) -> Statement {
7
+ pub fn parse_at_token(parser: &mut Parser, _global_store: &mut GlobalStore) -> Statement {
8
8
  parser.advance(); // consume '@'
9
9
 
10
10
  let Some(token) = parser.peek_clone() else {