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