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

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 (62) hide show
  1. package/Cargo.toml +54 -53
  2. package/README.md +1 -14
  3. package/docs/CHANGELOG.md +26 -0
  4. package/docs/TODO.md +1 -1
  5. package/examples/index.deva +10 -13
  6. package/out-tsc/bin/devalang.exe +0 -0
  7. package/package.json +1 -1
  8. package/project-version.json +3 -3
  9. package/rust/cli/build.rs +25 -2
  10. package/rust/cli/check.rs +26 -3
  11. package/rust/cli/play.rs +1 -1
  12. package/rust/core/audio/engine.rs +126 -73
  13. package/rust/core/audio/interpreter/call.rs +72 -47
  14. package/rust/core/audio/interpreter/condition.rs +14 -12
  15. package/rust/core/audio/interpreter/driver.rs +84 -127
  16. package/rust/core/audio/interpreter/function.rs +21 -0
  17. package/rust/core/audio/interpreter/load.rs +1 -1
  18. package/rust/core/audio/interpreter/loop_.rs +24 -18
  19. package/rust/core/audio/interpreter/mod.rs +2 -1
  20. package/rust/core/audio/interpreter/sleep.rs +0 -6
  21. package/rust/core/audio/interpreter/spawn.rs +78 -60
  22. package/rust/core/audio/interpreter/trigger.rs +157 -70
  23. package/rust/core/audio/loader/trigger.rs +37 -4
  24. package/rust/core/audio/player.rs +20 -10
  25. package/rust/core/audio/renderer.rs +24 -25
  26. package/rust/core/debugger/mod.rs +2 -0
  27. package/rust/core/debugger/module.rs +47 -0
  28. package/rust/core/debugger/store.rs +25 -11
  29. package/rust/core/error/mod.rs +6 -0
  30. package/rust/core/lexer/handler/driver.rs +23 -1
  31. package/rust/core/lexer/handler/identifier.rs +1 -0
  32. package/rust/core/lexer/handler/mod.rs +1 -0
  33. package/rust/core/lexer/handler/parenthesis.rs +41 -0
  34. package/rust/core/lexer/token.rs +3 -0
  35. package/rust/core/parser/driver.rs +3 -1
  36. package/rust/core/parser/handler/dot.rs +64 -127
  37. package/rust/core/parser/handler/identifier/call.rs +69 -22
  38. package/rust/core/parser/handler/identifier/function.rs +92 -0
  39. package/rust/core/parser/handler/identifier/let_.rs +13 -19
  40. package/rust/core/parser/handler/identifier/mod.rs +1 -0
  41. package/rust/core/parser/handler/identifier/spawn.rs +74 -27
  42. package/rust/core/parser/statement.rs +16 -4
  43. package/rust/core/preprocessor/loader.rs +45 -29
  44. package/rust/core/preprocessor/module.rs +3 -1
  45. package/rust/core/preprocessor/processor.rs +26 -1
  46. package/rust/core/preprocessor/resolver/call.rs +61 -84
  47. package/rust/core/preprocessor/resolver/condition.rs +11 -6
  48. package/rust/core/preprocessor/resolver/driver.rs +52 -6
  49. package/rust/core/preprocessor/resolver/function.rs +78 -0
  50. package/rust/core/preprocessor/resolver/group.rs +43 -13
  51. package/rust/core/preprocessor/resolver/let_.rs +7 -10
  52. package/rust/core/preprocessor/resolver/mod.rs +2 -1
  53. package/rust/core/preprocessor/resolver/spawn.rs +64 -30
  54. package/rust/core/preprocessor/resolver/trigger.rs +7 -3
  55. package/rust/core/preprocessor/resolver/value.rs +10 -1
  56. package/rust/core/shared/value.rs +4 -1
  57. package/rust/core/store/function.rs +34 -0
  58. package/rust/core/store/global.rs +9 -10
  59. package/rust/core/store/mod.rs +2 -1
  60. package/rust/core/store/variable.rs +6 -0
  61. package/rust/lib.rs +10 -7
  62. package/rust/utils/mod.rs +45 -1
