1use std::fmt::{Display, Write};
5
6use crate::{SourceSpan, tokenizer};
7
8const DISPLAY_INDENT: &str = " ";
9
10pub trait Node: Display + SourceLocation {}
13
14pub trait SourceLocation {
16 fn location(&self) -> Option<SourceSpan>;
18}
19
20pub(crate) fn maybe_location(
21 start: Option<&SourceSpan>,
22 end: Option<&SourceSpan>,
23) -> Option<SourceSpan> {
24 if let (Some(s), Some(e)) = (start, end) {
25 Some(SourceSpan::within(s, e))
26 } else {
27 None
28 }
29}
30
31#[derive(Clone, Debug)]
33#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
34#[cfg_attr(
35 any(test, feature = "serde"),
36 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
37)]
38pub struct Program {
39 pub complete_commands: Vec<CompleteCommand>,
41}
42
43impl Node for Program {}
44
45impl SourceLocation for Program {
46 fn location(&self) -> Option<SourceSpan> {
47 let start = self
48 .complete_commands
49 .first()
50 .and_then(SourceLocation::location);
51 let end = self
52 .complete_commands
53 .last()
54 .and_then(SourceLocation::location);
55 maybe_location(start.as_ref(), end.as_ref())
56 }
57}
58
59impl Display for Program {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 for complete_command in &self.complete_commands {
62 write!(f, "{complete_command}")?;
63 }
64 Ok(())
65 }
66}
67
68pub type CompleteCommand = CompoundList;
70
71pub type CompleteCommandItem = CompoundListItem;
73
74#[derive(Clone, Debug)]
77#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
78#[cfg_attr(
79 any(test, feature = "serde"),
80 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
81)]
82pub enum SeparatorOperator {
83 Async,
85 Sequence,
87}
88
89impl Display for SeparatorOperator {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match self {
92 Self::Async => write!(f, "&"),
93 Self::Sequence => write!(f, ";"),
94 }
95 }
96}
97
98#[derive(Clone, Debug)]
100#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
101#[cfg_attr(
102 any(test, feature = "serde"),
103 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
104)]
105pub struct AndOrList {
106 pub first: Pipeline,
108 #[cfg_attr(
110 any(test, feature = "serde"),
111 serde(skip_serializing_if = "Vec::is_empty", default)
112 )]
113 pub additional: Vec<AndOr>,
114}
115
116impl Node for AndOrList {}
117
118impl SourceLocation for AndOrList {
119 fn location(&self) -> Option<SourceSpan> {
120 let start = self.first.location();
121 let last = self.additional.last();
122 let end = last.and_then(SourceLocation::location);
123
124 match (start, end) {
125 (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
126 (start, _) => start,
127 }
128 }
129}
130
131impl Display for AndOrList {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 write!(f, "{}", self.first)?;
134 for item in &self.additional {
135 write!(f, "{item}")?;
136 }
137
138 Ok(())
139 }
140}
141
142#[derive(PartialEq, Eq)]
144pub enum PipelineOperator {
145 And,
147 Or,
149}
150
151impl PartialEq<AndOr> for PipelineOperator {
152 fn eq(&self, other: &AndOr) -> bool {
153 matches!(
154 (self, other),
155 (Self::And, AndOr::And(_)) | (Self::Or, AndOr::Or(_))
156 )
157 }
158}
159
160#[expect(clippy::from_over_into)]
162impl Into<PipelineOperator> for AndOr {
163 fn into(self) -> PipelineOperator {
164 match self {
165 Self::And(_) => PipelineOperator::And,
166 Self::Or(_) => PipelineOperator::Or,
167 }
168 }
169}
170
171pub struct AndOrListIter<'a> {
173 first: Option<&'a Pipeline>,
174 additional_iter: std::slice::Iter<'a, AndOr>,
175}
176
177impl<'a> Iterator for AndOrListIter<'a> {
178 type Item = (PipelineOperator, &'a Pipeline);
179
180 fn next(&mut self) -> Option<Self::Item> {
181 if let Some(first) = self.first.take() {
182 Some((PipelineOperator::And, first))
183 } else {
184 self.additional_iter.next().map(|and_or| match and_or {
185 AndOr::And(pipeline) => (PipelineOperator::And, pipeline),
186 AndOr::Or(pipeline) => (PipelineOperator::Or, pipeline),
187 })
188 }
189 }
190}
191
192impl<'a> IntoIterator for &'a AndOrList {
193 type Item = (PipelineOperator, &'a Pipeline);
194 type IntoIter = AndOrListIter<'a>;
195
196 fn into_iter(self) -> Self::IntoIter {
197 AndOrListIter {
198 first: Some(&self.first),
199 additional_iter: self.additional.iter(),
200 }
201 }
202}
203
204impl<'a> From<(PipelineOperator, &'a Pipeline)> for AndOr {
205 fn from(value: (PipelineOperator, &'a Pipeline)) -> Self {
206 match value.0 {
207 PipelineOperator::Or => Self::Or(value.1.to_owned()),
208 PipelineOperator::And => Self::And(value.1.to_owned()),
209 }
210 }
211}
212
213impl AndOrList {
214 pub fn iter(&self) -> AndOrListIter<'_> {
216 self.into_iter()
217 }
218}
219
220#[derive(Clone, Debug)]
223#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
224#[cfg_attr(
225 any(test, feature = "serde"),
226 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
227)]
228pub enum AndOr {
229 And(Pipeline),
232 Or(Pipeline),
235}
236
237impl Node for AndOr {}
238
239impl SourceLocation for AndOr {
241 fn location(&self) -> Option<SourceSpan> {
242 match self {
243 Self::And(p) => p.location(),
244 Self::Or(p) => p.location(),
245 }
246 }
247}
248
249impl Display for AndOr {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 match self {
252 Self::And(pipeline) => write!(f, " && {pipeline}"),
253 Self::Or(pipeline) => write!(f, " || {pipeline}"),
254 }
255 }
256}
257
258#[derive(Clone, Debug)]
260#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
261#[cfg_attr(
262 any(test, feature = "serde"),
263 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
264)]
265pub enum PipelineTimed {
266 Timed(SourceSpan),
268 TimedWithPosixOutput(SourceSpan),
270}
271
272impl Node for PipelineTimed {}
273
274impl SourceLocation for PipelineTimed {
275 fn location(&self) -> Option<SourceSpan> {
276 match self {
277 Self::Timed(t) => Some(t.to_owned()),
278 Self::TimedWithPosixOutput(t) => Some(t.to_owned()),
279 }
280 }
281}
282
283impl Display for PipelineTimed {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 match self {
286 Self::Timed(_) => write!(f, "time"),
287 Self::TimedWithPosixOutput(_) => write!(f, "time -p"),
288 }
289 }
290}
291
292impl PipelineTimed {
293 pub const fn is_posix_output(&self) -> bool {
295 matches!(self, Self::TimedWithPosixOutput(_))
296 }
297}
298
299#[derive(Clone, Debug)]
302#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
303#[cfg_attr(
304 any(test, feature = "serde"),
305 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
306)]
307pub struct Pipeline {
308 #[cfg_attr(
311 any(test, feature = "serde"),
312 serde(skip_serializing_if = "Option::is_none", default)
313 )]
314 pub timed: Option<PipelineTimed>,
315 #[cfg_attr(
318 any(test, feature = "serde"),
319 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
320 )]
321 pub bang: bool,
322 pub seq: Vec<Command>,
324}
325
326impl Node for Pipeline {}
327
328impl SourceLocation for Pipeline {
330 fn location(&self) -> Option<SourceSpan> {
331 let start = self
332 .timed
333 .as_ref()
334 .and_then(SourceLocation::location)
335 .or_else(|| self.seq.first().and_then(SourceLocation::location));
336 let end = self.seq.last().and_then(SourceLocation::location);
337
338 maybe_location(start.as_ref(), end.as_ref())
339 }
340}
341
342impl Display for Pipeline {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 if let Some(timed) = &self.timed {
345 write!(f, "{timed} ")?;
346 }
347
348 if self.bang {
349 write!(f, "! ")?;
350 }
351 for (i, command) in self.seq.iter().enumerate() {
352 if i > 0 {
353 write!(f, " |")?;
354 }
355 write!(f, "{command}")?;
356 }
357
358 Ok(())
359 }
360}
361
362#[derive(Clone, Debug)]
364#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
365#[cfg_attr(
366 any(test, feature = "serde"),
367 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
368)]
369pub enum Command {
370 Simple(SimpleCommand),
373 Compound(CompoundCommand, Option<RedirectList>),
375 Function(FunctionDefinition),
377}
378
379impl Node for Command {}
380
381impl SourceLocation for Command {
382 fn location(&self) -> Option<SourceSpan> {
383 match self {
384 Self::Simple(s) => s.location(),
385 Self::Compound(c, r) => {
386 match (c.location(), r.as_ref().and_then(SourceLocation::location)) {
387 (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
388 (s, _) => s,
389 }
390 }
391 Self::Function(f) => f.location(),
392 }
393 }
394}
395
396impl Display for Command {
397 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398 match self {
399 Self::Simple(simple_command) => write!(f, "{simple_command}"),
400 Self::Compound(compound_command, redirect_list) => {
401 write!(f, "{compound_command}")?;
402 if let Some(redirect_list) = redirect_list {
403 write!(f, "{redirect_list}")?;
404 }
405 Ok(())
406 }
407 Self::Function(function_definition) => write!(f, "{function_definition}"),
408 }
409 }
410}
411
412#[derive(Clone, Debug)]
414#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
415#[cfg_attr(
416 any(test, feature = "serde"),
417 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
418)]
419pub enum CompoundCommand {
420 Arithmetic(ArithmeticCommand),
422 ArithmeticForClause(ArithmeticForClauseCommand),
424 BraceGroup(BraceGroupCommand),
426 Subshell(SubshellCommand),
428 ForClause(ForClauseCommand),
430 CaseClause(CaseClauseCommand),
433 IfClause(IfClauseCommand),
435 WhileClause(WhileOrUntilClauseCommand),
437 UntilClause(WhileOrUntilClauseCommand),
439 Coprocess(CoprocessCommand),
441 ExtendedTest(ExtendedTestExprCommand),
443}
444
445impl Node for CompoundCommand {}
446
447impl SourceLocation for CompoundCommand {
448 fn location(&self) -> Option<SourceSpan> {
449 match self {
450 Self::Arithmetic(a) => a.location(),
451 Self::ArithmeticForClause(a) => a.location(),
452 Self::BraceGroup(b) => b.location(),
453 Self::Subshell(s) => s.location(),
454 Self::ForClause(f) => f.location(),
455 Self::CaseClause(c) => c.location(),
456 Self::IfClause(i) => i.location(),
457 Self::WhileClause(w) => w.location(),
458 Self::UntilClause(u) => u.location(),
459 Self::Coprocess(c) => c.location(),
460 Self::ExtendedTest(e) => e.location(),
461 }
462 }
463}
464
465impl Display for CompoundCommand {
466 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467 match self {
468 Self::Arithmetic(arithmetic_command) => write!(f, "{arithmetic_command}"),
469 Self::ArithmeticForClause(arithmetic_for_clause_command) => {
470 write!(f, "{arithmetic_for_clause_command}")
471 }
472 Self::BraceGroup(brace_group_command) => {
473 write!(f, "{brace_group_command}")
474 }
475 Self::Subshell(subshell_command) => write!(f, "{subshell_command}"),
476 Self::ForClause(for_clause_command) => write!(f, "{for_clause_command}"),
477 Self::CaseClause(case_clause_command) => {
478 write!(f, "{case_clause_command}")
479 }
480 Self::IfClause(if_clause_command) => write!(f, "{if_clause_command}"),
481 Self::WhileClause(while_or_until_clause_command) => {
482 write!(f, "while {while_or_until_clause_command}")
483 }
484 Self::UntilClause(while_or_until_clause_command) => {
485 write!(f, "until {while_or_until_clause_command}")
486 }
487 Self::Coprocess(coproc_clause_command) => {
488 write!(f, "{coproc_clause_command}")
489 }
490 Self::ExtendedTest(extended_test_expr_command) => {
491 write!(f, "[[ {extended_test_expr_command} ]]")
492 }
493 }
494 }
495}
496
497#[derive(Clone, Debug)]
499#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
500#[cfg_attr(
501 any(test, feature = "serde"),
502 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
503)]
504pub struct ArithmeticCommand {
505 pub expr: UnexpandedArithmeticExpr,
507 pub loc: SourceSpan,
509}
510
511impl Node for ArithmeticCommand {}
512
513impl SourceLocation for ArithmeticCommand {
514 fn location(&self) -> Option<SourceSpan> {
515 Some(self.loc.clone())
516 }
517}
518
519impl Display for ArithmeticCommand {
520 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521 write!(f, "(({}))", self.expr)
522 }
523}
524
525#[derive(Clone, Debug)]
527#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
528#[cfg_attr(
529 any(test, feature = "serde"),
530 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
531)]
532pub struct SubshellCommand {
533 pub list: CompoundList,
535 pub loc: SourceSpan,
537}
538
539impl Node for SubshellCommand {}
540
541impl SourceLocation for SubshellCommand {
542 fn location(&self) -> Option<SourceSpan> {
543 Some(self.loc.clone())
544 }
545}
546
547impl Display for SubshellCommand {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 write!(f, "( ")?;
550 write!(f, "{}", self.list)?;
551 write!(f, " )")
552 }
553}
554
555#[derive(Clone, Debug)]
557#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
558#[cfg_attr(
559 any(test, feature = "serde"),
560 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
561)]
562pub struct ForClauseCommand {
563 pub variable_name: String,
565 pub values: Option<Vec<Word>>,
567 pub body: DoGroupCommand,
569 pub loc: SourceSpan,
571}
572
573impl Node for ForClauseCommand {}
574
575impl SourceLocation for ForClauseCommand {
576 fn location(&self) -> Option<SourceSpan> {
577 Some(self.loc.clone())
578 }
579}
580
581impl Display for ForClauseCommand {
582 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
583 write!(f, "for {} in ", self.variable_name)?;
584
585 if let Some(values) = &self.values {
586 for (i, value) in values.iter().enumerate() {
587 if i > 0 {
588 write!(f, " ")?;
589 }
590
591 write!(f, "{value}")?;
592 }
593 }
594
595 writeln!(f, ";")?;
596
597 write!(f, "{}", self.body)
598 }
599}
600
601#[derive(Clone, Debug)]
603#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
604#[cfg_attr(
605 any(test, feature = "serde"),
606 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
607)]
608pub struct ArithmeticForClauseCommand {
609 pub initializer: Option<UnexpandedArithmeticExpr>,
611 pub condition: Option<UnexpandedArithmeticExpr>,
613 pub updater: Option<UnexpandedArithmeticExpr>,
615 pub body: DoGroupCommand,
617 pub loc: SourceSpan,
619}
620
621impl Node for ArithmeticForClauseCommand {}
622
623impl SourceLocation for ArithmeticForClauseCommand {
624 fn location(&self) -> Option<SourceSpan> {
625 Some(self.loc.clone())
626 }
627}
628
629impl Display for ArithmeticForClauseCommand {
630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631 write!(f, "for ((")?;
632
633 if let Some(initializer) = &self.initializer {
634 write!(f, "{initializer}")?;
635 }
636
637 write!(f, "; ")?;
638
639 if let Some(condition) = &self.condition {
640 write!(f, "{condition}")?;
641 }
642
643 write!(f, "; ")?;
644
645 if let Some(updater) = &self.updater {
646 write!(f, "{updater}")?;
647 }
648
649 writeln!(f, "))")?;
650
651 write!(f, "{}", self.body)
652 }
653}
654
655#[derive(Clone, Debug)]
658#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
659#[cfg_attr(
660 any(test, feature = "serde"),
661 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
662)]
663pub struct CaseClauseCommand {
664 pub value: Word,
666 pub cases: Vec<CaseItem>,
668 pub loc: SourceSpan,
670}
671
672impl Node for CaseClauseCommand {}
673
674impl SourceLocation for CaseClauseCommand {
675 fn location(&self) -> Option<SourceSpan> {
676 Some(self.loc.clone())
677 }
678}
679
680impl Display for CaseClauseCommand {
681 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
682 write!(f, "case {} in ", self.value)?;
684 for case in &self.cases {
685 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{case}")?;
686 }
687 writeln!(f)?;
688 write!(f, "esac")
689 }
690}
691
692#[derive(Clone, Debug)]
694#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
695#[cfg_attr(
696 any(test, feature = "serde"),
697 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
698)]
699pub struct CompoundList(pub Vec<CompoundListItem>);
700
701impl Node for CompoundList {}
702
703impl SourceLocation for CompoundList {
705 fn location(&self) -> Option<SourceSpan> {
706 let start = self.0.first().and_then(SourceLocation::location);
707 let end = self.0.last().and_then(SourceLocation::location);
708
709 if let (Some(s), Some(e)) = (start, end) {
710 Some(SourceSpan::within(&s, &e))
711 } else {
712 None
713 }
714 }
715}
716
717impl CompoundList {
718 fn terminated(&self) -> impl Display + '_ {
721 TerminatedCompoundList(self)
722 }
723
724 fn fmt_items(
725 &self,
726 f: &mut std::fmt::Formatter<'_>,
727 keep_trailing_separator: bool,
728 ) -> std::fmt::Result {
729 for (i, item) in self.0.iter().enumerate() {
730 if i > 0 {
731 writeln!(f)?;
732 }
733
734 write!(f, "{}", item.0)?;
736
737 if keep_trailing_separator
740 || i < self.0.len() - 1
741 || !matches!(item.1, SeparatorOperator::Sequence)
742 {
743 write!(f, "{}", item.1)?;
744 }
745 }
746
747 Ok(())
748 }
749}
750
751impl Display for CompoundList {
752 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
753 self.fmt_items(f, false)
754 }
755}
756
757struct TerminatedCompoundList<'a>(&'a CompoundList);
758
759impl Display for TerminatedCompoundList<'_> {
760 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
761 self.0.fmt_items(f, true)
762 }
763}
764
765#[derive(Clone, Debug)]
767#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
768#[cfg_attr(
769 any(test, feature = "serde"),
770 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
771)]
772pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
773
774impl Node for CompoundListItem {}
775
776impl SourceLocation for CompoundListItem {
778 fn location(&self) -> Option<SourceSpan> {
779 self.0.location()
780 }
781}
782
783impl Display for CompoundListItem {
784 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
785 write!(f, "{}", self.0)?;
786 write!(f, "{}", self.1)?;
787 Ok(())
788 }
789}
790
791#[derive(Clone, Debug)]
793#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
794#[cfg_attr(
795 any(test, feature = "serde"),
796 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
797)]
798pub struct IfClauseCommand {
799 pub condition: CompoundList,
801 pub then: CompoundList,
803 #[cfg_attr(
805 any(test, feature = "serde"),
806 serde(skip_serializing_if = "Option::is_none", default)
807 )]
808 pub elses: Option<Vec<ElseClause>>,
809 pub loc: SourceSpan,
811}
812
813impl Node for IfClauseCommand {}
814
815impl SourceLocation for IfClauseCommand {
816 fn location(&self) -> Option<SourceSpan> {
817 Some(self.loc.clone())
818 }
819}
820
821impl Display for IfClauseCommand {
822 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
823 writeln!(f, "if {}; then", self.condition)?;
824 write!(
825 indenter::indented(f).with_str(DISPLAY_INDENT),
826 "{}",
827 self.then.terminated()
828 )?;
829 if let Some(elses) = &self.elses {
830 for else_clause in elses {
831 write!(f, "{else_clause}")?;
832 }
833 }
834
835 writeln!(f)?;
836 write!(f, "fi")?;
837
838 Ok(())
839 }
840}
841
842#[derive(Clone, Debug)]
844#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
845#[cfg_attr(
846 any(test, feature = "serde"),
847 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
848)]
849pub struct ElseClause {
850 #[cfg_attr(
852 any(test, feature = "serde"),
853 serde(skip_serializing_if = "Option::is_none", default)
854 )]
855 pub condition: Option<CompoundList>,
856 pub body: CompoundList,
858}
859
860impl Display for ElseClause {
861 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
862 writeln!(f)?;
863 if let Some(condition) = &self.condition {
864 writeln!(f, "elif {condition}; then")?;
865 } else {
866 writeln!(f, "else")?;
867 }
868
869 write!(
870 indenter::indented(f).with_str(DISPLAY_INDENT),
871 "{}",
872 self.body.terminated()
873 )
874 }
875}
876
877#[derive(Clone, Debug)]
879#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
880#[cfg_attr(
881 any(test, feature = "serde"),
882 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
883)]
884pub struct CoprocessCommand {
885 #[cfg_attr(
887 any(test, feature = "serde"),
888 serde(skip_serializing_if = "Option::is_none", default)
889 )]
890 pub name: Option<Word>,
891 pub body: Box<Command>,
893 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing, default))]
895 pub loc: SourceSpan,
896}
897
898impl Node for CoprocessCommand {}
899
900impl SourceLocation for CoprocessCommand {
901 fn location(&self) -> Option<SourceSpan> {
902 Some(self.loc.clone())
903 }
904}
905
906impl Display for CoprocessCommand {
907 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
908 write!(f, "coproc")?;
909 if let Some(name) = &self.name {
910 write!(f, " {name}")?;
911 }
912 write!(f, " {}", self.body)?;
913 Ok(())
914 }
915}
916
917#[derive(Clone, Debug)]
919#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
920#[cfg_attr(
921 any(test, feature = "serde"),
922 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
923)]
924pub struct CaseItem {
925 pub patterns: Vec<Word>,
927 pub cmd: Option<CompoundList>,
929 pub post_action: CaseItemPostAction,
931 pub loc: Option<SourceSpan>,
933}
934
935impl Node for CaseItem {}
936
937impl SourceLocation for CaseItem {
938 fn location(&self) -> Option<SourceSpan> {
939 self.loc.clone()
940 }
941}
942
943impl Display for CaseItem {
944 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
945 writeln!(f)?;
946 for (i, pattern) in self.patterns.iter().enumerate() {
947 if i > 0 {
948 write!(f, " | ")?;
950 }
951 write!(f, "{pattern}")?;
952 }
953 writeln!(f, ")")?;
954
955 if let Some(cmd) = &self.cmd {
956 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{cmd}")?;
957 }
958 writeln!(f)?;
959 write!(f, "{}", self.post_action)
960 }
961}
962
963#[derive(Clone, Debug)]
965#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
966#[cfg_attr(
967 any(test, feature = "serde"),
968 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
969)]
970pub enum CaseItemPostAction {
971 ExitCase,
973 UnconditionallyExecuteNextCaseItem,
976 ContinueEvaluatingCases,
979}
980
981impl Display for CaseItemPostAction {
982 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
983 match self {
984 Self::ExitCase => write!(f, ";;"),
985 Self::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
986 Self::ContinueEvaluatingCases => write!(f, ";;&"),
987 }
988 }
989}
990
991#[derive(Clone, Debug)]
993#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
994#[cfg_attr(
995 any(test, feature = "serde"),
996 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
997)]
998pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand, pub SourceSpan);
999
1000impl Node for WhileOrUntilClauseCommand {}
1001
1002impl SourceLocation for WhileOrUntilClauseCommand {
1003 fn location(&self) -> Option<SourceSpan> {
1004 Some(self.2.clone())
1005 }
1006}
1007
1008impl Display for WhileOrUntilClauseCommand {
1009 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1010 write!(f, "{}; {}", self.0, self.1)
1011 }
1012}
1013
1014#[derive(Clone, Debug)]
1016#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1017#[cfg_attr(
1018 any(test, feature = "serde"),
1019 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1020)]
1021pub struct FunctionDefinition {
1022 pub fname: Word,
1024 pub body: FunctionBody,
1026}
1027
1028impl Node for FunctionDefinition {}
1029
1030impl SourceLocation for FunctionDefinition {
1033 fn location(&self) -> Option<SourceSpan> {
1034 let start = self.fname.location();
1035 let end = self.body.location();
1036
1037 if let (Some(s), Some(e)) = (start, end) {
1038 Some(SourceSpan::within(&s, &e))
1039 } else {
1040 None
1041 }
1042 }
1043}
1044
1045impl Display for FunctionDefinition {
1046 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1047 writeln!(f, "{} () ", self.fname.value)?;
1048 write!(f, "{}", self.body)?;
1049 Ok(())
1050 }
1051}
1052
1053#[derive(Clone, Debug)]
1055#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1056#[cfg_attr(
1057 any(test, feature = "serde"),
1058 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1059)]
1060pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
1061
1062impl Node for FunctionBody {}
1063
1064impl SourceLocation for FunctionBody {
1065 fn location(&self) -> Option<SourceSpan> {
1066 let cmd_span = self.0.location();
1067 let redirect_span = self.1.as_ref().and_then(SourceLocation::location);
1068
1069 match (cmd_span, redirect_span) {
1070 (Some(cmd_span), Some(redirect_span)) => {
1072 Some(SourceSpan::within(&cmd_span, &redirect_span))
1073 }
1074 (Some(cmd_span), None) => Some(cmd_span),
1076 _ => None,
1077 }
1078 }
1079}
1080
1081impl Display for FunctionBody {
1082 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1083 write!(f, "{}", self.0)?;
1084 if let Some(redirect_list) = &self.1 {
1085 write!(f, "{redirect_list}")?;
1086 }
1087
1088 Ok(())
1089 }
1090}
1091
1092#[derive(Clone, Debug)]
1094#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1095#[cfg_attr(
1096 any(test, feature = "serde"),
1097 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1098)]
1099pub struct BraceGroupCommand {
1100 pub list: CompoundList,
1102 pub loc: SourceSpan,
1104}
1105
1106impl Node for BraceGroupCommand {}
1107
1108impl SourceLocation for BraceGroupCommand {
1109 fn location(&self) -> Option<SourceSpan> {
1110 Some(self.loc.clone())
1111 }
1112}
1113
1114impl Display for BraceGroupCommand {
1115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1116 writeln!(f, "{{ ")?;
1117 write!(
1118 indenter::indented(f).with_str(DISPLAY_INDENT),
1119 "{}",
1120 self.list
1121 )?;
1122 writeln!(f)?;
1123 write!(f, "}}")?;
1124
1125 Ok(())
1126 }
1127}
1128
1129#[derive(Clone, Debug)]
1131#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1132#[cfg_attr(
1133 any(test, feature = "serde"),
1134 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1135)]
1136pub struct DoGroupCommand {
1137 pub list: CompoundList,
1139 pub loc: SourceSpan,
1141}
1142
1143impl Display for DoGroupCommand {
1144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1145 writeln!(f, "do")?;
1146 write!(
1147 indenter::indented(f).with_str(DISPLAY_INDENT),
1148 "{}",
1149 self.list.terminated()
1150 )?;
1151 writeln!(f)?;
1152 write!(f, "done")
1153 }
1154}
1155
1156#[derive(Clone, Debug)]
1158#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1159#[cfg_attr(
1160 any(test, feature = "serde"),
1161 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1162)]
1163pub struct SimpleCommand {
1164 #[cfg_attr(
1166 any(test, feature = "serde"),
1167 serde(skip_serializing_if = "Option::is_none", default)
1168 )]
1169 pub prefix: Option<CommandPrefix>,
1170 #[cfg_attr(
1172 any(test, feature = "serde"),
1173 serde(skip_serializing_if = "Option::is_none", default)
1174 )]
1175 pub word_or_name: Option<Word>,
1176 #[cfg_attr(
1178 any(test, feature = "serde"),
1179 serde(skip_serializing_if = "Option::is_none", default)
1180 )]
1181 pub suffix: Option<CommandSuffix>,
1182}
1183
1184impl Node for SimpleCommand {}
1185
1186impl SourceLocation for SimpleCommand {
1187 fn location(&self) -> Option<SourceSpan> {
1188 let mid = &self
1189 .word_or_name
1190 .as_ref()
1191 .and_then(SourceLocation::location);
1192 let start = self.prefix.as_ref().and_then(SourceLocation::location);
1193 let end = self.suffix.as_ref().and_then(SourceLocation::location);
1194
1195 match (start, mid, end) {
1196 (Some(start), _, Some(end)) => Some(SourceSpan::within(&start, &end)),
1197 (Some(start), Some(mid), None) => Some(SourceSpan::within(&start, mid)),
1198 (Some(start), None, None) => Some(start),
1199 (None, Some(mid), Some(end)) => Some(SourceSpan::within(mid, &end)),
1200 (None, Some(mid), None) => Some(mid.clone()),
1201 _ => None,
1202 }
1203 }
1204}
1205
1206impl Display for SimpleCommand {
1207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1208 let mut wrote_something = false;
1209
1210 if let Some(prefix) = &self.prefix {
1211 if wrote_something {
1212 write!(f, " ")?;
1213 }
1214
1215 write!(f, "{prefix}")?;
1216 wrote_something = true;
1217 }
1218
1219 if let Some(word_or_name) = &self.word_or_name {
1220 if wrote_something {
1221 write!(f, " ")?;
1222 }
1223
1224 write!(f, "{word_or_name}")?;
1225 wrote_something = true;
1226 }
1227
1228 if let Some(suffix) = &self.suffix {
1229 if wrote_something {
1230 write!(f, " ")?;
1231 }
1232
1233 write!(f, "{suffix}")?;
1234 }
1235
1236 Ok(())
1237 }
1238}
1239
1240#[derive(Clone, Debug, Default)]
1242#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1243#[cfg_attr(
1244 any(test, feature = "serde"),
1245 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1246)]
1247pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
1248
1249impl Node for CommandPrefix {}
1250
1251impl SourceLocation for CommandPrefix {
1252 fn location(&self) -> Option<SourceSpan> {
1253 let start = self.0.first().and_then(SourceLocation::location);
1254 let end = self.0.last().and_then(SourceLocation::location);
1255
1256 maybe_location(start.as_ref(), end.as_ref())
1257 }
1258}
1259
1260impl Display for CommandPrefix {
1261 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1262 for (i, item) in self.0.iter().enumerate() {
1263 if i > 0 {
1264 write!(f, " ")?;
1265 }
1266
1267 write!(f, "{item}")?;
1268 }
1269 Ok(())
1270 }
1271}
1272
1273#[derive(Clone, Default, Debug)]
1275#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1276#[cfg_attr(
1277 any(test, feature = "serde"),
1278 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1279)]
1280pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
1281
1282impl Node for CommandSuffix {}
1283
1284impl SourceLocation for CommandSuffix {
1285 fn location(&self) -> Option<SourceSpan> {
1286 let start = self.0.first().and_then(SourceLocation::location);
1287 let end = self.0.last().and_then(SourceLocation::location);
1288
1289 maybe_location(start.as_ref(), end.as_ref())
1290 }
1291}
1292
1293impl Display for CommandSuffix {
1294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1295 for (i, item) in self.0.iter().enumerate() {
1296 if i > 0 {
1297 write!(f, " ")?;
1298 }
1299
1300 write!(f, "{item}")?;
1301 }
1302 Ok(())
1303 }
1304}
1305
1306#[derive(Clone, Debug)]
1308#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1309#[cfg_attr(
1310 any(test, feature = "serde"),
1311 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1312)]
1313pub enum ProcessSubstitutionKind {
1314 Read,
1316 Write,
1318}
1319
1320impl Display for ProcessSubstitutionKind {
1321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1322 match self {
1323 Self::Read => write!(f, "<"),
1324 Self::Write => write!(f, ">"),
1325 }
1326 }
1327}
1328
1329#[derive(Clone, Debug)]
1331#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1332#[cfg_attr(
1333 any(test, feature = "serde"),
1334 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1335)]
1336pub enum CommandPrefixOrSuffixItem {
1337 IoRedirect(IoRedirect),
1339 Word(Word),
1341 AssignmentWord(Assignment, Word),
1343 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1345}
1346
1347impl Node for CommandPrefixOrSuffixItem {}
1348
1349impl SourceLocation for CommandPrefixOrSuffixItem {
1350 fn location(&self) -> Option<SourceSpan> {
1351 match self {
1352 Self::Word(w) => w.location(),
1353 Self::IoRedirect(io_redirect) => io_redirect.location(),
1354 Self::AssignmentWord(assignment, _word) => assignment.location(),
1355 Self::ProcessSubstitution(_kind, cmd) => cmd.location(),
1357 }
1358 }
1359}
1360
1361impl Display for CommandPrefixOrSuffixItem {
1362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1363 match self {
1364 Self::IoRedirect(io_redirect) => write!(f, "{io_redirect}"),
1365 Self::Word(word) => write!(f, "{word}"),
1366 Self::AssignmentWord(_assignment, word) => write!(f, "{word}"),
1367 Self::ProcessSubstitution(kind, subshell_command) => {
1368 write!(f, "{kind}({subshell_command})")
1369 }
1370 }
1371 }
1372}
1373
1374#[derive(Clone, Debug)]
1376#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1377#[cfg_attr(
1378 any(test, feature = "serde"),
1379 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1380)]
1381pub struct Assignment {
1382 pub name: AssignmentName,
1384 pub value: AssignmentValue,
1386 #[cfg_attr(
1388 any(test, feature = "serde"),
1389 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1390 )]
1391 pub append: bool,
1392 pub loc: SourceSpan,
1394}
1395
1396impl Node for Assignment {}
1397
1398impl SourceLocation for Assignment {
1399 fn location(&self) -> Option<SourceSpan> {
1400 Some(self.loc.clone())
1401 }
1402}
1403
1404impl Display for Assignment {
1405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1406 write!(f, "{}", self.name)?;
1407 if self.append {
1408 write!(f, "+")?;
1409 }
1410 write!(f, "={}", self.value)
1411 }
1412}
1413
1414#[derive(Clone, Debug)]
1416#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1417#[cfg_attr(
1418 any(test, feature = "serde"),
1419 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1420)]
1421pub enum AssignmentName {
1422 VariableName(String),
1424 ArrayElementName(String, String),
1426}
1427
1428impl AssignmentName {
1429 pub fn base_name(&self) -> &str {
1432 match self {
1433 Self::VariableName(name) => name,
1434 Self::ArrayElementName(name, _index) => name,
1435 }
1436 }
1437}
1438
1439impl Display for AssignmentName {
1440 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1441 match self {
1442 Self::VariableName(name) => write!(f, "{name}"),
1443 Self::ArrayElementName(name, index) => {
1444 write!(f, "{name}[{index}]")
1445 }
1446 }
1447 }
1448}
1449
1450#[derive(Clone, Debug)]
1452#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1453#[cfg_attr(
1454 any(test, feature = "serde"),
1455 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1456)]
1457pub enum AssignmentValue {
1458 Scalar(Word),
1460 Array(Vec<(Option<Word>, Word)>),
1462}
1463
1464impl Node for AssignmentValue {}
1465
1466impl SourceLocation for AssignmentValue {
1467 fn location(&self) -> Option<SourceSpan> {
1468 match self {
1469 Self::Scalar(word) => word.location(),
1470 Self::Array(words) => {
1471 let first = words.first().and_then(|(_key, value)| value.location());
1473 let last = words.last().and_then(|(_key, value)| value.location());
1474 maybe_location(first.as_ref(), last.as_ref())
1475 }
1476 }
1477 }
1478}
1479
1480impl Display for AssignmentValue {
1481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1482 match self {
1483 Self::Scalar(word) => write!(f, "{word}"),
1484 Self::Array(words) => {
1485 write!(f, "(")?;
1486 for (i, value) in words.iter().enumerate() {
1487 if i > 0 {
1488 write!(f, " ")?;
1489 }
1490 match value {
1491 (Some(key), value) => write!(f, "[{key}]={value}")?,
1492 (None, value) => write!(f, "{value}")?,
1493 }
1494 }
1495 write!(f, ")")
1496 }
1497 }
1498 }
1499}
1500
1501#[derive(Clone, Debug)]
1503#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1504#[cfg_attr(
1505 any(test, feature = "serde"),
1506 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1507)]
1508pub struct RedirectList(pub Vec<IoRedirect>);
1509
1510impl Node for RedirectList {}
1511
1512impl SourceLocation for RedirectList {
1513 fn location(&self) -> Option<SourceSpan> {
1514 let first = self.0.first().and_then(SourceLocation::location);
1515 let last = self.0.last().and_then(SourceLocation::location);
1516 maybe_location(first.as_ref(), last.as_ref())
1517 }
1518}
1519
1520impl Display for RedirectList {
1521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1522 for item in &self.0 {
1523 write!(f, "{item}")?;
1524 }
1525 Ok(())
1526 }
1527}
1528
1529pub type IoFd = i32;
1531
1532#[derive(Clone, Debug)]
1534#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1535#[cfg_attr(
1536 any(test, feature = "serde"),
1537 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1538)]
1539pub enum IoRedirect {
1540 File(Option<IoFd>, IoFileRedirectKind, IoFileRedirectTarget),
1542 HereDocument(Option<IoFd>, IoHereDocument),
1544 HereString(Option<IoFd>, Word),
1546 OutputAndError(Word, bool),
1548}
1549
1550impl Node for IoRedirect {}
1551
1552impl SourceLocation for IoRedirect {
1553 fn location(&self) -> Option<SourceSpan> {
1554 None
1556 }
1557}
1558
1559impl Display for IoRedirect {
1560 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1561 match self {
1562 Self::File(fd_num, kind, target) => {
1563 if let Some(fd_num) = fd_num {
1564 write!(f, "{fd_num}")?;
1565 }
1566
1567 write!(f, "{kind} {target}")?;
1568 }
1569 Self::OutputAndError(target, append) => {
1570 write!(f, "&>")?;
1571 if *append {
1572 write!(f, ">")?;
1573 }
1574 write!(f, " {target}")?;
1575 }
1576 Self::HereDocument(fd_num, here_doc) => {
1577 if let Some(fd_num) = fd_num {
1578 write!(f, "{fd_num}")?;
1579 }
1580
1581 write!(f, "<<{here_doc}")?;
1582 }
1583 Self::HereString(fd_num, s) => {
1584 if let Some(fd_num) = fd_num {
1585 write!(f, "{fd_num}")?;
1586 }
1587
1588 write!(f, "<<< {s}")?;
1589 }
1590 }
1591
1592 Ok(())
1593 }
1594}
1595
1596#[derive(Clone, Debug)]
1598#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1599#[cfg_attr(
1600 any(test, feature = "serde"),
1601 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1602)]
1603pub enum IoFileRedirectKind {
1604 Read,
1606 Write,
1608 Append,
1610 ReadAndWrite,
1612 Clobber,
1614 DuplicateInput,
1616 DuplicateOutput,
1618}
1619
1620impl Display for IoFileRedirectKind {
1621 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1622 match self {
1623 Self::Read => write!(f, "<"),
1624 Self::Write => write!(f, ">"),
1625 Self::Append => write!(f, ">>"),
1626 Self::ReadAndWrite => write!(f, "<>"),
1627 Self::Clobber => write!(f, ">|"),
1628 Self::DuplicateInput => write!(f, "<&"),
1629 Self::DuplicateOutput => write!(f, ">&"),
1630 }
1631 }
1632}
1633
1634#[derive(Clone, Debug)]
1636#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1637#[cfg_attr(
1638 any(test, feature = "serde"),
1639 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1640)]
1641pub enum IoFileRedirectTarget {
1642 Filename(Word),
1644 Fd(IoFd),
1646 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1649 Duplicate(Word),
1653}
1654
1655impl Display for IoFileRedirectTarget {
1656 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1657 match self {
1658 Self::Filename(word) => write!(f, "{word}"),
1659 Self::Fd(fd) => write!(f, "{fd}"),
1660 Self::ProcessSubstitution(kind, subshell_command) => {
1661 write!(f, "{kind}{subshell_command}")
1662 }
1663 Self::Duplicate(word) => write!(f, "{word}"),
1664 }
1665 }
1666}
1667
1668#[derive(Clone, Debug)]
1670#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1671#[cfg_attr(
1672 any(test, feature = "serde"),
1673 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1674)]
1675pub struct IoHereDocument {
1676 #[cfg_attr(
1678 any(test, feature = "serde"),
1679 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1680 )]
1681 pub remove_tabs: bool,
1682 #[cfg_attr(
1684 any(test, feature = "serde"),
1685 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1686 )]
1687 pub requires_expansion: bool,
1688 pub here_end: Word,
1690 pub doc: Word,
1692}
1693
1694impl Node for IoHereDocument {}
1695
1696impl SourceLocation for IoHereDocument {
1697 fn location(&self) -> Option<SourceSpan> {
1698 None
1700 }
1701}
1702
1703impl Display for IoHereDocument {
1704 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1705 if self.remove_tabs {
1706 write!(f, "-")?;
1707 }
1708
1709 writeln!(f, "{}", self.here_end)?;
1710 write!(f, "{}", self.doc)?;
1711 writeln!(f, "{}", self.here_end)?;
1712
1713 Ok(())
1714 }
1715}
1716
1717#[derive(Clone, Debug)]
1719#[cfg_attr(
1720 any(test, feature = "serde"),
1721 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1722)]
1723pub enum TestExpr {
1724 False,
1726 Literal(String),
1728 And(Box<Self>, Box<Self>),
1730 Or(Box<Self>, Box<Self>),
1732 Not(Box<Self>),
1734 Parenthesized(Box<Self>),
1736 UnaryTest(UnaryPredicate, String),
1738 BinaryTest(BinaryPredicate, String, String),
1740}
1741
1742impl Node for TestExpr {}
1743
1744impl SourceLocation for TestExpr {
1745 fn location(&self) -> Option<SourceSpan> {
1746 None
1748 }
1749}
1750
1751impl Display for TestExpr {
1752 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1753 match self {
1754 Self::False => Ok(()),
1755 Self::Literal(s) => write!(f, "{s}"),
1756 Self::And(left, right) => write!(f, "{left} -a {right}"),
1757 Self::Or(left, right) => write!(f, "{left} -o {right}"),
1758 Self::Not(expr) => write!(f, "! {expr}"),
1759 Self::Parenthesized(expr) => write!(f, "( {expr} )"),
1760 Self::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
1761 Self::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
1762 }
1763 }
1764}
1765
1766#[derive(Clone, Debug)]
1768#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1769#[cfg_attr(
1770 any(test, feature = "serde"),
1771 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1772)]
1773pub enum ExtendedTestExpr {
1774 And(Box<Self>, Box<Self>),
1776 Or(Box<Self>, Box<Self>),
1778 Not(Box<Self>),
1780 Parenthesized(Box<Self>),
1782 UnaryTest(UnaryPredicate, Word),
1784 BinaryTest(BinaryPredicate, Word, Word),
1786}
1787
1788impl Display for ExtendedTestExpr {
1789 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1790 match self {
1791 Self::And(left, right) => {
1792 write!(f, "{left} && {right}")
1793 }
1794 Self::Or(left, right) => {
1795 write!(f, "{left} || {right}")
1796 }
1797 Self::Not(expr) => {
1798 write!(f, "! {expr}")
1799 }
1800 Self::Parenthesized(expr) => {
1801 write!(f, "( {expr} )")
1802 }
1803 Self::UnaryTest(pred, word) => {
1804 write!(f, "{pred} {word}")
1805 }
1806 Self::BinaryTest(pred, left, right) => {
1807 write!(f, "{left} {pred} {right}")
1808 }
1809 }
1810 }
1811}
1812
1813#[derive(Clone, Debug)]
1815#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1816#[cfg_attr(
1817 any(test, feature = "serde"),
1818 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1819)]
1820pub struct ExtendedTestExprCommand {
1821 pub expr: ExtendedTestExpr,
1823 pub loc: SourceSpan,
1825}
1826
1827impl Node for ExtendedTestExprCommand {}
1828
1829impl SourceLocation for ExtendedTestExprCommand {
1830 fn location(&self) -> Option<SourceSpan> {
1831 Some(self.loc.clone())
1832 }
1833}
1834
1835impl Display for ExtendedTestExprCommand {
1836 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1837 self.expr.fmt(f)
1838 }
1839}
1840
1841#[derive(Clone, Debug)]
1843#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1844#[cfg_attr(
1845 any(test, feature = "serde"),
1846 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1847)]
1848pub enum UnaryPredicate {
1849 FileExists,
1851 FileExistsAndIsBlockSpecialFile,
1853 FileExistsAndIsCharSpecialFile,
1855 FileExistsAndIsDir,
1857 FileExistsAndIsRegularFile,
1859 FileExistsAndIsSetgid,
1861 FileExistsAndIsSymlink,
1863 FileExistsAndHasStickyBit,
1865 FileExistsAndIsFifo,
1867 FileExistsAndIsReadable,
1869 FileExistsAndIsNotZeroLength,
1871 FdIsOpenTerminal,
1873 FileExistsAndIsSetuid,
1875 FileExistsAndIsWritable,
1877 FileExistsAndIsExecutable,
1879 FileExistsAndOwnedByEffectiveGroupId,
1882 FileExistsAndModifiedSinceLastRead,
1885 FileExistsAndOwnedByEffectiveUserId,
1888 FileExistsAndIsSocket,
1890 ShellOptionEnabled,
1892 ShellVariableIsSetAndAssigned,
1894 ShellVariableIsSetAndNameRef,
1896 StringHasZeroLength,
1898 StringHasNonZeroLength,
1900}
1901
1902impl Display for UnaryPredicate {
1903 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1904 match self {
1905 Self::FileExists => write!(f, "-e"),
1906 Self::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
1907 Self::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
1908 Self::FileExistsAndIsDir => write!(f, "-d"),
1909 Self::FileExistsAndIsRegularFile => write!(f, "-f"),
1910 Self::FileExistsAndIsSetgid => write!(f, "-g"),
1911 Self::FileExistsAndIsSymlink => write!(f, "-h"),
1912 Self::FileExistsAndHasStickyBit => write!(f, "-k"),
1913 Self::FileExistsAndIsFifo => write!(f, "-p"),
1914 Self::FileExistsAndIsReadable => write!(f, "-r"),
1915 Self::FileExistsAndIsNotZeroLength => write!(f, "-s"),
1916 Self::FdIsOpenTerminal => write!(f, "-t"),
1917 Self::FileExistsAndIsSetuid => write!(f, "-u"),
1918 Self::FileExistsAndIsWritable => write!(f, "-w"),
1919 Self::FileExistsAndIsExecutable => write!(f, "-x"),
1920 Self::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
1921 Self::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
1922 Self::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
1923 Self::FileExistsAndIsSocket => write!(f, "-S"),
1924 Self::ShellOptionEnabled => write!(f, "-o"),
1925 Self::ShellVariableIsSetAndAssigned => write!(f, "-v"),
1926 Self::ShellVariableIsSetAndNameRef => write!(f, "-R"),
1927 Self::StringHasZeroLength => write!(f, "-z"),
1928 Self::StringHasNonZeroLength => write!(f, "-n"),
1929 }
1930 }
1931}
1932
1933#[derive(Clone, Debug)]
1935#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1936#[cfg_attr(
1937 any(test, feature = "serde"),
1938 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1939)]
1940pub enum BinaryPredicate {
1941 FilesReferToSameDeviceAndInodeNumbers,
1943 LeftFileIsNewerOrExistsWhenRightDoesNot,
1945 LeftFileIsOlderOrDoesNotExistWhenRightDoes,
1947 StringExactlyMatchesPattern,
1949 StringDoesNotExactlyMatchPattern,
1951 StringMatchesRegex,
1953 StringExactlyMatchesString,
1955 StringDoesNotExactlyMatchString,
1957 StringContainsSubstring,
1959 LeftSortsBeforeRight,
1961 LeftSortsAfterRight,
1963 ArithmeticEqualTo,
1965 ArithmeticNotEqualTo,
1967 ArithmeticLessThan,
1969 ArithmeticLessThanOrEqualTo,
1971 ArithmeticGreaterThan,
1973 ArithmeticGreaterThanOrEqualTo,
1975}
1976
1977impl Display for BinaryPredicate {
1978 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1979 match self {
1980 Self::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
1981 Self::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
1982 Self::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
1983 Self::StringExactlyMatchesPattern => write!(f, "=="),
1984 Self::StringDoesNotExactlyMatchPattern => write!(f, "!="),
1985 Self::StringMatchesRegex => write!(f, "=~"),
1986 Self::StringContainsSubstring => write!(f, "=~"),
1987 Self::StringExactlyMatchesString => write!(f, "=="),
1988 Self::StringDoesNotExactlyMatchString => write!(f, "!="),
1989 Self::LeftSortsBeforeRight => write!(f, "<"),
1990 Self::LeftSortsAfterRight => write!(f, ">"),
1991 Self::ArithmeticEqualTo => write!(f, "-eq"),
1992 Self::ArithmeticNotEqualTo => write!(f, "-ne"),
1993 Self::ArithmeticLessThan => write!(f, "-lt"),
1994 Self::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
1995 Self::ArithmeticGreaterThan => write!(f, "-gt"),
1996 Self::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
1997 }
1998 }
1999}
2000
2001#[derive(Clone, Debug)]
2003#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2004#[cfg_attr(
2005 any(test, feature = "serde"),
2006 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2007)]
2008pub struct Word {
2009 pub value: String,
2011 pub loc: Option<SourceSpan>,
2013}
2014
2015impl Node for Word {}
2016
2017impl SourceLocation for Word {
2018 fn location(&self) -> Option<SourceSpan> {
2019 self.loc.clone()
2020 }
2021}
2022
2023impl Display for Word {
2024 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2025 write!(f, "{}", self.value)
2026 }
2027}
2028
2029impl From<&tokenizer::Token> for Word {
2030 fn from(t: &tokenizer::Token) -> Self {
2031 match t {
2032 tokenizer::Token::Word(value, loc) => Self {
2033 value: value.clone(),
2034 loc: Some(loc.clone()),
2035 },
2036 tokenizer::Token::Operator(value, loc) => Self {
2037 value: value.clone(),
2038 loc: Some(loc.clone()),
2039 },
2040 }
2041 }
2042}
2043
2044impl From<String> for Word {
2045 fn from(s: String) -> Self {
2046 Self {
2047 value: s,
2048 loc: None,
2049 }
2050 }
2051}
2052
2053impl AsRef<str> for Word {
2054 fn as_ref(&self) -> &str {
2055 &self.value
2056 }
2057}
2058
2059impl Word {
2060 pub fn new(s: &str) -> Self {
2062 Self {
2063 value: s.to_owned(),
2064 loc: None,
2065 }
2066 }
2067
2068 pub fn with_location(s: &str, loc: &SourceSpan) -> Self {
2070 Self {
2071 value: s.to_owned(),
2072 loc: Some(loc.to_owned()),
2073 }
2074 }
2075
2076 pub fn flatten(&self) -> String {
2078 self.value.clone()
2079 }
2080}
2081
2082#[derive(Clone, Debug)]
2084#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2085#[cfg_attr(
2086 any(test, feature = "serde"),
2087 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2088)]
2089pub struct UnexpandedArithmeticExpr {
2090 pub value: String,
2092}
2093
2094impl Display for UnexpandedArithmeticExpr {
2095 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2096 write!(f, "{}", self.value)
2097 }
2098}
2099
2100#[derive(Clone, Debug)]
2102#[cfg_attr(
2103 any(test, feature = "serde"),
2104 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2105)]
2106pub enum ArithmeticExpr {
2107 Literal(i64),
2109 Reference(ArithmeticTarget),
2111 UnaryOp(UnaryOperator, Box<Self>),
2113 BinaryOp(BinaryOperator, Box<Self>, Box<Self>),
2115 Conditional(Box<Self>, Box<Self>, Box<Self>),
2117 Assignment(ArithmeticTarget, Box<Self>),
2119 BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<Self>),
2121 UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
2123}
2124
2125impl Node for ArithmeticExpr {}
2126
2127impl SourceLocation for ArithmeticExpr {
2128 fn location(&self) -> Option<SourceSpan> {
2129 None
2131 }
2132}
2133
2134#[cfg(feature = "arbitrary")]
2135impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
2136 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2137 let variant = u.choose(&[
2138 "Literal",
2139 "Reference",
2140 "UnaryOp",
2141 "BinaryOp",
2142 "Conditional",
2143 "Assignment",
2144 "BinaryAssignment",
2145 "UnaryAssignment",
2146 ])?;
2147
2148 match *variant {
2149 "Literal" => Ok(Self::Literal(i64::arbitrary(u)?)),
2150 "Reference" => Ok(Self::Reference(ArithmeticTarget::arbitrary(u)?)),
2151 "UnaryOp" => Ok(Self::UnaryOp(
2152 UnaryOperator::arbitrary(u)?,
2153 Box::new(Self::arbitrary(u)?),
2154 )),
2155 "BinaryOp" => Ok(Self::BinaryOp(
2156 BinaryOperator::arbitrary(u)?,
2157 Box::new(Self::arbitrary(u)?),
2158 Box::new(Self::arbitrary(u)?),
2159 )),
2160 "Conditional" => Ok(Self::Conditional(
2161 Box::new(Self::arbitrary(u)?),
2162 Box::new(Self::arbitrary(u)?),
2163 Box::new(Self::arbitrary(u)?),
2164 )),
2165 "Assignment" => Ok(Self::Assignment(
2166 ArithmeticTarget::arbitrary(u)?,
2167 Box::new(Self::arbitrary(u)?),
2168 )),
2169 "BinaryAssignment" => Ok(Self::BinaryAssignment(
2170 BinaryOperator::arbitrary(u)?,
2171 ArithmeticTarget::arbitrary(u)?,
2172 Box::new(Self::arbitrary(u)?),
2173 )),
2174 "UnaryAssignment" => Ok(Self::UnaryAssignment(
2175 UnaryAssignmentOperator::arbitrary(u)?,
2176 ArithmeticTarget::arbitrary(u)?,
2177 )),
2178 _ => unreachable!(),
2179 }
2180 }
2181}
2182
2183impl Display for ArithmeticExpr {
2184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2185 match self {
2186 Self::Literal(literal) => write!(f, "{literal}"),
2187 Self::Reference(target) => write!(f, "{target}"),
2188 Self::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
2189 Self::BinaryOp(op, left, right) => {
2190 if matches!(op, BinaryOperator::Comma) {
2191 write!(f, "{left}{op} {right}")
2192 } else {
2193 write!(f, "{left} {op} {right}")
2194 }
2195 }
2196 Self::Conditional(condition, if_branch, else_branch) => {
2197 write!(f, "{condition} ? {if_branch} : {else_branch}")
2198 }
2199 Self::Assignment(target, value) => write!(f, "{target} = {value}"),
2200 Self::BinaryAssignment(op, target, operand) => {
2201 write!(f, "{target} {op}= {operand}")
2202 }
2203 Self::UnaryAssignment(op, target) => match op {
2204 UnaryAssignmentOperator::PrefixIncrement
2205 | UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
2206 UnaryAssignmentOperator::PostfixIncrement
2207 | UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
2208 },
2209 }
2210 }
2211}
2212
2213#[derive(Clone, Copy, Debug)]
2215#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2216#[cfg_attr(
2217 any(test, feature = "serde"),
2218 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2219)]
2220pub enum BinaryOperator {
2221 Power,
2223 Multiply,
2225 Divide,
2227 Modulo,
2229 Comma,
2231 Add,
2233 Subtract,
2235 ShiftLeft,
2237 ShiftRight,
2239 LessThan,
2241 LessThanOrEqualTo,
2243 GreaterThan,
2245 GreaterThanOrEqualTo,
2247 Equals,
2249 NotEquals,
2251 BitwiseAnd,
2253 BitwiseXor,
2255 BitwiseOr,
2257 LogicalAnd,
2259 LogicalOr,
2261}
2262
2263impl Display for BinaryOperator {
2264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2265 match self {
2266 Self::Power => write!(f, "**"),
2267 Self::Multiply => write!(f, "*"),
2268 Self::Divide => write!(f, "/"),
2269 Self::Modulo => write!(f, "%"),
2270 Self::Comma => write!(f, ","),
2271 Self::Add => write!(f, "+"),
2272 Self::Subtract => write!(f, "-"),
2273 Self::ShiftLeft => write!(f, "<<"),
2274 Self::ShiftRight => write!(f, ">>"),
2275 Self::LessThan => write!(f, "<"),
2276 Self::LessThanOrEqualTo => write!(f, "<="),
2277 Self::GreaterThan => write!(f, ">"),
2278 Self::GreaterThanOrEqualTo => write!(f, ">="),
2279 Self::Equals => write!(f, "=="),
2280 Self::NotEquals => write!(f, "!="),
2281 Self::BitwiseAnd => write!(f, "&"),
2282 Self::BitwiseXor => write!(f, "^"),
2283 Self::BitwiseOr => write!(f, "|"),
2284 Self::LogicalAnd => write!(f, "&&"),
2285 Self::LogicalOr => write!(f, "||"),
2286 }
2287 }
2288}
2289
2290#[derive(Clone, Copy, Debug)]
2292#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2293#[cfg_attr(
2294 any(test, feature = "serde"),
2295 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2296)]
2297pub enum UnaryOperator {
2298 UnaryPlus,
2300 UnaryMinus,
2302 BitwiseNot,
2304 LogicalNot,
2306}
2307
2308impl Display for UnaryOperator {
2309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2310 match self {
2311 Self::UnaryPlus => write!(f, "+"),
2312 Self::UnaryMinus => write!(f, "-"),
2313 Self::BitwiseNot => write!(f, "~"),
2314 Self::LogicalNot => write!(f, "!"),
2315 }
2316 }
2317}
2318
2319#[derive(Clone, Copy, Debug)]
2321#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2322#[cfg_attr(
2323 any(test, feature = "serde"),
2324 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2325)]
2326pub enum UnaryAssignmentOperator {
2327 PrefixIncrement,
2329 PrefixDecrement,
2331 PostfixIncrement,
2333 PostfixDecrement,
2335}
2336
2337impl Display for UnaryAssignmentOperator {
2338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2339 match self {
2340 Self::PrefixIncrement => write!(f, "++"),
2341 Self::PrefixDecrement => write!(f, "--"),
2342 Self::PostfixIncrement => write!(f, "++"),
2343 Self::PostfixDecrement => write!(f, "--"),
2344 }
2345 }
2346}
2347
2348#[derive(Clone, Debug)]
2350#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2351#[cfg_attr(
2352 any(test, feature = "serde"),
2353 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2354)]
2355pub enum ArithmeticTarget {
2356 Variable(String),
2358 ArrayElement(String, Box<ArithmeticExpr>),
2360}
2361
2362impl Node for ArithmeticTarget {}
2363
2364impl SourceLocation for ArithmeticTarget {
2365 fn location(&self) -> Option<SourceSpan> {
2366 None
2368 }
2369}
2370
2371impl Display for ArithmeticTarget {
2372 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2373 match self {
2374 Self::Variable(name) => write!(f, "{name}"),
2375 Self::ArrayElement(name, index) => write!(f, "{name}[{index}]"),
2376 }
2377 }
2378}
2379
2380#[cfg(test)]
2381#[allow(clippy::panic)]
2382mod tests {
2383 use super::*;
2384 use crate::{ParserOptions, SourcePosition};
2385 use std::io::BufReader;
2386
2387 fn parse(input: &str) -> Program {
2388 let reader = BufReader::new(input.as_bytes());
2389 let mut parser = crate::Parser::new(reader, &ParserOptions::default());
2390 parser.parse_program().unwrap()
2391 }
2392
2393 #[test]
2394 fn program_source_loc() {
2395 const INPUT: &str = r"echo hi
2396echo there
2397";
2398
2399 let loc = parse(INPUT).location().unwrap();
2400
2401 assert_eq!(
2402 *(loc.start),
2403 SourcePosition {
2404 line: 1,
2405 column: 1,
2406 index: 0
2407 }
2408 );
2409 assert_eq!(
2410 *(loc.end),
2411 SourcePosition {
2412 line: 2,
2413 column: 11,
2414 index: 18
2415 }
2416 );
2417 }
2418
2419 #[test]
2420 fn function_def_loc() {
2421 const INPUT: &str = r"my_func() {
2422 echo hi
2423 echo there
2424}
2425
2426my_func
2427";
2428
2429 let program = parse(INPUT);
2430
2431 let Command::Function(func_def) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2432 panic!("expected function definition");
2433 };
2434
2435 let loc = func_def.location().unwrap();
2436
2437 assert_eq!(
2438 *(loc.start),
2439 SourcePosition {
2440 line: 1,
2441 column: 1,
2442 index: 0
2443 }
2444 );
2445 assert_eq!(
2446 *(loc.end),
2447 SourcePosition {
2448 line: 4,
2449 column: 2,
2450 index: 36
2451 }
2452 );
2453 }
2454
2455 #[test]
2456 fn simple_cmd_loc() {
2457 const INPUT: &str = r"var=value somecmd arg1 arg2
2458";
2459
2460 let program = parse(INPUT);
2461
2462 let Command::Simple(cmd) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2463 panic!("expected function definition");
2464 };
2465
2466 let loc = cmd.location().unwrap();
2467
2468 assert_eq!(
2469 *(loc.start),
2470 SourcePosition {
2471 line: 1,
2472 column: 1,
2473 index: 0
2474 }
2475 );
2476 assert_eq!(
2477 *(loc.end),
2478 SourcePosition {
2479 line: 1,
2480 column: 28,
2481 index: 27
2482 }
2483 );
2484 }
2485}