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