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
173fn is_synth_expr(value: &str) -> bool {
174 let value = value.trim();
175 value == "synth" || value.starts_with("synth ")
176}
177
178pub fn parse_let(
180 line: &str,
181 mut parts: impl Iterator<Item = impl AsRef<str>>,
182 line_number: usize,
183) -> Result<Statement> {
184 let name = parts
185 .next()
186 .ok_or_else(|| anyhow!("let statement requires a name"))?
187 .as_ref()
188 .to_string();
189
190 let remainder = line
191 .splitn(2, '=')
192 .nth(1)
193 .map(|r| r.trim().to_string())
194 .unwrap_or_default();
195
196 let value = if remainder.is_empty() {
197 None
198 } else if is_synth_expr(&remainder) {
199 if remainder.contains("->") {
201 let stmt =
202 crate::language::syntax::parser::driver::parse_arrow_call(&remainder, line_number)?;
203 let mut synth_map = std::collections::HashMap::new();
204 let first_part = remainder.split("->").next().unwrap_or("synth");
205 let synth_def_val = parse_synth_definition(first_part)?;
206 let mut param_map_present = false;
207 if let Value::Map(m) = synth_def_val {
208 param_map_present = m
209 .iter()
210 .any(|(k, v)| k != "type" && k != "waveform" && matches!(v, Value::Map(_)));
211 synth_map = m;
212 }
213
214 if let Value::Map(chain_container) = stmt.value {
215 let mut effects_arr: Vec<Value> = Vec::new();
216
217 if let Some(Value::String(first_method)) = chain_container.get("method") {
218 let mut handled_as_synth_param = false;
219 if let Some(Value::Array(args_arr)) = chain_container.get("args") {
220 if first_method == "type" {
221 return Err(anyhow!(
223 "ERROR: type() is deprecated in v0.2.0. Use preset() instead.\n \
224 Example: synth -> preset(\"lead\") instead of synth -> type(\"lead\")"
225 ));
226 } else if first_method == "preset" {
227 if let Some(first) = args_arr.get(0) {
228 match first {
229 Value::String(s) => {
230 synth_map.insert(
231 "synth_type".to_string(),
232 Value::String(s.clone()),
233 );
234 handled_as_synth_param = true;
235 }
236 Value::Identifier(id) => {
237 synth_map.insert(
238 "synth_type".to_string(),
239 Value::String(id.clone()),
240 );
241 handled_as_synth_param = true;
242 }
243 _ => {}
244 }
245 }
246 } else if first_method == "waveform" || first_method == "wave" {
247 if let Some(first) = args_arr.get(0) {
249 match first {
250 Value::String(s) | Value::Identifier(s) => {
251 synth_map.insert(
252 "waveform".to_string(),
253 Value::String(s.clone()),
254 );
255 handled_as_synth_param = true;
256 }
257 _ => {}
258 }
259 }
260 } else if first_method == "voices" {
261 if let Some(Value::Number(n)) = args_arr.get(0) {
262 synth_map.insert("voices".to_string(), Value::Number(*n));
263 handled_as_synth_param = true;
264 }
265 } else if first_method == "mono" {
266 synth_map.insert("voices".to_string(), Value::Number(1.0));
267 handled_as_synth_param = true;
268 } else if first_method == "poly" {
269 let voices = args_arr
270 .get(0)
271 .and_then(|v| match v {
272 Value::Number(n) => Some(*n),
273 _ => None,
274 })
275 .unwrap_or(8.0);
276 synth_map.insert("voices".to_string(), Value::Number(voices));
277 handled_as_synth_param = true;
278 } else if first_method == "adsr" {
279 if let Some(first) = args_arr.get(0) {
280 if let Value::Map(arg_map) = first {
281 for (k, v) in arg_map.iter() {
282 if ["attack", "decay", "sustain", "release"]
283 .contains(&k.as_str())
284 {
285 synth_map.insert(k.clone(), v.clone());
286 }
287 }
288 handled_as_synth_param = true;
289 }
290 }
291 }
292 }
293
294 if !handled_as_synth_param {
295 let mut eff_map = std::collections::HashMap::new();
296 eff_map.insert("type".to_string(), Value::String(first_method.clone()));
297 if let Some(Value::Array(args_arr)) = chain_container.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 eff_map.insert(k.clone(), v.clone());
302 }
303 } else {
304 eff_map.insert("value".to_string(), first.clone());
305 }
306 }
307 }
308 effects_arr.push(Value::Map(eff_map));
309 }
310 }
311
312 if let Some(Value::Array(chain_arr)) = chain_container.get("chain") {
313 for call_val in chain_arr.iter() {
314 if let Value::Map(call_map) = call_val {
315 if let Some(Value::String(mname)) = call_map.get("method") {
316 if mname == "type" {
317 return Err(anyhow!(
319 "ERROR: type() is deprecated in v0.2.0. Use preset() instead.\n \
320 Example: synth -> preset(\"lead\") instead of synth -> type(\"lead\")"
321 ));
322 } else if mname == "preset" {
323 if let Some(Value::Array(args_arr)) = call_map.get("args") {
324 if let Some(first) = args_arr.get(0) {
325 match first {
326 Value::String(s) => {
327 synth_map.insert(
328 "synth_type".to_string(),
329 Value::String(s.clone()),
330 );
331 continue;
332 }
333 Value::Identifier(id) => {
334 synth_map.insert(
335 "synth_type".to_string(),
336 Value::String(id.clone()),
337 );
338 continue;
339 }
340 _ => {}
341 }
342 }
343 }
344 }
345 if mname == "waveform" || mname == "wave" {
346 if let Some(Value::Array(args_arr)) = call_map.get("args") {
348 if let Some(first) = args_arr.get(0) {
349 match first {
350 Value::String(s) | Value::Identifier(s) => {
351 synth_map.insert(
352 "waveform".to_string(),
353 Value::String(s.clone()),
354 );
355 continue;
356 }
357 _ => {}
358 }
359 }
360 }
361 }
362 if mname == "voices" {
363 if let Some(Value::Array(args_arr)) = call_map.get("args") {
364 if let Some(Value::Number(n)) = args_arr.get(0) {
365 synth_map
366 .insert("voices".to_string(), Value::Number(*n));
367 continue;
368 }
369 }
370 }
371 if mname == "mono" {
372 synth_map.insert("voices".to_string(), Value::Number(1.0));
373 continue;
374 }
375 if mname == "poly" {
376 let voices = call_map
377 .get("args")
378 .and_then(|v| match v {
379 Value::Array(args_arr) => args_arr.get(0),
380 _ => None,
381 })
382 .and_then(|v| match v {
383 Value::Number(n) => Some(*n),
384 _ => None,
385 })
386 .unwrap_or(8.0);
387 synth_map.insert("voices".to_string(), Value::Number(voices));
388 continue;
389 }
390 if mname == "adsr" {
391 if let Some(Value::Array(args_arr)) = call_map.get("args") {
392 if let Some(first) = args_arr.get(0) {
393 if let Value::Map(arg_map) = first {
394 for (k, v) in arg_map.iter() {
395 if ["attack", "decay", "sustain", "release"]
396 .contains(&k.as_str())
397 {
398 synth_map.insert(k.clone(), v.clone());
399 }
400 }
401 continue;
402 }
403 }
404 }
405 }
406 let mut eff_map = std::collections::HashMap::new();
407 eff_map.insert("type".to_string(), Value::String(mname.clone()));
408 if let Some(Value::Array(args_arr)) = call_map.get("args") {
409 if let Some(first) = args_arr.get(0) {
410 if let Value::Map(arg_map) = first {
411 for (k, v) in arg_map.iter() {
412 eff_map.insert(k.clone(), v.clone());
413 }
414 } else {
415 eff_map.insert("value".to_string(), first.clone());
416 }
417 }
418 }
419 effects_arr.push(Value::Map(eff_map));
420 }
421 }
422 }
423 }
424
425 if !effects_arr.is_empty() {
426 synth_map.insert("chain".to_string(), Value::Array(effects_arr));
427 }
428 }
429
430 if param_map_present && synth_map.contains_key("chain") {
431 eprintln!(
432 "DEPRECATION: chained params for synth with param map are deprecated — both will be merged, but prefer chained params."
433 );
434 }
435
436 Some(Value::Map(synth_map))
437 } else {
438 Some(parse_synth_definition(&remainder)?)
439 }
440 } else if remainder.starts_with('[') && remainder.ends_with(']') {
441 Some(parse_array_value(&remainder)?)
442 } else if remainder.starts_with('.') {
443 let stmt = crate::language::syntax::parser::driver::trigger::parse_trigger_line(
444 &remainder,
445 line_number,
446 )?;
447 Some(crate::language::syntax::ast::Value::Statement(Box::new(
448 stmt,
449 )))
450 } else {
451 let lower = remainder.to_lowercase();
452 if lower == "true" {
453 Some(Value::Boolean(true))
454 } else if lower == "false" {
455 Some(Value::Boolean(false))
456 } else if let Ok(num) = remainder.parse::<f32>() {
457 Some(Value::Number(num))
458 } else {
459 Some(Value::Identifier(remainder))
460 }
461 };
462
463 Ok(Statement::new(
464 StatementKind::Let { name, value },
465 Value::Null,
466 0,
467 line_number,
468 1,
469 ))
470}
471
472pub fn parse_var(
474 line: &str,
475 mut parts: impl Iterator<Item = impl AsRef<str>>,
476 line_number: usize,
477) -> Result<Statement> {
478 let name = parts
479 .next()
480 .ok_or_else(|| anyhow!("var statement requires a name"))?
481 .as_ref()
482 .to_string();
483
484 let remainder = line
485 .splitn(2, '=')
486 .nth(1)
487 .map(|r| r.trim().to_string())
488 .unwrap_or_default();
489
490 let value = if remainder.is_empty() {
491 None
492 } else {
493 let lower = remainder.to_lowercase();
495 if lower == "true" {
496 Some(Value::Boolean(true))
497 } else if lower == "false" {
498 Some(Value::Boolean(false))
499 } else if let Ok(num) = remainder.parse::<f32>() {
500 Some(Value::Number(num))
501 } else {
502 Some(Value::Identifier(remainder))
503 }
504 };
505
506 Ok(Statement::new(
507 StatementKind::Var { name, value },
508 Value::Null,
509 0,
510 line_number,
511 1,
512 ))
513}
514
515pub fn parse_const(
517 line: &str,
518 mut parts: impl Iterator<Item = impl AsRef<str>>,
519 line_number: usize,
520) -> Result<Statement> {
521 let name = parts
522 .next()
523 .ok_or_else(|| anyhow!("const statement requires a name"))?
524 .as_ref()
525 .to_string();
526
527 let remainder = line
528 .splitn(2, '=')
529 .nth(1)
530 .map(|r| r.trim().to_string())
531 .unwrap_or_default();
532
533 if remainder.is_empty() {
534 return Err(anyhow!("const declaration requires initialization"));
535 }
536
537 let value = {
539 if is_synth_expr(&remainder) {
542 let fake_line = format!("let __const = {}", remainder);
543 let mut fake_parts = fake_line.split_whitespace();
544 let _ = fake_parts.next();
545 let stmt = parse_let(&fake_line, fake_parts, line_number)?;
546 if let StatementKind::Let { value, .. } = stmt.kind {
547 value
548 } else {
549 None
550 }
551 } else if remainder.starts_with('[') && remainder.ends_with(']') {
552 Some(parse_array_value(&remainder)?)
553 } else if remainder.starts_with('.') {
554 let stmt = crate::language::syntax::parser::driver::trigger::parse_trigger_line(
555 &remainder,
556 line_number,
557 )?;
558 Some(crate::language::syntax::ast::Value::Statement(Box::new(
559 stmt,
560 )))
561 } else {
562 let lower = remainder.to_lowercase();
563 if lower == "true" {
564 Some(Value::Boolean(true))
565 } else if lower == "false" {
566 Some(Value::Boolean(false))
567 } else if let Ok(num) = remainder.parse::<f32>() {
568 Some(Value::Number(num))
569 } else {
570 Some(Value::Identifier(remainder))
571 }
572 }
573 };
574
575 Ok(Statement::new(
576 StatementKind::Const { name, value },
577 Value::Null,
578 0,
579 line_number,
580 1,
581 ))
582}
583
584pub fn parse_return(line: &str, line_number: usize) -> Result<Statement> {
586 let remainder = line
588 .strip_prefix("return")
589 .ok_or_else(|| anyhow!("invalid return statement"))?
590 .trim();
591
592 if remainder.is_empty() {
593 Ok(Statement::new(
594 StatementKind::Return { value: None },
595 Value::Null,
596 0,
597 line_number,
598 1,
599 ))
600 } else {
601 let val = crate::language::syntax::parser::driver::helpers::parse_single_arg(remainder)?;
603 Ok(Statement::new(
604 StatementKind::Return {
605 value: Some(Box::new(val)),
606 },
607 Value::Null,
608 0,
609 line_number,
610 1,
611 ))
612 }
613}