@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.
- package/.devalang +2 -3
- package/Cargo.toml +58 -54
- package/README.md +59 -27
- package/docs/CHANGELOG.md +99 -2
- package/docs/CONTRIBUTING.md +1 -0
- package/docs/ROADMAP.md +3 -3
- package/docs/TODO.md +5 -4
- package/examples/automation.deva +44 -0
- package/examples/bank.deva +2 -4
- package/examples/function.deva +15 -0
- package/examples/index.deva +41 -11
- package/examples/plugin.deva +15 -0
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +6 -6
- package/project-version.json +3 -3
- package/rust/cli/bank.rs +16 -16
- package/rust/cli/build.rs +69 -30
- package/rust/cli/check.rs +46 -6
- package/rust/cli/driver.rs +40 -28
- package/rust/cli/install.rs +22 -7
- package/rust/cli/login.rs +134 -0
- package/rust/cli/mod.rs +2 -1
- package/rust/cli/play.rs +44 -19
- package/rust/cli/update.rs +1 -1
- package/rust/common/api.rs +5 -0
- package/rust/common/cdn.rs +3 -9
- package/rust/common/mod.rs +3 -1
- package/rust/common/sso.rs +5 -0
- package/rust/config/driver.rs +19 -1
- package/rust/config/loader.rs +56 -10
- package/rust/core/audio/engine.rs +314 -63
- package/rust/core/audio/evaluator.rs +101 -0
- package/rust/core/audio/interpreter/arrow_call.rs +60 -15
- package/rust/core/audio/interpreter/automate.rs +18 -0
- package/rust/core/audio/interpreter/call.rs +4 -4
- package/rust/core/audio/interpreter/condition.rs +3 -3
- package/rust/core/audio/interpreter/driver.rs +68 -30
- package/rust/core/audio/interpreter/let_.rs +14 -7
- package/rust/core/audio/interpreter/loop_.rs +39 -6
- package/rust/core/audio/interpreter/mod.rs +2 -1
- package/rust/core/audio/interpreter/sleep.rs +2 -4
- package/rust/core/audio/interpreter/spawn.rs +4 -4
- package/rust/core/audio/loader/trigger.rs +2 -5
- package/rust/core/audio/mod.rs +2 -1
- package/rust/core/audio/renderer.rs +1 -1
- package/rust/core/audio/special/easing.rs +120 -0
- package/rust/core/audio/special/env.rs +41 -0
- package/rust/core/audio/special/math.rs +92 -0
- package/rust/core/audio/special/mod.rs +9 -0
- package/rust/core/audio/special/modulator.rs +120 -0
- package/rust/core/builder/mod.rs +11 -6
- package/rust/core/debugger/store.rs +1 -1
- package/rust/core/error/mod.rs +4 -1
- package/rust/core/lexer/handler/arrow.rs +60 -9
- package/rust/core/lexer/handler/at.rs +4 -4
- package/rust/core/lexer/handler/brace.rs +8 -8
- package/rust/core/lexer/handler/colon.rs +4 -4
- package/rust/core/lexer/handler/comment.rs +2 -2
- package/rust/core/lexer/handler/dot.rs +4 -4
- package/rust/core/lexer/handler/driver.rs +42 -13
- package/rust/core/lexer/handler/identifier.rs +5 -4
- package/rust/core/lexer/handler/indent.rs +16 -2
- package/rust/core/lexer/handler/newline.rs +1 -1
- package/rust/core/lexer/handler/number.rs +3 -3
- package/rust/core/lexer/handler/operator.rs +3 -1
- package/rust/core/lexer/handler/parenthesis.rs +8 -8
- package/rust/core/lexer/handler/slash.rs +5 -5
- package/rust/core/lexer/handler/string.rs +1 -1
- package/rust/core/lexer/mod.rs +1 -1
- package/rust/core/lexer/token.rs +4 -0
- package/rust/core/mod.rs +2 -1
- package/rust/core/parser/driver.rs +134 -11
- package/rust/core/parser/handler/arrow_call.rs +141 -65
- package/rust/core/parser/handler/at.rs +1 -1
- package/rust/core/parser/handler/bank.rs +35 -7
- package/rust/core/parser/handler/dot.rs +43 -22
- package/rust/core/parser/handler/identifier/automate.rs +194 -0
- package/rust/core/parser/handler/identifier/function.rs +2 -3
- package/rust/core/parser/handler/identifier/let_.rs +16 -0
- package/rust/core/parser/handler/identifier/mod.rs +14 -10
- package/rust/core/parser/handler/identifier/print.rs +29 -0
- package/rust/core/parser/handler/identifier/sleep.rs +1 -1
- package/rust/core/parser/handler/identifier/synth.rs +7 -9
- package/rust/core/parser/handler/loop_.rs +60 -43
- package/rust/core/parser/statement.rs +5 -0
- package/rust/core/plugin/loader.rs +48 -0
- package/rust/core/plugin/mod.rs +1 -0
- package/rust/core/preprocessor/loader.rs +7 -5
- package/rust/core/preprocessor/processor.rs +4 -4
- package/rust/core/preprocessor/resolver/bank.rs +1 -2
- package/rust/core/preprocessor/resolver/call.rs +19 -18
- package/rust/core/preprocessor/resolver/driver.rs +7 -5
- package/rust/core/preprocessor/resolver/function.rs +3 -13
- package/rust/core/preprocessor/resolver/loop_.rs +31 -1
- package/rust/core/preprocessor/resolver/spawn.rs +3 -22
- package/rust/core/preprocessor/resolver/tempo.rs +1 -1
- package/rust/core/preprocessor/resolver/trigger.rs +2 -3
- package/rust/core/preprocessor/resolver/value.rs +6 -12
- package/rust/core/shared/bank.rs +1 -1
- package/rust/core/utils/path.rs +1 -1
- package/rust/core/utils/validation.rs +0 -1
- package/rust/installer/addon.rs +80 -0
- package/rust/installer/bank.rs +25 -15
- package/rust/installer/mod.rs +4 -1
- package/rust/installer/plugin.rs +55 -0
- package/rust/main.rs +32 -10
- package/rust/utils/error.rs +51 -0
- package/rust/utils/logger.rs +20 -0
- package/rust/utils/mod.rs +1 -44
- 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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use std::collections::HashMap;
|
|
2
2
|
|
|
3
3
|
use crate::core::{
|
|
4
|
-
lexer::token::
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
45
|
-
let message = format!("Expected ':' after loop count, got {:?}",
|
|
46
|
-
return Statement::error(
|
|
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
|
-
|
|
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
|
}
|