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