@@ -29,6 +29,7 @@ pub enum TokenKind {
29
29
  Tempo,
30
30
  Bank,
31
31
  Loop,
32
+ Function,
32
33
 
33
34
  // ───── Instruments ─────
34
35
  Synth,
@@ -64,6 +65,8 @@ pub enum TokenKind {
64
65
  RBrace, // }
65
66
  LBracket, // [
66
67
  RBracket, // ]
68
+ LParen, // (
69
+ RParen, // )
67
70
 
68
71
  // ───── Quotes ─────
69
72
  Quote, // '
@@ -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::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
  },
@@ -138,6 +138,7 @@ impl Parser {
138
138
  TokenKind::Bank => parse_bank_token(self, global_store),
139
139
  TokenKind::Loop => parse_loop_token(self, global_store),
140
140
  TokenKind::If => parse_condition_token(self, global_store),
141
+ TokenKind::Function => parse_function_token(self, global_store),
141
142
 
142
143
  | TokenKind::Else // Ignore else, already handled in `parse_condition_token`
143
144
  | TokenKind::Comment
@@ -303,6 +304,7 @@ impl Parser {
303
304
  }
304
305
  collected.push(self.advance().unwrap().clone());
305
306
  }
307
+
306
308
  collected
307
309
  }
308
310
 
@@ -1,165 +1,102 @@
1
1
  use crate::core::{
2
2
  lexer::token::TokenKind,
3
- parser::{ statement::{ Statement, StatementKind }, driver::Parser },
4
- shared::{ duration::Duration, value::Value },
5
- store::global::GlobalStore,
3
+ parser::{driver::Parser, statement::{Statement, StatementKind}},
4
+ shared::{duration::Duration, value::Value},
6
5
  };
7
6
 
