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

1pub mod directive;
2pub mod duration;
3pub mod effects;
4pub mod helpers;
5pub mod preprocessing;
6pub mod routing;
7pub mod statements;
8pub mod trigger;
9// Re-export statement-level parsers so they are available at driver root
10use crate::language::syntax::ast::nodes::{Statement, StatementKind, Value};
11use anyhow::{Result, anyhow};
12pub use statements::*;
13use std::path::{Path, PathBuf};
14
15/// Entry point: parse source into a list of Statements
16pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
17    // Pre-process: merge ALL multiline statements with braces
18    let braces_pre = preprocessing::preprocess_multiline_braces(source);
19
20    // Then merge multiline arrow calls (without braces)
21    let preprocessed = preprocessing::preprocess_multiline_arrow_calls(&braces_pre);
22
23    let lines: Vec<_> = preprocessed.lines().collect();
24    parse_lines(&lines, 0, lines.len(), 0, &path)
25}
26
27/// Parse a range of lines into statements, handling indentation for blocks.
28fn parse_lines(
29    lines: &Vec<&str>,
30    start: usize,
31    end: usize,
32    indent: usize,
33    path: &Path,
34) -> Result<Vec<Statement>> {
35    use crate::language::syntax::ast::nodes::StatementKind;
36
37    let mut i = start;
38    let mut statements: Vec<Statement> = Vec::new();
39
40    while i < end {
41        let raw = lines[i];
42        let trimmed = raw.trim();
43
44        if trimmed.is_empty() || trimmed.starts_with('#') {
45            i += 1;
46            continue;
47        }
48
49        let current_indent = raw.len() - raw.trim_start().len();
50        if current_indent < indent {
51            break;
52        }
53
54        // parse header line
55        let mut statement = parse_line(trimmed, i + 1, path)?;
56        statement.indent = current_indent;
57        statement.line = i + 1;
58
59        // determine body range for block statements
60        let body_start = i + 1;
61        let mut body_end = body_start;
62        while body_end < end {
63            let l = lines[body_end];
64            if l.trim().is_empty() || l.trim().starts_with('#') {
65                body_end += 1;
66                continue;
67            }
68            let indent_l = l.len() - l.trim_start().len();
69            if indent_l <= current_indent {
70                break;
71            }
72            body_end += 1;
73        }
74
75        // If we found a body, parse it and attach appropriately based on kind
76        if body_end > body_start {
77            let body = parse_lines(lines, body_start, body_end, current_indent + 1, path)?;
78
79            // To avoid borrowing `statement.kind` and then assigning to it
80            // (which the borrow checker forbids), take ownership of the kind
81            // temporarily and then replace it with the updated version.
82            let orig_kind = std::mem::replace(&mut statement.kind, StatementKind::Unknown);
83            match orig_kind {
84                StatementKind::If { condition, .. } => {
85                    // Keep If simple here; attach else/else-if later in post-processing
86                    statement.kind = StatementKind::If {
87                        condition,
88                        body: body.clone(),
89                        else_body: None,
90                    };
91                }
92                StatementKind::For {
93                    variable, iterable, ..
94                } => {
95                    statement.kind = StatementKind::For {
96                        variable,
97                        iterable,
98                        body: body.clone(),
99                    };
100                }
101                StatementKind::Loop { count, .. } => {
102                    statement.kind = StatementKind::Loop {
103                        count,
104                        body: body.clone(),
105                    };
106                }
107                StatementKind::On { event, args, .. } => {
108                    statement.kind = StatementKind::On {
109                        event,
110                        args,
111                        body: body.clone(),
112                    };
113                }
114                StatementKind::Automate { target } => {
115                    statement.kind = StatementKind::Automate { target };
116                    // store raw body as value map if needed elsewhere
117                    let raw_lines: Vec<String> = lines[body_start..body_end]
118                        .iter()
119                        .map(|s| s.to_string())
120                        .collect();
121                    let raw_body = raw_lines.join("\n");
122                    let mut map = std::collections::HashMap::new();
123                    map.insert("body".to_string(), Value::String(raw_body));
124                    statement.value = Value::Map(map);
125                }
126                StatementKind::Function {
127                    name, parameters, ..
128                } => {
129                    statement.kind = StatementKind::Function {
130                        name: name.clone(),
131                        parameters,
132                        body: body.clone(),
133                    };
134                    statement.value = Value::Identifier(name.clone());
135                }
136                StatementKind::Group { .. } => {
137                    // attach body for groups, keep name in value if present
138                    let group_name = match &statement.value {
139                        Value::Identifier(s) => s.clone(),
140                        _ => "".to_string(),
141                    };
142                    statement.kind = StatementKind::Group {
143                        name: group_name,
144                        body: body.clone(),
145                    };
146                }
147                other => {
148                    // keep original kind for other statements
149                    statement.kind = other;
150                }
151            }
152
153            // If this statement was a plain 'else' marker (Comment with value "else"),
154            // the parsed body was not attached to the marker (we intentionally
155            // keep the marker lightweight). We must append the parsed body
156            // statements into the parent statements vector so the post-process
157            // pass can collect them and attach to the previous If.
158            i = body_end;
159            statements.push(statement);
160
161            if let StatementKind::Comment = &statements.last().unwrap().kind {
162                if let Value::String(s) = &statements.last().unwrap().value {
163                    if s == "else" {
164                        // append the body statements after the marker
165                        for stmt in body.into_iter() {
166                            statements.push(stmt);
167                        }
168                    }
169                }
170            }
171            continue;
172        }
173
174        // No body found
175        statements.push(statement);
176        i += 1;
177    }
178
179    // Post-process to attach else/else-if blocks
180    fn attach_else_blocks(statements: &mut Vec<Statement>) {
181        use crate::language::syntax::ast::StatementKind;
182
183        // Helper: attach a new else-body (Vec<Statement>) to an existing If Statement.
184        // If the If already has a nested else-if chain (encoded as a single If in
185        // its else_body vector), descend into that chain and attach to the deepest
186        // nested If instead of overwriting the entire else_body.
187        fn attach_else_to_if_statement(target: Statement, new_else: Vec<Statement>) -> Statement {
188            use crate::language::syntax::ast::StatementKind;
189
190            match target.kind {
191                StatementKind::If {
192                    condition,
193                    body,
194                    else_body,
195                } => {
196                    match else_body {
197                        None => Statement::new(
198                            StatementKind::If {
199                                condition,
200                                body,
201                                else_body: Some(new_else),
202                            },
203                            target.value,
204                            target.indent,
205                            target.line,
206                            target.column,
207                        ),
208                        Some(mut eb) => {
209                            if eb.len() == 1 {
210                                let inner = eb.remove(0);
211                                // If inner is an If, recurse into it
212                                if let StatementKind::If { .. } = inner.kind {
213                                    let updated_inner =
214                                        attach_else_to_if_statement(inner, new_else);
215                                    Statement::new(
216                                        StatementKind::If {
217                                            condition,
218                                            body,
219                                            else_body: Some(vec![updated_inner]),
220                                        },
221                                        target.value,
222                                        target.indent,
223                                        target.line,
224                                        target.column,
225                                    )
226                                } else {
227                                    // Not an If inside else_body; overwrite
228                                    Statement::new(
229                                        StatementKind::If {
230                                            condition,
231                                            body,
232                                            else_body: Some(new_else),
233                                        },
234                                        target.value,
235                                        target.indent,
236                                        target.line,
237                                        target.column,
238                                    )
239                                }
240                            } else {
241                                // Multiple statements in existing else_body; overwrite for now
242                                Statement::new(
243                                    StatementKind::If {
244                                        condition,
245                                        body,
246                                        else_body: Some(new_else),
247                                    },
248                                    target.value,
249                                    target.indent,
250                                    target.line,
251                                    target.column,
252                                )
253                            }
254                        }
255                    }
256                }
257                _ => target,
258            }
259        }
260
261        let mut idx = 0;
262        while idx < statements.len() {
263            // Handle plain 'else' marker: Comment with value "else"
264            if let StatementKind::Comment = &statements[idx].kind {
265                if let Value::String(s) = &statements[idx].value {
266                    if s == "else" {
267                        let else_indent = statements[idx].indent;
268
269                        // Collect following statements that are part of the else body
270                        let mut body: Vec<Statement> = Vec::new();
271                        let j = idx + 1;
272                        while j < statements.len() && statements[j].indent > else_indent {
273                            body.push(statements.remove(j));
274                        }
275
276                        // Remove the 'else' marker itself
277                        statements.remove(idx);
278
279                        // Find previous If to attach to (search backwards) and attach
280                        // the parsed else body to the deepest nested If in the chain.
281                        let mut k = idx as isize - 1;
282                        while k >= 0 {
283                            if let StatementKind::If { .. } = &statements[k as usize].kind {
284                                // Take ownership of the previous If statement, attach into it,
285                                // then put back the updated statement.
286                                let prev = std::mem::replace(
287                                    &mut statements[k as usize],
288                                    Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
289                                );
290                                let updated = attach_else_to_if_statement(prev, body);
291                                statements[k as usize] = updated;
292                                break;
293                            }
294                            k -= 1;
295                        }
296
297                        continue;
298                    }
299                }
300            }
301
302            // Handle 'else if' which was parsed as an If with value == "else-if"
303            if let StatementKind::If { .. } = &statements[idx].kind {
304                if let Value::String(s) = &statements[idx].value {
305                    if s == "else-if" {
306                        // Remove this else-if statement and attach it as the
307                        // else_body (single-statement vector) of the previous If.
308                        let else_if_stmt = statements.remove(idx);
309
310                        // find previous If and attach this else-if into its nested chain
311                        let mut k = idx as isize - 1;
312                        while k >= 0 {
313                            if let StatementKind::If { .. } = &statements[k as usize].kind {
314                                let prev = std::mem::replace(
315                                    &mut statements[k as usize],
316                                    Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
317                                );
318                                let updated = attach_else_to_if_statement(prev, vec![else_if_stmt]);
319                                statements[k as usize] = updated;
320                                break;
321                            }
322                            k -= 1;
323                        }
324
325                        continue;
326                    }
327                }
328            }
329
330            idx += 1;
331        }
332    }
333
334    attach_else_blocks(&mut statements);
335
336    Ok(statements)
337}
338
339fn parse_line(line: &str, line_number: usize, path: &Path) -> Result<Statement> {
340    use crate::language::syntax::parser::driver::statements::*;
341
342    if line.starts_with('@') {
343        return directive::parse_directive(line, line_number, path);
344    }
345
346    if line.starts_with('.') {
347        return trigger::parse_trigger_line(line, line_number);
348    }
349
350    let mut parts = line.split_whitespace();
351    // Extract first token as keyword and strip a trailing ':' if present
352    let first_token = parts
353        .next()
354        .ok_or_else(|| anyhow!("empty line"))?
355        .to_string();
356    let keyword = first_token.trim_end_matches(':').to_lowercase();
357
358    // Check if this is a property assignment first: target.property = value
359    if line.contains('=') && keyword.contains('.') {
360        return parse_assign(line, line_number);
361    }
362
363    // Check if this looks like a trigger (contains a dot like "drums.kick")
364    if keyword.contains('.') && !keyword.contains('(') {
365        // Reconstruct the line and parse as trigger
366        return trigger::parse_trigger_line(line, line_number);
367    }
368
369    // Check if this is a bind statement first (before arrow call)
370    if keyword == "bind" && line.contains("->") {
371        return parse_bind(line, line_number);
372    }
373
374    // If this line contains an arrow call, parse it as an ArrowCall, UNLESS the
375    // statement starts with a reserved keyword that must be handled (let/var/const/etc.).
376    // This ensures constructs like `let name = .bank.kick -> reverse(...)` are
377    // parsed by `parse_let` rather than being mis-parsed as an ArrowCall.
378    let reserved_keywords = [
379        "bpm", "tempo", "print", "sleep", "pattern", "bank", "let", "var", "const", "for", "loop",
380        "if", "else", "group", "automate", "call", "spawn", "on", "emit", "routing", "return",
381        "break",
382    ];
383    if line.contains("->") && !reserved_keywords.contains(&keyword.as_str()) {
384        return statements::parse_arrow_call(line, line_number);
385    }
386
387    return match keyword.as_str() {
388        "bpm" | "tempo" => statements::core::parse_tempo(parts, line_number),
389        "print" => statements::core::parse_print(line, line_number),
390        "sleep" => statements::core::parse_sleep(parts, line_number),
391        "trigger" => Err(anyhow!(
392            "keyword 'trigger' is deprecated; use dot notation like '.alias' instead"
393        )),
394        "pattern" => parse_pattern(parts, line_number),
395        "bank" => parse_bank(parts, line_number),
396        "let" => parse_let(line, parts, line_number),
397        "var" => parse_var(line, parts, line_number),
398        "const" => parse_const(line, parts, line_number),
399        "for" => parse_for(parts, line_number),
400        "loop" => parse_loop(parts, line_number),
401        "if" => statements::structure::parse_if(parts, line_number),
402        "else" => statements::structure::parse_else(line, line_number),
403        "group" => statements::structure::parse_group(parts, line_number),
404        "automate" => {
405            crate::language::syntax::parser::driver::statements::structure::parse_automate(
406                parts,
407                line_number,
408            )
409        }
410        "call" => parse_call(line, parts, line_number),
411        "break" => statements::structure::parse_break(parts, line_number),
412        "function" => statements::structure::parse_function(line, line_number),
413        "spawn" => parse_spawn(parts, line_number),
414        "on" => parse_on(parts, line_number),
415        "emit" => parse_emit(line, parts, line_number),
416        "return" => statements::core::parse_return(line, line_number),
417        "routing" => crate::language::syntax::parser::driver::routing::parse_routing_command(
418            parts,
419            line_number,
420        ),
421        _ => {
422            // Check if this looks like a potential trigger identifier (single word, no special chars except dots)
423            // This handles cases like variable references: let kick = drums.kick; kick
424            if keyword
425                .chars()
426                .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
427            {
428                // Parse as trigger
429                return trigger::parse_trigger_line(line, line_number);
430            }
431
432            let error_msg = format!(
433                "Unknown statement '{}' at {}:{}",
434                keyword,
435                path.display(),
436                line_number
437            );
438
439            // Push structured error to WASM registry if available
440            #[cfg(feature = "wasm")]
441            {
442                use crate::web::registry::debug;
443                if debug::is_debug_errors_enabled() {
444                    // wasm registry expects: message, line, column, error_type
445                    debug::push_parse_error_from_parts(
446                        format!("Unknown statement '{}'", keyword),
447                        line_number,
448                        1,
449                        "UnknownStatement".to_string(),
450                    );
451                }
452            }
453
454            return Ok(Statement::new(
455                StatementKind::Unknown,
456                Value::String(error_msg),
457                0,
458                line_number,
459                1,
460            ));
461        }
462    };
463}
464
465/// Re-export helper parsing functions from helpers.rs so other modules can call them
466pub use helpers::{
467    parse_array_value, parse_condition, parse_function_args, parse_map_value, parse_single_arg,
468    parse_synth_definition,
469};
470
471/// SimpleParser is a small wrapper used by other modules/tests in the crate.
472/// It forwards to the top-level parse implementation above.
473pub struct SimpleParser;
474
475impl SimpleParser {
476    pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
477        crate::language::syntax::parser::driver::parse(source, path)
478    }
479
480    pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Vec<Statement>> {
481        let buf = std::path::PathBuf::from(path.as_ref());
482        let s = std::fs::read_to_string(&buf)?;
483        Self::parse(&s, buf)
484    }
485}