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 let preprocessed = preprocess_multiline_braces(source);
38
39 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 if trimmed.is_empty() || trimmed.starts_with('#') {
63 i += 1;
64 continue;
65 }
66
67 let indent = raw_line.len() - raw_line.trim_start().len();
69
70 if indent < base_indent {
72 break;
73 }
74
75 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 #[cfg(feature = "wasm")]
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 0,
95 "ParseError".to_string(),
96 "error".to_string(),
97 );
98 }
99 }
100
101 Statement::new(
102 StatementKind::Error { message: error_msg },
103 Value::String(trimmed.to_string()),
104 indent,
105 line_number,
106 1,
107 )
108 }
109 };
110
111 let needs_body_parsing = matches!(
113 &statement.kind,
114 StatementKind::Group { .. }
115 | StatementKind::For { .. }
116 | StatementKind::Loop { .. }
117 | StatementKind::If { .. }
118 | StatementKind::On { .. }
119 );
120
121 if needs_body_parsing {
122 i += 1; let block_indent = indent;
126 let body_start = i;
127 let mut body_end = i;
128
129 while body_end < end {
130 let body_line = lines[body_end];
131 let body_trimmed = body_line.trim();
132
133 if body_trimmed.is_empty() || body_trimmed.starts_with('#') {
134 body_end += 1;
135 continue;
136 }
137
138 let body_indent = body_line.len() - body_line.trim_start().len();
139 if body_indent <= block_indent {
140 break;
141 }
142
143 body_end += 1;
144 }
145
146 let body = Self::parse_lines(lines, body_start, body_end, block_indent + 1, path)?;
148
149 match &statement.kind {
151 StatementKind::Group { name, .. } => {
152 let group_name = name.clone();
153 statement.kind = StatementKind::Group {
154 name: group_name.clone(),
155 body,
156 };
157 statement.value = Value::Identifier(group_name);
158 }
159 StatementKind::For {
160 variable, iterable, ..
161 } => {
162 statement.kind = StatementKind::For {
163 variable: variable.clone(),
164 iterable: iterable.clone(),
165 body,
166 };
167 }
168 StatementKind::Loop { count, .. } => {
169 statement.kind = StatementKind::Loop {
170 count: count.clone(),
171 body,
172 };
173 }
174 StatementKind::On { event, args, .. } => {
175 statement.kind = StatementKind::On {
176 event: event.clone(),
177 args: args.clone(),
178 body,
179 };
180 }
181 StatementKind::If { condition, .. } => {
182 let mut else_body = None;
184 if body_end < end {
185 let next_line = lines[body_end].trim();
186 if next_line.starts_with("else") {
187 let else_start = body_end + 1;
189 let mut else_end = else_start;
190
191 while else_end < end {
192 let else_line = lines[else_end];
193 let else_trimmed = else_line.trim();
194
195 if else_trimmed.is_empty() || else_trimmed.starts_with('#') {
196 else_end += 1;
197 continue;
198 }
199
200 let else_indent =
201 else_line.len() - else_line.trim_start().len();
202 if else_indent <= block_indent {
203 break;
204 }
205
206 else_end += 1;
207 }
208
209 else_body = Some(Self::parse_lines(
210 lines,
211 else_start,
212 else_end,
213 block_indent + 1,
214 path,
215 )?);
216 body_end = else_end; }
218 }
219
220 statement.kind = StatementKind::If {
221 condition: condition.clone(),
222 body,
223 else_body,
224 };
225 }
226 _ => {}
227 }
228
229 i = body_end;
230 statements.push(statement);
231 continue;
232 }
233
234 statements.push(statement);
235 i += 1;
236 }
237
238 Ok(statements)
239 }
240
241 fn parse_line(line: &str, line_number: usize, path: &Path) -> Result<Statement> {
242 if line.starts_with('@') {
243 return parse_directive(line, line_number, path);
244 }
245
246 if line.starts_with('.') {
247 return parse_trigger_line(line, line_number);
248 }
249
250 let mut parts = line.split_whitespace();
251 let keyword = parts
252 .next()
253 .ok_or_else(|| anyhow!("empty line"))?
254 .to_lowercase();
255
256 if line.contains('=') && keyword.contains('.') {
258 return parse_assign(line, line_number);
259 }
260
261 if keyword.contains('.') && !keyword.contains('(') {
263 return parse_trigger_line(line, line_number);
265 }
266
267 if keyword == "bind" && line.contains("->") {
269 return parse_bind(line, line_number);
270 }
271
272 if line.contains("->") {
274 return parse_arrow_call(line, line_number);
275 }
276
277 match keyword.as_str() {
278 "bpm" | "tempo" => parse_tempo(parts, line_number),
279 "print" => parse_print(line, line_number),
280 "sleep" => parse_sleep(parts, line_number),
281 "trigger" => Err(anyhow!(
282 "keyword 'trigger' is deprecated; use dot notation like '.alias' instead"
283 )),
284 "pattern" => parse_pattern(parts, line_number),
285 "bank" => parse_bank(parts, line_number),
286 "let" => parse_let(line, parts, line_number),
287 "var" => parse_var(line, parts, line_number),
288 "const" => parse_const(line, parts, line_number),
289 "for" => parse_for(parts, line_number),
290 "loop" => parse_loop(parts, line_number),
291 "if" => parse_if(parts, line_number),
292 "else" => parse_else(line, line_number),
293 "group" => parse_group(parts, line_number),
294 "call" => parse_call(line, parts, line_number),
295 "spawn" => parse_spawn(parts, line_number),
296 "on" => parse_on(parts, line_number),
297 "emit" => parse_emit(line, parts, line_number),
298 "routing" => parse_routing_command(parts, line_number),
299 _ => {
300 if keyword
303 .chars()
304 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
305 {
306 return parse_trigger_line(line, line_number);
308 }
309
310 let error_msg = format!(
311 "Unknown statement '{}' at {}:{}",
312 keyword,
313 path.display(),
314 line_number
315 );
316
317 #[cfg(feature = "wasm")]
319 {
320 use crate::web::registry::debug;
321 if debug::is_debug_errors_enabled() {
322 debug::push_parse_error_from_parts(
323 format!("Unknown statement '{}'", keyword),
324 line_number,
325 1,
326 0,
327 "UnknownStatement".to_string(),
328 "error".to_string(),
329 );
330 }
331 } Ok(Statement::new(
332 StatementKind::Unknown,
333 Value::String(error_msg),
334 0,
335 line_number,
336 1,
337 ))
338 }
339 }
340 }
341
342 fn parse_condition(condition_str: &str) -> Result<Value> {
345 use std::collections::HashMap;
346
347 let operators = vec![">=", "<=", "==", "!=", ">", "<"];
349 for op in operators {
350 if let Some(idx) = condition_str.find(op) {
351 let left = condition_str[..idx].trim();
352 let right = condition_str[idx + op.len()..].trim();
353
354 let mut map = HashMap::new();
356 map.insert("operator".to_string(), Value::String(op.to_string()));
357 map.insert(
358 "left".to_string(),
359 if let Ok(num) = left.parse::<f32>() {
360 Value::Number(num)
361 } else {
362 Value::Identifier(left.to_string())
363 },
364 );
365 map.insert(
366 "right".to_string(),
367 if let Ok(num) = right.parse::<f32>() {
368 Value::Number(num)
369 } else {
370 Value::Identifier(right.to_string())
371 },
372 );
373
374 return Ok(Value::Map(map));
375 }
376 }
377
378 Ok(Value::Identifier(condition_str.to_string()))
380 }
381}
382
383fn parse_arrow_call(line: &str, line_number: usize) -> Result<Statement> {
386 use std::collections::HashMap;
387
388 let parts: Vec<&str> = line.split("->").map(|s| s.trim()).collect();
390
391 if parts.len() < 2 {
392 return Err(anyhow!("Arrow call requires at least one '->' operator"));
393 }
394
395 let target = parts[0].to_string();
397
398 let mut calls = Vec::new();
400
401 for method_call in &parts[1..] {
402 if let Some(paren_idx) = method_call.find('(') {
404 let method_name = method_call[..paren_idx].trim();
405 let args_str = &method_call[paren_idx + 1..];
406
407 let close_paren = args_str
409 .rfind(')')
410 .ok_or_else(|| anyhow!("Missing closing parenthesis in arrow call"))?;
411
412 let args_str = &args_str[..close_paren];
413
414 let args = if args_str.trim().is_empty() {
416 Vec::new()
417 } else {
418 parse_function_args(args_str)?
419 };
420
421 calls.push((method_name.to_string(), args));
422 } else {
423 calls.push((method_call.trim().to_string(), Vec::new()));
425 }
426 }
427
428 if calls.is_empty() {
433 return Err(anyhow!("No method calls found in arrow call"));
434 }
435
436 let (method, args) = calls[0].clone();
437
438 let mut chain_value = HashMap::new();
440 chain_value.insert("target".to_string(), Value::String(target.clone()));
441 chain_value.insert("method".to_string(), Value::String(method.clone()));
442 chain_value.insert("args".to_string(), Value::Array(args.clone()));
443
444 if calls.len() > 1 {
446 let chain_calls: Vec<Value> = calls[1..]
447 .iter()
448 .map(|(m, a)| {
449 let mut call_map = HashMap::new();
450 call_map.insert("method".to_string(), Value::String(m.clone()));
451 call_map.insert("args".to_string(), Value::Array(a.clone()));
452 Value::Map(call_map)
453 })
454 .collect();
455
456 chain_value.insert("chain".to_string(), Value::Array(chain_calls));
457 }
458
459 Ok(Statement::new(
460 StatementKind::ArrowCall {
461 target,
462 method,
463 args,
464 },
465 Value::Map(chain_value),
466 0,
467 line_number,
468 1,
469 ))
470}
471
472fn parse_function_args(args_str: &str) -> Result<Vec<Value>> {
475 let mut args = Vec::new();
476 let mut current_arg = String::new();
477 let mut depth = 0; let mut in_string = false;
479
480 for ch in args_str.chars() {
481 match ch {
482 '"' => {
483 in_string = !in_string;
484 current_arg.push(ch);
485 }
486 '[' | '{' if !in_string => {
487 depth += 1;
488 current_arg.push(ch);
489 }
490 ']' | '}' if !in_string => {
491 depth -= 1;
492 current_arg.push(ch);
493 }
494 ',' if depth == 0 && !in_string => {
495 if !current_arg.trim().is_empty() {
497 args.push(parse_single_arg(current_arg.trim())?);
498 current_arg.clear();
499 }
500 }
501 _ => {
502 current_arg.push(ch);
503 }
504 }
505 }
506
507 if !current_arg.trim().is_empty() {
509 args.push(parse_single_arg(current_arg.trim())?);
510 }
511
512 Ok(args)
513}
514
515fn parse_single_arg(arg: &str) -> Result<Value> {
517 use std::collections::HashMap;
518
519 let arg = arg.trim();
520
521 if arg.starts_with('"') && arg.ends_with('"') {
523 return Ok(Value::String(arg[1..arg.len() - 1].to_string()));
524 }
525
526 if arg.starts_with('[') && arg.ends_with(']') {
528 let inner = &arg[1..arg.len() - 1];
529 let items = parse_function_args(inner)?;
530 return Ok(Value::Array(items));
531 }
532
533 if arg.starts_with('{') && arg.ends_with('}') {
535 let inner = &arg[1..arg.len() - 1];
536 let mut map = HashMap::new();
537
538 for pair in inner.split(',') {
540 if let Some(colon_idx) = pair.find(':') {
541 let key = pair[..colon_idx].trim().trim_matches('"');
542 let value = parse_single_arg(pair[colon_idx + 1..].trim())?;
543 map.insert(key.to_string(), value);
544 }
545 }
546
547 return Ok(Value::Map(map));
548 }
549
550 if let Ok(num) = arg.parse::<f32>() {
552 return Ok(Value::Number(num));
553 }
554
555 match arg.to_lowercase().as_str() {
557 "true" => return Ok(Value::Boolean(true)),
558 "false" => return Ok(Value::Boolean(false)),
559 _ => {}
560 }
561
562 Ok(Value::Identifier(arg.to_string()))
564}
565
566fn parse_synth_definition(input: &str) -> Result<Value> {
569 use std::collections::HashMap;
570
571 let input = input.trim_start_matches("synth ").trim();
573
574 let (waveform, params_str) = if let Some(brace_idx) = input.find('{') {
576 let waveform = input[..brace_idx].trim();
577 let params = &input[brace_idx..];
578 (waveform, params)
579 } else {
580 return Ok(Value::Map({
582 let mut map = HashMap::new();
583 map.insert("type".to_string(), Value::String("synth".to_string()));
584 map.insert("waveform".to_string(), Value::String(input.to_string()));
585 map
586 }));
587 };
588
589 let params_str = params_str.trim_matches(|c| c == '{' || c == '}').trim();
591 let mut params_map = HashMap::new();
592
593 params_map.insert("type".to_string(), Value::String("synth".to_string()));
595 params_map.insert("waveform".to_string(), Value::String(waveform.to_string()));
596
597 if !params_str.is_empty() {
599 let mut cleaned_lines = Vec::new();
601 for line in params_str.lines() {
602 if let Some(comment_pos) = line.find("//") {
603 let clean_line = &line[..comment_pos];
604 if !clean_line.trim().is_empty() {
605 cleaned_lines.push(clean_line);
606 }
607 } else if !line.trim().is_empty() {
608 cleaned_lines.push(line);
609 }
610 }
611
612 let cleaned = cleaned_lines.join("\n");
614 let normalized = cleaned.replace('\n', ",").replace('\r', "");
615
616 for pair in normalized.split(',') {
617 let pair = pair.trim();
618 if pair.is_empty() {
619 continue;
620 }
621
622 let parts: Vec<&str> = pair.split(':').collect();
623 if parts.len() >= 2 {
624 let key = parts[0].trim().to_string();
625 let value_part = parts[1..].join(":");
627 let value_str = value_part.trim().trim_matches(',');
628
629 if value_str.starts_with('[') {
631 if let Ok(array_val) = parse_array_value(value_str) {
632 params_map.insert(key, array_val);
633 continue;
634 }
635 }
636
637 if let Ok(num) = value_str.parse::<f32>() {
639 params_map.insert(key, Value::Number(num));
640 } else {
641 params_map.insert(key, Value::String(value_str.to_string()));
643 }
644 }
645 }
646 }
647
648 Ok(Value::Map(params_map))
649}
650
651fn parse_array_value(input: &str) -> Result<Value> {
653 let input = input.trim().trim_matches(|c| c == '[' || c == ']').trim();
654 if input.is_empty() {
655 return Ok(Value::Array(Vec::new()));
656 }
657
658 if input.contains("..") {
660 let parts: Vec<&str> = input.split("..").collect();
661 if parts.len() == 2 {
662 let start_str = parts[0].trim();
663 let end_str = parts[1].trim();
664
665 if let (Ok(start), Ok(end)) = (start_str.parse::<f32>(), end_str.parse::<f32>()) {
667 return Ok(Value::Range {
668 start: Box::new(Value::Number(start)),
669 end: Box::new(Value::Number(end)),
670 });
671 }
672 }
673 }
674
675 let mut items = Vec::new();
676 let mut depth = 0;
677 let mut current = String::new();
678
679 for ch in input.chars() {
680 match ch {
681 '{' => {
682 depth += 1;
683 current.push(ch);
684 }
685 '}' => {
686 depth -= 1;
687 current.push(ch);
688
689 if depth == 0 && !current.trim().is_empty() {
690 if let Ok(obj) = parse_map_value(¤t) {
692 items.push(obj);
693 }
694 current.clear();
695 }
696 }
697 ',' if depth == 0 => {
698 continue;
700 }
701 _ => {
702 current.push(ch);
703 }
704 }
705 }
706
707 Ok(Value::Array(items))
708}
709
710fn parse_map_value(input: &str) -> Result<Value> {
712 use std::collections::HashMap;
713
714 let input = input.trim().trim_matches(|c| c == '{' || c == '}').trim();
715 let mut map = HashMap::new();
716
717 for pair in input.split(',') {
718 let pair = pair.trim();
719 if pair.is_empty() {
720 continue;
721 }
722
723 let parts: Vec<&str> = pair.split(':').collect();
724 if parts.len() >= 2 {
725 let key = parts[0].trim().to_string();
726 let value_part = parts[1..].join(":");
728
729 let value_clean = if let Some(comment_pos) = value_part.find("//") {
731 &value_part[..comment_pos]
732 } else {
733 &value_part
734 };
735
736 let value_str = value_clean.trim().trim_matches('"').trim_matches('\'');
737
738 if let Ok(num) = value_str.parse::<f32>() {
740 map.insert(key, Value::Number(num));
741 } else {
742 map.insert(key, Value::String(value_str.to_string()));
743 }
744 }
745 }
746
747 Ok(Value::Map(map))
748}