8
- pub fn parse_dot_token(parser: &mut Parser, _global_store: &mut GlobalStore) -> Statement {
9
- parser.advance(); // consume the first dot
7
+ pub fn parse_dot_token(
8
+ parser: &mut Parser,
9
+ _global_store: &mut crate::core::store::global::GlobalStore
10
+ ) -> Statement {
11
+ parser.advance(); // consume '.'
10
12
 
11
13
  let Some(dot_token) = parser.previous_clone() else {
12
14
  return Statement::unknown();
13
15
  };
14
16
 
15
- // Parse namespaced identifier: .808.kick.snare
17
+ // Parse a single entity (namespace-friendly, stops at newline)
16
18
  let mut parts = Vec::new();
17
19
 
18
20
  while let Some(token) = parser.peek_clone() {
19
21
  match token.kind {
20
- TokenKind::Number => {
21
- // Stop if it's part of a duration
22
- if let Some(TokenKind::Slash) = parser.peek_nth_kind(1) {
23
- break;
24
- }
25
-
22
+ TokenKind::Identifier | TokenKind::Number => {
26
23
  parts.push(token.lexeme.clone());
27
24
  parser.advance();
28
- }
29
-
30
- TokenKind::Identifier => {
31
- // Stop parsing entity name if next token is ':' or if already have one ident and current might be a param
32
- if parts.len() >= 1 {
33
- break; // we've already got the entity
34
- }
35
-
36
- if token.lexeme == "auto" {
25
+ if parser.peek_kind() != Some(TokenKind::Dot) {
37
26
  break;
38
27
  }
39
-
40
- parts.push(token.lexeme.clone());
41
- parser.advance();
42
28
  }
43
-
44
29
  TokenKind::Dot => {
45
- parser.advance(); // continue chaining
30
+ parser.advance();
31
+ }
32
+ TokenKind::Newline | TokenKind::EOF | TokenKind::Indent | TokenKind::Dedent => {
33
+ break; // Stop at newline or dedent
46
34
  }
47
-
48
35
  _ => {
49
36
  break;
50
37
  }
51
38
  }
52
39
  }
53
40
 
54
- let entity = if parts.len() == 1 { parts[0].clone() } else { parts[..=1].join(".") };
55
-
56
- if entity.is_empty() {
57
- return Statement {
58
- kind: StatementKind::Trigger {
59
- entity: String::new(),
60
- duration: Duration::Auto,
61
- },
62
- value: Value::Null,
63
- indent: dot_token.indent,
64
- line: dot_token.line,
65
- column: dot_token.column,
66
- };
67
- }
68
-
69
- // Check if there's a duration
70
- let next = parser.peek_clone();
71
-
72
- let (duration, value) = match next {
73
- None => (Duration::Auto, Value::Null),
74
-
75
- Some(token) =>
76
- match token.kind {
77
- TokenKind::Newline | TokenKind::EOF => (Duration::Auto, Value::Null),
78
-
79
- TokenKind::Number => {
80
- let numerator = token.lexeme.clone();
81
- parser.advance(); // consume numerator
82
-
83
- if let Some(TokenKind::Slash) = parser.peek_kind() {
84
- parser.advance(); // consume slash
85
-
86
- if let Some(denominator_token) = parser.peek_clone() {
87
- if denominator_token.kind == TokenKind::Number {
88
- let denominator = denominator_token.lexeme.clone();
89
- parser.advance(); // consume denominator
90
-
91
- let beat_str = format!("{}/{}", numerator, denominator);
92
- let beat_duration = Duration::Beat(beat_str);
93
-
94
- let val = match parser.peek_clone() {
95
- Some(param_token) if
96
- param_token.kind == TokenKind::Identifier
97
- => {
98
- parser.advance();
99
- Value::Identifier(param_token.lexeme.clone())
100
- }
101
- Some(param_token) if param_token.kind == TokenKind::LBrace => {
102
- parser.parse_map_value().unwrap_or(Value::Null)
103
- }
104
- _ => Value::Null,
105
- };
106
-
107
- return Statement {
108
- kind: StatementKind::Trigger {
109
- entity,
110
- duration: beat_duration,
111
- },
112
- value: val,
113
- indent: dot_token.indent,
114
- line: dot_token.line,
115
- column: dot_token.column,
116
- };
117
- }
118
- }
119
- }
41
+ // Build entity name properly
42
+ let entity = if !parts.is_empty() {
43
+ parts.join(".") // only join within the same line
44
+ } else {
45
+ eprintln!("⚠️ Empty entity after '.' at line {}", dot_token.line);
46
+ String::new()
47
+ };
120
48
 
121
- // fallback: simple numeric duration
122
- let duration = parse_duration(numerator);
49
+ // Optional duration and effects map
50
+ let mut duration = Duration::Auto;
51
+ let mut value = Value::Null;
123
52
 
124
- let val = match parser.peek_clone() {
125
- Some(param_token) if param_token.kind == TokenKind::Identifier => {
53
+ if let Some(token) = parser.peek_clone() {
54
+ match token.kind {
55
+ TokenKind::Number => {
56
+ let numerator = token.lexeme.clone();
57
+ 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();
126
63
  parser.advance();
127
- Value::Identifier(param_token.lexeme.clone())
64
+ duration = Duration::Beat(format!("{}/{}", numerator, denominator));
128
65
  }
129
- Some(param_token) if param_token.kind == TokenKind::LBrace => {
130
- parser.parse_map_value().unwrap_or(Value::Null)
131
- }
132
- _ => Value::Null,
133
- };
134
-
135
- (duration, val)
66
+ }
67
+ } else {
68
+ duration = parse_duration(numerator);
136
69
  }
137
-
138
- TokenKind::Identifier => {
139
- let duration_lexeme = token.lexeme.clone();
140
- parser.advance(); // consume duration
141
-
142
- let val = match parser.peek_clone() {
143
- Some(param_token) if param_token.kind == TokenKind::Identifier => {
144
- parser.advance();
145
- Value::Identifier(param_token.lexeme.clone())
146
- }
147
- Some(param_token) if param_token.kind == TokenKind::LBrace => {
148
- parser.parse_map_value().unwrap_or(Value::Null)
149
- }
150
- _ => Value::Null,
151
- };
152
-
153
- (parse_duration(duration_lexeme), val)
70
+ if let Some(next) = parser.peek_clone() {
71
+ if next.kind == TokenKind::LBrace {
72
+ value = parser.parse_map_value().unwrap_or(Value::Null);
73
+ }
154
74
  }
155
-
156
- _ => (Duration::Auto, Value::Null),
157
75
  }
158
- };
76
+ TokenKind::Identifier => {
77
+ let id = token.lexeme.clone();
78
+ parser.advance();
79
+ 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);
83
+ }
84
+ }
85
+ }
86
+ TokenKind::LBrace => {
87
+ value = parser.parse_map_value().unwrap_or(Value::Null);
88
+ }
89
+ _ => {}
90
+ }
91
+ }
159
92
 
160
93
  Statement {
161
- kind: StatementKind::Trigger { entity, duration },
162
- value,
94
+ kind: StatementKind::Trigger {
95
+ entity,
96
+ duration,
97
+ effects: Some(value.clone()),
98
+ },
99
+ value: Value::Null,
163
100
  indent: dot_token.indent,
164
101
  line: dot_token.line,
165
102
  column: dot_token.column,
@@ -1,6 +1,6 @@
1
1
  use crate::core::{
2
- lexer::token::{ Token, TokenKind },
3
- parser::{ statement::{ Statement, StatementKind }, driver::Parser },
2
+ lexer::token::{Token, TokenKind},
3
+ parser::{statement::{Statement, StatementKind}, driver::Parser},
4
4
  shared::value::Value,
5
5
  store::global::GlobalStore,
6
6
  };
@@ -8,34 +8,81 @@ use crate::core::{
8
8
  pub fn parse_call_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 "call"
14
14
 
15
- let value = if let Some(token) = parser.peek_clone() {
16
- parser.advance();
17
- match token.kind {
18
- TokenKind::Identifier => Value::Identifier(token.lexeme.clone()),
19
- TokenKind::String => Value::String(token.lexeme.clone()),
20
- _ => {
21
- return Statement::error(
22
- token,
23
- "Expected identifier or string after 'call'".to_string()
24
- );
25
- }
15
+ // Expect function name
16
+ let name_token = match parser.peek_clone() {
17
+ Some(t) => t,
18
+ None => {
19
+ return Statement::error(
20
+ current_token,
21
+ "Expected function name after 'call'".to_string()
22
+ );
26
23
  }
27
- } else {
24
+ };
25
+
26
+ if name_token.kind != TokenKind::Identifier {
28
27
  return Statement::error(
29
- current_token,
30
- "Expected identifier or string after 'call'".to_string()
28
+ name_token,
29
+ "Expected function name to be an identifier".to_string()
31
30
  );
32
- };
31
+ }
32
+
33
+ let func_name = name_token.lexeme.clone();
34
+ parser.advance(); // consume function name
35
+
36
+ // Expect '('
37
+ let mut args: Vec<Value> = Vec::new();
38
+ if let Some(open_paren) = parser.peek_clone() {
39
+ if open_paren.kind == TokenKind::LParen {
40
+ parser.advance(); // consume '('
33
41
 
34
- return Statement {
35
- kind: StatementKind::Call,
36
- value,
42
+ // Collect args until ')'
43
+ while let Some(token) = parser.peek_clone() {
44
+ if token.kind == TokenKind::RParen {
45
+ parser.advance(); // consume ')'
46
+ break;
47
+ }
48
+
49
+ match token.kind {
50
+ TokenKind::Number => {
51
+ if let Ok(num) = token.lexeme.parse::<f32>() {
52
+ args.push(Value::Number(num));
53
+ }
54
+ parser.advance();
55
+ }
56
+ TokenKind::String => {
57
+ args.push(Value::String(token.lexeme.clone()));
58
+ parser.advance();
59
+ }
60
+ TokenKind::Identifier => {
61
+ args.push(Value::Identifier(token.lexeme.clone()));
62
+ parser.advance();
63
+ }
64
+ TokenKind::Comma => {
65
+ parser.advance(); // skip comma
66
+ }
67
+ _ => {
68
+ return Statement::error(
69
+ token,
70
+ "Unexpected token in call arguments".to_string()
71
+ );
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ Statement {
79
+ kind: StatementKind::Call {
80
+ name: func_name,
81
+ args,
82
+ },
83
+ value: Value::Null,
37
84
  indent: current_token.indent,
38
85
  line: current_token.line,
39
86
  column: current_token.column,
40
- };
87
+ }
41
88
  }
@@ -0,0 +1,92 @@
1
+ use crate::core::{
2
+ lexer::{ token::TokenKind },
3
+ parser::{ statement::{ Statement, StatementKind }, driver::Parser },
4
+ shared::value::Value,
5
+ store::global::GlobalStore,
6
+ };
7
+
8
+ pub fn parse_function_token(parser: &mut Parser, global_store: &mut GlobalStore) -> Statement {
9
+ parser.advance(); // consume 'fn'
10
+
11
+ let fn_token = match parser.previous_clone() {
12
+ Some(tok) => tok,
13
+ None => return Statement::unknown(),
14
+ };
15
+
16
+ let name_token = match parser.peek_clone() {
17
+ Some(tok) => tok,
18
+ None => return Statement::error(fn_token, "Expected function name after 'fn'".to_string()),
19
+ };
20
+
21
+ if name_token.kind != TokenKind::Identifier {
22
+ return Statement::error(
23
+ name_token.clone(),
24
+ "Expected function name to be an identifier".to_string()
25
+ );
26
+ }
27
+
28
+ let function_name = name_token.lexeme.clone();
29
+ parser.advance(); // consume function name
30
+
31
+ let mut parameters = Vec::new();
32
+ let mut body = Vec::new();
33
+
34
+ // Expect '('
35
+ if parser.peek_kind() != Some(TokenKind::LParen) {
36
+ return Statement::error(name_token.clone(), "Expected '(' after function name".to_string());
37
+ }
38
+ parser.advance(); // consume '('
39
+
40
+ // Parse parameters until ')'
41
+ let tokens = parser.collect_until(|t| t.kind == TokenKind::RParen || t.kind == TokenKind::EOF);
42
+ for token in tokens {
43
+ if token.kind == TokenKind::Identifier {
44
+ parameters.push(token.lexeme.clone());
45
+ }
46
+ }
47
+
48
+ if parser.peek_kind() == Some(TokenKind::RParen) {
49
+ parser.advance(); // consume ')'
50
+ } else {
51
+ return Statement::error(name_token.clone(), "Expected ')' after parameters".to_string());
52
+ }
53
+
54
+ // Expect colon
55
+ if parser.peek_kind() != Some(TokenKind::Colon) {
56
+ return Statement::error(name_token.clone(), "Expected ':' after ')'".to_string());
57
+ }
58
+ parser.advance(); // consume ':'
59
+
60
+ // Collect ALL tokens indented after this line until Dedent
61
+ let base_indent = fn_token.indent;
62
+ let mut body_tokens = Vec::new();
63
+
64
+ while let Some(tok) = parser.peek() {
65
+ if tok.kind == TokenKind::Dedent && tok.indent <= base_indent {
66
+ break;
67
+ }
68
+ body_tokens.push(parser.advance().unwrap().clone());
69
+ }
70
+
71
+ // arse those tokens into block statements
72
+ body = parser.parse_block(body_tokens.clone(), global_store);
73
+
74
+ // Skip Dedent if present
75
+ if let Some(tok) = parser.peek() {
76
+ if tok.kind == TokenKind::Dedent {
77
+ parser.advance();
78
+ }
79
+ }
80
+
81
+ Statement {
82
+ kind: StatementKind::Function {
83
+ name: function_name.clone(),
84
+ parameters: parameters.clone(),
85
+ body: body.clone(),
86
+ },
87
+ value: Value::Null,
88
+ indent: fn_token.indent,
89
+ line: fn_token.line,
90
+ column: fn_token.column,
91
+ }
92
+ }
@@ -4,7 +4,7 @@ use crate::core::{
4
4
  lexer::token::{ Token, TokenKind },
5
5
  parser::{
6
6
  driver::Parser,
7
- handler::identifier::synth::parse_synth_token,
7
+ handler::{ dot::parse_dot_token, identifier::synth::parse_synth_token },
8
8
  statement::{ Statement, StatementKind },
9
9
  },
10
10
  shared::value::Value,
@@ -33,21 +33,15 @@ pub fn parse_let_token(
33
33
  return Statement::error(current_token, "Expected '=' after identifier".to_string());
34
34
  }
35
35
 
36
- if let Some(token) = parser.peek_clone() {
37
- if token.kind == TokenKind::Synth {
36
+ let value = match parser.peek_clone() {
37
+ Some(token) if token.kind == TokenKind::Dot => {
38
+ let dot_stmt = parse_dot_token(parser, global_store);
39
+ Value::Statement(Box::new(dot_stmt))
40
+ }
41
+ Some(token) if token.kind == TokenKind::Synth => {
38
42
  let synth_stmt = parse_synth_token(parser, token.clone(), global_store);
39
-
40
- return Statement {
41
- kind: StatementKind::Let { name: identifier },
42
- value: synth_stmt.value,
43
- indent: current_token.indent,
44
- line: current_token.line,
45
- column: current_token.column,
46
- };
43
+ Value::Statement(Box::new(synth_stmt))
47
44
  }
48
- }
49
-
50
- let value = match parser.peek_clone() {
51
45
  Some(token) if token.kind == TokenKind::Identifier => {
52
46
  parser.advance();
53
47
  Value::Identifier(token.lexeme.clone())
@@ -65,7 +59,8 @@ pub fn parse_let_token(
65
59
  Value::Boolean(token.lexeme.parse().unwrap_or(false))
66
60
  }
67
61
  Some(token) if token.kind == TokenKind::LBrace => {
68
- parser.advance(); // consume '{'
62
+ parser.advance();
63
+
69
64
  let mut map = HashMap::new();
70
65
 
71
66
  while let Some(key_token) = parser.peek_clone() {
@@ -117,15 +112,14 @@ pub fn parse_let_token(
117
112
 
118
113
  Value::Map(map)
119
114
  }
120
- other => {
121
- let message = format!("Unexpected value token in let: {:?}", other);
122
- return Statement::error(current_token, message);
115
+ _ => {
116
+ return Statement::error(current_token, "Unhandled value type after '='".to_string());
123
117
  }
124
118
  };
125
119
 
126
120
  Statement {
127
121
  kind: StatementKind::Let { name: identifier },
128
- value,
122
+ value: value,
129
123
  indent: current_token.indent,
130
124
  line: current_token.line,
131
125
  column: current_token.column,
@@ -4,6 +4,7 @@ pub mod call;
4
4
  pub mod spawn;
5
5
  pub mod sleep;
6
6
  pub mod synth;
7
+ pub mod function;
7
8
 
8
9
  use crate::core::{
9
10
  parser::{
@@ -8,34 +8,81 @@ use crate::core::{
8
8
  pub fn parse_spawn_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
- parser.advance(); // consume "spawn"
14
-
15
- let value = if let Some(token) = parser.peek_clone() {
16
- parser.advance();
17
- match token.kind {
18
- TokenKind::Identifier => Value::Identifier(token.lexeme.clone()),
19
- TokenKind::String => Value::String(token.lexeme.clone()),
20
- _ => {
21
- return Statement::error(
22
- token,
23
- "Expected identifier or string after 'spawn'".to_string()
24
- );
25
- }
26
- }
27
- } else {
13
+ parser.advance(); // consume "spawn"
14
+
15
+ // Expect function name
16
+ let name_token = match parser.peek_clone() {
17
+ Some(t) => t,
18
+ None => {
28
19
  return Statement::error(
29
20
  current_token,
30
- "Expected identifier or string after 'spawn'".to_string()
21
+ "Expected function name after 'spawn'".to_string()
31
22
  );
32
- };
33
-
34
- return Statement {
35
- kind: StatementKind::Spawn,
36
- value,
37
- indent: current_token.indent,
38
- line: current_token.line,
39
- column: current_token.column,
40
- };
41
- }
23
+ }
24
+ };
25
+
26
+ if name_token.kind != TokenKind::Identifier {
27
+ return Statement::error(
28
+ name_token,
29
+ "Expected function name to be an identifier".to_string()
30
+ );
31
+ }
32
+
33
+ let func_name = name_token.lexeme.clone();
34
+ parser.advance(); // consume function name
35
+
36
+ // Expect '('
37
+ let mut args: Vec<Value> = Vec::new();
38
+ if let Some(open_paren) = parser.peek_clone() {
39
+ if open_paren.kind == TokenKind::LParen {
40
+ parser.advance(); // consume '('
41
+
42
+ // Collect args until ')'
43
+ while let Some(token) = parser.peek_clone() {
44
+ if token.kind == TokenKind::RParen {
45
+ parser.advance(); // consume ')'
46
+ break;
47
+ }
48
+
49
+ match token.kind {
50
+ TokenKind::Number => {
51
+ if let Ok(num) = token.lexeme.parse::<f32>() {
52
+ args.push(Value::Number(num));
53
+ }
54
+ parser.advance();
55
+ }
56
+ TokenKind::String => {
57
+ args.push(Value::String(token.lexeme.clone()));
58
+ parser.advance();
59
+ }
60
+ TokenKind::Identifier => {
61
+ args.push(Value::Identifier(token.lexeme.clone()));
62
+ parser.advance();
63
+ }
64
+ TokenKind::Comma => {
65
+ parser.advance(); // skip comma
66
+ }
67
+ _ => {
68
+ return Statement::error(
69
+ token,
70
+ "Unexpected token in spawn arguments".to_string()
71
+ );
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ Statement {
79
+ kind: StatementKind::Spawn {
80
+ name: func_name,
81
+ args,
82
+ },
83
+ value: Value::Null,
84
+ indent: current_token.indent,
85
+ line: current_token.line,
86
+ column: current_token.column,
87
+ }
88
+ }