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