1use crate::ast::{
2 Arg, Expr, Guard, GuardExpr, IoBinding, IoStream, PipeTarget, PlatformGuard, Step, StepKind,
3 TypeKind,
4};
5use crate::command::ArgType;
6use crate::lexer::{self, RawToken, Rule};
7use anyhow::{Result, anyhow, bail};
8use pest::iterators::Pair;
9use std::collections::VecDeque;
10use std::str::FromStr;
11
12#[derive(Clone)]
13struct ScopeFrame {
14 line_no: usize,
15 had_command: bool,
16}
17
18#[derive(Clone)]
19struct PendingIoBlock {
20 line_no: usize,
21 bindings: Vec<IoBinding>,
22 guards: Option<GuardExpr>,
23}
24
25#[derive(Clone)]
26struct IoScopeFrame {
27 line_no: usize,
28 had_command: bool,
29 bindings: Vec<IoBinding>,
30 guards: Option<GuardExpr>,
31 first_step: usize,
35}
36
37#[derive(Clone, Copy, Debug)]
38enum BlockKind {
39 Guard,
40 Io,
41}
42
43#[derive(Default)]
44struct IoBindingSet {
45 stdin: Option<IoBinding>,
46 stdout: Option<IoBinding>,
47 stderr: Option<IoBinding>,
48}
49
50impl IoBindingSet {
51 fn insert(&mut self, binding: IoBinding) {
52 match binding.stream {
53 IoStream::Stdin => self.stdin = Some(binding),
54 IoStream::Stdout => self.stdout = Some(binding),
55 IoStream::Stderr => self.stderr = Some(binding),
56 }
57 }
58
59 fn into_vec(self) -> Vec<IoBinding> {
60 let mut out = Vec::new();
61 if let Some(binding) = self.stdin {
62 out.push(binding);
63 }
64 if let Some(binding) = self.stdout {
65 out.push(binding);
66 }
67 if let Some(binding) = self.stderr {
68 out.push(binding);
69 }
70 out
71 }
72}
73
74pub struct ScriptParser<'a, F: Fn(&str, Vec<Arg>) -> Result<StepKind>> {
75 tokens: VecDeque<RawToken<'a>>,
76 steps: Vec<Step>,
77 guard_stack: Vec<Option<GuardExpr>>,
78 pending_guards: Option<GuardExpr>,
79 pending_inline_guards: Option<GuardExpr>,
80 pending_can_open_block: bool,
81 pending_scope_enters: usize,
82 scope_stack: Vec<ScopeFrame>,
83 pending_io_block: Option<PendingIoBlock>,
84 io_scope_stack: Vec<IoScopeFrame>,
85 block_stack: Vec<BlockKind>,
86 lower: F,
87}
88
89impl<'a, F: Fn(&str, Vec<Arg>) -> Result<StepKind>> ScriptParser<'a, F> {
90 pub fn new(input: &'a str, lower: F) -> Result<Self> {
91 let tokens = VecDeque::from(lexer::tokenize(input)?);
92 Ok(Self {
93 tokens,
94 steps: Vec::new(),
95 guard_stack: vec![None],
96 pending_guards: None,
97 pending_inline_guards: None,
98 pending_can_open_block: false,
99 pending_scope_enters: 0,
100 scope_stack: Vec::new(),
101 pending_io_block: None,
102 io_scope_stack: Vec::new(),
103 block_stack: Vec::new(),
104 lower,
105 })
106 }
107
108 pub fn parse(mut self) -> Result<Vec<Step>> {
109 while let Some(token) = self.tokens.pop_front() {
110 if self.pending_io_block.is_some()
111 && !matches!(
112 token,
113 RawToken::BlockStart { .. }
114 | RawToken::Command { .. }
115 | RawToken::Instruction { .. }
116 | RawToken::RunExec { .. }
117 )
118 {
119 let pending = self.pending_io_block.take().unwrap();
120 bail!(
121 "line {}: WITH_IO block must be followed by '{{'",
122 pending.line_no
123 );
124 }
125 match token {
126 RawToken::Guard { pair, line_end } => {
127 let groups = parse_guard_line(pair)?;
128 self.handle_guard_token(line_end, groups)?
129 }
130 RawToken::BlockStart { line_no } => self.start_block(line_no)?,
131 RawToken::BlockEnd { line_no } => self.end_block(line_no)?,
132 RawToken::Command { pair, line_no } => {
133 let kind = parse_structural_command_with_lower(pair, &self.lower)?;
134 self.handle_command_token(line_no, kind)?
135 }
136 RawToken::Instruction { pair, line_no } => {
137 let kind = self.lower_instruction(pair)?;
138 self.handle_command_token(line_no, kind)?
139 }
140 RawToken::RunExec { pair, line_no } => {
141 let kind = lower_run_exec_pair(pair, &self.lower)?;
142 self.handle_command_token(line_no, kind)?
143 }
144 }
145 }
146
147 if let Some(pending) = self.pending_io_block.take() {
148 bail!(
149 "line {}: WITH_IO block must be followed by '{{'",
150 pending.line_no
151 );
152 }
153
154 if self.guard_stack.len() != 1 {
155 bail!("unclosed guard block at end of script");
156 }
157 if self.pending_guards.is_some() {
158 bail!("guard declared on final lines without a following command");
159 }
160
161 if let Some(frame) = self.io_scope_stack.last() {
162 bail!(
163 "WITH_IO block starting on line {} was not closed",
164 frame.line_no
165 );
166 }
167
168 {
171 let mut seen_non_prelude = false;
172 let mut inherit_count = 0usize;
173 for step in &self.steps {
174 match &step.kind {
175 StepKind::InheritEnv { .. } => {
176 if seen_non_prelude {
177 bail!("INHERIT_ENV must appear before any other commands");
178 }
179 if step.guard.is_some() || step.scope_enter > 0 || step.scope_exit > 0 {
180 bail!("INHERIT_ENV cannot be guarded or nested inside blocks");
181 }
182 inherit_count += 1;
183 }
184 kind => {
185 if contains_inherit_env(kind) {
186 bail!("INHERIT_ENV cannot be nested inside other commands");
187 }
188 seen_non_prelude = true;
189 }
190 }
191 }
192 if inherit_count > 1 {
193 bail!("only one INHERIT_ENV directive is allowed");
194 }
195 }
196
197 Ok(self.steps)
198 }
199
200 fn lower_instruction(&self, pair: Pair<Rule>) -> Result<StepKind> {
201 lower_instruction_pair(pair, &self.lower)
202 }
203
204 fn handle_guard_token(&mut self, line_end: usize, expr: GuardExpr) -> Result<()> {
205 if let Some(RawToken::Command { line_no, .. }) = self.tokens.front()
206 && *line_no == line_end
207 {
208 self.pending_inline_guards = Some(expr);
209 self.pending_can_open_block = false;
210 return Ok(());
211 }
212 self.stash_pending_guard(expr);
213 self.pending_can_open_block = true;
214 Ok(())
215 }
216
217 fn handle_command_token(&mut self, line_no: usize, kind: StepKind) -> Result<()> {
218 let inline = self.pending_inline_guards.take();
219 self.handle_command(line_no, kind, inline)
220 }
221
222 fn stash_pending_guard(&mut self, guard: GuardExpr) {
223 self.pending_guards = Some(if let Some(existing) = self.pending_guards.take() {
224 GuardExpr::all(vec![existing, guard])
225 } else {
226 guard
227 });
228 }
229
230 fn start_guard_block_from_pending(&mut self, line_no: usize) -> Result<()> {
231 let guards = self
232 .pending_guards
233 .take()
234 .ok_or_else(|| anyhow!("line {}: '{{' without a pending guard", line_no))?;
235 if !self.pending_can_open_block {
236 bail!("line {}: '{{' must directly follow a guard", line_no);
237 }
238 self.pending_can_open_block = false;
239 self.enter_guard_block(guards, line_no)
240 }
241
242 fn enter_guard_block(&mut self, guard: GuardExpr, line_no: usize) -> Result<()> {
243 let composed = if let Some(pending) = self.pending_guards.take() {
244 GuardExpr::all(vec![pending, guard])
245 } else {
246 guard
247 };
248 let parent = self.guard_stack.last().cloned().unwrap_or(None);
249 let next = and_guard_exprs(parent, Some(composed));
250 self.guard_stack.push(next);
251 self.scope_stack.push(ScopeFrame {
252 line_no,
253 had_command: false,
254 });
255 self.pending_scope_enters += 1;
256 Ok(())
257 }
258
259 fn begin_io_block(
260 &mut self,
261 line_no: usize,
262 bindings: Vec<IoBinding>,
263 guards: Option<GuardExpr>,
264 ) -> Result<()> {
265 if self.pending_io_block.is_some() {
266 bail!(
267 "line {}: previous WITH_IO block is still waiting for '{{'",
268 line_no
269 );
270 }
271 self.pending_io_block = Some(PendingIoBlock {
272 line_no,
273 bindings,
274 guards,
275 });
276 Ok(())
277 }
278
279 fn start_block(&mut self, line_no: usize) -> Result<()> {
280 if let Some(pending) = self.pending_io_block.take() {
281 self.block_stack.push(BlockKind::Io);
282 self.io_scope_stack.push(IoScopeFrame {
283 line_no: pending.line_no,
284 had_command: false,
285 bindings: pending.bindings,
286 guards: pending.guards,
287 first_step: self.steps.len(),
288 });
289 Ok(())
290 } else {
291 self.start_guard_block_from_pending(line_no)?;
292 self.block_stack.push(BlockKind::Guard);
293 Ok(())
294 }
295 }
296
297 fn end_block(&mut self, line_no: usize) -> Result<()> {
298 let kind = self
299 .block_stack
300 .pop()
301 .ok_or_else(|| anyhow!("line {}: unexpected '}}'", line_no))?;
302 match kind {
303 BlockKind::Guard => self.end_guard_block(line_no),
304 BlockKind::Io => self.end_io_block(line_no),
305 }
306 }
307
308 fn end_guard_block(&mut self, line_no: usize) -> Result<()> {
309 if self.guard_stack.len() == 1 {
310 bail!("line {}: unexpected '}}'", line_no);
311 }
312 if self.pending_guards.is_some() {
313 bail!(
314 "line {}: guard declared immediately before '}}' without a command",
315 line_no
316 );
317 }
318 let frame = self
319 .scope_stack
320 .last()
321 .cloned()
322 .ok_or_else(|| anyhow!("line {}: scope stack underflow", line_no))?;
323 if !frame.had_command {
324 bail!(
325 "line {}: guard block starting on line {} must contain at least one command",
326 line_no,
327 frame.line_no
328 );
329 }
330 let step = self
331 .steps
332 .last_mut()
333 .ok_or_else(|| anyhow!("line {}: guard block closed without any commands", line_no))?;
334 step.scope_exit += 1;
335 self.scope_stack.pop();
336 self.guard_stack.pop();
337 Ok(())
338 }
339
340 fn end_io_block(&mut self, line_no: usize) -> Result<()> {
341 let frame = self
342 .io_scope_stack
343 .pop()
344 .ok_or_else(|| anyhow!("line {}: unexpected '}}'", line_no))?;
345 if !frame.had_command {
346 bail!(
347 "line {}: WITH_IO block starting on line {} must contain at least one command",
348 line_no,
349 frame.line_no
350 );
351 }
352 if self.steps.len() > frame.first_step {
356 self.steps[frame.first_step].scope_enter += 1;
357 if let Some(last) = self.steps.last_mut() {
358 last.scope_exit += 1;
359 }
360 }
361 Ok(())
362 }
363
364 fn guard_context(&mut self, inline: Option<GuardExpr>) -> Option<GuardExpr> {
365 let mut context = self.guard_stack.last().cloned().unwrap_or(None);
366 if let Some(pending) = self.pending_guards.take() {
367 context = and_guard_exprs(context, Some(pending));
368 self.pending_can_open_block = false;
369 }
370 if let Some(inline_guard) = inline {
371 context = and_guard_exprs(context, Some(inline_guard));
372 }
373 context
374 }
375
376 fn handle_command(
377 &mut self,
378 line_no: usize,
379 kind: StepKind,
380 inline_guards: Option<GuardExpr>,
381 ) -> Result<()> {
382 if let StepKind::WithIoBlock { bindings } = kind {
383 let guards = self.guard_context(inline_guards);
384 self.begin_io_block(line_no, bindings, guards)?;
385 return Ok(());
386 }
387
388 let guards = self.guard_context(inline_guards);
389 let guards = self.apply_io_guards(guards);
390 let scope_enter = self.pending_scope_enters;
391 self.pending_scope_enters = 0;
392 for frame in self.scope_stack.iter_mut() {
393 frame.had_command = true;
394 }
395 for frame in self.io_scope_stack.iter_mut() {
396 frame.had_command = true;
397 }
398 let kind = self.apply_io_defaults(kind);
399 self.steps.push(Step {
400 guard: guards,
401 kind,
402 scope_enter,
403 scope_exit: 0,
404 });
405 Ok(())
406 }
407
408 fn apply_io_defaults(&self, kind: StepKind) -> StepKind {
409 let defaults = self.current_io_defaults();
410 if defaults.is_empty() {
411 return kind;
412 }
413 match kind {
414 StepKind::WithIo { bindings, cmd } => StepKind::WithIo {
415 bindings: merge_bindings(&defaults, &bindings),
416 cmd,
417 },
418 other => StepKind::WithIo {
419 bindings: defaults,
420 cmd: Box::new(other),
421 },
422 }
423 }
424
425 fn current_io_defaults(&self) -> Vec<IoBinding> {
426 if self.io_scope_stack.is_empty() {
427 return Vec::new();
428 }
429 let mut set = IoBindingSet::default();
430 for frame in &self.io_scope_stack {
431 for binding in &frame.bindings {
432 set.insert(binding.clone());
433 }
434 }
435 set.into_vec()
436 }
437
438 fn apply_io_guards(&self, guard: Option<GuardExpr>) -> Option<GuardExpr> {
439 self.io_scope_stack.iter().fold(guard, |acc, frame| {
440 and_guard_exprs(acc, frame.guards.clone())
441 })
442 }
443}
444
445pub fn parse_script(
446 input: &str,
447 lower: impl Fn(&str, Vec<Arg>) -> Result<StepKind>,
448) -> Result<Vec<Step>> {
449 ScriptParser::new(input, lower)?.parse()
450}
451
452pub fn parse_guard_expr_str(input: &str) -> Result<GuardExpr> {
453 use pest::Parser;
454 let pairs = lexer::LanguageParser::parse(Rule::guard_expr, input)
455 .map_err(|e| anyhow!("guard parse error: {e}"))?;
456 let pair = pairs
457 .into_iter()
458 .next()
459 .ok_or_else(|| anyhow!("empty guard"))?;
460 parse_guard_expr(pair)
461}
462
463fn and_guard_exprs(left: Option<GuardExpr>, right: Option<GuardExpr>) -> Option<GuardExpr> {
464 match (left, right) {
465 (None, None) => None,
466 (Some(expr), None) | (None, Some(expr)) => Some(expr),
467 (Some(lhs), Some(rhs)) => Some(GuardExpr::all(vec![lhs, rhs])),
468 }
469}
470
471fn merge_bindings(defaults: &[IoBinding], overrides: &[IoBinding]) -> Vec<IoBinding> {
472 let mut set = IoBindingSet::default();
473 for binding in defaults {
474 set.insert(binding.clone());
475 }
476 for binding in overrides {
477 set.insert(binding.clone());
478 }
479 set.into_vec()
480}
481
482fn contains_inherit_env(kind: &StepKind) -> bool {
483 match kind {
484 StepKind::InheritEnv { .. } => true,
485 StepKind::WithIo { cmd, .. } => contains_inherit_env(cmd),
486 StepKind::AssignCapture { cmd, .. } => contains_inherit_env(cmd),
487 StepKind::While { body, .. } | StepKind::FuncDef { body, .. } => {
488 body.iter().any(|s| contains_inherit_env(&s.kind))
489 }
490 StepKind::Timeout { body, .. } | StepKind::AssignAsync { body, .. } => {
491 body.iter().any(|s| contains_inherit_env(&s.kind))
492 }
493 _ => false,
494 }
495}
496
497fn has_stdout_pipe(bindings: &[IoBinding]) -> bool {
500 bindings
501 .iter()
502 .any(|b| b.stream == IoStream::Stdout && b.pipe.is_some())
503}
504
505fn reject_async_in_capture(kind: &StepKind) -> Result<()> {
508 let bad = match kind {
509 StepKind::AsyncBlock { .. }
510 | StepKind::AssignAsync { .. }
511 | StepKind::Await { .. }
512 | StepKind::AwaitCapture { .. }
513 | StepKind::Cancel { .. } => true,
514 StepKind::WithIo { cmd, .. } => reject_async_in_capture(cmd).is_err(),
515 StepKind::Timeout { body, .. } => body
516 .iter()
517 .any(|s| reject_async_in_capture(&s.kind).is_err()),
518 StepKind::While { body, .. } | StepKind::FuncDef { body, .. } => body
519 .iter()
520 .any(|s| reject_async_in_capture(&s.kind).is_err()),
521 _ => false,
522 };
523 if bad {
524 bail!(
525 "LET capture cannot run ASYNC/AWAIT/CANCEL inline; use LET $t: HANDLE = ASYNC ... then LET $o: STRING = AWAIT $t"
526 );
527 }
528 Ok(())
529}
530
531fn reject_pipe_stdout_in_capture(kind: &StepKind) -> Result<()> {
534 match kind {
535 StepKind::WithIo { bindings, cmd } => {
536 if has_stdout_pipe(bindings) {
537 bail!(
538 "LET capture cannot use WITH_IO [stdout=pipe:...]; the capture sink owns stdout"
539 );
540 }
541 reject_pipe_stdout_in_capture(cmd)
542 }
543 StepKind::Timeout { body, .. } => {
544 for step in body {
545 reject_pipe_stdout_in_capture(&step.kind)?;
546 }
547 Ok(())
548 }
549 StepKind::While { body, .. } | StepKind::FuncDef { body, .. } => {
550 for step in body {
551 reject_pipe_stdout_in_capture(&step.kind)?;
552 }
553 Ok(())
554 }
555 _ => Ok(()),
556 }
557}
558
559fn parse_expr_str(text: &str) -> Result<Expr> {
563 use pest::Parser;
564 let mut pairs = lexer::LanguageParser::parse(Rule::expr, text)
565 .map_err(|e| anyhow!("invalid LET expression {text:?}: {e}"))?;
566 let pair = pairs
567 .next()
568 .ok_or_else(|| anyhow!("LET requires an expression"))?;
569 if pair.as_span().end() != text.len() {
570 bail!("invalid LET expression {text:?}");
571 }
572 parse_expr(pair)
573}
574
575fn parse_structural_command_with_lower(
576 pair: Pair<Rule>,
577 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
578) -> Result<StepKind> {
579 let kind = match pair.as_rule() {
580 Rule::inherit_env_command => {
581 let mut keys = Vec::new();
582 for inner in pair.into_inner() {
583 if inner.as_rule() == Rule::inherit_list {
584 for key in inner.into_inner() {
585 if key.as_rule() == Rule::env_key {
586 keys.push(key.as_str().trim().to_string());
587 }
588 }
589 } else if inner.as_rule() == Rule::env_key {
590 keys.push(inner.as_str().trim().to_string());
591 }
592 }
593 StepKind::InheritEnv { keys }
594 }
595 Rule::with_io_command => {
596 let mut bindings = Vec::new();
597 let mut cmd = None;
598 for inner in pair.into_inner() {
599 match inner.as_rule() {
600 Rule::io_flags => {
601 for flag in inner.into_inner() {
602 if flag.as_rule() == Rule::io_binding {
603 bindings.push(parse_io_binding(flag)?);
604 }
605 }
606 }
607 Rule::with_io_command => {
608 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
609 }
610 Rule::inherit_env_command => {
611 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
612 }
613 Rule::async_statement | Rule::async_statement_block => {
614 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
615 }
616 Rule::timeout_statement | Rule::cancel_statement => {
617 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
618 }
619 Rule::call_statement | Rule::while_statement => {
620 cmd = Some(Box::new(parse_structural_command_with_lower(inner, lower)?));
621 }
622 Rule::func_def
623 | Rule::return_statement
624 | Rule::break_statement
625 | Rule::continue_statement => {
626 bail!(
627 "WITH_IO cannot wrap {:?}; place it around a command or block instead",
628 inner.as_rule()
629 );
630 }
631 Rule::instruction | Rule::instruction_inner => {
632 cmd = Some(Box::new(lower_instruction_pair(inner, lower)?));
633 }
634 Rule::run_exec_statement | Rule::run_exec_inner => {
635 cmd = Some(Box::new(lower_run_exec_pair(inner, lower)?));
636 }
637 _ => {}
638 }
639 }
640 if let Some(cmd) = cmd {
641 StepKind::WithIo { bindings, cmd }
642 } else {
643 StepKind::WithIoBlock { bindings }
644 }
645 }
646 Rule::for_statement => parse_for_statement_from_pair(pair, lower)?,
647 Rule::while_statement => parse_while_statement_from_pair(pair, lower)?,
648 Rule::func_def => parse_func_def_from_pair(pair, lower)?,
649 Rule::call_statement => parse_call_statement_from_pair(pair)?,
650 Rule::return_statement => parse_return_statement_from_pair(pair)?,
651 Rule::break_statement => StepKind::Break,
652 Rule::continue_statement => StepKind::Continue,
653 Rule::let_statement => parse_let_statement_from_pair(pair)?,
654 Rule::mutate_statement => parse_mutate_statement_from_pair(pair)?,
655 Rule::let_async_statement => parse_let_async_statement_from_pair(pair, lower)?,
656 Rule::let_capture_statement => parse_let_capture_statement_from_pair(pair, lower)?,
657 Rule::await_statement => parse_await_statement_from_pair(pair)?,
658 Rule::cancel_statement => parse_cancel_statement_from_pair(pair)?,
659 Rule::if_statement => parse_if_statement_from_pair(pair, lower)?,
660 Rule::async_statement => parse_async_statement_from_pair(pair, lower)?,
661 Rule::async_statement_block => parse_async_statement_block_from_pair(pair, lower)?,
662 Rule::timeout_statement => parse_timeout_statement_from_pair(pair, lower)?,
663 Rule::command_inner => {
664 let inner = pair
667 .into_inner()
668 .next()
669 .ok_or_else(|| anyhow!("empty command_inner"))?;
670 parse_structural_command_with_lower(inner, lower)?
671 }
672 Rule::instruction | Rule::instruction_inner => lower_instruction_pair(pair, lower)?,
673 Rule::run_exec_statement | Rule::run_exec_inner => lower_run_exec_pair(pair, lower)?,
674 _ => bail!("unexpected structural command rule: {:?}", pair.as_rule()),
675 };
676 Ok(kind)
677}
678
679fn extract_instruction(pair: Pair<Rule>) -> Result<(String, Vec<InsToken>)> {
680 let mut name = None;
681 let mut args = Vec::new();
682 for inner in pair.into_inner() {
683 match inner.as_rule() {
684 Rule::command_name => {
685 name = Some(inner.as_str().to_string());
686 }
687 Rule::argument => {
688 args.extend(parse_argument(inner)?.into_iter().map(InsToken::Pos));
689 }
690 Rule::assignment => {
691 let (key, value) = parse_assignment(inner)?;
692 args.push(InsToken::Assign(key, value));
693 }
694 _ => {}
695 }
696 }
697 let name = name.ok_or_else(|| anyhow!("instruction missing command name"))?;
698 Ok((name, args))
699}
700
701enum InsToken {
706 Pos(Arg),
707 Assign(String, Arg),
708}
709
710fn lower_instruction_pair(
715 pair: Pair<Rule>,
716 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
717) -> Result<StepKind> {
718 let (name, tokens) = extract_instruction(pair)?;
719 if name == "ENV" {
720 return lower_env_command(tokens);
721 }
722 if name == "EXPAND" {
723 return lower_expand_command(tokens);
724 }
725 let args = tokens
726 .into_iter()
727 .map(|token| match token {
728 InsToken::Pos(arg) => arg,
729 InsToken::Assign(key, value) => crate::commands::canonical_assignment_arg(&key, &value),
730 })
731 .collect();
732 lower(&name, args)
733}
734
735fn lower_run_exec_pair(
741 pair: Pair<Rule>,
742 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
743) -> Result<StepKind> {
744 let mut list = None;
745 for inner in pair.into_inner() {
746 if inner.as_rule() == Rule::run_exec_list {
747 list = Some(parse_run_exec_list(inner)?);
748 }
749 }
750 let list = list.ok_or_else(|| anyhow!("RUN exec form missing list literal"))?;
751 lower("RUN", vec![Arg::Expr(list)])
752}
753
754fn parse_run_exec_list(pair: Pair<Rule>) -> Result<Expr> {
759 let mut items = Vec::new();
760 for inner in pair.into_inner() {
761 if inner.as_rule() == Rule::run_exec_arg {
762 let item = parse_run_exec_arg(inner)?;
763 reject_boundary(&item)?;
764 items.push(item);
765 }
766 }
767 Ok(Expr::List(items))
768}
769
770fn parse_run_exec_arg(pair: Pair<Rule>) -> Result<Expr> {
771 let inner = pair
772 .into_inner()
773 .next()
774 .ok_or_else(|| anyhow!("RUN exec argument is empty"))?;
775 match inner.as_rule() {
776 Rule::parenthesized_expr => parse_expr_inner(inner.into_inner().next().unwrap()),
777 Rule::func_call => parse_func_call(inner),
778 Rule::key_path => parse_key_path(inner),
779 Rule::variable => {
780 let name = inner.as_str();
781 let name = name.strip_prefix('$').unwrap_or(name).to_string();
782 Ok(Expr::Var(name))
783 }
784 Rule::env_read => parse_env_read(inner).map(Expr::Env),
785 Rule::pipe_read => parse_pipe_read(inner).map(|name| Expr::Literal(Value::Pipe(name))),
786 Rule::list_literal => parse_list_literal(inner),
787 Rule::map_literal => parse_map_literal(inner),
788 Rule::string_literal | Rule::quoted_string => {
789 let s = parse_quoted_string(inner)?;
790 Ok(Expr::Literal(Value::String(s)))
791 }
792 Rule::numeric_literal => parse_numeric_literal(inner),
793 Rule::bare_word => {
794 let s = inner.as_str().to_string();
795 match s.as_str() {
796 "true" => Ok(Expr::Literal(Value::Bool(true))),
797 "false" => Ok(Expr::Literal(Value::Bool(false))),
798 _ => Ok(Expr::Literal(Value::String(s))),
799 }
800 }
801 _ => bail!("unexpected RUN exec argument rule: {:?}", inner.as_rule()),
802 }
803}
804
805fn parse_assignment(pair: Pair<Rule>) -> Result<(String, Arg)> {
807 let mut key = None;
808 let mut value = None;
809 for inner in pair.into_inner() {
810 match inner.as_rule() {
811 Rule::assign_key => {
812 key = Some(inner.as_str().to_string());
813 }
814 Rule::assign_value => {
815 value = Some(lower_command_value(inner)?);
816 }
817 _ => bail!("unexpected assignment rule: {:?}", inner.as_rule()),
818 }
819 }
820 Ok((
821 key.ok_or_else(|| anyhow!("assignment missing key"))?,
822 value.unwrap_or(Arg::String(String::new(), false)),
823 ))
824}
825
826fn lower_command_value(pair: Pair<Rule>) -> Result<Arg> {
831 let inner = pair
832 .into_inner()
833 .next()
834 .ok_or_else(|| anyhow!("assignment value is empty"))?;
835 match inner.as_rule() {
836 Rule::quoted_string => Ok(Arg::String(parse_quoted_string(inner)?, true)),
837 Rule::assign_expr => {
838 let shape = inner
839 .into_inner()
840 .next()
841 .ok_or_else(|| anyhow!("assignment expression is empty"))?;
842 match shape.as_rule() {
843 Rule::variable => Ok(Arg::Expr(Expr::Var(parse_dollar_ident(shape)))),
844 Rule::key_path => Ok(Arg::Expr(parse_key_path(shape)?)),
845 Rule::env_read => Ok(Arg::Expr(Expr::Env(parse_env_read(shape)?))),
846 Rule::func_call => Ok(Arg::Expr(parse_func_call(shape)?)),
847 other => bail!("unexpected assignment expression shape: {:?}", other),
848 }
849 }
850 Rule::raw_fragments => lower_raw_fragments(inner),
851 other => bail!("unexpected assignment value rule: {:?}", other),
852 }
853}
854
855fn lower_raw_fragments(pair: Pair<Rule>) -> Result<Arg> {
861 let mut body = String::new();
862 for fragment in pair.into_inner() {
863 match fragment.as_rule() {
864 Rule::quoted_string => body.push_str(&parse_quoted_string(fragment)?),
865 Rule::templated_arg => body.push_str(fragment.as_str()),
866 Rule::raw_text => body.push_str(&collapse_ws(fragment.as_str())),
867 other => bail!("unexpected raw value fragment: {:?}", other),
868 }
869 }
870 Ok(Arg::String(body.trim().to_string(), false))
871}
872
873fn collapse_ws(s: &str) -> String {
876 let mut out = String::with_capacity(s.len());
877 let mut in_run = false;
878 for c in s.chars() {
879 if c.is_whitespace() {
880 if !in_run {
881 out.push(' ');
882 in_run = true;
883 }
884 } else {
885 out.push(c);
886 in_run = false;
887 }
888 }
889 out
890}
891
892fn lower_env_command(tokens: Vec<InsToken>) -> Result<StepKind> {
896 if tokens.is_empty() {
897 bail!("ENV requires KEY=value");
898 }
899 match tokens.as_slice() {
900 [InsToken::Assign(key, value)] => {
901 ArgType::KeyValue
904 .check_arg(&Arg::String(format!("{key}={}", value.render()), false))?;
905 Ok(StepKind::Env {
906 key: key.clone(),
907 value: value.clone(),
908 })
909 }
910 [InsToken::Pos(Arg::String(text, _))] => match crate::command::split_assignment(text)? {
911 Some((key, value)) => Ok(StepKind::Env { key, value }),
912 None => bail!("ENV requires KEY=value format"),
913 },
914 _ => bail!("ENV requires KEY=value format"),
915 }
916}
917
918fn lower_expand_command(tokens: Vec<InsToken>) -> Result<StepKind> {
922 let mut path = None;
923 let mut overrides = Vec::new();
924 for token in tokens {
925 match token {
926 InsToken::Assign(key, value) => {
927 if key.is_empty() {
928 bail!("EXPAND requires KEY=value format for overrides")
929 }
930 overrides.push((key, value));
931 }
932 InsToken::Pos(arg) => match &arg {
933 Arg::String(text, quoted) if !quoted && text.contains('=') => {
934 let Some((key, value)) = crate::command::split_assignment(text)? else {
935 bail!("EXPAND requires KEY=value format for overrides")
936 };
937 overrides.push((key, value));
938 }
939 _ => {
940 if path.is_none() {
941 ArgType::Path.check_arg(&arg)?;
945 path = Some(arg);
946 } else {
947 bail!("EXPAND accepts at most one path");
948 }
949 }
950 },
951 }
952 }
953 Ok(StepKind::Expand { path, overrides })
954}
955
956fn parse_type_tag(pair: Pair<Rule>) -> Result<TypeKind> {
957 TypeKind::from_str(pair.as_str().trim())
958}
959
960fn check_func_ident(name: &str) -> Result<()> {
961 let ok = name
962 .chars()
963 .next()
964 .map(|c| c.is_ascii_uppercase())
965 .unwrap_or(false)
966 && name
967 .chars()
968 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_');
969 if !ok {
970 bail!("function names must be UPPERCASE (ASCII_ALPHA_UPPER, digits, _), got `{name}`");
971 }
972 Ok(())
973}
974
975fn parse_while_statement_from_pair(
976 pair: Pair<Rule>,
977 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
978) -> Result<StepKind> {
979 let mut cond = None;
980 let mut body = None;
981 for inner in pair.into_inner() {
982 match inner.as_rule() {
983 Rule::expr => {
984 if cond.is_none() {
985 cond = Some(parse_expr(inner)?);
986 }
987 }
988 Rule::block => {
989 body = Some(parse_block_elements_with_lower(inner, lower)?);
990 }
991 _ => {}
992 }
993 }
994 Ok(StepKind::While {
995 cond: Box::new(cond.ok_or_else(|| anyhow!("WHILE requires a condition"))?),
996 body: body.ok_or_else(|| anyhow!("WHILE requires a block"))?,
997 })
998}
999
1000fn parse_func_def_from_pair(
1001 pair: Pair<Rule>,
1002 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1003) -> Result<StepKind> {
1004 let mut name: Option<String> = None;
1005 let mut param_names: Vec<String> = Vec::new();
1006 let mut param_types: Vec<TypeKind> = Vec::new();
1007 let mut body = None;
1008 for inner in pair.into_inner() {
1009 match inner.as_rule() {
1010 Rule::func_ident => {
1011 if name.is_none() {
1012 name = Some(inner.as_str().to_string());
1013 }
1014 }
1015 Rule::func_param => {
1016 let mut pname = None;
1017 let mut ptype = None;
1018 for part in inner.into_inner() {
1019 match part.as_rule() {
1020 Rule::dollar_ident => {
1021 pname = Some(parse_dollar_ident(part));
1022 }
1023 Rule::type_tag => {
1024 ptype = Some(parse_type_tag(part)?);
1025 }
1026 _ => {}
1027 }
1028 }
1029 param_names
1030 .push(pname.ok_or_else(|| anyhow!("FUNC parameter requires a $variable"))?);
1031 param_types.push(ptype.ok_or_else(|| {
1032 anyhow!("FUNC parameters require explicit types: FUNC NAME($p: TYPE, ...)")
1033 })?);
1034 }
1035 Rule::block => {
1036 body = Some(parse_block_elements_with_lower(inner, lower)?);
1037 }
1038 _ => {}
1039 }
1040 }
1041 let name = name.ok_or_else(|| anyhow!("FUNC requires a name"))?;
1042 check_func_ident(&name)?;
1043 if param_names.len() != param_types.len() {
1044 bail!("FUNC {name} has mismatched parameter names and types");
1045 }
1046 let mut seen = std::collections::HashSet::new();
1047 for pname in ¶m_names {
1048 if !seen.insert(pname.clone()) {
1049 bail!("FUNC {name} declares duplicate parameter ${pname}");
1050 }
1051 }
1052 Ok(StepKind::FuncDef {
1053 name,
1054 params: param_names.into_iter().zip(param_types).collect(),
1055 body: body.ok_or_else(|| anyhow!("FUNC requires a block"))?,
1056 })
1057}
1058
1059fn parse_call_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
1060 let mut name: Option<String> = None;
1061 let mut args = Vec::new();
1062 for inner in pair.into_inner() {
1063 match inner.as_rule() {
1064 Rule::func_ident => {
1065 if name.is_none() {
1066 name = Some(inner.as_str().to_string());
1067 }
1068 }
1069 Rule::expr => {
1070 args.push(parse_expr(inner)?);
1071 }
1072 _ => {}
1073 }
1074 }
1075 let name = name.ok_or_else(|| anyhow!("CALL requires a function name"))?;
1076 check_func_ident(&name)?;
1077 Ok(StepKind::Call { name, args })
1078}
1079
1080fn parse_return_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
1081 use crate::ast::Value;
1082 for inner in pair.into_inner() {
1083 if inner.as_rule() == Rule::expr {
1084 return Ok(StepKind::Return {
1085 expr: Box::new(parse_expr(inner)?),
1086 });
1087 }
1088 }
1089 Ok(StepKind::Return {
1090 expr: Box::new(Expr::Literal(Value::String(String::new()))),
1091 })
1092}
1093
1094fn parse_for_statement_from_pair(
1095 pair: Pair<Rule>,
1096 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1097) -> Result<StepKind> {
1098 let mut idents: Vec<String> = Vec::new();
1099 let mut types: Vec<TypeKind> = Vec::new();
1100 let mut in_expr = None;
1101 let mut body_steps = Vec::new();
1102 for inner in pair.into_inner() {
1103 match inner.as_rule() {
1104 Rule::dollar_ident => {
1105 idents.push(parse_dollar_ident(inner));
1106 }
1107 Rule::type_tag => {
1108 types.push(parse_type_tag(inner)?);
1109 }
1110 Rule::expr => {
1111 in_expr = Some(parse_expr(inner)?);
1112 }
1113 Rule::block => {
1114 body_steps = parse_block_elements_with_lower(inner, lower)?;
1115 }
1116 _ => {}
1117 }
1118 }
1119 if idents.len() != types.len() {
1120 bail!(
1121 "FOR requires explicit types: FOR $item: TYPE IN <expr> (got {} vars, {} types)",
1122 idents.len(),
1123 types.len()
1124 );
1125 }
1126 let (key_var, key_type, var, var_type) = match idents.len() {
1127 1 => (
1128 None,
1129 None,
1130 idents.into_iter().next().unwrap(),
1131 types.into_iter().next().unwrap(),
1132 ),
1133 2 => {
1134 let mut iv = idents.into_iter();
1135 let mut tv = types.into_iter();
1136 (
1137 Some(iv.next().unwrap()),
1138 Some(tv.next().unwrap()),
1139 iv.next().unwrap(),
1140 tv.next().unwrap(),
1141 )
1142 }
1143 _ => bail!("FOR requires one or two variables"),
1144 };
1145 if let Some(kt) = &key_type
1146 && *kt != TypeKind::String
1147 && *kt != TypeKind::Int
1148 {
1149 bail!("FOR key variable must be INT or STRING, got {kt}");
1150 }
1151 Ok(StepKind::For {
1152 key_var,
1153 key_type,
1154 var,
1155 var_type,
1156 in_expr: in_expr.ok_or_else(|| anyhow!("FOR requires an iterable expression"))?,
1157 body: body_steps,
1158 })
1159}
1160
1161fn parse_let_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
1162 let mut var = None;
1163 let mut decl_type = None;
1164 let mut expr = None;
1165 for inner in pair.into_inner() {
1166 match inner.as_rule() {
1167 Rule::dollar_ident => {
1168 var = Some(parse_dollar_ident(inner));
1169 }
1170 Rule::type_tag => {
1171 decl_type = Some(parse_type_tag(inner)?);
1172 }
1173 Rule::expr => {
1174 expr = Some(parse_expr(inner)?);
1175 }
1176 _ => {}
1177 }
1178 }
1179 Ok(StepKind::Assign {
1180 var: var.ok_or_else(|| anyhow!("LET requires a variable"))?,
1181 decl_type: decl_type
1182 .ok_or_else(|| anyhow!("LET requires explicit type: LET $var: TYPE = <expr>"))?,
1183 expr: expr.ok_or_else(|| anyhow!("LET requires an expression"))?,
1184 })
1185}
1186
1187fn parse_mutate_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
1188 let mut var = None;
1189 let mut expr = None;
1190 for inner in pair.into_inner() {
1191 match inner.as_rule() {
1192 Rule::dollar_ident => {
1193 var = Some(parse_dollar_ident(inner));
1194 }
1195 Rule::expr => {
1196 expr = Some(parse_expr(inner)?);
1197 }
1198 _ => {}
1199 }
1200 }
1201 Ok(StepKind::Set {
1202 var: var.ok_or_else(|| anyhow!("mutation requires a variable: $var = <expr>"))?,
1203 expr: expr.ok_or_else(|| anyhow!("mutation requires an expression: $var = <expr>"))?,
1204 })
1205}
1206
1207fn parse_let_async_statement_from_pair(
1208 pair: Pair<Rule>,
1209 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1210) -> Result<StepKind> {
1211 let mut var = None;
1212 let mut decl_type: Option<TypeKind> = None;
1213 let mut body = None;
1214 for inner in pair.into_inner() {
1215 match inner.as_rule() {
1216 Rule::dollar_ident => {
1217 var = Some(parse_dollar_ident(inner));
1218 }
1219 Rule::type_tag => {
1220 decl_type = Some(parse_type_tag(inner)?);
1221 }
1222 Rule::block => {
1223 body = Some(parse_block_elements_with_lower(inner, lower)?);
1224 }
1225 Rule::command_inner => {
1226 let inner = inner
1229 .into_inner()
1230 .next()
1231 .ok_or_else(|| anyhow!("empty command_inner"))?;
1232 let step_kind = parse_structural_command_with_lower(inner, lower)?;
1233 body = Some(vec![Step {
1234 guard: None,
1235 kind: step_kind,
1236 scope_enter: 0,
1237 scope_exit: 0,
1238 }]);
1239 }
1240 Rule::with_io_command => {
1241 let kind = parse_structural_command_with_lower(inner, lower)?;
1251 let StepKind::WithIo { bindings, cmd } = kind else {
1252 bail!(
1253 "LET $var: TYPE = WITH_IO requires an ASYNC command (e.g. LET $t = WITH_IO [stdin=pipe:p] ASYNC WRITE \"f\")"
1254 );
1255 };
1256 match *cmd {
1257 StepKind::AsyncBlock { body: async_body } => {
1258 if async_body.len() != 1 {
1259 bail!(
1260 "LET $var: TYPE = WITH_IO [..] ASYNC accepts a single command; use LET $var: HANDLE = ASYNC {{ ... }} with WITH_IO inside the block for multi-step tasks"
1261 );
1262 }
1263 let step = async_body
1264 .into_iter()
1265 .next()
1266 .ok_or_else(|| anyhow!("LET $var: HANDLE = ASYNC requires a body"))?;
1267 body = Some(vec![Step {
1268 guard: step.guard,
1269 kind: StepKind::WithIo {
1270 bindings,
1271 cmd: Box::new(step.kind),
1272 },
1273 scope_enter: step.scope_enter,
1274 scope_exit: step.scope_exit,
1275 }]);
1276 }
1277 sync_cmd => {
1278 if has_stdout_pipe(&bindings) {
1279 bail!(
1280 "LET capture cannot use WITH_IO [stdout=pipe:...]; the capture sink owns stdout"
1281 );
1282 }
1283 reject_async_in_capture(&sync_cmd)?;
1284 let name = var.clone().ok_or_else(|| {
1285 anyhow!("LET $var: TYPE = WITH_IO requires a variable")
1286 })?;
1287 let dtype = decl_type.ok_or_else(|| {
1288 anyhow!("LET requires explicit type: LET $var: TYPE = ...")
1289 })?;
1290 return Ok(StepKind::AssignCapture {
1291 var: name,
1292 decl_type: dtype,
1293 cmd: Box::new(StepKind::WithIo {
1294 bindings,
1295 cmd: Box::new(sync_cmd),
1296 }),
1297 });
1298 }
1299 }
1300 }
1301 _ => {}
1302 }
1303 }
1304 Ok(StepKind::AssignAsync {
1305 var: var.ok_or_else(|| anyhow!("LET $var: HANDLE = ASYNC requires a variable"))?,
1306 decl_type: decl_type
1307 .ok_or_else(|| anyhow!("LET requires explicit type: LET $var: TYPE = ..."))?,
1308 body: body.ok_or_else(|| anyhow!("LET $var: HANDLE = ASYNC requires a body"))?,
1309 })
1310}
1311
1312fn parse_let_capture_statement_from_pair(
1320 pair: Pair<Rule>,
1321 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1322) -> Result<StepKind> {
1323 use pest::Parser;
1324 let mut var = None;
1325 let mut decl_type: Option<TypeKind> = None;
1326 let mut await_pair = None;
1327 let mut timeout_pair = None;
1328 let mut call_pair = None;
1329 let mut instruction_pair = None;
1330 for inner in pair.into_inner() {
1331 match inner.as_rule() {
1332 Rule::dollar_ident => {
1333 var = Some(parse_dollar_ident(inner));
1334 }
1335 Rule::type_tag => {
1336 decl_type = Some(parse_type_tag(inner)?);
1337 }
1338 Rule::await_statement => {
1339 await_pair = Some(inner);
1340 }
1341 Rule::timeout_statement => {
1342 timeout_pair = Some(inner);
1343 }
1344 Rule::call_statement => {
1345 call_pair = Some(inner);
1346 }
1347 Rule::instruction => {
1348 instruction_pair = Some(inner);
1349 }
1350 _ => {}
1351 }
1352 }
1353 let var = var.ok_or_else(|| anyhow!("LET requires a variable"))?;
1354 let dtype: TypeKind =
1355 decl_type.ok_or_else(|| anyhow!("LET requires explicit type: LET $var: TYPE = ..."))?;
1356 if let Some(awaited) = await_pair {
1357 let mut task_var = None;
1358 for inner in awaited.into_inner() {
1359 if inner.as_rule() == Rule::ident {
1360 task_var = Some(inner.as_str().to_string());
1361 }
1362 }
1363 return Ok(StepKind::AwaitCapture {
1364 out_var: var,
1365 out_type: dtype,
1366 task_var: task_var
1367 .ok_or_else(|| anyhow!("LET $out = AWAIT requires a task variable"))?,
1368 });
1369 }
1370 if let Some(timeouted) = timeout_pair {
1371 let kind = parse_structural_command_with_lower(timeouted, lower)?;
1372 reject_async_in_capture(&kind)?;
1373 reject_pipe_stdout_in_capture(&kind)?;
1374 return Ok(StepKind::AssignCapture {
1375 var,
1376 decl_type: dtype,
1377 cmd: Box::new(kind),
1378 });
1379 }
1380 if let Some(called) = call_pair {
1381 let kind = parse_call_statement_from_pair(called)?;
1382 reject_async_in_capture(&kind)?;
1383 reject_pipe_stdout_in_capture(&kind)?;
1384 return Ok(StepKind::AssignCapture {
1385 var,
1386 decl_type: dtype,
1387 cmd: Box::new(kind),
1388 });
1389 }
1390 if let Some(ins) = instruction_pair {
1391 let text = ins.as_str().to_string();
1392 let mut lead = None;
1393 for token in ins.into_inner() {
1394 if token.as_rule() == Rule::command_name {
1395 lead = Some(token.as_str().to_string());
1396 break;
1397 }
1398 }
1399 let lead = lead.ok_or_else(|| anyhow!("LET capture requires a command"))?;
1400 if crate::commands::is_known_command(&lead) {
1401 let kind = lower_instruction_pair(
1402 lexer::LanguageParser::parse(Rule::instruction, &text)
1403 .map_err(|e| anyhow!("invalid LET capture {text:?}: {e}"))?
1404 .next()
1405 .ok_or_else(|| anyhow!("LET capture requires a command"))?,
1406 lower,
1407 )?;
1408 reject_async_in_capture(&kind)?;
1409 reject_pipe_stdout_in_capture(&kind)?;
1410 return Ok(StepKind::AssignCapture {
1411 var,
1412 decl_type: dtype,
1413 cmd: Box::new(kind),
1414 });
1415 }
1416 let expr = parse_expr_str(&text)?;
1417 return Ok(StepKind::Assign {
1418 var,
1419 decl_type: dtype,
1420 expr,
1421 });
1422 }
1423 bail!("LET requires a value")
1424}
1425
1426fn parse_await_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
1427 let mut var = None;
1428 for inner in pair.into_inner() {
1429 if inner.as_rule() == Rule::ident {
1430 var = Some(inner.as_str().to_string());
1431 }
1432 }
1433 Ok(StepKind::Await {
1434 var: var.ok_or_else(|| anyhow!("AWAIT requires a variable"))?,
1435 })
1436}
1437
1438fn parse_cancel_statement_from_pair(pair: Pair<Rule>) -> Result<StepKind> {
1439 let mut var = None;
1440 for inner in pair.into_inner() {
1441 if inner.as_rule() == Rule::ident {
1442 var = Some(inner.as_str().to_string());
1443 }
1444 }
1445 Ok(StepKind::Cancel {
1446 var: var.ok_or_else(|| anyhow!("CANCEL requires a variable"))?,
1447 })
1448}
1449
1450fn parse_timeout_duration_arg(pair: Pair<Rule>) -> Result<Arg> {
1454 for inner in pair.into_inner() {
1455 let arg = match inner.as_rule() {
1456 Rule::timeout_literal => Arg::String(inner.as_str().to_string(), false),
1457 Rule::dollar_ident => Arg::Expr(Expr::Var(parse_dollar_ident(inner))),
1458 Rule::quoted_string => Arg::String(
1459 crate::command::strip_surrounding_quotes(inner.as_str()).to_string(),
1460 true,
1461 ),
1462 Rule::templated_arg => Arg::String(inner.as_str().to_string(), false),
1463 _ => continue,
1464 };
1465 ArgType::Duration.check_arg(&arg)?;
1466 return Ok(arg);
1467 }
1468 bail!("TIMEOUT requires a duration")
1469}
1470
1471fn parse_timeout_statement_from_pair(
1472 pair: Pair<Rule>,
1473 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1474) -> Result<StepKind> {
1475 let mut duration: Option<Arg> = None;
1476 let mut body: Option<Vec<Step>> = None;
1477 for inner in pair.into_inner() {
1478 match inner.as_rule() {
1479 Rule::timeout_duration => {
1480 duration = Some(parse_timeout_duration_arg(inner)?);
1481 }
1482 Rule::block => {
1483 body = Some(parse_block_elements_with_lower(inner, lower)?);
1484 }
1485 Rule::await_statement => {
1486 let kind = parse_await_statement_from_pair(inner)?;
1487 body = Some(vec![Step {
1488 guard: None,
1489 kind,
1490 scope_enter: 0,
1491 scope_exit: 0,
1492 }]);
1493 }
1494 Rule::cancel_statement => {
1495 let kind = parse_cancel_statement_from_pair(inner)?;
1496 body = Some(vec![Step {
1497 guard: None,
1498 kind,
1499 scope_enter: 0,
1500 scope_exit: 0,
1501 }]);
1502 }
1503 Rule::with_io_command
1504 | Rule::inherit_env_command
1505 | Rule::async_statement
1506 | Rule::async_statement_block
1507 | Rule::call_statement
1508 | Rule::while_statement
1509 | Rule::func_def
1510 | Rule::return_statement
1511 | Rule::break_statement
1512 | Rule::continue_statement
1513 | Rule::timeout_statement => {
1514 let kind = parse_structural_command_with_lower(inner, lower)?;
1515 body = Some(vec![Step {
1516 guard: None,
1517 kind,
1518 scope_enter: 0,
1519 scope_exit: 0,
1520 }]);
1521 }
1522 Rule::instruction | Rule::instruction_inner => {
1523 let kind = lower_instruction_pair(inner, lower)?;
1524 body = Some(vec![Step {
1525 guard: None,
1526 kind,
1527 scope_enter: 0,
1528 scope_exit: 0,
1529 }]);
1530 }
1531 Rule::run_exec_statement | Rule::run_exec_inner => {
1532 let kind = lower_run_exec_pair(inner, lower)?;
1533 body = Some(vec![Step {
1534 guard: None,
1535 kind,
1536 scope_enter: 0,
1537 scope_exit: 0,
1538 }]);
1539 }
1540 _ => {}
1541 }
1542 }
1543 Ok(StepKind::Timeout {
1544 duration: duration.ok_or_else(|| anyhow!("TIMEOUT requires a duration"))?,
1545 body: body.ok_or_else(|| anyhow!("TIMEOUT requires a command or block"))?,
1546 })
1547}
1548
1549fn parse_if_statement_from_pair(
1550 pair: Pair<Rule>,
1551 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1552) -> Result<StepKind> {
1553 let mut cond = None;
1554 let mut then_body = Vec::new();
1555 let mut else_ifs = Vec::new();
1556 let mut else_body = None;
1557
1558 for inner in pair.into_inner() {
1559 match inner.as_rule() {
1560 Rule::expr => {
1561 if cond.is_none() {
1562 cond = Some(parse_expr(inner)?);
1563 }
1564 }
1565 Rule::block => {
1566 if then_body.is_empty() {
1567 then_body = parse_block_elements_with_lower(inner, lower)?;
1568 }
1569 }
1570 Rule::else_if_clause => {
1571 let (eif_cond, eif_body) = parse_else_if_clause(inner, lower)?;
1572 else_ifs.push((eif_cond, eif_body));
1573 }
1574 Rule::else_clause => {
1575 else_body = Some(parse_else_clause(inner, lower)?);
1576 }
1577 _ => {}
1578 }
1579 }
1580 Ok(StepKind::If {
1581 cond: Box::new(cond.ok_or_else(|| anyhow!("IF requires a condition"))?),
1582 then_body,
1583 else_ifs,
1584 else_body,
1585 })
1586}
1587
1588fn parse_else_if_clause(
1589 pair: Pair<Rule>,
1590 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1591) -> Result<(Box<Expr>, Vec<Step>)> {
1592 let mut cond = None;
1593 let mut body = Vec::new();
1594 for inner in pair.into_inner() {
1595 match inner.as_rule() {
1596 Rule::expr => cond = Some(parse_expr(inner)?),
1597 Rule::block => body = parse_block_elements_with_lower(inner, lower)?,
1598 _ => {}
1599 }
1600 }
1601 Ok((
1602 Box::new(cond.ok_or_else(|| anyhow!("ELSE IF requires a condition"))?),
1603 body,
1604 ))
1605}
1606
1607fn parse_else_clause(
1608 pair: Pair<Rule>,
1609 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1610) -> Result<Vec<Step>> {
1611 for inner in pair.into_inner() {
1612 if let Rule::block = inner.as_rule() {
1613 return parse_block_elements_with_lower(inner, lower);
1614 }
1615 }
1616 Ok(Vec::new())
1617}
1618
1619fn parse_async_statement_from_pair(
1620 pair: Pair<Rule>,
1621 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1622) -> Result<StepKind> {
1623 let mut inner_cmd = None;
1624 let mut block_body = None;
1625 for inner in pair.into_inner() {
1626 match inner.as_rule() {
1627 Rule::command => {
1628 let cmd_text = inner.as_str();
1632 let steps = parse_script(cmd_text, |name, args| lower(name, args))?;
1633 if steps.len() == 1 {
1634 inner_cmd = Some(steps.into_iter().next().unwrap().kind);
1635 } else {
1636 bail!("unexpected multiple steps in async inner command");
1637 }
1638 }
1639 Rule::command_inner => {
1640 let child = inner
1642 .into_inner()
1643 .next()
1644 .ok_or_else(|| anyhow!("empty command_inner"))?;
1645 match child.as_rule() {
1646 Rule::inherit_env_command => {
1647 inner_cmd = Some(parse_structural_command_with_lower(child, lower)?);
1648 }
1649 Rule::async_statement | Rule::async_statement_block => {
1650 inner_cmd = Some(parse_structural_command_with_lower(child, lower)?);
1651 }
1652 Rule::timeout_statement | Rule::cancel_statement => {
1653 inner_cmd = Some(parse_structural_command_with_lower(child, lower)?);
1654 }
1655 Rule::call_statement | Rule::while_statement => {
1656 inner_cmd = Some(parse_structural_command_with_lower(child, lower)?);
1657 }
1658 Rule::func_def
1659 | Rule::return_statement
1660 | Rule::break_statement
1661 | Rule::continue_statement => {
1662 bail!(
1663 "{:?} cannot run as a lone ASYNC command; use ASYNC {{ ... }} block form if needed",
1664 child.as_rule()
1665 );
1666 }
1667 Rule::instruction => {
1668 inner_cmd = Some(lower_instruction_pair(child, lower)?);
1669 }
1670 Rule::run_exec_statement | Rule::run_exec_inner => {
1671 inner_cmd = Some(lower_run_exec_pair(child, lower)?);
1672 }
1673 other => bail!("unexpected command_inner child: {:?}", other),
1674 }
1675 }
1676 Rule::instruction | Rule::instruction_inner => {
1677 inner_cmd = Some(lower_instruction_pair(inner, lower)?);
1678 }
1679 Rule::run_exec_statement | Rule::run_exec_inner => {
1680 inner_cmd = Some(lower_run_exec_pair(inner, lower)?);
1681 }
1682 Rule::block => {
1683 block_body = Some(parse_block_elements_with_lower(inner, lower)?);
1684 }
1685 _ => {}
1686 }
1687 }
1688 if let Some(body) = block_body {
1689 for step in &body {
1690 if matches!(&step.kind, StepKind::WithIo { .. }) {
1691 bail!(
1692 "WITH_IO cannot be placed inside ASYNC. Place WITH_IO outside ASYNC instead (e.g. WITH_IO [...] ASYNC RUN ...)"
1693 );
1694 }
1695 }
1696 Ok(StepKind::AsyncBlock { body })
1697 } else if let Some(cmd) = inner_cmd {
1698 if matches!(&cmd, StepKind::WithIo { .. }) {
1699 bail!(
1700 "WITH_IO cannot be placed inside ASYNC. Place WITH_IO outside ASYNC instead (e.g. WITH_IO [...] ASYNC RUN ...)"
1701 );
1702 }
1703 Ok(StepKind::AsyncBlock {
1704 body: vec![Step {
1705 guard: None,
1706 kind: cmd,
1707 scope_enter: 0,
1708 scope_exit: 0,
1709 }],
1710 })
1711 } else {
1712 bail!("ASYNC requires either a command or a block");
1713 }
1714}
1715
1716fn parse_async_statement_block_from_pair(
1717 pair: Pair<Rule>,
1718 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1719) -> Result<StepKind> {
1720 let mut block_body = None;
1721 for inner in pair.into_inner() {
1722 if inner.as_rule() == Rule::block {
1723 block_body = Some(parse_block_elements_with_lower(inner, lower)?);
1724 }
1725 }
1726 let body = block_body.ok_or_else(|| anyhow!("async_statement_block requires a block"))?;
1727 for step in &body {
1728 if matches!(&step.kind, StepKind::WithIo { .. }) {
1729 bail!(
1730 "WITH_IO cannot be placed inside ASYNC. Place WITH_IO outside ASYNC instead (e.g. WITH_IO [...] ASYNC RUN ...)"
1731 );
1732 }
1733 }
1734 Ok(StepKind::AsyncBlock { body })
1735}
1736
1737fn parse_block_elements_with_lower(
1738 block_pair: Pair<Rule>,
1739 lower: &dyn Fn(&str, Vec<Arg>) -> Result<StepKind>,
1740) -> Result<Vec<Step>> {
1741 let mut steps = Vec::new();
1742 for elem in block_pair.into_inner() {
1743 match elem.as_rule() {
1744 Rule::for_statement
1745 | Rule::while_statement
1746 | Rule::func_def
1747 | Rule::call_statement
1748 | Rule::return_statement
1749 | Rule::break_statement
1750 | Rule::continue_statement
1751 | Rule::let_statement
1752 | Rule::mutate_statement
1753 | Rule::let_async_statement
1754 | Rule::let_capture_statement
1755 | Rule::await_statement
1756 | Rule::cancel_statement
1757 | Rule::if_statement
1758 | Rule::async_statement
1759 | Rule::timeout_statement
1760 | Rule::async_statement_block => {
1761 let step_kind = parse_structural_command_with_lower(elem, lower)?;
1762 steps.push(Step {
1763 guard: None,
1764 kind: step_kind,
1765 scope_enter: 0,
1766 scope_exit: 0,
1767 });
1768 }
1769 Rule::guard_block => {
1770 let mut guard_pair = None;
1771 let mut inner_block = None;
1772 for inner in elem.into_inner() {
1773 match inner.as_rule() {
1774 Rule::guard_line => guard_pair = Some(inner),
1775 Rule::block => inner_block = Some(inner),
1776 _ => {}
1777 }
1778 }
1779 if let (Some(gp), Some(bp)) = (guard_pair, inner_block) {
1780 let guard_expr = parse_guard_line(gp)?;
1781 let mut inner_steps = parse_block_elements_with_lower(bp, lower)?;
1782 for step in &mut inner_steps {
1783 step.guard = Some(guard_expr.clone());
1784 }
1785 steps.extend(inner_steps);
1786 }
1787 }
1788 Rule::instruction | Rule::instruction_inner => {
1789 let kind = lower_instruction_pair(elem, lower)?;
1790 steps.push(Step {
1791 guard: None,
1792 kind,
1793 scope_enter: 0,
1794 scope_exit: 0,
1795 });
1796 }
1797 Rule::run_exec_statement | Rule::run_exec_inner => {
1798 let kind = lower_run_exec_pair(elem, lower)?;
1799 steps.push(Step {
1800 guard: None,
1801 kind,
1802 scope_enter: 0,
1803 scope_exit: 0,
1804 });
1805 }
1806 Rule::with_io_command => {
1807 let step_kind = parse_structural_command_with_lower(elem, lower)?;
1808 steps.push(Step {
1809 guard: None,
1810 kind: step_kind,
1811 scope_enter: 0,
1812 scope_exit: 0,
1813 });
1814 }
1815 _ => {} }
1817 }
1818 Ok(steps)
1819}
1820
1821fn parse_argument(pair: Pair<Rule>) -> Result<Vec<Arg>> {
1822 let inners: Vec<_> = pair.into_inner().collect();
1823 let mut groups: Vec<Vec<Pair<Rule>>> = vec![Vec::new()];
1829 for fragment in inners {
1830 let glued = fragment.as_rule() == Rule::expr
1831 && fragment.as_str().ends_with(|c: char| c.is_whitespace());
1832 groups
1833 .last_mut()
1834 .expect("argument always holds a group")
1835 .push(fragment);
1836 if glued {
1837 groups.push(Vec::new());
1838 }
1839 }
1840 let mut args = Vec::new();
1841 for group in groups {
1842 if group.is_empty() {
1843 continue;
1844 }
1845 if group.len() == 1 && group[0].as_rule() == Rule::expr {
1847 args.push(Arg::Expr(parse_expr(
1848 group.into_iter().next().expect("group holds one pair"),
1849 )?));
1850 continue;
1851 }
1852 if group.len() == 1 && group[0].as_rule() == Rule::string_literal {
1854 args.push(Arg::String(parse_fragments(&group)?, true));
1855 continue;
1856 }
1857 args.push(Arg::String(parse_fragments(&group)?, false));
1858 }
1859 Ok(args)
1860}
1861
1862fn parse_quoted_string(pair: Pair<Rule>) -> Result<String> {
1863 let s = pair.as_str();
1864 let content = &s[1..s.len() - 1];
1865 Ok(content.to_string())
1867}
1868
1869fn parse_fragments(parts: &[Pair<Rule>]) -> Result<String> {
1873 if parts.len() == 1 && parts[0].as_rule() == Rule::string_literal {
1875 let s = parts[0].as_str();
1876 return Ok(s[1..s.len() - 1].to_string());
1877 }
1878
1879 let mut body = String::new();
1880 let mut last_end = None;
1881 for part in parts {
1882 let span = part.as_span();
1883 if let Some(end) = last_end
1884 && span.start() > end
1885 {
1886 body.push(' ');
1887 }
1888 match part.as_rule() {
1889 Rule::string_literal => {
1890 let s = part.as_str();
1891 let unquoted = &s[1..s.len() - 1];
1892 body.push_str(unquoted);
1893 }
1894 Rule::templated_arg | Rule::unquoted_arg => {
1895 body.push_str(part.as_str());
1896 }
1897 Rule::expr => body.push_str(part.as_str()),
1898 _ => {}
1899 }
1900 last_end = Some(span.end());
1901 }
1902 Ok(body)
1903}
1904
1905fn parse_guard_line(pair: Pair<Rule>) -> Result<GuardExpr> {
1906 for inner in pair.into_inner() {
1907 if inner.as_rule() == Rule::guard_expr {
1908 return parse_guard_expr(inner);
1909 }
1910 }
1911 bail!("guard line missing expression")
1912}
1913
1914fn parse_io_binding(pair: Pair<Rule>) -> Result<IoBinding> {
1915 let mut stream = None;
1916 let mut pipe = None;
1917 for inner in pair.into_inner() {
1918 match inner.as_rule() {
1919 Rule::io_stream => stream = Some(parse_io_stream(inner.as_str())),
1920 Rule::pipe_binding => pipe = Some(parse_pipe_binding(inner)?),
1921 _ => {}
1922 }
1923 }
1924 let stream = stream.ok_or_else(|| anyhow!("missing IO stream in WITH_IO"))?;
1925 Ok(IoBinding { stream, pipe })
1926}
1927
1928fn parse_io_stream(text: &str) -> IoStream {
1929 match text {
1930 "stdin" => IoStream::Stdin,
1931 "stdout" => IoStream::Stdout,
1932 "stderr" => IoStream::Stderr,
1933 _ => unreachable!("parser produced invalid io_stream token"),
1934 }
1935}
1936
1937fn parse_pipe_binding(pair: Pair<Rule>) -> Result<PipeTarget> {
1938 for inner in pair.into_inner() {
1939 match inner.as_rule() {
1940 Rule::pipe_name => return Ok(PipeTarget::Name(inner.as_str().to_string())),
1941 Rule::dollar_ident => {
1942 return Ok(PipeTarget::Var(parse_dollar_ident(inner)));
1943 }
1944 _ => {}
1945 }
1946 }
1947 bail!("missing pipe identifier in WITH_IO binding");
1948}
1949
1950fn parse_guard_expr(pair: Pair<Rule>) -> Result<GuardExpr> {
1951 match pair.as_rule() {
1952 Rule::guard_expr => {
1953 let next = pair
1954 .into_inner()
1955 .next()
1956 .ok_or_else(|| anyhow!("guard expression missing body"))?;
1957 parse_guard_expr(next)
1958 }
1959 Rule::guard_seq => parse_guard_seq(pair),
1960 Rule::guard_factor => parse_guard_factor(pair),
1961 Rule::guard_not => {
1962 bail!("guard_not should not create a pair")
1964 }
1965 Rule::guard_primary => parse_guard_primary(pair),
1966 Rule::guard_group => parse_guard_group(pair),
1967 Rule::guard_any_call => parse_guard_any_call(pair),
1968 Rule::guard_all_call => parse_guard_all_call(pair),
1969 Rule::not_call => parse_not_call(pair),
1970 Rule::guard_term => parse_guard_term(pair),
1971 _ => bail!("unexpected guard expression rule: {:?}", pair.as_rule()),
1972 }
1973}
1974
1975fn parse_guard_seq(pair: Pair<Rule>) -> Result<GuardExpr> {
1976 let mut exprs = Vec::new();
1977 for inner in pair.into_inner() {
1978 if inner.as_rule() == Rule::guard_factor {
1979 exprs.push(parse_guard_factor(inner)?);
1980 }
1981 }
1982 match exprs.len() {
1983 0 => bail!("guard list requires at least one entry"),
1984 1 => Ok(exprs.pop().unwrap()),
1985 _ => Ok(GuardExpr::all(exprs)),
1986 }
1987}
1988
1989fn parse_guard_factor(pair: Pair<Rule>) -> Result<GuardExpr> {
1990 let inner = pair
1991 .into_inner()
1992 .next()
1993 .ok_or_else(|| anyhow!("guard factor missing expression"))?;
1994 parse_guard_expr(inner)
1995}
1996
1997fn parse_not_call(pair: Pair<Rule>) -> Result<GuardExpr> {
1998 for inner in pair.into_inner() {
1999 if inner.as_rule() == Rule::guard_expr {
2000 return parse_guard_expr(inner).map(|e| GuardExpr::Not(Box::new(e)));
2001 }
2002 }
2003 bail!("not() missing expression")
2004}
2005
2006fn parse_guard_primary(pair: Pair<Rule>) -> Result<GuardExpr> {
2007 match pair.as_rule() {
2008 Rule::guard_primary => {
2009 let inner = pair
2010 .into_inner()
2011 .next()
2012 .ok_or_else(|| anyhow!("guard primary missing body"))?;
2013 parse_guard_primary(inner)
2014 }
2015 Rule::guard_group => parse_guard_group(pair),
2016 Rule::guard_any_call => parse_guard_any_call(pair),
2017 Rule::guard_all_call => parse_guard_all_call(pair),
2018 Rule::not_call => parse_not_call(pair),
2019 Rule::guard_term => parse_guard_term(pair),
2020 _ => bail!("unexpected guard primary rule: {:?}", pair.as_rule()),
2021 }
2022}
2023
2024fn parse_guard_group(pair: Pair<Rule>) -> Result<GuardExpr> {
2025 for inner in pair.into_inner() {
2026 if inner.as_rule() == Rule::guard_expr {
2027 return parse_guard_expr(inner);
2028 }
2029 }
2030 bail!("grouped guard missing expression")
2031}
2032
2033fn parse_guard_any_call(pair: Pair<Rule>) -> Result<GuardExpr> {
2034 let mut args = Vec::new();
2035 for inner in pair.into_inner() {
2036 if inner.as_rule() == Rule::guard_expr_list {
2037 args = parse_guard_expr_list(inner)?;
2038 }
2039 }
2040 if args.len() < 2 {
2041 bail!("any(...) requires at least two guard expressions");
2042 }
2043 Ok(GuardExpr::or(args))
2044}
2045
2046fn parse_guard_all_call(pair: Pair<Rule>) -> Result<GuardExpr> {
2047 let mut args = Vec::new();
2048 for inner in pair.into_inner() {
2049 if inner.as_rule() == Rule::guard_expr_list {
2050 args = parse_guard_expr_list(inner)?;
2051 }
2052 }
2053 if args.is_empty() {
2054 bail!("all(...) requires at least one guard expression");
2055 }
2056 Ok(GuardExpr::all(args))
2057}
2058
2059fn parse_guard_expr_list(pair: Pair<Rule>) -> Result<Vec<GuardExpr>> {
2060 let mut exprs = Vec::new();
2061 for inner in pair.into_inner() {
2062 if inner.as_rule() == Rule::guard_expr {
2063 push_guard_or_args_from_expr(inner, &mut exprs)?;
2064 }
2065 }
2066 Ok(exprs)
2067}
2068
2069fn push_guard_or_args_from_expr(expr_pair: Pair<Rule>, exprs: &mut Vec<GuardExpr>) -> Result<()> {
2070 if let Some(seq_pair) = expr_pair
2071 .clone()
2072 .into_inner()
2073 .find(|inner| inner.as_rule() == Rule::guard_seq)
2074 {
2075 let factors: Vec<Pair<Rule>> = seq_pair
2076 .into_inner()
2077 .filter(|inner| inner.as_rule() == Rule::guard_factor)
2078 .collect();
2079 if factors.len() > 1 {
2080 for factor in factors {
2081 exprs.push(parse_guard_factor(factor)?);
2082 }
2083 return Ok(());
2084 }
2085 }
2086 exprs.push(parse_guard_expr(expr_pair)?);
2087 Ok(())
2088}
2089
2090fn parse_guard_term(pair: Pair<Rule>) -> Result<GuardExpr> {
2091 for inner in pair.into_inner() {
2092 match inner.as_rule() {
2093 Rule::eq_guard => {
2094 return Ok(GuardExpr::Predicate(parse_func_guard(inner)?));
2095 }
2096 Rule::neq_guard => {
2097 let guard = parse_func_guard(inner)?;
2098 return Ok(GuardExpr::Not(Box::new(GuardExpr::Predicate(guard))));
2099 }
2100 Rule::bool_guard => {
2101 let val = inner
2102 .into_inner()
2103 .find(|p| p.as_rule() == Rule::bool_value)
2104 .expect("grammar invariant violated: bool_guard missing bool_value")
2105 .as_str()
2106 .to_string();
2107 return Ok(GuardExpr::Predicate(Guard::StaticBool { value: val }));
2108 }
2109 Rule::env_guard => {
2110 return Ok(GuardExpr::Predicate(parse_env_guard(inner)?));
2111 }
2112 Rule::bare_guard_ident => {
2113 let tag = inner.as_str();
2114 if let Ok(g) = parse_platform_tag(tag) {
2115 return Ok(GuardExpr::Predicate(g));
2116 }
2117 return Ok(GuardExpr::Predicate(Guard::EnvExists {
2118 key: tag.to_string(),
2119 }));
2120 }
2121 _ => {}
2122 }
2123 }
2124 bail!("missing guard predicate")
2125}
2126
2127fn parse_func_guard(pair: Pair<Rule>) -> Result<Guard> {
2128 let mut key = String::new();
2129 let mut value = String::new();
2130 let mut saw_env_prefix = false;
2131 for inner in pair.into_inner() {
2132 match inner.as_rule() {
2133 Rule::env_prefix => saw_env_prefix = true,
2134 Rule::env_key if saw_env_prefix => {
2135 key = inner.as_str().trim().to_string();
2136 }
2137 Rule::bare_guard_value | Rule::quoted_string => {
2138 value = unquote(inner.as_str().trim()).to_string();
2139 }
2140 _ => {}
2141 }
2142 }
2143 Ok(Guard::EnvEquals { key, value })
2144}
2145
2146fn unquote(s: &str) -> &str {
2147 s.strip_prefix('"')
2148 .and_then(|s| s.strip_suffix('"'))
2149 .or_else(|| s.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
2150 .unwrap_or(s)
2151}
2152
2153fn parse_env_guard(pair: Pair<Rule>) -> Result<Guard> {
2154 let mut key = String::new();
2155 for inner in pair.into_inner() {
2156 if inner.as_rule() == Rule::env_key {
2157 key = inner.as_str().trim().to_string();
2158 }
2159 }
2160 Ok(Guard::EnvExists { key })
2161}
2162
2163fn parse_platform_tag(tag: &str) -> Result<Guard> {
2164 let target = match tag.to_ascii_lowercase().as_str() {
2165 "unix" => PlatformGuard::Unix,
2166 "windows" => PlatformGuard::Windows,
2167 "mac" | "macos" => PlatformGuard::Macos,
2168 "linux" => PlatformGuard::Linux,
2169 _ => bail!("unknown platform '{}'", tag),
2170 };
2171 Ok(Guard::Platform { target })
2172}
2173
2174fn parse_dollar_ident(pair: Pair<Rule>) -> String {
2175 let s = pair.as_str();
2177 s.strip_prefix('$').unwrap_or(s).to_string()
2178}
2179
2180use crate::ast::{ArithOp, CompareOp, LogicalOp, MathOp, Value};
2181
2182fn parse_expr(pair: Pair<Rule>) -> Result<Expr> {
2183 let expr = parse_expr_inner(pair)?;
2184 if matches!(expr, Expr::UnsignedIntBoundary(_)) {
2185 bail!("integer overflow: 9223372036854775808 exceeds i64::MAX");
2186 }
2187 Ok(expr)
2188}
2189
2190fn parse_expr_inner(pair: Pair<Rule>) -> Result<Expr> {
2191 let inner = pair.into_inner().next().unwrap();
2192 match inner.as_rule() {
2193 Rule::expr_logical_or => parse_expr_logical_or(inner),
2194 _ => bail!("unexpected expr rule: {:?}", inner.as_rule()),
2195 }
2196}
2197
2198fn parse_expr_logical_or(pair: Pair<Rule>) -> Result<Expr> {
2199 let mut inner = pair.into_inner();
2200 let mut left = parse_expr_logical_and(inner.next().unwrap())?;
2201 while let Some(op_pair) = inner.next() {
2202 let op = match op_pair.as_rule() {
2203 Rule::or_op => LogicalOp::Or,
2204 _ => bail!("unexpected operator in logical-or: {:?}", op_pair.as_rule()),
2205 };
2206 let right = parse_expr_logical_and(inner.next().unwrap())?;
2207 left = Expr::Logical {
2208 op,
2209 left: Box::new(left),
2210 right: Box::new(right),
2211 };
2212 }
2213 Ok(left)
2214}
2215
2216fn parse_expr_logical_and(pair: Pair<Rule>) -> Result<Expr> {
2217 let mut inner = pair.into_inner();
2218 let mut left = parse_expr_comparison(inner.next().unwrap())?;
2219 while let Some(op_pair) = inner.next() {
2220 let op = match op_pair.as_rule() {
2221 Rule::and_op => LogicalOp::And,
2222 _ => bail!(
2223 "unexpected operator in logical-and: {:?}",
2224 op_pair.as_rule()
2225 ),
2226 };
2227 let right = parse_expr_comparison(inner.next().unwrap())?;
2228 reject_boundary(&left)?;
2229 reject_boundary(&right)?;
2230 left = Expr::Logical {
2231 op,
2232 left: Box::new(left),
2233 right: Box::new(right),
2234 };
2235 }
2236 Ok(left)
2237}
2238
2239fn parse_expr_comparison(pair: Pair<Rule>) -> Result<Expr> {
2240 let mut inner = pair.into_inner();
2241 let left = parse_expr_ordering(inner.next().unwrap())?;
2242 if let Some(op_pair) = inner.next() {
2243 let op = match op_pair.as_rule() {
2244 Rule::eq_op => CompareOp::Eq,
2245 Rule::neq_op => CompareOp::Ne,
2246 _ => bail!("unexpected comparison operator: {:?}", op_pair.as_rule()),
2247 };
2248 let right = parse_expr_ordering(inner.next().unwrap())?;
2249 return make_compare(op, left, right);
2250 }
2251 Ok(left)
2252}
2253
2254fn parse_expr_ordering(pair: Pair<Rule>) -> Result<Expr> {
2255 let mut inner = pair.into_inner();
2256 let left = parse_expr_add_sub(inner.next().unwrap())?;
2257 if let Some(op_pair) = inner.next() {
2258 let op = match op_pair.as_rule() {
2259 Rule::lt_op => CompareOp::Lt,
2260 Rule::le_op => CompareOp::Le,
2261 Rule::gt_op => CompareOp::Gt,
2262 Rule::ge_op => CompareOp::Ge,
2263 _ => bail!("unexpected ordering operator: {:?}", op_pair.as_rule()),
2264 };
2265 let right = parse_expr_add_sub(inner.next().unwrap())?;
2266 return make_compare(op, left, right);
2267 }
2268 Ok(left)
2269}
2270
2271fn parse_expr_add_sub(pair: Pair<Rule>) -> Result<Expr> {
2272 let mut inner = pair.into_inner();
2273 let mut left = parse_expr_mul_div(inner.next().unwrap())?;
2274 while let Some(op_pair) = inner.next() {
2275 let op = match op_pair.as_rule() {
2276 Rule::plus_op => ArithOp::Add,
2277 Rule::minus_op => ArithOp::Sub,
2278 _ => bail!("unexpected additive operator: {:?}", op_pair.as_rule()),
2279 };
2280 let right = parse_expr_mul_div(inner.next().unwrap())?;
2281 left = make_arith(op, left, right)?;
2282 }
2283 Ok(left)
2284}
2285
2286fn parse_expr_mul_div(pair: Pair<Rule>) -> Result<Expr> {
2287 let mut inner = pair.into_inner();
2288 let mut left = parse_expr_unary(inner.next().unwrap())?;
2289 while let Some(op_pair) = inner.next() {
2290 let op = match op_pair.as_rule() {
2291 Rule::star_op => ArithOp::Mul,
2292 Rule::slash_op => ArithOp::Div,
2293 _ => bail!(
2294 "unexpected multiplicative operator: {:?}",
2295 op_pair.as_rule()
2296 ),
2297 };
2298 let right = parse_expr_unary(inner.next().unwrap())?;
2299 left = make_arith(op, left, right)?;
2300 }
2301 Ok(left)
2302}
2303
2304fn parse_expr_unary(pair: Pair<Rule>) -> Result<Expr> {
2305 let mut prefixes = Vec::new();
2306 let mut atom = None;
2307 for inner in pair.into_inner() {
2308 match inner.as_rule() {
2309 Rule::not_op => prefixes.push(false),
2310 Rule::neg_op => prefixes.push(true),
2311 Rule::expr_atom => atom = Some(parse_expr_atom(inner)?),
2312 _ => bail!("unexpected unary operand rule: {:?}", inner.as_rule()),
2313 }
2314 }
2315 let mut expr = atom.ok_or_else(|| anyhow!("'!'/'-' requires an expression operand"))?;
2316 for is_neg in prefixes.into_iter().rev() {
2318 if is_neg {
2319 expr = apply_unary_neg(expr)?;
2320 } else {
2321 reject_boundary(&expr)?;
2322 expr = Expr::Not(Box::new(expr));
2323 }
2324 }
2325 Ok(expr)
2326}
2327
2328fn reject_boundary(expr: &Expr) -> Result<()> {
2331 if matches!(expr, Expr::UnsignedIntBoundary(_)) {
2332 bail!("integer overflow: 9223372036854775808 exceeds i64::MAX");
2333 }
2334 Ok(())
2335}
2336
2337fn apply_unary_neg(expr: Expr) -> Result<Expr> {
2340 match expr {
2341 Expr::Literal(Value::Int(n)) => match n.checked_neg() {
2342 Some(v) => Ok(Expr::Literal(Value::Int(v))),
2343 None => Ok(Expr::CompiledMath(vec![
2344 MathOp::PushConst(Value::Int(n)),
2345 MathOp::Neg,
2346 ])),
2347 },
2348 Expr::Literal(Value::Float(f)) => Ok(Expr::Literal(Value::Float(-f))),
2349 Expr::UnsignedIntBoundary(n) => {
2350 if n == i64::MAX as u64 + 1 {
2351 Ok(Expr::Literal(Value::Int(i64::MIN)))
2352 } else {
2353 bail!("integer overflow: {} exceeds i64::MAX", n);
2354 }
2355 }
2356 other => {
2357 if let Some(mut ops) = expr_to_rpn(&other) {
2358 ops.push(MathOp::Neg);
2359 Ok(Expr::CompiledMath(ops))
2360 } else {
2361 Ok(Expr::Arithmetic {
2364 op: ArithOp::Sub,
2365 left: Box::new(Expr::Literal(Value::Int(0))),
2366 right: Box::new(other),
2367 })
2368 }
2369 }
2370 }
2371}
2372
2373fn try_fold_arith(op: ArithOp, left: &Expr, right: &Expr) -> Option<Expr> {
2378 let (Expr::Literal(lv), Expr::Literal(rv)) = (left, right) else {
2379 return None;
2380 };
2381 fold_arith_values(op, lv, rv).map(Expr::Literal)
2382}
2383
2384fn fold_arith_values(op: ArithOp, left: &Value, right: &Value) -> Option<Value> {
2385 match (left, right) {
2386 (Value::Int(a), Value::Int(b)) => {
2387 let v = match op {
2388 ArithOp::Add => a.checked_add(*b)?,
2389 ArithOp::Sub => a.checked_sub(*b)?,
2390 ArithOp::Mul => a.checked_mul(*b)?,
2391 ArithOp::Div => a.checked_div(*b)?,
2392 };
2393 Some(Value::Int(v))
2394 }
2395 (Value::Int(a), Value::Float(b)) => fold_float(op, *a as f64, *b),
2396 (Value::Float(a), Value::Int(b)) => fold_float(op, *a, *b as f64),
2397 (Value::Float(a), Value::Float(b)) => fold_float(op, *a, *b),
2398 _ => None,
2399 }
2400}
2401
2402fn fold_float(op: ArithOp, a: f64, b: f64) -> Option<Value> {
2403 if !a.is_finite() || !b.is_finite() {
2404 return None;
2405 }
2406 let v = match op {
2407 ArithOp::Add => a + b,
2408 ArithOp::Sub => a - b,
2409 ArithOp::Mul => a * b,
2410 ArithOp::Div => {
2411 if b == 0.0 {
2412 return None;
2413 }
2414 a / b
2415 }
2416 };
2417 if v.is_finite() {
2418 Some(Value::Float(v))
2419 } else {
2420 None
2421 }
2422}
2423
2424fn try_fold_compare(op: CompareOp, left: &Expr, right: &Expr) -> Option<Expr> {
2425 let (Expr::Literal(lv), Expr::Literal(rv)) = (left, right) else {
2426 return None;
2427 };
2428 match (lv, rv) {
2429 (Value::Int(a), Value::Int(b)) => {
2430 let r = match op {
2431 CompareOp::Eq => a == b,
2432 CompareOp::Ne => a != b,
2433 CompareOp::Lt => a < b,
2434 CompareOp::Le => a <= b,
2435 CompareOp::Gt => a > b,
2436 CompareOp::Ge => a >= b,
2437 };
2438 Some(Expr::Literal(Value::Bool(r)))
2439 }
2440 (Value::Int(_), Value::Float(_))
2441 | (Value::Float(_), Value::Int(_))
2442 | (Value::Float(_), Value::Float(_)) => {
2443 let (af, bf) = (as_f64(lv)?, as_f64(rv)?);
2444 let r = match op {
2445 CompareOp::Eq => af == bf,
2446 CompareOp::Ne => af != bf,
2447 CompareOp::Lt => af < bf,
2448 CompareOp::Le => af <= bf,
2449 CompareOp::Gt => af > bf,
2450 CompareOp::Ge => af >= bf,
2451 };
2452 Some(Expr::Literal(Value::Bool(r)))
2453 }
2454 (Value::Bool(a), Value::Bool(b)) => match op {
2455 CompareOp::Eq => Some(Expr::Literal(Value::Bool(a == b))),
2456 CompareOp::Ne => Some(Expr::Literal(Value::Bool(a != b))),
2457 _ => None,
2458 },
2459 _ => None,
2460 }
2461}
2462
2463fn as_f64(v: &Value) -> Option<f64> {
2464 match v {
2465 Value::Int(n) => Some(*n as f64),
2466 Value::Float(f) if f.is_finite() => Some(*f),
2467 _ => None,
2468 }
2469}
2470
2471fn make_arith(op: ArithOp, left: Expr, right: Expr) -> Result<Expr> {
2472 reject_boundary(&left)?;
2473 reject_boundary(&right)?;
2474 if let Some(folded) = try_fold_arith(op, &left, &right) {
2475 return Ok(folded);
2476 }
2477 if let (Some(mut lops), Some(mut rops)) = (expr_to_rpn(&left), expr_to_rpn(&right)) {
2478 lops.append(&mut rops);
2479 lops.push(match op {
2480 ArithOp::Add => MathOp::Add,
2481 ArithOp::Sub => MathOp::Sub,
2482 ArithOp::Mul => MathOp::Mul,
2483 ArithOp::Div => MathOp::Div,
2484 });
2485 return Ok(Expr::CompiledMath(lops));
2486 }
2487 Ok(Expr::Arithmetic {
2488 op,
2489 left: Box::new(left),
2490 right: Box::new(right),
2491 })
2492}
2493
2494fn make_compare(op: CompareOp, left: Expr, right: Expr) -> Result<Expr> {
2495 reject_boundary(&left)?;
2496 reject_boundary(&right)?;
2497 if let Some(folded) = try_fold_compare(op, &left, &right) {
2498 return Ok(folded);
2499 }
2500 if let (Some(mut lops), Some(mut rops)) = (expr_to_rpn(&left), expr_to_rpn(&right)) {
2501 lops.append(&mut rops);
2502 lops.push(match op {
2503 CompareOp::Eq => MathOp::Eq,
2504 CompareOp::Ne => MathOp::Ne,
2505 CompareOp::Lt => MathOp::Lt,
2506 CompareOp::Le => MathOp::Le,
2507 CompareOp::Gt => MathOp::Gt,
2508 CompareOp::Ge => MathOp::Ge,
2509 });
2510 return Ok(Expr::CompiledMath(lops));
2511 }
2512 Ok(Expr::Compare {
2513 op,
2514 left: Box::new(left),
2515 right: Box::new(right),
2516 })
2517}
2518
2519fn expr_to_rpn(expr: &Expr) -> Option<Vec<MathOp>> {
2523 match expr {
2524 Expr::Literal(v) => Some(vec![MathOp::PushConst(v.clone())]),
2525 Expr::Var(name) => Some(vec![MathOp::LoadVar(name.clone())]),
2526 Expr::Env(key) => Some(vec![MathOp::LoadEnv(key.clone())]),
2527 Expr::KeyPath { base, keys } => Some(vec![MathOp::LoadKeyPath {
2528 base: base.clone(),
2529 keys: keys.clone(),
2530 }]),
2531 Expr::Call { name, args } => {
2532 if name == "INSPECT" {
2533 let [arg] = args.as_slice() else {
2534 return None;
2535 };
2536 if let Expr::Var(var) = arg {
2537 return Some(vec![MathOp::Inspect(var.clone())]);
2538 }
2539 return None;
2540 }
2541 let mut ops = Vec::new();
2542 for arg in args {
2543 ops.extend(expr_to_rpn(arg)?);
2544 }
2545 ops.push(MathOp::Call {
2546 name: name.clone(),
2547 arity: args.len(),
2548 });
2549 Some(ops)
2550 }
2551 Expr::Arithmetic { op, left, right } => {
2552 let mut ops = expr_to_rpn(left)?;
2553 ops.extend(expr_to_rpn(right)?);
2554 ops.push(match op {
2555 ArithOp::Add => MathOp::Add,
2556 ArithOp::Sub => MathOp::Sub,
2557 ArithOp::Mul => MathOp::Mul,
2558 ArithOp::Div => MathOp::Div,
2559 });
2560 Some(ops)
2561 }
2562 Expr::Compare { op, left, right } => {
2563 let mut ops = expr_to_rpn(left)?;
2564 ops.extend(expr_to_rpn(right)?);
2565 ops.push(match op {
2566 CompareOp::Eq => MathOp::Eq,
2567 CompareOp::Ne => MathOp::Ne,
2568 CompareOp::Lt => MathOp::Lt,
2569 CompareOp::Le => MathOp::Le,
2570 CompareOp::Gt => MathOp::Gt,
2571 CompareOp::Ge => MathOp::Ge,
2572 });
2573 Some(ops)
2574 }
2575 Expr::CompiledMath(ops) => Some(ops.clone()),
2576 Expr::Not(_) | Expr::Logical { .. } | Expr::List(_) | Expr::Map(_) => None,
2577 Expr::UnsignedIntBoundary(_) => None,
2578 }
2579}
2580
2581fn parse_expr_atom(pair: Pair<Rule>) -> Result<Expr> {
2582 let inner = pair.into_inner().next().unwrap();
2583 match inner.as_rule() {
2584 Rule::parenthesized_expr => parse_expr_inner(inner.into_inner().next().unwrap()),
2585 Rule::func_call => parse_func_call(inner),
2586 Rule::key_path => parse_key_path(inner),
2587 Rule::variable => {
2588 let name = inner.as_str();
2589 let name = name.strip_prefix('$').unwrap_or(name).to_string();
2590 Ok(Expr::Var(name))
2591 }
2592 Rule::env_read => parse_env_read(inner).map(Expr::Env),
2593 Rule::pipe_read => parse_pipe_read(inner).map(|name| Expr::Literal(Value::Pipe(name))),
2594 Rule::list_literal => parse_list_literal(inner),
2595 Rule::map_literal => parse_map_literal(inner),
2596 Rule::string_literal | Rule::quoted_string => {
2597 let s = parse_quoted_string(inner)?;
2598 Ok(Expr::Literal(Value::String(s)))
2599 }
2600 Rule::numeric_literal => parse_numeric_literal(inner),
2601 Rule::bare_word => {
2602 let s = inner.as_str().to_string();
2603 match s.as_str() {
2604 "true" => Ok(Expr::Literal(Value::Bool(true))),
2605 "false" => Ok(Expr::Literal(Value::Bool(false))),
2606 _ => Ok(Expr::Literal(Value::String(s))),
2607 }
2608 }
2609 _ => bail!("unexpected expression atom rule: {:?}", inner.as_rule()),
2610 }
2611}
2612
2613fn parse_numeric_literal(pair: Pair<Rule>) -> Result<Expr> {
2618 let text = pair.as_str();
2619 if text.contains('.') {
2620 let parsed: f64 = text
2621 .parse()
2622 .map_err(|_| anyhow!("invalid float literal {text:?}"))?;
2623 if !parsed.is_finite() {
2624 bail!("invalid float literal {text:?}");
2625 }
2626 return Ok(Expr::Literal(Value::Float(parsed)));
2627 }
2628 let digits: u64 = text
2629 .parse()
2630 .map_err(|_| anyhow!("integer overflow: {text:?} exceeds i64::MAX"))?;
2631 if digits <= i64::MAX as u64 {
2632 Ok(Expr::Literal(Value::Int(digits as i64)))
2633 } else if digits == i64::MAX as u64 + 1 {
2634 Ok(Expr::UnsignedIntBoundary(digits))
2635 } else {
2636 bail!("integer overflow: {text:?} exceeds i64::MAX");
2637 }
2638}
2639
2640fn parse_env_read(pair: Pair<Rule>) -> Result<String> {
2641 for inner in pair.into_inner() {
2642 if inner.as_rule() == Rule::env_read_key {
2643 return Ok(inner.as_str().trim().to_string());
2644 }
2645 }
2646 bail!("env read requires a key: env:KEY")
2647}
2648
2649fn parse_pipe_read(pair: Pair<Rule>) -> Result<String> {
2650 for inner in pair.into_inner() {
2651 if inner.as_rule() == Rule::pipe_name {
2652 return Ok(inner.as_str().trim().to_string());
2653 }
2654 }
2655 bail!("pipe read requires a name: pipe:NAME")
2656}
2657
2658fn parse_key_path(pair: Pair<Rule>) -> Result<Expr> {
2659 let mut base = None;
2660 let mut keys = Vec::new();
2661 for inner in pair.into_inner() {
2662 match inner.as_rule() {
2663 Rule::ident => {
2664 if base.is_none() {
2665 base = Some(inner.as_str().to_string());
2666 }
2667 }
2668 Rule::key_path_segment => {
2669 keys.push(inner.as_str().to_string());
2670 }
2671 _ => {}
2672 }
2673 }
2674 Ok(Expr::KeyPath {
2675 base: base.ok_or_else(|| anyhow!("key path requires a base identifier"))?,
2676 keys,
2677 })
2678}
2679
2680fn parse_func_call(pair: Pair<Rule>) -> Result<Expr> {
2681 let mut name = None;
2682 let mut args = Vec::new();
2683 for inner in pair.into_inner() {
2684 match inner.as_rule() {
2685 Rule::ident => {
2686 name = Some(inner.as_str().to_string());
2687 }
2688 Rule::expr => {
2689 let arg = parse_expr_inner(inner)?;
2690 reject_boundary(&arg)?;
2691 args.push(arg);
2692 }
2693 _ => {}
2694 }
2695 }
2696 Ok(Expr::Call {
2697 name: name.ok_or_else(|| anyhow!("function call requires a name"))?,
2698 args,
2699 })
2700}
2701
2702fn parse_list_literal(pair: Pair<Rule>) -> Result<Expr> {
2703 let mut items = Vec::new();
2704 for inner in pair.into_inner() {
2705 if inner.as_rule() == Rule::expr {
2706 let item = parse_expr_inner(inner)?;
2707 reject_boundary(&item)?;
2708 items.push(item);
2709 }
2710 }
2711 Ok(Expr::List(items))
2712}
2713
2714fn parse_map_literal(pair: Pair<Rule>) -> Result<Expr> {
2715 let mut entries = Vec::new();
2716 for inner in pair.into_inner() {
2717 if inner.as_rule() == Rule::map_entry {
2718 let mut key = String::new();
2719 let mut value = None;
2720 for entry_inner in inner.into_inner() {
2721 match entry_inner.as_rule() {
2722 Rule::quoted_string => {
2723 key = parse_quoted_string(entry_inner)?;
2724 }
2725 Rule::bare_word => {
2726 key = entry_inner.as_str().to_string();
2727 }
2728 Rule::expr => {
2729 let val = parse_expr_inner(entry_inner)?;
2730 reject_boundary(&val)?;
2731 value = Some(val);
2732 }
2733 _ => {}
2734 }
2735 }
2736 let val = value.ok_or_else(|| anyhow!("map entry missing value"))?;
2737 entries.push((key, val));
2738 }
2739 }
2740 Ok(Expr::Map(entries))
2741}