Skip to main content

devalang_wasm/language/syntax/parser/driver/statements/
core.rs

1use super::super::duration::parse_duration_token;
2use super::super::helpers::{parse_array_value, parse_synth_definition};
3use crate::language::syntax::ast::{Statement, StatementKind, Value};
4/// Core statement parsing: tempo, print, let, var, const, sleep, bank
5use anyhow::{Result, anyhow};
6use std::iter::Iterator;
7
8/// Parse tempo/bpm statement
9pub fn parse_tempo(line: &str, line_number: usize) -> Result<Statement> {
10    let trimmed = line.trim_start();
11    // Skip keyword (bpm or tempo)
12    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    // Check if it's a block (ends with :)
23    let is_block = rest.ends_with(':');
24
25    // Remove the colon if present
26    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        // Return a Tempo statement with body (will be filled during block parsing)
38        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        // Return a simple Tempo statement
50        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
63/// Parse print statement
64pub 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 is a quoted string, keep it as String; if it's a number, parse as Number;
76    // otherwise treat as an identifier (variable name) so it can be resolved at runtime.
77    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        // Support simple concatenation using '+' operator in print expressions, e.g.
96        //   print "Loop iteration: " + i
97        // We split on '+' and create a Value::Array of parts so runtime can resolve
98        // each part and join them when printing.
99        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            // Attempt to parse the message as a full expression (supports function calls)
119            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
125/// Parse sleep statement
126pub 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
143/// Parse bank statement
144pub 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
178/// Parse let statement
179pub 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        // Reuse existing synth parsing logic (supports chained params and param maps)
200        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                            // v0.2.0: type() is deprecated, use preset() instead
222                            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                            // v0.2.0: both waveform and wave are aliases
248                            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                                    // v0.2.0: type() is deprecated, use preset() instead
318                                    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                                    // v0.2.0: both waveform and wave are aliases
347                                    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
472/// Parse var statement
473pub 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        // Try to parse booleans first, then numbers, then identifiers
494        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
515/// Parse const statement
516pub 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    // Try to parse booleans first, then numbers, then identifiers
538    let value = {
539        // Reuse the same parsing rules as `let` for RHS expressions so that
540        // const can accept synth definitions, arrays, triggers, numbers, and identifiers.
541        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
584/// Parse return statement: return <expr>?
585pub fn parse_return(line: &str, line_number: usize) -> Result<Statement> {
586    // strip 'return' keyword
587    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        // Parse a single argument/expression
602        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}