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