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
173/// Parse let statement
174pub 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        // Reuse existing synth parsing logic (supports chained params and param maps)
195        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
378/// Parse var statement
379pub 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        // Try to parse booleans first, then numbers, then identifiers
400        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
421/// Parse const statement
422pub 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    // Try to parse booleans first, then numbers, then identifiers
444    let value = {
445        // Reuse the same parsing rules as `let` for RHS expressions so that
446        // const can accept synth definitions, arrays, triggers, numbers, and identifiers.
447        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
490/// Parse return statement: return <expr>?
491pub fn parse_return(line: &str, line_number: usize) -> Result<Statement> {
492    // strip 'return' keyword
493    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        // Parse a single argument/expression
508        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}