1use crate::ast::{
2 Arg, Expr, Guard, GuardExpr, IoBinding, IoStream, ModuleTable, PipeTarget, PlatformGuard, Step,
3 StepKind,
4};
5use crate::command::ArgType;
6use crate::constants::{
7 KEYWORD_EXPORT, KEYWORD_IMPORT, KEYWORD_INSPECT, MODULE_SEPARATOR, SCRIPT_MODULE_NAME, qualify,
8 split_qualified,
9};
10use crate::error::{ParseError, ParseResult, SpanContext};
11use crate::lexer::{self, RawToken, Rule, parse_pest_error, refine_span, span_for_line, span_of};
12use pest::iterators::Pair;
13use std::cell::RefCell;
14use std::collections::{HashSet, VecDeque};
15
16pub(super) struct LowerCtx<'a> {
25 pub lower: &'a dyn Fn(&str, Vec<Arg>) -> ParseResult<StepKind>,
27 pub reserved_names: &'a HashSet<String>,
31 func_scopes: &'a RefCell<Vec<HashSet<String>>>,
37 modules: &'a ModuleTable,
39 import_scopes: &'a RefCell<Vec<Vec<String>>>,
43}
44
45impl<'a> LowerCtx<'a> {
46 pub(super) fn new(
47 lower: &'a dyn Fn(&str, Vec<Arg>) -> ParseResult<StepKind>,
48 reserved_names: &'a HashSet<String>,
49 func_scopes: &'a RefCell<Vec<HashSet<String>>>,
50 modules: &'a ModuleTable,
51 import_scopes: &'a RefCell<Vec<Vec<String>>>,
52 ) -> Self {
53 Self {
54 lower,
55 reserved_names,
56 func_scopes,
57 modules,
58 import_scopes,
59 }
60 }
61
62 pub(super) fn enter_scope(&self) {
63 self.func_scopes.borrow_mut().push(HashSet::new());
64 self.import_scopes.borrow_mut().push(Vec::new());
65 }
66
67 pub(super) fn exit_scope(&self) {
68 self.func_scopes.borrow_mut().pop();
69 self.import_scopes.borrow_mut().pop();
70 }
71
72 pub(super) fn declare_func(&self, name: &str) -> bool {
76 let mut scopes = self.func_scopes.borrow_mut();
77 match scopes.last_mut() {
78 Some(current) => current.insert(name.to_string()),
79 None => true,
80 }
81 }
82
83 pub(super) fn import_modules(&self, ctx: &SpanContext, modules: &[String]) -> ParseResult<()> {
85 let mut known: Vec<String> = self.modules.modules.keys().cloned().collect();
86 known.sort();
87 for module in modules {
88 if !self.modules.modules.contains_key(module) {
89 return Err(ParseError::validation(
90 KEYWORD_IMPORT,
91 format!(
92 "unknown module `{module}`; known modules: {}",
93 known.join(", ")
94 ),
95 ctx,
96 ));
97 }
98 let mut frames = self.import_scopes.borrow_mut();
99 match frames.last_mut() {
100 Some(frame) => {
101 if !frame.contains(module) {
102 frame.push(module.clone());
103 }
104 }
105 None => {
106 frames.push(vec![module.clone()]);
107 }
108 }
109 }
110 Ok(())
111 }
112
113 pub(super) fn resolve_call(&self, ctx: &SpanContext, name: &str) -> ParseResult<String> {
121 if let Some((module, base)) = split_qualified(name) {
122 if base == KEYWORD_INSPECT {
123 return Err(ParseError::validation(
124 "FUNC",
125 "INSPECT is a builtin keyword and cannot be module-qualified".to_string(),
126 ctx,
127 ));
128 }
129 check_func_ident(ctx, module)?;
130 check_func_ident(ctx, base)?;
131 match self.modules.modules.get(module) {
132 None => {
133 let mut known: Vec<String> = self.modules.modules.keys().cloned().collect();
134 known.sort();
135 Err(ParseError::validation(
136 "FUNC",
137 format!(
138 "unknown module `{module}`; known modules: {}",
139 known.join(", ")
140 ),
141 ctx,
142 ))
143 }
144 Some(None) => Ok(name.to_string()),
145 Some(Some(funcs)) => {
146 if funcs.functions.contains(base) {
147 Ok(qualify(module, base))
148 } else {
149 Err(ParseError::validation(
150 "FUNC",
151 format!("unknown function `{module}::{base}`"),
152 ctx,
153 ))
154 }
155 }
156 }
157 } else {
158 if name == KEYWORD_IMPORT || name == KEYWORD_EXPORT {
159 return Err(ParseError::validation(
160 "FUNC",
161 format!("`{name}` is a directive, not a function"),
162 ctx,
163 ));
164 }
165 if name == KEYWORD_INSPECT {
166 return Ok(name.to_string());
167 }
168 let scopes = self.func_scopes.borrow();
169 if scopes.iter().rev().any(|scope| scope.contains(name)) {
170 return Ok(qualify(SCRIPT_MODULE_NAME, name));
171 }
172 drop(scopes);
173 let frames = self.import_scopes.borrow();
176 let mut known_matches: Vec<String> = Vec::new();
177 let mut opaque_matches: Vec<String> = Vec::new();
178 for frame in frames.iter() {
179 for module in frame {
180 match self.modules.modules.get(module) {
181 Some(Some(funcs)) => {
182 if funcs.functions.contains(name) && !known_matches.contains(module) {
183 known_matches.push(module.clone());
184 }
185 }
186 Some(None) if !opaque_matches.contains(module) => {
187 opaque_matches.push(module.clone());
188 }
189 Some(None) | None => {}
190 }
191 }
192 }
193 drop(frames);
194 if known_matches.len() > 1 {
195 known_matches.sort();
196 return Err(ParseError::validation(
197 "FUNC",
198 format!(
199 "ambiguous function `{name}`: exported by {}; qualify it (e.g. `{}::{name}`)",
200 known_matches.join(", "),
201 known_matches[0],
202 ),
203 ctx,
204 ));
205 }
206 if let Some(module) = known_matches.pop() {
207 return Ok(qualify(&module, name));
208 }
209 if opaque_matches.len() > 1 {
210 opaque_matches.sort();
211 return Err(ParseError::validation(
212 "FUNC",
213 format!(
214 "ambiguous function `{name}`: imported opaque modules {}; qualify it",
215 opaque_matches.join(", "),
216 ),
217 ctx,
218 ));
219 }
220 if let Some(module) = opaque_matches.pop() {
221 return Ok(qualify(&module, name));
222 }
223 let mut exporters: Vec<String> = self
226 .modules
227 .modules
228 .iter()
229 .filter_map(|(module, funcs)| match funcs {
230 Some(funcs) if funcs.functions.contains(name) => Some(module.clone()),
231 _ => None,
232 })
233 .collect();
234 exporters.sort();
235 if let Some(first) = exporters.first() {
236 return Err(ParseError::validation(
237 "FUNC",
238 format!(
239 "unknown function `{name}`; qualify it (`{first}::{name}`) or add `IMPORT [{first}]`"
240 ),
241 ctx,
242 ));
243 }
244 Err(ParseError::validation(
245 "FUNC",
246 format!("unknown function `{name}`"),
247 ctx,
248 ))
249 }
250 }
251
252 pub(super) fn module_base_names(&self) -> HashSet<String> {
255 self.modules.reserved_base_names()
256 }
257
258 fn visible_snapshot(&self) -> (HashSet<String>, Vec<String>) {
263 let mut funcs = HashSet::new();
264 for scope in self.func_scopes.borrow().iter() {
265 funcs.extend(scope.iter().cloned());
266 }
267 let mut imports = Vec::new();
268 for frame in self.import_scopes.borrow().iter() {
269 for module in frame {
270 if !imports.contains(module) {
271 imports.push(module.clone());
272 }
273 }
274 }
275 (funcs, imports)
276 }
277}
278
279#[derive(Clone)]
280struct ScopeFrame {
281 line_no: usize,
282 had_command: bool,
283}
284
285#[derive(Clone)]
286struct PendingIoBlock<'a> {
287 line_no: usize,
288 span: SpanContext<'a>,
289 bindings: Vec<IoBinding>,
290 guards: Option<GuardExpr>,
291}
292
293#[derive(Clone)]
294struct IoScopeFrame {
295 line_no: usize,
296 had_command: bool,
297 bindings: Vec<IoBinding>,
298 guards: Option<GuardExpr>,
299 first_step: usize,
303}
304
305#[derive(Clone, Copy, Debug)]
306enum BlockKind {
307 Guard,
308 Io,
309}
310
311#[derive(Default)]
312struct IoBindingSet {
313 stdin: Option<IoBinding>,
314 stdout: Option<IoBinding>,
315 stderr: Option<IoBinding>,
316}
317
318impl IoBindingSet {
319 fn insert(&mut self, binding: IoBinding) {
320 match binding.stream {
321 IoStream::Stdin => self.stdin = Some(binding),
322 IoStream::Stdout => self.stdout = Some(binding),
323 IoStream::Stderr => self.stderr = Some(binding),
324 }
325 }
326
327 fn into_vec(self) -> Vec<IoBinding> {
328 let mut out = Vec::new();
329 if let Some(binding) = self.stdin {
330 out.push(binding);
331 }
332 if let Some(binding) = self.stdout {
333 out.push(binding);
334 }
335 if let Some(binding) = self.stderr {
336 out.push(binding);
337 }
338 out
339 }
340}
341
342pub struct ScriptParser<'a, F: Fn(&str, Vec<Arg>) -> ParseResult<StepKind>> {
343 input: &'a str,
344 tokens: VecDeque<RawToken<'a>>,
345 steps: Vec<Step>,
346 guard_stack: Vec<Option<GuardExpr>>,
347 pending_guards: Option<GuardExpr>,
348 pending_inline_guards: Option<GuardExpr>,
349 pending_can_open_block: bool,
350 pending_scope_enters: usize,
351 scope_stack: Vec<ScopeFrame>,
352 pending_io_block: Option<PendingIoBlock<'a>>,
353 io_scope_stack: Vec<IoScopeFrame>,
354 block_stack: Vec<BlockKind>,
355 lower: F,
356 reserved_names: HashSet<String>,
361 modules: ModuleTable,
365 preseed_imports: Vec<String>,
368 preseed_funcs: HashSet<String>,
371}
372
373impl<'a, F: Fn(&str, Vec<Arg>) -> ParseResult<StepKind>> ScriptParser<'a, F> {
374 pub fn new(input: &'a str, lower: F) -> ParseResult<Self> {
375 Self::new_with_hosts(input, lower, HashSet::new())
376 }
377
378 pub fn new_with_hosts(
379 input: &'a str,
380 lower: F,
381 reserved_names: HashSet<String>,
382 ) -> ParseResult<Self> {
383 Self::new_with_modules(input, lower, reserved_names, ModuleTable::default())
384 }
385
386 pub fn new_with_modules(
387 input: &'a str,
388 lower: F,
389 reserved_names: HashSet<String>,
390 modules: ModuleTable,
391 ) -> ParseResult<Self> {
392 Self::new_with_preseed(
393 input,
394 lower,
395 reserved_names,
396 modules,
397 HashSet::new(),
398 Vec::new(),
399 )
400 }
401
402 pub fn new_with_preseed(
403 input: &'a str,
404 lower: F,
405 reserved_names: HashSet<String>,
406 modules: ModuleTable,
407 preseed_funcs: HashSet<String>,
408 preseed_imports: Vec<String>,
409 ) -> ParseResult<Self> {
410 let tokens = VecDeque::from(lexer::tokenize(input)?);
411 Ok(Self {
412 input,
413 tokens,
414 steps: Vec::new(),
415 guard_stack: vec![None],
416 pending_guards: None,
417 pending_inline_guards: None,
418 pending_can_open_block: false,
419 pending_scope_enters: 0,
420 scope_stack: Vec::new(),
421 pending_io_block: None,
422 io_scope_stack: Vec::new(),
423 block_stack: Vec::new(),
424 lower,
425 reserved_names,
426 modules,
427 preseed_imports,
428 preseed_funcs,
429 })
430 }
431
432 fn eof_span(&self) -> SpanContext<'_> {
434 let lines = self.input.lines().count().max(1);
435 span_for_line(self.input, lines)
436 }
437
438 pub fn parse(mut self) -> ParseResult<Vec<Step>> {
439 let func_scopes: RefCell<Vec<HashSet<String>>> = RefCell::new(vec![HashSet::new()]);
448 let import_scopes: RefCell<Vec<Vec<String>>> = RefCell::new(vec![Vec::new()]);
452 func_scopes.borrow_mut()[0].extend(self.preseed_funcs.iter().cloned());
455 import_scopes.borrow_mut()[0].extend(self.preseed_imports.iter().cloned());
456 while let Some(token) = self.tokens.pop_front() {
457 let step_index = self.steps.len();
458 if self.pending_io_block.is_some()
459 && !matches!(
460 token,
461 RawToken::BlockStart { .. }
462 | RawToken::Command { .. }
463 | RawToken::Instruction { .. }
464 | RawToken::RunExec { .. }
465 )
466 {
467 let pending = self.pending_io_block.take().unwrap();
468 return Err(ParseError::structural(
469 "with_io",
470 format!(
471 "line {}: WITH_IO block must be followed by '{{'",
472 pending.line_no
473 ),
474 &pending.span,
475 ));
476 }
477 match token {
478 RawToken::Guard {
479 pair,
480 line_end,
481 span,
482 } => {
483 let span = span.with_step(step_index);
484 let groups = parse_guard_line(&span, pair)?;
485 self.handle_guard_token(line_end, groups)?;
486 }
487 RawToken::BlockStart { line_no, span } => {
488 let span = span.with_step(step_index);
489 self.start_block(&span, line_no)?;
490 LowerCtx::new(
491 &self.lower,
492 &self.reserved_names,
493 &func_scopes,
494 &self.modules,
495 &import_scopes,
496 )
497 .enter_scope();
498 }
499 RawToken::BlockEnd { line_no, span } => {
500 let span = span.with_step(step_index);
501 self.end_block(&span, line_no)?;
502 LowerCtx::new(
503 &self.lower,
504 &self.reserved_names,
505 &func_scopes,
506 &self.modules,
507 &import_scopes,
508 )
509 .exit_scope();
510 }
511 RawToken::Command {
512 pair,
513 line_no,
514 span,
515 } => {
516 let span = span.with_step(step_index);
517 let lctx = LowerCtx::new(
518 &self.lower,
519 &self.reserved_names,
520 &func_scopes,
521 &self.modules,
522 &import_scopes,
523 );
524 if pair.as_rule() == Rule::import_statement {
529 if self.pending_guards.is_some() || self.pending_inline_guards.is_some() {
530 return Err(ParseError::structural(
531 KEYWORD_IMPORT,
532 "IMPORT cannot be guarded".to_string(),
533 &span,
534 ));
535 }
536 self.lower_import(&span, pair, &lctx)?;
537 continue;
538 }
539 if pair.as_rule() == Rule::export_statement {
540 return Err(ParseError::validation(
541 KEYWORD_EXPORT,
542 "`EXPORT` is reserved for future script-module support and cannot be used yet.".to_string(),
543 &span,
544 ));
545 }
546 let kind = parse_structural_command_with_lower(&span, pair, &lctx)?;
547 self.handle_command_token(&span, line_no, kind)?;
548 }
549 RawToken::Instruction {
550 pair,
551 line_no,
552 span,
553 } => {
554 let span = span.with_step(step_index);
555 let lctx = LowerCtx::new(
556 &self.lower,
557 &self.reserved_names,
558 &func_scopes,
559 &self.modules,
560 &import_scopes,
561 );
562 let kind = self
563 .lower_instruction(&span, pair, &lctx)
564 .map_err(|e| e.with_span(&span))?;
565 self.handle_command_token(&span, line_no, kind)?;
566 }
567 RawToken::RunExec {
568 pair,
569 line_no,
570 span,
571 } => {
572 let span = span.with_step(step_index);
573 let lctx = LowerCtx::new(
574 &self.lower,
575 &self.reserved_names,
576 &func_scopes,
577 &self.modules,
578 &import_scopes,
579 );
580 let kind = lower_run_exec_pair(&span, pair, &lctx)?;
581 self.handle_command_token(&span, line_no, kind)?;
582 }
583 }
584 }
585
586 if let Some(pending) = self.pending_io_block.take() {
587 return Err(ParseError::structural(
588 "with_io",
589 format!(
590 "line {}: WITH_IO block must be followed by '{{'",
591 pending.line_no
592 ),
593 &pending.span,
594 ));
595 }
596
597 if self.guard_stack.len() != 1 {
598 let ctx = self.eof_span();
599 return Err(ParseError::structural(
600 "guard",
601 "unclosed guard block at end of script".to_string(),
602 &ctx,
603 ));
604 }
605 if self.pending_guards.is_some() {
606 let ctx = self.eof_span();
607 return Err(ParseError::structural(
608 "guard",
609 "guard declared on final lines without a following command".to_string(),
610 &ctx,
611 ));
612 }
613
614 if let Some(frame) = self.io_scope_stack.last() {
615 let ctx = span_for_line(self.input, frame.line_no);
616 return Err(ParseError::structural(
617 "with_io",
618 format!(
619 "WITH_IO block starting on line {} was not closed",
620 frame.line_no
621 ),
622 &ctx,
623 ));
624 }
625
626 {
629 let ctx = self.eof_span();
630 let mut seen_non_prelude = false;
631 let mut inherit_count = 0usize;
632 for step in &self.steps {
633 match &step.kind {
634 StepKind::InheritEnv { .. } => {
635 if seen_non_prelude {
636 return Err(ParseError::structural(
637 "inherit_env",
638 "INHERIT_ENV must appear before any other commands".to_string(),
639 &ctx,
640 ));
641 }
642 if step.guard.is_some() || step.scope_enter > 0 || step.scope_exit > 0 {
643 return Err(ParseError::structural(
644 "inherit_env",
645 "INHERIT_ENV cannot be guarded or nested inside blocks".to_string(),
646 &ctx,
647 ));
648 }
649 inherit_count += 1;
650 }
651 kind => {
652 if contains_inherit_env(kind) {
653 return Err(ParseError::structural(
654 "inherit_env",
655 "INHERIT_ENV cannot be nested inside other commands".to_string(),
656 &ctx,
657 ));
658 }
659 seen_non_prelude = true;
660 }
661 }
662 }
663 if inherit_count > 1 {
664 return Err(ParseError::structural(
665 "inherit_env",
666 "only one INHERIT_ENV directive is allowed".to_string(),
667 &ctx,
668 ));
669 }
670 }
671
672 Ok(self.steps)
673 }
674
675 fn lower_instruction(
676 &self,
677 ctx: &SpanContext,
678 pair: Pair<Rule>,
679 lctx: &LowerCtx,
680 ) -> ParseResult<StepKind> {
681 lower_instruction_pair(ctx, pair, lctx)
682 }
683
684 fn lower_import(
688 &self,
689 ctx: &SpanContext,
690 pair: Pair<Rule>,
691 lctx: &LowerCtx,
692 ) -> ParseResult<()> {
693 lower_import_statement(ctx, pair, lctx)
694 }
695
696 fn handle_guard_token(&mut self, line_end: usize, expr: GuardExpr) -> ParseResult<()> {
697 if let Some(RawToken::Command { line_no, .. }) = self.tokens.front()
698 && *line_no == line_end
699 {
700 self.pending_inline_guards = Some(expr);
701 self.pending_can_open_block = false;
702 return Ok(());
703 }
704 self.stash_pending_guard(expr);
705 self.pending_can_open_block = true;
706 Ok(())
707 }
708
709 fn handle_command_token(
710 &mut self,
711 ctx: &SpanContext<'a>,
712 line_no: usize,
713 kind: StepKind,
714 ) -> ParseResult<()> {
715 let inline = self.pending_inline_guards.take();
716 self.handle_command(ctx, line_no, kind, inline)
717 }
718
719 fn stash_pending_guard(&mut self, guard: GuardExpr) {
720 self.pending_guards = Some(if let Some(existing) = self.pending_guards.take() {
721 GuardExpr::all(vec![existing, guard])
722 } else {
723 guard
724 });
725 }
726
727 fn start_guard_block_from_pending(
728 &mut self,
729 ctx: &SpanContext,
730 line_no: usize,
731 ) -> ParseResult<()> {
732 let guards = self.pending_guards.take().ok_or_else(|| {
733 ParseError::structural(
734 "guard",
735 format!("line {}: '{{' without a pending guard", line_no),
736 ctx,
737 )
738 })?;
739 if !self.pending_can_open_block {
740 return Err(ParseError::structural(
741 "guard",
742 format!("line {}: '{{' must directly follow a guard", line_no),
743 ctx,
744 ));
745 }
746 self.pending_can_open_block = false;
747 self.enter_guard_block(guards, line_no)
748 }
749
750 fn enter_guard_block(&mut self, guard: GuardExpr, line_no: usize) -> ParseResult<()> {
751 let composed = if let Some(pending) = self.pending_guards.take() {
752 GuardExpr::all(vec![pending, guard])
753 } else {
754 guard
755 };
756 let parent = self.guard_stack.last().cloned().unwrap_or(None);
757 let next = and_guard_exprs(parent, Some(composed));
758 self.guard_stack.push(next);
759 self.scope_stack.push(ScopeFrame {
760 line_no,
761 had_command: false,
762 });
763 self.pending_scope_enters += 1;
764 Ok(())
765 }
766
767 fn begin_io_block(
768 &mut self,
769 ctx: &SpanContext<'a>,
770 line_no: usize,
771 bindings: Vec<IoBinding>,
772 guards: Option<GuardExpr>,
773 ) -> ParseResult<()> {
774 if self.pending_io_block.is_some() {
775 return Err(ParseError::structural(
776 "with_io",
777 format!(
778 "line {}: previous WITH_IO block is still waiting for '{{'",
779 line_no
780 ),
781 ctx,
782 ));
783 }
784 self.pending_io_block = Some(PendingIoBlock {
785 line_no,
786 span: ctx.clone(),
787 bindings,
788 guards,
789 });
790 Ok(())
791 }
792
793 fn start_block(&mut self, ctx: &SpanContext, line_no: usize) -> ParseResult<()> {
794 if let Some(pending) = self.pending_io_block.take() {
795 self.block_stack.push(BlockKind::Io);
796 self.io_scope_stack.push(IoScopeFrame {
797 line_no: pending.line_no,
798 had_command: false,
799 bindings: pending.bindings,
800 guards: pending.guards,
801 first_step: self.steps.len(),
802 });
803 Ok(())
804 } else {
805 self.start_guard_block_from_pending(ctx, line_no)?;
806 self.block_stack.push(BlockKind::Guard);
807 Ok(())
808 }
809 }
810
811 fn end_block(&mut self, ctx: &SpanContext, line_no: usize) -> ParseResult<()> {
812 let kind = self.block_stack.pop().ok_or_else(|| {
813 ParseError::structural("block", format!("line {}: unexpected '}}'", line_no), ctx)
814 })?;
815 match kind {
816 BlockKind::Guard => self.end_guard_block(ctx, line_no),
817 BlockKind::Io => self.end_io_block(ctx, line_no),
818 }
819 }
820
821 fn end_guard_block(&mut self, ctx: &SpanContext, line_no: usize) -> ParseResult<()> {
822 if self.guard_stack.len() == 1 {
823 return Err(ParseError::structural(
824 "guard",
825 format!("line {}: unexpected '}}'", line_no),
826 ctx,
827 ));
828 }
829 if self.pending_guards.is_some() {
830 return Err(ParseError::structural(
831 "guard",
832 format!(
833 "line {}: guard declared immediately before '}}' without a command",
834 line_no
835 ),
836 ctx,
837 ));
838 }
839 let frame = self.scope_stack.last().cloned().ok_or_else(|| {
840 ParseError::structural(
841 "guard",
842 format!("line {}: scope stack underflow", line_no),
843 ctx,
844 )
845 })?;
846 if !frame.had_command {
847 return Err(ParseError::structural(
848 "guard",
849 format!(
850 "line {}: guard block starting on line {} must contain at least one command",
851 line_no, frame.line_no
852 ),
853 ctx,
854 ));
855 }
856 let step = self.steps.last_mut().ok_or_else(|| {
857 ParseError::structural(
858 "guard",
859 format!("line {}: guard block closed without any commands", line_no),
860 ctx,
861 )
862 })?;
863 step.scope_exit += 1;
864 self.scope_stack.pop();
865 self.guard_stack.pop();
866 Ok(())
867 }
868
869 fn end_io_block(&mut self, ctx: &SpanContext, line_no: usize) -> ParseResult<()> {
870 let frame = self.io_scope_stack.pop().ok_or_else(|| {
871 ParseError::structural("with_io", format!("line {}: unexpected '}}'", line_no), ctx)
872 })?;
873 if !frame.had_command {
874 return Err(ParseError::structural(
875 "with_io",
876 format!(
877 "line {}: WITH_IO block starting on line {} must contain at least one command",
878 line_no, frame.line_no
879 ),
880 ctx,
881 ));
882 }
883 if self.steps.len() > frame.first_step {
887 self.steps[frame.first_step].scope_enter += 1;
888 if let Some(last) = self.steps.last_mut() {
889 last.scope_exit += 1;
890 }
891 }
892 Ok(())
893 }
894
895 fn guard_context(&mut self, inline: Option<GuardExpr>) -> Option<GuardExpr> {
896 let mut context = self.guard_stack.last().cloned().unwrap_or(None);
897 if let Some(pending) = self.pending_guards.take() {
898 context = and_guard_exprs(context, Some(pending));
899 self.pending_can_open_block = false;
900 }
901 if let Some(inline_guard) = inline {
902 context = and_guard_exprs(context, Some(inline_guard));
903 }
904 context
905 }
906
907 fn handle_command(
908 &mut self,
909 ctx: &SpanContext<'a>,
910 line_no: usize,
911 kind: StepKind,
912 inline_guards: Option<GuardExpr>,
913 ) -> ParseResult<()> {
914 if let StepKind::WithIoBlock { bindings } = kind {
915 let guards = self.guard_context(inline_guards);
916 self.begin_io_block(ctx, line_no, bindings, guards)?;
917 return Ok(());
918 }
919
920 let guards = self.guard_context(inline_guards);
921 let guards = self.apply_io_guards(guards);
922 let scope_enter = self.pending_scope_enters;
923 self.pending_scope_enters = 0;
924 for frame in self.scope_stack.iter_mut() {
925 frame.had_command = true;
926 }
927 for frame in self.io_scope_stack.iter_mut() {
928 frame.had_command = true;
929 }
930 let kind = self.apply_io_defaults(kind);
931 self.steps.push(Step {
932 guard: guards,
933 kind,
934 scope_enter,
935 scope_exit: 0,
936 });
937 Ok(())
938 }
939
940 fn apply_io_defaults(&self, kind: StepKind) -> StepKind {
941 let defaults = self.current_io_defaults();
942 if defaults.is_empty() {
943 return kind;
944 }
945 match kind {
946 StepKind::WithIo { bindings, cmd } => StepKind::WithIo {
947 bindings: merge_bindings(&defaults, &bindings),
948 cmd,
949 },
950 other => StepKind::WithIo {
951 bindings: defaults,
952 cmd: Box::new(other),
953 },
954 }
955 }
956
957 fn current_io_defaults(&self) -> Vec<IoBinding> {
958 if self.io_scope_stack.is_empty() {
959 return Vec::new();
960 }
961 let mut set = IoBindingSet::default();
962 for frame in &self.io_scope_stack {
963 for binding in &frame.bindings {
964 set.insert(binding.clone());
965 }
966 }
967 set.into_vec()
968 }
969
970 fn apply_io_guards(&self, guard: Option<GuardExpr>) -> Option<GuardExpr> {
971 self.io_scope_stack.iter().fold(guard, |acc, frame| {
972 and_guard_exprs(acc, frame.guards.clone())
973 })
974 }
975}
976
977pub fn parse_script(
978 input: &str,
979 lower: impl Fn(&str, Vec<Arg>) -> ParseResult<StepKind>,
980) -> ParseResult<Vec<Step>> {
981 ScriptParser::new(input, lower)?.parse()
982}
983
984pub fn parse_script_with_preseed(
988 input: &str,
989 lower: impl Fn(&str, Vec<Arg>) -> ParseResult<StepKind>,
990 reserved_names: HashSet<String>,
991 modules: ModuleTable,
992 preseed_funcs: HashSet<String>,
993 preseed_imports: Vec<String>,
994) -> ParseResult<Vec<Step>> {
995 ScriptParser::new_with_preseed(
996 input,
997 lower,
998 reserved_names,
999 modules,
1000 preseed_funcs,
1001 preseed_imports,
1002 )?
1003 .parse()
1004}
1005
1006pub fn parse_script_with_modules(
1011 input: &str,
1012 lower: impl Fn(&str, Vec<Arg>) -> ParseResult<StepKind>,
1013 reserved_names: HashSet<String>,
1014 modules: ModuleTable,
1015) -> ParseResult<Vec<Step>> {
1016 ScriptParser::new_with_modules(input, lower, reserved_names, modules)?.parse()
1017}
1018
1019pub fn parse_guard_expr_str(input: &str) -> ParseResult<GuardExpr> {
1020 use pest::Parser;
1021 let pairs = lexer::LanguageParser::parse(Rule::guard_expr, input).map_err(parse_pest_error)?;
1022 let pair = pairs.into_iter().next().ok_or_else(|| {
1023 ParseError::structural("guard", "empty guard".to_string(), &span_for_line(input, 1))
1024 })?;
1025 let ctx = span_of(&pair, input);
1026 parse_guard_expr(&ctx, pair)
1027}
1028
1029fn and_guard_exprs(left: Option<GuardExpr>, right: Option<GuardExpr>) -> Option<GuardExpr> {
1030 match (left, right) {
1031 (None, None) => None,
1032 (Some(expr), None) | (None, Some(expr)) => Some(expr),
1033 (Some(lhs), Some(rhs)) => Some(GuardExpr::all(vec![lhs, rhs])),
1034 }
1035}
1036
1037fn merge_bindings(defaults: &[IoBinding], overrides: &[IoBinding]) -> Vec<IoBinding> {
1038 let mut set = IoBindingSet::default();
1039 for binding in defaults {
1040 set.insert(binding.clone());
1041 }
1042 for binding in overrides {
1043 set.insert(binding.clone());
1044 }
1045 set.into_vec()
1046}
1047
1048fn contains_inherit_env(kind: &StepKind) -> bool {
1049 match kind {
1050 StepKind::InheritEnv { .. } => true,
1051 StepKind::WithIo { cmd, .. } => contains_inherit_env(cmd),
1052 StepKind::AssignCapture { cmd, .. } => contains_inherit_env(cmd),
1053 StepKind::While { body, .. } | StepKind::FuncDef { body, .. } => {
1054 body.iter().any(|s| contains_inherit_env(&s.kind))
1055 }
1056 StepKind::Timeout { body, .. } | StepKind::AssignAsync { body, .. } => {
1057 body.iter().any(|s| contains_inherit_env(&s.kind))
1058 }
1059 _ => false,
1060 }
1061}
1062
1063fn has_stdout_pipe(bindings: &[IoBinding]) -> bool {
1066 bindings
1067 .iter()
1068 .any(|b| b.stream == IoStream::Stdout && b.pipe.is_some())
1069}
1070
1071fn reject_async_in_capture(ctx: &SpanContext, kind: &StepKind) -> ParseResult<()> {
1074 let bad = match kind {
1075 StepKind::AsyncBlock { .. }
1076 | StepKind::AssignAsync { .. }
1077 | StepKind::Await { .. }
1078 | StepKind::AwaitCapture { .. }
1079 | StepKind::Cancel { .. } => true,
1080 StepKind::WithIo { cmd, .. } => reject_async_in_capture(ctx, cmd).is_err(),
1081 StepKind::Timeout { body, .. } => body
1082 .iter()
1083 .any(|s| reject_async_in_capture(ctx, &s.kind).is_err()),
1084 StepKind::While { body, .. } | StepKind::FuncDef { body, .. } => body
1085 .iter()
1086 .any(|s| reject_async_in_capture(ctx, &s.kind).is_err()),
1087 _ => false,
1088 };
1089 if bad {
1090 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));
1091 }
1092 Ok(())
1093}
1094
1095fn reject_pipe_stdout_in_capture(ctx: &SpanContext, kind: &StepKind) -> ParseResult<()> {
1098 match kind {
1099 StepKind::WithIo { bindings, cmd } => {
1100 if has_stdout_pipe(bindings) {
1101 return Err(ParseError::structural("let", "LET capture cannot use WITH_IO [stdout=pipe:...]; the capture sink owns stdout".to_string(), ctx));
1102 }
1103 reject_pipe_stdout_in_capture(ctx, cmd)
1104 }
1105 StepKind::Timeout { body, .. } => {
1106 for step in body {
1107 reject_pipe_stdout_in_capture(ctx, &step.kind)?;
1108 }
1109 Ok(())
1110 }
1111 StepKind::While { body, .. } | StepKind::FuncDef { body, .. } => {
1112 for step in body {
1113 reject_pipe_stdout_in_capture(ctx, &step.kind)?;
1114 }
1115 Ok(())
1116 }
1117 _ => Ok(()),
1118 }
1119}
1120
1121fn parse_expr_str(ctx: &SpanContext, lctx: &LowerCtx, text: &str) -> ParseResult<Expr> {
1125 use pest::Parser;
1126 let mut pairs = lexer::LanguageParser::parse(Rule::expr, text).map_err(parse_pest_error)?;
1127 let pair = pairs.next().ok_or_else(|| {
1128 ParseError::validation("LET", "LET requires an expression".to_string(), ctx)
1129 })?;
1130 if pair.as_span().end() != text.len() {
1131 return Err(ParseError::structural(
1132 "expr",
1133 format!("invalid LET expression {text:?}"),
1134 ctx,
1135 ));
1136 }
1137 parse_expr(ctx, lctx, pair)
1138}
1139
1140fn parse_structural_command_with_lower(
1141 ctx: &SpanContext,
1142 pair: Pair<Rule>,
1143 lctx: &LowerCtx,
1144) -> ParseResult<StepKind> {
1145 let span = refine_span(ctx, &pair);
1146 let kind = match pair.as_rule() {
1147 Rule::inherit_env_command => {
1148 let mut keys = Vec::new();
1149 for inner in pair.into_inner() {
1150 if inner.as_rule() == Rule::inherit_list {
1151 for key in inner.into_inner() {
1152 if key.as_rule() == Rule::env_key {
1153 keys.push(key.as_str().trim().to_string());
1154 }
1155 }
1156 } else if inner.as_rule() == Rule::env_key {
1157 keys.push(inner.as_str().trim().to_string());
1158 }
1159 }
1160 StepKind::InheritEnv { keys }
1161 }
1162 Rule::with_io_command => {
1163 let mut bindings = Vec::new();
1164 let mut cmd = None;
1165 for inner in pair.into_inner() {
1166 match inner.as_rule() {
1167 Rule::io_flags => {
1168 for flag in inner.into_inner() {
1169 if flag.as_rule() == Rule::io_binding {
1170 bindings.push(parse_io_binding(ctx, flag)?);
1171 }
1172 }
1173 }
1174 Rule::with_io_command => {
1175 cmd = Some(Box::new(parse_structural_command_with_lower(
1176 ctx, inner, lctx,
1177 )?));
1178 }
1179 Rule::inherit_env_command => {
1180 cmd = Some(Box::new(parse_structural_command_with_lower(
1181 ctx, inner, lctx,
1182 )?));
1183 }
1184 Rule::async_statement | Rule::async_statement_block => {
1185 cmd = Some(Box::new(parse_structural_command_with_lower(
1186 ctx, inner, lctx,
1187 )?));
1188 }
1189 Rule::timeout_statement | Rule::cancel_statement => {
1190 cmd = Some(Box::new(parse_structural_command_with_lower(
1191 ctx, inner, lctx,
1192 )?));
1193 }
1194 Rule::bare_call_statement | Rule::while_statement => {
1195 cmd = Some(Box::new(parse_structural_command_with_lower(
1196 ctx, inner, lctx,
1197 )?));
1198 }
1199 Rule::func_def
1200 | Rule::return_statement
1201 | Rule::break_statement
1202 | Rule::continue_statement => {
1203 return Err(ParseError::structural(
1204 "parser",
1205 format!(
1206 "WITH_IO cannot wrap {:?}; place it around a command or block instead",
1207 inner.as_rule()
1208 ),
1209 &span,
1210 ));
1211 }
1212 Rule::instruction | Rule::instruction_inner => {
1213 cmd = Some(Box::new(lower_instruction_pair(ctx, inner, lctx)?));
1214 }
1215 Rule::run_exec_statement | Rule::run_exec_inner => {
1216 cmd = Some(Box::new(lower_run_exec_pair(ctx, inner, lctx)?));
1217 }
1218 _ => {}
1219 }
1220 }
1221 if let Some(cmd) = cmd {
1222 StepKind::WithIo { bindings, cmd }
1223 } else {
1224 StepKind::WithIoBlock { bindings }
1225 }
1226 }
1227 Rule::for_statement => parse_for_statement_from_pair(ctx, pair, lctx)?,
1228 Rule::while_statement => parse_while_statement_from_pair(ctx, pair, lctx)?,
1229 Rule::func_def => parse_func_def_from_pair(ctx, pair, lctx)?,
1230 Rule::bare_call_statement => parse_bare_call_from_pair(ctx, lctx, pair)?,
1231 Rule::return_statement => parse_return_statement_from_pair(ctx, lctx, pair)?,
1232 Rule::break_statement => StepKind::Break,
1233 Rule::continue_statement => StepKind::Continue,
1234 Rule::let_statement => parse_let_statement_from_pair(ctx, lctx, pair)?,
1235 Rule::mutate_statement => parse_mutate_statement_from_pair(ctx, lctx, pair)?,
1236 Rule::let_async_statement => parse_let_async_statement_from_pair(ctx, pair, lctx)?,
1237 Rule::let_capture_statement => parse_let_capture_statement_from_pair(ctx, pair, lctx)?,
1238 Rule::await_statement => parse_await_statement_from_pair(ctx, pair)?,
1239 Rule::cancel_statement => parse_cancel_statement_from_pair(ctx, pair)?,
1240 Rule::if_statement => parse_if_statement_from_pair(ctx, pair, lctx)?,
1241 Rule::async_statement => parse_async_statement_from_pair(ctx, pair, lctx)?,
1242 Rule::async_statement_block => parse_async_statement_block_from_pair(ctx, pair, lctx)?,
1243 Rule::timeout_statement => parse_timeout_statement_from_pair(ctx, pair, lctx)?,
1244 Rule::command_inner => {
1245 let inner = pair.into_inner().next().ok_or_else(|| {
1248 ParseError::structural("parser", "empty command_inner".to_string(), &span)
1249 })?;
1250 parse_structural_command_with_lower(ctx, inner, lctx)?
1251 }
1252 Rule::instruction | Rule::instruction_inner => lower_instruction_pair(ctx, pair, lctx)?,
1253 Rule::run_exec_statement | Rule::run_exec_inner => lower_run_exec_pair(ctx, pair, lctx)?,
1254 _ => {
1255 return Err(ParseError::structural(
1256 "parser",
1257 format!("unexpected structural command rule: {:?}", pair.as_rule()),
1258 &span,
1259 ));
1260 }
1261 };
1262 Ok(kind)
1263}
1264
1265fn extract_instruction(
1266 ctx: &SpanContext,
1267 pair: Pair<Rule>,
1268 lctx: &LowerCtx,
1269) -> ParseResult<(String, Vec<InsToken>)> {
1270 let span = refine_span(ctx, &pair);
1271 let mut name = None;
1272 let mut args = Vec::new();
1273 for inner in pair.into_inner() {
1274 match inner.as_rule() {
1275 Rule::command_name => {
1276 name = Some(inner.as_str().to_string());
1277 }
1278 Rule::argument => {
1279 args.extend(
1280 parse_argument(ctx, lctx, inner)?
1281 .into_iter()
1282 .map(InsToken::Pos),
1283 );
1284 }
1285 Rule::assignment => {
1286 let (key, value) = parse_assignment(ctx, lctx, inner)?;
1287 args.push(InsToken::Assign(key, value));
1288 }
1289 _ => {}
1290 }
1291 }
1292 let name = name.ok_or_else(|| {
1293 ParseError::structural(
1294 "instruction",
1295 "instruction missing command name".to_string(),
1296 &span,
1297 )
1298 })?;
1299 Ok((name, args))
1300}
1301
1302enum InsToken {
1307 Pos(Arg),
1308 Assign(String, Arg),
1309}
1310
1311fn lower_instruction_pair(
1316 ctx: &SpanContext,
1317 pair: Pair<Rule>,
1318 lctx: &LowerCtx,
1319) -> ParseResult<StepKind> {
1320 let span = refine_span(ctx, &pair);
1321 let (name, tokens) = extract_instruction(ctx, pair, lctx)?;
1322 if name == "ENV" {
1323 return lower_env_command(ctx, tokens);
1324 }
1325 if name == "EXPAND" {
1326 return lower_expand_command(ctx, tokens);
1327 }
1328 let args = tokens
1329 .into_iter()
1330 .map(|token| match token {
1331 InsToken::Pos(arg) => arg,
1332 InsToken::Assign(key, value) => crate::commands::canonical_assignment_arg(&key, &value),
1333 })
1334 .collect();
1335 (lctx.lower)(&name, args).map_err(|e| e.with_span(&span))
1336}
1337
1338fn lower_run_exec_pair(
1344 ctx: &SpanContext,
1345 pair: Pair<Rule>,
1346 lctx: &LowerCtx,
1347) -> ParseResult<StepKind> {
1348 let span = refine_span(ctx, &pair);
1349 let mut list = None;
1350 for inner in pair.into_inner() {
1351 if inner.as_rule() == Rule::run_exec_list {
1352 list = Some(parse_run_exec_list(ctx, lctx, inner)?);
1353 }
1354 }
1355 let list = list.ok_or_else(|| {
1356 ParseError::structural(
1357 "run_exec",
1358 "RUN exec form missing list literal".to_string(),
1359 &span,
1360 )
1361 })?;
1362 (lctx.lower)("RUN", vec![Arg::Expr(list)]).map_err(|e| e.with_span(&span))
1363}
1364
1365fn parse_run_exec_list(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
1370 let mut items = Vec::new();
1371 for inner in pair.into_inner() {
1372 if inner.as_rule() == Rule::run_exec_arg {
1373 let item = parse_run_exec_arg(ctx, lctx, inner)?;
1374 reject_boundary(ctx, &item)?;
1375 items.push(item);
1376 }
1377 }
1378 Ok(Expr::List(items))
1379}
1380
1381fn parse_run_exec_arg(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
1382 let span = refine_span(ctx, &pair);
1383 let inner = pair.into_inner().next().ok_or_else(|| {
1384 ParseError::structural("run_exec", "RUN exec argument is empty".to_string(), &span)
1385 })?;
1386 match inner.as_rule() {
1387 Rule::parenthesized_expr => parse_expr_inner(ctx, lctx, inner.into_inner().next().unwrap()),
1388 Rule::func_call => parse_func_call(ctx, lctx, inner),
1389 Rule::key_path => parse_key_path(ctx, inner),
1390 Rule::variable => {
1391 let name = inner.as_str();
1392 let name = name.strip_prefix('$').unwrap_or(name).to_string();
1393 Ok(Expr::Var(name))
1394 }
1395 Rule::env_read => parse_env_read(ctx, inner).map(Expr::Env),
1396 Rule::pipe_read => parse_pipe_read(ctx, inner).map(|name| Expr::Literal(Value::pipe(name))),
1397 Rule::list_literal => parse_list_literal(ctx, lctx, inner),
1398 Rule::map_literal => parse_map_literal(ctx, lctx, inner),
1399 Rule::string_literal | Rule::quoted_string => {
1400 let s = parse_quoted_string(inner)?;
1401 Ok(Expr::Literal(Value::string(s)))
1402 }
1403 Rule::numeric_literal => parse_numeric_literal(ctx, inner),
1404 Rule::bare_word => {
1405 let s = inner.as_str().to_string();
1406 match s.as_str() {
1407 "true" => Ok(Expr::Literal(Value::bool(true))),
1408 "false" => Ok(Expr::Literal(Value::bool(false))),
1409 _ => Ok(Expr::Literal(Value::string(s))),
1410 }
1411 }
1412 _ => Err(ParseError::structural(
1413 "run_exec",
1414 format!("unexpected RUN exec argument rule: {:?}", inner.as_rule()),
1415 &span,
1416 )),
1417 }
1418}
1419
1420fn parse_assignment(
1422 ctx: &SpanContext,
1423 lctx: &LowerCtx,
1424 pair: Pair<Rule>,
1425) -> ParseResult<(String, Arg)> {
1426 let span = refine_span(ctx, &pair);
1427 let mut key = None;
1428 let mut value = None;
1429 for inner in pair.into_inner() {
1430 match inner.as_rule() {
1431 Rule::assign_key => {
1432 key = Some(inner.as_str().to_string());
1433 }
1434 Rule::assign_value => {
1435 value = Some(lower_command_value(ctx, lctx, inner)?);
1436 }
1437 _ => {
1438 return Err(ParseError::structural(
1439 "assignment",
1440 format!("unexpected assignment rule: {:?}", inner.as_rule()),
1441 &span,
1442 ));
1443 }
1444 }
1445 }
1446 Ok((
1447 key.ok_or_else(|| {
1448 ParseError::structural("assignment", "assignment missing key".to_string(), &span)
1449 })?,
1450 value.unwrap_or(Arg::String(String::new(), false)),
1451 ))
1452}
1453
1454fn lower_command_value(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Arg> {
1459 let span = refine_span(ctx, &pair);
1460 let inner = pair.into_inner().next().ok_or_else(|| {
1461 ParseError::structural("assignment", "assignment value is empty".to_string(), &span)
1462 })?;
1463 match inner.as_rule() {
1464 Rule::quoted_string => Ok(Arg::String(parse_quoted_string(inner)?, true)),
1465 Rule::assign_expr => {
1466 let shape = inner.into_inner().next().ok_or_else(|| {
1467 ParseError::structural(
1468 "assignment",
1469 "assignment expression is empty".to_string(),
1470 &span,
1471 )
1472 })?;
1473 match shape.as_rule() {
1474 Rule::variable => Ok(Arg::Expr(Expr::Var(parse_dollar_ident(shape)))),
1475 Rule::key_path => Ok(Arg::Expr(parse_key_path(ctx, shape)?)),
1476 Rule::env_read => Ok(Arg::Expr(Expr::Env(parse_env_read(ctx, shape)?))),
1477 Rule::func_call => Ok(Arg::Expr(parse_func_call(ctx, lctx, shape)?)),
1478 other => Err(ParseError::structural(
1479 "assignment",
1480 format!("unexpected assignment expression shape: {:?}", other),
1481 &span,
1482 )),
1483 }
1484 }
1485 Rule::raw_fragments => lower_raw_fragments(ctx, inner),
1486 other => Err(ParseError::structural(
1487 "assignment",
1488 format!("unexpected assignment value rule: {:?}", other),
1489 &span,
1490 )),
1491 }
1492}
1493
1494fn lower_raw_fragments(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Arg> {
1500 let span = refine_span(ctx, &pair);
1501 let mut body = String::new();
1502 for fragment in pair.into_inner() {
1503 match fragment.as_rule() {
1504 Rule::quoted_string => body.push_str(&parse_quoted_string(fragment)?),
1505 Rule::templated_arg => body.push_str(fragment.as_str()),
1506 Rule::raw_text => body.push_str(&collapse_ws(fragment.as_str())),
1507 other => {
1508 return Err(ParseError::structural(
1509 "assignment",
1510 format!("unexpected raw value fragment: {:?}", other),
1511 &span,
1512 ));
1513 }
1514 }
1515 }
1516 Ok(Arg::String(body.trim().to_string(), false))
1517}
1518
1519fn collapse_ws(s: &str) -> String {
1522 let mut out = String::with_capacity(s.len());
1523 let mut in_run = false;
1524 for c in s.chars() {
1525 if c.is_whitespace() {
1526 if !in_run {
1527 out.push(' ');
1528 in_run = true;
1529 }
1530 } else {
1531 out.push(c);
1532 in_run = false;
1533 }
1534 }
1535 out
1536}
1537
1538fn lower_env_command(ctx: &SpanContext, tokens: Vec<InsToken>) -> ParseResult<StepKind> {
1542 if tokens.is_empty() {
1543 return Err(ParseError::validation(
1544 "ENV",
1545 "ENV requires KEY=value".to_string(),
1546 ctx,
1547 ));
1548 }
1549 match tokens.as_slice() {
1550 [InsToken::Assign(key, value)] => {
1551 ArgType::KeyValue
1554 .check_arg(&Arg::String(format!("{key}={}", value.render()), false))
1555 .map_err(|e| ParseError::validation("ENV", e.to_string(), ctx))?;
1556 Ok(StepKind::Env {
1557 key: key.clone(),
1558 value: value.clone(),
1559 })
1560 }
1561 [InsToken::Pos(Arg::String(text, _))] => match crate::command::split_assignment(text)
1562 .map_err(|e| ParseError::validation("ENV", e.to_string(), ctx))?
1563 {
1564 Some((key, value)) => Ok(StepKind::Env { key, value }),
1565 None => Err(ParseError::validation(
1566 "ENV",
1567 "ENV requires KEY=value format".to_string(),
1568 ctx,
1569 )),
1570 },
1571 _ => Err(ParseError::validation(
1572 "ENV",
1573 "ENV requires KEY=value format".to_string(),
1574 ctx,
1575 )),
1576 }
1577}
1578
1579fn lower_expand_command(ctx: &SpanContext, tokens: Vec<InsToken>) -> ParseResult<StepKind> {
1583 let mut path = None;
1584 let mut overrides = Vec::new();
1585 for token in tokens {
1586 match token {
1587 InsToken::Assign(key, value) => {
1588 if key.is_empty() {
1589 return Err(ParseError::validation(
1590 "EXPAND",
1591 "EXPAND requires KEY=value format for overrides".to_string(),
1592 ctx,
1593 ));
1594 }
1595 overrides.push((key, value));
1596 }
1597 InsToken::Pos(arg) => match &arg {
1598 Arg::String(text, quoted) if !quoted && text.contains('=') => {
1599 let Some((key, value)) = crate::command::split_assignment(text)
1600 .map_err(|e| ParseError::validation("EXPAND", e.to_string(), ctx))?
1601 else {
1602 return Err(ParseError::validation(
1603 "EXPAND",
1604 "EXPAND requires KEY=value format for overrides".to_string(),
1605 ctx,
1606 ));
1607 };
1608 overrides.push((key, value));
1609 }
1610 _ => {
1611 if path.is_none() {
1612 ArgType::Path
1616 .check_arg(&arg)
1617 .map_err(|e| ParseError::validation("EXPAND", e.to_string(), ctx))?;
1618 path = Some(arg);
1619 } else {
1620 return Err(ParseError::validation(
1621 "EXPAND",
1622 "EXPAND accepts at most one path".to_string(),
1623 ctx,
1624 ));
1625 }
1626 }
1627 },
1628 }
1629 }
1630 Ok(StepKind::Expand { path, overrides })
1631}
1632
1633fn parse_type_tag(pair: Pair<Rule>) -> String {
1634 pair.as_str().trim().to_string()
1637}
1638
1639fn check_func_ident(ctx: &SpanContext, name: &str) -> ParseResult<()> {
1640 let ok = name
1641 .chars()
1642 .next()
1643 .map(|c| c.is_ascii_uppercase())
1644 .unwrap_or(false)
1645 && name
1646 .chars()
1647 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_');
1648 if !ok {
1649 return Err(ParseError::validation(
1650 "FUNC",
1651 format!(
1652 "function names must be UPPERCASE (ASCII_ALPHA_UPPER, digits, _), got `{name}`"
1653 ),
1654 ctx,
1655 ));
1656 }
1657 Ok(())
1658}
1659
1660fn parse_while_statement_from_pair(
1661 ctx: &SpanContext,
1662 pair: Pair<Rule>,
1663 lctx: &LowerCtx,
1664) -> ParseResult<StepKind> {
1665 let span = refine_span(ctx, &pair);
1666 let mut cond = None;
1667 let mut body = None;
1668 for inner in pair.into_inner() {
1669 match inner.as_rule() {
1670 Rule::expr => {
1671 if cond.is_none() {
1672 cond = Some(parse_expr(ctx, lctx, inner)?);
1673 }
1674 }
1675 Rule::block => {
1676 body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
1677 }
1678 _ => {}
1679 }
1680 }
1681 Ok(StepKind::While {
1682 cond: Box::new(cond.ok_or_else(|| {
1683 ParseError::validation("WHILE", "WHILE requires a condition".to_string(), &span)
1684 })?),
1685 body: body.ok_or_else(|| {
1686 ParseError::validation("WHILE", "WHILE requires a block".to_string(), &span)
1687 })?,
1688 })
1689}
1690
1691fn parse_func_def_from_pair(
1692 ctx: &SpanContext,
1693 pair: Pair<Rule>,
1694 lctx: &LowerCtx,
1695) -> ParseResult<StepKind> {
1696 let span = refine_span(ctx, &pair);
1697 let def_name = pair
1702 .clone()
1703 .into_inner()
1704 .find(|inner| inner.as_rule() == Rule::func_ident)
1705 .map(|inner| inner.as_str().to_string())
1706 .ok_or_else(|| ParseError::validation("FUNC", "FUNC requires a name".to_string(), &span))?;
1707 check_func_ident(ctx, &def_name)?;
1708 if lctx.reserved_names.contains(&def_name) || lctx.module_base_names().contains(&def_name) {
1715 return Err(ParseError::validation(
1716 "FUNC",
1717 format!("FUNC {def_name} cannot shadow reserved function `{def_name}`"),
1718 &span,
1719 ));
1720 }
1721 if !lctx.declare_func(&def_name) {
1722 return Err(ParseError::validation(
1723 "FUNC",
1724 format!("duplicate function `{def_name}` in same scope"),
1725 &span,
1726 ));
1727 }
1728 let mut name: Option<String> = None;
1729 let mut param_names: Vec<String> = Vec::new();
1730 let mut param_types: Vec<String> = Vec::new();
1731 let mut body = None;
1732 for inner in pair.into_inner() {
1733 match inner.as_rule() {
1734 Rule::func_ident => {
1735 if name.is_none() {
1736 name = Some(inner.as_str().to_string());
1737 }
1738 }
1739 Rule::func_param => {
1740 let mut pname = None;
1741 let mut ptype = None;
1742 for part in inner.into_inner() {
1743 match part.as_rule() {
1744 Rule::dollar_ident => {
1745 pname = Some(parse_dollar_ident(part));
1746 }
1747 Rule::type_tag => {
1748 ptype = Some(parse_type_tag(part));
1749 }
1750 _ => {}
1751 }
1752 }
1753 param_names.push(pname.ok_or_else(|| {
1754 ParseError::validation(
1755 "FUNC",
1756 "FUNC parameter requires a $variable".to_string(),
1757 &span,
1758 )
1759 })?);
1760 param_types.push(ptype.ok_or_else(|| {
1761 ParseError::validation(
1762 "FUNC",
1763 "FUNC parameters require explicit types: FUNC NAME($p: TYPE, ...)"
1764 .to_string(),
1765 &span,
1766 )
1767 })?);
1768 }
1769 Rule::block => {
1770 body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
1771 }
1772 _ => {}
1773 }
1774 }
1775 let name = name
1776 .ok_or_else(|| ParseError::validation("FUNC", "FUNC requires a name".to_string(), &span))?;
1777 check_func_ident(ctx, &name)?;
1778 if param_names.len() != param_types.len() {
1779 return Err(ParseError::validation(
1780 "FUNC",
1781 format!("FUNC {name} has mismatched parameter names and types"),
1782 &span,
1783 ));
1784 }
1785 let mut seen = std::collections::HashSet::new();
1786 for pname in ¶m_names {
1787 if !seen.insert(pname.clone()) {
1788 return Err(ParseError::validation(
1789 "FUNC",
1790 format!("FUNC {name} declares duplicate parameter ${pname}"),
1791 &span,
1792 ));
1793 }
1794 }
1795 Ok(StepKind::FuncDef {
1796 name,
1797 params: param_names.into_iter().zip(param_types).collect(),
1798 body: body.ok_or_else(|| {
1799 ParseError::validation("FUNC", "FUNC requires a block".to_string(), &span)
1800 })?,
1801 })
1802}
1803
1804fn parse_bare_call_from_pair(
1805 ctx: &SpanContext,
1806 lctx: &LowerCtx,
1807 pair: Pair<Rule>,
1808) -> ParseResult<StepKind> {
1809 let span = refine_span(ctx, &pair);
1810 let mut name: Option<String> = None;
1811 let mut args = Vec::new();
1812 for inner in pair.into_inner() {
1816 match inner.as_rule() {
1817 Rule::call_head_paren => {
1818 if name.is_none() {
1819 let text = inner.as_str();
1820 name = Some(text.strip_suffix('(').unwrap_or(text).to_string());
1821 }
1822 }
1823 Rule::func_call_head => {
1824 if name.is_none() {
1825 name = Some(inner.as_str().to_string());
1826 }
1827 }
1828 Rule::expr => {
1829 args.push(parse_expr(ctx, lctx, inner)?);
1830 }
1831 _ => {}
1832 }
1833 }
1834 let name = name.ok_or_else(|| {
1835 ParseError::validation(
1836 "FUNC",
1837 "function call requires a function name".to_string(),
1838 &span,
1839 )
1840 })?;
1841 if !name.contains(MODULE_SEPARATOR) {
1844 check_func_ident(ctx, &name)?;
1845 }
1846 let qualified = lctx.resolve_call(&span, &name)?;
1847 Ok(StepKind::Call {
1848 name: qualified,
1849 args,
1850 })
1851}
1852
1853fn parse_return_statement_from_pair(
1854 ctx: &SpanContext,
1855 lctx: &LowerCtx,
1856 pair: Pair<Rule>,
1857) -> ParseResult<StepKind> {
1858 use crate::ast::Value;
1859 for inner in pair.into_inner() {
1860 if inner.as_rule() == Rule::expr {
1861 return Ok(StepKind::Return {
1862 expr: Box::new(parse_expr(ctx, lctx, inner)?),
1863 });
1864 }
1865 }
1866 Ok(StepKind::Return {
1867 expr: Box::new(Expr::Literal(Value::string(String::new()))),
1868 })
1869}
1870
1871fn parse_for_statement_from_pair(
1872 ctx: &SpanContext,
1873 pair: Pair<Rule>,
1874 lctx: &LowerCtx,
1875) -> ParseResult<StepKind> {
1876 let span = refine_span(ctx, &pair);
1877 let mut idents: Vec<String> = Vec::new();
1878 let mut types: Vec<String> = Vec::new();
1879 let mut type_spans: Vec<SpanContext> = Vec::new();
1880 let mut in_expr = None;
1881 let mut body_steps = Vec::new();
1882 for inner in pair.into_inner() {
1883 match inner.as_rule() {
1884 Rule::dollar_ident => {
1885 idents.push(parse_dollar_ident(inner));
1886 }
1887 Rule::type_tag => {
1888 type_spans.push(refine_span(ctx, &inner));
1889 types.push(parse_type_tag(inner));
1890 }
1891 Rule::expr => {
1892 in_expr = Some(parse_expr(ctx, lctx, inner)?);
1893 }
1894 Rule::block => {
1895 body_steps = parse_block_elements_with_lower(ctx, inner, lctx)?;
1896 }
1897 _ => {}
1898 }
1899 }
1900 if idents.len() != types.len() {
1901 return Err(ParseError::validation(
1902 "FOR",
1903 format!(
1904 "FOR requires explicit types: FOR $item: TYPE IN <expr> (got {} vars, {} types)",
1905 idents.len(),
1906 types.len()
1907 ),
1908 &span,
1909 ));
1910 }
1911 let (key_var, key_type, var, var_type) = match idents.len() {
1912 1 => (
1913 None,
1914 None,
1915 idents.into_iter().next().unwrap(),
1916 types.into_iter().next().unwrap(),
1917 ),
1918 2 => {
1919 let mut iv = idents.into_iter();
1920 let mut tv = types.into_iter();
1921 (
1922 Some(iv.next().unwrap()),
1923 Some(tv.next().unwrap()),
1924 iv.next().unwrap(),
1925 tv.next().unwrap(),
1926 )
1927 }
1928 _ => {
1929 return Err(ParseError::validation(
1930 "FOR",
1931 "FOR requires one or two variables".to_string(),
1932 &span,
1933 ));
1934 }
1935 };
1936 if let Some(kt) = &key_type
1937 && kt != "STRING"
1938 && kt != "INT"
1939 {
1940 let at = type_spans.first().unwrap_or(&span);
1942 return Err(ParseError::validation(
1943 "FOR",
1944 format!("FOR key variable must be INT or STRING, got {kt}"),
1945 at,
1946 ));
1947 }
1948 Ok(StepKind::For {
1949 key_var,
1950 key_type,
1951 var,
1952 var_type,
1953 in_expr: in_expr.ok_or_else(|| {
1954 ParseError::validation(
1955 "FOR",
1956 "FOR requires an iterable expression".to_string(),
1957 &span,
1958 )
1959 })?,
1960 body: body_steps,
1961 })
1962}
1963
1964fn parse_let_statement_from_pair(
1965 ctx: &SpanContext,
1966 lctx: &LowerCtx,
1967 pair: Pair<Rule>,
1968) -> ParseResult<StepKind> {
1969 let span = refine_span(ctx, &pair);
1970 let mut var = None;
1971 let mut decl_type = None;
1972 let mut expr = None;
1973 for inner in pair.into_inner() {
1974 match inner.as_rule() {
1975 Rule::dollar_ident => {
1976 var = Some(parse_dollar_ident(inner));
1977 }
1978 Rule::type_tag => {
1979 decl_type = Some(parse_type_tag(inner));
1980 }
1981 Rule::expr => {
1982 expr = Some(parse_expr(ctx, lctx, inner)?);
1983 }
1984 _ => {}
1985 }
1986 }
1987 Ok(StepKind::Assign {
1988 var: var.ok_or_else(|| {
1989 ParseError::validation("LET", "LET requires a variable".to_string(), &span)
1990 })?,
1991 decl_type: decl_type.ok_or_else(|| {
1992 ParseError::validation(
1993 "LET",
1994 "LET requires explicit type: LET $var: TYPE = <expr>".to_string(),
1995 &span,
1996 )
1997 })?,
1998 expr: expr.ok_or_else(|| {
1999 ParseError::validation("LET", "LET requires an expression".to_string(), &span)
2000 })?,
2001 })
2002}
2003
2004fn parse_mutate_statement_from_pair(
2005 ctx: &SpanContext,
2006 lctx: &LowerCtx,
2007 pair: Pair<Rule>,
2008) -> ParseResult<StepKind> {
2009 let span = refine_span(ctx, &pair);
2010 let mut var = None;
2011 let mut expr = None;
2012 for inner in pair.into_inner() {
2013 match inner.as_rule() {
2014 Rule::dollar_ident => {
2015 var = Some(parse_dollar_ident(inner));
2016 }
2017 Rule::expr => {
2018 expr = Some(parse_expr(ctx, lctx, inner)?);
2019 }
2020 _ => {}
2021 }
2022 }
2023 Ok(StepKind::Set {
2024 var: var.ok_or_else(|| {
2025 ParseError::validation(
2026 "mutate",
2027 "mutation requires a variable: $var = <expr>".to_string(),
2028 &span,
2029 )
2030 })?,
2031 expr: expr.ok_or_else(|| {
2032 ParseError::validation(
2033 "mutate",
2034 "mutation requires an expression: $var = <expr>".to_string(),
2035 &span,
2036 )
2037 })?,
2038 })
2039}
2040
2041fn parse_let_async_statement_from_pair(
2042 ctx: &SpanContext,
2043 pair: Pair<Rule>,
2044 lctx: &LowerCtx,
2045) -> ParseResult<StepKind> {
2046 let span = refine_span(ctx, &pair);
2047 let mut var = None;
2048 let mut decl_type: Option<String> = None;
2049 let mut body = None;
2050 for inner in pair.into_inner() {
2051 match inner.as_rule() {
2052 Rule::dollar_ident => {
2053 var = Some(parse_dollar_ident(inner));
2054 }
2055 Rule::type_tag => {
2056 decl_type = Some(parse_type_tag(inner));
2057 }
2058 Rule::block => {
2059 body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
2060 }
2061 Rule::command_inner => {
2062 let inner = inner.into_inner().next().ok_or_else(|| {
2065 ParseError::structural("let", "empty command_inner".to_string(), &span)
2066 })?;
2067 let step_kind = parse_structural_command_with_lower(ctx, inner, lctx)?;
2068 body = Some(vec![Step {
2069 guard: None,
2070 kind: step_kind,
2071 scope_enter: 0,
2072 scope_exit: 0,
2073 }]);
2074 }
2075 Rule::with_io_command => {
2076 let kind = parse_structural_command_with_lower(ctx, inner, lctx)?;
2086 let StepKind::WithIo { bindings, cmd } = kind else {
2087 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));
2088 };
2089 match *cmd {
2090 StepKind::AsyncBlock { body: async_body } => {
2091 if async_body.len() != 1 {
2092 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));
2093 }
2094 let step = async_body.into_iter().next().ok_or_else(|| {
2095 ParseError::validation(
2096 "LET",
2097 "LET $var: HANDLE = ASYNC requires a body".to_string(),
2098 &span,
2099 )
2100 })?;
2101 body = Some(vec![Step {
2102 guard: step.guard,
2103 kind: StepKind::WithIo {
2104 bindings,
2105 cmd: Box::new(step.kind),
2106 },
2107 scope_enter: step.scope_enter,
2108 scope_exit: step.scope_exit,
2109 }]);
2110 }
2111 sync_cmd => {
2112 if has_stdout_pipe(&bindings) {
2113 return Err(ParseError::structural("let", "LET capture cannot use WITH_IO [stdout=pipe:...]; the capture sink owns stdout".to_string(), &span));
2114 }
2115 reject_async_in_capture(ctx, &sync_cmd)?;
2116 let name = var.clone().ok_or_else(|| {
2117 ParseError::validation(
2118 "LET",
2119 "LET $var: TYPE = WITH_IO requires a variable".to_string(),
2120 &span,
2121 )
2122 })?;
2123 let dtype = decl_type.ok_or_else(|| {
2124 ParseError::validation(
2125 "LET",
2126 "LET requires explicit type: LET $var: TYPE = ...".to_string(),
2127 &span,
2128 )
2129 })?;
2130 return Ok(StepKind::AssignCapture {
2131 var: name,
2132 decl_type: dtype,
2133 cmd: Box::new(StepKind::WithIo {
2134 bindings,
2135 cmd: Box::new(sync_cmd),
2136 }),
2137 });
2138 }
2139 }
2140 }
2141 _ => {}
2142 }
2143 }
2144 Ok(StepKind::AssignAsync {
2145 var: var.ok_or_else(|| {
2146 ParseError::validation(
2147 "LET",
2148 "LET $var: HANDLE = ASYNC requires a variable".to_string(),
2149 &span,
2150 )
2151 })?,
2152 decl_type: decl_type.ok_or_else(|| {
2153 ParseError::validation(
2154 "LET",
2155 "LET requires explicit type: LET $var: TYPE = ...".to_string(),
2156 &span,
2157 )
2158 })?,
2159 body: body.ok_or_else(|| {
2160 ParseError::validation(
2161 "LET",
2162 "LET $var: HANDLE = ASYNC requires a body".to_string(),
2163 &span,
2164 )
2165 })?,
2166 })
2167}
2168
2169fn parse_let_capture_statement_from_pair(
2177 ctx: &SpanContext,
2178 pair: Pair<Rule>,
2179 lctx: &LowerCtx,
2180) -> ParseResult<StepKind> {
2181 let span = refine_span(ctx, &pair);
2182 use pest::Parser;
2183 let mut var = None;
2184 let mut decl_type: Option<String> = None;
2185 let mut await_pair = None;
2186 let mut timeout_pair = None;
2187 let mut instruction_pair = None;
2188 for inner in pair.into_inner() {
2189 match inner.as_rule() {
2190 Rule::dollar_ident => {
2191 var = Some(parse_dollar_ident(inner));
2192 }
2193 Rule::type_tag => {
2194 decl_type = Some(parse_type_tag(inner));
2195 }
2196 Rule::await_statement => {
2197 await_pair = Some(inner);
2198 }
2199 Rule::timeout_statement => {
2200 timeout_pair = Some(inner);
2201 }
2202 Rule::instruction => {
2203 instruction_pair = Some(inner);
2204 }
2205 _ => {}
2206 }
2207 }
2208 let var = var.ok_or_else(|| {
2209 ParseError::validation("LET", "LET requires a variable".to_string(), &span)
2210 })?;
2211 let dtype: String = decl_type.ok_or_else(|| {
2212 ParseError::validation(
2213 "LET",
2214 "LET requires explicit type: LET $var: TYPE = ...".to_string(),
2215 &span,
2216 )
2217 })?;
2218 if let Some(awaited) = await_pair {
2219 let mut task_var = None;
2220 for inner in awaited.into_inner() {
2221 if inner.as_rule() == Rule::ident {
2222 task_var = Some(inner.as_str().to_string());
2223 }
2224 }
2225 return Ok(StepKind::AwaitCapture {
2226 out_var: var,
2227 out_type: dtype,
2228 task_var: task_var.ok_or_else(|| {
2229 ParseError::validation(
2230 "LET",
2231 "LET $out = AWAIT requires a task variable".to_string(),
2232 &span,
2233 )
2234 })?,
2235 });
2236 }
2237 if let Some(timeouted) = timeout_pair {
2238 let kind = parse_structural_command_with_lower(ctx, timeouted, lctx)?;
2239 reject_async_in_capture(ctx, &kind)?;
2240 reject_pipe_stdout_in_capture(ctx, &kind)?;
2241 return Ok(StepKind::AssignCapture {
2242 var,
2243 decl_type: dtype,
2244 cmd: Box::new(kind),
2245 });
2246 }
2247 if let Some(ins) = instruction_pair {
2248 let text = ins.as_str().to_string();
2249 let mut lead = None;
2250 for token in ins.into_inner() {
2251 if token.as_rule() == Rule::command_name {
2252 lead = Some(token.as_str().to_string());
2253 break;
2254 }
2255 }
2256 let lead = lead.ok_or_else(|| {
2257 ParseError::validation("LET", "LET capture requires a command".to_string(), &span)
2258 })?;
2259 if crate::commands::is_known_command(&lead) {
2260 let kind = lower_instruction_pair(
2261 ctx,
2262 lexer::LanguageParser::parse(Rule::instruction, &text)
2263 .map_err(parse_pest_error)?
2264 .next()
2265 .ok_or_else(|| {
2266 ParseError::validation(
2267 "LET",
2268 "LET capture requires a command".to_string(),
2269 &span,
2270 )
2271 })?,
2272 lctx,
2273 )?;
2274 reject_async_in_capture(ctx, &kind)?;
2275 reject_pipe_stdout_in_capture(ctx, &kind)?;
2276 return Ok(StepKind::AssignCapture {
2277 var,
2278 decl_type: dtype,
2279 cmd: Box::new(kind),
2280 });
2281 }
2282 let expr = parse_expr_str(&span, lctx, &text)?;
2283 return Ok(StepKind::Assign {
2284 var,
2285 decl_type: dtype,
2286 expr,
2287 });
2288 }
2289 Err(ParseError::structural(
2290 "let",
2291 "LET requires a value".to_string(),
2292 &span,
2293 ))
2294}
2295
2296fn parse_await_statement_from_pair(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<StepKind> {
2297 let span = refine_span(ctx, &pair);
2298 let mut var = None;
2299 for inner in pair.into_inner() {
2300 if inner.as_rule() == Rule::ident {
2301 var = Some(inner.as_str().to_string());
2302 }
2303 }
2304 Ok(StepKind::Await {
2305 var: var.ok_or_else(|| {
2306 ParseError::validation("AWAIT", "AWAIT requires a variable".to_string(), &span)
2307 })?,
2308 })
2309}
2310
2311fn parse_cancel_statement_from_pair(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<StepKind> {
2312 let span = refine_span(ctx, &pair);
2313 let mut var = None;
2314 for inner in pair.into_inner() {
2315 if inner.as_rule() == Rule::ident {
2316 var = Some(inner.as_str().to_string());
2317 }
2318 }
2319 Ok(StepKind::Cancel {
2320 var: var.ok_or_else(|| {
2321 ParseError::validation("CANCEL", "CANCEL requires a variable".to_string(), &span)
2322 })?,
2323 })
2324}
2325
2326fn parse_timeout_duration_arg(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Arg> {
2330 let span = refine_span(ctx, &pair);
2331 for inner in pair.into_inner() {
2332 let arg = match inner.as_rule() {
2333 Rule::timeout_literal => Arg::String(inner.as_str().to_string(), false),
2334 Rule::dollar_ident => Arg::Expr(Expr::Var(parse_dollar_ident(inner))),
2335 Rule::quoted_string => Arg::String(
2336 crate::command::strip_surrounding_quotes(inner.as_str()).to_string(),
2337 true,
2338 ),
2339 Rule::templated_arg => Arg::String(inner.as_str().to_string(), false),
2340 _ => continue,
2341 };
2342 ArgType::Duration
2343 .check_arg(&arg)
2344 .map_err(|e| ParseError::validation("TIMEOUT", e.to_string(), &span))?;
2345 return Ok(arg);
2346 }
2347 Err(ParseError::validation(
2348 "TIMEOUT",
2349 "TIMEOUT requires a duration".to_string(),
2350 &span,
2351 ))
2352}
2353
2354fn parse_timeout_statement_from_pair(
2355 ctx: &SpanContext,
2356 pair: Pair<Rule>,
2357 lctx: &LowerCtx,
2358) -> ParseResult<StepKind> {
2359 let span = refine_span(ctx, &pair);
2360 let mut duration: Option<Arg> = None;
2361 let mut body: Option<Vec<Step>> = None;
2362 for inner in pair.into_inner() {
2363 match inner.as_rule() {
2364 Rule::timeout_duration => {
2365 duration = Some(parse_timeout_duration_arg(ctx, inner)?);
2366 }
2367 Rule::block => {
2368 body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
2369 }
2370 Rule::await_statement => {
2371 let kind = parse_await_statement_from_pair(ctx, inner)?;
2372 body = Some(vec![Step {
2373 guard: None,
2374 kind,
2375 scope_enter: 0,
2376 scope_exit: 0,
2377 }]);
2378 }
2379 Rule::cancel_statement => {
2380 let kind = parse_cancel_statement_from_pair(ctx, inner)?;
2381 body = Some(vec![Step {
2382 guard: None,
2383 kind,
2384 scope_enter: 0,
2385 scope_exit: 0,
2386 }]);
2387 }
2388 Rule::with_io_command
2389 | Rule::inherit_env_command
2390 | Rule::async_statement
2391 | Rule::async_statement_block
2392 | Rule::bare_call_statement
2393 | Rule::while_statement
2394 | Rule::func_def
2395 | Rule::return_statement
2396 | Rule::break_statement
2397 | Rule::continue_statement
2398 | Rule::timeout_statement => {
2399 let kind = parse_structural_command_with_lower(ctx, inner, lctx)?;
2400 body = Some(vec![Step {
2401 guard: None,
2402 kind,
2403 scope_enter: 0,
2404 scope_exit: 0,
2405 }]);
2406 }
2407 Rule::instruction | Rule::instruction_inner => {
2408 let kind = lower_instruction_pair(ctx, inner, lctx)?;
2409 body = Some(vec![Step {
2410 guard: None,
2411 kind,
2412 scope_enter: 0,
2413 scope_exit: 0,
2414 }]);
2415 }
2416 Rule::run_exec_statement | Rule::run_exec_inner => {
2417 let kind = lower_run_exec_pair(ctx, inner, lctx)?;
2418 body = Some(vec![Step {
2419 guard: None,
2420 kind,
2421 scope_enter: 0,
2422 scope_exit: 0,
2423 }]);
2424 }
2425 _ => {}
2426 }
2427 }
2428 Ok(StepKind::Timeout {
2429 duration: duration.ok_or_else(|| {
2430 ParseError::validation("TIMEOUT", "TIMEOUT requires a duration".to_string(), &span)
2431 })?,
2432 body: body.ok_or_else(|| {
2433 ParseError::validation(
2434 "TIMEOUT",
2435 "TIMEOUT requires a command or block".to_string(),
2436 &span,
2437 )
2438 })?,
2439 })
2440}
2441
2442fn parse_if_statement_from_pair(
2443 ctx: &SpanContext,
2444 pair: Pair<Rule>,
2445 lctx: &LowerCtx,
2446) -> ParseResult<StepKind> {
2447 let span = refine_span(ctx, &pair);
2448 let mut cond = None;
2449 let mut then_body = Vec::new();
2450 let mut else_ifs = Vec::new();
2451 let mut else_body = None;
2452
2453 for inner in pair.into_inner() {
2454 match inner.as_rule() {
2455 Rule::expr => {
2456 if cond.is_none() {
2457 cond = Some(parse_expr(ctx, lctx, inner)?);
2458 }
2459 }
2460 Rule::block => {
2461 if then_body.is_empty() {
2462 then_body = parse_block_elements_with_lower(ctx, inner, lctx)?;
2463 }
2464 }
2465 Rule::else_if_clause => {
2466 let (eif_cond, eif_body) = parse_else_if_clause(ctx, inner, lctx)?;
2467 else_ifs.push((eif_cond, eif_body));
2468 }
2469 Rule::else_clause => {
2470 else_body = Some(parse_else_clause(ctx, inner, lctx)?);
2471 }
2472 _ => {}
2473 }
2474 }
2475 Ok(StepKind::If {
2476 cond: Box::new(cond.ok_or_else(|| {
2477 ParseError::structural("if", "IF requires a condition".to_string(), &span)
2478 })?),
2479 then_body,
2480 else_ifs,
2481 else_body,
2482 })
2483}
2484
2485fn parse_else_if_clause(
2486 ctx: &SpanContext,
2487 pair: Pair<Rule>,
2488 lctx: &LowerCtx,
2489) -> ParseResult<(Box<Expr>, Vec<Step>)> {
2490 let span = refine_span(ctx, &pair);
2491 let mut cond = None;
2492 let mut body = Vec::new();
2493 for inner in pair.into_inner() {
2494 match inner.as_rule() {
2495 Rule::expr => cond = Some(parse_expr(ctx, lctx, inner)?),
2496 Rule::block => body = parse_block_elements_with_lower(ctx, inner, lctx)?,
2497 _ => {}
2498 }
2499 }
2500 Ok((
2501 Box::new(cond.ok_or_else(|| {
2502 ParseError::structural("if", "ELSE IF requires a condition".to_string(), &span)
2503 })?),
2504 body,
2505 ))
2506}
2507
2508fn parse_else_clause(
2509 ctx: &SpanContext,
2510 pair: Pair<Rule>,
2511 lctx: &LowerCtx,
2512) -> ParseResult<Vec<Step>> {
2513 for inner in pair.into_inner() {
2514 if let Rule::block = inner.as_rule() {
2515 return parse_block_elements_with_lower(ctx, inner, lctx);
2516 }
2517 }
2518 Ok(Vec::new())
2519}
2520
2521fn parse_async_statement_from_pair(
2522 ctx: &SpanContext,
2523 pair: Pair<Rule>,
2524 lctx: &LowerCtx,
2525) -> ParseResult<StepKind> {
2526 let span = refine_span(ctx, &pair);
2527 let mut inner_cmd = None;
2528 let mut block_body = None;
2529 for inner in pair.into_inner() {
2530 match inner.as_rule() {
2531 Rule::command => {
2532 let cmd_text = inner.as_str();
2537 let (preseed_funcs, preseed_imports) = lctx.visible_snapshot();
2538 let steps = parse_script_with_preseed(
2539 cmd_text,
2540 |name, args| (lctx.lower)(name, args),
2541 lctx.reserved_names.clone(),
2542 lctx.modules.clone(),
2543 preseed_funcs,
2544 preseed_imports,
2545 )?;
2546 if steps.len() == 1 {
2547 inner_cmd = Some(steps.into_iter().next().unwrap().kind);
2548 } else {
2549 return Err(ParseError::structural(
2550 "async",
2551 "unexpected multiple steps in async inner command".to_string(),
2552 &span,
2553 ));
2554 }
2555 }
2556 Rule::command_inner => {
2557 let child = inner.into_inner().next().ok_or_else(|| {
2559 ParseError::structural("async", "empty command_inner".to_string(), &span)
2560 })?;
2561 match child.as_rule() {
2562 Rule::inherit_env_command => {
2563 inner_cmd = Some(parse_structural_command_with_lower(ctx, child, lctx)?);
2564 }
2565 Rule::async_statement | Rule::async_statement_block => {
2566 inner_cmd = Some(parse_structural_command_with_lower(ctx, child, lctx)?);
2567 }
2568 Rule::timeout_statement | Rule::cancel_statement => {
2569 inner_cmd = Some(parse_structural_command_with_lower(ctx, child, lctx)?);
2570 }
2571 Rule::bare_call_statement | Rule::while_statement => {
2572 inner_cmd = Some(parse_structural_command_with_lower(ctx, child, lctx)?);
2573 }
2574 Rule::func_def
2575 | Rule::return_statement
2576 | Rule::break_statement
2577 | Rule::continue_statement => {
2578 return Err(ParseError::structural(
2579 "async",
2580 format!(
2581 "{:?} cannot run as a lone ASYNC command; use ASYNC {{ ... }} block form if needed",
2582 child.as_rule()
2583 ),
2584 &span,
2585 ));
2586 }
2587 Rule::instruction => {
2588 inner_cmd = Some(lower_instruction_pair(ctx, child, lctx)?);
2589 }
2590 Rule::run_exec_statement | Rule::run_exec_inner => {
2591 inner_cmd = Some(lower_run_exec_pair(ctx, child, lctx)?);
2592 }
2593 other => {
2594 return Err(ParseError::structural(
2595 "async",
2596 format!("unexpected command_inner child: {:?}", other),
2597 &span,
2598 ));
2599 }
2600 }
2601 }
2602 Rule::instruction | Rule::instruction_inner => {
2603 inner_cmd = Some(lower_instruction_pair(ctx, inner, lctx)?);
2604 }
2605 Rule::run_exec_statement | Rule::run_exec_inner => {
2606 inner_cmd = Some(lower_run_exec_pair(ctx, inner, lctx)?);
2607 }
2608 Rule::block => {
2609 block_body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
2610 }
2611 _ => {}
2612 }
2613 }
2614 if let Some(body) = block_body {
2615 for step in &body {
2616 if matches!(&step.kind, StepKind::WithIo { .. }) {
2617 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));
2618 }
2619 }
2620 Ok(StepKind::AsyncBlock { body })
2621 } else if let Some(cmd) = inner_cmd {
2622 if matches!(&cmd, StepKind::WithIo { .. }) {
2623 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));
2624 }
2625 Ok(StepKind::AsyncBlock {
2626 body: vec![Step {
2627 guard: None,
2628 kind: cmd,
2629 scope_enter: 0,
2630 scope_exit: 0,
2631 }],
2632 })
2633 } else {
2634 Err(ParseError::structural(
2635 "async",
2636 "ASYNC requires either a command or a block".to_string(),
2637 &span,
2638 ))
2639 }
2640}
2641
2642fn parse_async_statement_block_from_pair(
2643 ctx: &SpanContext,
2644 pair: Pair<Rule>,
2645 lctx: &LowerCtx,
2646) -> ParseResult<StepKind> {
2647 let span = refine_span(ctx, &pair);
2648 let mut block_body = None;
2649 for inner in pair.into_inner() {
2650 if inner.as_rule() == Rule::block {
2651 block_body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
2652 }
2653 }
2654 let body = block_body.ok_or_else(|| {
2655 ParseError::structural(
2656 "async",
2657 "async_statement_block requires a block".to_string(),
2658 &span,
2659 )
2660 })?;
2661 for step in &body {
2662 if matches!(&step.kind, StepKind::WithIo { .. }) {
2663 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));
2664 }
2665 }
2666 Ok(StepKind::AsyncBlock { body })
2667}
2668
2669fn lower_import_statement(ctx: &SpanContext, pair: Pair<Rule>, lctx: &LowerCtx) -> ParseResult<()> {
2671 let mut modules = Vec::new();
2672 for inner in pair.into_inner() {
2673 match inner.as_rule() {
2674 Rule::import_list => {
2675 for module in inner.into_inner() {
2676 if module.as_rule() == Rule::import_module {
2677 modules.push(module.as_str().to_string());
2678 }
2679 }
2680 }
2681 Rule::import_module => modules.push(inner.as_str().to_string()),
2682 _ => {}
2683 }
2684 }
2685 lctx.import_modules(ctx, &modules)
2686}
2687
2688fn parse_block_elements_with_lower(
2689 ctx: &SpanContext,
2690 block_pair: Pair<Rule>,
2691 lctx: &LowerCtx,
2692) -> ParseResult<Vec<Step>> {
2693 lctx.enter_scope();
2697 let mut steps = Vec::new();
2698 for elem in block_pair.into_inner() {
2699 match elem.as_rule() {
2700 Rule::for_statement
2701 | Rule::while_statement
2702 | Rule::func_def
2703 | Rule::bare_call_statement
2704 | Rule::return_statement
2705 | Rule::break_statement
2706 | Rule::continue_statement
2707 | Rule::let_statement
2708 | Rule::mutate_statement
2709 | Rule::let_async_statement
2710 | Rule::let_capture_statement
2711 | Rule::await_statement
2712 | Rule::cancel_statement
2713 | Rule::if_statement
2714 | Rule::async_statement
2715 | Rule::timeout_statement
2716 | Rule::async_statement_block => {
2717 let step_kind = parse_structural_command_with_lower(ctx, elem, lctx)?;
2718 steps.push(Step {
2719 guard: None,
2720 kind: step_kind,
2721 scope_enter: 0,
2722 scope_exit: 0,
2723 });
2724 }
2725 Rule::guard_block => {
2726 let mut guard_pair = None;
2727 let mut inner_block = None;
2728 for inner in elem.into_inner() {
2729 match inner.as_rule() {
2730 Rule::guard_line => guard_pair = Some(inner),
2731 Rule::block => inner_block = Some(inner),
2732 _ => {}
2733 }
2734 }
2735 if let (Some(gp), Some(bp)) = (guard_pair, inner_block) {
2736 let guard_expr = parse_guard_line(ctx, gp)?;
2737 let mut inner_steps = parse_block_elements_with_lower(ctx, bp, lctx)?;
2738 for step in &mut inner_steps {
2739 step.guard = Some(guard_expr.clone());
2740 }
2741 steps.extend(inner_steps);
2742 }
2743 }
2744 Rule::instruction | Rule::instruction_inner => {
2745 let kind = lower_instruction_pair(ctx, elem, lctx)?;
2746 steps.push(Step {
2747 guard: None,
2748 kind,
2749 scope_enter: 0,
2750 scope_exit: 0,
2751 });
2752 }
2753 Rule::run_exec_statement | Rule::run_exec_inner => {
2754 let kind = lower_run_exec_pair(ctx, elem, lctx)?;
2755 steps.push(Step {
2756 guard: None,
2757 kind,
2758 scope_enter: 0,
2759 scope_exit: 0,
2760 });
2761 }
2762 Rule::with_io_command => {
2763 let step_kind = parse_structural_command_with_lower(ctx, elem, lctx)?;
2764 steps.push(Step {
2765 guard: None,
2766 kind: step_kind,
2767 scope_enter: 0,
2768 scope_exit: 0,
2769 });
2770 }
2771 Rule::import_statement => {
2772 lower_import_statement(ctx, elem, lctx)?;
2776 }
2777 Rule::export_statement => {
2778 return Err(ParseError::validation(
2779 KEYWORD_EXPORT,
2780 "`EXPORT` is reserved for future script-module support and cannot be used yet."
2781 .to_string(),
2782 ctx,
2783 ));
2784 }
2785 _ => {} }
2787 }
2788 lctx.exit_scope();
2789 Ok(steps)
2790}
2791
2792fn parse_argument(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Vec<Arg>> {
2793 let inners: Vec<_> = pair.into_inner().collect();
2794 let mut groups: Vec<Vec<Pair<Rule>>> = vec![Vec::new()];
2800 for fragment in inners {
2801 let glued = fragment.as_rule() == Rule::expr
2802 && fragment.as_str().ends_with(|c: char| c.is_whitespace());
2803 groups
2804 .last_mut()
2805 .expect("argument always holds a group")
2806 .push(fragment);
2807 if glued {
2808 groups.push(Vec::new());
2809 }
2810 }
2811 let mut args = Vec::new();
2812 for group in groups {
2813 if group.is_empty() {
2814 continue;
2815 }
2816 if group.len() == 1 && group[0].as_rule() == Rule::expr {
2818 args.push(Arg::Expr(parse_expr(
2819 ctx,
2820 lctx,
2821 group.into_iter().next().expect("group holds one pair"),
2822 )?));
2823 continue;
2824 }
2825 if group.len() == 1 && group[0].as_rule() == Rule::string_literal {
2827 args.push(Arg::String(parse_fragments(&group)?, true));
2828 continue;
2829 }
2830 args.push(Arg::String(parse_fragments(&group)?, false));
2831 }
2832 Ok(args)
2833}
2834
2835fn parse_quoted_string(pair: Pair<Rule>) -> ParseResult<String> {
2836 let s = pair.as_str();
2837 let content = &s[1..s.len() - 1];
2838 Ok(content.to_string())
2840}
2841
2842fn parse_fragments(parts: &[Pair<Rule>]) -> ParseResult<String> {
2846 if parts.len() == 1 && parts[0].as_rule() == Rule::string_literal {
2848 let s = parts[0].as_str();
2849 return Ok(s[1..s.len() - 1].to_string());
2850 }
2851
2852 let mut body = String::new();
2853 let mut last_end = None;
2854 for part in parts {
2855 let span = part.as_span();
2856 if let Some(end) = last_end
2857 && span.start() > end
2858 {
2859 body.push(' ');
2860 }
2861 match part.as_rule() {
2862 Rule::string_literal => {
2863 let s = part.as_str();
2864 let unquoted = &s[1..s.len() - 1];
2865 body.push_str(unquoted);
2866 }
2867 Rule::templated_arg | Rule::unquoted_arg => {
2868 body.push_str(part.as_str());
2869 }
2870 Rule::expr => body.push_str(part.as_str()),
2871 _ => {}
2872 }
2873 last_end = Some(span.end());
2874 }
2875 Ok(body)
2876}
2877
2878fn parse_guard_line(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
2879 let span = refine_span(ctx, &pair);
2880 for inner in pair.into_inner() {
2881 if inner.as_rule() == Rule::guard_expr {
2882 return parse_guard_expr(ctx, inner);
2883 }
2884 }
2885 Err(ParseError::structural(
2886 "guard",
2887 "guard line missing expression".to_string(),
2888 &span,
2889 ))
2890}
2891
2892fn parse_io_binding(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<IoBinding> {
2893 let span = refine_span(ctx, &pair);
2894 let mut stream = None;
2895 let mut pipe = None;
2896 for inner in pair.into_inner() {
2897 match inner.as_rule() {
2898 Rule::io_stream => stream = Some(parse_io_stream(inner.as_str())),
2899 Rule::pipe_binding => pipe = Some(parse_pipe_binding(ctx, inner)?),
2900 _ => {}
2901 }
2902 }
2903 let stream = stream.ok_or_else(|| {
2904 ParseError::structural("with_io", "missing IO stream in WITH_IO".to_string(), &span)
2905 })?;
2906 Ok(IoBinding { stream, pipe })
2907}
2908
2909fn parse_io_stream(text: &str) -> IoStream {
2910 match text {
2911 "stdin" => IoStream::Stdin,
2912 "stdout" => IoStream::Stdout,
2913 "stderr" => IoStream::Stderr,
2914 _ => unreachable!("parser produced invalid io_stream token"),
2915 }
2916}
2917
2918fn parse_pipe_binding(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<PipeTarget> {
2919 let span = refine_span(ctx, &pair);
2920 for inner in pair.into_inner() {
2921 match inner.as_rule() {
2922 Rule::pipe_name => return Ok(PipeTarget::Name(inner.as_str().to_string())),
2923 Rule::dollar_ident => {
2924 return Ok(PipeTarget::Var(parse_dollar_ident(inner)));
2925 }
2926 _ => {}
2927 }
2928 }
2929 Err(ParseError::structural(
2930 "with_io",
2931 "missing pipe identifier in WITH_IO binding".to_string(),
2932 &span,
2933 ))
2934}
2935
2936fn parse_guard_expr(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
2937 let span = refine_span(ctx, &pair);
2938 match pair.as_rule() {
2939 Rule::guard_expr => {
2940 let next = pair.into_inner().next().ok_or_else(|| {
2941 ParseError::structural("guard", "guard expression missing body".to_string(), &span)
2942 })?;
2943 parse_guard_expr(ctx, next)
2944 }
2945 Rule::guard_seq => parse_guard_seq(ctx, pair),
2946 Rule::guard_factor => parse_guard_factor(ctx, pair),
2947 Rule::guard_not => {
2948 Err(ParseError::structural(
2950 "guard",
2951 "guard_not should not create a pair".to_string(),
2952 &span,
2953 ))
2954 }
2955 Rule::guard_primary => parse_guard_primary(ctx, pair),
2956 Rule::guard_group => parse_guard_group(ctx, pair),
2957 Rule::guard_any_call => parse_guard_any_call(ctx, pair),
2958 Rule::guard_all_call => parse_guard_all_call(ctx, pair),
2959 Rule::not_call => parse_not_call(ctx, pair),
2960 Rule::guard_term => parse_guard_term(ctx, pair),
2961 _ => Err(ParseError::structural(
2962 "guard",
2963 format!("unexpected guard expression rule: {:?}", pair.as_rule()),
2964 &span,
2965 )),
2966 }
2967}
2968
2969fn parse_guard_seq(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
2970 let span = refine_span(ctx, &pair);
2971 let mut exprs = Vec::new();
2972 for inner in pair.into_inner() {
2973 if inner.as_rule() == Rule::guard_factor {
2974 exprs.push(parse_guard_factor(ctx, inner)?);
2975 }
2976 }
2977 match exprs.len() {
2978 0 => Err(ParseError::structural(
2979 "guard",
2980 "guard list requires at least one entry".to_string(),
2981 &span,
2982 )),
2983 1 => Ok(exprs.pop().unwrap()),
2984 _ => Ok(GuardExpr::all(exprs)),
2985 }
2986}
2987
2988fn parse_guard_factor(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
2989 let span = refine_span(ctx, &pair);
2990 let inner = pair.into_inner().next().ok_or_else(|| {
2991 ParseError::structural(
2992 "guard",
2993 "guard factor missing expression".to_string(),
2994 &span,
2995 )
2996 })?;
2997 parse_guard_expr(ctx, inner)
2998}
2999
3000fn parse_not_call(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3001 let span = refine_span(ctx, &pair);
3002 for inner in pair.into_inner() {
3003 if inner.as_rule() == Rule::guard_expr {
3004 return parse_guard_expr(ctx, inner).map(|e| GuardExpr::Not(Box::new(e)));
3005 }
3006 }
3007 Err(ParseError::structural(
3008 "guard",
3009 "not() missing expression".to_string(),
3010 &span,
3011 ))
3012}
3013
3014fn parse_guard_primary(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3015 let span = refine_span(ctx, &pair);
3016 match pair.as_rule() {
3017 Rule::guard_primary => {
3018 let inner = pair.into_inner().next().ok_or_else(|| {
3019 ParseError::structural("guard", "guard primary missing body".to_string(), &span)
3020 })?;
3021 parse_guard_primary(ctx, inner)
3022 }
3023 Rule::guard_group => parse_guard_group(ctx, pair),
3024 Rule::guard_any_call => parse_guard_any_call(ctx, pair),
3025 Rule::guard_all_call => parse_guard_all_call(ctx, pair),
3026 Rule::not_call => parse_not_call(ctx, pair),
3027 Rule::guard_term => parse_guard_term(ctx, pair),
3028 _ => Err(ParseError::structural(
3029 "guard",
3030 format!("unexpected guard primary rule: {:?}", pair.as_rule()),
3031 &span,
3032 )),
3033 }
3034}
3035
3036fn parse_guard_group(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3037 let span = refine_span(ctx, &pair);
3038 for inner in pair.into_inner() {
3039 if inner.as_rule() == Rule::guard_expr {
3040 return parse_guard_expr(ctx, inner);
3041 }
3042 }
3043 Err(ParseError::structural(
3044 "guard",
3045 "grouped guard missing expression".to_string(),
3046 &span,
3047 ))
3048}
3049
3050fn parse_guard_any_call(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3051 let span = refine_span(ctx, &pair);
3052 let mut args = Vec::new();
3053 for inner in pair.into_inner() {
3054 if inner.as_rule() == Rule::guard_expr_list {
3055 args = parse_guard_expr_list(ctx, inner)?;
3056 }
3057 }
3058 if args.len() < 2 {
3059 return Err(ParseError::structural(
3060 "guard",
3061 "any(...) requires at least two guard expressions".to_string(),
3062 &span,
3063 ));
3064 }
3065 Ok(GuardExpr::or(args))
3066}
3067
3068fn parse_guard_all_call(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3069 let span = refine_span(ctx, &pair);
3070 let mut args = Vec::new();
3071 for inner in pair.into_inner() {
3072 if inner.as_rule() == Rule::guard_expr_list {
3073 args = parse_guard_expr_list(ctx, inner)?;
3074 }
3075 }
3076 if args.is_empty() {
3077 return Err(ParseError::structural(
3078 "guard",
3079 "all(...) requires at least one guard expression".to_string(),
3080 &span,
3081 ));
3082 }
3083 Ok(GuardExpr::all(args))
3084}
3085
3086fn parse_guard_expr_list(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Vec<GuardExpr>> {
3087 let mut exprs = Vec::new();
3088 for inner in pair.into_inner() {
3089 if inner.as_rule() == Rule::guard_expr {
3090 push_guard_or_args_from_expr(ctx, inner, &mut exprs)?;
3091 }
3092 }
3093 Ok(exprs)
3094}
3095
3096fn push_guard_or_args_from_expr(
3097 ctx: &SpanContext,
3098 expr_pair: Pair<Rule>,
3099 exprs: &mut Vec<GuardExpr>,
3100) -> ParseResult<()> {
3101 if let Some(seq_pair) = expr_pair
3102 .clone()
3103 .into_inner()
3104 .find(|inner| inner.as_rule() == Rule::guard_seq)
3105 {
3106 let factors: Vec<Pair<Rule>> = seq_pair
3107 .into_inner()
3108 .filter(|inner| inner.as_rule() == Rule::guard_factor)
3109 .collect();
3110 if factors.len() > 1 {
3111 for factor in factors {
3112 exprs.push(parse_guard_factor(ctx, factor)?);
3113 }
3114 return Ok(());
3115 }
3116 }
3117 exprs.push(parse_guard_expr(ctx, expr_pair)?);
3118 Ok(())
3119}
3120
3121fn parse_guard_term(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3122 let span = refine_span(ctx, &pair);
3123 for inner in pair.into_inner() {
3124 match inner.as_rule() {
3125 Rule::eq_guard => {
3126 return Ok(GuardExpr::Predicate(parse_func_guard(inner)?));
3127 }
3128 Rule::neq_guard => {
3129 let guard = parse_func_guard(inner)?;
3130 return Ok(GuardExpr::Not(Box::new(GuardExpr::Predicate(guard))));
3131 }
3132 Rule::bool_guard => {
3133 let val = inner
3134 .into_inner()
3135 .find(|p| p.as_rule() == Rule::bool_value)
3136 .expect("grammar invariant violated: bool_guard missing bool_value")
3137 .as_str()
3138 .to_string();
3139 return Ok(GuardExpr::Predicate(Guard::StaticBool { value: val }));
3140 }
3141 Rule::env_guard => {
3142 return Ok(GuardExpr::Predicate(parse_env_guard(inner)?));
3143 }
3144 Rule::bare_guard_ident => {
3145 let tag = inner.as_str();
3146 if let Ok(g) = parse_platform_tag(ctx, tag) {
3147 return Ok(GuardExpr::Predicate(g));
3148 }
3149 return Ok(GuardExpr::Predicate(Guard::EnvExists {
3150 key: tag.to_string(),
3151 }));
3152 }
3153 _ => {}
3154 }
3155 }
3156 Err(ParseError::structural(
3157 "guard",
3158 "missing guard predicate".to_string(),
3159 &span,
3160 ))
3161}
3162
3163fn parse_func_guard(pair: Pair<Rule>) -> ParseResult<Guard> {
3164 let mut key = String::new();
3165 let mut value = String::new();
3166 let mut saw_env_prefix = false;
3167 for inner in pair.into_inner() {
3168 match inner.as_rule() {
3169 Rule::env_prefix => saw_env_prefix = true,
3170 Rule::env_key if saw_env_prefix => {
3171 key = inner.as_str().trim().to_string();
3172 }
3173 Rule::bare_guard_value | Rule::quoted_string => {
3174 value = unquote(inner.as_str().trim()).to_string();
3175 }
3176 _ => {}
3177 }
3178 }
3179 Ok(Guard::EnvEquals { key, value })
3180}
3181
3182fn unquote(s: &str) -> &str {
3183 s.strip_prefix('"')
3184 .and_then(|s| s.strip_suffix('"'))
3185 .or_else(|| s.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
3186 .unwrap_or(s)
3187}
3188
3189fn parse_env_guard(pair: Pair<Rule>) -> ParseResult<Guard> {
3190 let mut key = String::new();
3191 for inner in pair.into_inner() {
3192 if inner.as_rule() == Rule::env_key {
3193 key = inner.as_str().trim().to_string();
3194 }
3195 }
3196 Ok(Guard::EnvExists { key })
3197}
3198
3199fn parse_platform_tag(ctx: &SpanContext, tag: &str) -> ParseResult<Guard> {
3200 let target = match tag.to_ascii_lowercase().as_str() {
3201 "unix" => PlatformGuard::Unix,
3202 "windows" => PlatformGuard::Windows,
3203 "mac" | "macos" => PlatformGuard::Macos,
3204 "linux" => PlatformGuard::Linux,
3205 _ => {
3206 return Err(ParseError::structural(
3207 "platform",
3208 format!("unknown platform '{}'", tag),
3209 ctx,
3210 ));
3211 }
3212 };
3213 Ok(Guard::Platform { target })
3214}
3215
3216fn parse_dollar_ident(pair: Pair<Rule>) -> String {
3217 let s = pair.as_str();
3219 s.strip_prefix('$').unwrap_or(s).to_string()
3220}
3221
3222use crate::ast::{ArithOp, CompareOp, LogicalOp, MathOp, Value};
3223
3224fn parse_expr(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3225 let span = refine_span(ctx, &pair);
3226 let expr = parse_expr_inner(ctx, lctx, pair)?;
3227 if matches!(expr, Expr::UnsignedIntBoundary(_)) {
3228 return Err(ParseError::structural(
3229 "expr",
3230 "integer overflow: 9223372036854775808 exceeds i64::MAX".to_string(),
3231 &span,
3232 ));
3233 }
3234 Ok(expr)
3235}
3236
3237fn parse_expr_inner(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3238 let span = refine_span(ctx, &pair);
3239 let inner = pair.into_inner().next().unwrap();
3240 match inner.as_rule() {
3241 Rule::expr_logical_or => parse_expr_logical_or(ctx, lctx, inner),
3242 _ => Err(ParseError::structural(
3243 "expr",
3244 format!("unexpected expr rule: {:?}", inner.as_rule()),
3245 &span,
3246 )),
3247 }
3248}
3249
3250fn parse_expr_logical_or(
3251 ctx: &SpanContext,
3252 lctx: &LowerCtx,
3253 pair: Pair<Rule>,
3254) -> ParseResult<Expr> {
3255 let span = refine_span(ctx, &pair);
3256 let mut inner = pair.into_inner();
3257 let mut left = parse_expr_logical_and(ctx, lctx, inner.next().unwrap())?;
3258 while let Some(op_pair) = inner.next() {
3259 let op = match op_pair.as_rule() {
3260 Rule::or_op => LogicalOp::Or,
3261 _ => {
3262 return Err(ParseError::structural(
3263 "expr",
3264 format!("unexpected operator in logical-or: {:?}", op_pair.as_rule()),
3265 &span,
3266 ));
3267 }
3268 };
3269 let right = parse_expr_logical_and(ctx, lctx, inner.next().unwrap())?;
3270 left = Expr::Logical {
3271 op,
3272 left: Box::new(left),
3273 right: Box::new(right),
3274 };
3275 }
3276 Ok(left)
3277}
3278
3279fn parse_expr_logical_and(
3280 ctx: &SpanContext,
3281 lctx: &LowerCtx,
3282 pair: Pair<Rule>,
3283) -> ParseResult<Expr> {
3284 let span = refine_span(ctx, &pair);
3285 let mut inner = pair.into_inner();
3286 let mut left = parse_expr_comparison(ctx, lctx, inner.next().unwrap())?;
3287 while let Some(op_pair) = inner.next() {
3288 let op = match op_pair.as_rule() {
3289 Rule::and_op => LogicalOp::And,
3290 _ => {
3291 return Err(ParseError::structural(
3292 "expr",
3293 format!(
3294 "unexpected operator in logical-and: {:?}",
3295 op_pair.as_rule()
3296 ),
3297 &span,
3298 ));
3299 }
3300 };
3301 let right = parse_expr_comparison(ctx, lctx, inner.next().unwrap())?;
3302 reject_boundary(ctx, &left)?;
3303 reject_boundary(ctx, &right)?;
3304 left = Expr::Logical {
3305 op,
3306 left: Box::new(left),
3307 right: Box::new(right),
3308 };
3309 }
3310 Ok(left)
3311}
3312
3313fn parse_expr_comparison(
3314 ctx: &SpanContext,
3315 lctx: &LowerCtx,
3316 pair: Pair<Rule>,
3317) -> ParseResult<Expr> {
3318 let span = refine_span(ctx, &pair);
3319 let mut inner = pair.into_inner();
3320 let left = parse_expr_ordering(ctx, lctx, inner.next().unwrap())?;
3321 if let Some(op_pair) = inner.next() {
3322 let op = match op_pair.as_rule() {
3323 Rule::eq_op => CompareOp::Eq,
3324 Rule::neq_op => CompareOp::Ne,
3325 _ => {
3326 return Err(ParseError::structural(
3327 "expr",
3328 format!("unexpected comparison operator: {:?}", op_pair.as_rule()),
3329 &span,
3330 ));
3331 }
3332 };
3333 let right = parse_expr_ordering(ctx, lctx, inner.next().unwrap())?;
3334 return make_compare(ctx, op, left, right);
3335 }
3336 Ok(left)
3337}
3338
3339fn parse_expr_ordering(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3340 let span = refine_span(ctx, &pair);
3341 let mut inner = pair.into_inner();
3342 let left = parse_expr_add_sub(ctx, lctx, inner.next().unwrap())?;
3343 if let Some(op_pair) = inner.next() {
3344 let op = match op_pair.as_rule() {
3345 Rule::lt_op => CompareOp::Lt,
3346 Rule::le_op => CompareOp::Le,
3347 Rule::gt_op => CompareOp::Gt,
3348 Rule::ge_op => CompareOp::Ge,
3349 _ => {
3350 return Err(ParseError::structural(
3351 "expr",
3352 format!("unexpected ordering operator: {:?}", op_pair.as_rule()),
3353 &span,
3354 ));
3355 }
3356 };
3357 let right = parse_expr_add_sub(ctx, lctx, inner.next().unwrap())?;
3358 return make_compare(ctx, op, left, right);
3359 }
3360 Ok(left)
3361}
3362
3363fn parse_expr_add_sub(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3364 let span = refine_span(ctx, &pair);
3365 let mut inner = pair.into_inner();
3366 let mut left = parse_expr_mul_div(ctx, lctx, inner.next().unwrap())?;
3367 while let Some(op_pair) = inner.next() {
3368 let op = match op_pair.as_rule() {
3369 Rule::plus_op => ArithOp::Add,
3370 Rule::minus_op => ArithOp::Sub,
3371 _ => {
3372 return Err(ParseError::structural(
3373 "expr",
3374 format!("unexpected additive operator: {:?}", op_pair.as_rule()),
3375 &span,
3376 ));
3377 }
3378 };
3379 let right = parse_expr_mul_div(ctx, lctx, inner.next().unwrap())?;
3380 left = make_arith(ctx, op, left, right)?;
3381 }
3382 Ok(left)
3383}
3384
3385fn parse_expr_mul_div(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3386 let span = refine_span(ctx, &pair);
3387 let mut inner = pair.into_inner();
3388 let mut left = parse_expr_unary(ctx, lctx, inner.next().unwrap())?;
3389 while let Some(op_pair) = inner.next() {
3390 let op = match op_pair.as_rule() {
3391 Rule::star_op => ArithOp::Mul,
3392 Rule::slash_op => ArithOp::Div,
3393 _ => {
3394 return Err(ParseError::structural(
3395 "expr",
3396 format!(
3397 "unexpected multiplicative operator: {:?}",
3398 op_pair.as_rule()
3399 ),
3400 &span,
3401 ));
3402 }
3403 };
3404 let right = parse_expr_unary(ctx, lctx, inner.next().unwrap())?;
3405 left = make_arith(ctx, op, left, right)?;
3406 }
3407 Ok(left)
3408}
3409
3410fn parse_expr_unary(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3411 let span = refine_span(ctx, &pair);
3412 let mut prefixes = Vec::new();
3413 let mut atom = None;
3414 for inner in pair.into_inner() {
3415 match inner.as_rule() {
3416 Rule::not_op => prefixes.push(false),
3417 Rule::neg_op => prefixes.push(true),
3418 Rule::expr_atom => atom = Some(parse_expr_atom(ctx, lctx, inner)?),
3419 _ => {
3420 return Err(ParseError::structural(
3421 "expr",
3422 format!("unexpected unary operand rule: {:?}", inner.as_rule()),
3423 &span,
3424 ));
3425 }
3426 }
3427 }
3428 let mut expr = atom.ok_or_else(|| {
3429 ParseError::structural(
3430 "expr",
3431 "'!'/'-' requires an expression operand".to_string(),
3432 &span,
3433 )
3434 })?;
3435 for is_neg in prefixes.into_iter().rev() {
3437 if is_neg {
3438 expr = apply_unary_neg(ctx, expr)?;
3439 } else {
3440 reject_boundary(ctx, &expr)?;
3441 expr = Expr::Not(Box::new(expr));
3442 }
3443 }
3444 Ok(expr)
3445}
3446
3447fn reject_boundary(ctx: &SpanContext, expr: &Expr) -> ParseResult<()> {
3450 if matches!(expr, Expr::UnsignedIntBoundary(_)) {
3451 return Err(ParseError::structural(
3452 "expr",
3453 "integer overflow: 9223372036854775808 exceeds i64::MAX".to_string(),
3454 ctx,
3455 ));
3456 }
3457 Ok(())
3458}
3459
3460fn apply_unary_neg(ctx: &SpanContext, expr: Expr) -> ParseResult<Expr> {
3463 match expr {
3464 Expr::Literal(v) => match (v.as_i64(), v.as_f64()) {
3465 (Some(n), _) => match n.checked_neg() {
3466 Some(neg) => Ok(Expr::Literal(Value::int(neg))),
3467 None => Ok(Expr::CompiledMath(vec![MathOp::PushConst(v), MathOp::Neg])),
3468 },
3469 (None, Some(f)) => Ok(Expr::Literal(Value::float(-f))),
3470 (None, None) => {
3471 let other = Expr::Literal(v);
3472 if let Some(mut ops) = expr_to_rpn(&other) {
3473 ops.push(MathOp::Neg);
3474 Ok(Expr::CompiledMath(ops))
3475 } else {
3476 Ok(Expr::Arithmetic {
3479 op: ArithOp::Sub,
3480 left: Box::new(Expr::Literal(Value::int(0))),
3481 right: Box::new(other),
3482 })
3483 }
3484 }
3485 },
3486 Expr::UnsignedIntBoundary(n) => {
3487 if n == i64::MAX as u64 + 1 {
3488 Ok(Expr::Literal(Value::int(i64::MIN)))
3489 } else {
3490 Err(ParseError::structural(
3491 "expr",
3492 format!("integer overflow: {} exceeds i64::MAX", n),
3493 ctx,
3494 ))
3495 }
3496 }
3497 other => {
3498 if let Some(mut ops) = expr_to_rpn(&other) {
3499 ops.push(MathOp::Neg);
3500 Ok(Expr::CompiledMath(ops))
3501 } else {
3502 Ok(Expr::Arithmetic {
3505 op: ArithOp::Sub,
3506 left: Box::new(Expr::Literal(Value::int(0))),
3507 right: Box::new(other),
3508 })
3509 }
3510 }
3511 }
3512}
3513
3514fn try_fold_arith(op: ArithOp, left: &Expr, right: &Expr) -> Option<Expr> {
3519 let (Expr::Literal(lv), Expr::Literal(rv)) = (left, right) else {
3520 return None;
3521 };
3522 fold_arith_values(op, lv, rv).map(Expr::Literal)
3523}
3524
3525fn fold_arith_values(op: ArithOp, left: &Value, right: &Value) -> Option<Value> {
3526 match (left.as_i64(), right.as_i64()) {
3527 (Some(a), Some(b)) => {
3528 let v = match op {
3529 ArithOp::Add => a.checked_add(b)?,
3530 ArithOp::Sub => a.checked_sub(b)?,
3531 ArithOp::Mul => a.checked_mul(b)?,
3532 ArithOp::Div => a.checked_div(b)?,
3533 };
3534 Some(Value::int(v))
3535 }
3536 _ => {
3537 let (af, bf) = (as_f64(left)?, as_f64(right)?);
3538 fold_float(op, af, bf)
3539 }
3540 }
3541}
3542
3543fn fold_float(op: ArithOp, a: f64, b: f64) -> Option<Value> {
3544 if !a.is_finite() || !b.is_finite() {
3545 return None;
3546 }
3547 let v = match op {
3548 ArithOp::Add => a + b,
3549 ArithOp::Sub => a - b,
3550 ArithOp::Mul => a * b,
3551 ArithOp::Div => {
3552 if b == 0.0 {
3553 return None;
3554 }
3555 a / b
3556 }
3557 };
3558 if v.is_finite() {
3559 Some(Value::float(v))
3560 } else {
3561 None
3562 }
3563}
3564
3565fn try_fold_compare(op: CompareOp, left: &Expr, right: &Expr) -> Option<Expr> {
3566 let (Expr::Literal(lv), Expr::Literal(rv)) = (left, right) else {
3567 return None;
3568 };
3569 match (lv.as_i64(), rv.as_i64()) {
3570 (Some(a), Some(b)) => {
3571 let r = match op {
3572 CompareOp::Eq => a == b,
3573 CompareOp::Ne => a != b,
3574 CompareOp::Lt => a < b,
3575 CompareOp::Le => a <= b,
3576 CompareOp::Gt => a > b,
3577 CompareOp::Ge => a >= b,
3578 };
3579 Some(Expr::Literal(Value::bool(r)))
3580 }
3581 _ => {
3582 if let (Some(a), Some(b)) = (lv.as_bool(), rv.as_bool()) {
3583 return match op {
3584 CompareOp::Eq => Some(Expr::Literal(Value::bool(a == b))),
3585 CompareOp::Ne => Some(Expr::Literal(Value::bool(a != b))),
3586 _ => None,
3587 };
3588 }
3589 let (af, bf) = (as_f64(lv)?, as_f64(rv)?);
3590 let r = match op {
3591 CompareOp::Eq => af == bf,
3592 CompareOp::Ne => af != bf,
3593 CompareOp::Lt => af < bf,
3594 CompareOp::Le => af <= bf,
3595 CompareOp::Gt => af > bf,
3596 CompareOp::Ge => af >= bf,
3597 };
3598 Some(Expr::Literal(Value::bool(r)))
3599 }
3600 }
3601}
3602
3603fn as_f64(v: &Value) -> Option<f64> {
3604 if let Some(n) = v.as_i64() {
3605 return Some(n as f64);
3606 }
3607 match v.as_f64() {
3608 Some(f) if f.is_finite() => Some(f),
3609 _ => None,
3610 }
3611}
3612
3613fn make_arith(ctx: &SpanContext, op: ArithOp, left: Expr, right: Expr) -> ParseResult<Expr> {
3614 reject_boundary(ctx, &left)?;
3615 reject_boundary(ctx, &right)?;
3616 if let Some(folded) = try_fold_arith(op, &left, &right) {
3617 return Ok(folded);
3618 }
3619 if let (Some(mut lops), Some(mut rops)) = (expr_to_rpn(&left), expr_to_rpn(&right)) {
3620 lops.append(&mut rops);
3621 lops.push(match op {
3622 ArithOp::Add => MathOp::Add,
3623 ArithOp::Sub => MathOp::Sub,
3624 ArithOp::Mul => MathOp::Mul,
3625 ArithOp::Div => MathOp::Div,
3626 });
3627 return Ok(Expr::CompiledMath(lops));
3628 }
3629 Ok(Expr::Arithmetic {
3630 op,
3631 left: Box::new(left),
3632 right: Box::new(right),
3633 })
3634}
3635
3636fn make_compare(ctx: &SpanContext, op: CompareOp, left: Expr, right: Expr) -> ParseResult<Expr> {
3637 reject_boundary(ctx, &left)?;
3638 reject_boundary(ctx, &right)?;
3639 if let Some(folded) = try_fold_compare(op, &left, &right) {
3640 return Ok(folded);
3641 }
3642 if let (Some(mut lops), Some(mut rops)) = (expr_to_rpn(&left), expr_to_rpn(&right)) {
3643 lops.append(&mut rops);
3644 lops.push(match op {
3645 CompareOp::Eq => MathOp::Eq,
3646 CompareOp::Ne => MathOp::Ne,
3647 CompareOp::Lt => MathOp::Lt,
3648 CompareOp::Le => MathOp::Le,
3649 CompareOp::Gt => MathOp::Gt,
3650 CompareOp::Ge => MathOp::Ge,
3651 });
3652 return Ok(Expr::CompiledMath(lops));
3653 }
3654 Ok(Expr::Compare {
3655 op,
3656 left: Box::new(left),
3657 right: Box::new(right),
3658 })
3659}
3660
3661fn expr_to_rpn(expr: &Expr) -> Option<Vec<MathOp>> {
3665 match expr {
3666 Expr::Literal(v) => Some(vec![MathOp::PushConst(v.clone())]),
3667 Expr::Var(name) => Some(vec![MathOp::LoadVar(name.clone())]),
3668 Expr::Env(key) => Some(vec![MathOp::LoadEnv(key.clone())]),
3669 Expr::KeyPath { base, keys } => Some(vec![MathOp::LoadKeyPath {
3670 base: base.clone(),
3671 keys: keys.clone(),
3672 }]),
3673 Expr::Call { name, args } => {
3674 let mut ops = Vec::new();
3675 for arg in args {
3676 ops.extend(expr_to_rpn(arg)?);
3677 }
3678 ops.push(MathOp::Call {
3679 name: name.clone(),
3680 arity: args.len(),
3681 });
3682 Some(ops)
3683 }
3684 Expr::Inspect(var) => Some(vec![MathOp::Inspect(var.clone())]),
3685 Expr::Arithmetic { op, left, right } => {
3686 let mut ops = expr_to_rpn(left)?;
3687 ops.extend(expr_to_rpn(right)?);
3688 ops.push(match op {
3689 ArithOp::Add => MathOp::Add,
3690 ArithOp::Sub => MathOp::Sub,
3691 ArithOp::Mul => MathOp::Mul,
3692 ArithOp::Div => MathOp::Div,
3693 });
3694 Some(ops)
3695 }
3696 Expr::Compare { op, left, right } => {
3697 let mut ops = expr_to_rpn(left)?;
3698 ops.extend(expr_to_rpn(right)?);
3699 ops.push(match op {
3700 CompareOp::Eq => MathOp::Eq,
3701 CompareOp::Ne => MathOp::Ne,
3702 CompareOp::Lt => MathOp::Lt,
3703 CompareOp::Le => MathOp::Le,
3704 CompareOp::Gt => MathOp::Gt,
3705 CompareOp::Ge => MathOp::Ge,
3706 });
3707 Some(ops)
3708 }
3709 Expr::CompiledMath(ops) => Some(ops.clone()),
3710 Expr::Not(_) | Expr::Logical { .. } | Expr::List(_) | Expr::Map(_) => None,
3711 Expr::UnsignedIntBoundary(_) => None,
3712 }
3713}
3714
3715fn parse_expr_atom(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3716 let span = refine_span(ctx, &pair);
3717 let inner = pair.into_inner().next().unwrap();
3718 match inner.as_rule() {
3719 Rule::parenthesized_expr => parse_expr_inner(ctx, lctx, inner.into_inner().next().unwrap()),
3720 Rule::func_call => parse_func_call(ctx, lctx, inner),
3721 Rule::key_path => parse_key_path(ctx, inner),
3722 Rule::variable => {
3723 let name = inner.as_str();
3724 let name = name.strip_prefix('$').unwrap_or(name).to_string();
3725 Ok(Expr::Var(name))
3726 }
3727 Rule::env_read => parse_env_read(ctx, inner).map(Expr::Env),
3728 Rule::pipe_read => parse_pipe_read(ctx, inner).map(|name| Expr::Literal(Value::pipe(name))),
3729 Rule::list_literal => parse_list_literal(ctx, lctx, inner),
3730 Rule::map_literal => parse_map_literal(ctx, lctx, inner),
3731 Rule::string_literal | Rule::quoted_string => {
3732 let s = parse_quoted_string(inner)?;
3733 Ok(Expr::Literal(Value::string(s)))
3734 }
3735 Rule::numeric_literal => parse_numeric_literal(ctx, inner),
3736 Rule::bare_word => {
3737 let s = inner.as_str().to_string();
3738 match s.as_str() {
3739 "true" => Ok(Expr::Literal(Value::bool(true))),
3740 "false" => Ok(Expr::Literal(Value::bool(false))),
3741 _ => Ok(Expr::Literal(Value::string(s))),
3742 }
3743 }
3744 _ => Err(ParseError::structural(
3745 "expr",
3746 format!("unexpected expression atom rule: {:?}", inner.as_rule()),
3747 &span,
3748 )),
3749 }
3750}
3751
3752fn parse_numeric_literal(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Expr> {
3757 let span = refine_span(ctx, &pair);
3758 let text = pair.as_str();
3759 if text.contains('.') {
3760 let parsed: f64 = text.parse().map_err(|_| {
3761 ParseError::structural("expr", format!("invalid float literal {text:?}"), &span)
3762 })?;
3763 if !parsed.is_finite() {
3764 return Err(ParseError::structural(
3765 "expr",
3766 format!("invalid float literal {text:?}"),
3767 &span,
3768 ));
3769 }
3770 return Ok(Expr::Literal(Value::float(parsed)));
3771 }
3772 let digits: u64 = text.parse().map_err(|_| {
3773 ParseError::structural(
3774 "expr",
3775 format!("integer overflow: {text:?} exceeds i64::MAX"),
3776 &span,
3777 )
3778 })?;
3779 if digits <= i64::MAX as u64 {
3780 Ok(Expr::Literal(Value::int(digits as i64)))
3781 } else if digits == i64::MAX as u64 + 1 {
3782 Ok(Expr::UnsignedIntBoundary(digits))
3783 } else {
3784 Err(ParseError::structural(
3785 "expr",
3786 format!("integer overflow: {text:?} exceeds i64::MAX"),
3787 &span,
3788 ))
3789 }
3790}
3791
3792fn parse_env_read(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<String> {
3793 let span = refine_span(ctx, &pair);
3794 for inner in pair.into_inner() {
3795 if inner.as_rule() == Rule::env_read_key {
3796 return Ok(inner.as_str().trim().to_string());
3797 }
3798 }
3799 Err(ParseError::structural(
3800 "expr",
3801 "env read requires a key: env:KEY".to_string(),
3802 &span,
3803 ))
3804}
3805
3806fn parse_pipe_read(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<String> {
3807 let span = refine_span(ctx, &pair);
3808 for inner in pair.into_inner() {
3809 if inner.as_rule() == Rule::pipe_name {
3810 return Ok(inner.as_str().trim().to_string());
3811 }
3812 }
3813 Err(ParseError::structural(
3814 "expr",
3815 "pipe read requires a name: pipe:NAME".to_string(),
3816 &span,
3817 ))
3818}
3819
3820fn parse_key_path(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Expr> {
3821 let span = refine_span(ctx, &pair);
3822 let mut base = None;
3823 let mut keys = Vec::new();
3824 for inner in pair.into_inner() {
3825 match inner.as_rule() {
3826 Rule::ident => {
3827 if base.is_none() {
3828 base = Some(inner.as_str().to_string());
3829 }
3830 }
3831 Rule::key_path_segment => {
3832 keys.push(inner.as_str().to_string());
3833 }
3834 _ => {}
3835 }
3836 }
3837 Ok(Expr::KeyPath {
3838 base: base.ok_or_else(|| {
3839 ParseError::structural(
3840 "expr",
3841 "key path requires a base identifier".to_string(),
3842 &span,
3843 )
3844 })?,
3845 keys,
3846 })
3847}
3848
3849fn parse_func_call(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3850 let span = refine_span(ctx, &pair);
3851 let mut name = None;
3852 let mut args = Vec::new();
3853 for inner in pair.into_inner() {
3854 match inner.as_rule() {
3855 Rule::qualified_func_name => {
3856 name = Some(inner.as_str().to_string());
3857 }
3858 Rule::expr => {
3859 let arg = parse_expr_inner(ctx, lctx, inner)?;
3860 reject_boundary(ctx, &arg)?;
3861 args.push(arg);
3862 }
3863 _ => {}
3864 }
3865 }
3866 let name = name.ok_or_else(|| {
3867 ParseError::structural("expr", "function call requires a name".to_string(), &span)
3868 })?;
3869 if name == KEYWORD_INSPECT {
3874 let [arg] = args.as_slice() else {
3875 return Err(ParseError::structural(
3876 "expr",
3877 "INSPECT requires exactly one argument: INSPECT($var)".to_string(),
3878 &span,
3879 ));
3880 };
3881 if let Expr::Var(var) = arg {
3882 return Ok(Expr::Inspect(var.clone()));
3883 }
3884 return Err(ParseError::structural(
3885 "expr",
3886 format!("INSPECT requires a $variable argument, found {arg:?}"),
3887 &span,
3888 ));
3889 }
3890 let qualified = lctx.resolve_call(&span, &name)?;
3891 Ok(Expr::Call {
3892 name: qualified,
3893 args,
3894 })
3895}
3896
3897fn parse_list_literal(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3898 let mut items = Vec::new();
3899 for inner in pair.into_inner() {
3900 if inner.as_rule() == Rule::expr {
3901 let item = parse_expr_inner(ctx, lctx, inner)?;
3902 reject_boundary(ctx, &item)?;
3903 items.push(item);
3904 }
3905 }
3906 Ok(Expr::List(items))
3907}
3908
3909fn parse_map_literal(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3910 let span = refine_span(ctx, &pair);
3911 let mut entries = Vec::new();
3912 for inner in pair.into_inner() {
3913 if inner.as_rule() == Rule::map_entry {
3914 let mut key = String::new();
3915 let mut value = None;
3916 for entry_inner in inner.into_inner() {
3917 match entry_inner.as_rule() {
3918 Rule::quoted_string => {
3919 key = parse_quoted_string(entry_inner)?;
3920 }
3921 Rule::bare_word => {
3922 key = entry_inner.as_str().to_string();
3923 }
3924 Rule::expr => {
3925 let val = parse_expr_inner(ctx, lctx, entry_inner)?;
3926 reject_boundary(ctx, &val)?;
3927 value = Some(val);
3928 }
3929 _ => {}
3930 }
3931 }
3932 let val = value.ok_or_else(|| {
3933 ParseError::structural("expr", "map entry missing value".to_string(), &span)
3934 })?;
3935 entries.push((key, val));
3936 }
3937 }
3938 Ok(Expr::Map(entries))
3939}