devalang_wasm/language/syntax/parser/driver/
mod.rs

1pub mod directive;
2pub mod duration;
3pub mod helpers;
4pub mod preprocessing;
5pub mod routing;
6pub mod statements;
7pub mod trigger;
8
9use std::fs;
10use std::path::{Path, PathBuf};
11
12use anyhow::{Context, Result, anyhow};
13
14use crate::language::syntax::ast::{Statement, StatementKind, Value};
15use directive::parse_directive;
16use preprocessing::{preprocess_multiline_arrow_calls, preprocess_multiline_braces};
17use routing::parse_routing_command;
18use statements::*;
19use trigger::parse_trigger_line;
20
21pub struct SimpleParser;
22
23impl SimpleParser {
24    pub fn new() -> Self {
25        Self
26    }
27
28    pub fn parse_file(path: impl AsRef<Path>) -> Result<Vec<Statement>> {
29        let path = path.as_ref();
30        let content = fs::read_to_string(path)
31            .with_context(|| format!("failed to read source file: {}", path.display()))?;
32        Self::parse(&content, path.to_path_buf())
33    }
34
35    pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
36        // Pre-process: merge ALL multiline statements with braces
37        let preprocessed = preprocess_multiline_braces(source);
38
39        // Then merge multiline arrow calls (without braces)
40        let preprocessed = preprocess_multiline_arrow_calls(&preprocessed);
41
42        let lines: Vec<_> = preprocessed.lines().collect();
43        Self::parse_lines(&lines, 0, lines.len(), 0, &path)
44    }
45
46    fn parse_lines(
47        lines: &[&str],
48        start: usize,
49        end: usize,
50        base_indent: usize,
51        path: &Path,
52    ) -> Result<Vec<Statement>> {
53        let mut statements = Vec::new();
54        let mut i = start;
55
56        while i < end {
57            let raw_line = lines[i];
58            let line_number = i + 1;
59            let trimmed = raw_line.trim();
60
61            // Skip empty lines and comments
62            if trimmed.is_empty() || trimmed.starts_with('#') {
63                i += 1;
64                continue;
65            }
66
67            // Calculate indentation
68            let indent = raw_line.len() - raw_line.trim_start().len();
69
70            // If this line is less indented than base, stop parsing this block
71            if indent < base_indent {
72                break;
73            }
74
75            // Parse the statement
76            let mut statement = match Self::parse_line(trimmed, line_number, path) {
77                Ok(stmt) => {
78                    let mut s = stmt;
79                    s.indent = indent;
80                    s
81                }
82                Err(error) => {
83                    let error_msg = error.to_string();
84
85                    // Push structured error to WASM registry if available
86                    #[cfg(feature = "wasm")]
87                    {
88                        use crate::web::registry::debug;
89                        if debug::is_debug_errors_enabled() {
90                            debug::push_parse_error_from_parts(
91                                error_msg.clone(),
92                                line_number,
93                                1,
94                                "ParseError".to_string(),
95                            );
96                        }
97                    }
98
99                    Statement::new(
100                        StatementKind::Error { message: error_msg },
101                        Value::String(trimmed.to_string()),
102                        indent,
103                        line_number,
104                        1,
105                    )
106                }
107            };
108
109            // If this is a statement needing body parsing (group, for, loop, if, on, automate)
110            let needs_body_parsing = matches!(
111                &statement.kind,
112                StatementKind::Group { .. }
113                    | StatementKind::For { .. }
114                    | StatementKind::Loop { .. }
115                    | StatementKind::If { .. }
116                    | StatementKind::On { .. }
117                    | StatementKind::Automate { .. }
118            );
119
120            if needs_body_parsing {
121                i += 1; // Move to next line
122
123                // Find the end of the body (next line with same or less indentation)
124                let block_indent = indent;
125                let body_start = i;
126                let mut body_end = i;
127
128                while body_end < end {
129                    let body_line = lines[body_end];
130                    let body_trimmed = body_line.trim();
131
132                    if body_trimmed.is_empty() || body_trimmed.starts_with('#') {
133                        body_end += 1;
134                        continue;
135                    }
136
137                    let body_indent = body_line.len() - body_line.trim_start().len();
138                    if body_indent <= block_indent {
139                        break;
140                    }
141
142                    body_end += 1;
143                }
144
145                // Parse the body recursively
146                let body = Self::parse_lines(lines, body_start, body_end, block_indent + 1, path)?;
147
148                // Update the statement with the parsed body
149                match &statement.kind {
150                    StatementKind::Group { name, .. } => {
151                        let group_name = name.clone();
152                        statement.kind = StatementKind::Group {
153                            name: group_name.clone(),
154                            body,
155                        };
156                        statement.value = Value::Identifier(group_name);
157                    }
158                    StatementKind::For {
159                        variable, iterable, ..
160                    } => {
161                        statement.kind = StatementKind::For {
162                            variable: variable.clone(),
163                            iterable: iterable.clone(),
164                            body,
165                        };
166                    }
167                    StatementKind::Loop { count, .. } => {
168                        statement.kind = StatementKind::Loop {
169                            count: count.clone(),
170                            body,
171                        };
172                    }
173                    StatementKind::On { event, args, .. } => {
174                        statement.kind = StatementKind::On {
175                            event: event.clone(),
176                            args: args.clone(),
177                            body,
178                        };
179                    }
180                    StatementKind::If { condition, .. } => {
181                        // Check if there's an else clause after the body
182                        let mut else_body = None;
183                        if body_end < end {
184                            let next_line = lines[body_end].trim();
185                            if next_line.starts_with("else") {
186                                // Found else, parse its body
187                                let else_start = body_end + 1;
188                                let mut else_end = else_start;
189
190                                while else_end < end {
191                                    let else_line = lines[else_end];
192                                    let else_trimmed = else_line.trim();
193
194                                    if else_trimmed.is_empty() || else_trimmed.starts_with('#') {
195                                        else_end += 1;
196                                        continue;
197                                    }
198
199                                    let else_indent =
200                                        else_line.len() - else_line.trim_start().len();
201                                    if else_indent <= block_indent {
202                                        break;
203                                    }
204
205                                    else_end += 1;
206                                }
207
208                                else_body = Some(Self::parse_lines(
209                                    lines,
210                                    else_start,
211                                    else_end,
212                                    block_indent + 1,
213                                    path,
214                                )?);
215                                body_end = else_end; // Update i to skip else body
216                            }
217                        }
218
219                        statement.kind = StatementKind::If {
220                            condition: condition.clone(),
221                            body,
222                            else_body,
223                        };
224                    }
225                    StatementKind::Automate { target } => {
226                        // Preserve any provided mode from the original statement value
227                        let mode = if let Value::Map(map) = &statement.value {
228                            map.get("mode").and_then(|v| {
229                                if let Value::String(s) = v {
230                                    Some(s.clone())
231                                } else {
232                                    None
233                                }
234                            })
235                        } else {
236                            None
237                        };
238
239                        // Reconstruct raw body text from the original source lines
240                        let raw_lines: Vec<String> = lines[body_start..body_end]
241                            .iter()
242                            .map(|s| s.to_string())
243                            .collect();
244                        let raw_body = raw_lines.join("\n");
245
246                        let mut map = std::collections::HashMap::new();
247                        if let Some(m) = mode {
248                            map.insert("mode".to_string(), Value::String(m));
249                        }
250                        map.insert("body".to_string(), Value::String(raw_body));
251
252                        statement.kind = StatementKind::Automate {
253                            target: target.clone(),
254                        };
255                        statement.value = Value::Map(map);
256                    }
257                    _ => {}
258                }
259
260                i = body_end;
261                statements.push(statement);
262                continue;
263            }
264
265            statements.push(statement);
266            i += 1;
267        }
268
269        Ok(statements)
270    }
271
272    fn parse_line(line: &str, line_number: usize, path: &Path) -> Result<Statement> {
273        if line.starts_with('@') {
274            return parse_directive(line, line_number, path);
275        }
276
277        if line.starts_with('.') {
278            return parse_trigger_line(line, line_number);
279        }
280
281        let mut parts = line.split_whitespace();
282        let keyword = parts
283            .next()
284            .ok_or_else(|| anyhow!("empty line"))?
285            .to_lowercase();
286
287        // Check if this is a property assignment first: target.property = value
288        if line.contains('=') && keyword.contains('.') {
289            return parse_assign(line, line_number);
290        }
291
292        // Check if this looks like a trigger (contains a dot like "drums.kick")
293        if keyword.contains('.') && !keyword.contains('(') {
294            // Reconstruct the line and parse as trigger
295            return parse_trigger_line(line, line_number);
296        }
297
298        // Check if this is a bind statement first (before arrow call)
299        if keyword == "bind" && line.contains("->") {
300            return parse_bind(line, line_number);
301        }
302
303        // Check if this is an arrow call: target -> method(args)
304        if line.contains("->") {
305            return parse_arrow_call(line, line_number);
306        }
307
308        return match keyword.as_str() {
309            "bpm" | "tempo" => parse_tempo(parts, line_number),
310            "print" => parse_print(line, line_number),
311            "sleep" => parse_sleep(parts, line_number),
312            "trigger" => Err(anyhow!(
313                "keyword 'trigger' is deprecated; use dot notation like '.alias' instead"
314            )),
315            "pattern" => parse_pattern(parts, line_number),
316            "bank" => parse_bank(parts, line_number),
317            "let" => parse_let(line, parts, line_number),
318            "var" => parse_var(line, parts, line_number),
319            "const" => parse_const(line, parts, line_number),
320            "for" => parse_for(parts, line_number),
321            "loop" => parse_loop(parts, line_number),
322            "if" => parse_if(parts, line_number),
323            "else" => parse_else(line, line_number),
324            "group" => parse_group(parts, line_number),
325            "automate" => {
326                crate::language::syntax::parser::driver::statements::structure::parse_automate(
327                    parts,
328                    line_number,
329                )
330            }
331            "call" => parse_call(line, parts, line_number),
332            "spawn" => parse_spawn(parts, line_number),
333            "on" => parse_on(parts, line_number),
334            "emit" => parse_emit(line, parts, line_number),
335            "routing" => parse_routing_command(parts, line_number),
336            _ => {
337                // Check if this looks like a potential trigger identifier (single word, no special chars except dots)
338                // This handles cases like variable references: let kick = drums.kick; kick
339                if keyword
340                    .chars()
341                    .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
342                {
343                    // Parse as trigger
344                    return parse_trigger_line(line, line_number);
345                }
346
347                let error_msg = format!(
348                    "Unknown statement '{}' at {}:{}",
349                    keyword,
350                    path.display(),
351                    line_number
352                );
353
354                // Push structured error to WASM registry if available
355                #[cfg(feature = "wasm")]
356                {
357                    use crate::web::registry::debug;
358                    if debug::is_debug_errors_enabled() {
359                        debug::push_parse_error_from_parts(
360                            format!("Unknown statement '{}'", keyword),
361                            line_number,
362                            1,
363                            "UnknownStatement".to_string(),
364                        );
365                    }
366                }
367
368                return Ok(Statement::new(
369                    StatementKind::Unknown,
370                    Value::String(error_msg),
371                    0,
372                    line_number,
373                    1,
374                ));
375            }
376        };
377    }
378
379    /// Parse a condition string into a Value (for if statements)
380    /// Supports: var > value, var < value, var == value, var != value, var >= value, var <= value
381    fn parse_condition(condition_str: &str) -> Result<Value> {
382        use std::collections::HashMap;
383
384        // Find the operator
385        let operators = vec![">=", "<=", "==", "!=", ">", "<"];
386        for op in operators {
387            if let Some(idx) = condition_str.find(op) {
388                let left = condition_str[..idx].trim();
389                let right = condition_str[idx + op.len()..].trim();
390
391                // Create a map representing the condition
392                let mut map = HashMap::new();
393                map.insert("operator".to_string(), Value::String(op.to_string()));
394                map.insert(
395                    "left".to_string(),
396                    if let Ok(num) = left.parse::<f32>() {
397                        Value::Number(num)
398                    } else {
399                        Value::Identifier(left.to_string())
400                    },
401                );
402                map.insert(
403                    "right".to_string(),
404                    if let Ok(num) = right.parse::<f32>() {
405                        Value::Number(num)
406                    } else {
407                        Value::Identifier(right.to_string())
408                    },
409                );
410
411                return Ok(Value::Map(map));
412            }
413        }
414
415        // No operator found, treat as boolean identifier
416        Ok(Value::Identifier(condition_str.to_string()))
417    }
418}
419
420/// Parse an arrow call: target -> method(args) -> method2(args2)
421/// Supports chaining multiple calls
422fn parse_arrow_call(line: &str, line_number: usize) -> Result<Statement> {
423    use std::collections::HashMap;
424
425    // Split by "->" to get chain of calls
426    let parts: Vec<&str> = line.split("->").map(|s| s.trim()).collect();
427
428    if parts.len() < 2 {
429        return Err(anyhow!("Arrow call requires at least one '->' operator"));
430    }
431
432    // First part is the target
433    let target = parts[0].to_string();
434
435    // Parse method calls
436    let mut calls = Vec::new();
437
438    for method_call in &parts[1..] {
439        // Parse method(args) or just method
440        if let Some(paren_idx) = method_call.find('(') {
441            let method_name = method_call[..paren_idx].trim();
442            let args_str = &method_call[paren_idx + 1..];
443
444            // Find matching closing paren
445            let close_paren = args_str
446                .rfind(')')
447                .ok_or_else(|| anyhow!("Missing closing parenthesis in arrow call"))?;
448
449            let args_str = &args_str[..close_paren];
450
451            // Parse arguments
452            let args = if args_str.trim().is_empty() {
453                Vec::new()
454            } else {
455                parse_function_args(args_str)?
456            };
457
458            calls.push((method_name.to_string(), args));
459        } else {
460            // Method without args
461            calls.push((method_call.trim().to_string(), Vec::new()));
462        }
463    }
464
465    // For now, we'll store all calls as separate ArrowCall statements
466    // or we can store them in a chain structure
467    // Let's store the first call and chain the rest
468
469    if calls.is_empty() {
470        return Err(anyhow!("No method calls found in arrow call"));
471    }
472
473    let (method, args) = calls[0].clone();
474
475    // Store chain in value for later processing
476    let mut chain_value = HashMap::new();
477    chain_value.insert("target".to_string(), Value::String(target.clone()));
478    chain_value.insert("method".to_string(), Value::String(method.clone()));
479    chain_value.insert("args".to_string(), Value::Array(args.clone()));
480
481    // Add remaining calls to chain
482    if calls.len() > 1 {
483        let chain_calls: Vec<Value> = calls[1..]
484            .iter()
485            .map(|(m, a)| {
486                let mut call_map = HashMap::new();
487                call_map.insert("method".to_string(), Value::String(m.clone()));
488                call_map.insert("args".to_string(), Value::Array(a.clone()));
489                Value::Map(call_map)
490            })
491            .collect();
492
493        chain_value.insert("chain".to_string(), Value::Array(chain_calls));
494    }
495
496    Ok(Statement::new(
497        StatementKind::ArrowCall {
498            target,
499            method,
500            args,
501        },
502        Value::Map(chain_value),
503        0,
504        line_number,
505        1,
506    ))
507}
508
509/// Parse function arguments from string
510/// Supports: numbers, strings, identifiers, arrays, maps
511fn parse_function_args(args_str: &str) -> Result<Vec<Value>> {
512    let mut args = Vec::new();
513    let mut current_arg = String::new();
514    let mut depth = 0; // Track nested structures
515    let mut in_string = false;
516
517    for ch in args_str.chars() {
518        match ch {
519            '"' => {
520                in_string = !in_string;
521                current_arg.push(ch);
522            }
523            '[' | '{' if !in_string => {
524                depth += 1;
525                current_arg.push(ch);
526            }
527            ']' | '}' if !in_string => {
528                depth -= 1;
529                current_arg.push(ch);
530            }
531            ',' if depth == 0 && !in_string => {
532                // End of argument
533                if !current_arg.trim().is_empty() {
534                    args.push(parse_single_arg(current_arg.trim())?);
535                    current_arg.clear();
536                }
537            }
538            _ => {
539                current_arg.push(ch);
540            }
541        }
542    }
543
544    // Last argument
545    if !current_arg.trim().is_empty() {
546        args.push(parse_single_arg(current_arg.trim())?);
547    }
548
549    Ok(args)
550}
551
552/// Parse a single argument value
553fn parse_single_arg(arg: &str) -> Result<Value> {
554    use std::collections::HashMap;
555
556    let arg = arg.trim();
557
558    // String literal
559    if arg.starts_with('"') && arg.ends_with('"') {
560        return Ok(Value::String(arg[1..arg.len() - 1].to_string()));
561    }
562
563    // Array
564    if arg.starts_with('[') && arg.ends_with(']') {
565        let inner = &arg[1..arg.len() - 1];
566        let items = parse_function_args(inner)?;
567        return Ok(Value::Array(items));
568    }
569
570    // Map/Object
571    if arg.starts_with('{') && arg.ends_with('}') {
572        let inner = &arg[1..arg.len() - 1];
573        let mut map = HashMap::new();
574
575        // Parse key: value pairs
576        for pair in inner.split(',') {
577            if let Some(colon_idx) = pair.find(':') {
578                let key = pair[..colon_idx].trim().trim_matches('"');
579                let value = parse_single_arg(pair[colon_idx + 1..].trim())?;
580                map.insert(key.to_string(), value);
581            }
582        }
583
584        return Ok(Value::Map(map));
585    }
586
587    // Number
588    if let Ok(num) = arg.parse::<f32>() {
589        return Ok(Value::Number(num));
590    }
591
592    // Boolean
593    match arg.to_lowercase().as_str() {
594        "true" => return Ok(Value::Boolean(true)),
595        "false" => return Ok(Value::Boolean(false)),
596        _ => {}
597    }
598
599    // Default to identifier
600    Ok(Value::Identifier(arg.to_string()))
601}
602
603/// Parse synth definition: synth waveform { params }
604/// Returns a Map with type="synth", waveform, and ADSR parameters
605fn parse_synth_definition(input: &str) -> Result<Value> {
606    use std::collections::HashMap;
607
608    // Remove "synth " prefix
609    let input = input.trim_start_matches("synth ").trim();
610
611    // Extract waveform (everything before '{')
612    let (waveform, params_str) = if let Some(brace_idx) = input.find('{') {
613        let waveform = input[..brace_idx].trim();
614        let params = &input[brace_idx..];
615        (waveform, params)
616    } else {
617        // No parameters, just waveform
618        return Ok(Value::Map({
619            let mut map = HashMap::new();
620            map.insert("type".to_string(), Value::String("synth".to_string()));
621            map.insert("waveform".to_string(), Value::String(input.to_string()));
622            map
623        }));
624    };
625
626    // Parse parameters from { key: value, ... }
627    let params_str = params_str.trim_matches(|c| c == '{' || c == '}').trim();
628    let mut params_map = HashMap::new();
629
630    // Add type and waveform
631    params_map.insert("type".to_string(), Value::String("synth".to_string()));
632    params_map.insert("waveform".to_string(), Value::String(waveform.to_string()));
633
634    // Parse key: value pairs (support newlines by replacing them with commas)
635    if !params_str.is_empty() {
636        // First, remove inline comments (everything after //)
637        let mut cleaned_lines = Vec::new();
638        for line in params_str.lines() {
639            if let Some(comment_pos) = line.find("//") {
640                let clean_line = &line[..comment_pos];
641                if !clean_line.trim().is_empty() {
642                    cleaned_lines.push(clean_line);
643                }
644            } else if !line.trim().is_empty() {
645                cleaned_lines.push(line);
646            }
647        }
648
649        // Now join lines and split by comma and newline
650        let cleaned = cleaned_lines.join("\n");
651        let normalized = cleaned.replace('\n', ",").replace('\r', "");
652
653        for pair in normalized.split(',') {
654            let pair = pair.trim();
655            if pair.is_empty() {
656                continue;
657            }
658
659            let parts: Vec<&str> = pair.split(':').collect();
660            if parts.len() >= 2 {
661                let key = parts[0].trim().to_string();
662                // Join back in case value contains ':'
663                let value_part = parts[1..].join(":");
664                let value_str = value_part.trim().trim_matches(',');
665
666                // Parse arrays (for filters)
667                if value_str.starts_with('[') {
668                    if let Ok(array_val) = parse_array_value(value_str) {
669                        params_map.insert(key, array_val);
670                        continue;
671                    }
672                }
673
674                // Try to parse as number
675                if let Ok(num) = value_str.parse::<f32>() {
676                    params_map.insert(key, Value::Number(num));
677                } else {
678                    // Store as string
679                    params_map.insert(key, Value::String(value_str.to_string()));
680                }
681            }
682        }
683    }
684
685    Ok(Value::Map(params_map))
686}
687
688/// Parse array value like [{ key: val }, ...]
689fn parse_array_value(input: &str) -> Result<Value> {
690    let input = input.trim().trim_matches(|c| c == '[' || c == ']').trim();
691    if input.is_empty() {
692        return Ok(Value::Array(Vec::new()));
693    }
694
695    // Check for range pattern: "start..end"
696    if input.contains("..") {
697        let parts: Vec<&str> = input.split("..").collect();
698        if parts.len() == 2 {
699            let start_str = parts[0].trim();
700            let end_str = parts[1].trim();
701
702            // Try to parse as numbers
703            if let (Ok(start), Ok(end)) = (start_str.parse::<f32>(), end_str.parse::<f32>()) {
704                return Ok(Value::Range {
705                    start: Box::new(Value::Number(start)),
706                    end: Box::new(Value::Number(end)),
707                });
708            }
709        }
710    }
711
712    let mut items = Vec::new();
713    let mut depth = 0;
714    let mut current = String::new();
715
716    for ch in input.chars() {
717        match ch {
718            '{' => {
719                depth += 1;
720                current.push(ch);
721            }
722            '}' => {
723                depth -= 1;
724                current.push(ch);
725
726                if depth == 0 && !current.trim().is_empty() {
727                    // Parse this object
728                    if let Ok(obj) = parse_map_value(&current) {
729                        items.push(obj);
730                    }
731                    current.clear();
732                }
733            }
734            ',' if depth == 0 => {
735                // Skip commas at array level
736                continue;
737            }
738            _ => {
739                current.push(ch);
740            }
741        }
742    }
743
744    Ok(Value::Array(items))
745}
746
747/// Parse map value like { key: val, key2: val2 }
748fn parse_map_value(input: &str) -> Result<Value> {
749    use std::collections::HashMap;
750
751    let input = input.trim().trim_matches(|c| c == '{' || c == '}').trim();
752    let mut map = HashMap::new();
753
754    for pair in input.split(',') {
755        let pair = pair.trim();
756        if pair.is_empty() {
757            continue;
758        }
759
760        let parts: Vec<&str> = pair.split(':').collect();
761        if parts.len() >= 2 {
762            let key = parts[0].trim().to_string();
763            // Join back in case value contains ':' (shouldn't happen but just in case)
764            let value_part = parts[1..].join(":");
765
766            // Remove inline comments (everything after //)
767            let value_clean = if let Some(comment_pos) = value_part.find("//") {
768                &value_part[..comment_pos]
769            } else {
770                &value_part
771            };
772
773            let value_str = value_clean.trim().trim_matches('"').trim_matches('\'');
774
775            // Try to parse as number
776            if let Ok(num) = value_str.parse::<f32>() {
777                map.insert(key, Value::Number(num));
778            } else {
779                map.insert(key, Value::String(value_str.to_string()));
780            }
781        }
782    }
783
784    Ok(Value::Map(map))
785}