1use crate::ast::{Arg, Guard, GuardExpr, IoBinding, IoStream, PlatformGuard, Step, StepKind};
2use crate::commands::parse_duration;
3use crate::lexer::{self, RawToken, Rule};
4use anyhow::{Result, anyhow, bail};
5use pest::iterators::Pair;
6use std::collections::VecDeque;
7
8#[derive(Clone)]
9struct ScopeFrame {
10 line_no: usize,
11 had_command: bool,
12}
13
14#[derive(Clone)]
15struct PendingIoBlock {
16 line_no: usize,
17 bindings: Vec<IoBinding>,
18 guards: Option<GuardExpr>,
19}
20
21#[derive(Clone)]
22struct IoScopeFrame {
23 line_no: usize,
24 had_command: bool,
25 bindings: Vec<IoBinding>,
26 guards: Option<GuardExpr>,
27 first_step: usize,
31}
32
33#[derive(Clone, Copy, Debug)]
34enum BlockKind {
35 Guard,
36 Io,
37}
38
39#[derive(Default)]
40struct IoBindingSet {
41 stdin: Option<IoBinding>,
42 stdout: Option<IoBinding>,
43 stderr: Option<IoBinding>,
44}
45
46impl IoBindingSet {
47 fn insert(&mut self, binding: IoBinding) {
48 match binding.stream {
49 IoStream::Stdin => self.stdin = Some(binding),
50 IoStream::Stdout => self.stdout = Some(binding),
51 IoStream::Stderr => self.stderr = Some(binding),
52 }
53 }
54
55 fn into_vec(self) -> Vec<IoBinding> {
56 let mut out = Vec::new();
57 if let Some(binding) = self.stdin {
58 out.push(binding);
59 }
60 if let Some(binding) = self.stdout {
61 out.push(binding);
62 }
63 if let Some(binding) = self.stderr {
64 out.push(binding);
65 }
66 out
67 }
68}
69
70pub struct ScriptParser<'a, F: Fn(&str, Vec<Arg>) -> Result<StepKind>> {
71 tokens: VecDeque<RawToken<'a>>,
72 steps: Vec<Step>,
73 guard_stack: Vec<Option<GuardExpr>>,
74 pending_guards: Option<GuardExpr>,
75 pending_inline_guards: Option<GuardExpr>,
76 pending_can_open_block: bool,
77 pending_scope_enters: usize,
78 scope_stack: Vec<ScopeFrame>,
79 pending_io_block: Option<PendingIoBlock>,
80 io_scope_stack: Vec<IoScopeFrame>,
81 block_stack: Vec<BlockKind>,
82 lower: F,
83}
84
85impl<'a, F: Fn(&str, Vec<Arg>) -> Result<StepKind>> ScriptParser<'a, F> {
86 pub fn new(input: &'a str, lower: F) -> Result<Self> {
87 let tokens = VecDeque::from(lexer::tokenize(input)?);
88 Ok(Self {
89 tokens,
90 steps: Vec::new(),
91 guard_stack: vec![None],
92 pending_guards: None,
93 pending_inline_guards: None,
94 pending_can_open_block: false,
95 pending_scope_enters: 0,
96 scope_stack: Vec::new(),
97 pending_io_block: None,
98 io_scope_stack: Vec::new(),
99 block_stack: Vec::new(),
100 lower,
101 })
102 }
103
104 pub fn parse(mut self) -> Result<Vec<Step>> {
105 while let Some(token) = self.tokens.pop_front() {
106 if self.pending_io_block.is_some()
107 && !matches!(
108 token,
109 RawToken::BlockStart { .. }
110 | RawToken::Command { .. }
111 | RawToken::Instruction { .. }
112 )
113 {
114 let pending = self.pending_io_block.take().unwrap();
115 bail!(
116 "line {}: WITH_IO block must be followed by '{{'",
117 pending.line_no
118 );
119 }
120 match token {
121 RawToken::Guard { pair, line_end } => {
122 let groups = parse_guard_line(pair)?;
123 self.handle_guard_token(line_end, groups)?
124 }
125 RawToken::BlockStart { line_no } => self.start_block(line_no)?,
126 RawToken::BlockEnd { line_no } => self.end_block(line_no)?,
127 RawToken::Command { pair, line_no } => {
128 let kind = parse_structural_command_with_lower(pair, &self.lower)?;
129 self.handle_command_token(line_no, kind)?
130 }
131 RawToken::Instruction { pair, line_no } => {
132 let kind = self.lower_instruction(pair)?;
133 self.handle_command_token(line_no, kind)?
134 }
135 }
136 }
137
138 if let Some(pending) = self.pending_io_block.take() {
139 bail!(
140 "line {}: WITH_IO block must be followed by '{{'",
141 pending.line_no
142 );
143 }
144
145 if self.guard_stack.len() != 1 {
146 bail!("unclosed guard block at end of script");
147 }
148 if self.pending_guards.is_some() {
149 bail!("guard declared on final lines without a following command");
150 }
151
152 if let Some(frame) = self.io_scope_stack.last() {
153 bail!(
154 "WITH_IO block starting on line {} was not closed",
155 frame.line_no
156 );
157 }
158
159 {
162 let mut seen_non_prelude = false;
163 let mut inherit_count = 0usize;
164 for step in &self.steps {
165 match &step.kind {
166 StepKind::InheritEnv { .. } => {
167 if seen_non_prelude {
168 bail!("INHERIT_ENV must appear before any other commands");
169 }
170 if step.guard.is_some() || step.scope_enter > 0 || step.scope_exit > 0 {
171 bail!("INHERIT_ENV cannot be guarded or nested inside blocks");
172 }
173 inherit_count += 1;
174 }
175 kind => {
176 if contains_inherit_env(kind) {
177 bail!("INHERIT_ENV cannot be nested inside other commands");
178 }
179 seen_non_prelude = true;
180 }
181 }
182 }
183 if inherit_count > 1 {
184 bail!("only one INHERIT_ENV directive is allowed");
185 }
186 }
187
188 Ok(self.steps)
189 }
190
191 fn lower_instruction(&self, pair: Pair<Rule>) -> Result<StepKind> {
192 let (name, args) = extract_instruction(pair)?;
193 (self.lower)(&name, args)
194 }
195
196 fn handle_guard_token(&mut self, line_end: usize, expr: GuardExpr) -> Result<()> {
197 if let Some(RawToken::Command { line_no, .. }) = self.tokens.front()
198 && *line_no == line_end
199 {
200 self.pending_inline_guards = Some(expr);
201 self.pending_can_open_block = false;
202 return Ok(());
203 }
204 self.stash_pending_guard(expr);
205 self.pending_can_open_block = true;
206 Ok(())
207 }
208
209 fn handle_command_token(&mut self, line_no: usize, kind: StepKind) -> Result<()> {
210 let inline = self.pending_inline_guards.take();
211 self.handle_command(line_no, kind, inline)
212 }
213
214 fn stash_pending_guard(&mut self, guard: GuardExpr) {
215 self.pending_guards = Some(if let Some(existing) = self.pending_guards.take() {
216 GuardExpr::all(vec![existing, guard])
217 } else {
218 guard
219 });
220 }
221
222 fn start_guard_block_from_pending(&mut self, line_no: usize) -> Result<()> {
223 let guards = self
224 .pending_guards
225 .take()
226 .ok_or_else(|| anyhow!("line {}: '{{' without a pending guard", line_no))?;
227 if !self.pending_can_open_block {
228 bail!("line {}: '{{' must directly follow a guard", line_no);
229 }
230 self.pending_can_open_block = false;
231 self.enter_guard_block(guards, line_no)
232 }
233
234 fn enter_guard_block(&mut self, guard: GuardExpr, line_no: usize) -> Result<()> {
235 let composed = if let Some(pending) = self.pending_guards.take() {
236 GuardExpr::all(vec![pending, guard])
237 } else {
238 guard
239 };
240 let parent = self.guard_stack.last().cloned().unwrap_or(None);
241 let next = and_guard_exprs(parent, Some(composed));
242 self.guard_stack.push(next);
243 self.scope_stack.push(ScopeFrame {
244 line_no,
245 had_command: false,
246 });
247 self.pending_scope_enters += 1;
248 Ok(())
249 }
250
251 fn begin_io_block(
252 &mut self,
253 line_no: usize,
254 bindings: Vec<IoBinding>,
255 guards: Option<GuardExpr>,
256 ) -> Result<()> {
257 if self.pending_io_block.is_some() {
258 bail!(
259 "line {}: previous WITH_IO block is still waiting for '{{'",
260 line_no
261 );
262 }
263 self.pending_io_block = Some(PendingIoBlock {
264 line_no,
265 bindings,
266 guards,
267 });
268 Ok(())
269 }
270
271 fn start_block(&mut self, line_no: usize) -> Result<()> {
272 if let Some(pending) = self.pending_io_block.take() {
273 self.block_stack.push(BlockKind::Io);
274 self.io_scope_stack.push(IoScopeFrame {
275 line_no: pending.line_no,
276 had_command: false,
277 bindings: pending.bindings,
278 guards: pending.guards,
279 first_step: self.steps.len(),
280 });
281 Ok(())
282 } else {
283 self.start_guard_block_from_pending(line_no)?;
284 self.block_stack.push(BlockKind::Guard);
285 Ok(())
286 }
287 }
288
289 fn end_block(&mut self, line_no: usize) -> Result<()> {
290 let kind = self
291 .block_stack
292 .pop()
293 .ok_or_else(|| anyhow!("line {}: unexpected '}}'", line_no))?;
294 match kind {
295 BlockKind::Guard => self.end_guard_block(line_no),
296 BlockKind::Io => self.end_io_block(line_no),
297 }
298 }
299
300 fn end_guard_block(&mut self, line_no: usize) -> Result<()> {
301 if self.guard_stack.len() == 1 {
302 bail!("line {}: unexpected '}}'", line_no);
303 }
304 if self.pending_guards.is_some() {
305 bail!(
306 "line {}: guard declared immediately before '}}' without a command",
307 line_no
308 );
309 }
310 let frame = self
311 .scope_stack
312 .last()
313 .cloned()
314 .ok_or_else(|| anyhow!("line {}: scope stack underflow", line_no))?;
315 if !frame.had_command {
316 bail!(
317 "line {}: guard block starting on line {} must contain at least one command",
318 line_no,
319 frame.line_no
320 );
321 }
322 let step = self
323 .steps
324 .last_mut()
325 .ok_or_else(|| anyhow!("line {}: guard block closed without any commands", line_no))?;
326 step.scope_exit += 1;
327 self.scope_stack.pop();
328 self.guard_stack.pop();
329 Ok(())
330 }
331
332 fn end_io_block(&mut self, line_no: usize) -> Result<()> {
333 let frame = self
334 .io_scope_stack
335 .pop()
336 .ok_or_else(|| anyhow!("line {}: unexpected '}}'", line_no))?;
337 if !frame.had_command {
338 bail!(
339 "line {}: WITH_IO block starting on line {} must contain at least one command",
340 line_no,
341 frame.line_no
342 );
343 }
344 if self.steps.len() > frame.first_step {
348 self.steps[frame.first_step].scope_enter += 1;
349 if let Some(last) = self.steps.last_mut() {
350 last.scope_exit += 1;
351 }
352 }
353 Ok(())
354 }
355
356 fn guard_context(&mut self, inline: Option<GuardExpr>) -> Option<GuardExpr> {
357 let mut context = self.guard_stack.last().cloned().unwrap_or(None);
358 if let Some(pending) = self.pending_guards.take() {
359 context = and_guard_exprs(context, Some(pending));
360 self.pending_can_open_block = false;
361 }
362 if let Some(inline_guard) = inline {
363 context = and_guard_exprs(context, Some(inline_guard));
364 }
365 context
366 }
367
368 fn handle_command(
369 &mut self,
370 line_no: usize,
371 kind: StepKind,
372 inline_guards: Option<GuardExpr>,
373 ) -> Result<()> {
374 if let StepKind::WithIoBlock { bindings } = kind {
375 let guards = self.guard_context(inline_guards);
376 self.begin_io_block(line_no, bindings, guards)?;
377 return Ok(());
378 }
379
380 let guards = self.guard_context(inline_guards);
381 let guards = self.apply_io_guards(guards);
382 let scope_enter = self.pending_scope_enters;
383 self.pending_scope_enters = 0;
384 for frame in self.scope_stack.iter_mut() {
385 frame.had_command = true;
386 }
387 for frame in self.io_scope_stack.iter_mut() {
388 frame.had_command = true;
389 }
390 let kind = self.apply_io_defaults(kind);
391 self.steps.push(Step {
392 guard: guards,
393 kind,
394 scope_enter,
395 scope_exit: 0,
396 });
397 Ok(())
398 }
399
400 fn apply_io_defaults(&self, kind: StepKind) -> StepKind {
401 let defaults = self.current_io_defaults();
402 if defaults.is_empty() {
403 return kind;
404 }
405 match kind {
406 StepKind::WithIo { bindings, cmd } => StepKind::WithIo {
407 bindings: merge_bindings(&defaults, &bindings),
408 cmd,
409 },
410 other => StepKind::WithIo {
411 bindings: defaults,
412 cmd: Box::new(other),
413 },
414 }
415 }
416
417 fn current_io_defaults(&self) -> Vec<IoBinding> {
418 if self.io_scope_stack.is_empty() {
419 return Vec::new();
420 }
421 let mut set = IoBindingSet::default();
422 for frame in &self.io_scope_stack {
423 for binding in &frame.bindings {
424 set.insert(binding.clone());
425 }
426 }
427 set.into_vec()
428 }
429
430 fn apply_io_guards(&self, guard: Option<GuardExpr>) -> Option<GuardExpr> {
431 self.io_scope_stack.iter().fold(guard, |acc, frame| {
432 and_guard_exprs(acc, frame.guards.clone())
433 })
434 }
435}
436
437pub fn parse_script(
438 input: &str,
439 lower: impl Fn(&str, Vec<Arg>) -> Result<StepKind>,
440) -> Result<Vec<Step>> {
441 ScriptParser::new(input, lower)?.parse()
442}
443
444pub fn parse_guard_expr_str(input: &str) -> Result<GuardExpr> {
445 use pest::Parser;
446 let pairs = lexer::LanguageParser::parse(Rule::guard_expr, input)
447 .map_err(|e| anyhow!("guard parse error: {e}"))?;
448 let pair = pairs
449 .into_iter()
450 .next()
451 .ok_or_else(|| anyhow!("empty guard"))?;
452 parse_guard_expr(pair)
453}
454
455fn and_guard_exprs(left: Option<GuardExpr>, right: Option<GuardExpr>) -> Option<GuardExpr> {
456 match (left, right) {
457 (None, None) => None,
458 (Some(expr), None) | (None, Some(expr)) => Some(expr),
459 (Some(lhs), Some(rhs)) => Some(GuardExpr::all(vec![lhs, rhs])),
460 }
461}
462
463fn merge_bindings(defaults: &[IoBinding], overrides: &[IoBinding]) -> Vec<IoBinding> {
464 let mut set = IoBindingSet::default();
465 for binding in defaults {
466 set.insert(binding.clone());
467 }
468 for binding in overrides {
469 set.insert(binding.clone());
470 }
471 set.into_vec()
472}
473
474fn contains_inherit_env(kind: &StepKind) -> bool {
475 match kind {
476 StepKind::InheritEnv { .. } => true,
477 StepKind::WithIo { cmd, .. } => contains_inherit_env(cmd),
478 _ => false,
479 }
480}
481
482fn parse_structural_command_with_lower(
483 pair: Pair<Rule>,
484 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
485) -> Result<StepKind> {
486 let kind = match pair.as_rule() {
487 Rule::inherit_env_command => {
488 let mut keys = Vec::new();
489 for inner in pair.into_inner() {
490 if inner.as_rule() == Rule::inherit_list {
491 for key in inner.into_inner() {
492 if key.as_rule() == Rule::env_key {
493 keys.push(key.as_str().trim().to_string());
494 }
495 }
496 } else if inner.as_rule() == Rule::env_key {
497 keys.push(inner.as_str().trim().to_string());
498 }
499 }
500 StepKind::InheritEnv { keys }
501 }
502 Rule::with_io_command => {
503 let mut bindings = Vec::new();
504 let mut cmd = None;
505 for inner in pair.into_inner() {
506 match inner.as_rule() {
507 Rule::io_flags => {
508 for flag in inner.into_inner() {
509 if flag.as_rule() == Rule::io_binding {
510 bindings.push(parse_io_binding(flag)?);
511 }
512 }
513 }
514 Rule::with_io_command => {
515 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
516 }
517 Rule::inherit_env_command => {
518 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
519 }
520 Rule::async_statement | Rule::async_statement_block => {
521 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
522 }
523 Rule::timeout_statement | Rule::cancel_statement => {
524 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
525 }
526 Rule::instruction | Rule::instruction_inner => {
527 let (name, args) = extract_instruction(inner)?;
528 cmd = Some(Box::new(lower(&name, args)?));
529 }
530 _ => {}
531 }
532 }
533 if let Some(cmd) = cmd {
534 StepKind::WithIo { bindings, cmd }
535 } else {
536 StepKind::WithIoBlock { bindings }
537 }
538 }
539 Rule::for_statement => parse_for_statement_from_pair(pair, lower)?,
540 Rule::let_statement => parse_let_statement_from_pair(pair)?,
541 Rule::let_async_statement => parse_let_async_statement_from_pair(pair, lower)?,
542 Rule::await_statement => parse_await_statement_from_pair(pair)?,
543 Rule::cancel_statement => parse_cancel_statement_from_pair(pair)?,
544 Rule::if_statement => parse_if_statement_from_pair(pair, lower)?,
545 Rule::async_statement => parse_async_statement_from_pair(pair, lower)?,
546 Rule::async_statement_block => parse_async_statement_block_from_pair(pair, lower)?,
547 Rule::timeout_statement => parse_timeout_statement_from_pair(pair, lower)?,
548 Rule::command_inner => {
549 let inner = pair
552 .into_inner()
553 .next()
554 .ok_or_else(|| anyhow!("empty command_inner"))?;
555 parse_structural_command_with_lower(inner, lower)?
556 }
557 Rule::instruction | Rule::instruction_inner => {
558 let (name, args) = extract_instruction(pair)?;
559 lower(&name, args)?
560 }
561 _ => bail!("unexpected structural command rule: {:?}", pair.as_rule()),
562 };
563 Ok(kind)
564}
565
566fn extract_instruction(pair: Pair<Rule>) -> Result<(String, Vec<Arg>)> {
567 let mut name = None;
568 let mut args = Vec::new();
569 for inner in pair.into_inner() {
570 match inner.as_rule() {
571 Rule::command_name => {
572 name = Some(inner.as_str().to_string());
573 }
574 Rule::argument => {
575 args.push(parse_argument(inner)?);
576 }
577 _ => {}
578 }
579 }
580 let name = name.ok_or_else(|| anyhow!("instruction missing command name"))?;
581 Ok((name, args))
582}
583
584fn parse_for_statement_from_pair(
585 pair: Pair<Rule>,
586 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
587) -> Result<StepKind> {
588 let mut idents = Vec::new();
589 let mut in_expr = None;
590 let mut body_steps = Vec::new();
591 for inner in pair.into_inner() {
592 match inner.as_rule() {
593 Rule::dollar_ident => {
594 idents.push(parse_dollar_ident(inner));
595 }
596 Rule::expr => {
597 in_expr = Some(parse_expr(inner)?);
598 }
599 Rule::block => {
600 body_steps = parse_block_elements_with_lower(inner, lower)?;
601 }
602 _ => {}
603 }
604 }
605 let (key_var, var) = match idents.len() {
606 1 => (None, idents.into_iter().next().unwrap()),
607 2 => {
608 let mut iter = idents.into_iter();
609 (Some(iter.next().unwrap()), iter.next().unwrap())
610 }
611 _ => bail!("FOR requires at least one variable"),
612 };
613 Ok(StepKind::For {
614 key_var,
615 var,
616 in_expr: in_expr.ok_or_else(|| anyhow!("FOR requires an iterable expression"))?,
617 body: body_steps,
618 })
619}
620
621fn parse_let_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
622 let mut var = None;
623 let mut expr = None;
624 for inner in pair.into_inner() {
625 match inner.as_rule() {
626 Rule::dollar_ident => {
627 var = Some(parse_dollar_ident(inner));
628 }
629 Rule::expr => {
630 expr = Some(parse_expr(inner)?);
631 }
632 _ => {}
633 }
634 }
635 Ok(StepKind::Assign {
636 var: var.ok_or_else(|| anyhow!("LET requires a variable"))?,
637 expr: expr.ok_or_else(|| anyhow!("LET requires an expression"))?,
638 })
639}
640
641fn parse_let_async_statement_from_pair(
642 pair: Pair<Rule>,
643 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
644) -> Result<StepKind> {
645 let mut var = None;
646 let mut body = None;
647 for inner in pair.into_inner() {
648 match inner.as_rule() {
649 Rule::dollar_ident => {
650 var = Some(parse_dollar_ident(inner));
651 }
652 Rule::block => {
653 body = Some(parse_block_elements_with_lower(inner, lower)?);
654 }
655 Rule::command_inner => {
656 let inner = inner
659 .into_inner()
660 .next()
661 .ok_or_else(|| anyhow!("empty command_inner"))?;
662 let step_kind = parse_structural_command_with_lower(inner, lower)?;
663 body = Some(vec![Step {
664 guard: None,
665 kind: step_kind,
666 scope_enter: 0,
667 scope_exit: 0,
668 }]);
669 }
670 Rule::with_io_command => {
671 let kind = parse_structural_command_with_lower(inner, lower)?;
676 let StepKind::WithIo { bindings, cmd } = kind else {
677 bail!(
678 "LET $var = WITH_IO requires an ASYNC command (e.g. LET $t = WITH_IO [stdin=pipe:p] ASYNC WRITE \"f\")"
679 );
680 };
681 let StepKind::AsyncBlock { body: async_body } = *cmd else {
682 bail!(
683 "LET $var = WITH_IO requires an ASYNC command (e.g. LET $t = WITH_IO [stdin=pipe:p] ASYNC WRITE \"f\")"
684 );
685 };
686 if async_body.len() != 1 {
687 bail!(
688 "LET $var = WITH_IO [..] ASYNC accepts a single command; use LET $var = ASYNC {{ ... }} with WITH_IO inside the block for multi-step tasks"
689 );
690 }
691 let step = async_body
692 .into_iter()
693 .next()
694 .ok_or_else(|| anyhow!("LET $var = ASYNC requires a body"))?;
695 body = Some(vec![Step {
696 guard: step.guard,
697 kind: StepKind::WithIo {
698 bindings,
699 cmd: Box::new(step.kind),
700 },
701 scope_enter: step.scope_enter,
702 scope_exit: step.scope_exit,
703 }]);
704 }
705 _ => {}
706 }
707 }
708 Ok(StepKind::AssignAsync {
709 var: var.ok_or_else(|| anyhow!("LET $var = ASYNC requires a variable"))?,
710 body: body.ok_or_else(|| anyhow!("LET $var = ASYNC requires a body"))?,
711 })
712}
713
714fn parse_await_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
715 let mut var = None;
716 for inner in pair.into_inner() {
717 if inner.as_rule() == Rule::ident {
718 var = Some(inner.as_str().to_string());
719 }
720 }
721 Ok(StepKind::Await {
722 var: var.ok_or_else(|| anyhow!("AWAIT requires a variable"))?,
723 })
724}
725
726fn parse_cancel_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
727 let mut var = None;
728 for inner in pair.into_inner() {
729 if inner.as_rule() == Rule::ident {
730 var = Some(inner.as_str().to_string());
731 }
732 }
733 Ok(StepKind::Cancel {
734 var: var.ok_or_else(|| anyhow!("CANCEL requires a variable"))?,
735 })
736}
737
738fn parse_timeout_statement_from_pair(
739 pair: Pair<Rule>,
740 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
741) -> Result<StepKind> {
742 let mut duration = None;
743 let mut body: Option<Vec<Step>> = None;
744 for inner in pair.into_inner() {
745 match inner.as_rule() {
746 Rule::timeout_duration => {
747 duration = Some(parse_duration(inner.as_str())?);
748 }
749 Rule::block => {
750 body = Some(parse_block_elements_with_lower(inner, lower)?);
751 }
752 Rule::await_statement => {
753 let kind = parse_await_statement_from_pair(inner)?;
754 body = Some(vec![Step {
755 guard: None,
756 kind,
757 scope_enter: 0,
758 scope_exit: 0,
759 }]);
760 }
761 Rule::cancel_statement => {
762 let kind = parse_cancel_statement_from_pair(inner)?;
763 body = Some(vec![Step {
764 guard: None,
765 kind,
766 scope_enter: 0,
767 scope_exit: 0,
768 }]);
769 }
770 Rule::with_io_command
771 | Rule::inherit_env_command
772 | Rule::async_statement
773 | Rule::async_statement_block
774 | Rule::timeout_statement => {
775 let kind = parse_structural_command_with_lower(inner, lower)?;
776 body = Some(vec![Step {
777 guard: None,
778 kind,
779 scope_enter: 0,
780 scope_exit: 0,
781 }]);
782 }
783 Rule::instruction | Rule::instruction_inner => {
784 let (name, args) = extract_instruction(inner)?;
785 let kind = lower(&name, args)?;
786 body = Some(vec![Step {
787 guard: None,
788 kind,
789 scope_enter: 0,
790 scope_exit: 0,
791 }]);
792 }
793 _ => {}
794 }
795 }
796 Ok(StepKind::Timeout {
797 duration: duration.ok_or_else(|| anyhow!("TIMEOUT requires a duration"))?,
798 body: body.ok_or_else(|| anyhow!("TIMEOUT requires a command or block"))?,
799 })
800}
801
802fn parse_if_statement_from_pair(
803 pair: Pair<Rule>,
804 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
805) -> Result<StepKind> {
806 let mut cond = None;
807 let mut then_body = Vec::new();
808 let mut else_ifs = Vec::new();
809 let mut else_body = None;
810
811 for inner in pair.into_inner() {
812 match inner.as_rule() {
813 Rule::expr => {
814 if cond.is_none() {
815 cond = Some(parse_expr(inner)?);
816 }
817 }
818 Rule::block => {
819 if then_body.is_empty() {
820 then_body = parse_block_elements_with_lower(inner, lower)?;
821 }
822 }
823 Rule::else_if_clause => {
824 let (eif_cond, eif_body) = parse_else_if_clause(inner, lower)?;
825 else_ifs.push((eif_cond, eif_body));
826 }
827 Rule::else_clause => {
828 else_body = Some(parse_else_clause(inner, lower)?);
829 }
830 _ => {}
831 }
832 }
833 Ok(StepKind::If {
834 cond: Box::new(cond.ok_or_else(|| anyhow!("IF requires a condition"))?),
835 then_body,
836 else_ifs,
837 else_body,
838 })
839}
840
841fn parse_else_if_clause(
842 pair: Pair<Rule>,
843 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
844) -> Result<(Box<Expr>, Vec<Step>)> {
845 let mut cond = None;
846 let mut body = Vec::new();
847 for inner in pair.into_inner() {
848 match inner.as_rule() {
849 Rule::expr => cond = Some(parse_expr(inner)?),
850 Rule::block => body = parse_block_elements_with_lower(inner, lower)?,
851 _ => {}
852 }
853 }
854 Ok((
855 Box::new(cond.ok_or_else(|| anyhow!("ELSE IF requires a condition"))?),
856 body,
857 ))
858}
859
860fn parse_else_clause(
861 pair: Pair<Rule>,
862 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
863) -> Result<Vec<Step>> {
864 for inner in pair.into_inner() {
865 if let Rule::block = inner.as_rule() {
866 return parse_block_elements_with_lower(inner, lower);
867 }
868 }
869 Ok(Vec::new())
870}
871
872fn parse_async_statement_from_pair(
873 pair: Pair<Rule>,
874 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
875) -> Result<StepKind> {
876 let mut inner_cmd = None;
877 let mut block_body = None;
878 for inner in pair.into_inner() {
879 match inner.as_rule() {
880 Rule::command => {
881 let cmd_text = inner.as_str();
885 let steps = parse_script(cmd_text, |name, args| lower(name, args))?;
886 if steps.len() == 1 {
887 inner_cmd = Some(steps.into_iter().next().unwrap().kind);
888 } else {
889 bail!("unexpected multiple steps in async inner command");
890 }
891 }
892 Rule::command_inner => {
893 let child = inner
895 .into_inner()
896 .next()
897 .ok_or_else(|| anyhow!("empty command_inner"))?;
898 match child.as_rule() {
899 Rule::inherit_env_command => {
900 inner_cmd = Some(parse_structural_command_with_lower(child, lower)?);
901 }
902 Rule::async_statement | Rule::async_statement_block => {
903 inner_cmd = Some(parse_structural_command_with_lower(child, lower)?);
904 }
905 Rule::timeout_statement | Rule::cancel_statement => {
906 inner_cmd = Some(parse_structural_command_with_lower(child, lower)?);
907 }
908 Rule::instruction => {
909 let (name, args) = extract_instruction(child)?;
910 inner_cmd = Some(lower(&name, args)?);
911 }
912 other => bail!("unexpected command_inner child: {:?}", other),
913 }
914 }
915 Rule::instruction | Rule::instruction_inner => {
916 let (name, args) = extract_instruction(inner)?;
917 inner_cmd = Some(lower(&name, args)?);
918 }
919 Rule::block => {
920 block_body = Some(parse_block_elements_with_lower(inner, lower)?);
921 }
922 _ => {}
923 }
924 }
925 if let Some(body) = block_body {
926 for step in &body {
927 if matches!(&step.kind, StepKind::WithIo { .. }) {
928 bail!(
929 "WITH_IO cannot be placed inside ASYNC. Place WITH_IO outside ASYNC instead (e.g. WITH_IO [...] ASYNC RUN ...)"
930 );
931 }
932 }
933 Ok(StepKind::AsyncBlock { body })
934 } else if let Some(cmd) = inner_cmd {
935 if matches!(&cmd, StepKind::WithIo { .. }) {
936 bail!(
937 "WITH_IO cannot be placed inside ASYNC. Place WITH_IO outside ASYNC instead (e.g. WITH_IO [...] ASYNC RUN ...)"
938 );
939 }
940 Ok(StepKind::AsyncBlock {
941 body: vec![Step {
942 guard: None,
943 kind: cmd,
944 scope_enter: 0,
945 scope_exit: 0,
946 }],
947 })
948 } else {
949 bail!("ASYNC requires either a command or a block");
950 }
951}
952
953fn parse_async_statement_block_from_pair(
954 pair: Pair<Rule>,
955 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
956) -> Result<StepKind> {
957 let mut block_body = None;
958 for inner in pair.into_inner() {
959 if inner.as_rule() == Rule::block {
960 block_body = Some(parse_block_elements_with_lower(inner, lower)?);
961 }
962 }
963 let body = block_body.ok_or_else(|| anyhow!("async_statement_block requires a block"))?;
964 for step in &body {
965 if matches!(&step.kind, StepKind::WithIo { .. }) {
966 bail!(
967 "WITH_IO cannot be placed inside ASYNC. Place WITH_IO outside ASYNC instead (e.g. WITH_IO [...] ASYNC RUN ...)"
968 );
969 }
970 }
971 Ok(StepKind::AsyncBlock { body })
972}
973
974fn parse_block_elements_with_lower(
975 block_pair: Pair<Rule>,
976 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
977) -> Result<Vec<Step>> {
978 let mut steps = Vec::new();
979 for elem in block_pair.into_inner() {
980 match elem.as_rule() {
981 Rule::for_statement
982 | Rule::let_statement
983 | Rule::let_async_statement
984 | Rule::await_statement
985 | Rule::cancel_statement
986 | Rule::if_statement
987 | Rule::async_statement
988 | Rule::timeout_statement
989 | Rule::async_statement_block => {
990 let step_kind = parse_structural_command_with_lower(elem, lower)?;
991 steps.push(Step {
992 guard: None,
993 kind: step_kind,
994 scope_enter: 0,
995 scope_exit: 0,
996 });
997 }
998 Rule::guard_block => {
999 let mut guard_pair = None;
1000 let mut inner_block = None;
1001 for inner in elem.into_inner() {
1002 match inner.as_rule() {
1003 Rule::guard_line => guard_pair = Some(inner),
1004 Rule::block => inner_block = Some(inner),
1005 _ => {}
1006 }
1007 }
1008 if let (Some(gp), Some(bp)) = (guard_pair, inner_block) {
1009 let guard_expr = parse_guard_line(gp)?;
1010 let mut inner_steps = parse_block_elements_with_lower(bp, lower)?;
1011 for step in &mut inner_steps {
1012 step.guard = Some(guard_expr.clone());
1013 }
1014 steps.extend(inner_steps);
1015 }
1016 }
1017 Rule::instruction | Rule::instruction_inner => {
1018 let (name, args) = extract_instruction(elem)?;
1019 let kind = lower(&name, args)?;
1020 steps.push(Step {
1021 guard: None,
1022 kind,
1023 scope_enter: 0,
1024 scope_exit: 0,
1025 });
1026 }
1027 Rule::with_io_command => {
1028 let step_kind = parse_structural_command_with_lower(elem, lower)?;
1029 steps.push(Step {
1030 guard: None,
1031 kind: step_kind,
1032 scope_enter: 0,
1033 scope_exit: 0,
1034 });
1035 }
1036 _ => {} }
1038 }
1039 Ok(steps)
1040}
1041
1042fn parse_argument(pair: Pair<Rule>) -> Result<Arg> {
1043 let inner: Vec<_> = pair.into_inner().collect();
1044 if inner.len() == 1 && inner[0].as_rule() == Rule::expr {
1046 return Ok(Arg::Expr(parse_expr(inner.into_iter().next().unwrap())?));
1047 }
1048 if inner.len() == 1 && inner[0].as_rule() == Rule::string_literal {
1050 return Ok(Arg::String(parse_fragments(&inner)?, true));
1051 }
1052 Ok(Arg::String(parse_fragments(&inner)?, false))
1053}
1054
1055fn parse_quoted_string(pair: Pair<Rule>) -> Result<String> {
1056 let s = pair.as_str();
1057 let content = &s[1..s.len() - 1];
1058 Ok(content.to_string())
1060}
1061
1062fn parse_fragments(parts: &[Pair<Rule>]) -> Result<String> {
1066 if parts.len() == 1 && parts[0].as_rule() == Rule::string_literal {
1068 let s = parts[0].as_str();
1069 return Ok(s[1..s.len() - 1].to_string());
1070 }
1071
1072 let mut body = String::new();
1073 let mut last_end = None;
1074 for part in parts {
1075 let span = part.as_span();
1076 if let Some(end) = last_end
1077 && span.start() > end
1078 {
1079 body.push(' ');
1080 }
1081 match part.as_rule() {
1082 Rule::string_literal => {
1083 let s = part.as_str();
1084 let unquoted = &s[1..s.len() - 1];
1085 body.push_str(unquoted);
1086 }
1087 Rule::templated_arg | Rule::unquoted_arg => {
1088 body.push_str(part.as_str());
1089 }
1090 Rule::expr => body.push_str(part.as_str()),
1091 _ => {}
1092 }
1093 last_end = Some(span.end());
1094 }
1095 Ok(body)
1096}
1097
1098fn parse_guard_line(pair: Pair<Rule>) -> Result<GuardExpr> {
1099 for inner in pair.into_inner() {
1100 if inner.as_rule() == Rule::guard_expr {
1101 return parse_guard_expr(inner);
1102 }
1103 }
1104 bail!("guard line missing expression")
1105}
1106
1107fn parse_io_binding(pair: Pair<Rule>) -> Result<IoBinding> {
1108 let mut stream = None;
1109 let mut pipe = None;
1110 for inner in pair.into_inner() {
1111 match inner.as_rule() {
1112 Rule::io_stream => stream = Some(parse_io_stream(inner.as_str())),
1113 Rule::pipe_binding => pipe = Some(parse_pipe_binding(inner)?),
1114 _ => {}
1115 }
1116 }
1117 let stream = stream.ok_or_else(|| anyhow!("missing IO stream in WITH_IO"))?;
1118 Ok(IoBinding { stream, pipe })
1119}
1120
1121fn parse_io_stream(text: &str) -> IoStream {
1122 match text {
1123 "stdin" => IoStream::Stdin,
1124 "stdout" => IoStream::Stdout,
1125 "stderr" => IoStream::Stderr,
1126 _ => unreachable!("parser produced invalid io_stream token"),
1127 }
1128}
1129
1130fn parse_pipe_binding(pair: Pair<Rule>) -> Result<String> {
1131 for inner in pair.into_inner() {
1132 if inner.as_rule() == Rule::pipe_name {
1133 return Ok(inner.as_str().to_string());
1134 }
1135 }
1136 bail!("missing pipe identifier in WITH_IO binding");
1137}
1138
1139fn parse_guard_expr(pair: Pair<Rule>) -> Result<GuardExpr> {
1140 match pair.as_rule() {
1141 Rule::guard_expr => {
1142 let next = pair
1143 .into_inner()
1144 .next()
1145 .ok_or_else(|| anyhow!("guard expression missing body"))?;
1146 parse_guard_expr(next)
1147 }
1148 Rule::guard_seq => parse_guard_seq(pair),
1149 Rule::guard_factor => parse_guard_factor(pair),
1150 Rule::guard_not => {
1151 bail!("guard_not should not create a pair")
1153 }
1154 Rule::guard_primary => parse_guard_primary(pair),
1155 Rule::guard_group => parse_guard_group(pair),
1156 Rule::guard_any_call => parse_guard_any_call(pair),
1157 Rule::guard_all_call => parse_guard_all_call(pair),
1158 Rule::not_call => parse_not_call(pair),
1159 Rule::guard_term => parse_guard_term(pair),
1160 _ => bail!("unexpected guard expression rule: {:?}", pair.as_rule()),
1161 }
1162}
1163
1164fn parse_guard_seq(pair: Pair<Rule>) -> Result<GuardExpr> {
1165 let mut exprs = Vec::new();
1166 for inner in pair.into_inner() {
1167 if inner.as_rule() == Rule::guard_factor {
1168 exprs.push(parse_guard_factor(inner)?);
1169 }
1170 }
1171 match exprs.len() {
1172 0 => bail!("guard list requires at least one entry"),
1173 1 => Ok(exprs.pop().unwrap()),
1174 _ => Ok(GuardExpr::all(exprs)),
1175 }
1176}
1177
1178fn parse_guard_factor(pair: Pair<Rule>) -> Result<GuardExpr> {
1179 let inner = pair
1180 .into_inner()
1181 .next()
1182 .ok_or_else(|| anyhow!("guard factor missing expression"))?;
1183 parse_guard_expr(inner)
1184}
1185
1186fn parse_not_call(pair: Pair<Rule>) -> Result<GuardExpr> {
1187 for inner in pair.into_inner() {
1188 if inner.as_rule() == Rule::guard_expr {
1189 return parse_guard_expr(inner).map(|e| GuardExpr::Not(Box::new(e)));
1190 }
1191 }
1192 bail!("not() missing expression")
1193}
1194
1195fn parse_guard_primary(pair: Pair<Rule>) -> Result<GuardExpr> {
1196 match pair.as_rule() {
1197 Rule::guard_primary => {
1198 let inner = pair
1199 .into_inner()
1200 .next()
1201 .ok_or_else(|| anyhow!("guard primary missing body"))?;
1202 parse_guard_primary(inner)
1203 }
1204 Rule::guard_group => parse_guard_group(pair),
1205 Rule::guard_any_call => parse_guard_any_call(pair),
1206 Rule::guard_all_call => parse_guard_all_call(pair),
1207 Rule::not_call => parse_not_call(pair),
1208 Rule::guard_term => parse_guard_term(pair),
1209 _ => bail!("unexpected guard primary rule: {:?}", pair.as_rule()),
1210 }
1211}
1212
1213fn parse_guard_group(pair: Pair<Rule>) -> Result<GuardExpr> {
1214 for inner in pair.into_inner() {
1215 if inner.as_rule() == Rule::guard_expr {
1216 return parse_guard_expr(inner);
1217 }
1218 }
1219 bail!("grouped guard missing expression")
1220}
1221
1222fn parse_guard_any_call(pair: Pair<Rule>) -> Result<GuardExpr> {
1223 let mut args = Vec::new();
1224 for inner in pair.into_inner() {
1225 if inner.as_rule() == Rule::guard_expr_list {
1226 args = parse_guard_expr_list(inner)?;
1227 }
1228 }
1229 if args.len() < 2 {
1230 bail!("any(...) requires at least two guard expressions");
1231 }
1232 Ok(GuardExpr::or(args))
1233}
1234
1235fn parse_guard_all_call(pair: Pair<Rule>) -> Result<GuardExpr> {
1236 let mut args = Vec::new();
1237 for inner in pair.into_inner() {
1238 if inner.as_rule() == Rule::guard_expr_list {
1239 args = parse_guard_expr_list(inner)?;
1240 }
1241 }
1242 if args.is_empty() {
1243 bail!("all(...) requires at least one guard expression");
1244 }
1245 Ok(GuardExpr::all(args))
1246}
1247
1248fn parse_guard_expr_list(pair: Pair<Rule>) -> Result<Vec<GuardExpr>> {
1249 let mut exprs = Vec::new();
1250 for inner in pair.into_inner() {
1251 if inner.as_rule() == Rule::guard_expr {
1252 push_guard_or_args_from_expr(inner, &mut exprs)?;
1253 }
1254 }
1255 Ok(exprs)
1256}
1257
1258fn push_guard_or_args_from_expr(expr_pair: Pair<Rule>, exprs: &mut Vec<GuardExpr>) -> Result<()> {
1259 if let Some(seq_pair) = expr_pair
1260 .clone()
1261 .into_inner()
1262 .find(|inner| inner.as_rule() == Rule::guard_seq)
1263 {
1264 let factors: Vec<Pair<Rule>> = seq_pair
1265 .into_inner()
1266 .filter(|inner| inner.as_rule() == Rule::guard_factor)
1267 .collect();
1268 if factors.len() > 1 {
1269 for factor in factors {
1270 exprs.push(parse_guard_factor(factor)?);
1271 }
1272 return Ok(());
1273 }
1274 }
1275 exprs.push(parse_guard_expr(expr_pair)?);
1276 Ok(())
1277}
1278
1279fn parse_guard_term(pair: Pair<Rule>) -> Result<GuardExpr> {
1280 for inner in pair.into_inner() {
1281 match inner.as_rule() {
1282 Rule::eq_guard => {
1283 return Ok(GuardExpr::Predicate(parse_func_guard(inner)?));
1284 }
1285 Rule::neq_guard => {
1286 let guard = parse_func_guard(inner)?;
1287 return Ok(GuardExpr::Not(Box::new(GuardExpr::Predicate(guard))));
1288 }
1289 Rule::bool_guard => {
1290 let val = inner
1291 .into_inner()
1292 .find(|p| p.as_rule() == Rule::bool_value)
1293 .expect("grammar invariant violated: bool_guard missing bool_value")
1294 .as_str()
1295 .to_string();
1296 return Ok(GuardExpr::Predicate(Guard::StaticBool { value: val }));
1297 }
1298 Rule::env_guard => {
1299 return Ok(GuardExpr::Predicate(parse_env_guard(inner)?));
1300 }
1301 Rule::bare_guard_ident => {
1302 let tag = inner.as_str();
1303 if let Ok(g) = parse_platform_tag(tag) {
1304 return Ok(GuardExpr::Predicate(g));
1305 }
1306 return Ok(GuardExpr::Predicate(Guard::EnvExists {
1307 key: tag.to_string(),
1308 }));
1309 }
1310 _ => {}
1311 }
1312 }
1313 bail!("missing guard predicate")
1314}
1315
1316fn parse_func_guard(pair: Pair<Rule>) -> Result<Guard> {
1317 let mut key = String::new();
1318 let mut value = String::new();
1319 let mut saw_env_prefix = false;
1320 for inner in pair.into_inner() {
1321 match inner.as_rule() {
1322 Rule::env_prefix => saw_env_prefix = true,
1323 Rule::env_key if saw_env_prefix => {
1324 key = inner.as_str().trim().to_string();
1325 }
1326 Rule::bare_guard_value | Rule::quoted_string => {
1327 value = unquote(inner.as_str().trim()).to_string();
1328 }
1329 _ => {}
1330 }
1331 }
1332 Ok(Guard::EnvEquals { key, value })
1333}
1334
1335fn unquote(s: &str) -> &str {
1336 s.strip_prefix('"')
1337 .and_then(|s| s.strip_suffix('"'))
1338 .or_else(|| s.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
1339 .unwrap_or(s)
1340}
1341
1342fn parse_env_guard(pair: Pair<Rule>) -> Result<Guard> {
1343 let mut key = String::new();
1344 for inner in pair.into_inner() {
1345 if inner.as_rule() == Rule::env_key {
1346 key = inner.as_str().trim().to_string();
1347 }
1348 }
1349 Ok(Guard::EnvExists { key })
1350}
1351
1352fn parse_platform_tag(tag: &str) -> Result<Guard> {
1353 let target = match tag.to_ascii_lowercase().as_str() {
1354 "unix" => PlatformGuard::Unix,
1355 "windows" => PlatformGuard::Windows,
1356 "mac" | "macos" => PlatformGuard::Macos,
1357 "linux" => PlatformGuard::Linux,
1358 _ => bail!("unknown platform '{}'", tag),
1359 };
1360 Ok(Guard::Platform { target })
1361}
1362
1363fn parse_dollar_ident(pair: Pair<Rule>) -> String {
1364 let s = pair.as_str();
1366 s.strip_prefix('$').unwrap_or(s).to_string()
1367}
1368
1369use crate::ast::{CompareOp, Expr, LogicalOp, Value};
1370
1371fn parse_expr(pair: Pair<Rule>) -> Result<Expr> {
1372 let inner = pair.into_inner().next().unwrap();
1373 match inner.as_rule() {
1374 Rule::expr_logical_or => parse_expr_logical_or(inner),
1375 _ => bail!("unexpected expr rule: {:?}", inner.as_rule()),
1376 }
1377}
1378
1379fn parse_expr_logical_or(pair: Pair<Rule>) -> Result<Expr> {
1380 let mut inner = pair.into_inner();
1381 let mut left = parse_expr_logical_and(inner.next().unwrap())?;
1382 while let Some(op_pair) = inner.next() {
1383 let op = match op_pair.as_rule() {
1384 Rule::or_op => LogicalOp::Or,
1385 _ => bail!("unexpected operator in logical-or: {:?}", op_pair.as_rule()),
1386 };
1387 let right = parse_expr_logical_and(inner.next().unwrap())?;
1388 left = Expr::Logical {
1389 op,
1390 left: Box::new(left),
1391 right: Box::new(right),
1392 };
1393 }
1394 Ok(left)
1395}
1396
1397fn parse_expr_logical_and(pair: Pair<Rule>) -> Result<Expr> {
1398 let mut inner = pair.into_inner();
1399 let mut left = parse_expr_comparison(inner.next().unwrap())?;
1400 while let Some(op_pair) = inner.next() {
1401 let op = match op_pair.as_rule() {
1402 Rule::and_op => LogicalOp::And,
1403 _ => bail!(
1404 "unexpected operator in logical-and: {:?}",
1405 op_pair.as_rule()
1406 ),
1407 };
1408 let right = parse_expr_comparison(inner.next().unwrap())?;
1409 left = Expr::Logical {
1410 op,
1411 left: Box::new(left),
1412 right: Box::new(right),
1413 };
1414 }
1415 Ok(left)
1416}
1417
1418fn parse_expr_comparison(pair: Pair<Rule>) -> Result<Expr> {
1419 let mut inner = pair.into_inner();
1420 let left = parse_expr_unary(inner.next().unwrap())?;
1421 if let Some(op_pair) = inner.next() {
1422 let op = match op_pair.as_rule() {
1423 Rule::eq_op => CompareOp::Eq,
1424 Rule::neq_op => CompareOp::Ne,
1425 _ => bail!("unexpected comparison operator: {:?}", op_pair.as_rule()),
1426 };
1427 let right = parse_expr_unary(inner.next().unwrap())?;
1428 Ok(Expr::Compare {
1429 op,
1430 left: Box::new(left),
1431 right: Box::new(right),
1432 })
1433 } else {
1434 Ok(left)
1435 }
1436}
1437
1438fn parse_expr_unary(pair: Pair<Rule>) -> Result<Expr> {
1439 let mut bangs = 0u32;
1440 let mut atom = None;
1441 for inner in pair.into_inner() {
1442 match inner.as_rule() {
1443 Rule::not_op => bangs += 1,
1444 Rule::expr_atom => atom = Some(parse_expr_atom(inner)?),
1445 _ => bail!("unexpected unary operand rule: {:?}", inner.as_rule()),
1446 }
1447 }
1448 let mut expr = atom.ok_or_else(|| anyhow!("'!' requires an expression operand"))?;
1449 for _ in 0..bangs {
1450 expr = Expr::Not(Box::new(expr));
1451 }
1452 Ok(expr)
1453}
1454
1455fn parse_expr_atom(pair: Pair<Rule>) -> Result<Expr> {
1456 let inner = pair.into_inner().next().unwrap();
1457 match inner.as_rule() {
1458 Rule::parenthesized_expr => parse_expr(inner.into_inner().next().unwrap()),
1459 Rule::func_call => parse_func_call(inner),
1460 Rule::key_path => parse_key_path(inner),
1461 Rule::variable => {
1462 let name = inner.as_str();
1463 let name = name.strip_prefix('$').unwrap_or(name).to_string();
1464 Ok(Expr::Var(name))
1465 }
1466 Rule::list_literal => parse_list_literal(inner),
1467 Rule::map_literal => parse_map_literal(inner),
1468 Rule::string_literal | Rule::quoted_string => {
1469 let s = parse_quoted_string(inner)?;
1470 Ok(Expr::Literal(Value::String(s)))
1471 }
1472 Rule::bare_word => {
1473 let s = inner.as_str().to_string();
1474 match s.as_str() {
1475 "true" => Ok(Expr::Literal(Value::Bool(true))),
1476 "false" => Ok(Expr::Literal(Value::Bool(false))),
1477 _ => Ok(Expr::Literal(Value::String(s))),
1478 }
1479 }
1480 _ => bail!("unexpected expression atom rule: {:?}", inner.as_rule()),
1481 }
1482}
1483
1484fn parse_key_path(pair: Pair<Rule>) -> Result<Expr> {
1485 let mut base = None;
1486 let mut keys = Vec::new();
1487 for inner in pair.into_inner() {
1488 match inner.as_rule() {
1489 Rule::ident => {
1490 if base.is_none() {
1491 base = Some(inner.as_str().to_string());
1492 }
1493 }
1494 Rule::key_path_segment => {
1495 keys.push(inner.as_str().to_string());
1496 }
1497 _ => {}
1498 }
1499 }
1500 Ok(Expr::KeyPath {
1501 base: base.ok_or_else(|| anyhow!("key path requires a base identifier"))?,
1502 keys,
1503 })
1504}
1505
1506fn parse_func_call(pair: Pair<Rule>) -> Result<Expr> {
1507 let mut name = None;
1508 let mut args = Vec::new();
1509 for inner in pair.into_inner() {
1510 match inner.as_rule() {
1511 Rule::ident => {
1512 name = Some(inner.as_str().to_string());
1513 }
1514 Rule::expr => {
1515 args.push(parse_expr(inner)?);
1516 }
1517 _ => {}
1518 }
1519 }
1520 Ok(Expr::Call {
1521 name: name.ok_or_else(|| anyhow!("function call requires a name"))?,
1522 args,
1523 })
1524}
1525
1526fn parse_list_literal(pair: Pair<Rule>) -> Result<Expr> {
1527 let mut items = Vec::new();
1528 for inner in pair.into_inner() {
1529 if inner.as_rule() == Rule::expr {
1530 items.push(parse_expr(inner)?);
1531 }
1532 }
1533 Ok(Expr::List(items))
1534}
1535
1536fn parse_map_literal(pair: Pair<Rule>) -> Result<Expr> {
1537 let mut entries = Vec::new();
1538 for inner in pair.into_inner() {
1539 if inner.as_rule() == Rule::map_entry {
1540 let mut key = String::new();
1541 let mut value = None;
1542 for entry_inner in inner.into_inner() {
1543 match entry_inner.as_rule() {
1544 Rule::quoted_string => {
1545 key = parse_quoted_string(entry_inner)?;
1546 }
1547 Rule::bare_word => {
1548 key = entry_inner.as_str().to_string();
1549 }
1550 Rule::expr => {
1551 value = Some(parse_expr(entry_inner)?);
1552 }
1553 _ => {}
1554 }
1555 }
1556 let val = value.ok_or_else(|| anyhow!("map entry missing value"))?;
1557 entries.push((key, val));
1558 }
1559 }
1560 Ok(Expr::Map(entries))
1561}