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(
1102 "let",
1103 "LET capture cannot use WITH_IO [stdout=$var]; the capture sink owns stdout"
1104 .to_string(),
1105 ctx,
1106 ));
1107 }
1108 reject_pipe_stdout_in_capture(ctx, cmd)
1109 }
1110 StepKind::Timeout { body, .. } => {
1111 for step in body {
1112 reject_pipe_stdout_in_capture(ctx, &step.kind)?;
1113 }
1114 Ok(())
1115 }
1116 StepKind::While { body, .. } | StepKind::FuncDef { body, .. } => {
1117 for step in body {
1118 reject_pipe_stdout_in_capture(ctx, &step.kind)?;
1119 }
1120 Ok(())
1121 }
1122 _ => Ok(()),
1123 }
1124}
1125
1126fn parse_expr_str(ctx: &SpanContext, lctx: &LowerCtx, text: &str) -> ParseResult<Expr> {
1130 use pest::Parser;
1131 let mut pairs = lexer::LanguageParser::parse(Rule::expr, text).map_err(parse_pest_error)?;
1132 let pair = pairs.next().ok_or_else(|| {
1133 ParseError::validation("LET", "LET requires an expression".to_string(), ctx)
1134 })?;
1135 if pair.as_span().end() != text.len() {
1136 return Err(ParseError::structural(
1137 "expr",
1138 format!("invalid LET expression {text:?}"),
1139 ctx,
1140 ));
1141 }
1142 parse_expr(ctx, lctx, pair)
1143}
1144
1145fn parse_structural_command_with_lower(
1146 ctx: &SpanContext,
1147 pair: Pair<Rule>,
1148 lctx: &LowerCtx,
1149) -> ParseResult<StepKind> {
1150 let span = refine_span(ctx, &pair);
1151 let kind = match pair.as_rule() {
1152 Rule::inherit_env_command => {
1153 let mut keys = Vec::new();
1154 for inner in pair.into_inner() {
1155 if inner.as_rule() == Rule::inherit_list {
1156 for key in inner.into_inner() {
1157 if key.as_rule() == Rule::env_key {
1158 keys.push(key.as_str().trim().to_string());
1159 }
1160 }
1161 } else if inner.as_rule() == Rule::env_key {
1162 keys.push(inner.as_str().trim().to_string());
1163 }
1164 }
1165 StepKind::InheritEnv { keys }
1166 }
1167 Rule::with_io_command => {
1168 let mut bindings = Vec::new();
1169 let mut cmd = None;
1170 for inner in pair.into_inner() {
1171 match inner.as_rule() {
1172 Rule::io_flags => {
1173 for flag in inner.into_inner() {
1174 if flag.as_rule() == Rule::io_binding {
1175 bindings.push(parse_io_binding(ctx, flag)?);
1176 }
1177 }
1178 }
1179 Rule::with_io_command => {
1180 cmd = Some(Box::new(parse_structural_command_with_lower(
1181 ctx, inner, lctx,
1182 )?));
1183 }
1184 Rule::inherit_env_command => {
1185 cmd = Some(Box::new(parse_structural_command_with_lower(
1186 ctx, inner, lctx,
1187 )?));
1188 }
1189 Rule::async_statement | Rule::async_statement_block => {
1190 cmd = Some(Box::new(parse_structural_command_with_lower(
1191 ctx, inner, lctx,
1192 )?));
1193 }
1194 Rule::timeout_statement | Rule::cancel_statement => {
1195 cmd = Some(Box::new(parse_structural_command_with_lower(
1196 ctx, inner, lctx,
1197 )?));
1198 }
1199 Rule::call_statement | Rule::while_statement => {
1200 cmd = Some(Box::new(parse_structural_command_with_lower(
1201 ctx, inner, lctx,
1202 )?));
1203 }
1204 Rule::func_def
1205 | Rule::return_statement
1206 | Rule::break_statement
1207 | Rule::continue_statement => {
1208 return Err(ParseError::structural(
1209 "parser",
1210 format!(
1211 "WITH_IO cannot wrap {:?}; place it around a command or block instead",
1212 inner.as_rule()
1213 ),
1214 &span,
1215 ));
1216 }
1217 Rule::instruction | Rule::instruction_inner => {
1218 cmd = Some(Box::new(lower_instruction_pair(ctx, inner, lctx)?));
1219 }
1220 Rule::run_exec_statement | Rule::run_exec_inner => {
1221 cmd = Some(Box::new(lower_run_exec_pair(ctx, inner, lctx)?));
1222 }
1223 _ => {}
1224 }
1225 }
1226 if let Some(cmd) = cmd {
1227 StepKind::WithIo { bindings, cmd }
1228 } else {
1229 StepKind::WithIoBlock { bindings }
1230 }
1231 }
1232 Rule::for_statement => parse_for_statement_from_pair(ctx, pair, lctx)?,
1233 Rule::while_statement => parse_while_statement_from_pair(ctx, pair, lctx)?,
1234 Rule::func_def => parse_func_def_from_pair(ctx, pair, lctx)?,
1235 Rule::call_statement => parse_call_statement_from_pair(ctx, lctx, pair)?,
1236 Rule::return_statement => parse_return_statement_from_pair(ctx, lctx, pair)?,
1237 Rule::break_statement => StepKind::Break,
1238 Rule::continue_statement => StepKind::Continue,
1239 Rule::let_statement => parse_let_statement_from_pair(ctx, lctx, pair)?,
1240 Rule::mutate_statement => parse_mutate_statement_from_pair(ctx, lctx, pair)?,
1241 Rule::let_async_statement => parse_let_async_statement_from_pair(ctx, pair, lctx)?,
1242 Rule::let_capture_statement => parse_let_capture_statement_from_pair(ctx, pair, lctx)?,
1243 Rule::await_statement => parse_await_statement_from_pair(ctx, pair)?,
1244 Rule::cancel_statement => parse_cancel_statement_from_pair(ctx, pair)?,
1245 Rule::if_statement => parse_if_statement_from_pair(ctx, pair, lctx)?,
1246 Rule::async_statement => parse_async_statement_from_pair(ctx, pair, lctx)?,
1247 Rule::async_statement_block => parse_async_statement_block_from_pair(ctx, pair, lctx)?,
1248 Rule::timeout_statement => parse_timeout_statement_from_pair(ctx, pair, lctx)?,
1249 Rule::command_inner => {
1250 let inner = pair.into_inner().next().ok_or_else(|| {
1253 ParseError::structural("parser", "empty command_inner".to_string(), &span)
1254 })?;
1255 parse_structural_command_with_lower(ctx, inner, lctx)?
1256 }
1257 Rule::instruction | Rule::instruction_inner => lower_instruction_pair(ctx, pair, lctx)?,
1258 Rule::run_exec_statement | Rule::run_exec_inner => lower_run_exec_pair(ctx, pair, lctx)?,
1259 _ => {
1260 return Err(ParseError::structural(
1261 "parser",
1262 format!("unexpected structural command rule: {:?}", pair.as_rule()),
1263 &span,
1264 ));
1265 }
1266 };
1267 Ok(kind)
1268}
1269
1270fn extract_instruction(
1271 ctx: &SpanContext,
1272 pair: Pair<Rule>,
1273 lctx: &LowerCtx,
1274) -> ParseResult<(String, Vec<InsToken>)> {
1275 let span = refine_span(ctx, &pair);
1276 let mut name = None;
1277 let mut args = Vec::new();
1278 for inner in pair.into_inner() {
1279 match inner.as_rule() {
1280 Rule::command_name => {
1281 name = Some(inner.as_str().to_string());
1282 }
1283 Rule::argument => {
1284 args.extend(
1285 parse_argument(ctx, lctx, inner)?
1286 .into_iter()
1287 .map(InsToken::Pos),
1288 );
1289 }
1290 Rule::assignment => {
1291 let (key, value) = parse_assignment(ctx, lctx, inner)?;
1292 args.push(InsToken::Assign(key, value));
1293 }
1294 _ => {}
1295 }
1296 }
1297 let name = name.ok_or_else(|| {
1298 ParseError::structural(
1299 "instruction",
1300 "instruction missing command name".to_string(),
1301 &span,
1302 )
1303 })?;
1304 Ok((name, args))
1305}
1306
1307enum InsToken {
1312 Pos(Arg),
1313 Assign(String, Arg),
1314}
1315
1316fn lower_instruction_pair(
1321 ctx: &SpanContext,
1322 pair: Pair<Rule>,
1323 lctx: &LowerCtx,
1324) -> ParseResult<StepKind> {
1325 let span = refine_span(ctx, &pair);
1326 let (name, tokens) = extract_instruction(ctx, pair, lctx)?;
1327 if name == "ENV" {
1328 return lower_env_command(ctx, tokens);
1329 }
1330 if name == "EXPAND" {
1331 return lower_expand_command(ctx, tokens);
1332 }
1333 let args = tokens
1334 .into_iter()
1335 .map(|token| match token {
1336 InsToken::Pos(arg) => arg,
1337 InsToken::Assign(key, value) => crate::commands::canonical_assignment_arg(&key, &value),
1338 })
1339 .collect();
1340 (lctx.lower)(&name, args).map_err(|e| e.with_span(&span))
1341}
1342
1343fn lower_run_exec_pair(
1349 ctx: &SpanContext,
1350 pair: Pair<Rule>,
1351 lctx: &LowerCtx,
1352) -> ParseResult<StepKind> {
1353 let span = refine_span(ctx, &pair);
1354 let mut list = None;
1355 for inner in pair.into_inner() {
1356 if inner.as_rule() == Rule::run_exec_list {
1357 list = Some(parse_run_exec_list(ctx, lctx, inner)?);
1358 }
1359 }
1360 let list = list.ok_or_else(|| {
1361 ParseError::structural(
1362 "run_exec",
1363 "RUN exec form missing list literal".to_string(),
1364 &span,
1365 )
1366 })?;
1367 (lctx.lower)("RUN", vec![Arg::Expr(list)]).map_err(|e| e.with_span(&span))
1368}
1369
1370fn parse_run_exec_list(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
1375 let mut items = Vec::new();
1376 for inner in pair.into_inner() {
1377 if inner.as_rule() == Rule::run_exec_arg {
1378 let item = parse_run_exec_arg(ctx, lctx, inner)?;
1379 reject_boundary(ctx, &item)?;
1380 items.push(item);
1381 }
1382 }
1383 Ok(Expr::List(items))
1384}
1385
1386fn parse_run_exec_arg(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
1387 let span = refine_span(ctx, &pair);
1388 let inner = pair.into_inner().next().ok_or_else(|| {
1389 ParseError::structural("run_exec", "RUN exec argument is empty".to_string(), &span)
1390 })?;
1391 match inner.as_rule() {
1392 Rule::parenthesized_expr => parse_expr_inner(ctx, lctx, inner.into_inner().next().unwrap()),
1393 Rule::func_call => parse_func_call(ctx, lctx, inner),
1394 Rule::key_path => parse_key_path(ctx, inner),
1395 Rule::variable => {
1396 let name = inner.as_str();
1397 let name = name.strip_prefix('$').unwrap_or(name).to_string();
1398 Ok(Expr::Var(name))
1399 }
1400 Rule::env_read => parse_env_read(ctx, inner).map(Expr::Env),
1401 Rule::list_literal => parse_list_literal(ctx, lctx, inner),
1402 Rule::map_literal => parse_map_literal(ctx, lctx, inner),
1403 Rule::block => Ok(Expr::Block(parse_block_elements_with_lower(
1404 ctx, inner, lctx,
1405 )?)),
1406 Rule::string_literal | Rule::quoted_string => {
1407 let s = parse_quoted_string(inner)?;
1408 Ok(Expr::Literal(Value::string(s)))
1409 }
1410 Rule::numeric_literal => parse_numeric_literal(ctx, inner),
1411 Rule::bare_word => {
1412 let s = inner.as_str().to_string();
1413 match s.as_str() {
1414 "true" => Ok(Expr::Literal(Value::bool(true))),
1415 "false" => Ok(Expr::Literal(Value::bool(false))),
1416 _ => Ok(Expr::Literal(Value::string(s))),
1417 }
1418 }
1419 _ => Err(ParseError::structural(
1420 "run_exec",
1421 format!("unexpected RUN exec argument rule: {:?}", inner.as_rule()),
1422 &span,
1423 )),
1424 }
1425}
1426
1427fn parse_assignment(
1429 ctx: &SpanContext,
1430 lctx: &LowerCtx,
1431 pair: Pair<Rule>,
1432) -> ParseResult<(String, Arg)> {
1433 let span = refine_span(ctx, &pair);
1434 let mut key = None;
1435 let mut value = None;
1436 for inner in pair.into_inner() {
1437 match inner.as_rule() {
1438 Rule::assign_key => {
1439 key = Some(inner.as_str().to_string());
1440 }
1441 Rule::assign_value => {
1442 value = Some(lower_command_value(ctx, lctx, inner)?);
1443 }
1444 _ => {
1445 return Err(ParseError::structural(
1446 "assignment",
1447 format!("unexpected assignment rule: {:?}", inner.as_rule()),
1448 &span,
1449 ));
1450 }
1451 }
1452 }
1453 Ok((
1454 key.ok_or_else(|| {
1455 ParseError::structural("assignment", "assignment missing key".to_string(), &span)
1456 })?,
1457 value.unwrap_or(Arg::String(String::new(), false)),
1458 ))
1459}
1460
1461fn lower_command_value(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Arg> {
1466 let span = refine_span(ctx, &pair);
1467 let inner = pair.into_inner().next().ok_or_else(|| {
1468 ParseError::structural("assignment", "assignment value is empty".to_string(), &span)
1469 })?;
1470 match inner.as_rule() {
1471 Rule::quoted_string => Ok(Arg::String(parse_quoted_string(inner)?, true)),
1472 Rule::assign_expr => {
1473 let shape = inner.into_inner().next().ok_or_else(|| {
1474 ParseError::structural(
1475 "assignment",
1476 "assignment expression is empty".to_string(),
1477 &span,
1478 )
1479 })?;
1480 match shape.as_rule() {
1481 Rule::variable => Ok(Arg::Expr(Expr::Var(parse_dollar_ident(shape)))),
1482 Rule::key_path => Ok(Arg::Expr(parse_key_path(ctx, shape)?)),
1483 Rule::env_read => Ok(Arg::Expr(Expr::Env(parse_env_read(ctx, shape)?))),
1484 Rule::func_call => Ok(Arg::Expr(parse_func_call(ctx, lctx, shape)?)),
1485 other => Err(ParseError::structural(
1486 "assignment",
1487 format!("unexpected assignment expression shape: {:?}", other),
1488 &span,
1489 )),
1490 }
1491 }
1492 Rule::raw_fragments => lower_raw_fragments(ctx, inner),
1493 other => Err(ParseError::structural(
1494 "assignment",
1495 format!("unexpected assignment value rule: {:?}", other),
1496 &span,
1497 )),
1498 }
1499}
1500
1501fn lower_raw_fragments(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Arg> {
1507 let span = refine_span(ctx, &pair);
1508 let mut body = String::new();
1509 for fragment in pair.into_inner() {
1510 match fragment.as_rule() {
1511 Rule::quoted_string => body.push_str(&parse_quoted_string(fragment)?),
1512 Rule::templated_arg => body.push_str(fragment.as_str()),
1513 Rule::raw_text => body.push_str(&collapse_ws(fragment.as_str())),
1514 other => {
1515 return Err(ParseError::structural(
1516 "assignment",
1517 format!("unexpected raw value fragment: {:?}", other),
1518 &span,
1519 ));
1520 }
1521 }
1522 }
1523 Ok(Arg::String(body.trim().to_string(), false))
1524}
1525
1526fn collapse_ws(s: &str) -> String {
1529 let mut out = String::with_capacity(s.len());
1530 let mut in_run = false;
1531 for c in s.chars() {
1532 if c.is_whitespace() {
1533 if !in_run {
1534 out.push(' ');
1535 in_run = true;
1536 }
1537 } else {
1538 out.push(c);
1539 in_run = false;
1540 }
1541 }
1542 out
1543}
1544
1545fn lower_env_command(ctx: &SpanContext, tokens: Vec<InsToken>) -> ParseResult<StepKind> {
1549 if tokens.is_empty() {
1550 return Err(ParseError::validation(
1551 "ENV",
1552 "ENV requires KEY=value".to_string(),
1553 ctx,
1554 ));
1555 }
1556 match tokens.as_slice() {
1557 [InsToken::Assign(key, value)] => {
1558 if crate::command::split_assignment(&format!("{key}={}", value.render()))
1561 .map_err(|e| ParseError::validation("ENV", e.to_string(), ctx))?
1562 .is_none()
1563 {
1564 return Err(ParseError::validation(
1565 "ENV",
1566 "ENV requires KEY=value format".to_string(),
1567 ctx,
1568 ));
1569 }
1570 Ok(StepKind::Env {
1571 key: key.clone(),
1572 value: value.clone(),
1573 })
1574 }
1575 [InsToken::Pos(Arg::String(text, _))] => match crate::command::split_assignment(text)
1576 .map_err(|e| ParseError::validation("ENV", e.to_string(), ctx))?
1577 {
1578 Some((key, value)) => Ok(StepKind::Env { key, value }),
1579 None => Err(ParseError::validation(
1580 "ENV",
1581 "ENV requires KEY=value format".to_string(),
1582 ctx,
1583 )),
1584 },
1585 _ => Err(ParseError::validation(
1586 "ENV",
1587 "ENV requires KEY=value format".to_string(),
1588 ctx,
1589 )),
1590 }
1591}
1592
1593fn lower_expand_command(ctx: &SpanContext, tokens: Vec<InsToken>) -> ParseResult<StepKind> {
1597 let mut path = None;
1598 let mut overrides = Vec::new();
1599 for token in tokens {
1600 match token {
1601 InsToken::Assign(key, value) => {
1602 if key.is_empty() {
1603 return Err(ParseError::validation(
1604 "EXPAND",
1605 "EXPAND requires KEY=value format for overrides".to_string(),
1606 ctx,
1607 ));
1608 }
1609 overrides.push((key, value));
1610 }
1611 InsToken::Pos(arg) => match &arg {
1612 Arg::String(text, quoted) if !quoted && text.contains('=') => {
1613 let Some((key, value)) = crate::command::split_assignment(text)
1614 .map_err(|e| ParseError::validation("EXPAND", e.to_string(), ctx))?
1615 else {
1616 return Err(ParseError::validation(
1617 "EXPAND",
1618 "EXPAND requires KEY=value format for overrides".to_string(),
1619 ctx,
1620 ));
1621 };
1622 overrides.push((key, value));
1623 }
1624 _ => {
1625 if path.is_none() {
1626 ArgType::Path
1630 .check_arg(&arg)
1631 .map_err(|e| ParseError::validation("EXPAND", e.to_string(), ctx))?;
1632 path = Some(arg);
1633 } else {
1634 return Err(ParseError::validation(
1635 "EXPAND",
1636 "EXPAND accepts at most one path".to_string(),
1637 ctx,
1638 ));
1639 }
1640 }
1641 },
1642 }
1643 }
1644 Ok(StepKind::Expand { path, overrides })
1645}
1646
1647fn parse_type_tag(pair: Pair<Rule>) -> String {
1648 pair.as_str().trim().to_string()
1651}
1652
1653fn check_func_ident(ctx: &SpanContext, name: &str) -> ParseResult<()> {
1654 let ok = name
1655 .chars()
1656 .next()
1657 .map(|c| c.is_ascii_uppercase())
1658 .unwrap_or(false)
1659 && name
1660 .chars()
1661 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_');
1662 if !ok {
1663 return Err(ParseError::validation(
1664 "FUNC",
1665 format!(
1666 "function names must be UPPERCASE (ASCII_ALPHA_UPPER, digits, _), got `{name}`"
1667 ),
1668 ctx,
1669 ));
1670 }
1671 Ok(())
1672}
1673
1674fn parse_while_statement_from_pair(
1675 ctx: &SpanContext,
1676 pair: Pair<Rule>,
1677 lctx: &LowerCtx,
1678) -> ParseResult<StepKind> {
1679 let span = refine_span(ctx, &pair);
1680 let mut cond = None;
1681 let mut body = None;
1682 for inner in pair.into_inner() {
1683 match inner.as_rule() {
1684 Rule::expr => {
1685 if cond.is_none() {
1686 cond = Some(parse_expr(ctx, lctx, inner)?);
1687 }
1688 }
1689 Rule::block => {
1690 body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
1691 }
1692 _ => {}
1693 }
1694 }
1695 Ok(StepKind::While {
1696 cond: Box::new(cond.ok_or_else(|| {
1697 ParseError::validation("WHILE", "WHILE requires a condition".to_string(), &span)
1698 })?),
1699 body: body.ok_or_else(|| {
1700 ParseError::validation("WHILE", "WHILE requires a block".to_string(), &span)
1701 })?,
1702 })
1703}
1704
1705fn parse_func_def_from_pair(
1706 ctx: &SpanContext,
1707 pair: Pair<Rule>,
1708 lctx: &LowerCtx,
1709) -> ParseResult<StepKind> {
1710 let span = refine_span(ctx, &pair);
1711 let def_name = pair
1716 .clone()
1717 .into_inner()
1718 .find(|inner| inner.as_rule() == Rule::func_ident)
1719 .map(|inner| inner.as_str().to_string())
1720 .ok_or_else(|| ParseError::validation("FUNC", "FUNC requires a name".to_string(), &span))?;
1721 check_func_ident(ctx, &def_name)?;
1722 if lctx.reserved_names.contains(&def_name) || lctx.module_base_names().contains(&def_name) {
1729 return Err(ParseError::validation(
1730 "FUNC",
1731 format!("FUNC {def_name} cannot shadow reserved function `{def_name}`"),
1732 &span,
1733 ));
1734 }
1735 if !lctx.declare_func(&def_name) {
1736 return Err(ParseError::validation(
1737 "FUNC",
1738 format!("duplicate function `{def_name}` in same scope"),
1739 &span,
1740 ));
1741 }
1742 let mut name: Option<String> = None;
1743 let mut param_names: Vec<String> = Vec::new();
1744 let mut param_types: Vec<String> = Vec::new();
1745 let mut body = None;
1746 for inner in pair.into_inner() {
1747 match inner.as_rule() {
1748 Rule::func_ident => {
1749 if name.is_none() {
1750 name = Some(inner.as_str().to_string());
1751 }
1752 }
1753 Rule::func_param => {
1754 let mut pname = None;
1755 let mut ptype = None;
1756 for part in inner.into_inner() {
1757 match part.as_rule() {
1758 Rule::dollar_ident => {
1759 pname = Some(parse_dollar_ident(part));
1760 }
1761 Rule::type_tag => {
1762 ptype = Some(parse_type_tag(part));
1763 }
1764 _ => {}
1765 }
1766 }
1767 param_names.push(pname.ok_or_else(|| {
1768 ParseError::validation(
1769 "FUNC",
1770 "FUNC parameter requires a $variable".to_string(),
1771 &span,
1772 )
1773 })?);
1774 param_types.push(ptype.ok_or_else(|| {
1775 ParseError::validation(
1776 "FUNC",
1777 "FUNC parameters require explicit types: FUNC NAME($p: TYPE, ...)"
1778 .to_string(),
1779 &span,
1780 )
1781 })?);
1782 }
1783 Rule::block => {
1784 body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
1785 }
1786 _ => {}
1787 }
1788 }
1789 let name = name
1790 .ok_or_else(|| ParseError::validation("FUNC", "FUNC requires a name".to_string(), &span))?;
1791 check_func_ident(ctx, &name)?;
1792 if param_names.len() != param_types.len() {
1793 return Err(ParseError::validation(
1794 "FUNC",
1795 format!("FUNC {name} has mismatched parameter names and types"),
1796 &span,
1797 ));
1798 }
1799 let mut seen = std::collections::HashSet::new();
1800 for pname in ¶m_names {
1801 if !seen.insert(pname.clone()) {
1802 return Err(ParseError::validation(
1803 "FUNC",
1804 format!("FUNC {name} declares duplicate parameter ${pname}"),
1805 &span,
1806 ));
1807 }
1808 }
1809 Ok(StepKind::FuncDef {
1810 name,
1811 params: param_names.into_iter().zip(param_types).collect(),
1812 body: body.ok_or_else(|| {
1813 ParseError::validation("FUNC", "FUNC requires a block".to_string(), &span)
1814 })?,
1815 })
1816}
1817
1818fn parse_call_statement_from_pair(
1819 ctx: &SpanContext,
1820 lctx: &LowerCtx,
1821 pair: Pair<Rule>,
1822) -> ParseResult<StepKind> {
1823 let span = refine_span(ctx, &pair);
1824 let mut name: Option<String> = None;
1825 let mut args = Vec::new();
1826 for inner in pair.into_inner() {
1830 match inner.as_rule() {
1831 Rule::call_head_paren => {
1832 if name.is_none() {
1833 let text = inner.as_str();
1834 name = Some(text.strip_suffix('(').unwrap_or(text).to_string());
1835 }
1836 }
1837 Rule::func_call_head => {
1838 if name.is_none() {
1839 name = Some(inner.as_str().to_string());
1840 }
1841 }
1842 Rule::expr => {
1843 args.push(parse_expr(ctx, lctx, inner)?);
1844 }
1845 _ => {}
1846 }
1847 }
1848 let name = name.ok_or_else(|| {
1849 ParseError::validation(
1850 "FUNC",
1851 "function call requires a function name".to_string(),
1852 &span,
1853 )
1854 })?;
1855 if !name.contains(MODULE_SEPARATOR) {
1858 check_func_ident(ctx, &name)?;
1859 }
1860 let qualified = lctx.resolve_call(&span, &name)?;
1861 Ok(StepKind::Call {
1862 name: qualified,
1863 args,
1864 })
1865}
1866
1867fn parse_return_statement_from_pair(
1868 ctx: &SpanContext,
1869 lctx: &LowerCtx,
1870 pair: Pair<Rule>,
1871) -> ParseResult<StepKind> {
1872 use crate::ast::Value;
1873 for inner in pair.into_inner() {
1874 if inner.as_rule() == Rule::expr {
1875 return Ok(StepKind::Return {
1876 expr: Box::new(parse_expr(ctx, lctx, inner)?),
1877 });
1878 }
1879 }
1880 Ok(StepKind::Return {
1881 expr: Box::new(Expr::Literal(Value::string(String::new()))),
1882 })
1883}
1884
1885fn parse_for_statement_from_pair(
1886 ctx: &SpanContext,
1887 pair: Pair<Rule>,
1888 lctx: &LowerCtx,
1889) -> ParseResult<StepKind> {
1890 let span = refine_span(ctx, &pair);
1891 let mut idents: Vec<String> = Vec::new();
1892 let mut types: Vec<String> = Vec::new();
1893 let mut type_spans: Vec<SpanContext> = Vec::new();
1894 let mut in_expr = None;
1895 let mut body_steps = Vec::new();
1896 for inner in pair.into_inner() {
1897 match inner.as_rule() {
1898 Rule::dollar_ident => {
1899 idents.push(parse_dollar_ident(inner));
1900 }
1901 Rule::type_tag => {
1902 type_spans.push(refine_span(ctx, &inner));
1903 types.push(parse_type_tag(inner));
1904 }
1905 Rule::expr => {
1906 in_expr = Some(parse_expr(ctx, lctx, inner)?);
1907 }
1908 Rule::block => {
1909 body_steps = parse_block_elements_with_lower(ctx, inner, lctx)?;
1910 }
1911 _ => {}
1912 }
1913 }
1914 if idents.len() != types.len() {
1915 return Err(ParseError::validation(
1916 "FOR",
1917 format!(
1918 "FOR requires explicit types: FOR $item: TYPE IN <expr> (got {} vars, {} types)",
1919 idents.len(),
1920 types.len()
1921 ),
1922 &span,
1923 ));
1924 }
1925 let (key_var, key_type, var, var_type) = match idents.len() {
1926 1 => (
1927 None,
1928 None,
1929 idents.into_iter().next().unwrap(),
1930 types.into_iter().next().unwrap(),
1931 ),
1932 2 => {
1933 let mut iv = idents.into_iter();
1934 let mut tv = types.into_iter();
1935 (
1936 Some(iv.next().unwrap()),
1937 Some(tv.next().unwrap()),
1938 iv.next().unwrap(),
1939 tv.next().unwrap(),
1940 )
1941 }
1942 _ => {
1943 return Err(ParseError::validation(
1944 "FOR",
1945 "FOR requires one or two variables".to_string(),
1946 &span,
1947 ));
1948 }
1949 };
1950 if let Some(kt) = &key_type
1951 && kt != "STRING"
1952 && kt != "INT"
1953 {
1954 let at = type_spans.first().unwrap_or(&span);
1956 return Err(ParseError::validation(
1957 "FOR",
1958 format!("FOR key variable must be INT or STRING, got {kt}"),
1959 at,
1960 ));
1961 }
1962 Ok(StepKind::For {
1963 key_var,
1964 key_type,
1965 var,
1966 var_type,
1967 in_expr: in_expr.ok_or_else(|| {
1968 ParseError::validation(
1969 "FOR",
1970 "FOR requires an iterable expression".to_string(),
1971 &span,
1972 )
1973 })?,
1974 body: body_steps,
1975 })
1976}
1977
1978fn parse_let_statement_from_pair(
1979 ctx: &SpanContext,
1980 lctx: &LowerCtx,
1981 pair: Pair<Rule>,
1982) -> ParseResult<StepKind> {
1983 let span = refine_span(ctx, &pair);
1984 let mut var = None;
1985 let mut decl_type = None;
1986 let mut expr = None;
1987 for inner in pair.into_inner() {
1988 match inner.as_rule() {
1989 Rule::dollar_ident => {
1990 var = Some(parse_dollar_ident(inner));
1991 }
1992 Rule::type_tag => {
1993 decl_type = Some(parse_type_tag(inner));
1994 }
1995 Rule::expr => {
1996 expr = Some(parse_expr(ctx, lctx, inner)?);
1997 }
1998 _ => {}
1999 }
2000 }
2001 let var = var.ok_or_else(|| {
2002 ParseError::validation("LET", "LET requires a variable".to_string(), &span)
2003 })?;
2004 let decl_type = decl_type.ok_or_else(|| {
2005 ParseError::validation(
2006 "LET",
2007 "LET requires explicit type: LET $var: TYPE = <expr>".to_string(),
2008 &span,
2009 )
2010 })?;
2011 let expr = match expr {
2014 Some(e) => e,
2015 None if decl_type == "PIPE" => Expr::FreshPipe,
2016 None => {
2017 return Err(ParseError::validation(
2018 "LET",
2019 "LET requires an expression: LET $var: TYPE = <expr> (only LET $p: PIPE omits the initializer)".to_string(),
2020 &span,
2021 ));
2022 }
2023 };
2024 Ok(StepKind::Assign {
2025 var,
2026 decl_type,
2027 expr,
2028 })
2029}
2030
2031fn parse_mutate_statement_from_pair(
2032 ctx: &SpanContext,
2033 lctx: &LowerCtx,
2034 pair: Pair<Rule>,
2035) -> ParseResult<StepKind> {
2036 let span = refine_span(ctx, &pair);
2037 let mut var = None;
2038 let mut expr = None;
2039 for inner in pair.into_inner() {
2040 match inner.as_rule() {
2041 Rule::dollar_ident => {
2042 var = Some(parse_dollar_ident(inner));
2043 }
2044 Rule::expr => {
2045 expr = Some(parse_expr(ctx, lctx, inner)?);
2046 }
2047 _ => {}
2048 }
2049 }
2050 Ok(StepKind::Set {
2051 var: var.ok_or_else(|| {
2052 ParseError::validation(
2053 "mutate",
2054 "mutation requires a variable: $var = <expr>".to_string(),
2055 &span,
2056 )
2057 })?,
2058 expr: expr.ok_or_else(|| {
2059 ParseError::validation(
2060 "mutate",
2061 "mutation requires an expression: $var = <expr>".to_string(),
2062 &span,
2063 )
2064 })?,
2065 })
2066}
2067
2068fn parse_let_async_statement_from_pair(
2069 ctx: &SpanContext,
2070 pair: Pair<Rule>,
2071 lctx: &LowerCtx,
2072) -> ParseResult<StepKind> {
2073 let span = refine_span(ctx, &pair);
2074 let mut var = None;
2075 let mut decl_type: Option<String> = None;
2076 let mut body = None;
2077 for inner in pair.into_inner() {
2078 match inner.as_rule() {
2079 Rule::dollar_ident => {
2080 var = Some(parse_dollar_ident(inner));
2081 }
2082 Rule::type_tag => {
2083 decl_type = Some(parse_type_tag(inner));
2084 }
2085 Rule::block => {
2086 body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
2087 }
2088 Rule::command_inner => {
2089 let inner = inner.into_inner().next().ok_or_else(|| {
2092 ParseError::structural("let", "empty command_inner".to_string(), &span)
2093 })?;
2094 let step_kind = parse_structural_command_with_lower(ctx, inner, lctx)?;
2095 body = Some(vec![Step {
2096 guard: None,
2097 kind: step_kind,
2098 scope_enter: 0,
2099 scope_exit: 0,
2100 }]);
2101 }
2102 Rule::with_io_command => {
2103 let kind = parse_structural_command_with_lower(ctx, inner, lctx)?;
2113 let StepKind::WithIo { bindings, cmd } = kind else {
2114 return Err(ParseError::validation("LET", "LET $var: TYPE = WITH_IO requires an ASYNC command (e.g. LET $t = WITH_IO [stdin=$p] ASYNC WRITE \"f\")".to_string(), &span));
2115 };
2116 match *cmd {
2117 StepKind::AsyncBlock { body: async_body } => {
2118 if async_body.len() != 1 {
2119 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));
2120 }
2121 let step = async_body.into_iter().next().ok_or_else(|| {
2122 ParseError::validation(
2123 "LET",
2124 "LET $var: HANDLE = ASYNC requires a body".to_string(),
2125 &span,
2126 )
2127 })?;
2128 body = Some(vec![Step {
2129 guard: step.guard,
2130 kind: StepKind::WithIo {
2131 bindings,
2132 cmd: Box::new(step.kind),
2133 },
2134 scope_enter: step.scope_enter,
2135 scope_exit: step.scope_exit,
2136 }]);
2137 }
2138 sync_cmd => {
2139 if has_stdout_pipe(&bindings) {
2140 return Err(ParseError::structural("let", "LET capture cannot use WITH_IO [stdout=$var]; the capture sink owns stdout".to_string(), &span));
2141 }
2142 reject_async_in_capture(ctx, &sync_cmd)?;
2143 let name = var.clone().ok_or_else(|| {
2144 ParseError::validation(
2145 "LET",
2146 "LET $var: TYPE = WITH_IO requires a variable".to_string(),
2147 &span,
2148 )
2149 })?;
2150 let dtype = decl_type.ok_or_else(|| {
2151 ParseError::validation(
2152 "LET",
2153 "LET requires explicit type: LET $var: TYPE = ...".to_string(),
2154 &span,
2155 )
2156 })?;
2157 return Ok(StepKind::AssignCapture {
2158 var: name,
2159 decl_type: dtype,
2160 cmd: Box::new(StepKind::WithIo {
2161 bindings,
2162 cmd: Box::new(sync_cmd),
2163 }),
2164 });
2165 }
2166 }
2167 }
2168 _ => {}
2169 }
2170 }
2171 Ok(StepKind::AssignAsync {
2172 var: var.ok_or_else(|| {
2173 ParseError::validation(
2174 "LET",
2175 "LET $var: HANDLE = ASYNC requires a variable".to_string(),
2176 &span,
2177 )
2178 })?,
2179 decl_type: decl_type.ok_or_else(|| {
2180 ParseError::validation(
2181 "LET",
2182 "LET requires explicit type: LET $var: TYPE = ...".to_string(),
2183 &span,
2184 )
2185 })?,
2186 body: body.ok_or_else(|| {
2187 ParseError::validation(
2188 "LET",
2189 "LET $var: HANDLE = ASYNC requires a body".to_string(),
2190 &span,
2191 )
2192 })?,
2193 })
2194}
2195
2196fn parse_let_capture_statement_from_pair(
2204 ctx: &SpanContext,
2205 pair: Pair<Rule>,
2206 lctx: &LowerCtx,
2207) -> ParseResult<StepKind> {
2208 let span = refine_span(ctx, &pair);
2209 use pest::Parser;
2210 let mut var = None;
2211 let mut decl_type: Option<String> = None;
2212 let mut await_pair = None;
2213 let mut timeout_pair = None;
2214 let mut instruction_pair = None;
2215 for inner in pair.into_inner() {
2216 match inner.as_rule() {
2217 Rule::dollar_ident => {
2218 var = Some(parse_dollar_ident(inner));
2219 }
2220 Rule::type_tag => {
2221 decl_type = Some(parse_type_tag(inner));
2222 }
2223 Rule::await_statement => {
2224 await_pair = Some(inner);
2225 }
2226 Rule::timeout_statement => {
2227 timeout_pair = Some(inner);
2228 }
2229 Rule::instruction => {
2230 instruction_pair = Some(inner);
2231 }
2232 _ => {}
2233 }
2234 }
2235 let var = var.ok_or_else(|| {
2236 ParseError::validation("LET", "LET requires a variable".to_string(), &span)
2237 })?;
2238 let dtype: String = decl_type.ok_or_else(|| {
2239 ParseError::validation(
2240 "LET",
2241 "LET requires explicit type: LET $var: TYPE = ...".to_string(),
2242 &span,
2243 )
2244 })?;
2245 if let Some(awaited) = await_pair {
2246 let mut task_var = None;
2247 for inner in awaited.into_inner() {
2248 if inner.as_rule() == Rule::ident {
2249 task_var = Some(inner.as_str().to_string());
2250 }
2251 }
2252 return Ok(StepKind::AwaitCapture {
2253 out_var: var,
2254 out_type: dtype,
2255 task_var: task_var.ok_or_else(|| {
2256 ParseError::validation(
2257 "LET",
2258 "LET $out = AWAIT requires a task variable".to_string(),
2259 &span,
2260 )
2261 })?,
2262 });
2263 }
2264 if let Some(timeouted) = timeout_pair {
2265 let kind = parse_structural_command_with_lower(ctx, timeouted, lctx)?;
2266 reject_async_in_capture(ctx, &kind)?;
2267 reject_pipe_stdout_in_capture(ctx, &kind)?;
2268 return Ok(StepKind::AssignCapture {
2269 var,
2270 decl_type: dtype,
2271 cmd: Box::new(kind),
2272 });
2273 }
2274 if let Some(ins) = instruction_pair {
2275 let text = ins.as_str().to_string();
2276 let mut lead = None;
2277 for token in ins.into_inner() {
2278 if token.as_rule() == Rule::command_name {
2279 lead = Some(token.as_str().to_string());
2280 break;
2281 }
2282 }
2283 let lead = lead.ok_or_else(|| {
2284 ParseError::validation("LET", "LET capture requires a command".to_string(), &span)
2285 })?;
2286 if crate::commands::is_known_command(&lead) {
2287 let kind = lower_instruction_pair(
2288 ctx,
2289 lexer::LanguageParser::parse(Rule::instruction, &text)
2290 .map_err(parse_pest_error)?
2291 .next()
2292 .ok_or_else(|| {
2293 ParseError::validation(
2294 "LET",
2295 "LET capture requires a command".to_string(),
2296 &span,
2297 )
2298 })?,
2299 lctx,
2300 )?;
2301 reject_async_in_capture(ctx, &kind)?;
2302 reject_pipe_stdout_in_capture(ctx, &kind)?;
2303 return Ok(StepKind::AssignCapture {
2304 var,
2305 decl_type: dtype,
2306 cmd: Box::new(kind),
2307 });
2308 }
2309 let expr = parse_expr_str(&span, lctx, &text)?;
2310 return Ok(StepKind::Assign {
2311 var,
2312 decl_type: dtype,
2313 expr,
2314 });
2315 }
2316 Err(ParseError::structural(
2317 "let",
2318 "LET requires a value".to_string(),
2319 &span,
2320 ))
2321}
2322
2323fn parse_await_statement_from_pair(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<StepKind> {
2324 let span = refine_span(ctx, &pair);
2325 let mut var = None;
2326 for inner in pair.into_inner() {
2327 if inner.as_rule() == Rule::ident {
2328 var = Some(inner.as_str().to_string());
2329 }
2330 }
2331 Ok(StepKind::Await {
2332 var: var.ok_or_else(|| {
2333 ParseError::validation("AWAIT", "AWAIT requires a variable".to_string(), &span)
2334 })?,
2335 })
2336}
2337
2338fn parse_cancel_statement_from_pair(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<StepKind> {
2339 let span = refine_span(ctx, &pair);
2340 let mut var = None;
2341 for inner in pair.into_inner() {
2342 if inner.as_rule() == Rule::ident {
2343 var = Some(inner.as_str().to_string());
2344 }
2345 }
2346 Ok(StepKind::Cancel {
2347 var: var.ok_or_else(|| {
2348 ParseError::validation("CANCEL", "CANCEL requires a variable".to_string(), &span)
2349 })?,
2350 })
2351}
2352
2353fn parse_timeout_duration_arg(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Arg> {
2357 let span = refine_span(ctx, &pair);
2358 for inner in pair.into_inner() {
2359 let arg = match inner.as_rule() {
2360 Rule::timeout_literal => Arg::String(inner.as_str().to_string(), false),
2361 Rule::dollar_ident => Arg::Expr(Expr::Var(parse_dollar_ident(inner))),
2362 Rule::quoted_string => Arg::String(
2363 crate::command::strip_surrounding_quotes(inner.as_str()).to_string(),
2364 true,
2365 ),
2366 Rule::templated_arg => Arg::String(inner.as_str().to_string(), false),
2367 _ => continue,
2368 };
2369 ArgType::Duration
2370 .check_arg(&arg)
2371 .map_err(|e| ParseError::validation("TIMEOUT", e.to_string(), &span))?;
2372 return Ok(arg);
2373 }
2374 Err(ParseError::validation(
2375 "TIMEOUT",
2376 "TIMEOUT requires a duration".to_string(),
2377 &span,
2378 ))
2379}
2380
2381fn parse_timeout_statement_from_pair(
2382 ctx: &SpanContext,
2383 pair: Pair<Rule>,
2384 lctx: &LowerCtx,
2385) -> ParseResult<StepKind> {
2386 let span = refine_span(ctx, &pair);
2387 let mut duration: Option<Arg> = None;
2388 let mut body: Option<Vec<Step>> = None;
2389 for inner in pair.into_inner() {
2390 match inner.as_rule() {
2391 Rule::timeout_duration => {
2392 duration = Some(parse_timeout_duration_arg(ctx, inner)?);
2393 }
2394 Rule::block => {
2395 body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
2396 }
2397 Rule::await_statement => {
2398 let kind = parse_await_statement_from_pair(ctx, inner)?;
2399 body = Some(vec![Step {
2400 guard: None,
2401 kind,
2402 scope_enter: 0,
2403 scope_exit: 0,
2404 }]);
2405 }
2406 Rule::cancel_statement => {
2407 let kind = parse_cancel_statement_from_pair(ctx, inner)?;
2408 body = Some(vec![Step {
2409 guard: None,
2410 kind,
2411 scope_enter: 0,
2412 scope_exit: 0,
2413 }]);
2414 }
2415 Rule::with_io_command
2416 | Rule::inherit_env_command
2417 | Rule::async_statement
2418 | Rule::async_statement_block
2419 | Rule::call_statement
2420 | Rule::while_statement
2421 | Rule::func_def
2422 | Rule::return_statement
2423 | Rule::break_statement
2424 | Rule::continue_statement
2425 | Rule::timeout_statement => {
2426 let kind = parse_structural_command_with_lower(ctx, inner, lctx)?;
2427 body = Some(vec![Step {
2428 guard: None,
2429 kind,
2430 scope_enter: 0,
2431 scope_exit: 0,
2432 }]);
2433 }
2434 Rule::instruction | Rule::instruction_inner => {
2435 let kind = lower_instruction_pair(ctx, inner, lctx)?;
2436 body = Some(vec![Step {
2437 guard: None,
2438 kind,
2439 scope_enter: 0,
2440 scope_exit: 0,
2441 }]);
2442 }
2443 Rule::run_exec_statement | Rule::run_exec_inner => {
2444 let kind = lower_run_exec_pair(ctx, inner, lctx)?;
2445 body = Some(vec![Step {
2446 guard: None,
2447 kind,
2448 scope_enter: 0,
2449 scope_exit: 0,
2450 }]);
2451 }
2452 _ => {}
2453 }
2454 }
2455 Ok(StepKind::Timeout {
2456 duration: duration.ok_or_else(|| {
2457 ParseError::validation("TIMEOUT", "TIMEOUT requires a duration".to_string(), &span)
2458 })?,
2459 body: body.ok_or_else(|| {
2460 ParseError::validation(
2461 "TIMEOUT",
2462 "TIMEOUT requires a command or block".to_string(),
2463 &span,
2464 )
2465 })?,
2466 })
2467}
2468
2469fn parse_if_statement_from_pair(
2470 ctx: &SpanContext,
2471 pair: Pair<Rule>,
2472 lctx: &LowerCtx,
2473) -> ParseResult<StepKind> {
2474 let span = refine_span(ctx, &pair);
2475 let mut cond = None;
2476 let mut then_body = Vec::new();
2477 let mut else_ifs = Vec::new();
2478 let mut else_body = None;
2479
2480 for inner in pair.into_inner() {
2481 match inner.as_rule() {
2482 Rule::expr => {
2483 if cond.is_none() {
2484 cond = Some(parse_expr(ctx, lctx, inner)?);
2485 }
2486 }
2487 Rule::block => {
2488 if then_body.is_empty() {
2489 then_body = parse_block_elements_with_lower(ctx, inner, lctx)?;
2490 }
2491 }
2492 Rule::else_if_clause => {
2493 let (eif_cond, eif_body) = parse_else_if_clause(ctx, inner, lctx)?;
2494 else_ifs.push((eif_cond, eif_body));
2495 }
2496 Rule::else_clause => {
2497 else_body = Some(parse_else_clause(ctx, inner, lctx)?);
2498 }
2499 _ => {}
2500 }
2501 }
2502 Ok(StepKind::If {
2503 cond: Box::new(cond.ok_or_else(|| {
2504 ParseError::structural("if", "IF requires a condition".to_string(), &span)
2505 })?),
2506 then_body,
2507 else_ifs,
2508 else_body,
2509 })
2510}
2511
2512fn parse_else_if_clause(
2513 ctx: &SpanContext,
2514 pair: Pair<Rule>,
2515 lctx: &LowerCtx,
2516) -> ParseResult<(Box<Expr>, Vec<Step>)> {
2517 let span = refine_span(ctx, &pair);
2518 let mut cond = None;
2519 let mut body = Vec::new();
2520 for inner in pair.into_inner() {
2521 match inner.as_rule() {
2522 Rule::expr => cond = Some(parse_expr(ctx, lctx, inner)?),
2523 Rule::block => body = parse_block_elements_with_lower(ctx, inner, lctx)?,
2524 _ => {}
2525 }
2526 }
2527 Ok((
2528 Box::new(cond.ok_or_else(|| {
2529 ParseError::structural("if", "ELSE IF requires a condition".to_string(), &span)
2530 })?),
2531 body,
2532 ))
2533}
2534
2535fn parse_else_clause(
2536 ctx: &SpanContext,
2537 pair: Pair<Rule>,
2538 lctx: &LowerCtx,
2539) -> ParseResult<Vec<Step>> {
2540 for inner in pair.into_inner() {
2541 if let Rule::block = inner.as_rule() {
2542 return parse_block_elements_with_lower(ctx, inner, lctx);
2543 }
2544 }
2545 Ok(Vec::new())
2546}
2547
2548fn parse_async_statement_from_pair(
2549 ctx: &SpanContext,
2550 pair: Pair<Rule>,
2551 lctx: &LowerCtx,
2552) -> ParseResult<StepKind> {
2553 let span = refine_span(ctx, &pair);
2554 let mut inner_cmd = None;
2555 let mut block_body = None;
2556 for inner in pair.into_inner() {
2557 match inner.as_rule() {
2558 Rule::command => {
2559 let cmd_text = inner.as_str();
2564 let (preseed_funcs, preseed_imports) = lctx.visible_snapshot();
2565 let steps = parse_script_with_preseed(
2566 cmd_text,
2567 |name, args| (lctx.lower)(name, args),
2568 lctx.reserved_names.clone(),
2569 lctx.modules.clone(),
2570 preseed_funcs,
2571 preseed_imports,
2572 )?;
2573 if steps.len() == 1 {
2574 inner_cmd = Some(steps.into_iter().next().unwrap().kind);
2575 } else {
2576 return Err(ParseError::structural(
2577 "async",
2578 "unexpected multiple steps in async inner command".to_string(),
2579 &span,
2580 ));
2581 }
2582 }
2583 Rule::command_inner => {
2584 let child = inner.into_inner().next().ok_or_else(|| {
2586 ParseError::structural("async", "empty command_inner".to_string(), &span)
2587 })?;
2588 match child.as_rule() {
2589 Rule::inherit_env_command => {
2590 inner_cmd = Some(parse_structural_command_with_lower(ctx, child, lctx)?);
2591 }
2592 Rule::async_statement | Rule::async_statement_block => {
2593 inner_cmd = Some(parse_structural_command_with_lower(ctx, child, lctx)?);
2594 }
2595 Rule::timeout_statement | Rule::cancel_statement => {
2596 inner_cmd = Some(parse_structural_command_with_lower(ctx, child, lctx)?);
2597 }
2598 Rule::call_statement | Rule::while_statement => {
2599 inner_cmd = Some(parse_structural_command_with_lower(ctx, child, lctx)?);
2600 }
2601 Rule::func_def
2602 | Rule::return_statement
2603 | Rule::break_statement
2604 | Rule::continue_statement => {
2605 return Err(ParseError::structural(
2606 "async",
2607 format!(
2608 "{:?} cannot run as a lone ASYNC command; use ASYNC {{ ... }} block form if needed",
2609 child.as_rule()
2610 ),
2611 &span,
2612 ));
2613 }
2614 Rule::instruction => {
2615 inner_cmd = Some(lower_instruction_pair(ctx, child, lctx)?);
2616 }
2617 Rule::run_exec_statement | Rule::run_exec_inner => {
2618 inner_cmd = Some(lower_run_exec_pair(ctx, child, lctx)?);
2619 }
2620 other => {
2621 return Err(ParseError::structural(
2622 "async",
2623 format!("unexpected command_inner child: {:?}", other),
2624 &span,
2625 ));
2626 }
2627 }
2628 }
2629 Rule::instruction | Rule::instruction_inner => {
2630 inner_cmd = Some(lower_instruction_pair(ctx, inner, lctx)?);
2631 }
2632 Rule::run_exec_statement | Rule::run_exec_inner => {
2633 inner_cmd = Some(lower_run_exec_pair(ctx, inner, lctx)?);
2634 }
2635 Rule::block => {
2636 block_body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
2637 }
2638 _ => {}
2639 }
2640 }
2641 if let Some(body) = block_body {
2642 for step in &body {
2643 if matches!(&step.kind, StepKind::WithIo { .. }) {
2644 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));
2645 }
2646 }
2647 Ok(StepKind::AsyncBlock { body })
2648 } else if let Some(cmd) = inner_cmd {
2649 if matches!(&cmd, StepKind::WithIo { .. }) {
2650 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));
2651 }
2652 Ok(StepKind::AsyncBlock {
2653 body: vec![Step {
2654 guard: None,
2655 kind: cmd,
2656 scope_enter: 0,
2657 scope_exit: 0,
2658 }],
2659 })
2660 } else {
2661 Err(ParseError::structural(
2662 "async",
2663 "ASYNC requires either a command or a block".to_string(),
2664 &span,
2665 ))
2666 }
2667}
2668
2669fn parse_async_statement_block_from_pair(
2670 ctx: &SpanContext,
2671 pair: Pair<Rule>,
2672 lctx: &LowerCtx,
2673) -> ParseResult<StepKind> {
2674 let span = refine_span(ctx, &pair);
2675 let mut block_body = None;
2676 for inner in pair.into_inner() {
2677 if inner.as_rule() == Rule::block {
2678 block_body = Some(parse_block_elements_with_lower(ctx, inner, lctx)?);
2679 }
2680 }
2681 let body = block_body.ok_or_else(|| {
2682 ParseError::structural(
2683 "async",
2684 "async_statement_block requires a block".to_string(),
2685 &span,
2686 )
2687 })?;
2688 for step in &body {
2689 if matches!(&step.kind, StepKind::WithIo { .. }) {
2690 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));
2691 }
2692 }
2693 Ok(StepKind::AsyncBlock { body })
2694}
2695
2696fn lower_import_statement(ctx: &SpanContext, pair: Pair<Rule>, lctx: &LowerCtx) -> ParseResult<()> {
2698 let mut modules = Vec::new();
2699 for inner in pair.into_inner() {
2700 match inner.as_rule() {
2701 Rule::import_list => {
2702 for module in inner.into_inner() {
2703 if module.as_rule() == Rule::import_module {
2704 modules.push(module.as_str().to_string());
2705 }
2706 }
2707 }
2708 Rule::import_module => modules.push(inner.as_str().to_string()),
2709 _ => {}
2710 }
2711 }
2712 lctx.import_modules(ctx, &modules)
2713}
2714
2715fn parse_block_elements_with_lower(
2716 ctx: &SpanContext,
2717 block_pair: Pair<Rule>,
2718 lctx: &LowerCtx,
2719) -> ParseResult<Vec<Step>> {
2720 lctx.enter_scope();
2724 let mut steps = Vec::new();
2725 for elem in block_pair.into_inner() {
2726 match elem.as_rule() {
2727 Rule::for_statement
2728 | Rule::while_statement
2729 | Rule::func_def
2730 | Rule::call_statement
2731 | Rule::return_statement
2732 | Rule::break_statement
2733 | Rule::continue_statement
2734 | Rule::let_statement
2735 | Rule::mutate_statement
2736 | Rule::let_async_statement
2737 | Rule::let_capture_statement
2738 | Rule::await_statement
2739 | Rule::cancel_statement
2740 | Rule::if_statement
2741 | Rule::async_statement
2742 | Rule::timeout_statement
2743 | Rule::async_statement_block => {
2744 let step_kind = parse_structural_command_with_lower(ctx, elem, lctx)?;
2745 steps.push(Step {
2746 guard: None,
2747 kind: step_kind,
2748 scope_enter: 0,
2749 scope_exit: 0,
2750 });
2751 }
2752 Rule::guard_block => {
2753 let mut guard_pair = None;
2754 let mut inner_block = None;
2755 for inner in elem.into_inner() {
2756 match inner.as_rule() {
2757 Rule::guard_line => guard_pair = Some(inner),
2758 Rule::block => inner_block = Some(inner),
2759 _ => {}
2760 }
2761 }
2762 if let (Some(gp), Some(bp)) = (guard_pair, inner_block) {
2763 let guard_expr = parse_guard_line(ctx, gp)?;
2764 let mut inner_steps = parse_block_elements_with_lower(ctx, bp, lctx)?;
2765 for step in &mut inner_steps {
2766 step.guard = Some(guard_expr.clone());
2767 }
2768 steps.extend(inner_steps);
2769 }
2770 }
2771 Rule::instruction | Rule::instruction_inner => {
2772 let kind = lower_instruction_pair(ctx, elem, lctx)?;
2773 steps.push(Step {
2774 guard: None,
2775 kind,
2776 scope_enter: 0,
2777 scope_exit: 0,
2778 });
2779 }
2780 Rule::run_exec_statement | Rule::run_exec_inner => {
2781 let kind = lower_run_exec_pair(ctx, elem, lctx)?;
2782 steps.push(Step {
2783 guard: None,
2784 kind,
2785 scope_enter: 0,
2786 scope_exit: 0,
2787 });
2788 }
2789 Rule::with_io_command => {
2790 let step_kind = parse_structural_command_with_lower(ctx, elem, lctx)?;
2791 steps.push(Step {
2792 guard: None,
2793 kind: step_kind,
2794 scope_enter: 0,
2795 scope_exit: 0,
2796 });
2797 }
2798 Rule::import_statement => {
2799 lower_import_statement(ctx, elem, lctx)?;
2803 }
2804 Rule::export_statement => {
2805 return Err(ParseError::validation(
2806 KEYWORD_EXPORT,
2807 "`EXPORT` is reserved for future script-module support and cannot be used yet."
2808 .to_string(),
2809 ctx,
2810 ));
2811 }
2812 _ => {} }
2814 }
2815 lctx.exit_scope();
2816 Ok(steps)
2817}
2818
2819fn parse_argument(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Vec<Arg>> {
2820 let inners: Vec<_> = pair.into_inner().collect();
2821 let mut groups: Vec<Vec<Pair<Rule>>> = vec![Vec::new()];
2827 for fragment in inners {
2828 let glued = fragment.as_rule() == Rule::expr
2829 && fragment.as_str().ends_with(|c: char| c.is_whitespace());
2830 groups
2831 .last_mut()
2832 .expect("argument always holds a group")
2833 .push(fragment);
2834 if glued {
2835 groups.push(Vec::new());
2836 }
2837 }
2838 let mut args = Vec::new();
2839 for group in groups {
2840 if group.is_empty() {
2841 continue;
2842 }
2843 if group.len() == 1 && group[0].as_rule() == Rule::expr {
2845 args.push(Arg::Expr(parse_expr(
2846 ctx,
2847 lctx,
2848 group.into_iter().next().expect("group holds one pair"),
2849 )?));
2850 continue;
2851 }
2852 if group.len() == 1 && group[0].as_rule() == Rule::string_literal {
2854 args.push(Arg::String(parse_fragments(&group)?, true));
2855 continue;
2856 }
2857 args.push(Arg::String(parse_fragments(&group)?, false));
2858 }
2859 Ok(args)
2860}
2861
2862fn parse_quoted_string(pair: Pair<Rule>) -> ParseResult<String> {
2863 let s = pair.as_str();
2864 let content = &s[1..s.len() - 1];
2865 Ok(content.to_string())
2867}
2868
2869fn parse_fragments(parts: &[Pair<Rule>]) -> ParseResult<String> {
2873 if parts.len() == 1 && parts[0].as_rule() == Rule::string_literal {
2875 let s = parts[0].as_str();
2876 return Ok(s[1..s.len() - 1].to_string());
2877 }
2878
2879 let mut body = String::new();
2880 let mut last_end = None;
2881 for part in parts {
2882 let span = part.as_span();
2883 if let Some(end) = last_end
2884 && span.start() > end
2885 {
2886 body.push(' ');
2887 }
2888 match part.as_rule() {
2889 Rule::string_literal => {
2890 let s = part.as_str();
2891 let unquoted = &s[1..s.len() - 1];
2892 body.push_str(unquoted);
2893 }
2894 Rule::templated_arg | Rule::unquoted_arg => {
2895 body.push_str(part.as_str());
2896 }
2897 Rule::expr => body.push_str(part.as_str()),
2898 _ => {}
2899 }
2900 last_end = Some(span.end());
2901 }
2902 Ok(body)
2903}
2904
2905fn parse_guard_line(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
2906 let span = refine_span(ctx, &pair);
2907 for inner in pair.into_inner() {
2908 if inner.as_rule() == Rule::guard_expr {
2909 return parse_guard_expr(ctx, inner);
2910 }
2911 }
2912 Err(ParseError::structural(
2913 "guard",
2914 "guard line missing expression".to_string(),
2915 &span,
2916 ))
2917}
2918
2919fn parse_io_binding(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<IoBinding> {
2920 let span = refine_span(ctx, &pair);
2921 let mut stream = None;
2922 let mut pipe = None;
2923 for inner in pair.into_inner() {
2924 match inner.as_rule() {
2925 Rule::io_stream => stream = Some(parse_io_stream(inner.as_str())),
2926 Rule::pipe_binding => pipe = Some(parse_pipe_binding(ctx, inner)?),
2927 _ => {}
2928 }
2929 }
2930 let stream = stream.ok_or_else(|| {
2931 ParseError::structural("with_io", "missing IO stream in WITH_IO".to_string(), &span)
2932 })?;
2933 Ok(IoBinding { stream, pipe })
2934}
2935
2936fn parse_io_stream(text: &str) -> IoStream {
2937 match text {
2938 "stdin" => IoStream::Stdin,
2939 "stdout" => IoStream::Stdout,
2940 "stderr" => IoStream::Stderr,
2941 _ => unreachable!("parser produced invalid io_stream token"),
2942 }
2943}
2944
2945fn parse_pipe_binding(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<PipeTarget> {
2946 let span = refine_span(ctx, &pair);
2947 for inner in pair.into_inner() {
2948 if inner.as_rule() == Rule::dollar_ident {
2949 return Ok(PipeTarget::Var(parse_dollar_ident(inner)));
2950 }
2951 }
2952 Err(ParseError::structural(
2953 "with_io",
2954 "missing pipe identifier in WITH_IO binding".to_string(),
2955 &span,
2956 ))
2957}
2958
2959fn parse_guard_expr(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
2960 let span = refine_span(ctx, &pair);
2961 match pair.as_rule() {
2962 Rule::guard_expr => {
2963 let next = pair.into_inner().next().ok_or_else(|| {
2964 ParseError::structural("guard", "guard expression missing body".to_string(), &span)
2965 })?;
2966 parse_guard_expr(ctx, next)
2967 }
2968 Rule::guard_seq => parse_guard_seq(ctx, pair),
2969 Rule::guard_factor => parse_guard_factor(ctx, pair),
2970 Rule::guard_not => {
2971 Err(ParseError::structural(
2973 "guard",
2974 "guard_not should not create a pair".to_string(),
2975 &span,
2976 ))
2977 }
2978 Rule::guard_primary => parse_guard_primary(ctx, pair),
2979 Rule::guard_group => parse_guard_group(ctx, pair),
2980 Rule::guard_any_call => parse_guard_any_call(ctx, pair),
2981 Rule::guard_all_call => parse_guard_all_call(ctx, pair),
2982 Rule::not_call => parse_not_call(ctx, pair),
2983 Rule::guard_term => parse_guard_term(ctx, pair),
2984 _ => Err(ParseError::structural(
2985 "guard",
2986 format!("unexpected guard expression rule: {:?}", pair.as_rule()),
2987 &span,
2988 )),
2989 }
2990}
2991
2992fn parse_guard_seq(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
2993 let span = refine_span(ctx, &pair);
2994 let mut exprs = Vec::new();
2995 for inner in pair.into_inner() {
2996 if inner.as_rule() == Rule::guard_factor {
2997 exprs.push(parse_guard_factor(ctx, inner)?);
2998 }
2999 }
3000 match exprs.len() {
3001 0 => Err(ParseError::structural(
3002 "guard",
3003 "guard list requires at least one entry".to_string(),
3004 &span,
3005 )),
3006 1 => Ok(exprs.pop().unwrap()),
3007 _ => Ok(GuardExpr::all(exprs)),
3008 }
3009}
3010
3011fn parse_guard_factor(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3012 let span = refine_span(ctx, &pair);
3013 let inner = pair.into_inner().next().ok_or_else(|| {
3014 ParseError::structural(
3015 "guard",
3016 "guard factor missing expression".to_string(),
3017 &span,
3018 )
3019 })?;
3020 parse_guard_expr(ctx, inner)
3021}
3022
3023fn parse_not_call(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3024 let span = refine_span(ctx, &pair);
3025 for inner in pair.into_inner() {
3026 if inner.as_rule() == Rule::guard_expr {
3027 return parse_guard_expr(ctx, inner).map(|e| GuardExpr::Not(Box::new(e)));
3028 }
3029 }
3030 Err(ParseError::structural(
3031 "guard",
3032 "not() missing expression".to_string(),
3033 &span,
3034 ))
3035}
3036
3037fn parse_guard_primary(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3038 let span = refine_span(ctx, &pair);
3039 match pair.as_rule() {
3040 Rule::guard_primary => {
3041 let inner = pair.into_inner().next().ok_or_else(|| {
3042 ParseError::structural("guard", "guard primary missing body".to_string(), &span)
3043 })?;
3044 parse_guard_primary(ctx, inner)
3045 }
3046 Rule::guard_group => parse_guard_group(ctx, pair),
3047 Rule::guard_any_call => parse_guard_any_call(ctx, pair),
3048 Rule::guard_all_call => parse_guard_all_call(ctx, pair),
3049 Rule::not_call => parse_not_call(ctx, pair),
3050 Rule::guard_term => parse_guard_term(ctx, pair),
3051 _ => Err(ParseError::structural(
3052 "guard",
3053 format!("unexpected guard primary rule: {:?}", pair.as_rule()),
3054 &span,
3055 )),
3056 }
3057}
3058
3059fn parse_guard_group(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3060 let span = refine_span(ctx, &pair);
3061 for inner in pair.into_inner() {
3062 if inner.as_rule() == Rule::guard_expr {
3063 return parse_guard_expr(ctx, inner);
3064 }
3065 }
3066 Err(ParseError::structural(
3067 "guard",
3068 "grouped guard missing expression".to_string(),
3069 &span,
3070 ))
3071}
3072
3073fn parse_guard_any_call(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3074 let span = refine_span(ctx, &pair);
3075 let mut args = Vec::new();
3076 for inner in pair.into_inner() {
3077 if inner.as_rule() == Rule::guard_expr_list {
3078 args = parse_guard_expr_list(ctx, inner)?;
3079 }
3080 }
3081 if args.len() < 2 {
3082 return Err(ParseError::structural(
3083 "guard",
3084 "any(...) requires at least two guard expressions".to_string(),
3085 &span,
3086 ));
3087 }
3088 Ok(GuardExpr::or(args))
3089}
3090
3091fn parse_guard_all_call(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3092 let span = refine_span(ctx, &pair);
3093 let mut args = Vec::new();
3094 for inner in pair.into_inner() {
3095 if inner.as_rule() == Rule::guard_expr_list {
3096 args = parse_guard_expr_list(ctx, inner)?;
3097 }
3098 }
3099 if args.is_empty() {
3100 return Err(ParseError::structural(
3101 "guard",
3102 "all(...) requires at least one guard expression".to_string(),
3103 &span,
3104 ));
3105 }
3106 Ok(GuardExpr::all(args))
3107}
3108
3109fn parse_guard_expr_list(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Vec<GuardExpr>> {
3110 let mut exprs = Vec::new();
3111 for inner in pair.into_inner() {
3112 if inner.as_rule() == Rule::guard_expr {
3113 push_guard_or_args_from_expr(ctx, inner, &mut exprs)?;
3114 }
3115 }
3116 Ok(exprs)
3117}
3118
3119fn push_guard_or_args_from_expr(
3120 ctx: &SpanContext,
3121 expr_pair: Pair<Rule>,
3122 exprs: &mut Vec<GuardExpr>,
3123) -> ParseResult<()> {
3124 if let Some(seq_pair) = expr_pair
3125 .clone()
3126 .into_inner()
3127 .find(|inner| inner.as_rule() == Rule::guard_seq)
3128 {
3129 let factors: Vec<Pair<Rule>> = seq_pair
3130 .into_inner()
3131 .filter(|inner| inner.as_rule() == Rule::guard_factor)
3132 .collect();
3133 if factors.len() > 1 {
3134 for factor in factors {
3135 exprs.push(parse_guard_factor(ctx, factor)?);
3136 }
3137 return Ok(());
3138 }
3139 }
3140 exprs.push(parse_guard_expr(ctx, expr_pair)?);
3141 Ok(())
3142}
3143
3144fn parse_guard_term(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<GuardExpr> {
3145 let span = refine_span(ctx, &pair);
3146 for inner in pair.into_inner() {
3147 match inner.as_rule() {
3148 Rule::eq_guard => {
3149 return Ok(GuardExpr::Predicate(parse_func_guard(inner)?));
3150 }
3151 Rule::neq_guard => {
3152 let guard = parse_func_guard(inner)?;
3153 return Ok(GuardExpr::Not(Box::new(GuardExpr::Predicate(guard))));
3154 }
3155 Rule::bool_guard => {
3156 let val = inner
3157 .into_inner()
3158 .find(|p| p.as_rule() == Rule::bool_value)
3159 .expect("grammar invariant violated: bool_guard missing bool_value")
3160 .as_str()
3161 .to_string();
3162 return Ok(GuardExpr::Predicate(Guard::StaticBool { value: val }));
3163 }
3164 Rule::env_guard => {
3165 return Ok(GuardExpr::Predicate(parse_env_guard(inner)?));
3166 }
3167 Rule::bare_guard_ident => {
3168 let tag = inner.as_str();
3169 if let Ok(g) = parse_platform_tag(ctx, tag) {
3170 return Ok(GuardExpr::Predicate(g));
3171 }
3172 return Ok(GuardExpr::Predicate(Guard::EnvExists {
3173 key: tag.to_string(),
3174 }));
3175 }
3176 _ => {}
3177 }
3178 }
3179 Err(ParseError::structural(
3180 "guard",
3181 "missing guard predicate".to_string(),
3182 &span,
3183 ))
3184}
3185
3186fn parse_func_guard(pair: Pair<Rule>) -> ParseResult<Guard> {
3187 let mut key = String::new();
3188 let mut value = String::new();
3189 let mut saw_env_prefix = false;
3190 for inner in pair.into_inner() {
3191 match inner.as_rule() {
3192 Rule::env_prefix => saw_env_prefix = true,
3193 Rule::env_key if saw_env_prefix => {
3194 key = inner.as_str().trim().to_string();
3195 }
3196 Rule::bare_guard_value | Rule::quoted_string => {
3197 value = unquote(inner.as_str().trim()).to_string();
3198 }
3199 _ => {}
3200 }
3201 }
3202 Ok(Guard::EnvEquals { key, value })
3203}
3204
3205fn unquote(s: &str) -> &str {
3206 s.strip_prefix('"')
3207 .and_then(|s| s.strip_suffix('"'))
3208 .or_else(|| s.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
3209 .unwrap_or(s)
3210}
3211
3212fn parse_env_guard(pair: Pair<Rule>) -> ParseResult<Guard> {
3213 let mut key = String::new();
3214 for inner in pair.into_inner() {
3215 if inner.as_rule() == Rule::env_key {
3216 key = inner.as_str().trim().to_string();
3217 }
3218 }
3219 Ok(Guard::EnvExists { key })
3220}
3221
3222fn parse_platform_tag(ctx: &SpanContext, tag: &str) -> ParseResult<Guard> {
3223 let target = match tag.to_ascii_lowercase().as_str() {
3224 "unix" => PlatformGuard::Unix,
3225 "windows" => PlatformGuard::Windows,
3226 "mac" | "macos" => PlatformGuard::Macos,
3227 "linux" => PlatformGuard::Linux,
3228 _ => {
3229 return Err(ParseError::structural(
3230 "platform",
3231 format!("unknown platform '{}'", tag),
3232 ctx,
3233 ));
3234 }
3235 };
3236 Ok(Guard::Platform { target })
3237}
3238
3239fn parse_dollar_ident(pair: Pair<Rule>) -> String {
3240 let s = pair.as_str();
3242 s.strip_prefix('$').unwrap_or(s).to_string()
3243}
3244
3245use crate::ast::{ArithOp, CompareOp, LogicalOp, MathOp, Value};
3246
3247fn parse_expr(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3248 let span = refine_span(ctx, &pair);
3249 let expr = parse_expr_inner(ctx, lctx, pair)?;
3250 if matches!(expr, Expr::UnsignedIntBoundary(_)) {
3251 return Err(ParseError::structural(
3252 "expr",
3253 "integer overflow: 9223372036854775808 exceeds i64::MAX".to_string(),
3254 &span,
3255 ));
3256 }
3257 Ok(expr)
3258}
3259
3260fn parse_expr_inner(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3261 let span = refine_span(ctx, &pair);
3262 let inner = pair.into_inner().next().unwrap();
3263 match inner.as_rule() {
3264 Rule::expr_logical_or => parse_expr_logical_or(ctx, lctx, inner),
3265 _ => Err(ParseError::structural(
3266 "expr",
3267 format!("unexpected expr rule: {:?}", inner.as_rule()),
3268 &span,
3269 )),
3270 }
3271}
3272
3273fn parse_expr_logical_or(
3274 ctx: &SpanContext,
3275 lctx: &LowerCtx,
3276 pair: Pair<Rule>,
3277) -> ParseResult<Expr> {
3278 let span = refine_span(ctx, &pair);
3279 let mut inner = pair.into_inner();
3280 let mut left = parse_expr_logical_and(ctx, lctx, inner.next().unwrap())?;
3281 while let Some(op_pair) = inner.next() {
3282 let op = match op_pair.as_rule() {
3283 Rule::or_op => LogicalOp::Or,
3284 _ => {
3285 return Err(ParseError::structural(
3286 "expr",
3287 format!("unexpected operator in logical-or: {:?}", op_pair.as_rule()),
3288 &span,
3289 ));
3290 }
3291 };
3292 let right = parse_expr_logical_and(ctx, lctx, inner.next().unwrap())?;
3293 left = Expr::Logical {
3294 op,
3295 left: Box::new(left),
3296 right: Box::new(right),
3297 };
3298 }
3299 Ok(left)
3300}
3301
3302fn parse_expr_logical_and(
3303 ctx: &SpanContext,
3304 lctx: &LowerCtx,
3305 pair: Pair<Rule>,
3306) -> ParseResult<Expr> {
3307 let span = refine_span(ctx, &pair);
3308 let mut inner = pair.into_inner();
3309 let mut left = parse_expr_comparison(ctx, lctx, inner.next().unwrap())?;
3310 while let Some(op_pair) = inner.next() {
3311 let op = match op_pair.as_rule() {
3312 Rule::and_op => LogicalOp::And,
3313 _ => {
3314 return Err(ParseError::structural(
3315 "expr",
3316 format!(
3317 "unexpected operator in logical-and: {:?}",
3318 op_pair.as_rule()
3319 ),
3320 &span,
3321 ));
3322 }
3323 };
3324 let right = parse_expr_comparison(ctx, lctx, inner.next().unwrap())?;
3325 reject_boundary(ctx, &left)?;
3326 reject_boundary(ctx, &right)?;
3327 left = Expr::Logical {
3328 op,
3329 left: Box::new(left),
3330 right: Box::new(right),
3331 };
3332 }
3333 Ok(left)
3334}
3335
3336fn parse_expr_comparison(
3337 ctx: &SpanContext,
3338 lctx: &LowerCtx,
3339 pair: Pair<Rule>,
3340) -> ParseResult<Expr> {
3341 let span = refine_span(ctx, &pair);
3342 let mut inner = pair.into_inner();
3343 let left = parse_expr_ordering(ctx, lctx, inner.next().unwrap())?;
3344 if let Some(op_pair) = inner.next() {
3345 let op = match op_pair.as_rule() {
3346 Rule::eq_op => CompareOp::Eq,
3347 Rule::neq_op => CompareOp::Ne,
3348 _ => {
3349 return Err(ParseError::structural(
3350 "expr",
3351 format!("unexpected comparison operator: {:?}", op_pair.as_rule()),
3352 &span,
3353 ));
3354 }
3355 };
3356 let right = parse_expr_ordering(ctx, lctx, inner.next().unwrap())?;
3357 return make_compare(ctx, op, left, right);
3358 }
3359 Ok(left)
3360}
3361
3362fn parse_expr_ordering(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3363 let span = refine_span(ctx, &pair);
3364 let mut inner = pair.into_inner();
3365 let left = parse_expr_add_sub(ctx, lctx, inner.next().unwrap())?;
3366 if let Some(op_pair) = inner.next() {
3367 let op = match op_pair.as_rule() {
3368 Rule::lt_op => CompareOp::Lt,
3369 Rule::le_op => CompareOp::Le,
3370 Rule::gt_op => CompareOp::Gt,
3371 Rule::ge_op => CompareOp::Ge,
3372 _ => {
3373 return Err(ParseError::structural(
3374 "expr",
3375 format!("unexpected ordering operator: {:?}", op_pair.as_rule()),
3376 &span,
3377 ));
3378 }
3379 };
3380 let right = parse_expr_add_sub(ctx, lctx, inner.next().unwrap())?;
3381 return make_compare(ctx, op, left, right);
3382 }
3383 Ok(left)
3384}
3385
3386fn parse_expr_add_sub(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3387 let span = refine_span(ctx, &pair);
3388 let mut inner = pair.into_inner();
3389 let mut left = parse_expr_mul_div(ctx, lctx, inner.next().unwrap())?;
3390 while let Some(op_pair) = inner.next() {
3391 let op = match op_pair.as_rule() {
3392 Rule::plus_op => ArithOp::Add,
3393 Rule::minus_op => ArithOp::Sub,
3394 _ => {
3395 return Err(ParseError::structural(
3396 "expr",
3397 format!("unexpected additive operator: {:?}", op_pair.as_rule()),
3398 &span,
3399 ));
3400 }
3401 };
3402 let right = parse_expr_mul_div(ctx, lctx, inner.next().unwrap())?;
3403 left = make_arith(ctx, op, left, right)?;
3404 }
3405 Ok(left)
3406}
3407
3408fn parse_expr_mul_div(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3409 let span = refine_span(ctx, &pair);
3410 let mut inner = pair.into_inner();
3411 let mut left = parse_expr_unary(ctx, lctx, inner.next().unwrap())?;
3412 while let Some(op_pair) = inner.next() {
3413 let op = match op_pair.as_rule() {
3414 Rule::star_op => ArithOp::Mul,
3415 Rule::slash_op => ArithOp::Div,
3416 _ => {
3417 return Err(ParseError::structural(
3418 "expr",
3419 format!(
3420 "unexpected multiplicative operator: {:?}",
3421 op_pair.as_rule()
3422 ),
3423 &span,
3424 ));
3425 }
3426 };
3427 let right = parse_expr_unary(ctx, lctx, inner.next().unwrap())?;
3428 left = make_arith(ctx, op, left, right)?;
3429 }
3430 Ok(left)
3431}
3432
3433fn parse_expr_unary(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3434 let span = refine_span(ctx, &pair);
3435 let mut prefixes = Vec::new();
3436 let mut atom = None;
3437 for inner in pair.into_inner() {
3438 match inner.as_rule() {
3439 Rule::not_op => prefixes.push(false),
3440 Rule::neg_op => prefixes.push(true),
3441 Rule::expr_atom => atom = Some(parse_expr_atom(ctx, lctx, inner)?),
3442 _ => {
3443 return Err(ParseError::structural(
3444 "expr",
3445 format!("unexpected unary operand rule: {:?}", inner.as_rule()),
3446 &span,
3447 ));
3448 }
3449 }
3450 }
3451 let mut expr = atom.ok_or_else(|| {
3452 ParseError::structural(
3453 "expr",
3454 "'!'/'-' requires an expression operand".to_string(),
3455 &span,
3456 )
3457 })?;
3458 for is_neg in prefixes.into_iter().rev() {
3460 if is_neg {
3461 expr = apply_unary_neg(ctx, expr)?;
3462 } else {
3463 reject_boundary(ctx, &expr)?;
3464 expr = Expr::Not(Box::new(expr));
3465 }
3466 }
3467 Ok(expr)
3468}
3469
3470fn reject_boundary(ctx: &SpanContext, expr: &Expr) -> ParseResult<()> {
3473 if matches!(expr, Expr::UnsignedIntBoundary(_)) {
3474 return Err(ParseError::structural(
3475 "expr",
3476 "integer overflow: 9223372036854775808 exceeds i64::MAX".to_string(),
3477 ctx,
3478 ));
3479 }
3480 Ok(())
3481}
3482
3483fn apply_unary_neg(ctx: &SpanContext, expr: Expr) -> ParseResult<Expr> {
3486 match expr {
3487 Expr::Literal(v) => match (v.as_i64(), v.as_f64()) {
3488 (Some(n), _) => match n.checked_neg() {
3489 Some(neg) => Ok(Expr::Literal(Value::int(neg))),
3490 None => Ok(Expr::CompiledMath(vec![MathOp::PushConst(v), MathOp::Neg])),
3491 },
3492 (None, Some(f)) => Ok(Expr::Literal(Value::float(-f))),
3493 (None, None) => {
3494 let other = Expr::Literal(v);
3495 if let Some(mut ops) = expr_to_rpn(&other) {
3496 ops.push(MathOp::Neg);
3497 Ok(Expr::CompiledMath(ops))
3498 } else {
3499 Ok(Expr::Arithmetic {
3502 op: ArithOp::Sub,
3503 left: Box::new(Expr::Literal(Value::int(0))),
3504 right: Box::new(other),
3505 })
3506 }
3507 }
3508 },
3509 Expr::UnsignedIntBoundary(n) => {
3510 if n == i64::MAX as u64 + 1 {
3511 Ok(Expr::Literal(Value::int(i64::MIN)))
3512 } else {
3513 Err(ParseError::structural(
3514 "expr",
3515 format!("integer overflow: {} exceeds i64::MAX", n),
3516 ctx,
3517 ))
3518 }
3519 }
3520 other => {
3521 if let Some(mut ops) = expr_to_rpn(&other) {
3522 ops.push(MathOp::Neg);
3523 Ok(Expr::CompiledMath(ops))
3524 } else {
3525 Ok(Expr::Arithmetic {
3528 op: ArithOp::Sub,
3529 left: Box::new(Expr::Literal(Value::int(0))),
3530 right: Box::new(other),
3531 })
3532 }
3533 }
3534 }
3535}
3536
3537fn try_fold_arith(op: ArithOp, left: &Expr, right: &Expr) -> Option<Expr> {
3542 let (Expr::Literal(lv), Expr::Literal(rv)) = (left, right) else {
3543 return None;
3544 };
3545 fold_arith_values(op, lv, rv).map(Expr::Literal)
3546}
3547
3548fn fold_arith_values(op: ArithOp, left: &Value, right: &Value) -> Option<Value> {
3549 match (left.as_i64(), right.as_i64()) {
3550 (Some(a), Some(b)) => {
3551 let v = match op {
3552 ArithOp::Add => a.checked_add(b)?,
3553 ArithOp::Sub => a.checked_sub(b)?,
3554 ArithOp::Mul => a.checked_mul(b)?,
3555 ArithOp::Div => a.checked_div(b)?,
3556 };
3557 Some(Value::int(v))
3558 }
3559 _ => {
3560 let (af, bf) = (as_f64(left)?, as_f64(right)?);
3561 fold_float(op, af, bf)
3562 }
3563 }
3564}
3565
3566fn fold_float(op: ArithOp, a: f64, b: f64) -> Option<Value> {
3567 if !a.is_finite() || !b.is_finite() {
3568 return None;
3569 }
3570 let v = match op {
3571 ArithOp::Add => a + b,
3572 ArithOp::Sub => a - b,
3573 ArithOp::Mul => a * b,
3574 ArithOp::Div => {
3575 if b == 0.0 {
3576 return None;
3577 }
3578 a / b
3579 }
3580 };
3581 if v.is_finite() {
3582 Some(Value::float(v))
3583 } else {
3584 None
3585 }
3586}
3587
3588fn try_fold_compare(op: CompareOp, left: &Expr, right: &Expr) -> Option<Expr> {
3589 let (Expr::Literal(lv), Expr::Literal(rv)) = (left, right) else {
3590 return None;
3591 };
3592 match (lv.as_i64(), rv.as_i64()) {
3593 (Some(a), Some(b)) => {
3594 let r = match op {
3595 CompareOp::Eq => a == b,
3596 CompareOp::Ne => a != b,
3597 CompareOp::Lt => a < b,
3598 CompareOp::Le => a <= b,
3599 CompareOp::Gt => a > b,
3600 CompareOp::Ge => a >= b,
3601 };
3602 Some(Expr::Literal(Value::bool(r)))
3603 }
3604 _ => {
3605 if let (Some(a), Some(b)) = (lv.as_bool(), rv.as_bool()) {
3606 return match op {
3607 CompareOp::Eq => Some(Expr::Literal(Value::bool(a == b))),
3608 CompareOp::Ne => Some(Expr::Literal(Value::bool(a != b))),
3609 _ => None,
3610 };
3611 }
3612 let (af, bf) = (as_f64(lv)?, as_f64(rv)?);
3613 let r = match op {
3614 CompareOp::Eq => af == bf,
3615 CompareOp::Ne => af != bf,
3616 CompareOp::Lt => af < bf,
3617 CompareOp::Le => af <= bf,
3618 CompareOp::Gt => af > bf,
3619 CompareOp::Ge => af >= bf,
3620 };
3621 Some(Expr::Literal(Value::bool(r)))
3622 }
3623 }
3624}
3625
3626fn as_f64(v: &Value) -> Option<f64> {
3627 if let Some(n) = v.as_i64() {
3628 return Some(n as f64);
3629 }
3630 match v.as_f64() {
3631 Some(f) if f.is_finite() => Some(f),
3632 _ => None,
3633 }
3634}
3635
3636fn make_arith(ctx: &SpanContext, op: ArithOp, left: Expr, right: Expr) -> ParseResult<Expr> {
3637 reject_boundary(ctx, &left)?;
3638 reject_boundary(ctx, &right)?;
3639 if let Some(folded) = try_fold_arith(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 ArithOp::Add => MathOp::Add,
3646 ArithOp::Sub => MathOp::Sub,
3647 ArithOp::Mul => MathOp::Mul,
3648 ArithOp::Div => MathOp::Div,
3649 });
3650 return Ok(Expr::CompiledMath(lops));
3651 }
3652 Ok(Expr::Arithmetic {
3653 op,
3654 left: Box::new(left),
3655 right: Box::new(right),
3656 })
3657}
3658
3659fn make_compare(ctx: &SpanContext, op: CompareOp, left: Expr, right: Expr) -> ParseResult<Expr> {
3660 reject_boundary(ctx, &left)?;
3661 reject_boundary(ctx, &right)?;
3662 if let Some(folded) = try_fold_compare(op, &left, &right) {
3663 return Ok(folded);
3664 }
3665 if let (Some(mut lops), Some(mut rops)) = (expr_to_rpn(&left), expr_to_rpn(&right)) {
3666 lops.append(&mut rops);
3667 lops.push(match op {
3668 CompareOp::Eq => MathOp::Eq,
3669 CompareOp::Ne => MathOp::Ne,
3670 CompareOp::Lt => MathOp::Lt,
3671 CompareOp::Le => MathOp::Le,
3672 CompareOp::Gt => MathOp::Gt,
3673 CompareOp::Ge => MathOp::Ge,
3674 });
3675 return Ok(Expr::CompiledMath(lops));
3676 }
3677 Ok(Expr::Compare {
3678 op,
3679 left: Box::new(left),
3680 right: Box::new(right),
3681 })
3682}
3683
3684fn expr_to_rpn(expr: &Expr) -> Option<Vec<MathOp>> {
3688 match expr {
3689 Expr::Literal(v) => Some(vec![MathOp::PushConst(v.clone())]),
3690 Expr::Var(name) => Some(vec![MathOp::LoadVar(name.clone())]),
3691 Expr::Env(key) => Some(vec![MathOp::LoadEnv(key.clone())]),
3692 Expr::KeyPath { base, keys } => Some(vec![MathOp::LoadKeyPath {
3693 base: base.clone(),
3694 keys: keys.clone(),
3695 }]),
3696 Expr::Call { name, args } => {
3697 let mut ops = Vec::new();
3698 for arg in args {
3699 ops.extend(expr_to_rpn(arg)?);
3700 }
3701 ops.push(MathOp::Call {
3702 name: name.clone(),
3703 arity: args.len(),
3704 });
3705 Some(ops)
3706 }
3707 Expr::Inspect(var) => Some(vec![MathOp::Inspect(var.clone())]),
3708 Expr::Arithmetic { op, left, right } => {
3709 let mut ops = expr_to_rpn(left)?;
3710 ops.extend(expr_to_rpn(right)?);
3711 ops.push(match op {
3712 ArithOp::Add => MathOp::Add,
3713 ArithOp::Sub => MathOp::Sub,
3714 ArithOp::Mul => MathOp::Mul,
3715 ArithOp::Div => MathOp::Div,
3716 });
3717 Some(ops)
3718 }
3719 Expr::Compare { op, left, right } => {
3720 let mut ops = expr_to_rpn(left)?;
3721 ops.extend(expr_to_rpn(right)?);
3722 ops.push(match op {
3723 CompareOp::Eq => MathOp::Eq,
3724 CompareOp::Ne => MathOp::Ne,
3725 CompareOp::Lt => MathOp::Lt,
3726 CompareOp::Le => MathOp::Le,
3727 CompareOp::Gt => MathOp::Gt,
3728 CompareOp::Ge => MathOp::Ge,
3729 });
3730 Some(ops)
3731 }
3732 Expr::CompiledMath(ops) => Some(ops.clone()),
3733 Expr::FreshPipe => None,
3734 Expr::Not(_) | Expr::Logical { .. } | Expr::List(_) | Expr::Map(_) | Expr::Block(_) => None,
3735 Expr::UnsignedIntBoundary(_) => None,
3736 }
3737}
3738
3739fn parse_expr_atom(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3740 let span = refine_span(ctx, &pair);
3741 let inner = pair.into_inner().next().unwrap();
3742 match inner.as_rule() {
3743 Rule::parenthesized_expr => parse_expr_inner(ctx, lctx, inner.into_inner().next().unwrap()),
3744 Rule::func_call => parse_func_call(ctx, lctx, inner),
3745 Rule::key_path => parse_key_path(ctx, inner),
3746 Rule::variable => {
3747 let name = inner.as_str();
3748 let name = name.strip_prefix('$').unwrap_or(name).to_string();
3749 Ok(Expr::Var(name))
3750 }
3751 Rule::env_read => parse_env_read(ctx, inner).map(Expr::Env),
3752 Rule::list_literal => parse_list_literal(ctx, lctx, inner),
3753 Rule::map_literal => parse_map_literal(ctx, lctx, inner),
3754 Rule::block => Ok(Expr::Block(parse_block_elements_with_lower(
3755 ctx, inner, lctx,
3756 )?)),
3757 Rule::string_literal | Rule::quoted_string => {
3758 let s = parse_quoted_string(inner)?;
3759 Ok(Expr::Literal(Value::string(s)))
3760 }
3761 Rule::numeric_literal => parse_numeric_literal(ctx, inner),
3762 Rule::bare_word => {
3763 let s = inner.as_str().to_string();
3764 match s.as_str() {
3765 "true" => Ok(Expr::Literal(Value::bool(true))),
3766 "false" => Ok(Expr::Literal(Value::bool(false))),
3767 _ => Ok(Expr::Literal(Value::string(s))),
3768 }
3769 }
3770 _ => Err(ParseError::structural(
3771 "expr",
3772 format!("unexpected expression atom rule: {:?}", inner.as_rule()),
3773 &span,
3774 )),
3775 }
3776}
3777
3778fn parse_numeric_literal(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Expr> {
3783 let span = refine_span(ctx, &pair);
3784 let text = pair.as_str();
3785 if text.contains('.') {
3786 let parsed: f64 = text.parse().map_err(|_| {
3787 ParseError::structural("expr", format!("invalid float literal {text:?}"), &span)
3788 })?;
3789 if !parsed.is_finite() {
3790 return Err(ParseError::structural(
3791 "expr",
3792 format!("invalid float literal {text:?}"),
3793 &span,
3794 ));
3795 }
3796 return Ok(Expr::Literal(Value::float(parsed)));
3797 }
3798 let digits: u64 = text.parse().map_err(|_| {
3799 ParseError::structural(
3800 "expr",
3801 format!("integer overflow: {text:?} exceeds i64::MAX"),
3802 &span,
3803 )
3804 })?;
3805 if digits <= i64::MAX as u64 {
3806 Ok(Expr::Literal(Value::int(digits as i64)))
3807 } else if digits == i64::MAX as u64 + 1 {
3808 Ok(Expr::UnsignedIntBoundary(digits))
3809 } else {
3810 Err(ParseError::structural(
3811 "expr",
3812 format!("integer overflow: {text:?} exceeds i64::MAX"),
3813 &span,
3814 ))
3815 }
3816}
3817
3818fn parse_env_read(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<String> {
3819 let span = refine_span(ctx, &pair);
3820 for inner in pair.into_inner() {
3821 if inner.as_rule() == Rule::env_read_key {
3822 return Ok(inner.as_str().trim().to_string());
3823 }
3824 }
3825 Err(ParseError::structural(
3826 "expr",
3827 "env read requires a key: env:KEY".to_string(),
3828 &span,
3829 ))
3830}
3831
3832fn parse_key_path(ctx: &SpanContext, pair: Pair<Rule>) -> ParseResult<Expr> {
3833 let span = refine_span(ctx, &pair);
3834 let mut base = None;
3835 let mut keys = Vec::new();
3836 for inner in pair.into_inner() {
3837 match inner.as_rule() {
3838 Rule::ident => {
3839 if base.is_none() {
3840 base = Some(inner.as_str().to_string());
3841 }
3842 }
3843 Rule::key_path_segment => {
3844 keys.push(inner.as_str().to_string());
3845 }
3846 _ => {}
3847 }
3848 }
3849 Ok(Expr::KeyPath {
3850 base: base.ok_or_else(|| {
3851 ParseError::structural(
3852 "expr",
3853 "key path requires a base identifier".to_string(),
3854 &span,
3855 )
3856 })?,
3857 keys,
3858 })
3859}
3860
3861fn parse_func_call(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3862 let span = refine_span(ctx, &pair);
3863 let mut name = None;
3864 let mut args = Vec::new();
3865 for inner in pair.into_inner() {
3866 match inner.as_rule() {
3867 Rule::qualified_func_name => {
3868 name = Some(inner.as_str().to_string());
3869 }
3870 Rule::expr => {
3871 let arg = parse_expr_inner(ctx, lctx, inner)?;
3872 reject_boundary(ctx, &arg)?;
3873 args.push(arg);
3874 }
3875 _ => {}
3876 }
3877 }
3878 let name = name.ok_or_else(|| {
3879 ParseError::structural("expr", "function call requires a name".to_string(), &span)
3880 })?;
3881 if name == KEYWORD_INSPECT {
3886 let [arg] = args.as_slice() else {
3887 return Err(ParseError::structural(
3888 "expr",
3889 "INSPECT requires exactly one argument: INSPECT($var)".to_string(),
3890 &span,
3891 ));
3892 };
3893 if let Expr::Var(var) = arg {
3894 return Ok(Expr::Inspect(var.clone()));
3895 }
3896 return Err(ParseError::structural(
3897 "expr",
3898 format!("INSPECT requires a $variable argument, found {arg:?}"),
3899 &span,
3900 ));
3901 }
3902 let qualified = lctx.resolve_call(&span, &name)?;
3903 Ok(Expr::Call {
3904 name: qualified,
3905 args,
3906 })
3907}
3908
3909fn parse_list_literal(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3910 let mut items = Vec::new();
3911 for inner in pair.into_inner() {
3912 if inner.as_rule() == Rule::expr {
3913 let item = parse_expr_inner(ctx, lctx, inner)?;
3914 reject_boundary(ctx, &item)?;
3915 items.push(item);
3916 }
3917 }
3918 Ok(Expr::List(items))
3919}
3920
3921fn parse_map_literal(ctx: &SpanContext, lctx: &LowerCtx, pair: Pair<Rule>) -> ParseResult<Expr> {
3922 let span = refine_span(ctx, &pair);
3923 let mut entries = Vec::new();
3924 for inner in pair.into_inner() {
3925 if inner.as_rule() == Rule::map_entry {
3926 let mut key = String::new();
3927 let mut value = None;
3928 for entry_inner in inner.into_inner() {
3929 match entry_inner.as_rule() {
3930 Rule::quoted_string => {
3931 key = parse_quoted_string(entry_inner)?;
3932 }
3933 Rule::bare_word => {
3934 key = entry_inner.as_str().to_string();
3935 }
3936 Rule::expr => {
3937 let val = parse_expr_inner(ctx, lctx, entry_inner)?;
3938 reject_boundary(ctx, &val)?;
3939 value = Some(val);
3940 }
3941 _ => {}
3942 }
3943 }
3944 let val = value.ok_or_else(|| {
3945 ParseError::structural("expr", "map entry missing value".to_string(), &span)
3946 })?;
3947 entries.push((key, val));
3948 }
3949 }
3950 Ok(Expr::Map(entries))
3951}