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