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)]
79#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
80#[cfg_attr(
81 any(test, feature = "serde"),
82 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
83)]
84pub enum SeparatorOperator {
85 Async,
87 Sequence,
89}
90
91impl Display for SeparatorOperator {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 match self {
94 Self::Async => write!(f, "&"),
95 Self::Sequence => write!(f, ";"),
96 }
97 }
98}
99
100#[derive(Clone, Debug)]
102#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
103#[cfg_attr(
104 any(test, feature = "serde"),
105 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
106)]
107pub struct AndOrList {
108 pub first: Pipeline,
110 #[cfg_attr(
112 any(test, feature = "serde"),
113 serde(skip_serializing_if = "Vec::is_empty", default)
114 )]
115 pub additional: Vec<AndOr>,
116}
117
118impl Node for AndOrList {}
119
120impl SourceLocation for AndOrList {
121 fn location(&self) -> Option<SourceSpan> {
122 let start = self.first.location();
123 let last = self.additional.last();
124 let end = last.and_then(SourceLocation::location);
125
126 match (start, end) {
127 (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
128 (start, _) => start,
129 }
130 }
131}
132
133impl Display for AndOrList {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 write!(f, "{}", self.first)?;
136 for item in &self.additional {
137 write!(f, "{item}")?;
138 }
139
140 Ok(())
141 }
142}
143
144#[derive(PartialEq, Eq)]
147pub enum PipelineOperator {
148 And,
150 Or,
152}
153
154impl PartialEq<AndOr> for PipelineOperator {
155 fn eq(&self, other: &AndOr) -> bool {
156 matches!(
157 (self, other),
158 (Self::And, AndOr::And(_)) | (Self::Or, AndOr::Or(_))
159 )
160 }
161}
162
163#[expect(clippy::from_over_into)]
165impl Into<PipelineOperator> for AndOr {
166 fn into(self) -> PipelineOperator {
167 match self {
168 Self::And(_) => PipelineOperator::And,
169 Self::Or(_) => PipelineOperator::Or,
170 }
171 }
172}
173
174pub struct AndOrListIter<'a> {
176 first: Option<&'a Pipeline>,
177 additional_iter: std::slice::Iter<'a, AndOr>,
178}
179
180impl<'a> Iterator for AndOrListIter<'a> {
181 type Item = (PipelineOperator, &'a Pipeline);
182
183 fn next(&mut self) -> Option<Self::Item> {
184 if let Some(first) = self.first.take() {
185 Some((PipelineOperator::And, first))
186 } else {
187 self.additional_iter.next().map(|and_or| match and_or {
188 AndOr::And(pipeline) => (PipelineOperator::And, pipeline),
189 AndOr::Or(pipeline) => (PipelineOperator::Or, pipeline),
190 })
191 }
192 }
193}
194
195impl<'a> IntoIterator for &'a AndOrList {
196 type Item = (PipelineOperator, &'a Pipeline);
197 type IntoIter = AndOrListIter<'a>;
198
199 fn into_iter(self) -> Self::IntoIter {
200 AndOrListIter {
201 first: Some(&self.first),
202 additional_iter: self.additional.iter(),
203 }
204 }
205}
206
207impl<'a> From<(PipelineOperator, &'a Pipeline)> for AndOr {
208 fn from(value: (PipelineOperator, &'a Pipeline)) -> Self {
209 match value.0 {
210 PipelineOperator::Or => Self::Or(value.1.to_owned()),
211 PipelineOperator::And => Self::And(value.1.to_owned()),
212 }
213 }
214}
215
216impl AndOrList {
217 pub fn iter(&self) -> AndOrListIter<'_> {
219 self.into_iter()
220 }
221}
222
223#[derive(Clone, Debug)]
226#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
227#[cfg_attr(
228 any(test, feature = "serde"),
229 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
230)]
231pub enum AndOr {
232 And(Pipeline),
235 Or(Pipeline),
238}
239
240impl Node for AndOr {}
241
242impl SourceLocation for AndOr {
244 fn location(&self) -> Option<SourceSpan> {
245 match self {
246 Self::And(p) => p.location(),
247 Self::Or(p) => p.location(),
248 }
249 }
250}
251
252impl Display for AndOr {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 match self {
255 Self::And(pipeline) => write!(f, " && {pipeline}"),
256 Self::Or(pipeline) => write!(f, " || {pipeline}"),
257 }
258 }
259}
260
261#[derive(Clone, Debug)]
263#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
264#[cfg_attr(
265 any(test, feature = "serde"),
266 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
267)]
268pub enum PipelineTimed {
269 Timed(SourceSpan),
271 TimedWithPosixOutput(SourceSpan),
273}
274
275impl Node for PipelineTimed {}
276
277impl SourceLocation for PipelineTimed {
278 fn location(&self) -> Option<SourceSpan> {
279 match self {
280 Self::Timed(t) => Some(t.to_owned()),
281 Self::TimedWithPosixOutput(t) => Some(t.to_owned()),
282 }
283 }
284}
285
286impl Display for PipelineTimed {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 match self {
289 Self::Timed(_) => write!(f, "time"),
290 Self::TimedWithPosixOutput(_) => write!(f, "time -p"),
291 }
292 }
293}
294
295impl PipelineTimed {
296 pub const fn is_posix_output(&self) -> bool {
298 matches!(self, Self::TimedWithPosixOutput(_))
299 }
300}
301
302#[derive(Clone, Debug)]
305#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
306#[cfg_attr(
307 any(test, feature = "serde"),
308 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
309)]
310pub struct Pipeline {
311 #[cfg_attr(
314 any(test, feature = "serde"),
315 serde(skip_serializing_if = "Option::is_none", default)
316 )]
317 pub timed: Option<PipelineTimed>,
318 #[cfg_attr(
321 any(test, feature = "serde"),
322 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
323 )]
324 pub bang: bool,
325 pub seq: Vec<Command>,
327}
328
329impl Node for Pipeline {}
330
331impl SourceLocation for Pipeline {
334 fn location(&self) -> Option<SourceSpan> {
335 let start = self
336 .timed
337 .as_ref()
338 .and_then(SourceLocation::location)
339 .or_else(|| self.seq.first().and_then(SourceLocation::location));
340 let end = self.seq.last().and_then(SourceLocation::location);
341
342 maybe_location(start.as_ref(), end.as_ref())
343 }
344}
345
346impl Display for Pipeline {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 if let Some(timed) = &self.timed {
349 write!(f, "{timed} ")?;
350 }
351
352 if self.bang {
353 write!(f, "! ")?;
354 }
355 for (i, command) in self.seq.iter().enumerate() {
356 if i > 0 {
357 write!(f, " |")?;
358 }
359 write!(f, "{command}")?;
360 }
361
362 Ok(())
363 }
364}
365
366#[derive(Clone, Debug)]
368#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
369#[cfg_attr(
370 any(test, feature = "serde"),
371 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
372)]
373pub enum Command {
374 Simple(SimpleCommand),
377 Compound(CompoundCommand, Option<RedirectList>),
379 Function(FunctionDefinition),
381}
382
383impl Node for Command {}
384
385impl SourceLocation for Command {
386 fn location(&self) -> Option<SourceSpan> {
387 match self {
388 Self::Simple(s) => s.location(),
389 Self::Compound(c, r) => {
390 match (c.location(), r.as_ref().and_then(SourceLocation::location)) {
391 (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
392 (s, _) => s,
393 }
394 }
395 Self::Function(f) => f.location(),
396 }
397 }
398}
399
400impl Display for Command {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 match self {
403 Self::Simple(simple_command) => write!(f, "{simple_command}"),
404 Self::Compound(compound_command, redirect_list) => {
405 write!(f, "{compound_command}")?;
406 if let Some(redirect_list) = redirect_list {
407 write!(f, "{redirect_list}")?;
408 }
409 Ok(())
410 }
411 Self::Function(function_definition) => write!(f, "{function_definition}"),
412 }
413 }
414}
415
416#[derive(Clone, Debug)]
419#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
420#[cfg_attr(
421 any(test, feature = "serde"),
422 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
423)]
424pub enum CompoundCommand {
425 Arithmetic(ArithmeticCommand),
427 ArithmeticForClause(ArithmeticForClauseCommand),
430 BraceGroup(BraceGroupCommand),
432 Subshell(SubshellCommand),
434 ForClause(ForClauseCommand),
436 CaseClause(CaseClauseCommand),
439 IfClause(IfClauseCommand),
441 WhileClause(WhileOrUntilClauseCommand),
443 UntilClause(WhileOrUntilClauseCommand),
445 Coprocess(CoprocessCommand),
447 ExtendedTest(ExtendedTestExprCommand),
449}
450
451impl Node for CompoundCommand {}
452
453impl SourceLocation for CompoundCommand {
454 fn location(&self) -> Option<SourceSpan> {
455 match self {
456 Self::Arithmetic(a) => a.location(),
457 Self::ArithmeticForClause(a) => a.location(),
458 Self::BraceGroup(b) => b.location(),
459 Self::Subshell(s) => s.location(),
460 Self::ForClause(f) => f.location(),
461 Self::CaseClause(c) => c.location(),
462 Self::IfClause(i) => i.location(),
463 Self::WhileClause(w) => w.location(),
464 Self::UntilClause(u) => u.location(),
465 Self::Coprocess(c) => c.location(),
466 Self::ExtendedTest(e) => e.location(),
467 }
468 }
469}
470
471impl Display for CompoundCommand {
472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473 match self {
474 Self::Arithmetic(arithmetic_command) => write!(f, "{arithmetic_command}"),
475 Self::ArithmeticForClause(arithmetic_for_clause_command) => {
476 write!(f, "{arithmetic_for_clause_command}")
477 }
478 Self::BraceGroup(brace_group_command) => {
479 write!(f, "{brace_group_command}")
480 }
481 Self::Subshell(subshell_command) => write!(f, "{subshell_command}"),
482 Self::ForClause(for_clause_command) => write!(f, "{for_clause_command}"),
483 Self::CaseClause(case_clause_command) => {
484 write!(f, "{case_clause_command}")
485 }
486 Self::IfClause(if_clause_command) => write!(f, "{if_clause_command}"),
487 Self::WhileClause(while_or_until_clause_command) => {
488 write!(f, "while {while_or_until_clause_command}")
489 }
490 Self::UntilClause(while_or_until_clause_command) => {
491 write!(f, "until {while_or_until_clause_command}")
492 }
493 Self::Coprocess(coproc_clause_command) => {
494 write!(f, "{coproc_clause_command}")
495 }
496 Self::ExtendedTest(extended_test_expr_command) => {
497 write!(f, "[[ {extended_test_expr_command} ]]")
498 }
499 }
500 }
501}
502
503#[derive(Clone, Debug)]
505#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
506#[cfg_attr(
507 any(test, feature = "serde"),
508 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
509)]
510pub struct ArithmeticCommand {
511 pub expr: UnexpandedArithmeticExpr,
513 pub loc: SourceSpan,
515}
516
517impl Node for ArithmeticCommand {}
518
519impl SourceLocation for ArithmeticCommand {
520 fn location(&self) -> Option<SourceSpan> {
521 Some(self.loc.clone())
522 }
523}
524
525impl Display for ArithmeticCommand {
526 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
527 write!(f, "(({}))", self.expr)
528 }
529}
530
531#[derive(Clone, Debug)]
533#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
534#[cfg_attr(
535 any(test, feature = "serde"),
536 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
537)]
538pub struct SubshellCommand {
539 pub list: CompoundList,
541 pub loc: SourceSpan,
543}
544
545impl Node for SubshellCommand {}
546
547impl SourceLocation for SubshellCommand {
548 fn location(&self) -> Option<SourceSpan> {
549 Some(self.loc.clone())
550 }
551}
552
553impl Display for SubshellCommand {
554 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
555 write!(f, "( ")?;
556 write!(f, "{}", self.list)?;
557 write!(f, " )")
558 }
559}
560
561#[derive(Clone, Debug)]
563#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
564#[cfg_attr(
565 any(test, feature = "serde"),
566 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
567)]
568pub struct ForClauseCommand {
569 pub variable_name: String,
571 pub values: Option<Vec<Word>>,
573 pub body: DoGroupCommand,
575 pub loc: SourceSpan,
577}
578
579impl Node for ForClauseCommand {}
580
581impl SourceLocation for ForClauseCommand {
582 fn location(&self) -> Option<SourceSpan> {
583 Some(self.loc.clone())
584 }
585}
586
587impl Display for ForClauseCommand {
588 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589 write!(f, "for {} in ", self.variable_name)?;
590
591 if let Some(values) = &self.values {
592 for (i, value) in values.iter().enumerate() {
593 if i > 0 {
594 write!(f, " ")?;
595 }
596
597 write!(f, "{value}")?;
598 }
599 }
600
601 writeln!(f, ";")?;
602
603 write!(f, "{}", self.body)
604 }
605}
606
607#[derive(Clone, Debug)]
610#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
611#[cfg_attr(
612 any(test, feature = "serde"),
613 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
614)]
615pub struct ArithmeticForClauseCommand {
616 pub initializer: Option<UnexpandedArithmeticExpr>,
619 pub condition: Option<UnexpandedArithmeticExpr>,
621 pub updater: Option<UnexpandedArithmeticExpr>,
623 pub body: DoGroupCommand,
625 pub loc: SourceSpan,
627}
628
629impl Node for ArithmeticForClauseCommand {}
630
631impl SourceLocation for ArithmeticForClauseCommand {
632 fn location(&self) -> Option<SourceSpan> {
633 Some(self.loc.clone())
634 }
635}
636
637impl Display for ArithmeticForClauseCommand {
638 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
639 write!(f, "for ((")?;
640
641 if let Some(initializer) = &self.initializer {
642 write!(f, "{initializer}")?;
643 }
644
645 write!(f, "; ")?;
646
647 if let Some(condition) = &self.condition {
648 write!(f, "{condition}")?;
649 }
650
651 write!(f, "; ")?;
652
653 if let Some(updater) = &self.updater {
654 write!(f, "{updater}")?;
655 }
656
657 writeln!(f, "))")?;
658
659 write!(f, "{}", self.body)
660 }
661}
662
663#[derive(Clone, Debug)]
666#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
667#[cfg_attr(
668 any(test, feature = "serde"),
669 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
670)]
671pub struct CaseClauseCommand {
672 pub value: Word,
674 pub cases: Vec<CaseItem>,
676 pub loc: SourceSpan,
678}
679
680impl Node for CaseClauseCommand {}
681
682impl SourceLocation for CaseClauseCommand {
683 fn location(&self) -> Option<SourceSpan> {
684 Some(self.loc.clone())
685 }
686}
687
688impl Display for CaseClauseCommand {
689 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
690 write!(f, "case {} in ", self.value)?;
693 for case in &self.cases {
694 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{case}")?;
695 }
696 writeln!(f)?;
697 write!(f, "esac")
698 }
699}
700
701#[derive(Clone, Debug)]
703#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
704#[cfg_attr(
705 any(test, feature = "serde"),
706 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
707)]
708pub struct CompoundList(pub Vec<CompoundListItem>);
709
710impl Node for CompoundList {}
711
712impl SourceLocation for CompoundList {
714 fn location(&self) -> Option<SourceSpan> {
715 let start = self.0.first().and_then(SourceLocation::location);
716 let end = self.0.last().and_then(SourceLocation::location);
717
718 if let (Some(s), Some(e)) = (start, end) {
719 Some(SourceSpan::within(&s, &e))
720 } else {
721 None
722 }
723 }
724}
725
726impl CompoundList {
727 fn terminated(&self) -> impl Display + '_ {
731 TerminatedCompoundList(self)
732 }
733
734 fn fmt_items(
735 &self,
736 f: &mut std::fmt::Formatter<'_>,
737 keep_trailing_separator: bool,
738 ) -> std::fmt::Result {
739 for (i, item) in self.0.iter().enumerate() {
740 if i > 0 {
741 writeln!(f)?;
742 }
743
744 write!(f, "{}", item.0)?;
746
747 if keep_trailing_separator
750 || i < self.0.len() - 1
751 || !matches!(item.1, SeparatorOperator::Sequence)
752 {
753 write!(f, "{}", item.1)?;
754 }
755 }
756
757 Ok(())
758 }
759}
760
761impl Display for CompoundList {
762 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
763 self.fmt_items(f, false)
764 }
765}
766
767struct TerminatedCompoundList<'a>(&'a CompoundList);
768
769impl Display for TerminatedCompoundList<'_> {
770 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
771 self.0.fmt_items(f, true)
772 }
773}
774
775#[derive(Clone, Debug)]
777#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
778#[cfg_attr(
779 any(test, feature = "serde"),
780 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
781)]
782pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
783
784impl Node for CompoundListItem {}
785
786impl SourceLocation for CompoundListItem {
788 fn location(&self) -> Option<SourceSpan> {
789 self.0.location()
790 }
791}
792
793impl Display for CompoundListItem {
794 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
795 write!(f, "{}", self.0)?;
796 write!(f, "{}", self.1)?;
797 Ok(())
798 }
799}
800
801#[derive(Clone, Debug)]
803#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
804#[cfg_attr(
805 any(test, feature = "serde"),
806 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
807)]
808pub struct IfClauseCommand {
809 pub condition: CompoundList,
811 pub then: CompoundList,
813 #[cfg_attr(
816 any(test, feature = "serde"),
817 serde(skip_serializing_if = "Option::is_none", default)
818 )]
819 pub elses: Option<Vec<ElseClause>>,
820 pub loc: SourceSpan,
822}
823
824impl Node for IfClauseCommand {}
825
826impl SourceLocation for IfClauseCommand {
827 fn location(&self) -> Option<SourceSpan> {
828 Some(self.loc.clone())
829 }
830}
831
832impl Display for IfClauseCommand {
833 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
834 writeln!(f, "if {}; then", self.condition)?;
835 write!(
836 indenter::indented(f).with_str(DISPLAY_INDENT),
837 "{}",
838 self.then.terminated()
839 )?;
840 if let Some(elses) = &self.elses {
841 for else_clause in elses {
842 write!(f, "{else_clause}")?;
843 }
844 }
845
846 writeln!(f)?;
847 write!(f, "fi")?;
848
849 Ok(())
850 }
851}
852
853#[derive(Clone, Debug)]
855#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
856#[cfg_attr(
857 any(test, feature = "serde"),
858 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
859)]
860pub struct ElseClause {
861 #[cfg_attr(
864 any(test, feature = "serde"),
865 serde(skip_serializing_if = "Option::is_none", default)
866 )]
867 pub condition: Option<CompoundList>,
868 pub body: CompoundList,
870}
871
872impl Display for ElseClause {
873 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
874 writeln!(f)?;
875 if let Some(condition) = &self.condition {
876 writeln!(f, "elif {condition}; then")?;
877 } else {
878 writeln!(f, "else")?;
879 }
880
881 write!(
882 indenter::indented(f).with_str(DISPLAY_INDENT),
883 "{}",
884 self.body.terminated()
885 )
886 }
887}
888
889#[derive(Clone, Debug)]
891#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
892#[cfg_attr(
893 any(test, feature = "serde"),
894 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
895)]
896pub struct CoprocessCommand {
897 #[cfg_attr(
899 any(test, feature = "serde"),
900 serde(skip_serializing_if = "Option::is_none", default)
901 )]
902 pub name: Option<Word>,
903 pub body: Box<Command>,
905 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing, default))]
907 pub loc: SourceSpan,
908}
909
910impl Node for CoprocessCommand {}
911
912impl SourceLocation for CoprocessCommand {
913 fn location(&self) -> Option<SourceSpan> {
914 Some(self.loc.clone())
915 }
916}
917
918impl Display for CoprocessCommand {
919 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
920 write!(f, "coproc")?;
921 if let Some(name) = &self.name {
922 write!(f, " {name}")?;
923 }
924 write!(f, " {}", self.body)?;
925 Ok(())
926 }
927}
928
929#[derive(Clone, Debug)]
931#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
932#[cfg_attr(
933 any(test, feature = "serde"),
934 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
935)]
936pub struct CaseItem {
937 pub patterns: Vec<Word>,
939 pub cmd: Option<CompoundList>,
941 pub post_action: CaseItemPostAction,
944 pub loc: Option<SourceSpan>,
946}
947
948impl Node for CaseItem {}
949
950impl SourceLocation for CaseItem {
951 fn location(&self) -> Option<SourceSpan> {
952 self.loc.clone()
953 }
954}
955
956impl Display for CaseItem {
957 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
958 writeln!(f)?;
959 for (i, pattern) in self.patterns.iter().enumerate() {
960 if i > 0 {
961 write!(f, " | ")?;
964 }
965 write!(f, "{pattern}")?;
966 }
967 writeln!(f, ")")?;
968
969 if let Some(cmd) = &self.cmd {
970 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{cmd}")?;
971 }
972 writeln!(f)?;
973 write!(f, "{}", self.post_action)
974 }
975}
976
977#[derive(Clone, Debug)]
980#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
981#[cfg_attr(
982 any(test, feature = "serde"),
983 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
984)]
985pub enum CaseItemPostAction {
986 ExitCase,
988 UnconditionallyExecuteNextCaseItem,
991 ContinueEvaluatingCases,
994}
995
996impl Display for CaseItemPostAction {
997 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
998 match self {
999 Self::ExitCase => write!(f, ";;"),
1000 Self::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
1001 Self::ContinueEvaluatingCases => write!(f, ";;&"),
1002 }
1003 }
1004}
1005
1006#[derive(Clone, Debug)]
1008#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1009#[cfg_attr(
1010 any(test, feature = "serde"),
1011 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1012)]
1013pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand, pub SourceSpan);
1014
1015impl Node for WhileOrUntilClauseCommand {}
1016
1017impl SourceLocation for WhileOrUntilClauseCommand {
1018 fn location(&self) -> Option<SourceSpan> {
1019 Some(self.2.clone())
1020 }
1021}
1022
1023impl Display for WhileOrUntilClauseCommand {
1024 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1025 write!(f, "{}; {}", self.0, self.1)
1026 }
1027}
1028
1029#[derive(Clone, Debug)]
1031#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1032#[cfg_attr(
1033 any(test, feature = "serde"),
1034 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1035)]
1036pub struct FunctionDefinition {
1037 pub fname: Word,
1039 pub body: FunctionBody,
1041}
1042
1043impl Node for FunctionDefinition {}
1044
1045impl SourceLocation for FunctionDefinition {
1048 fn location(&self) -> Option<SourceSpan> {
1049 let start = self.fname.location();
1050 let end = self.body.location();
1051
1052 if let (Some(s), Some(e)) = (start, end) {
1053 Some(SourceSpan::within(&s, &e))
1054 } else {
1055 None
1056 }
1057 }
1058}
1059
1060impl Display for FunctionDefinition {
1061 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1062 writeln!(f, "{} () ", self.fname.value)?;
1063 write!(f, "{}", self.body)?;
1064 Ok(())
1065 }
1066}
1067
1068#[derive(Clone, Debug)]
1070#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1071#[cfg_attr(
1072 any(test, feature = "serde"),
1073 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1074)]
1075pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
1076
1077impl Node for FunctionBody {}
1078
1079impl SourceLocation for FunctionBody {
1080 fn location(&self) -> Option<SourceSpan> {
1081 let cmd_span = self.0.location();
1082 let redirect_span = self.1.as_ref().and_then(SourceLocation::location);
1083
1084 match (cmd_span, redirect_span) {
1085 (Some(cmd_span), Some(redirect_span)) => {
1087 Some(SourceSpan::within(&cmd_span, &redirect_span))
1088 }
1089 (Some(cmd_span), None) => Some(cmd_span),
1091 _ => None,
1092 }
1093 }
1094}
1095
1096impl Display for FunctionBody {
1097 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1098 write!(f, "{}", self.0)?;
1099 if let Some(redirect_list) = &self.1 {
1100 write!(f, "{redirect_list}")?;
1101 }
1102
1103 Ok(())
1104 }
1105}
1106
1107#[derive(Clone, Debug)]
1109#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1110#[cfg_attr(
1111 any(test, feature = "serde"),
1112 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1113)]
1114pub struct BraceGroupCommand {
1115 pub list: CompoundList,
1117 pub loc: SourceSpan,
1119}
1120
1121impl Node for BraceGroupCommand {}
1122
1123impl SourceLocation for BraceGroupCommand {
1124 fn location(&self) -> Option<SourceSpan> {
1125 Some(self.loc.clone())
1126 }
1127}
1128
1129impl Display for BraceGroupCommand {
1130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1131 writeln!(f, "{{ ")?;
1132 write!(
1133 indenter::indented(f).with_str(DISPLAY_INDENT),
1134 "{}",
1135 self.list
1136 )?;
1137 writeln!(f)?;
1138 write!(f, "}}")?;
1139
1140 Ok(())
1141 }
1142}
1143
1144#[derive(Clone, Debug)]
1146#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1147#[cfg_attr(
1148 any(test, feature = "serde"),
1149 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1150)]
1151pub struct DoGroupCommand {
1152 pub list: CompoundList,
1154 pub loc: SourceSpan,
1156}
1157
1158impl Display for DoGroupCommand {
1159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1160 writeln!(f, "do")?;
1161 write!(
1162 indenter::indented(f).with_str(DISPLAY_INDENT),
1163 "{}",
1164 self.list.terminated()
1165 )?;
1166 writeln!(f)?;
1167 write!(f, "done")
1168 }
1169}
1170
1171#[derive(Clone, Debug)]
1173#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1174#[cfg_attr(
1175 any(test, feature = "serde"),
1176 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1177)]
1178pub struct SimpleCommand {
1179 #[cfg_attr(
1181 any(test, feature = "serde"),
1182 serde(skip_serializing_if = "Option::is_none", default)
1183 )]
1184 pub prefix: Option<CommandPrefix>,
1185 #[cfg_attr(
1187 any(test, feature = "serde"),
1188 serde(skip_serializing_if = "Option::is_none", default)
1189 )]
1190 pub word_or_name: Option<Word>,
1191 #[cfg_attr(
1193 any(test, feature = "serde"),
1194 serde(skip_serializing_if = "Option::is_none", default)
1195 )]
1196 pub suffix: Option<CommandSuffix>,
1197}
1198
1199impl Node for SimpleCommand {}
1200
1201impl SourceLocation for SimpleCommand {
1202 fn location(&self) -> Option<SourceSpan> {
1203 let mid = &self
1204 .word_or_name
1205 .as_ref()
1206 .and_then(SourceLocation::location);
1207 let start = self.prefix.as_ref().and_then(SourceLocation::location);
1208 let end = self.suffix.as_ref().and_then(SourceLocation::location);
1209
1210 match (start, mid, end) {
1211 (Some(start), _, Some(end)) => Some(SourceSpan::within(&start, &end)),
1212 (Some(start), Some(mid), None) => Some(SourceSpan::within(&start, mid)),
1213 (Some(start), None, None) => Some(start),
1214 (None, Some(mid), Some(end)) => Some(SourceSpan::within(mid, &end)),
1215 (None, Some(mid), None) => Some(mid.clone()),
1216 _ => None,
1217 }
1218 }
1219}
1220
1221impl Display for SimpleCommand {
1222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1223 let mut wrote_something = false;
1224
1225 if let Some(prefix) = &self.prefix {
1226 if wrote_something {
1227 write!(f, " ")?;
1228 }
1229
1230 write!(f, "{prefix}")?;
1231 wrote_something = true;
1232 }
1233
1234 if let Some(word_or_name) = &self.word_or_name {
1235 if wrote_something {
1236 write!(f, " ")?;
1237 }
1238
1239 write!(f, "{word_or_name}")?;
1240 wrote_something = true;
1241 }
1242
1243 if let Some(suffix) = &self.suffix {
1244 if wrote_something {
1245 write!(f, " ")?;
1246 }
1247
1248 write!(f, "{suffix}")?;
1249 }
1250
1251 Ok(())
1252 }
1253}
1254
1255#[derive(Clone, Debug, Default)]
1257#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1258#[cfg_attr(
1259 any(test, feature = "serde"),
1260 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1261)]
1262pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
1263
1264impl Node for CommandPrefix {}
1265
1266impl SourceLocation for CommandPrefix {
1267 fn location(&self) -> Option<SourceSpan> {
1268 let start = self.0.first().and_then(SourceLocation::location);
1269 let end = self.0.last().and_then(SourceLocation::location);
1270
1271 maybe_location(start.as_ref(), end.as_ref())
1272 }
1273}
1274
1275impl Display for CommandPrefix {
1276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1277 for (i, item) in self.0.iter().enumerate() {
1278 if i > 0 {
1279 write!(f, " ")?;
1280 }
1281
1282 write!(f, "{item}")?;
1283 }
1284 Ok(())
1285 }
1286}
1287
1288#[derive(Clone, Default, Debug)]
1291#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1292#[cfg_attr(
1293 any(test, feature = "serde"),
1294 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1295)]
1296pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
1297
1298impl Node for CommandSuffix {}
1299
1300impl SourceLocation for CommandSuffix {
1301 fn location(&self) -> Option<SourceSpan> {
1302 let start = self.0.first().and_then(SourceLocation::location);
1303 let end = self.0.last().and_then(SourceLocation::location);
1304
1305 maybe_location(start.as_ref(), end.as_ref())
1306 }
1307}
1308
1309impl Display for CommandSuffix {
1310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1311 for (i, item) in self.0.iter().enumerate() {
1312 if i > 0 {
1313 write!(f, " ")?;
1314 }
1315
1316 write!(f, "{item}")?;
1317 }
1318 Ok(())
1319 }
1320}
1321
1322#[derive(Clone, Debug)]
1324#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1325#[cfg_attr(
1326 any(test, feature = "serde"),
1327 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1328)]
1329pub enum ProcessSubstitutionKind {
1330 Read,
1332 Write,
1334}
1335
1336impl Display for ProcessSubstitutionKind {
1337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1338 match self {
1339 Self::Read => write!(f, "<"),
1340 Self::Write => write!(f, ">"),
1341 }
1342 }
1343}
1344
1345#[derive(Clone, Debug)]
1347#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1348#[cfg_attr(
1349 any(test, feature = "serde"),
1350 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1351)]
1352pub enum CommandPrefixOrSuffixItem {
1353 IoRedirect(IoRedirect),
1355 Word(Word),
1357 AssignmentWord(Assignment, Word),
1359 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1361}
1362
1363impl Node for CommandPrefixOrSuffixItem {}
1364
1365impl SourceLocation for CommandPrefixOrSuffixItem {
1366 fn location(&self) -> Option<SourceSpan> {
1367 match self {
1368 Self::Word(w) => w.location(),
1369 Self::IoRedirect(io_redirect) => io_redirect.location(),
1370 Self::AssignmentWord(assignment, _word) => assignment.location(),
1371 Self::ProcessSubstitution(_kind, cmd) => cmd.location(),
1373 }
1374 }
1375}
1376
1377impl Display for CommandPrefixOrSuffixItem {
1378 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1379 match self {
1380 Self::IoRedirect(io_redirect) => write!(f, "{io_redirect}"),
1381 Self::Word(word) => write!(f, "{word}"),
1382 Self::AssignmentWord(_assignment, word) => write!(f, "{word}"),
1383 Self::ProcessSubstitution(kind, subshell_command) => {
1384 write!(f, "{kind}({subshell_command})")
1385 }
1386 }
1387 }
1388}
1389
1390#[derive(Clone, Debug)]
1392#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1393#[cfg_attr(
1394 any(test, feature = "serde"),
1395 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1396)]
1397pub struct Assignment {
1398 pub name: AssignmentName,
1400 pub value: AssignmentValue,
1402 #[cfg_attr(
1405 any(test, feature = "serde"),
1406 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1407 )]
1408 pub append: bool,
1409 pub loc: SourceSpan,
1411}
1412
1413impl Node for Assignment {}
1414
1415impl SourceLocation for Assignment {
1416 fn location(&self) -> Option<SourceSpan> {
1417 Some(self.loc.clone())
1418 }
1419}
1420
1421impl Display for Assignment {
1422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1423 write!(f, "{}", self.name)?;
1424 if self.append {
1425 write!(f, "+")?;
1426 }
1427 write!(f, "={}", self.value)
1428 }
1429}
1430
1431#[derive(Clone, Debug)]
1433#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1434#[cfg_attr(
1435 any(test, feature = "serde"),
1436 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1437)]
1438pub enum AssignmentName {
1439 VariableName(String),
1441 ArrayElementName(String, String),
1443}
1444
1445impl AssignmentName {
1446 pub fn base_name(&self) -> &str {
1449 match self {
1450 Self::VariableName(name) => name,
1451 Self::ArrayElementName(name, _index) => name,
1452 }
1453 }
1454}
1455
1456impl Display for AssignmentName {
1457 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1458 match self {
1459 Self::VariableName(name) => write!(f, "{name}"),
1460 Self::ArrayElementName(name, index) => {
1461 write!(f, "{name}[{index}]")
1462 }
1463 }
1464 }
1465}
1466
1467#[derive(Clone, Debug)]
1469#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1470#[cfg_attr(
1471 any(test, feature = "serde"),
1472 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1473)]
1474pub enum AssignmentValue {
1475 Scalar(Word),
1477 Array(Vec<(Option<Word>, Word)>),
1479}
1480
1481impl Node for AssignmentValue {}
1482
1483impl SourceLocation for AssignmentValue {
1484 fn location(&self) -> Option<SourceSpan> {
1485 match self {
1486 Self::Scalar(word) => word.location(),
1487 Self::Array(words) => {
1488 let first = words.first().and_then(|(_key, value)| value.location());
1491 let last = words.last().and_then(|(_key, value)| value.location());
1492 maybe_location(first.as_ref(), last.as_ref())
1493 }
1494 }
1495 }
1496}
1497
1498impl Display for AssignmentValue {
1499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1500 match self {
1501 Self::Scalar(word) => write!(f, "{word}"),
1502 Self::Array(words) => {
1503 write!(f, "(")?;
1504 for (i, value) in words.iter().enumerate() {
1505 if i > 0 {
1506 write!(f, " ")?;
1507 }
1508 match value {
1509 (Some(key), value) => write!(f, "[{key}]={value}")?,
1510 (None, value) => write!(f, "{value}")?,
1511 }
1512 }
1513 write!(f, ")")
1514 }
1515 }
1516 }
1517}
1518
1519#[derive(Clone, Debug)]
1521#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1522#[cfg_attr(
1523 any(test, feature = "serde"),
1524 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1525)]
1526pub struct RedirectList(pub Vec<IoRedirect>);
1527
1528impl Node for RedirectList {}
1529
1530impl SourceLocation for RedirectList {
1531 fn location(&self) -> Option<SourceSpan> {
1532 let first = self.0.first().and_then(SourceLocation::location);
1533 let last = self.0.last().and_then(SourceLocation::location);
1534 maybe_location(first.as_ref(), last.as_ref())
1535 }
1536}
1537
1538impl Display for RedirectList {
1539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1540 for item in &self.0 {
1541 write!(f, "{item}")?;
1542 }
1543 Ok(())
1544 }
1545}
1546
1547pub type IoFd = i32;
1549
1550#[derive(Clone, Debug)]
1552#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1553#[cfg_attr(
1554 any(test, feature = "serde"),
1555 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1556)]
1557pub enum IoRedirect {
1558 File(Option<IoFd>, IoFileRedirectKind, IoFileRedirectTarget),
1560 HereDocument(Option<IoFd>, IoHereDocument),
1562 HereString(Option<IoFd>, Word),
1564 OutputAndError(Word, bool),
1567}
1568
1569impl Node for IoRedirect {}
1570
1571impl SourceLocation for IoRedirect {
1572 fn location(&self) -> Option<SourceSpan> {
1573 None
1575 }
1576}
1577
1578impl Display for IoRedirect {
1579 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1580 match self {
1581 Self::File(fd_num, kind, target) => {
1582 if let Some(fd_num) = fd_num {
1583 write!(f, "{fd_num}")?;
1584 }
1585
1586 write!(f, "{kind} {target}")?;
1587 }
1588 Self::OutputAndError(target, append) => {
1589 write!(f, "&>")?;
1590 if *append {
1591 write!(f, ">")?;
1592 }
1593 write!(f, " {target}")?;
1594 }
1595 Self::HereDocument(fd_num, here_doc) => {
1596 if let Some(fd_num) = fd_num {
1597 write!(f, "{fd_num}")?;
1598 }
1599
1600 write!(f, "<<{here_doc}")?;
1601 }
1602 Self::HereString(fd_num, s) => {
1603 if let Some(fd_num) = fd_num {
1604 write!(f, "{fd_num}")?;
1605 }
1606
1607 write!(f, "<<< {s}")?;
1608 }
1609 }
1610
1611 Ok(())
1612 }
1613}
1614
1615#[derive(Clone, Debug)]
1617#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1618#[cfg_attr(
1619 any(test, feature = "serde"),
1620 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1621)]
1622pub enum IoFileRedirectKind {
1623 Read,
1625 Write,
1627 Append,
1629 ReadAndWrite,
1631 Clobber,
1633 DuplicateInput,
1635 DuplicateOutput,
1637}
1638
1639impl Display for IoFileRedirectKind {
1640 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1641 match self {
1642 Self::Read => write!(f, "<"),
1643 Self::Write => write!(f, ">"),
1644 Self::Append => write!(f, ">>"),
1645 Self::ReadAndWrite => write!(f, "<>"),
1646 Self::Clobber => write!(f, ">|"),
1647 Self::DuplicateInput => write!(f, "<&"),
1648 Self::DuplicateOutput => write!(f, ">&"),
1649 }
1650 }
1651}
1652
1653#[derive(Clone, Debug)]
1655#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1656#[cfg_attr(
1657 any(test, feature = "serde"),
1658 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1659)]
1660pub enum IoFileRedirectTarget {
1661 Filename(Word),
1663 Fd(IoFd),
1665 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1668 Duplicate(Word),
1672}
1673
1674impl Display for IoFileRedirectTarget {
1675 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1676 match self {
1677 Self::Filename(word) => write!(f, "{word}"),
1678 Self::Fd(fd) => write!(f, "{fd}"),
1679 Self::ProcessSubstitution(kind, subshell_command) => {
1680 write!(f, "{kind}{subshell_command}")
1681 }
1682 Self::Duplicate(word) => write!(f, "{word}"),
1683 }
1684 }
1685}
1686
1687#[derive(Clone, Debug)]
1689#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1690#[cfg_attr(
1691 any(test, feature = "serde"),
1692 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1693)]
1694pub struct IoHereDocument {
1695 #[cfg_attr(
1697 any(test, feature = "serde"),
1698 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1699 )]
1700 pub remove_tabs: bool,
1701 #[cfg_attr(
1703 any(test, feature = "serde"),
1704 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1705 )]
1706 pub requires_expansion: bool,
1707 pub here_end: Word,
1709 pub doc: Word,
1711}
1712
1713impl Node for IoHereDocument {}
1714
1715impl SourceLocation for IoHereDocument {
1716 fn location(&self) -> Option<SourceSpan> {
1717 None
1719 }
1720}
1721
1722impl Display for IoHereDocument {
1723 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1724 if self.remove_tabs {
1725 write!(f, "-")?;
1726 }
1727
1728 writeln!(f, "{}", self.here_end)?;
1729 write!(f, "{}", self.doc)?;
1730 writeln!(f, "{}", self.here_end)?;
1731
1732 Ok(())
1733 }
1734}
1735
1736#[derive(Clone, Debug)]
1738#[cfg_attr(
1739 any(test, feature = "serde"),
1740 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1741)]
1742pub enum TestExpr {
1743 False,
1745 Literal(String),
1747 And(Box<Self>, Box<Self>),
1749 Or(Box<Self>, Box<Self>),
1751 Not(Box<Self>),
1753 Parenthesized(Box<Self>),
1755 UnaryTest(UnaryPredicate, String),
1757 BinaryTest(BinaryPredicate, String, String),
1759}
1760
1761impl Node for TestExpr {}
1762
1763impl SourceLocation for TestExpr {
1764 fn location(&self) -> Option<SourceSpan> {
1765 None
1767 }
1768}
1769
1770impl Display for TestExpr {
1771 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1772 match self {
1773 Self::False => Ok(()),
1774 Self::Literal(s) => write!(f, "{s}"),
1775 Self::And(left, right) => write!(f, "{left} -a {right}"),
1776 Self::Or(left, right) => write!(f, "{left} -o {right}"),
1777 Self::Not(expr) => write!(f, "! {expr}"),
1778 Self::Parenthesized(expr) => write!(f, "( {expr} )"),
1779 Self::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
1780 Self::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
1781 }
1782 }
1783}
1784
1785#[derive(Clone, Debug)]
1787#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1788#[cfg_attr(
1789 any(test, feature = "serde"),
1790 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1791)]
1792pub enum ExtendedTestExpr {
1793 And(Box<Self>, Box<Self>),
1795 Or(Box<Self>, Box<Self>),
1797 Not(Box<Self>),
1799 Parenthesized(Box<Self>),
1801 UnaryTest(UnaryPredicate, Word),
1803 BinaryTest(BinaryPredicate, Word, Word),
1805}
1806
1807impl Display for ExtendedTestExpr {
1808 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1809 match self {
1810 Self::And(left, right) => {
1811 write!(f, "{left} && {right}")
1812 }
1813 Self::Or(left, right) => {
1814 write!(f, "{left} || {right}")
1815 }
1816 Self::Not(expr) => {
1817 write!(f, "! {expr}")
1818 }
1819 Self::Parenthesized(expr) => {
1820 write!(f, "( {expr} )")
1821 }
1822 Self::UnaryTest(pred, word) => {
1823 write!(f, "{pred} {word}")
1824 }
1825 Self::BinaryTest(pred, left, right) => {
1826 write!(f, "{left} {pred} {right}")
1827 }
1828 }
1829 }
1830}
1831
1832#[derive(Clone, Debug)]
1834#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1835#[cfg_attr(
1836 any(test, feature = "serde"),
1837 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1838)]
1839pub struct ExtendedTestExprCommand {
1840 pub expr: ExtendedTestExpr,
1842 pub loc: SourceSpan,
1844}
1845
1846impl Node for ExtendedTestExprCommand {}
1847
1848impl SourceLocation for ExtendedTestExprCommand {
1849 fn location(&self) -> Option<SourceSpan> {
1850 Some(self.loc.clone())
1851 }
1852}
1853
1854impl Display for ExtendedTestExprCommand {
1855 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1856 self.expr.fmt(f)
1857 }
1858}
1859
1860#[derive(Clone, Debug)]
1862#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1863#[cfg_attr(
1864 any(test, feature = "serde"),
1865 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1866)]
1867pub enum UnaryPredicate {
1868 FileExists,
1870 FileExistsAndIsBlockSpecialFile,
1872 FileExistsAndIsCharSpecialFile,
1874 FileExistsAndIsDir,
1876 FileExistsAndIsRegularFile,
1878 FileExistsAndIsSetgid,
1881 FileExistsAndIsSymlink,
1883 FileExistsAndHasStickyBit,
1886 FileExistsAndIsFifo,
1888 FileExistsAndIsReadable,
1890 FileExistsAndIsNotZeroLength,
1893 FdIsOpenTerminal,
1895 FileExistsAndIsSetuid,
1898 FileExistsAndIsWritable,
1900 FileExistsAndIsExecutable,
1903 FileExistsAndOwnedByEffectiveGroupId,
1906 FileExistsAndModifiedSinceLastRead,
1909 FileExistsAndOwnedByEffectiveUserId,
1912 FileExistsAndIsSocket,
1914 ShellOptionEnabled,
1916 ShellVariableIsSetAndAssigned,
1919 ShellVariableIsSetAndNameRef,
1922 StringHasZeroLength,
1924 StringHasNonZeroLength,
1926}
1927
1928impl Display for UnaryPredicate {
1929 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1930 match self {
1931 Self::FileExists => write!(f, "-e"),
1932 Self::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
1933 Self::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
1934 Self::FileExistsAndIsDir => write!(f, "-d"),
1935 Self::FileExistsAndIsRegularFile => write!(f, "-f"),
1936 Self::FileExistsAndIsSetgid => write!(f, "-g"),
1937 Self::FileExistsAndIsSymlink => write!(f, "-h"),
1938 Self::FileExistsAndHasStickyBit => write!(f, "-k"),
1939 Self::FileExistsAndIsFifo => write!(f, "-p"),
1940 Self::FileExistsAndIsReadable => write!(f, "-r"),
1941 Self::FileExistsAndIsNotZeroLength => write!(f, "-s"),
1942 Self::FdIsOpenTerminal => write!(f, "-t"),
1943 Self::FileExistsAndIsSetuid => write!(f, "-u"),
1944 Self::FileExistsAndIsWritable => write!(f, "-w"),
1945 Self::FileExistsAndIsExecutable => write!(f, "-x"),
1946 Self::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
1947 Self::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
1948 Self::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
1949 Self::FileExistsAndIsSocket => write!(f, "-S"),
1950 Self::ShellOptionEnabled => write!(f, "-o"),
1951 Self::ShellVariableIsSetAndAssigned => write!(f, "-v"),
1952 Self::ShellVariableIsSetAndNameRef => write!(f, "-R"),
1953 Self::StringHasZeroLength => write!(f, "-z"),
1954 Self::StringHasNonZeroLength => write!(f, "-n"),
1955 }
1956 }
1957}
1958
1959#[derive(Clone, Debug)]
1961#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1962#[cfg_attr(
1963 any(test, feature = "serde"),
1964 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1965)]
1966pub enum BinaryPredicate {
1967 FilesReferToSameDeviceAndInodeNumbers,
1969 LeftFileIsNewerOrExistsWhenRightDoesNot,
1972 LeftFileIsOlderOrDoesNotExistWhenRightDoes,
1975 StringExactlyMatchesPattern,
1977 StringDoesNotExactlyMatchPattern,
1979 StringMatchesRegex,
1981 StringExactlyMatchesString,
1983 StringDoesNotExactlyMatchString,
1985 StringContainsSubstring,
1987 LeftSortsBeforeRight,
1989 LeftSortsAfterRight,
1991 ArithmeticEqualTo,
1993 ArithmeticNotEqualTo,
1995 ArithmeticLessThan,
1998 ArithmeticLessThanOrEqualTo,
2001 ArithmeticGreaterThan,
2004 ArithmeticGreaterThanOrEqualTo,
2007}
2008
2009impl Display for BinaryPredicate {
2010 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2011 match self {
2012 Self::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
2013 Self::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
2014 Self::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
2015 Self::StringExactlyMatchesPattern => write!(f, "=="),
2016 Self::StringDoesNotExactlyMatchPattern => write!(f, "!="),
2017 Self::StringMatchesRegex => write!(f, "=~"),
2018 Self::StringContainsSubstring => write!(f, "=~"),
2019 Self::StringExactlyMatchesString => write!(f, "=="),
2020 Self::StringDoesNotExactlyMatchString => write!(f, "!="),
2021 Self::LeftSortsBeforeRight => write!(f, "<"),
2022 Self::LeftSortsAfterRight => write!(f, ">"),
2023 Self::ArithmeticEqualTo => write!(f, "-eq"),
2024 Self::ArithmeticNotEqualTo => write!(f, "-ne"),
2025 Self::ArithmeticLessThan => write!(f, "-lt"),
2026 Self::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
2027 Self::ArithmeticGreaterThan => write!(f, "-gt"),
2028 Self::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
2029 }
2030 }
2031}
2032
2033#[derive(Clone, Debug)]
2035#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2036#[cfg_attr(
2037 any(test, feature = "serde"),
2038 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2039)]
2040pub struct Word {
2041 pub value: String,
2043 pub loc: Option<SourceSpan>,
2045}
2046
2047impl Node for Word {}
2048
2049impl SourceLocation for Word {
2050 fn location(&self) -> Option<SourceSpan> {
2051 self.loc.clone()
2052 }
2053}
2054
2055impl Display for Word {
2056 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2057 write!(f, "{}", self.value)
2058 }
2059}
2060
2061impl From<&tokenizer::Token> for Word {
2062 fn from(t: &tokenizer::Token) -> Self {
2063 match t {
2064 tokenizer::Token::Word(value, loc) => Self {
2065 value: value.clone(),
2066 loc: Some(loc.clone()),
2067 },
2068 tokenizer::Token::Operator(value, loc) => Self {
2069 value: value.clone(),
2070 loc: Some(loc.clone()),
2071 },
2072 }
2073 }
2074}
2075
2076impl From<String> for Word {
2077 fn from(s: String) -> Self {
2078 Self {
2079 value: s,
2080 loc: None,
2081 }
2082 }
2083}
2084
2085impl AsRef<str> for Word {
2086 fn as_ref(&self) -> &str {
2087 &self.value
2088 }
2089}
2090
2091impl Word {
2092 pub fn new(s: &str) -> Self {
2094 Self {
2095 value: s.to_owned(),
2096 loc: None,
2097 }
2098 }
2099
2100 pub fn with_location(s: &str, loc: &SourceSpan) -> Self {
2102 Self {
2103 value: s.to_owned(),
2104 loc: Some(loc.to_owned()),
2105 }
2106 }
2107
2108 pub fn flatten(&self) -> String {
2110 self.value.clone()
2111 }
2112}
2113
2114#[derive(Clone, Debug)]
2116#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2117#[cfg_attr(
2118 any(test, feature = "serde"),
2119 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2120)]
2121pub struct UnexpandedArithmeticExpr {
2122 pub value: String,
2124}
2125
2126impl Display for UnexpandedArithmeticExpr {
2127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2128 write!(f, "{}", self.value)
2129 }
2130}
2131
2132#[derive(Clone, Debug)]
2134#[cfg_attr(
2135 any(test, feature = "serde"),
2136 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2137)]
2138pub enum ArithmeticExpr {
2139 Literal(i64),
2141 Reference(ArithmeticTarget),
2143 UnaryOp(UnaryOperator, Box<Self>),
2145 BinaryOp(BinaryOperator, Box<Self>, Box<Self>),
2147 Conditional(Box<Self>, Box<Self>, Box<Self>),
2149 Assignment(ArithmeticTarget, Box<Self>),
2151 BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<Self>),
2153 UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
2155}
2156
2157impl Node for ArithmeticExpr {}
2158
2159impl SourceLocation for ArithmeticExpr {
2160 fn location(&self) -> Option<SourceSpan> {
2161 None
2163 }
2164}
2165
2166#[cfg(feature = "arbitrary")]
2167impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
2168 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2169 let variant = u.choose(&[
2170 "Literal",
2171 "Reference",
2172 "UnaryOp",
2173 "BinaryOp",
2174 "Conditional",
2175 "Assignment",
2176 "BinaryAssignment",
2177 "UnaryAssignment",
2178 ])?;
2179
2180 match *variant {
2181 "Literal" => Ok(Self::Literal(i64::arbitrary(u)?)),
2182 "Reference" => Ok(Self::Reference(ArithmeticTarget::arbitrary(u)?)),
2183 "UnaryOp" => Ok(Self::UnaryOp(
2184 UnaryOperator::arbitrary(u)?,
2185 Box::new(Self::arbitrary(u)?),
2186 )),
2187 "BinaryOp" => Ok(Self::BinaryOp(
2188 BinaryOperator::arbitrary(u)?,
2189 Box::new(Self::arbitrary(u)?),
2190 Box::new(Self::arbitrary(u)?),
2191 )),
2192 "Conditional" => Ok(Self::Conditional(
2193 Box::new(Self::arbitrary(u)?),
2194 Box::new(Self::arbitrary(u)?),
2195 Box::new(Self::arbitrary(u)?),
2196 )),
2197 "Assignment" => Ok(Self::Assignment(
2198 ArithmeticTarget::arbitrary(u)?,
2199 Box::new(Self::arbitrary(u)?),
2200 )),
2201 "BinaryAssignment" => Ok(Self::BinaryAssignment(
2202 BinaryOperator::arbitrary(u)?,
2203 ArithmeticTarget::arbitrary(u)?,
2204 Box::new(Self::arbitrary(u)?),
2205 )),
2206 "UnaryAssignment" => Ok(Self::UnaryAssignment(
2207 UnaryAssignmentOperator::arbitrary(u)?,
2208 ArithmeticTarget::arbitrary(u)?,
2209 )),
2210 _ => unreachable!(),
2211 }
2212 }
2213}
2214
2215impl Display for ArithmeticExpr {
2216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2217 match self {
2218 Self::Literal(literal) => write!(f, "{literal}"),
2219 Self::Reference(target) => write!(f, "{target}"),
2220 Self::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
2221 Self::BinaryOp(op, left, right) => {
2222 if matches!(op, BinaryOperator::Comma) {
2223 write!(f, "{left}{op} {right}")
2224 } else {
2225 write!(f, "{left} {op} {right}")
2226 }
2227 }
2228 Self::Conditional(condition, if_branch, else_branch) => {
2229 write!(f, "{condition} ? {if_branch} : {else_branch}")
2230 }
2231 Self::Assignment(target, value) => write!(f, "{target} = {value}"),
2232 Self::BinaryAssignment(op, target, operand) => {
2233 write!(f, "{target} {op}= {operand}")
2234 }
2235 Self::UnaryAssignment(op, target) => match op {
2236 UnaryAssignmentOperator::PrefixIncrement
2237 | UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
2238 UnaryAssignmentOperator::PostfixIncrement
2239 | UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
2240 },
2241 }
2242 }
2243}
2244
2245#[derive(Clone, Copy, Debug)]
2247#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2248#[cfg_attr(
2249 any(test, feature = "serde"),
2250 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2251)]
2252pub enum BinaryOperator {
2253 Power,
2255 Multiply,
2257 Divide,
2259 Modulo,
2261 Comma,
2263 Add,
2265 Subtract,
2267 ShiftLeft,
2269 ShiftRight,
2271 LessThan,
2273 LessThanOrEqualTo,
2275 GreaterThan,
2277 GreaterThanOrEqualTo,
2279 Equals,
2281 NotEquals,
2283 BitwiseAnd,
2285 BitwiseXor,
2287 BitwiseOr,
2289 LogicalAnd,
2291 LogicalOr,
2293}
2294
2295impl Display for BinaryOperator {
2296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2297 match self {
2298 Self::Power => write!(f, "**"),
2299 Self::Multiply => write!(f, "*"),
2300 Self::Divide => write!(f, "/"),
2301 Self::Modulo => write!(f, "%"),
2302 Self::Comma => write!(f, ","),
2303 Self::Add => write!(f, "+"),
2304 Self::Subtract => write!(f, "-"),
2305 Self::ShiftLeft => write!(f, "<<"),
2306 Self::ShiftRight => write!(f, ">>"),
2307 Self::LessThan => write!(f, "<"),
2308 Self::LessThanOrEqualTo => write!(f, "<="),
2309 Self::GreaterThan => write!(f, ">"),
2310 Self::GreaterThanOrEqualTo => write!(f, ">="),
2311 Self::Equals => write!(f, "=="),
2312 Self::NotEquals => write!(f, "!="),
2313 Self::BitwiseAnd => write!(f, "&"),
2314 Self::BitwiseXor => write!(f, "^"),
2315 Self::BitwiseOr => write!(f, "|"),
2316 Self::LogicalAnd => write!(f, "&&"),
2317 Self::LogicalOr => write!(f, "||"),
2318 }
2319 }
2320}
2321
2322#[derive(Clone, Copy, Debug)]
2324#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2325#[cfg_attr(
2326 any(test, feature = "serde"),
2327 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2328)]
2329pub enum UnaryOperator {
2330 UnaryPlus,
2332 UnaryMinus,
2334 BitwiseNot,
2336 LogicalNot,
2338}
2339
2340impl Display for UnaryOperator {
2341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2342 match self {
2343 Self::UnaryPlus => write!(f, "+"),
2344 Self::UnaryMinus => write!(f, "-"),
2345 Self::BitwiseNot => write!(f, "~"),
2346 Self::LogicalNot => write!(f, "!"),
2347 }
2348 }
2349}
2350
2351#[derive(Clone, Copy, Debug)]
2353#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2354#[cfg_attr(
2355 any(test, feature = "serde"),
2356 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2357)]
2358pub enum UnaryAssignmentOperator {
2359 PrefixIncrement,
2361 PrefixDecrement,
2363 PostfixIncrement,
2365 PostfixDecrement,
2367}
2368
2369impl Display for UnaryAssignmentOperator {
2370 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2371 match self {
2372 Self::PrefixIncrement => write!(f, "++"),
2373 Self::PrefixDecrement => write!(f, "--"),
2374 Self::PostfixIncrement => write!(f, "++"),
2375 Self::PostfixDecrement => write!(f, "--"),
2376 }
2377 }
2378}
2379
2380#[derive(Clone, Debug)]
2382#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2383#[cfg_attr(
2384 any(test, feature = "serde"),
2385 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2386)]
2387pub enum ArithmeticTarget {
2388 Variable(String),
2390 ArrayElement(String, Box<ArithmeticExpr>),
2392}
2393
2394impl Node for ArithmeticTarget {}
2395
2396impl SourceLocation for ArithmeticTarget {
2397 fn location(&self) -> Option<SourceSpan> {
2398 None
2400 }
2401}
2402
2403impl Display for ArithmeticTarget {
2404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2405 match self {
2406 Self::Variable(name) => write!(f, "{name}"),
2407 Self::ArrayElement(name, index) => write!(f, "{name}[{index}]"),
2408 }
2409 }
2410}
2411
2412#[cfg(test)]
2413#[allow(clippy::panic)]
2414mod tests {
2415 use std::io::BufReader;
2416
2417 use super::*;
2418 use crate::{ParserOptions, SourcePosition};
2419
2420 fn parse(input: &str) -> Program {
2421 let reader = BufReader::new(input.as_bytes());
2422 let mut parser = crate::Parser::new(reader, &ParserOptions::default());
2423 parser.parse_program().unwrap()
2424 }
2425
2426 #[test]
2427 fn program_source_loc() {
2428 const INPUT: &str = r"echo hi
2429echo there
2430";
2431
2432 let loc = parse(INPUT).location().unwrap();
2433
2434 assert_eq!(
2435 *(loc.start),
2436 SourcePosition {
2437 line: 1,
2438 column: 1,
2439 index: 0
2440 }
2441 );
2442 assert_eq!(
2443 *(loc.end),
2444 SourcePosition {
2445 line: 2,
2446 column: 11,
2447 index: 18
2448 }
2449 );
2450 }
2451
2452 #[test]
2453 fn function_def_loc() {
2454 const INPUT: &str = r"my_func() {
2455 echo hi
2456 echo there
2457}
2458
2459my_func
2460";
2461
2462 let program = parse(INPUT);
2463
2464 let Command::Function(func_def) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2465 panic!("expected function definition");
2466 };
2467
2468 let loc = func_def.location().unwrap();
2469
2470 assert_eq!(
2471 *(loc.start),
2472 SourcePosition {
2473 line: 1,
2474 column: 1,
2475 index: 0
2476 }
2477 );
2478 assert_eq!(
2479 *(loc.end),
2480 SourcePosition {
2481 line: 4,
2482 column: 2,
2483 index: 36
2484 }
2485 );
2486 }
2487
2488 #[test]
2489 fn simple_cmd_loc() {
2490 const INPUT: &str = r"var=value somecmd arg1 arg2
2491";
2492
2493 let program = parse(INPUT);
2494
2495 let Command::Simple(cmd) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2496 panic!("expected function definition");
2497 };
2498
2499 let loc = cmd.location().unwrap();
2500
2501 assert_eq!(
2502 *(loc.start),
2503 SourcePosition {
2504 line: 1,
2505 column: 1,
2506 index: 0
2507 }
2508 );
2509 assert_eq!(
2510 *(loc.end),
2511 SourcePosition {
2512 line: 1,
2513 column: 28,
2514 index: 27
2515 }
2516 );
2517 }
2518}