devalang_wasm/language/syntax/parser/driver/statements/
core.rs1use super::super::duration::parse_duration_token;
2use super::super::helpers::{parse_array_value, parse_synth_definition};
3use crate::language::syntax::ast::{Statement, StatementKind, Value};
4use anyhow::{Result, anyhow};
6use std::iter::Iterator;
7
8pub fn parse_tempo(line: &str, line_number: usize) -> Result<Statement> {
10 let trimmed = line.trim_start();
11 let keyword_end = if trimmed.starts_with("tempo") {
13 "tempo".len()
14 } else if trimmed.starts_with("bpm") {
15 "bpm".len()
16 } else {
17 return Err(anyhow!("tempo/bpm parsing error"));
18 };
19
20 let rest = trimmed[keyword_end..].trim();
21
22 let is_block = rest.ends_with(':');
24
25 let value_str = if is_block {
27 rest[..rest.len() - 1].trim()
28 } else {
29 rest
30 };
31
32 let bpm: f32 = value_str
33 .parse()
34 .map_err(|_| anyhow!("invalid tempo value: '{}'", value_str))?;
35
36 if is_block {
37 Ok(Statement::new(
39 StatementKind::Tempo {
40 value: bpm,
41 body: Some(Vec::new()),
42 },
43 Value::Number(bpm),
44 0,
45 line_number,
46 1,
47 ))
48 } else {
49 Ok(Statement::new(
51 StatementKind::Tempo {
52 value: bpm,
53 body: None,
54 },
55 Value::Number(bpm),
56 0,
57 line_number,
58 1,
59 ))
60 }
61}
62
63pub fn parse_print(line: &str, line_number: usize) -> Result<Statement> {
65 let message = line
66 .strip_prefix("print")
67 .ok_or_else(|| {
68 anyhow!(
69 "Invalid print statement: expected 'print' keyword at line {}",
70 line_number
71 )
72 })?
73 .trim();
74
75 if message.starts_with('"') && message.ends_with('"') && message.len() >= 2 {
78 let cleaned = message[1..message.len() - 1].to_string();
79 Ok(Statement::new(
80 StatementKind::Print,
81 Value::String(cleaned),
82 0,
83 line_number,
84 1,
85 ))
86 } else if let Ok(num) = message.parse::<f32>() {
87 Ok(Statement::new(
88 StatementKind::Print,
89 Value::Number(num),
90 0,
91 line_number,
92 1,
93 ))
94 } else {
95 if message.contains('+') {
100 let parts: Vec<Value> = message
101 .split('+')
102 .map(|p| p.trim())
103 .filter(|s| !s.is_empty())
104 .map(|tok| {
105 crate::language::syntax::parser::driver::helpers::parse_single_arg(tok)
106 .unwrap_or(Value::Identifier(tok.to_string()))
107 })
108 .collect();
109
110 Ok(Statement::new(
111 StatementKind::Print,
112 Value::Array(parts),
113 0,
114 line_number,
115 1,
116 ))
117 } else {
118 let val = crate::language::syntax::parser::driver::helpers::parse_single_arg(message)?;
120 Ok(Statement::new(StatementKind::Print, val, 0, line_number, 1))
121 }
122 }
123}
124
125pub fn parse_sleep(
127 mut parts: impl Iterator<Item = impl AsRef<str>>,
128 line_number: usize,
129) -> Result<Statement> {
130 let value = parts
131 .next()
132 .ok_or_else(|| anyhow!("sleep instruction requires a duration"))?;
133 let duration = parse_duration_token(value.as_ref())?;
134 Ok(Statement::new(
135 StatementKind::Sleep,
136 Value::Duration(duration),
137 0,
138 line_number,
139 1,
140 ))
141}
142
143pub fn parse_bank(
145 mut parts: impl Iterator<Item = impl AsRef<str>>,
146 line_number: usize,
147) -> Result<Statement> {
148 let name = parts
149 .next()
150 .ok_or_else(|| anyhow!("bank declaration requires a name"))?
151 .as_ref()
152 .to_string();
153
154 let alias = if let Some(word) = parts.next() {
155 if word.as_ref() == "as" {
156 parts.next().map(|v| v.as_ref().to_string())
157 } else {
158 None
159 }
160 } else {
161 None
162 };
163
164 Ok(Statement::new(
165 StatementKind::Bank { name, alias },
166 Value::Null,
167 0,
168 line_number,
169 1,
170 ))
171}
172
173pub fn parse_let(
175 line: &str,
176 mut parts: impl Iterator<Item = impl AsRef<str>>,
177 line_number: usize,
178) -> Result<Statement> {
179 let name = parts
180 .next()
181 .ok_or_else(|| anyhow!("let statement requires a name"))?
182 .as_ref()
183 .to_string();
184
185 let remainder = line
186 .splitn(2, '=')
187 .nth(1)
188 .map(|r| r.trim().to_string())
189 .unwrap_or_default();
190
191 let value = if remainder.is_empty() {
192 None
193 } else if remainder.starts_with("synth ") {
194 if remainder.contains("->") {
196 let stmt =
197 crate::language::syntax::parser::driver::parse_arrow_call(&remainder, line_number)?;
198 let mut synth_map = std::collections::HashMap::new();
199 let first_part = remainder.split("->").next().unwrap_or("synth");
200 let synth_def_val = parse_synth_definition(first_part)?;
201 let mut param_map_present = false;
202 if let Value::Map(m) = synth_def_val {
203 param_map_present = m
204 .iter()
205 .any(|(k, v)| k != "type" && k != "waveform" && matches!(v, Value::Map(_)));
206 synth_map = m;
207 }
208
209 if let Value::Map(chain_container) = stmt.value {
210 let mut effects_arr: Vec<Value> = Vec::new();
211
212 if let Some(Value::String(first_method)) = chain_container.get("method") {
213 let mut handled_as_synth_param = false;
214 if let Some(Value::Array(args_arr)) = chain_container.get("args") {
215 if first_method == "type" {
216 if let Some(first) = args_arr.get(0) {
217 match first {
218 Value::String(s) => {
219 synth_map.insert(
220 "synth_type".to_string(),
221 Value::String(s.clone()),
222 );
223 handled_as_synth_param = true;
224 }
225 Value::Identifier(id) => {
226 synth_map.insert(
227 "synth_type".to_string(),
228 Value::String(id.clone()),
229 );
230 handled_as_synth_param = true;
231 }
232 _ => {}
233 }
234 }
235 } else if first_method == "adsr" {
236 if let Some(first) = args_arr.get(0) {
237 if let Value::Map(arg_map) = first {
238 for (k, v) in arg_map.iter() {
239 if ["attack", "decay", "sustain", "release"]
240 .contains(&k.as_str())
241 {
242 synth_map.insert(k.clone(), v.clone());
243 }
244 }
245 handled_as_synth_param = true;
246 }
247 }
248 }
249 }
250
251 if !handled_as_synth_param {
252 let mut eff_map = std::collections::HashMap::new();
253 eff_map.insert("type".to_string(), Value::String(first_method.clone()));
254 if let Some(Value::Array(args_arr)) = chain_container.get("args") {
255 if let Some(first) = args_arr.get(0) {
256 if let Value::Map(arg_map) = first {
257 for (k, v) in arg_map.iter() {
258 eff_map.insert(k.clone(), v.clone());
259 }
260 } else {
261 eff_map.insert("value".to_string(), first.clone());
262 }
263 }
264 }
265 effects_arr.push(Value::Map(eff_map));
266 }
267 }
268
269 if let Some(Value::Array(chain_arr)) = chain_container.get("chain") {
270 for call_val in chain_arr.iter() {
271 if let Value::Map(call_map) = call_val {
272 if let Some(Value::String(mname)) = call_map.get("method") {
273 if mname == "type" {
274 if let Some(Value::Array(args_arr)) = call_map.get("args") {
275 if let Some(first) = args_arr.get(0) {
276 match first {
277 Value::String(s) => {
278 synth_map.insert(
279 "synth_type".to_string(),
280 Value::String(s.clone()),
281 );
282 continue;
283 }
284 Value::Identifier(id) => {
285 synth_map.insert(
286 "synth_type".to_string(),
287 Value::String(id.clone()),
288 );
289 continue;
290 }
291 _ => {}
292 }
293 }
294 }
295 }
296 if mname == "adsr" {
297 if let Some(Value::Array(args_arr)) = call_map.get("args") {
298 if let Some(first) = args_arr.get(0) {
299 if let Value::Map(arg_map) = first {
300 for (k, v) in arg_map.iter() {
301 if ["attack", "decay", "sustain", "release"]
302 .contains(&k.as_str())
303 {
304 synth_map.insert(k.clone(), v.clone());
305 }
306 }
307 continue;
308 }
309 }
310 }
311 }
312 let mut eff_map = std::collections::HashMap::new();
313 eff_map.insert("type".to_string(), Value::String(mname.clone()));
314 if let Some(Value::Array(args_arr)) = call_map.get("args") {
315 if let Some(first) = args_arr.get(0) {
316 if let Value::Map(arg_map) = first {
317 for (k, v) in arg_map.iter() {
318 eff_map.insert(k.clone(), v.clone());
319 }
320 } else {
321 eff_map.insert("value".to_string(), first.clone());
322 }
323 }
324 }
325 effects_arr.push(Value::Map(eff_map));
326 }
327 }
328 }
329 }
330
331 if !effects_arr.is_empty() {
332 synth_map.insert("chain".to_string(), Value::Array(effects_arr));
333 }
334 }
335
336 if param_map_present && synth_map.contains_key("chain") {
337 eprintln!(
338 "DEPRECATION: chained params for synth with param map are deprecated — both will be merged, but prefer chained params."
339 );
340 }
341
342 Some(Value::Map(synth_map))
343 } else {
344 Some(parse_synth_definition(&remainder)?)
345 }
346 } else if remainder.starts_with('[') && remainder.ends_with(']') {
347 Some(parse_array_value(&remainder)?)
348 } else if remainder.starts_with('.') {
349 let stmt = crate::language::syntax::parser::driver::trigger::parse_trigger_line(
350 &remainder,
351 line_number,
352 )?;
353 Some(crate::language::syntax::ast::Value::Statement(Box::new(
354 stmt,
355 )))
356 } else {
357 let lower = remainder.to_lowercase();
358 if lower == "true" {
359 Some(Value::Boolean(true))
360 } else if lower == "false" {
361 Some(Value::Boolean(false))
362 } else if let Ok(num) = remainder.parse::<f32>() {
363 Some(Value::Number(num))
364 } else {
365 Some(Value::Identifier(remainder))
366 }
367 };
368
369 Ok(Statement::new(
370 StatementKind::Let { name, value },
371 Value::Null,
372 0,
373 line_number,
374 1,
375 ))
376}
377
378pub fn parse_var(
380 line: &str,
381 mut parts: impl Iterator<Item = impl AsRef<str>>,
382 line_number: usize,
383) -> Result<Statement> {
384 let name = parts
385 .next()
386 .ok_or_else(|| anyhow!("var statement requires a name"))?
387 .as_ref()
388 .to_string();
389
390 let remainder = line
391 .splitn(2, '=')
392 .nth(1)
393 .map(|r| r.trim().to_string())
394 .unwrap_or_default();
395
396 let value = if remainder.is_empty() {
397 None
398 } else {
399 let lower = remainder.to_lowercase();
401 if lower == "true" {
402 Some(Value::Boolean(true))
403 } else if lower == "false" {
404 Some(Value::Boolean(false))
405 } else if let Ok(num) = remainder.parse::<f32>() {
406 Some(Value::Number(num))
407 } else {
408 Some(Value::Identifier(remainder))
409 }
410 };
411
412 Ok(Statement::new(
413 StatementKind::Var { name, value },
414 Value::Null,
415 0,
416 line_number,
417 1,
418 ))
419}
420
421pub fn parse_const(
423 line: &str,
424 mut parts: impl Iterator<Item = impl AsRef<str>>,
425 line_number: usize,
426) -> Result<Statement> {
427 let name = parts
428 .next()
429 .ok_or_else(|| anyhow!("const statement requires a name"))?
430 .as_ref()
431 .to_string();
432
433 let remainder = line
434 .splitn(2, '=')
435 .nth(1)
436 .map(|r| r.trim().to_string())
437 .unwrap_or_default();
438
439 if remainder.is_empty() {
440 return Err(anyhow!("const declaration requires initialization"));
441 }
442
443 let value = {
445 if remainder.starts_with("synth ") {
448 if remainder.contains("->") {
449 let stmt = crate::language::syntax::parser::driver::parse_arrow_call(
450 &remainder,
451 line_number,
452 )?;
453 Some(stmt.value)
454 } else {
455 Some(parse_synth_definition(&remainder)?)
456 }
457 } else if remainder.starts_with('[') && remainder.ends_with(']') {
458 Some(parse_array_value(&remainder)?)
459 } else if remainder.starts_with('.') {
460 let stmt = crate::language::syntax::parser::driver::trigger::parse_trigger_line(
461 &remainder,
462 line_number,
463 )?;
464 Some(crate::language::syntax::ast::Value::Statement(Box::new(
465 stmt,
466 )))
467 } else {
468 let lower = remainder.to_lowercase();
469 if lower == "true" {
470 Some(Value::Boolean(true))
471 } else if lower == "false" {
472 Some(Value::Boolean(false))
473 } else if let Ok(num) = remainder.parse::<f32>() {
474 Some(Value::Number(num))
475 } else {
476 Some(Value::Identifier(remainder))
477 }
478 }
479 };
480
481 Ok(Statement::new(
482 StatementKind::Const { name, value },
483 Value::Null,
484 0,
485 line_number,
486 1,
487 ))
488}
489
490pub fn parse_return(line: &str, line_number: usize) -> Result<Statement> {
492 let remainder = line
494 .strip_prefix("return")
495 .ok_or_else(|| anyhow!("invalid return statement"))?
496 .trim();
497
498 if remainder.is_empty() {
499 Ok(Statement::new(
500 StatementKind::Return { value: None },
501 Value::Null,
502 0,
503 line_number,
504 1,
505 ))
506 } else {
507 let val = crate::language::syntax::parser::driver::helpers::parse_single_arg(remainder)?;
509 Ok(Statement::new(
510 StatementKind::Return {
511 value: Some(Box::new(val)),
512 },
513 Value::Null,
514 0,
515 line_number,
516 1,
517 ))
518 }
519}