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() || is_comment_line(trimmed) {
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() || is_comment_line(l.trim()) {
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() || is_comment_line(line) {
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() || is_comment_line(l.trim()) {
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 Some(last_statement) = statements.last() {
310 if let StatementKind::Comment = &last_statement.kind {
311 if let Value::String(s) = &last_statement.value {
312 if s == "else" {
313 for stmt in body.into_iter() {
315 statements.push(stmt);
316 }
317 }
318 }
319 }
320 }
321 continue;
322 }
323
324 statements.push(statement);
326 i += 1;
327 }
328
329 fn attach_else_blocks(statements: &mut Vec<Statement>) {
331 use crate::language::syntax::ast::StatementKind;
332
333 fn attach_else_to_if_statement(target: Statement, new_else: Vec<Statement>) -> Statement {
338 use crate::language::syntax::ast::StatementKind;
339
340 match target.kind {
341 StatementKind::If {
342 condition,
343 body,
344 else_body,
345 } => {
346 match else_body {
347 None => Statement::new(
348 StatementKind::If {
349 condition,
350 body,
351 else_body: Some(new_else),
352 },
353 target.value,
354 target.indent,
355 target.line,
356 target.column,
357 ),
358 Some(mut eb) => {
359 if eb.len() == 1 {
360 let inner = eb.remove(0);
361 if let StatementKind::If { .. } = inner.kind {
363 let updated_inner =
364 attach_else_to_if_statement(inner, new_else);
365 Statement::new(
366 StatementKind::If {
367 condition,
368 body,
369 else_body: Some(vec![updated_inner]),
370 },
371 target.value,
372 target.indent,
373 target.line,
374 target.column,
375 )
376 } else {
377 Statement::new(
379 StatementKind::If {
380 condition,
381 body,
382 else_body: Some(new_else),
383 },
384 target.value,
385 target.indent,
386 target.line,
387 target.column,
388 )
389 }
390 } else {
391 Statement::new(
393 StatementKind::If {
394 condition,
395 body,
396 else_body: Some(new_else),
397 },
398 target.value,
399 target.indent,
400 target.line,
401 target.column,
402 )
403 }
404 }
405 }
406 }
407 _ => target,
408 }
409 }
410
411 let mut idx = 0;
412 while idx < statements.len() {
413 if let StatementKind::Comment = &statements[idx].kind {
415 if let Value::String(s) = &statements[idx].value {
416 if s == "else" {
417 let else_indent = statements[idx].indent;
418
419 let mut body: Vec<Statement> = Vec::new();
421 let j = idx + 1;
422 while j < statements.len() && statements[j].indent > else_indent {
423 body.push(statements.remove(j));
424 }
425
426 statements.remove(idx);
428
429 let mut k = idx as isize - 1;
432 while k >= 0 {
433 if let StatementKind::If { .. } = &statements[k as usize].kind {
434 let prev = std::mem::replace(
437 &mut statements[k as usize],
438 Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
439 );
440 let updated = attach_else_to_if_statement(prev, body);
441 statements[k as usize] = updated;
442 break;
443 }
444 k -= 1;
445 }
446
447 continue;
448 }
449 }
450 }
451
452 if let StatementKind::If { .. } = &statements[idx].kind {
454 if let Value::String(s) = &statements[idx].value {
455 if s == "else-if" {
456 let else_if_stmt = statements.remove(idx);
459
460 let mut k = idx as isize - 1;
462 while k >= 0 {
463 if let StatementKind::If { .. } = &statements[k as usize].kind {
464 let prev = std::mem::replace(
465 &mut statements[k as usize],
466 Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
467 );
468 let updated = attach_else_to_if_statement(prev, vec![else_if_stmt]);
469 statements[k as usize] = updated;
470 break;
471 }
472 k -= 1;
473 }
474
475 continue;
476 }
477 }
478 }
479
480 idx += 1;
481 }
482 }
483
484 attach_else_blocks(&mut statements);
485
486 Ok(statements)
487}
488
489fn parse_line(line: &str, line_number: usize, path: &Path) -> Result<Statement> {
490 use crate::language::syntax::parser::driver::statements::*;
491
492 if line.starts_with('.') {
493 return trigger::parse_trigger_line(line, line_number);
494 }
495
496 let mut parts = line.split_whitespace();
497 let first_token = parts
499 .next()
500 .ok_or_else(|| anyhow!("empty line"))?
501 .to_string();
502 let keyword = first_token.trim_end_matches(':').to_lowercase();
503
504 let routing_keywords = ["node", "fx", "route", "duck", "sidechain"];
506 if routing_keywords.contains(&keyword.as_str()) {
507 return crate::language::syntax::parser::driver::routing::parse_routing_statement(
508 line,
509 line_number,
510 );
511 }
512
513 if let Some(statement) = statements::structure::parse_pattern_shorthand(line, line_number)? {
514 return Ok(statement);
515 }
516
517 if line.contains('=') && keyword.contains('.') {
519 return parse_assign(line, line_number);
520 }
521
522 if keyword.contains('.') && !keyword.contains('(') {
524 return trigger::parse_trigger_line(line, line_number);
526 }
527
528 if keyword == "bind" && line.contains("->") {
530 return parse_bind(line, line_number);
531 }
532
533 let reserved_keywords = [
538 "bpm", "tempo", "print", "sleep", "rest", "wait", "pattern", "bank", "let", "const", "for",
539 "foreach", "loop", "if", "while", "else", "group", "automate", "call", "spawn", "sequence",
540 "layer", "on", "emit", "routing", "return", "break", "import", "export", "use", "load",
541 "fade", "section", "timeline", "schedule", "stop", "silence", "mute",
542 ];
543 if line.contains("->") && !reserved_keywords.contains(&keyword.as_str()) {
544 return statements::parse_arrow_call(line, line_number);
545 }
546
547 return match keyword.as_str() {
548 "bpm" | "tempo" => statements::core::parse_tempo(line, line_number),
549 "print" => statements::core::parse_print(line, line_number),
550 "sleep" | "rest" | "wait" => statements::core::parse_sleep(parts, line_number),
551 "trigger" => Err(anyhow!(
552 "keyword 'trigger' is deprecated; use dot notation like '.alias' instead"
553 )),
554 "pattern" => parse_pattern(parts, line_number),
555 "bank" => parse_bank(parts, line_number),
556 "let" => parse_let(line, parts, line_number),
557 "const" => parse_const(line, parts, line_number),
558 "for" | "foreach" => parse_for(parts, line_number),
559 "loop" => parse_loop(parts, line_number),
560 "if" => statements::structure::parse_if(parts, line_number),
561 "while" => statements::structure::parse_while(parts, line_number),
562 "else" => statements::structure::parse_else(line, line_number),
563 "group" => statements::structure::parse_group(parts, line_number),
564 "automate" => {
565 crate::language::syntax::parser::driver::statements::structure::parse_automate(
566 parts,
567 line_number,
568 )
569 }
570 "call" | "sequence" => parse_call(line, parts, line_number),
571 "break" => statements::structure::parse_break(parts, line_number),
572 "function" => statements::structure::parse_function(line, line_number),
573 "spawn" | "layer" => parse_spawn(parts, line_number),
574 "on" => parse_on(parts, line_number),
575 "emit" => parse_emit(line, parts, line_number),
576 "fade" => statements::extended::parse_fade(line, line_number),
577 "section" => statements::extended::parse_section(parts, line_number),
578 "timeline" => statements::extended::parse_timeline(line, line_number),
579 "schedule" => statements::extended::parse_schedule(line, line_number),
580 "stop" | "silence" | "mute" => statements::extended::parse_stop(line, line_number),
581 "return" => statements::core::parse_return(line, line_number),
582 "routing" => {
583 crate::language::syntax::parser::driver::routing::parse_routing_command(line_number)
584 }
585 "import" => directive::parse_directive_keyword(line, "import", line_number, path),
586 "export" => directive::parse_directive_keyword(line, "export", line_number, path),
587 "use" => directive::parse_directive_keyword(line, "use", line_number, path),
588 "load" => directive::parse_directive_keyword(line, "load", line_number, path),
589 _ => {
590 let suggestion = find_keyword_suggestion(&keyword, &reserved_keywords);
592
593 if let Some(suggestion_str) = suggestion {
595 let error_msg = format!(
598 "Unknown statement '{}' at {}:{}|||{}|||{}",
599 keyword,
600 path.display(),
601 line_number,
602 path.display(),
603 suggestion_str
604 );
605
606 #[cfg(feature = "wasm")]
608 {
609 use crate::web::registry::debug;
610 if debug::is_debug_errors_enabled() {
611 debug::push_parse_error_from_parts(
612 format!(
613 "Unknown statement '{}'. Did you mean '{}' ?",
614 keyword, suggestion_str
615 ),
616 line_number,
617 1,
618 "UnknownStatement".to_string(),
619 );
620 }
621 }
622
623 return Ok(Statement::new(
624 StatementKind::Unknown,
625 Value::String(error_msg),
626 0,
627 line_number,
628 1,
629 ));
630 }
631
632 if keyword
635 .chars()
636 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
637 {
638 return trigger::parse_trigger_line(line, line_number);
640 }
641
642 let error_msg = format!(
645 "Unknown statement '{}' at {}:{}|||{}|||",
646 keyword,
647 path.display(),
648 line_number,
649 path.display()
650 );
651
652 #[cfg(feature = "wasm")]
654 {
655 use crate::web::registry::debug;
656 if debug::is_debug_errors_enabled() {
657 debug::push_parse_error_from_parts(
658 format!("Unknown statement '{}'", keyword),
659 line_number,
660 1,
661 "UnknownStatement".to_string(),
662 );
663 }
664 }
665
666 return Ok(Statement::new(
667 StatementKind::Unknown,
668 Value::String(error_msg),
669 0,
670 line_number,
671 1,
672 ));
673 }
674 };
675}
676
677fn is_comment_line(line: &str) -> bool {
678 line.starts_with('#') || line.starts_with("//")
679}
680
681pub use duration::parse_duration_token;
682pub use helpers::{
684 parse_array_value, parse_condition, parse_function_args, parse_map_value, parse_single_arg,
685 parse_synth_definition,
686};
687
688pub struct SimpleParser;
691
692impl SimpleParser {
693 pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
694 crate::language::syntax::parser::driver::parse(source, path)
695 }
696
697 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Vec<Statement>> {
698 let buf = std::path::PathBuf::from(path.as_ref());
699 let s = std::fs::read_to_string(&buf)?;
700 let s = s.strip_prefix('\u{feff}').unwrap_or(&s);
701 Self::parse(&s, buf)
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708
709 #[test]
710 fn test_unknown_statement_with_suggestion_is_structured() {
711 let statements = parse("pritn hello", PathBuf::from("test.deva")).unwrap();
712
713 assert_eq!(statements.len(), 1);
714 assert!(matches!(statements[0].kind, StatementKind::Unknown));
715 match &statements[0].value {
716 Value::String(message) => {
717 assert!(message.contains("Unknown statement 'pritn'"));
718 assert!(message.ends_with("|||print"));
719 }
720 other => panic!("expected structured unknown message, got {:?}", other),
721 }
722 }
723
724 #[test]
725 fn test_else_body_is_attached_to_previous_if() {
726 let source = "if true:\n print yes\nelse:\n print no";
727 let statements = parse(source, PathBuf::from("test.deva")).unwrap();
728
729 assert_eq!(statements.len(), 1);
730 match &statements[0].kind {
731 StatementKind::If {
732 body, else_body, ..
733 } => {
734 assert_eq!(body.len(), 1);
735 let else_body = else_body.as_ref().expect("else body should be attached");
736 assert_eq!(else_body.len(), 1);
737 assert!(matches!(else_body[0].kind, StatementKind::Print));
738 }
739 other => panic!("expected if statement, got {:?}", other),
740 }
741 }
742
743 #[test]
744 fn test_bare_synth_chained_waveform_and_preset() {
745 let source = r#"let bass = synth -> waveform("saw") -> preset("bass")"#;
746 let statements = parse(source, PathBuf::from("test.deva")).unwrap();
747
748 assert_eq!(statements.len(), 1);
749 match &statements[0].kind {
750 StatementKind::Let {
751 value: Some(Value::Map(map)),
752 ..
753 } => {
754 assert!(matches!(
755 map.get("type"),
756 Some(Value::String(kind)) if kind == "synth"
757 ));
758 assert!(matches!(
759 map.get("waveform"),
760 Some(Value::String(waveform)) if waveform == "saw"
761 ));
762 assert!(matches!(
763 map.get("synth_type"),
764 Some(Value::String(preset)) if preset == "bass"
765 ));
766 }
767 other => panic!("expected synth let statement, got {:?}", other),
768 }
769 }
770
771 #[test]
772 fn test_livecoding_synth_aliases_parse_to_canonical_map() {
773 let source = r#"let bass = synth -> wave("saw") -> preset("bass") -> voices(2) -> env({ attack: 0.01, release: 0.08 })"#;
774 let statements = parse(source, PathBuf::from("test.deva")).unwrap();
775
776 assert_eq!(statements.len(), 1);
777 match &statements[0].kind {
778 StatementKind::Let {
779 value: Some(Value::Map(map)),
780 ..
781 } => {
782 assert!(matches!(
783 map.get("waveform"),
784 Some(Value::String(waveform)) if waveform == "saw"
785 ));
786 assert!(matches!(
787 map.get("synth_type"),
788 Some(Value::String(preset)) if preset == "bass"
789 ));
790 assert!(
791 matches!(map.get("attack"), Some(Value::Number(n)) if (*n - 0.01).abs() < f32::EPSILON)
792 );
793 assert!(
794 matches!(map.get("release"), Some(Value::Number(n)) if (*n - 0.08).abs() < f32::EPSILON)
795 );
796 assert!(
797 matches!(map.get("voices"), Some(Value::Number(n)) if (*n - 2.0).abs() < f32::EPSILON)
798 );
799 }
800 other => panic!("expected synth let statement, got {:?}", other),
801 }
802 }
803
804 #[test]
805 fn test_pattern_shorthand_without_options() {
806 let source = r#"kick: drums.kick = "x--- x---""#;
807 let statements = parse(source, PathBuf::from("test.deva")).unwrap();
808
809 assert_eq!(statements.len(), 1);
810 match &statements[0].kind {
811 StatementKind::Pattern { name, target } => {
812 assert_eq!(name, "kick");
813 assert_eq!(target.as_deref(), Some("drums.kick"));
814 assert!(
815 matches!(&statements[0].value, Value::String(pattern) if pattern == "x--- x---")
816 );
817 }
818 other => panic!("expected pattern shorthand, got {:?}", other),
819 }
820 }
821
822 #[test]
823 fn test_pattern_shorthand_with_arrow_options() {
824 let source = r#"hat: drums.hat -> swing(0.08) -> velocity(0.7) = "x-x-""#;
825 let statements = parse(source, PathBuf::from("test.deva")).unwrap();
826
827 assert_eq!(statements.len(), 1);
828 match &statements[0].kind {
829 StatementKind::Pattern { name, target } => {
830 assert_eq!(name, "hat");
831 assert_eq!(target.as_deref(), Some("drums.hat"));
832 }
833 other => panic!("expected pattern shorthand, got {:?}", other),
834 }
835
836 match &statements[0].value {
837 Value::Map(map) => {
838 assert!(
839 matches!(map.get("pattern"), Some(Value::String(pattern)) if pattern == "x-x-")
840 );
841 assert!(
842 matches!(map.get("swing"), Some(Value::Number(n)) if (*n - 0.08).abs() < f32::EPSILON)
843 );
844 assert!(
845 matches!(map.get("velocity"), Some(Value::Number(n)) if (*n - 0.7).abs() < f32::EPSILON)
846 );
847 }
848 other => panic!("expected pattern options map, got {:?}", other),
849 }
850 }
851}