1pub mod directive;
2pub mod duration;
3pub mod effects;
4pub mod helpers;
5pub mod preprocessing;
6pub mod routing;
7pub mod statements;
8pub mod trigger;
9use crate::language::syntax::ast::nodes::{Statement, StatementKind, Value};
11use anyhow::{Result, anyhow};
12pub use statements::*;
13use std::path::{Path, PathBuf};
14
15fn find_keyword_suggestion(input: &str, keywords: &[&str]) -> Option<String> {
18 fn levenshtein(s1: &str, s2: &str) -> usize {
20 let len1 = s1.len();
21 let len2 = s2.len();
22 let mut matrix = vec![vec![0; len2 + 1]; len1 + 1];
23
24 for i in 0..=len1 {
25 matrix[i][0] = i;
26 }
27 for j in 0..=len2 {
28 matrix[0][j] = j;
29 }
30
31 for (i, c1) in s1.chars().enumerate() {
32 for (j, c2) in s2.chars().enumerate() {
33 let cost = if c1 == c2 { 0 } else { 1 };
34 matrix[i + 1][j + 1] = std::cmp::min(
35 std::cmp::min(
36 matrix[i][j + 1] + 1, matrix[i + 1][j] + 1, ),
39 matrix[i][j] + cost, );
41 }
42 }
43 matrix[len1][len2]
44 }
45
46 let mut best = (usize::MAX, "");
47 for &keyword in keywords {
48 let distance = levenshtein(input, keyword);
49 if distance < best.0 && distance <= 2 {
50 best = (distance, keyword);
51 }
52 }
53
54 if best.0 <= 2 {
55 Some(best.1.to_string())
56 } else {
57 None
58 }
59}
60
61pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
63 let braces_pre = preprocessing::preprocess_multiline_braces(source);
65
66 let brackets_pre = preprocessing::preprocess_multiline_brackets(&braces_pre);
68
69 let preprocessed = preprocessing::preprocess_multiline_arrow_calls(&brackets_pre);
71
72 let lines: Vec<_> = preprocessed.lines().collect();
73 parse_lines(&lines, 0, lines.len(), 0, &path)
74}
75
76fn parse_lines(
78 lines: &Vec<&str>,
79 start: usize,
80 end: usize,
81 indent: usize,
82 path: &Path,
83) -> Result<Vec<Statement>> {
84 use crate::language::syntax::ast::nodes::StatementKind;
85
86 let mut i = start;
87 let mut statements: Vec<Statement> = Vec::new();
88
89 while i < end {
90 let raw = lines[i];
91 let trimmed = raw.trim();
92
93 if trimmed.is_empty() || trimmed.starts_with('#') {
94 i += 1;
95 continue;
96 }
97
98 let current_indent = raw.len() - raw.trim_start().len();
99 if current_indent < indent {
100 break;
101 }
102
103 let mut statement = parse_line(trimmed, i + 1, path)?;
105 statement.indent = current_indent;
106 statement.line = i + 1;
107
108 let body_start = i + 1;
110 let mut body_end = body_start;
111 while body_end < end {
112 let l = lines[body_end];
113 if l.trim().is_empty() || l.trim().starts_with('#') {
114 body_end += 1;
115 continue;
116 }
117 let indent_l = l.len() - l.trim_start().len();
118 if indent_l <= current_indent {
119 break;
120 }
121 body_end += 1;
122 }
123
124 if body_end > body_start {
126 let body = parse_lines(lines, body_start, body_end, current_indent + 1, path)?;
127
128 let orig_kind = std::mem::replace(&mut statement.kind, StatementKind::Unknown);
132 match orig_kind {
133 StatementKind::If { condition, .. } => {
134 statement.kind = StatementKind::If {
136 condition,
137 body: body.clone(),
138 else_body: None,
139 };
140 }
141 StatementKind::While { condition, .. } => {
142 statement.kind = StatementKind::While {
144 condition,
145 body: body.clone(),
146 };
147 }
148 StatementKind::For {
149 variable, iterable, ..
150 } => {
151 statement.kind = StatementKind::For {
152 variable,
153 iterable,
154 body: body.clone(),
155 };
156 }
157 StatementKind::Loop { count, .. } => {
158 statement.kind = StatementKind::Loop {
159 count,
160 body: body.clone(),
161 };
162 }
163 StatementKind::On { event, args, .. } => {
164 statement.kind = StatementKind::On {
165 event,
166 args,
167 body: body.clone(),
168 };
169 }
170 StatementKind::Automate { target } => {
171 statement.kind = StatementKind::Automate { target };
172 let raw_lines: Vec<String> = lines[body_start..body_end]
174 .iter()
175 .map(|s| s.to_string())
176 .collect();
177 let raw_body = raw_lines.join("\n");
178 let mut map = std::collections::HashMap::new();
179 map.insert("body".to_string(), Value::String(raw_body));
180 statement.value = Value::Map(map);
181 }
182 StatementKind::Function {
183 name, parameters, ..
184 } => {
185 statement.kind = StatementKind::Function {
186 name: name.clone(),
187 parameters,
188 body: body.clone(),
189 };
190 statement.value = Value::Identifier(name.clone());
191 }
192 StatementKind::Group { duration, .. } => {
193 let group_name = match &statement.value {
195 Value::Identifier(s) => s.clone(),
196 _ => "".to_string(),
197 };
198 statement.kind = StatementKind::Group {
199 name: group_name,
200 body: body.clone(),
201 duration,
202 };
203 }
204 StatementKind::Routing { .. } => {
205 statement.kind = StatementKind::Routing { body: body.clone() };
207 }
208 StatementKind::Section { name, duration, .. } => {
209 statement.kind = StatementKind::Section {
210 name,
211 duration,
212 body: body.clone(),
213 };
214 }
215 StatementKind::Schedule { .. } => {
216 let mut events = Vec::new();
218 let mut i_local = body_start;
219 while i_local < body_end {
220 let line = lines[i_local].trim();
221
222 if line.is_empty() || line.starts_with('#') {
224 i_local += 1;
225 continue;
226 }
227
228 if line.starts_with("at ") {
230 if let Ok((time, optional_duration)) =
231 statements::extended::parse_schedule_event_header(lines[i_local])
232 {
233 let event_body_start = i_local + 1;
235 let mut event_body_end = event_body_start;
236
237 while event_body_end < body_end {
238 let l = lines[event_body_end];
239 if l.trim().is_empty() || l.trim().starts_with('#') {
240 event_body_end += 1;
241 continue;
242 }
243
244 let indent_l = l.len() - l.trim_start().len();
245 let event_indent =
246 lines[i_local].len() - lines[i_local].trim_start().len();
247
248 if l.trim().starts_with("at ") && indent_l == event_indent {
250 break;
251 }
252
253 if indent_l <= event_indent {
254 break;
255 }
256
257 event_body_end += 1;
258 }
259
260 if event_body_end > event_body_start {
262 if let Ok(event_stmts) = parse_lines(
263 lines,
264 event_body_start,
265 event_body_end,
266 current_indent + 1,
267 path,
268 ) {
269 events.push((time, optional_duration, event_stmts));
270 }
271 }
272
273 i_local = event_body_end;
274 } else {
275 i_local += 1;
276 }
277 } else {
278 i_local += 1;
279 }
280 }
281
282 statement.kind = StatementKind::Schedule { events };
283 }
284 StatementKind::Tempo {
285 value,
286 body: Some(_),
287 } => {
288 statement.kind = StatementKind::Tempo {
290 value,
291 body: Some(body.clone()),
292 };
293 }
294
295 other => {
296 statement.kind = other;
298 }
299 }
300
301 i = body_end;
307 statements.push(statement);
308
309 if let StatementKind::Comment = &statements.last().unwrap().kind {
310 if let Value::String(s) = &statements.last().unwrap().value {
311 if s == "else" {
312 for stmt in body.into_iter() {
314 statements.push(stmt);
315 }
316 }
317 }
318 }
319 continue;
320 }
321
322 statements.push(statement);
324 i += 1;
325 }
326
327 fn attach_else_blocks(statements: &mut Vec<Statement>) {
329 use crate::language::syntax::ast::StatementKind;
330
331 fn attach_else_to_if_statement(target: Statement, new_else: Vec<Statement>) -> Statement {
336 use crate::language::syntax::ast::StatementKind;
337
338 match target.kind {
339 StatementKind::If {
340 condition,
341 body,
342 else_body,
343 } => {
344 match else_body {
345 None => Statement::new(
346 StatementKind::If {
347 condition,
348 body,
349 else_body: Some(new_else),
350 },
351 target.value,
352 target.indent,
353 target.line,
354 target.column,
355 ),
356 Some(mut eb) => {
357 if eb.len() == 1 {
358 let inner = eb.remove(0);
359 if let StatementKind::If { .. } = inner.kind {
361 let updated_inner =
362 attach_else_to_if_statement(inner, new_else);
363 Statement::new(
364 StatementKind::If {
365 condition,
366 body,
367 else_body: Some(vec![updated_inner]),
368 },
369 target.value,
370 target.indent,
371 target.line,
372 target.column,
373 )
374 } else {
375 Statement::new(
377 StatementKind::If {
378 condition,
379 body,
380 else_body: Some(new_else),
381 },
382 target.value,
383 target.indent,
384 target.line,
385 target.column,
386 )
387 }
388 } else {
389 Statement::new(
391 StatementKind::If {
392 condition,
393 body,
394 else_body: Some(new_else),
395 },
396 target.value,
397 target.indent,
398 target.line,
399 target.column,
400 )
401 }
402 }
403 }
404 }
405 _ => target,
406 }
407 }
408
409 let mut idx = 0;
410 while idx < statements.len() {
411 if let StatementKind::Comment = &statements[idx].kind {
413 if let Value::String(s) = &statements[idx].value {
414 if s == "else" {
415 let else_indent = statements[idx].indent;
416
417 let mut body: Vec<Statement> = Vec::new();
419 let j = idx + 1;
420 while j < statements.len() && statements[j].indent > else_indent {
421 body.push(statements.remove(j));
422 }
423
424 statements.remove(idx);
426
427 let mut k = idx as isize - 1;
430 while k >= 0 {
431 if let StatementKind::If { .. } = &statements[k as usize].kind {
432 let prev = std::mem::replace(
435 &mut statements[k as usize],
436 Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
437 );
438 let updated = attach_else_to_if_statement(prev, body);
439 statements[k as usize] = updated;
440 break;
441 }
442 k -= 1;
443 }
444
445 continue;
446 }
447 }
448 }
449
450 if let StatementKind::If { .. } = &statements[idx].kind {
452 if let Value::String(s) = &statements[idx].value {
453 if s == "else-if" {
454 let else_if_stmt = statements.remove(idx);
457
458 let mut k = idx as isize - 1;
460 while k >= 0 {
461 if let StatementKind::If { .. } = &statements[k as usize].kind {
462 let prev = std::mem::replace(
463 &mut statements[k as usize],
464 Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
465 );
466 let updated = attach_else_to_if_statement(prev, vec![else_if_stmt]);
467 statements[k as usize] = updated;
468 break;
469 }
470 k -= 1;
471 }
472
473 continue;
474 }
475 }
476 }
477
478 idx += 1;
479 }
480 }
481
482 attach_else_blocks(&mut statements);
483
484 Ok(statements)
485}
486
487fn parse_line(line: &str, line_number: usize, path: &Path) -> Result<Statement> {
488 use crate::language::syntax::parser::driver::statements::*;
489
490 if line.starts_with('.') {
491 return trigger::parse_trigger_line(line, line_number);
492 }
493
494 let mut parts = line.split_whitespace();
495 let first_token = parts
497 .next()
498 .ok_or_else(|| anyhow!("empty line"))?
499 .to_string();
500 let keyword = first_token.trim_end_matches(':').to_lowercase();
501
502 let routing_keywords = ["node", "fx", "route", "duck", "sidechain"];
504 if routing_keywords.contains(&keyword.as_str()) {
505 return crate::language::syntax::parser::driver::routing::parse_routing_statement(
506 line,
507 line_number,
508 );
509 }
510
511 if line.contains('=') && keyword.contains('.') {
513 return parse_assign(line, line_number);
514 }
515
516 if keyword.contains('.') && !keyword.contains('(') {
518 return trigger::parse_trigger_line(line, line_number);
520 }
521
522 if keyword == "bind" && line.contains("->") {
524 return parse_bind(line, line_number);
525 }
526
527 let reserved_keywords = [
532 "bpm", "tempo", "print", "sleep", "rest", "wait", "pattern", "bank", "let", "const", "for",
533 "foreach", "loop", "if", "while", "else", "group", "automate", "call", "spawn", "sequence",
534 "layer", "on", "emit", "routing", "return", "break", "import", "export", "use", "load",
535 "fade", "section", "timeline", "schedule", "stop", "silence", "mute",
536 ];
537 if line.contains("->") && !reserved_keywords.contains(&keyword.as_str()) {
538 return statements::parse_arrow_call(line, line_number);
539 }
540
541 return match keyword.as_str() {
542 "bpm" | "tempo" => statements::core::parse_tempo(line, line_number),
543 "print" => statements::core::parse_print(line, line_number),
544 "sleep" | "rest" | "wait" => statements::core::parse_sleep(parts, line_number),
545 "trigger" => Err(anyhow!(
546 "keyword 'trigger' is deprecated; use dot notation like '.alias' instead"
547 )),
548 "pattern" => parse_pattern(parts, line_number),
549 "bank" => parse_bank(parts, line_number),
550 "let" => parse_let(line, parts, line_number),
551 "const" => parse_const(line, parts, line_number),
552 "for" | "foreach" => parse_for(parts, line_number),
553 "loop" => parse_loop(parts, line_number),
554 "if" => statements::structure::parse_if(parts, line_number),
555 "while" => statements::structure::parse_while(parts, line_number),
556 "else" => statements::structure::parse_else(line, line_number),
557 "group" => statements::structure::parse_group(parts, line_number),
558 "automate" => {
559 crate::language::syntax::parser::driver::statements::structure::parse_automate(
560 parts,
561 line_number,
562 )
563 }
564 "call" | "sequence" => parse_call(line, parts, line_number),
565 "break" => statements::structure::parse_break(parts, line_number),
566 "function" => statements::structure::parse_function(line, line_number),
567 "spawn" | "layer" => parse_spawn(parts, line_number),
568 "on" => parse_on(parts, line_number),
569 "emit" => parse_emit(line, parts, line_number),
570 "fade" => statements::extended::parse_fade(line, line_number),
571 "section" => statements::extended::parse_section(parts, line_number),
572 "timeline" => statements::extended::parse_timeline(line, line_number),
573 "schedule" => statements::extended::parse_schedule(line, line_number),
574 "stop" | "silence" | "mute" => statements::extended::parse_stop(line, line_number),
575 "return" => statements::core::parse_return(line, line_number),
576 "routing" => {
577 crate::language::syntax::parser::driver::routing::parse_routing_command(line_number)
578 }
579 "import" => directive::parse_directive_keyword(line, "import", line_number, path),
580 "export" => directive::parse_directive_keyword(line, "export", line_number, path),
581 "use" => directive::parse_directive_keyword(line, "use", line_number, path),
582 "load" => directive::parse_directive_keyword(line, "load", line_number, path),
583 _ => {
584 let suggestion = find_keyword_suggestion(&keyword, &reserved_keywords);
586
587 if suggestion.is_some() {
589 let suggestion_str = suggestion.unwrap();
590 let error_msg = format!(
593 "Unknown statement '{}' at {}:{}|||{}|||{}",
594 keyword,
595 path.display(),
596 line_number,
597 path.display(),
598 suggestion_str
599 );
600
601 #[cfg(feature = "wasm")]
603 {
604 use crate::web::registry::debug;
605 if debug::is_debug_errors_enabled() {
606 debug::push_parse_error_from_parts(
607 format!(
608 "Unknown statement '{}'. Did you mean '{}' ?",
609 keyword, suggestion_str
610 ),
611 line_number,
612 1,
613 "UnknownStatement".to_string(),
614 );
615 }
616 }
617
618 return Ok(Statement::new(
619 StatementKind::Unknown,
620 Value::String(error_msg),
621 0,
622 line_number,
623 1,
624 ));
625 }
626
627 if keyword
630 .chars()
631 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
632 {
633 return trigger::parse_trigger_line(line, line_number);
635 }
636
637 let error_msg = format!(
640 "Unknown statement '{}' at {}:{}|||{}|||",
641 keyword,
642 path.display(),
643 line_number,
644 path.display()
645 );
646
647 #[cfg(feature = "wasm")]
649 {
650 use crate::web::registry::debug;
651 if debug::is_debug_errors_enabled() {
652 debug::push_parse_error_from_parts(
653 format!("Unknown statement '{}'", keyword),
654 line_number,
655 1,
656 "UnknownStatement".to_string(),
657 );
658 }
659 }
660
661 return Ok(Statement::new(
662 StatementKind::Unknown,
663 Value::String(error_msg),
664 0,
665 line_number,
666 1,
667 ));
668 }
669 };
670}
671
672pub use duration::parse_duration_token;
673pub use helpers::{
675 parse_array_value, parse_condition, parse_function_args, parse_map_value, parse_single_arg,
676 parse_synth_definition,
677};
678
679pub struct SimpleParser;
682
683impl SimpleParser {
684 pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
685 crate::language::syntax::parser::driver::parse(source, path)
686 }
687
688 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Vec<Statement>> {
689 let buf = std::path::PathBuf::from(path.as_ref());
690 let s = std::fs::read_to_string(&buf)?;
691 Self::parse(&s, buf)
692 }
693}