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 preprocessed = preprocessing::preprocess_multiline_arrow_calls(&braces_pre);
68
69 let lines: Vec<_> = preprocessed.lines().collect();
70 parse_lines(&lines, 0, lines.len(), 0, &path)
71}
72
73fn parse_lines(
75 lines: &Vec<&str>,
76 start: usize,
77 end: usize,
78 indent: usize,
79 path: &Path,
80) -> Result<Vec<Statement>> {
81 use crate::language::syntax::ast::nodes::StatementKind;
82
83 let mut i = start;
84 let mut statements: Vec<Statement> = Vec::new();
85
86 while i < end {
87 let raw = lines[i];
88 let trimmed = raw.trim();
89
90 if trimmed.is_empty() || trimmed.starts_with('#') {
91 i += 1;
92 continue;
93 }
94
95 let current_indent = raw.len() - raw.trim_start().len();
96 if current_indent < indent {
97 break;
98 }
99
100 let mut statement = parse_line(trimmed, i + 1, path)?;
102 statement.indent = current_indent;
103 statement.line = i + 1;
104
105 let body_start = i + 1;
107 let mut body_end = body_start;
108 while body_end < end {
109 let l = lines[body_end];
110 if l.trim().is_empty() || l.trim().starts_with('#') {
111 body_end += 1;
112 continue;
113 }
114 let indent_l = l.len() - l.trim_start().len();
115 if indent_l <= current_indent {
116 break;
117 }
118 body_end += 1;
119 }
120
121 if body_end > body_start {
123 let body = parse_lines(lines, body_start, body_end, current_indent + 1, path)?;
124
125 let orig_kind = std::mem::replace(&mut statement.kind, StatementKind::Unknown);
129 match orig_kind {
130 StatementKind::If { condition, .. } => {
131 statement.kind = StatementKind::If {
133 condition,
134 body: body.clone(),
135 else_body: None,
136 };
137 }
138 StatementKind::For {
139 variable, iterable, ..
140 } => {
141 statement.kind = StatementKind::For {
142 variable,
143 iterable,
144 body: body.clone(),
145 };
146 }
147 StatementKind::Loop { count, .. } => {
148 statement.kind = StatementKind::Loop {
149 count,
150 body: body.clone(),
151 };
152 }
153 StatementKind::On { event, args, .. } => {
154 statement.kind = StatementKind::On {
155 event,
156 args,
157 body: body.clone(),
158 };
159 }
160 StatementKind::Automate { target } => {
161 statement.kind = StatementKind::Automate { target };
162 let raw_lines: Vec<String> = lines[body_start..body_end]
164 .iter()
165 .map(|s| s.to_string())
166 .collect();
167 let raw_body = raw_lines.join("\n");
168 let mut map = std::collections::HashMap::new();
169 map.insert("body".to_string(), Value::String(raw_body));
170 statement.value = Value::Map(map);
171 }
172 StatementKind::Function {
173 name, parameters, ..
174 } => {
175 statement.kind = StatementKind::Function {
176 name: name.clone(),
177 parameters,
178 body: body.clone(),
179 };
180 statement.value = Value::Identifier(name.clone());
181 }
182 StatementKind::Group { .. } => {
183 let group_name = match &statement.value {
185 Value::Identifier(s) => s.clone(),
186 _ => "".to_string(),
187 };
188 statement.kind = StatementKind::Group {
189 name: group_name,
190 body: body.clone(),
191 };
192 }
193 StatementKind::Routing { .. } => {
194 statement.kind = StatementKind::Routing { body: body.clone() };
196 }
197 StatementKind::Tempo {
198 value,
199 body: Some(_),
200 } => {
201 statement.kind = StatementKind::Tempo {
203 value,
204 body: Some(body.clone()),
205 };
206 }
207 other => {
208 statement.kind = other;
210 }
211 }
212
213 i = body_end;
219 statements.push(statement);
220
221 if let StatementKind::Comment = &statements.last().unwrap().kind {
222 if let Value::String(s) = &statements.last().unwrap().value {
223 if s == "else" {
224 for stmt in body.into_iter() {
226 statements.push(stmt);
227 }
228 }
229 }
230 }
231 continue;
232 }
233
234 statements.push(statement);
236 i += 1;
237 }
238
239 fn attach_else_blocks(statements: &mut Vec<Statement>) {
241 use crate::language::syntax::ast::StatementKind;
242
243 fn attach_else_to_if_statement(target: Statement, new_else: Vec<Statement>) -> Statement {
248 use crate::language::syntax::ast::StatementKind;
249
250 match target.kind {
251 StatementKind::If {
252 condition,
253 body,
254 else_body,
255 } => {
256 match else_body {
257 None => Statement::new(
258 StatementKind::If {
259 condition,
260 body,
261 else_body: Some(new_else),
262 },
263 target.value,
264 target.indent,
265 target.line,
266 target.column,
267 ),
268 Some(mut eb) => {
269 if eb.len() == 1 {
270 let inner = eb.remove(0);
271 if let StatementKind::If { .. } = inner.kind {
273 let updated_inner =
274 attach_else_to_if_statement(inner, new_else);
275 Statement::new(
276 StatementKind::If {
277 condition,
278 body,
279 else_body: Some(vec![updated_inner]),
280 },
281 target.value,
282 target.indent,
283 target.line,
284 target.column,
285 )
286 } else {
287 Statement::new(
289 StatementKind::If {
290 condition,
291 body,
292 else_body: Some(new_else),
293 },
294 target.value,
295 target.indent,
296 target.line,
297 target.column,
298 )
299 }
300 } else {
301 Statement::new(
303 StatementKind::If {
304 condition,
305 body,
306 else_body: Some(new_else),
307 },
308 target.value,
309 target.indent,
310 target.line,
311 target.column,
312 )
313 }
314 }
315 }
316 }
317 _ => target,
318 }
319 }
320
321 let mut idx = 0;
322 while idx < statements.len() {
323 if let StatementKind::Comment = &statements[idx].kind {
325 if let Value::String(s) = &statements[idx].value {
326 if s == "else" {
327 let else_indent = statements[idx].indent;
328
329 let mut body: Vec<Statement> = Vec::new();
331 let j = idx + 1;
332 while j < statements.len() && statements[j].indent > else_indent {
333 body.push(statements.remove(j));
334 }
335
336 statements.remove(idx);
338
339 let mut k = idx as isize - 1;
342 while k >= 0 {
343 if let StatementKind::If { .. } = &statements[k as usize].kind {
344 let prev = std::mem::replace(
347 &mut statements[k as usize],
348 Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
349 );
350 let updated = attach_else_to_if_statement(prev, body);
351 statements[k as usize] = updated;
352 break;
353 }
354 k -= 1;
355 }
356
357 continue;
358 }
359 }
360 }
361
362 if let StatementKind::If { .. } = &statements[idx].kind {
364 if let Value::String(s) = &statements[idx].value {
365 if s == "else-if" {
366 let else_if_stmt = statements.remove(idx);
369
370 let mut k = idx as isize - 1;
372 while k >= 0 {
373 if let StatementKind::If { .. } = &statements[k as usize].kind {
374 let prev = std::mem::replace(
375 &mut statements[k as usize],
376 Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
377 );
378 let updated = attach_else_to_if_statement(prev, vec![else_if_stmt]);
379 statements[k as usize] = updated;
380 break;
381 }
382 k -= 1;
383 }
384
385 continue;
386 }
387 }
388 }
389
390 idx += 1;
391 }
392 }
393
394 attach_else_blocks(&mut statements);
395
396 Ok(statements)
397}
398
399fn parse_line(line: &str, line_number: usize, path: &Path) -> Result<Statement> {
400 use crate::language::syntax::parser::driver::statements::*;
401
402 if line.starts_with('.') {
403 return trigger::parse_trigger_line(line, line_number);
404 }
405
406 let mut parts = line.split_whitespace();
407 let first_token = parts
409 .next()
410 .ok_or_else(|| anyhow!("empty line"))?
411 .to_string();
412 let keyword = first_token.trim_end_matches(':').to_lowercase();
413
414 let routing_keywords = ["node", "fx", "route", "duck", "sidechain"];
416 if routing_keywords.contains(&keyword.as_str()) {
417 return crate::language::syntax::parser::driver::routing::parse_routing_statement(
418 line,
419 line_number,
420 );
421 }
422
423 if line.contains('=') && keyword.contains('.') {
425 return parse_assign(line, line_number);
426 }
427
428 if keyword.contains('.') && !keyword.contains('(') {
430 return trigger::parse_trigger_line(line, line_number);
432 }
433
434 if keyword == "bind" && line.contains("->") {
436 return parse_bind(line, line_number);
437 }
438
439 let reserved_keywords = [
444 "bpm", "tempo", "print", "sleep", "rest", "wait", "pattern", "bank", "let", "const", "for",
445 "foreach", "loop", "if", "else", "group", "automate", "call", "spawn", "sequence", "layer",
446 "on", "emit", "routing", "return", "break", "import", "export", "use", "load",
447 ];
448 if line.contains("->") && !reserved_keywords.contains(&keyword.as_str()) {
449 return statements::parse_arrow_call(line, line_number);
450 }
451
452 return match keyword.as_str() {
453 "bpm" | "tempo" => statements::core::parse_tempo(line, line_number),
454 "print" => statements::core::parse_print(line, line_number),
455 "sleep" | "rest" | "wait" => statements::core::parse_sleep(parts, line_number),
456 "trigger" => Err(anyhow!(
457 "keyword 'trigger' is deprecated; use dot notation like '.alias' instead"
458 )),
459 "pattern" => parse_pattern(parts, line_number),
460 "bank" => parse_bank(parts, line_number),
461 "let" => parse_let(line, parts, line_number),
462 "const" => parse_const(line, parts, line_number),
463 "for" | "foreach" => parse_for(parts, line_number),
464 "loop" => parse_loop(parts, line_number),
465 "if" => statements::structure::parse_if(parts, line_number),
466 "else" => statements::structure::parse_else(line, line_number),
467 "group" => statements::structure::parse_group(parts, line_number),
468 "automate" => {
469 crate::language::syntax::parser::driver::statements::structure::parse_automate(
470 parts,
471 line_number,
472 )
473 }
474 "call" | "sequence" => parse_call(line, parts, line_number),
475 "break" => statements::structure::parse_break(parts, line_number),
476 "function" => statements::structure::parse_function(line, line_number),
477 "spawn" | "layer" => parse_spawn(parts, line_number),
478 "on" => parse_on(parts, line_number),
479 "emit" => parse_emit(line, parts, line_number),
480 "return" => statements::core::parse_return(line, line_number),
481 "routing" => {
482 crate::language::syntax::parser::driver::routing::parse_routing_command(line_number)
483 }
484 "import" => directive::parse_directive_keyword(line, "import", line_number, path),
485 "export" => directive::parse_directive_keyword(line, "export", line_number, path),
486 "use" => directive::parse_directive_keyword(line, "use", line_number, path),
487 "load" => directive::parse_directive_keyword(line, "load", line_number, path),
488 _ => {
489 let suggestion = find_keyword_suggestion(&keyword, &reserved_keywords);
491
492 if suggestion.is_some() {
494 let suggestion_str = suggestion.unwrap();
495 let error_msg = format!(
498 "Unknown statement '{}' at {}:{}|||{}|||{}",
499 keyword,
500 path.display(),
501 line_number,
502 path.display(),
503 suggestion_str
504 );
505
506 #[cfg(feature = "wasm")]
508 {
509 use crate::web::registry::debug;
510 if debug::is_debug_errors_enabled() {
511 debug::push_parse_error_from_parts(
512 format!(
513 "Unknown statement '{}'. Did you mean '{}' ?",
514 keyword, suggestion_str
515 ),
516 line_number,
517 1,
518 "UnknownStatement".to_string(),
519 );
520 }
521 }
522
523 return Ok(Statement::new(
524 StatementKind::Unknown,
525 Value::String(error_msg),
526 0,
527 line_number,
528 1,
529 ));
530 }
531
532 if keyword
535 .chars()
536 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
537 {
538 return trigger::parse_trigger_line(line, line_number);
540 }
541
542 let error_msg = format!(
545 "Unknown statement '{}' at {}:{}|||{}|||",
546 keyword,
547 path.display(),
548 line_number,
549 path.display()
550 );
551
552 #[cfg(feature = "wasm")]
554 {
555 use crate::web::registry::debug;
556 if debug::is_debug_errors_enabled() {
557 debug::push_parse_error_from_parts(
558 format!("Unknown statement '{}'", keyword),
559 line_number,
560 1,
561 "UnknownStatement".to_string(),
562 );
563 }
564 }
565
566 return Ok(Statement::new(
567 StatementKind::Unknown,
568 Value::String(error_msg),
569 0,
570 line_number,
571 1,
572 ));
573 }
574 };
575}
576
577pub use helpers::{
579 parse_array_value, parse_condition, parse_function_args, parse_map_value, parse_single_arg,
580 parse_synth_definition,
581};
582
583pub struct SimpleParser;
586
587impl SimpleParser {
588 pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
589 crate::language::syntax::parser::driver::parse(source, path)
590 }
591
592 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Vec<Statement>> {
593 let buf = std::path::PathBuf::from(path.as_ref());
594 let s = std::fs::read_to_string(&buf)?;
595 Self::parse(&s, buf)
596 }
597}