Skip to main content

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 compact pattern shorthand:
106///   kick: drums.kick = "x---"
107///   hat: drums.hat -> swing(0.08) = "x-x-"
108pub fn parse_pattern_shorthand(line: &str, line_number: usize) -> Result<Option<Statement>> {
109    let Some(colon_idx) = line.find(':') else {
110        return Ok(None);
111    };
112    let Some(eq_idx) = line.find('=') else {
113        return Ok(None);
114    };
115    if colon_idx > eq_idx {
116        return Ok(None);
117    }
118
119    let name = line[..colon_idx].trim();
120    if name.is_empty()
121        || name.contains(char::is_whitespace)
122        || !name.chars().all(|c| c.is_alphanumeric() || c == '_')
123    {
124        return Ok(None);
125    }
126
127    let target_expr = line[colon_idx + 1..eq_idx].trim();
128    let pattern_part = line[eq_idx + 1..].trim();
129    if target_expr.is_empty()
130        || !(pattern_part.starts_with('"')
131            && pattern_part.ends_with('"')
132            && pattern_part.len() >= 2)
133    {
134        return Ok(None);
135    }
136
137    let mut target = target_expr.to_string();
138    let mut options: HashMap<String, Value> = HashMap::new();
139
140    fn insert_option(options: &mut HashMap<String, Value>, method: &str, args: &[Value]) {
141        if let Some(first) = args.first() {
142            match first {
143                Value::Number(_) => {
144                    options.insert(method.to_string(), first.clone());
145                }
146                Value::Map(map) => {
147                    for (key, value) in map.iter() {
148                        if matches!(value, Value::Number(_)) {
149                            options.insert(key.clone(), value.clone());
150                        }
151                    }
152                }
153                _ => {}
154            }
155        }
156    }
157
158    if target_expr.contains("->") {
159        let stmt =
160            crate::language::syntax::parser::driver::parse_arrow_call(target_expr, line_number)?;
161        if let Value::Map(chain_container) = stmt.value {
162            if let Some(Value::String(parsed_target)) = chain_container.get("target") {
163                target = parsed_target.clone();
164            }
165
166            if let Some(Value::String(method)) = chain_container.get("method") {
167                if let Some(Value::Array(args)) = chain_container.get("args") {
168                    insert_option(&mut options, method, args);
169                }
170            }
171
172            if let Some(Value::Array(chain)) = chain_container.get("chain") {
173                for item in chain {
174                    if let Value::Map(call_map) = item {
175                        if let (Some(Value::String(method)), Some(Value::Array(args))) =
176                            (call_map.get("method"), call_map.get("args"))
177                        {
178                            insert_option(&mut options, method, args);
179                        }
180                    }
181                }
182            }
183        }
184    }
185
186    let pattern = pattern_part.trim_matches('"').to_string();
187    let value = if options.is_empty() {
188        Value::String(pattern)
189    } else {
190        options.insert("pattern".to_string(), Value::String(pattern));
191        Value::Map(options)
192    };
193
194    Ok(Some(Statement::new(
195        StatementKind::Pattern {
196            name: name.to_string(),
197            target: Some(target),
198        },
199        value,
200        0,
201        line_number,
202        1,
203    )))
204}
205
206/// Parse group statement: group <name>: or group <name> during <duration>:
207/// Example: group verse: or group verse during 16 bars:
208pub fn parse_group(
209    mut parts: impl Iterator<Item = impl AsRef<str>>,
210    line_number: usize,
211) -> Result<Statement> {
212    let name = parts
213        .next()
214        .ok_or_else(|| anyhow!("group requires a name"))?
215        .as_ref()
216        .trim_end_matches(':')
217        .to_string();
218
219    // Check for "during" keyword for optional duration
220    let next = parts.next();
221    let duration = if next.as_ref().map(|w| w.as_ref()) == Some("during") {
222        // Parse duration (everything after 'during')
223        let duration_str = parts
224            .map(|p| p.as_ref().to_string())
225            .collect::<Vec<_>>()
226            .join(" ")
227            .trim_end_matches(':')
228            .to_string();
229
230        Some(super::super::duration::parse_duration_token(&duration_str)?)
231    } else {
232        None
233    };
234
235    Ok(Statement::new(
236        StatementKind::Group {
237            name: name.clone(),
238            body: Vec::new(),
239            duration,
240        },
241        Value::Identifier(name),
242        0,
243        line_number,
244        1,
245    ))
246}
247
248/// Parse automate statement: automate <target> [mode <note|global>]:
249pub fn parse_automate(
250    mut parts: impl Iterator<Item = impl AsRef<str>>,
251    line_number: usize,
252) -> Result<Statement> {
253    // First part is the target
254    let target = parts
255        .next()
256        .ok_or_else(|| anyhow!("automate requires a target"))?
257        .as_ref()
258        .trim_end_matches(':')
259        .to_string();
260
261    // Optional mode: 'mode note' or 'mode global'
262    let mut mode: Option<String> = None;
263    if let Some(word) = parts.next() {
264        if word.as_ref() == "mode" {
265            if let Some(m) = parts.next() {
266                mode = Some(m.as_ref().trim_end_matches(':').to_string());
267            }
268        }
269    }
270
271    let mut map = HashMap::new();
272    if let Some(m) = mode {
273        map.insert("mode".to_string(), Value::String(m));
274    }
275
276    Ok(Statement::new(
277        StatementKind::Automate { target },
278        Value::Map(map),
279        0,
280        line_number,
281        1,
282    ))
283}
284
285/// Parse loop statement
286pub fn parse_loop(
287    parts: impl Iterator<Item = impl AsRef<str>>,
288    line_number: usize,
289) -> Result<Statement> {
290    // Parse: loop <count>:  OR plain `loop:` (no count)
291    // If no count is provided we store Value::Null to indicate an unbounded loop
292    // Special case: loop pass(<duration>): - pass with explicit duration
293
294    // First, collect all parts and rejoin them to handle durations with spaces
295    let all_parts: Vec<String> = parts.map(|p| p.as_ref().to_string()).collect();
296
297    let count = if all_parts.is_empty() {
298        // No explicit count -> mark as Null (infinite/indefinite)
299        Value::Null
300    } else {
301        // Join all remaining parts to handle things like "pass(1 bar)"
302        let count_str = all_parts.join(" ").trim_end_matches(':').to_string();
303
304        // Support forms:
305        // - loop pass:
306        // - loop pass():
307        // - loop pass(500):
308        // - loop pass(1 bar):
309        // - loop pass(2.5 bars):
310        // - loop 4:
311        if let Ok(num) = count_str.parse::<f32>() {
312            Value::Number(num)
313        } else if count_str.contains('(') && count_str.ends_with(')') {
314            // Parse as a call-like value (e.g., pass(500) or pass(1 bar))
315            if let Some(open_idx) = count_str.find('(') {
316                let name = count_str[..open_idx].to_string();
317                let inside = &count_str[open_idx + 1..count_str.len() - 1];
318
319                // Special case: if it's "pass" with a duration argument, parse as duration
320                if name == "pass" && !inside.trim().is_empty() {
321                    // Try to parse as duration first
322                    if let Ok(duration) =
323                        crate::language::syntax::parser::driver::parse_duration_token(inside.trim())
324                    {
325                        Value::Duration(duration)
326                    } else {
327                        // Fall back to call parsing
328                        let args = if inside.trim().is_empty() {
329                            Vec::new()
330                        } else {
331                            crate::language::syntax::parser::driver::parse_function_args(inside)?
332                        };
333                        Value::Call { name, args }
334                    }
335                } else {
336                    // Regular call parsing
337                    let args = if inside.trim().is_empty() {
338                        Vec::new()
339                    } else {
340                        crate::language::syntax::parser::driver::parse_function_args(inside)?
341                    };
342                    Value::Call { name, args }
343                }
344            } else {
345                Value::Identifier(count_str.to_string())
346            }
347        } else {
348            Value::Identifier(count_str)
349        }
350    };
351
352    Ok(Statement::new(
353        StatementKind::Loop {
354            count,
355            body: Vec::new(), // Will be filled during indentation parsing
356        },
357        Value::Null,
358        0,
359        line_number,
360        1,
361    ))
362}
363
364/// Parse for statement
365pub fn parse_for(
366    parts: impl Iterator<Item = impl AsRef<str>>,
367    line_number: usize,
368) -> Result<Statement> {
369    // Parse: for <var> in <iterable>:
370    let parts_vec: Vec<String> = parts.map(|s| s.as_ref().to_string()).collect();
371
372    if parts_vec.is_empty() {
373        return Err(anyhow!("for loop requires a variable name"));
374    }
375
376    let variable = parts_vec[0].clone();
377
378    // Expect "in"
379    if parts_vec.len() < 2 || parts_vec[1] != "in" {
380        return Err(anyhow!("Expected 'in' after variable in for loop"));
381    }
382
383    // Parse iterable (array or identifier)
384    let iterable_str = parts_vec[2..].join(" ");
385    let iterable_str = iterable_str.trim_end_matches(':').trim();
386
387    let iterable = if iterable_str.starts_with('[') && iterable_str.ends_with(']') {
388        // Parse as array: [1, 2, 3] or range: [1..10]
389        parse_array_value(iterable_str)?
390    } else {
391        // Parse as identifier or number
392        if let Ok(num) = iterable_str.parse::<f32>() {
393            Value::Number(num)
394        } else {
395            Value::Identifier(iterable_str.to_string())
396        }
397    };
398
399    Ok(Statement::new(
400        StatementKind::For {
401            variable,
402            iterable,
403            body: Vec::new(), // Will be filled during indentation parsing
404        },
405        Value::Null,
406        0,
407        line_number,
408        1,
409    ))
410}
411
412/// Parse function statement: function name(arg1, arg2, ...):
413pub fn parse_function(line: &str, line_number: usize) -> Result<Statement> {
414    // Expect form: function <name>(arg1, arg2, ...):
415    let after_kw = line
416        .trim()
417        .strip_prefix("function")
418        .ok_or_else(|| anyhow!("function parsing error"))?
419        .trim();
420
421    // Find name and args parentheses
422    if let Some(paren_idx) = after_kw.find('(') {
423        let name = after_kw[..paren_idx].trim().to_string();
424        if let Some(close_idx) = after_kw.rfind(')') {
425            let args_str = &after_kw[paren_idx + 1..close_idx];
426            // Parse parameter names: split by ',' and trim
427            let params: Vec<String> = if args_str.trim().is_empty() {
428                Vec::new()
429            } else {
430                args_str
431                    .split(',')
432                    .map(|s| s.trim().trim_end_matches(':').to_string())
433                    .filter(|s| !s.is_empty())
434                    .collect()
435            };
436
437            return Ok(Statement::new(
438                StatementKind::Function {
439                    name: name.clone(),
440                    parameters: params,
441                    body: Vec::new(),
442                },
443                Value::Identifier(name),
444                0,
445                line_number,
446                1,
447            ));
448        }
449    }
450
451    Err(anyhow!("Invalid function declaration"))
452}
453
454/// Parse if statement
455pub fn parse_if(
456    parts: impl Iterator<Item = impl AsRef<str>>,
457    line_number: usize,
458) -> Result<Statement> {
459    // Parse: if <condition>:
460    let condition_str = parts
461        .map(|s| s.as_ref().to_string())
462        .collect::<Vec<_>>()
463        .join(" ");
464    let condition_str = condition_str.trim_end_matches(':').trim();
465
466    // Parse condition (for now, simple comparison)
467    let condition = parse_condition(condition_str)?;
468
469    Ok(Statement::new(
470        StatementKind::If {
471            condition,
472            body: Vec::new(), // Will be filled during indentation parsing
473            else_body: None,
474        },
475        Value::Null,
476        0,
477        line_number,
478        1,
479    ))
480}
481
482/// Parse while statement
483/// Syntax: while <condition>:
484pub fn parse_while(
485    parts: impl Iterator<Item = impl AsRef<str>>,
486    line_number: usize,
487) -> Result<Statement> {
488    // Parse: while <condition>:
489    let condition_str = parts
490        .map(|s| s.as_ref().to_string())
491        .collect::<Vec<_>>()
492        .join(" ");
493    let condition_str = condition_str.trim_end_matches(':').trim();
494
495    // Parse condition
496    let condition = parse_condition(condition_str)?;
497
498    Ok(Statement::new(
499        StatementKind::While {
500            condition,
501            body: Vec::new(), // Will be filled during indentation parsing
502        },
503        Value::Null,
504        0,
505        line_number,
506        1,
507    ))
508}
509
510/// Parse else statement (can be "else if" or just "else")
511pub fn parse_else(line: &str, line_number: usize) -> Result<Statement> {
512    // Check if this is "else if" or just "else"
513    if line.trim().starts_with("else if") {
514        // Parse as "else if <condition>:"
515        let Some(condition_str) = line.trim().strip_prefix("else if") else {
516            return Err(anyhow::anyhow!(
517                "Invalid else-if statement at line {}",
518                line_number
519            ));
520        };
521        let condition_str = condition_str.trim();
522        let condition_str = condition_str.trim_end_matches(':').trim();
523        let condition = parse_condition(condition_str)?;
524
525        // Mark this If as originating from an 'else if' so the outer parser
526        // post-process can attach it as an else-branch to the previous If.
527        Ok(Statement::new(
528            StatementKind::If {
529                condition,
530                body: Vec::new(), // Will be filled during indentation parsing
531                else_body: None,
532            },
533            Value::String("else-if".to_string()),
534            0,
535            line_number,
536            1,
537        ))
538    } else {
539        // Just "else:"
540        // Return a lightweight marker (Comment with value "else") so the
541        // outer parser can detect and attach the following indented block
542        // as the else-body of the preceding If.
543        Ok(Statement::new(
544            StatementKind::Comment, // We'll handle this specially during body parsing
545            Value::String("else".to_string()),
546            0,
547            line_number,
548            1,
549        ))
550    }
551}
552
553/// Parse call statement
554/// Supports:
555/// - call groupName
556/// - call target = "pattern" (inline pattern assignment)
557pub fn parse_call(
558    line: &str,
559    mut parts: impl Iterator<Item = impl AsRef<str>>,
560    line_number: usize,
561) -> Result<Statement> {
562    // Determine the call name. If the call uses parentheses (call name(arg1, arg2))
563    // extract the name from the text before the first '(', otherwise fall back to
564    // the whitespace-split first token.
565    let first_token = parts
566        .next()
567        .ok_or_else(|| anyhow!("call requires a target"))?
568        .as_ref()
569        .to_string();
570    let mut call_name = first_token.clone();
571    if line.find('(').is_some() {
572        // Extract the substring between 'call' and the first '(' as the name
573        if let Some(after_call) = line.trim().strip_prefix("call") {
574            let snippet = after_call.trim();
575            if let Some(pidx) = snippet.find('(') {
576                call_name = snippet[..pidx].trim().to_string();
577            }
578        }
579    }
580
581    // Check if this is an inline pattern assignment: call target = "pattern"
582    if line.contains('=') {
583        // Split by = to get target and pattern
584        let eq_parts: Vec<&str> = line.splitn(2, '=').collect();
585        if eq_parts.len() == 2 {
586            // Extract target (remove "call " prefix)
587            let target = eq_parts[0]
588                .trim()
589                .strip_prefix("call")
590                .unwrap_or(eq_parts[0])
591                .trim()
592                .to_string();
593            let pattern = eq_parts[1].trim().trim_matches('"').to_string();
594
595            // Create an inline pattern statement
596            // Store pattern data in the Call's value field
597            let mut map = HashMap::new();
598            map.insert("inline_pattern".to_string(), Value::Boolean(true));
599            map.insert("target".to_string(), Value::String(target.clone()));
600            map.insert("pattern".to_string(), Value::String(pattern));
601
602            return Ok(Statement::new(
603                StatementKind::Call {
604                    name: target,
605                    args: Vec::new(),
606                },
607                Value::Map(map),
608                0,
609                line_number,
610                1,
611            ));
612        }
613    }
614
615    // Regular call statement (call groupName or call patternName)
616    // Support call with arguments: call name(arg1, arg2)
617    if let Some(paren_idx) = line.find('(') {
618        if let Some(close_idx) = line.rfind(')') {
619            let args_str = &line[paren_idx + 1..close_idx];
620            let args = if args_str.trim().is_empty() {
621                Vec::new()
622            } else {
623                // Reuse parse_function_args from parent module
624                crate::language::syntax::parser::driver::parse_function_args(args_str)?
625            };
626
627            return Ok(Statement::new(
628                StatementKind::Call {
629                    name: call_name,
630                    args,
631                },
632                Value::Null,
633                0,
634                line_number,
635                1,
636            ));
637        }
638    }
639
640    Ok(Statement::new(
641        StatementKind::Call {
642            name: call_name,
643            args: Vec::new(),
644        },
645        Value::Null,
646        0,
647        line_number,
648        1,
649    ))
650}
651
652/// Parse break statement
653pub fn parse_break(
654    _parts: impl Iterator<Item = impl AsRef<str>>,
655    line_number: usize,
656) -> Result<Statement> {
657    Ok(Statement::new(
658        StatementKind::Break,
659        Value::Null,
660        0,
661        line_number,
662        1,
663    ))
664}
665
666/// Parse spawn statement
667pub fn parse_spawn(
668    mut parts: impl Iterator<Item = impl AsRef<str>>,
669    line_number: usize,
670) -> Result<Statement> {
671    let name = parts
672        .next()
673        .ok_or_else(|| anyhow!("spawn requires a target"))?
674        .as_ref()
675        .to_string();
676
677    Ok(Statement::new(
678        StatementKind::Spawn {
679            name,
680            args: Vec::new(),
681        },
682        Value::Null,
683        0,
684        line_number,
685        1,
686    ))
687}
688
689/// Parse on statement (event handler)
690pub fn parse_on(
691    mut parts: impl Iterator<Item = impl AsRef<str>>,
692    line_number: usize,
693) -> Result<Statement> {
694    // Parse: on eventName: or on eventName once:
695    // Accept forms like: on beat: | on beat once: | on beat(4): | on beat(4) once:
696    let raw = parts
697        .next()
698        .ok_or_else(|| anyhow!("on statement requires an event name"))?
699        .as_ref()
700        .to_string();
701
702    // Parse optional interval within parentheses, e.g. beat(4)
703    let mut event_name = raw.clone();
704    let mut args_vec: Vec<Value> = Vec::new();
705    if let Some(open_idx) = raw.find('(') {
706        if raw.ends_with(')') {
707            let base = raw[..open_idx].to_string();
708            let inside = &raw[open_idx + 1..raw.len() - 1];
709            if let Ok(n) = inside.trim().parse::<f32>() {
710                args_vec.push(Value::Number(n));
711            }
712            event_name = base;
713        }
714    }
715
716    // Check for "once" keyword as next token
717    let next_word = parts.next();
718    let once = next_word.map(|w| w.as_ref() == "once").unwrap_or(false);
719    if once {
720        args_vec.push(Value::String("once".to_string()));
721    }
722
723    let args = if !args_vec.is_empty() {
724        Some(args_vec)
725    } else {
726        None
727    };
728
729    Ok(Statement::new(
730        StatementKind::On {
731            event: event_name,
732            args,
733            body: Vec::new(), // Will be filled during indentation parsing
734        },
735        Value::Null,
736        0,
737        line_number,
738        1,
739    ))
740}
741
742/// Parse emit statement (event emission)
743pub fn parse_emit(
744    line: &str,
745    mut parts: impl Iterator<Item = impl AsRef<str>>,
746    line_number: usize,
747) -> Result<Statement> {
748    // Parse: emit eventName { key: value, key: value }
749    let event_name = parts
750        .next()
751        .ok_or_else(|| anyhow!("emit statement requires an event name"))?
752        .as_ref()
753        .to_string();
754
755    // Parse payload if present
756    let remainder = line.splitn(2, &event_name).nth(1).unwrap_or("").trim();
757
758    let payload = if remainder.starts_with('{') && remainder.ends_with('}') {
759        // Parse as map: { key: value, key: value }
760        let inner = &remainder[1..remainder.len() - 1];
761        let mut map = HashMap::new();
762
763        // Split by comma and parse key-value pairs
764        for pair in inner.split(',') {
765            let parts: Vec<&str> = pair.split(':').collect();
766            if parts.len() == 2 {
767                let key = parts[0].trim().to_string();
768                let value_str = parts[1].trim();
769
770                // Parse value
771                let value = if value_str.starts_with('"') && value_str.ends_with('"') {
772                    Value::String(value_str.trim_matches('"').to_string())
773                } else if let Ok(num) = value_str.parse::<f32>() {
774                    Value::Number(num)
775                } else {
776                    Value::Identifier(value_str.to_string())
777                };
778
779                map.insert(key, value);
780            }
781        }
782
783        Some(Value::Map(map))
784    } else {
785        None
786    };
787
788    Ok(Statement::new(
789        StatementKind::Emit {
790            event: event_name,
791            payload,
792        },
793        Value::Null,
794        0,
795        line_number,
796        1,
797    ))
798}