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