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
15pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
17 let braces_pre = preprocessing::preprocess_multiline_braces(source);
19
20 let preprocessed = preprocessing::preprocess_multiline_arrow_calls(&braces_pre);
22
23 let lines: Vec<_> = preprocessed.lines().collect();
24 parse_lines(&lines, 0, lines.len(), 0, &path)
25}
26
27fn parse_lines(
29 lines: &Vec<&str>,
30 start: usize,
31 end: usize,
32 indent: usize,
33 path: &Path,
34) -> Result<Vec<Statement>> {
35 use crate::language::syntax::ast::nodes::StatementKind;
36
37 let mut i = start;
38 let mut statements: Vec<Statement> = Vec::new();
39
40 while i < end {
41 let raw = lines[i];
42 let trimmed = raw.trim();
43
44 if trimmed.is_empty() || trimmed.starts_with('#') {
45 i += 1;
46 continue;
47 }
48
49 let current_indent = raw.len() - raw.trim_start().len();
50 if current_indent < indent {
51 break;
52 }
53
54 let mut statement = parse_line(trimmed, i + 1, path)?;
56 statement.indent = current_indent;
57 statement.line = i + 1;
58
59 let body_start = i + 1;
61 let mut body_end = body_start;
62 while body_end < end {
63 let l = lines[body_end];
64 if l.trim().is_empty() || l.trim().starts_with('#') {
65 body_end += 1;
66 continue;
67 }
68 let indent_l = l.len() - l.trim_start().len();
69 if indent_l <= current_indent {
70 break;
71 }
72 body_end += 1;
73 }
74
75 if body_end > body_start {
77 let body = parse_lines(lines, body_start, body_end, current_indent + 1, path)?;
78
79 let orig_kind = std::mem::replace(&mut statement.kind, StatementKind::Unknown);
83 match orig_kind {
84 StatementKind::If { condition, .. } => {
85 statement.kind = StatementKind::If {
87 condition,
88 body: body.clone(),
89 else_body: None,
90 };
91 }
92 StatementKind::For {
93 variable, iterable, ..
94 } => {
95 statement.kind = StatementKind::For {
96 variable,
97 iterable,
98 body: body.clone(),
99 };
100 }
101 StatementKind::Loop { count, .. } => {
102 statement.kind = StatementKind::Loop {
103 count,
104 body: body.clone(),
105 };
106 }
107 StatementKind::On { event, args, .. } => {
108 statement.kind = StatementKind::On {
109 event,
110 args,
111 body: body.clone(),
112 };
113 }
114 StatementKind::Automate { target } => {
115 statement.kind = StatementKind::Automate { target };
116 let raw_lines: Vec<String> = lines[body_start..body_end]
118 .iter()
119 .map(|s| s.to_string())
120 .collect();
121 let raw_body = raw_lines.join("\n");
122 let mut map = std::collections::HashMap::new();
123 map.insert("body".to_string(), Value::String(raw_body));
124 statement.value = Value::Map(map);
125 }
126 StatementKind::Function {
127 name, parameters, ..
128 } => {
129 statement.kind = StatementKind::Function {
130 name: name.clone(),
131 parameters,
132 body: body.clone(),
133 };
134 statement.value = Value::Identifier(name.clone());
135 }
136 StatementKind::Group { .. } => {
137 let group_name = match &statement.value {
139 Value::Identifier(s) => s.clone(),
140 _ => "".to_string(),
141 };
142 statement.kind = StatementKind::Group {
143 name: group_name,
144 body: body.clone(),
145 };
146 }
147 other => {
148 statement.kind = other;
150 }
151 }
152
153 i = body_end;
159 statements.push(statement);
160
161 if let StatementKind::Comment = &statements.last().unwrap().kind {
162 if let Value::String(s) = &statements.last().unwrap().value {
163 if s == "else" {
164 for stmt in body.into_iter() {
166 statements.push(stmt);
167 }
168 }
169 }
170 }
171 continue;
172 }
173
174 statements.push(statement);
176 i += 1;
177 }
178
179 fn attach_else_blocks(statements: &mut Vec<Statement>) {
181 use crate::language::syntax::ast::StatementKind;
182
183 fn attach_else_to_if_statement(target: Statement, new_else: Vec<Statement>) -> Statement {
188 use crate::language::syntax::ast::StatementKind;
189
190 match target.kind {
191 StatementKind::If {
192 condition,
193 body,
194 else_body,
195 } => {
196 match else_body {
197 None => Statement::new(
198 StatementKind::If {
199 condition,
200 body,
201 else_body: Some(new_else),
202 },
203 target.value,
204 target.indent,
205 target.line,
206 target.column,
207 ),
208 Some(mut eb) => {
209 if eb.len() == 1 {
210 let inner = eb.remove(0);
211 if let StatementKind::If { .. } = inner.kind {
213 let updated_inner =
214 attach_else_to_if_statement(inner, new_else);
215 Statement::new(
216 StatementKind::If {
217 condition,
218 body,
219 else_body: Some(vec![updated_inner]),
220 },
221 target.value,
222 target.indent,
223 target.line,
224 target.column,
225 )
226 } else {
227 Statement::new(
229 StatementKind::If {
230 condition,
231 body,
232 else_body: Some(new_else),
233 },
234 target.value,
235 target.indent,
236 target.line,
237 target.column,
238 )
239 }
240 } else {
241 Statement::new(
243 StatementKind::If {
244 condition,
245 body,
246 else_body: Some(new_else),
247 },
248 target.value,
249 target.indent,
250 target.line,
251 target.column,
252 )
253 }
254 }
255 }
256 }
257 _ => target,
258 }
259 }
260
261 let mut idx = 0;
262 while idx < statements.len() {
263 if let StatementKind::Comment = &statements[idx].kind {
265 if let Value::String(s) = &statements[idx].value {
266 if s == "else" {
267 let else_indent = statements[idx].indent;
268
269 let mut body: Vec<Statement> = Vec::new();
271 let j = idx + 1;
272 while j < statements.len() && statements[j].indent > else_indent {
273 body.push(statements.remove(j));
274 }
275
276 statements.remove(idx);
278
279 let mut k = idx as isize - 1;
282 while k >= 0 {
283 if let StatementKind::If { .. } = &statements[k as usize].kind {
284 let prev = std::mem::replace(
287 &mut statements[k as usize],
288 Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
289 );
290 let updated = attach_else_to_if_statement(prev, body);
291 statements[k as usize] = updated;
292 break;
293 }
294 k -= 1;
295 }
296
297 continue;
298 }
299 }
300 }
301
302 if let StatementKind::If { .. } = &statements[idx].kind {
304 if let Value::String(s) = &statements[idx].value {
305 if s == "else-if" {
306 let else_if_stmt = statements.remove(idx);
309
310 let mut k = idx as isize - 1;
312 while k >= 0 {
313 if let StatementKind::If { .. } = &statements[k as usize].kind {
314 let prev = std::mem::replace(
315 &mut statements[k as usize],
316 Statement::new(StatementKind::Unknown, Value::Null, 0, 0, 1),
317 );
318 let updated = attach_else_to_if_statement(prev, vec![else_if_stmt]);
319 statements[k as usize] = updated;
320 break;
321 }
322 k -= 1;
323 }
324
325 continue;
326 }
327 }
328 }
329
330 idx += 1;
331 }
332 }
333
334 attach_else_blocks(&mut statements);
335
336 Ok(statements)
337}
338
339fn parse_line(line: &str, line_number: usize, path: &Path) -> Result<Statement> {
340 use crate::language::syntax::parser::driver::statements::*;
341
342 if line.starts_with('@') {
343 return directive::parse_directive(line, line_number, path);
344 }
345
346 if line.starts_with('.') {
347 return trigger::parse_trigger_line(line, line_number);
348 }
349
350 let mut parts = line.split_whitespace();
351 let first_token = parts
353 .next()
354 .ok_or_else(|| anyhow!("empty line"))?
355 .to_string();
356 let keyword = first_token.trim_end_matches(':').to_lowercase();
357
358 if line.contains('=') && keyword.contains('.') {
360 return parse_assign(line, line_number);
361 }
362
363 if keyword.contains('.') && !keyword.contains('(') {
365 return trigger::parse_trigger_line(line, line_number);
367 }
368
369 if keyword == "bind" && line.contains("->") {
371 return parse_bind(line, line_number);
372 }
373
374 let reserved_keywords = [
379 "bpm", "tempo", "print", "sleep", "pattern", "bank", "let", "var", "const", "for", "loop",
380 "if", "else", "group", "automate", "call", "spawn", "on", "emit", "routing", "return",
381 "break",
382 ];
383 if line.contains("->") && !reserved_keywords.contains(&keyword.as_str()) {
384 return statements::parse_arrow_call(line, line_number);
385 }
386
387 return match keyword.as_str() {
388 "bpm" | "tempo" => statements::core::parse_tempo(parts, line_number),
389 "print" => statements::core::parse_print(line, line_number),
390 "sleep" => statements::core::parse_sleep(parts, line_number),
391 "trigger" => Err(anyhow!(
392 "keyword 'trigger' is deprecated; use dot notation like '.alias' instead"
393 )),
394 "pattern" => parse_pattern(parts, line_number),
395 "bank" => parse_bank(parts, line_number),
396 "let" => parse_let(line, parts, line_number),
397 "var" => parse_var(line, parts, line_number),
398 "const" => parse_const(line, parts, line_number),
399 "for" => parse_for(parts, line_number),
400 "loop" => parse_loop(parts, line_number),
401 "if" => statements::structure::parse_if(parts, line_number),
402 "else" => statements::structure::parse_else(line, line_number),
403 "group" => statements::structure::parse_group(parts, line_number),
404 "automate" => {
405 crate::language::syntax::parser::driver::statements::structure::parse_automate(
406 parts,
407 line_number,
408 )
409 }
410 "call" => parse_call(line, parts, line_number),
411 "break" => statements::structure::parse_break(parts, line_number),
412 "function" => statements::structure::parse_function(line, line_number),
413 "spawn" => parse_spawn(parts, line_number),
414 "on" => parse_on(parts, line_number),
415 "emit" => parse_emit(line, parts, line_number),
416 "return" => statements::core::parse_return(line, line_number),
417 "routing" => crate::language::syntax::parser::driver::routing::parse_routing_command(
418 parts,
419 line_number,
420 ),
421 _ => {
422 if keyword
425 .chars()
426 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
427 {
428 return trigger::parse_trigger_line(line, line_number);
430 }
431
432 let error_msg = format!(
433 "Unknown statement '{}' at {}:{}",
434 keyword,
435 path.display(),
436 line_number
437 );
438
439 #[cfg(feature = "wasm")]
441 {
442 use crate::web::registry::debug;
443 if debug::is_debug_errors_enabled() {
444 debug::push_parse_error_from_parts(
446 format!("Unknown statement '{}'", keyword),
447 line_number,
448 1,
449 "UnknownStatement".to_string(),
450 );
451 }
452 }
453
454 return Ok(Statement::new(
455 StatementKind::Unknown,
456 Value::String(error_msg),
457 0,
458 line_number,
459 1,
460 ));
461 }
462 };
463}
464
465pub use helpers::{
467 parse_array_value, parse_condition, parse_function_args, parse_map_value, parse_single_arg,
468 parse_synth_definition,
469};
470
471pub struct SimpleParser;
474
475impl SimpleParser {
476 pub fn parse(source: &str, path: PathBuf) -> Result<Vec<Statement>> {
477 crate::language::syntax::parser::driver::parse(source, path)
478 }
479
480 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Vec<Statement>> {
481 let buf = std::path::PathBuf::from(path.as_ref());
482 let s = std::fs::read_to_string(&buf)?;
483 Self::parse(&s, buf)
484 }
485}