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

1use super::super::helpers::{parse_array_value, parse_condition};
2use crate::language::syntax::ast::{Statement, StatementKind, Value};
3/// Structure statement parsing: group, pattern, loop, for, if, on, emit, call, spawn
4use anyhow::{Result, anyhow};
5use std::collections::HashMap;
6use std::iter::Iterator;
7
8/// Parse pattern statement
9/// Supports:
10/// - pattern name with target = "pattern"
11/// - pattern name with target { options } = "pattern"
12pub fn parse_pattern(
13    mut parts: impl Iterator<Item = impl AsRef<str>>,
14    line_number: usize,
15) -> Result<Statement> {
16    let name = parts
17        .next()
18        .ok_or_else(|| anyhow!("pattern requires a name"))?
19        .as_ref()
20        .to_string();
21
22    let mut target = None;
23    let mut pattern_str = None;
24    let mut options: HashMap<String, Value> = HashMap::new();
25
26    // Check for "with" keyword
27    if let Some(word) = parts.next() {
28        if word.as_ref() == "with" {
29            // Next is the target
30            target = parts.next().map(|v| v.as_ref().to_string());
31
32            // Collect remaining parts to check for options block or pattern
33            let rest: Vec<String> = parts.map(|s| s.as_ref().to_string()).collect();
34            let joined = rest.join(" ");
35
36            // Check if there's an options block: { key: value, key: value }
37            if let Some(brace_start) = joined.find('{') {
38                if let Some(brace_end) = joined.rfind('}') {
39                    // Extract options block
40                    let options_str = &joined[brace_start + 1..brace_end];
41
42                    // Parse options (key: value pairs)
43                    for pair in options_str.split(',') {
44                        let parts: Vec<&str> = pair.split(':').collect();
45                        if parts.len() == 2 {
46                            let key = parts[0].trim().to_string();
47                            let value_str = parts[1].trim();
48
49                            // Parse value
50                            let value = if let Ok(num) = value_str.parse::<f32>() {
51                                Value::Number(num)
52                            } else if value_str == "true" {
53                                Value::Boolean(true)
54                            } else if value_str == "false" {
55                                Value::Boolean(false)
56                            } else if value_str.starts_with('"') && value_str.ends_with('"') {
57                                Value::String(value_str.trim_matches('"').to_string())
58                            } else {
59                                Value::Identifier(value_str.to_string())
60                            };
61
62                            options.insert(key, value);
63                        }
64                    }
65
66                    // Check for "=" and pattern after the brace
67                    let after_brace = joined[brace_end + 1..].trim();
68                    if let Some(eq_pos) = after_brace.find('=') {
69                        let pattern_part = after_brace[eq_pos + 1..].trim();
70                        pattern_str = Some(pattern_part.trim_matches('"').to_string());
71                    }
72                } else {
73                    return Err(anyhow!("Unclosed brace in pattern options"));
74                }
75            } else {
76                // No options block, check for "=" and pattern directly
77                if let Some(eq_pos) = joined.find('=') {
78                    let pattern_part = joined[eq_pos + 1..].trim();
79                    pattern_str = Some(pattern_part.trim_matches('"').to_string());
80                }
81            }
82        }
83    }
84
85    // Store pattern string and options in value
86    let value = if !options.is_empty() {
87        let mut map = options;
88        if let Some(pat) = pattern_str {
89            map.insert("pattern".to_string(), Value::String(pat));
90        }
91        Value::Map(map)
92    } else {
93        pattern_str.map(Value::String).unwrap_or(Value::Null)
94    };
95
96    Ok(Statement::new(
97        StatementKind::Pattern { name, target },
98        value,
99        0,
100        line_number,
101        1,
102    ))
103}
104
105/// Parse group statement: group <name>: or group <name> during <duration>:
106/// Example: group verse: or group verse during 16 bars:
107pub fn parse_group(
108    mut parts: impl Iterator<Item = impl AsRef<str>>,
109    line_number: usize,
110) -> Result<Statement> {
111    let name = parts
112        .next()
113        .ok_or_else(|| anyhow!("group requires a name"))?
114        .as_ref()
115        .trim_end_matches(':')
116        .to_string();
117
118    // Check for "during" keyword for optional duration
119    let next = parts.next();
120    let duration = if next.as_ref().map(|w| w.as_ref()) == Some("during") {
121        // Parse duration (everything after 'during')
122        let duration_str = parts
123            .map(|p| p.as_ref().to_string())
124            .collect::<Vec<_>>()
125            .join(" ")
126            .trim_end_matches(':')
127            .to_string();
128
129        Some(super::super::duration::parse_duration_token(&duration_str)?)
130    } else {
131        None
132    };
133
134    Ok(Statement::new(
135        StatementKind::Group {
136            name: name.clone(),
137            body: Vec::new(),
138            duration,
139        },
140        Value::Identifier(name),
141        0,
142        line_number,
143        1,
144    ))
145}
146
147/// Parse automate statement: automate <target> [mode <note|global>]:
148pub fn parse_automate(
149    mut parts: impl Iterator<Item = impl AsRef<str>>,
150    line_number: usize,
151) -> Result<Statement> {
152    // First part is the target
153    let target = parts
154        .next()
155        .ok_or_else(|| anyhow!("automate requires a target"))?
156        .as_ref()
157        .trim_end_matches(':')
158        .to_string();
159
160    // Optional mode: 'mode note' or 'mode global'
161    let mut mode: Option<String> = None;
162    if let Some(word) = parts.next() {
163        if word.as_ref() == "mode" {
164            if let Some(m) = parts.next() {
165                mode = Some(m.as_ref().trim_end_matches(':').to_string());
166            }
167        }
168    }
169
170    let mut map = HashMap::new();
171    if let Some(m) = mode {
172        map.insert("mode".to_string(), Value::String(m));
173    }
174
175    Ok(Statement::new(
176        StatementKind::Automate { target },
177        Value::Map(map),
178        0,
179        line_number,
180        1,
181    ))
182}
183
184/// Parse loop statement
185pub fn parse_loop(
186    parts: impl Iterator<Item = impl AsRef<str>>,
187    line_number: usize,
188) -> Result<Statement> {
189    // Parse: loop <count>:  OR plain `loop:` (no count)
190    // If no count is provided we store Value::Null to indicate an unbounded loop
191    // Special case: loop pass(<duration>): - pass with explicit duration
192
193    // First, collect all parts and rejoin them to handle durations with spaces
194    let all_parts: Vec<String> = parts.map(|p| p.as_ref().to_string()).collect();
195
196    let count = if all_parts.is_empty() {
197        // No explicit count -> mark as Null (infinite/indefinite)
198        Value::Null
199    } else {
200        // Join all remaining parts to handle things like "pass(1 bar)"
201        let count_str = all_parts.join(" ").trim_end_matches(':').to_string();
202
203        // Support forms:
204        // - loop pass:
205        // - loop pass():
206        // - loop pass(500):
207        // - loop pass(1 bar):
208        // - loop pass(2.5 bars):
209        // - loop 4:
210        if let Ok(num) = count_str.parse::<f32>() {
211            Value::Number(num)
212        } else if count_str.contains('(') && count_str.ends_with(')') {
213            // Parse as a call-like value (e.g., pass(500) or pass(1 bar))
214            if let Some(open_idx) = count_str.find('(') {
215                let name = count_str[..open_idx].to_string();
216                let inside = &count_str[open_idx + 1..count_str.len() - 1];
217
218                // Special case: if it's "pass" with a duration argument, parse as duration
219                if name == "pass" && !inside.trim().is_empty() {
220                    // Try to parse as duration first
221                    if let Ok(duration) =
222                        crate::language::syntax::parser::driver::parse_duration_token(inside.trim())
223                    {
224                        Value::Duration(duration)
225                    } else {
226                        // Fall back to call parsing
227                        let args = if inside.trim().is_empty() {
228                            Vec::new()
229                        } else {
230                            crate::language::syntax::parser::driver::parse_function_args(inside)?
231                        };
232                        Value::Call { name, args }
233                    }
234                } else {
235                    // Regular call parsing
236                    let args = if inside.trim().is_empty() {
237                        Vec::new()
238                    } else {
239                        crate::language::syntax::parser::driver::parse_function_args(inside)?
240                    };
241                    Value::Call { name, args }
242                }
243            } else {
244                Value::Identifier(count_str.to_string())
245            }
246        } else {
247            Value::Identifier(count_str)
248        }
249    };
250
251    Ok(Statement::new(
252        StatementKind::Loop {
253            count,
254            body: Vec::new(), // Will be filled during indentation parsing
255        },
256        Value::Null,
257        0,
258        line_number,
259        1,
260    ))
261}
262
263/// Parse for statement
264pub fn parse_for(
265    parts: impl Iterator<Item = impl AsRef<str>>,
266    line_number: usize,
267) -> Result<Statement> {
268    // Parse: for <var> in <iterable>:
269    let parts_vec: Vec<String> = parts.map(|s| s.as_ref().to_string()).collect();
270
271    if parts_vec.is_empty() {
272        return Err(anyhow!("for loop requires a variable name"));
273    }
274
275    let variable = parts_vec[0].clone();
276
277    // Expect "in"
278    if parts_vec.len() < 2 || parts_vec[1] != "in" {
279        return Err(anyhow!("Expected 'in' after variable in for loop"));
280    }
281
282    // Parse iterable (array or identifier)
283    let iterable_str = parts_vec[2..].join(" ");
284    let iterable_str = iterable_str.trim_end_matches(':').trim();
285
286    let iterable = if iterable_str.starts_with('[') && iterable_str.ends_with(']') {
287        // Parse as array: [1, 2, 3] or range: [1..10]
288        parse_array_value(iterable_str)?
289    } else {
290        // Parse as identifier or number
291        if let Ok(num) = iterable_str.parse::<f32>() {
292            Value::Number(num)
293        } else {
294            Value::Identifier(iterable_str.to_string())
295        }
296    };
297
298    Ok(Statement::new(
299        StatementKind::For {
300            variable,
301            iterable,
302            body: Vec::new(), // Will be filled during indentation parsing
303        },
304        Value::Null,
305        0,
306        line_number,
307        1,
308    ))
309}
310
311/// Parse function statement: function name(arg1, arg2, ...):
312pub fn parse_function(line: &str, line_number: usize) -> Result<Statement> {
313    // Expect form: function <name>(arg1, arg2, ...):
314    let after_kw = line
315        .trim()
316        .strip_prefix("function")
317        .ok_or_else(|| anyhow!("function parsing error"))?
318        .trim();
319
320    // Find name and args parentheses
321    if let Some(paren_idx) = after_kw.find('(') {
322        let name = after_kw[..paren_idx].trim().to_string();
323        if let Some(close_idx) = after_kw.rfind(')') {
324            let args_str = &after_kw[paren_idx + 1..close_idx];
325            // Parse parameter names: split by ',' and trim
326            let params: Vec<String> = if args_str.trim().is_empty() {
327                Vec::new()
328            } else {
329                args_str
330                    .split(',')
331                    .map(|s| s.trim().trim_end_matches(':').to_string())
332                    .filter(|s| !s.is_empty())
333                    .collect()
334            };
335
336            return Ok(Statement::new(
337                StatementKind::Function {
338                    name: name.clone(),
339                    parameters: params,
340                    body: Vec::new(),
341                },
342                Value::Identifier(name),
343                0,
344                line_number,
345                1,
346            ));
347        }
348    }
349
350    Err(anyhow!("Invalid function declaration"))
351}
352
353/// Parse if statement
354pub fn parse_if(
355    parts: impl Iterator<Item = impl AsRef<str>>,
356    line_number: usize,
357) -> Result<Statement> {
358    // Parse: if <condition>:
359    let condition_str = parts
360        .map(|s| s.as_ref().to_string())
361        .collect::<Vec<_>>()
362        .join(" ");
363    let condition_str = condition_str.trim_end_matches(':').trim();
364
365    // Parse condition (for now, simple comparison)
366    let condition = parse_condition(condition_str)?;
367
368    Ok(Statement::new(
369        StatementKind::If {
370            condition,
371            body: Vec::new(), // Will be filled during indentation parsing
372            else_body: None,
373        },
374        Value::Null,
375        0,
376        line_number,
377        1,
378    ))
379}
380
381/// Parse while statement
382/// Syntax: while <condition>:
383pub fn parse_while(
384    parts: impl Iterator<Item = impl AsRef<str>>,
385    line_number: usize,
386) -> Result<Statement> {
387    // Parse: while <condition>:
388    let condition_str = parts
389        .map(|s| s.as_ref().to_string())
390        .collect::<Vec<_>>()
391        .join(" ");
392    let condition_str = condition_str.trim_end_matches(':').trim();
393
394    // Parse condition
395    let condition = parse_condition(condition_str)?;
396
397    Ok(Statement::new(
398        StatementKind::While {
399            condition,
400            body: Vec::new(), // Will be filled during indentation parsing
401        },
402        Value::Null,
403        0,
404        line_number,
405        1,
406    ))
407}
408
409/// Parse else statement (can be "else if" or just "else")
410pub fn parse_else(line: &str, line_number: usize) -> Result<Statement> {
411    // Check if this is "else if" or just "else"
412    if line.trim().starts_with("else if") {
413        // Parse as "else if <condition>:"
414        let condition_str = line.trim().strip_prefix("else if").unwrap().trim();
415        let condition_str = condition_str.trim_end_matches(':').trim();
416        let condition = parse_condition(condition_str)?;
417
418        // Mark this If as originating from an 'else if' so the outer parser
419        // post-process can attach it as an else-branch to the previous If.
420        Ok(Statement::new(
421            StatementKind::If {
422                condition,
423                body: Vec::new(), // Will be filled during indentation parsing
424                else_body: None,
425            },
426            Value::String("else-if".to_string()),
427            0,
428            line_number,
429            1,
430        ))
431    } else {
432        // Just "else:"
433        // Return a lightweight marker (Comment with value "else") so the
434        // outer parser can detect and attach the following indented block
435        // as the else-body of the preceding If.
436        Ok(Statement::new(
437            StatementKind::Comment, // We'll handle this specially during body parsing
438            Value::String("else".to_string()),
439            0,
440            line_number,
441            1,
442        ))
443    }
444}
445
446/// Parse call statement
447/// Supports:
448/// - call groupName
449/// - call target = "pattern" (inline pattern assignment)
450pub fn parse_call(
451    line: &str,
452    mut parts: impl Iterator<Item = impl AsRef<str>>,
453    line_number: usize,
454) -> Result<Statement> {
455    // Determine the call name. If the call uses parentheses (call name(arg1, arg2))
456    // extract the name from the text before the first '(', otherwise fall back to
457    // the whitespace-split first token.
458    let first_token = parts
459        .next()
460        .ok_or_else(|| anyhow!("call requires a target"))?
461        .as_ref()
462        .to_string();
463    let mut call_name = first_token.clone();
464    if line.find('(').is_some() {
465        // Extract the substring between 'call' and the first '(' as the name
466        if let Some(after_call) = line.trim().strip_prefix("call") {
467            let snippet = after_call.trim();
468            if let Some(pidx) = snippet.find('(') {
469                call_name = snippet[..pidx].trim().to_string();
470            }
471        }
472    }
473
474    // Check if this is an inline pattern assignment: call target = "pattern"
475    if line.contains('=') {
476        // Split by = to get target and pattern
477        let eq_parts: Vec<&str> = line.splitn(2, '=').collect();
478        if eq_parts.len() == 2 {
479            // Extract target (remove "call " prefix)
480            let target = eq_parts[0]
481                .trim()
482                .strip_prefix("call")
483                .unwrap_or(eq_parts[0])
484                .trim()
485                .to_string();
486            let pattern = eq_parts[1].trim().trim_matches('"').to_string();
487
488            // Create an inline pattern statement
489            // Store pattern data in the Call's value field
490            let mut map = HashMap::new();
491            map.insert("inline_pattern".to_string(), Value::Boolean(true));
492            map.insert("target".to_string(), Value::String(target.clone()));
493            map.insert("pattern".to_string(), Value::String(pattern));
494
495            return Ok(Statement::new(
496                StatementKind::Call {
497                    name: target,
498                    args: Vec::new(),
499                },
500                Value::Map(map),
501                0,
502                line_number,
503                1,
504            ));
505        }
506    }
507
508    // Regular call statement (call groupName or call patternName)
509    // Support call with arguments: call name(arg1, arg2)
510    if let Some(paren_idx) = line.find('(') {
511        if let Some(close_idx) = line.rfind(')') {
512            let args_str = &line[paren_idx + 1..close_idx];
513            let args = if args_str.trim().is_empty() {
514                Vec::new()
515            } else {
516                // Reuse parse_function_args from parent module
517                crate::language::syntax::parser::driver::parse_function_args(args_str)?
518            };
519
520            return Ok(Statement::new(
521                StatementKind::Call {
522                    name: call_name,
523                    args,
524                },
525                Value::Null,
526                0,
527                line_number,
528                1,
529            ));
530        }
531    }
532
533    Ok(Statement::new(
534        StatementKind::Call {
535            name: call_name,
536            args: Vec::new(),
537        },
538        Value::Null,
539        0,
540        line_number,
541        1,
542    ))
543}
544
545/// Parse break statement
546pub fn parse_break(
547    _parts: impl Iterator<Item = impl AsRef<str>>,
548    line_number: usize,
549) -> Result<Statement> {
550    Ok(Statement::new(
551        StatementKind::Break,
552        Value::Null,
553        0,
554        line_number,
555        1,
556    ))
557}
558
559/// Parse spawn statement
560pub fn parse_spawn(
561    mut parts: impl Iterator<Item = impl AsRef<str>>,
562    line_number: usize,
563) -> Result<Statement> {
564    let name = parts
565        .next()
566        .ok_or_else(|| anyhow!("spawn requires a target"))?
567        .as_ref()
568        .to_string();
569
570    Ok(Statement::new(
571        StatementKind::Spawn {
572            name,
573            args: Vec::new(),
574        },
575        Value::Null,
576        0,
577        line_number,
578        1,
579    ))
580}
581
582/// Parse on statement (event handler)
583pub fn parse_on(
584    mut parts: impl Iterator<Item = impl AsRef<str>>,
585    line_number: usize,
586) -> Result<Statement> {
587    // Parse: on eventName: or on eventName once:
588    // Accept forms like: on beat: | on beat once: | on beat(4): | on beat(4) once:
589    let raw = parts
590        .next()
591        .ok_or_else(|| anyhow!("on statement requires an event name"))?
592        .as_ref()
593        .to_string();
594
595    // Parse optional interval within parentheses, e.g. beat(4)
596    let mut event_name = raw.clone();
597    let mut args_vec: Vec<Value> = Vec::new();
598    if let Some(open_idx) = raw.find('(') {
599        if raw.ends_with(')') {
600            let base = raw[..open_idx].to_string();
601            let inside = &raw[open_idx + 1..raw.len() - 1];
602            if let Ok(n) = inside.trim().parse::<f32>() {
603                args_vec.push(Value::Number(n));
604            }
605            event_name = base;
606        }
607    }
608
609    // Check for "once" keyword as next token
610    let next_word = parts.next();
611    let once = next_word.map(|w| w.as_ref() == "once").unwrap_or(false);
612    if once {
613        args_vec.push(Value::String("once".to_string()));
614    }
615
616    let args = if !args_vec.is_empty() {
617        Some(args_vec)
618    } else {
619        None
620    };
621
622    Ok(Statement::new(
623        StatementKind::On {
624            event: event_name,
625            args,
626            body: Vec::new(), // Will be filled during indentation parsing
627        },
628        Value::Null,
629        0,
630        line_number,
631        1,
632    ))
633}
634
635/// Parse emit statement (event emission)
636pub fn parse_emit(
637    line: &str,
638    mut parts: impl Iterator<Item = impl AsRef<str>>,
639    line_number: usize,
640) -> Result<Statement> {
641    // Parse: emit eventName { key: value, key: value }
642    let event_name = parts
643        .next()
644        .ok_or_else(|| anyhow!("emit statement requires an event name"))?
645        .as_ref()
646        .to_string();
647
648    // Parse payload if present
649    let remainder = line.splitn(2, &event_name).nth(1).unwrap_or("").trim();
650
651    let payload = if remainder.starts_with('{') && remainder.ends_with('}') {
652        // Parse as map: { key: value, key: value }
653        let inner = &remainder[1..remainder.len() - 1];
654        let mut map = HashMap::new();
655
656        // Split by comma and parse key-value pairs
657        for pair in inner.split(',') {
658            let parts: Vec<&str> = pair.split(':').collect();
659            if parts.len() == 2 {
660                let key = parts[0].trim().to_string();
661                let value_str = parts[1].trim();
662
663                // Parse value
664                let value = if value_str.starts_with('"') && value_str.ends_with('"') {
665                    Value::String(value_str.trim_matches('"').to_string())
666                } else if let Ok(num) = value_str.parse::<f32>() {
667                    Value::Number(num)
668                } else {
669                    Value::Identifier(value_str.to_string())
670                };
671
672                map.insert(key, value);
673            }
674        }
675
676        Some(Value::Map(map))
677    } else {
678        None
679    };
680
681    Ok(Statement::new(
682        StatementKind::Emit {
683            event: event_name,
684            payload,
685        },
686        Value::Null,
687        0,
688        line_number,
689        1,
690    ))
691}