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 ExtendedTest(ExtendedTestExprCommand, Option<RedirectList>),
379}
380
381impl Node for Command {}
382
383impl SourceLocation for Command {
384 fn location(&self) -> Option<SourceSpan> {
385 match self {
386 Self::Simple(s) => s.location(),
387 Self::Compound(c, r) => {
388 match (c.location(), r.as_ref().and_then(SourceLocation::location)) {
389 (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
390 (s, _) => s,
391 }
392 }
393 Self::Function(f) => f.location(),
394 Self::ExtendedTest(e, _) => e.location(),
395 }
396 }
397}
398
399impl Display for Command {
400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401 match self {
402 Self::Simple(simple_command) => write!(f, "{simple_command}"),
403 Self::Compound(compound_command, redirect_list) => {
404 write!(f, "{compound_command}")?;
405 if let Some(redirect_list) = redirect_list {
406 write!(f, "{redirect_list}")?;
407 }
408 Ok(())
409 }
410 Self::Function(function_definition) => write!(f, "{function_definition}"),
411 Self::ExtendedTest(extended_test_expr, redirect_list) => {
412 write!(f, "[[ {extended_test_expr} ]]")?;
413 if let Some(redirect_list) = redirect_list {
414 write!(f, "{redirect_list}")?;
415 }
416 Ok(())
417 }
418 }
419 }
420}
421
422#[derive(Clone, Debug)]
424#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
425#[cfg_attr(
426 any(test, feature = "serde"),
427 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
428)]
429pub enum CompoundCommand {
430 Arithmetic(ArithmeticCommand),
432 ArithmeticForClause(ArithmeticForClauseCommand),
434 BraceGroup(BraceGroupCommand),
436 Subshell(SubshellCommand),
438 ForClause(ForClauseCommand),
440 CaseClause(CaseClauseCommand),
443 IfClause(IfClauseCommand),
445 WhileClause(WhileOrUntilClauseCommand),
447 UntilClause(WhileOrUntilClauseCommand),
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 }
466 }
467}
468
469impl Display for CompoundCommand {
470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471 match self {
472 Self::Arithmetic(arithmetic_command) => write!(f, "{arithmetic_command}"),
473 Self::ArithmeticForClause(arithmetic_for_clause_command) => {
474 write!(f, "{arithmetic_for_clause_command}")
475 }
476 Self::BraceGroup(brace_group_command) => {
477 write!(f, "{brace_group_command}")
478 }
479 Self::Subshell(subshell_command) => write!(f, "{subshell_command}"),
480 Self::ForClause(for_clause_command) => write!(f, "{for_clause_command}"),
481 Self::CaseClause(case_clause_command) => {
482 write!(f, "{case_clause_command}")
483 }
484 Self::IfClause(if_clause_command) => write!(f, "{if_clause_command}"),
485 Self::WhileClause(while_or_until_clause_command) => {
486 write!(f, "while {while_or_until_clause_command}")
487 }
488 Self::UntilClause(while_or_until_clause_command) => {
489 write!(f, "until {while_or_until_clause_command}")
490 }
491 }
492 }
493}
494
495#[derive(Clone, Debug)]
497#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
498#[cfg_attr(
499 any(test, feature = "serde"),
500 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
501)]
502pub struct ArithmeticCommand {
503 pub expr: UnexpandedArithmeticExpr,
505 pub loc: SourceSpan,
507}
508
509impl Node for ArithmeticCommand {}
510
511impl SourceLocation for ArithmeticCommand {
512 fn location(&self) -> Option<SourceSpan> {
513 Some(self.loc.clone())
514 }
515}
516
517impl Display for ArithmeticCommand {
518 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519 write!(f, "(({}))", self.expr)
520 }
521}
522
523#[derive(Clone, Debug)]
525#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
526#[cfg_attr(
527 any(test, feature = "serde"),
528 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
529)]
530pub struct SubshellCommand {
531 pub list: CompoundList,
533 pub loc: SourceSpan,
535}
536
537impl Node for SubshellCommand {}
538
539impl SourceLocation for SubshellCommand {
540 fn location(&self) -> Option<SourceSpan> {
541 Some(self.loc.clone())
542 }
543}
544
545impl Display for SubshellCommand {
546 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547 write!(f, "( ")?;
548 write!(f, "{}", self.list)?;
549 write!(f, " )")
550 }
551}
552
553#[derive(Clone, Debug)]
555#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
556#[cfg_attr(
557 any(test, feature = "serde"),
558 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
559)]
560pub struct ForClauseCommand {
561 pub variable_name: String,
563 pub values: Option<Vec<Word>>,
565 pub body: DoGroupCommand,
567 pub loc: SourceSpan,
569}
570
571impl Node for ForClauseCommand {}
572
573impl SourceLocation for ForClauseCommand {
574 fn location(&self) -> Option<SourceSpan> {
575 Some(self.loc.clone())
576 }
577}
578
579impl Display for ForClauseCommand {
580 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
581 write!(f, "for {} in ", self.variable_name)?;
582
583 if let Some(values) = &self.values {
584 for (i, value) in values.iter().enumerate() {
585 if i > 0 {
586 write!(f, " ")?;
587 }
588
589 write!(f, "{value}")?;
590 }
591 }
592
593 writeln!(f, ";")?;
594
595 write!(f, "{}", self.body)
596 }
597}
598
599#[derive(Clone, Debug)]
601#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
602#[cfg_attr(
603 any(test, feature = "serde"),
604 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
605)]
606pub struct ArithmeticForClauseCommand {
607 pub initializer: Option<UnexpandedArithmeticExpr>,
609 pub condition: Option<UnexpandedArithmeticExpr>,
611 pub updater: Option<UnexpandedArithmeticExpr>,
613 pub body: DoGroupCommand,
615 pub loc: SourceSpan,
617}
618
619impl Node for ArithmeticForClauseCommand {}
620
621impl SourceLocation for ArithmeticForClauseCommand {
622 fn location(&self) -> Option<SourceSpan> {
623 Some(self.loc.clone())
624 }
625}
626
627impl Display for ArithmeticForClauseCommand {
628 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
629 write!(f, "for ((")?;
630
631 if let Some(initializer) = &self.initializer {
632 write!(f, "{initializer}")?;
633 }
634
635 write!(f, "; ")?;
636
637 if let Some(condition) = &self.condition {
638 write!(f, "{condition}")?;
639 }
640
641 write!(f, "; ")?;
642
643 if let Some(updater) = &self.updater {
644 write!(f, "{updater}")?;
645 }
646
647 writeln!(f, "))")?;
648
649 write!(f, "{}", self.body)
650 }
651}
652
653#[derive(Clone, Debug)]
656#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
657#[cfg_attr(
658 any(test, feature = "serde"),
659 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
660)]
661pub struct CaseClauseCommand {
662 pub value: Word,
664 pub cases: Vec<CaseItem>,
666 pub loc: SourceSpan,
668}
669
670impl Node for CaseClauseCommand {}
671
672impl SourceLocation for CaseClauseCommand {
673 fn location(&self) -> Option<SourceSpan> {
674 Some(self.loc.clone())
675 }
676}
677
678impl Display for CaseClauseCommand {
679 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
680 write!(f, "case {} in", self.value)?;
681 for case in &self.cases {
682 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{case}")?;
683 }
684 writeln!(f)?;
685 write!(f, "esac")
686 }
687}
688
689#[derive(Clone, Debug)]
691#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
692#[cfg_attr(
693 any(test, feature = "serde"),
694 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
695)]
696pub struct CompoundList(pub Vec<CompoundListItem>);
697
698impl Node for CompoundList {}
699
700impl SourceLocation for CompoundList {
702 fn location(&self) -> Option<SourceSpan> {
703 let start = self.0.first().and_then(SourceLocation::location);
704 let end = self.0.last().and_then(SourceLocation::location);
705
706 if let (Some(s), Some(e)) = (start, end) {
707 Some(SourceSpan::within(&s, &e))
708 } else {
709 None
710 }
711 }
712}
713
714impl Display for CompoundList {
715 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
716 for (i, item) in self.0.iter().enumerate() {
717 if i > 0 {
718 writeln!(f)?;
719 }
720
721 write!(f, "{}", item.0)?;
723
724 if i == self.0.len() - 1 && matches!(item.1, SeparatorOperator::Sequence) {
726 } else {
728 write!(f, "{}", item.1)?;
729 }
730 }
731
732 Ok(())
733 }
734}
735
736#[derive(Clone, Debug)]
738#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
739#[cfg_attr(
740 any(test, feature = "serde"),
741 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
742)]
743pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
744
745impl Node for CompoundListItem {}
746
747impl SourceLocation for CompoundListItem {
749 fn location(&self) -> Option<SourceSpan> {
750 self.0.location()
751 }
752}
753
754impl Display for CompoundListItem {
755 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
756 write!(f, "{}", self.0)?;
757 write!(f, "{}", self.1)?;
758 Ok(())
759 }
760}
761
762#[derive(Clone, Debug)]
764#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
765#[cfg_attr(
766 any(test, feature = "serde"),
767 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
768)]
769pub struct IfClauseCommand {
770 pub condition: CompoundList,
772 pub then: CompoundList,
774 #[cfg_attr(
776 any(test, feature = "serde"),
777 serde(skip_serializing_if = "Option::is_none", default)
778 )]
779 pub elses: Option<Vec<ElseClause>>,
780 pub loc: SourceSpan,
782}
783
784impl Node for IfClauseCommand {}
785
786impl SourceLocation for IfClauseCommand {
787 fn location(&self) -> Option<SourceSpan> {
788 Some(self.loc.clone())
789 }
790}
791
792impl Display for IfClauseCommand {
793 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
794 writeln!(f, "if {}; then", self.condition)?;
795 write!(
796 indenter::indented(f).with_str(DISPLAY_INDENT),
797 "{}",
798 self.then
799 )?;
800 if let Some(elses) = &self.elses {
801 for else_clause in elses {
802 write!(f, "{else_clause}")?;
803 }
804 }
805
806 writeln!(f)?;
807 write!(f, "fi")?;
808
809 Ok(())
810 }
811}
812
813#[derive(Clone, Debug)]
815#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
816#[cfg_attr(
817 any(test, feature = "serde"),
818 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
819)]
820pub struct ElseClause {
821 #[cfg_attr(
823 any(test, feature = "serde"),
824 serde(skip_serializing_if = "Option::is_none", default)
825 )]
826 pub condition: Option<CompoundList>,
827 pub body: CompoundList,
829}
830
831impl Display for ElseClause {
832 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
833 writeln!(f)?;
834 if let Some(condition) = &self.condition {
835 writeln!(f, "elif {condition}; then")?;
836 } else {
837 writeln!(f, "else")?;
838 }
839
840 write!(
841 indenter::indented(f).with_str(DISPLAY_INDENT),
842 "{}",
843 self.body
844 )
845 }
846}
847
848#[derive(Clone, Debug)]
850#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
851#[cfg_attr(
852 any(test, feature = "serde"),
853 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
854)]
855pub struct CaseItem {
856 pub patterns: Vec<Word>,
858 pub cmd: Option<CompoundList>,
860 pub post_action: CaseItemPostAction,
862 pub loc: Option<SourceSpan>,
864}
865
866impl Node for CaseItem {}
867
868impl SourceLocation for CaseItem {
869 fn location(&self) -> Option<SourceSpan> {
870 self.loc.clone()
871 }
872}
873
874impl Display for CaseItem {
875 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
876 writeln!(f)?;
877 for (i, pattern) in self.patterns.iter().enumerate() {
878 if i > 0 {
879 write!(f, "|")?;
880 }
881 write!(f, "{pattern}")?;
882 }
883 writeln!(f, ")")?;
884
885 if let Some(cmd) = &self.cmd {
886 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{cmd}")?;
887 }
888 writeln!(f)?;
889 write!(f, "{}", self.post_action)
890 }
891}
892
893#[derive(Clone, Debug)]
895#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
896#[cfg_attr(
897 any(test, feature = "serde"),
898 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
899)]
900pub enum CaseItemPostAction {
901 ExitCase,
903 UnconditionallyExecuteNextCaseItem,
906 ContinueEvaluatingCases,
909}
910
911impl Display for CaseItemPostAction {
912 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
913 match self {
914 Self::ExitCase => write!(f, ";;"),
915 Self::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
916 Self::ContinueEvaluatingCases => write!(f, ";;&"),
917 }
918 }
919}
920
921#[derive(Clone, Debug)]
923#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
924#[cfg_attr(
925 any(test, feature = "serde"),
926 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
927)]
928pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand, pub SourceSpan);
929
930impl Node for WhileOrUntilClauseCommand {}
931
932impl SourceLocation for WhileOrUntilClauseCommand {
933 fn location(&self) -> Option<SourceSpan> {
934 Some(self.2.clone())
935 }
936}
937
938impl Display for WhileOrUntilClauseCommand {
939 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
940 write!(f, "{}; {}", self.0, self.1)
941 }
942}
943
944#[derive(Clone, Debug)]
946#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
947#[cfg_attr(
948 any(test, feature = "serde"),
949 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
950)]
951pub struct FunctionDefinition {
952 pub fname: Word,
954 pub body: FunctionBody,
956}
957
958impl Node for FunctionDefinition {}
959
960impl SourceLocation for FunctionDefinition {
963 fn location(&self) -> Option<SourceSpan> {
964 let start = self.fname.location();
965 let end = self.body.location();
966
967 if let (Some(s), Some(e)) = (start, end) {
968 Some(SourceSpan::within(&s, &e))
969 } else {
970 None
971 }
972 }
973}
974
975impl Display for FunctionDefinition {
976 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
977 writeln!(f, "{} () ", self.fname.value)?;
978 write!(f, "{}", self.body)?;
979 Ok(())
980 }
981}
982
983#[derive(Clone, Debug)]
985#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
986#[cfg_attr(
987 any(test, feature = "serde"),
988 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
989)]
990pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
991
992impl Node for FunctionBody {}
993
994impl SourceLocation for FunctionBody {
995 fn location(&self) -> Option<SourceSpan> {
996 let cmd_span = self.0.location();
997 let redirect_span = self.1.as_ref().and_then(SourceLocation::location);
998
999 match (cmd_span, redirect_span) {
1000 (Some(cmd_span), Some(redirect_span)) => {
1002 Some(SourceSpan::within(&cmd_span, &redirect_span))
1003 }
1004 (Some(cmd_span), None) => Some(cmd_span),
1006 _ => None,
1007 }
1008 }
1009}
1010
1011impl Display for FunctionBody {
1012 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1013 write!(f, "{}", self.0)?;
1014 if let Some(redirect_list) = &self.1 {
1015 write!(f, "{redirect_list}")?;
1016 }
1017
1018 Ok(())
1019 }
1020}
1021
1022#[derive(Clone, Debug)]
1024#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1025#[cfg_attr(
1026 any(test, feature = "serde"),
1027 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1028)]
1029pub struct BraceGroupCommand {
1030 pub list: CompoundList,
1032 pub loc: SourceSpan,
1034}
1035
1036impl Node for BraceGroupCommand {}
1037
1038impl SourceLocation for BraceGroupCommand {
1039 fn location(&self) -> Option<SourceSpan> {
1040 Some(self.loc.clone())
1041 }
1042}
1043
1044impl Display for BraceGroupCommand {
1045 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1046 writeln!(f, "{{ ")?;
1047 write!(
1048 indenter::indented(f).with_str(DISPLAY_INDENT),
1049 "{}",
1050 self.list
1051 )?;
1052 writeln!(f)?;
1053 write!(f, "}}")?;
1054
1055 Ok(())
1056 }
1057}
1058
1059#[derive(Clone, Debug)]
1061#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1062#[cfg_attr(
1063 any(test, feature = "serde"),
1064 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1065)]
1066pub struct DoGroupCommand {
1067 pub list: CompoundList,
1069 pub loc: SourceSpan,
1071}
1072
1073impl Display for DoGroupCommand {
1074 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1075 writeln!(f, "do")?;
1076 write!(
1077 indenter::indented(f).with_str(DISPLAY_INDENT),
1078 "{}",
1079 self.list
1080 )?;
1081 writeln!(f)?;
1082 write!(f, "done")
1083 }
1084}
1085
1086#[derive(Clone, Debug)]
1088#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1089#[cfg_attr(
1090 any(test, feature = "serde"),
1091 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1092)]
1093pub struct SimpleCommand {
1094 #[cfg_attr(
1096 any(test, feature = "serde"),
1097 serde(skip_serializing_if = "Option::is_none", default)
1098 )]
1099 pub prefix: Option<CommandPrefix>,
1100 #[cfg_attr(
1102 any(test, feature = "serde"),
1103 serde(skip_serializing_if = "Option::is_none", default)
1104 )]
1105 pub word_or_name: Option<Word>,
1106 #[cfg_attr(
1108 any(test, feature = "serde"),
1109 serde(skip_serializing_if = "Option::is_none", default)
1110 )]
1111 pub suffix: Option<CommandSuffix>,
1112}
1113
1114impl Node for SimpleCommand {}
1115
1116impl SourceLocation for SimpleCommand {
1117 fn location(&self) -> Option<SourceSpan> {
1118 let mid = &self
1119 .word_or_name
1120 .as_ref()
1121 .and_then(SourceLocation::location);
1122 let start = self.prefix.as_ref().and_then(SourceLocation::location);
1123 let end = self.suffix.as_ref().and_then(SourceLocation::location);
1124
1125 match (start, mid, end) {
1126 (Some(start), _, Some(end)) => Some(SourceSpan::within(&start, &end)),
1127 (Some(start), Some(mid), None) => Some(SourceSpan::within(&start, mid)),
1128 (Some(start), None, None) => Some(start),
1129 (None, Some(mid), Some(end)) => Some(SourceSpan::within(mid, &end)),
1130 (None, Some(mid), None) => Some(mid.clone()),
1131 _ => None,
1132 }
1133 }
1134}
1135
1136impl Display for SimpleCommand {
1137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1138 let mut wrote_something = false;
1139
1140 if let Some(prefix) = &self.prefix {
1141 if wrote_something {
1142 write!(f, " ")?;
1143 }
1144
1145 write!(f, "{prefix}")?;
1146 wrote_something = true;
1147 }
1148
1149 if let Some(word_or_name) = &self.word_or_name {
1150 if wrote_something {
1151 write!(f, " ")?;
1152 }
1153
1154 write!(f, "{word_or_name}")?;
1155 wrote_something = true;
1156 }
1157
1158 if let Some(suffix) = &self.suffix {
1159 if wrote_something {
1160 write!(f, " ")?;
1161 }
1162
1163 write!(f, "{suffix}")?;
1164 }
1165
1166 Ok(())
1167 }
1168}
1169
1170#[derive(Clone, Debug, Default)]
1172#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1173#[cfg_attr(
1174 any(test, feature = "serde"),
1175 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1176)]
1177pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
1178
1179impl Node for CommandPrefix {}
1180
1181impl SourceLocation for CommandPrefix {
1182 fn location(&self) -> Option<SourceSpan> {
1183 let start = self.0.first().and_then(SourceLocation::location);
1184 let end = self.0.last().and_then(SourceLocation::location);
1185
1186 maybe_location(start.as_ref(), end.as_ref())
1187 }
1188}
1189
1190impl Display for CommandPrefix {
1191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1192 for (i, item) in self.0.iter().enumerate() {
1193 if i > 0 {
1194 write!(f, " ")?;
1195 }
1196
1197 write!(f, "{item}")?;
1198 }
1199 Ok(())
1200 }
1201}
1202
1203#[derive(Clone, Default, Debug)]
1205#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1206#[cfg_attr(
1207 any(test, feature = "serde"),
1208 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1209)]
1210pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
1211
1212impl Node for CommandSuffix {}
1213
1214impl SourceLocation for CommandSuffix {
1215 fn location(&self) -> Option<SourceSpan> {
1216 let start = self.0.first().and_then(SourceLocation::location);
1217 let end = self.0.last().and_then(SourceLocation::location);
1218
1219 maybe_location(start.as_ref(), end.as_ref())
1220 }
1221}
1222
1223impl Display for CommandSuffix {
1224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1225 for (i, item) in self.0.iter().enumerate() {
1226 if i > 0 {
1227 write!(f, " ")?;
1228 }
1229
1230 write!(f, "{item}")?;
1231 }
1232 Ok(())
1233 }
1234}
1235
1236#[derive(Clone, Debug)]
1238#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1239#[cfg_attr(
1240 any(test, feature = "serde"),
1241 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1242)]
1243pub enum ProcessSubstitutionKind {
1244 Read,
1246 Write,
1248}
1249
1250impl Display for ProcessSubstitutionKind {
1251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1252 match self {
1253 Self::Read => write!(f, "<"),
1254 Self::Write => write!(f, ">"),
1255 }
1256 }
1257}
1258
1259#[derive(Clone, Debug)]
1261#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1262#[cfg_attr(
1263 any(test, feature = "serde"),
1264 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1265)]
1266pub enum CommandPrefixOrSuffixItem {
1267 IoRedirect(IoRedirect),
1269 Word(Word),
1271 AssignmentWord(Assignment, Word),
1273 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1275}
1276
1277impl Node for CommandPrefixOrSuffixItem {}
1278
1279impl SourceLocation for CommandPrefixOrSuffixItem {
1280 fn location(&self) -> Option<SourceSpan> {
1281 match self {
1282 Self::Word(w) => w.location(),
1283 Self::IoRedirect(io_redirect) => io_redirect.location(),
1284 Self::AssignmentWord(assignment, _word) => assignment.location(),
1285 Self::ProcessSubstitution(_kind, cmd) => cmd.location(),
1287 }
1288 }
1289}
1290
1291impl Display for CommandPrefixOrSuffixItem {
1292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1293 match self {
1294 Self::IoRedirect(io_redirect) => write!(f, "{io_redirect}"),
1295 Self::Word(word) => write!(f, "{word}"),
1296 Self::AssignmentWord(_assignment, word) => write!(f, "{word}"),
1297 Self::ProcessSubstitution(kind, subshell_command) => {
1298 write!(f, "{kind}({subshell_command})")
1299 }
1300 }
1301 }
1302}
1303
1304#[derive(Clone, Debug)]
1306#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1307#[cfg_attr(
1308 any(test, feature = "serde"),
1309 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1310)]
1311pub struct Assignment {
1312 pub name: AssignmentName,
1314 pub value: AssignmentValue,
1316 #[cfg_attr(
1318 any(test, feature = "serde"),
1319 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1320 )]
1321 pub append: bool,
1322 pub loc: SourceSpan,
1324}
1325
1326impl Node for Assignment {}
1327
1328impl SourceLocation for Assignment {
1329 fn location(&self) -> Option<SourceSpan> {
1330 Some(self.loc.clone())
1331 }
1332}
1333
1334impl Display for Assignment {
1335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1336 write!(f, "{}", self.name)?;
1337 if self.append {
1338 write!(f, "+")?;
1339 }
1340 write!(f, "={}", self.value)
1341 }
1342}
1343
1344#[derive(Clone, Debug)]
1346#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1347#[cfg_attr(
1348 any(test, feature = "serde"),
1349 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1350)]
1351pub enum AssignmentName {
1352 VariableName(String),
1354 ArrayElementName(String, String),
1356}
1357
1358impl Display for AssignmentName {
1359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1360 match self {
1361 Self::VariableName(name) => write!(f, "{name}"),
1362 Self::ArrayElementName(name, index) => {
1363 write!(f, "{name}[{index}]")
1364 }
1365 }
1366 }
1367}
1368
1369#[derive(Clone, Debug)]
1371#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1372#[cfg_attr(
1373 any(test, feature = "serde"),
1374 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1375)]
1376pub enum AssignmentValue {
1377 Scalar(Word),
1379 Array(Vec<(Option<Word>, Word)>),
1381}
1382
1383impl Node for AssignmentValue {}
1384
1385impl SourceLocation for AssignmentValue {
1386 fn location(&self) -> Option<SourceSpan> {
1387 match self {
1388 Self::Scalar(word) => word.location(),
1389 Self::Array(words) => {
1390 let first = words.first().and_then(|(_key, value)| value.location());
1392 let last = words.last().and_then(|(_key, value)| value.location());
1393 maybe_location(first.as_ref(), last.as_ref())
1394 }
1395 }
1396 }
1397}
1398
1399impl Display for AssignmentValue {
1400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1401 match self {
1402 Self::Scalar(word) => write!(f, "{word}"),
1403 Self::Array(words) => {
1404 write!(f, "(")?;
1405 for (i, value) in words.iter().enumerate() {
1406 if i > 0 {
1407 write!(f, " ")?;
1408 }
1409 match value {
1410 (Some(key), value) => write!(f, "[{key}]={value}")?,
1411 (None, value) => write!(f, "{value}")?,
1412 }
1413 }
1414 write!(f, ")")
1415 }
1416 }
1417 }
1418}
1419
1420#[derive(Clone, Debug)]
1422#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1423#[cfg_attr(
1424 any(test, feature = "serde"),
1425 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1426)]
1427pub struct RedirectList(pub Vec<IoRedirect>);
1428
1429impl Node for RedirectList {}
1430
1431impl SourceLocation for RedirectList {
1432 fn location(&self) -> Option<SourceSpan> {
1433 let first = self.0.first().and_then(SourceLocation::location);
1434 let last = self.0.last().and_then(SourceLocation::location);
1435 maybe_location(first.as_ref(), last.as_ref())
1436 }
1437}
1438
1439impl Display for RedirectList {
1440 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1441 for item in &self.0 {
1442 write!(f, "{item}")?;
1443 }
1444 Ok(())
1445 }
1446}
1447
1448pub type IoFd = i32;
1450
1451#[derive(Clone, Debug)]
1453#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1454#[cfg_attr(
1455 any(test, feature = "serde"),
1456 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1457)]
1458pub enum IoRedirect {
1459 File(Option<IoFd>, IoFileRedirectKind, IoFileRedirectTarget),
1461 HereDocument(Option<IoFd>, IoHereDocument),
1463 HereString(Option<IoFd>, Word),
1465 OutputAndError(Word, bool),
1467}
1468
1469impl Node for IoRedirect {}
1470
1471impl SourceLocation for IoRedirect {
1472 fn location(&self) -> Option<SourceSpan> {
1473 None
1475 }
1476}
1477
1478impl Display for IoRedirect {
1479 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1480 match self {
1481 Self::File(fd_num, kind, target) => {
1482 if let Some(fd_num) = fd_num {
1483 write!(f, "{fd_num}")?;
1484 }
1485
1486 write!(f, "{kind} {target}")?;
1487 }
1488 Self::OutputAndError(target, append) => {
1489 write!(f, "&>")?;
1490 if *append {
1491 write!(f, ">")?;
1492 }
1493 write!(f, " {target}")?;
1494 }
1495 Self::HereDocument(fd_num, here_doc) => {
1496 if let Some(fd_num) = fd_num {
1497 write!(f, "{fd_num}")?;
1498 }
1499
1500 write!(f, "<<{here_doc}")?;
1501 }
1502 Self::HereString(fd_num, s) => {
1503 if let Some(fd_num) = fd_num {
1504 write!(f, "{fd_num}")?;
1505 }
1506
1507 write!(f, "<<< {s}")?;
1508 }
1509 }
1510
1511 Ok(())
1512 }
1513}
1514
1515#[derive(Clone, Debug)]
1517#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1518#[cfg_attr(
1519 any(test, feature = "serde"),
1520 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1521)]
1522pub enum IoFileRedirectKind {
1523 Read,
1525 Write,
1527 Append,
1529 ReadAndWrite,
1531 Clobber,
1533 DuplicateInput,
1535 DuplicateOutput,
1537}
1538
1539impl Display for IoFileRedirectKind {
1540 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1541 match self {
1542 Self::Read => write!(f, "<"),
1543 Self::Write => write!(f, ">"),
1544 Self::Append => write!(f, ">>"),
1545 Self::ReadAndWrite => write!(f, "<>"),
1546 Self::Clobber => write!(f, ">|"),
1547 Self::DuplicateInput => write!(f, "<&"),
1548 Self::DuplicateOutput => write!(f, ">&"),
1549 }
1550 }
1551}
1552
1553#[derive(Clone, Debug)]
1555#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1556#[cfg_attr(
1557 any(test, feature = "serde"),
1558 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1559)]
1560pub enum IoFileRedirectTarget {
1561 Filename(Word),
1563 Fd(IoFd),
1565 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1568 Duplicate(Word),
1572}
1573
1574impl Display for IoFileRedirectTarget {
1575 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1576 match self {
1577 Self::Filename(word) => write!(f, "{word}"),
1578 Self::Fd(fd) => write!(f, "{fd}"),
1579 Self::ProcessSubstitution(kind, subshell_command) => {
1580 write!(f, "{kind}{subshell_command}")
1581 }
1582 Self::Duplicate(word) => write!(f, "{word}"),
1583 }
1584 }
1585}
1586
1587#[derive(Clone, Debug)]
1589#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1590#[cfg_attr(
1591 any(test, feature = "serde"),
1592 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1593)]
1594pub struct IoHereDocument {
1595 #[cfg_attr(
1597 any(test, feature = "serde"),
1598 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1599 )]
1600 pub remove_tabs: bool,
1601 #[cfg_attr(
1603 any(test, feature = "serde"),
1604 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1605 )]
1606 pub requires_expansion: bool,
1607 pub here_end: Word,
1609 pub doc: Word,
1611}
1612
1613impl Node for IoHereDocument {}
1614
1615impl SourceLocation for IoHereDocument {
1616 fn location(&self) -> Option<SourceSpan> {
1617 None
1619 }
1620}
1621
1622impl Display for IoHereDocument {
1623 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1624 if self.remove_tabs {
1625 write!(f, "-")?;
1626 }
1627
1628 writeln!(f, "{}", self.here_end)?;
1629 write!(f, "{}", self.doc)?;
1630 writeln!(f, "{}", self.here_end)?;
1631
1632 Ok(())
1633 }
1634}
1635
1636#[derive(Clone, Debug)]
1638#[cfg_attr(
1639 any(test, feature = "serde"),
1640 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1641)]
1642pub enum TestExpr {
1643 False,
1645 Literal(String),
1647 And(Box<Self>, Box<Self>),
1649 Or(Box<Self>, Box<Self>),
1651 Not(Box<Self>),
1653 Parenthesized(Box<Self>),
1655 UnaryTest(UnaryPredicate, String),
1657 BinaryTest(BinaryPredicate, String, String),
1659}
1660
1661impl Node for TestExpr {}
1662
1663impl SourceLocation for TestExpr {
1664 fn location(&self) -> Option<SourceSpan> {
1665 None
1667 }
1668}
1669
1670impl Display for TestExpr {
1671 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1672 match self {
1673 Self::False => Ok(()),
1674 Self::Literal(s) => write!(f, "{s}"),
1675 Self::And(left, right) => write!(f, "{left} -a {right}"),
1676 Self::Or(left, right) => write!(f, "{left} -o {right}"),
1677 Self::Not(expr) => write!(f, "! {expr}"),
1678 Self::Parenthesized(expr) => write!(f, "( {expr} )"),
1679 Self::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
1680 Self::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
1681 }
1682 }
1683}
1684
1685#[derive(Clone, Debug)]
1687#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1688#[cfg_attr(
1689 any(test, feature = "serde"),
1690 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1691)]
1692pub enum ExtendedTestExpr {
1693 And(Box<Self>, Box<Self>),
1695 Or(Box<Self>, Box<Self>),
1697 Not(Box<Self>),
1699 Parenthesized(Box<Self>),
1701 UnaryTest(UnaryPredicate, Word),
1703 BinaryTest(BinaryPredicate, Word, Word),
1705}
1706
1707impl Display for ExtendedTestExpr {
1708 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1709 match self {
1710 Self::And(left, right) => {
1711 write!(f, "{left} && {right}")
1712 }
1713 Self::Or(left, right) => {
1714 write!(f, "{left} || {right}")
1715 }
1716 Self::Not(expr) => {
1717 write!(f, "! {expr}")
1718 }
1719 Self::Parenthesized(expr) => {
1720 write!(f, "( {expr} )")
1721 }
1722 Self::UnaryTest(pred, word) => {
1723 write!(f, "{pred} {word}")
1724 }
1725 Self::BinaryTest(pred, left, right) => {
1726 write!(f, "{left} {pred} {right}")
1727 }
1728 }
1729 }
1730}
1731
1732#[derive(Clone, Debug)]
1734#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1735#[cfg_attr(
1736 any(test, feature = "serde"),
1737 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1738)]
1739pub struct ExtendedTestExprCommand {
1740 pub expr: ExtendedTestExpr,
1742 pub loc: SourceSpan,
1744}
1745
1746impl Node for ExtendedTestExprCommand {}
1747
1748impl SourceLocation for ExtendedTestExprCommand {
1749 fn location(&self) -> Option<SourceSpan> {
1750 Some(self.loc.clone())
1751 }
1752}
1753
1754impl Display for ExtendedTestExprCommand {
1755 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1756 self.expr.fmt(f)
1757 }
1758}
1759
1760#[derive(Clone, Debug)]
1762#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1763#[cfg_attr(
1764 any(test, feature = "serde"),
1765 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1766)]
1767pub enum UnaryPredicate {
1768 FileExists,
1770 FileExistsAndIsBlockSpecialFile,
1772 FileExistsAndIsCharSpecialFile,
1774 FileExistsAndIsDir,
1776 FileExistsAndIsRegularFile,
1778 FileExistsAndIsSetgid,
1780 FileExistsAndIsSymlink,
1782 FileExistsAndHasStickyBit,
1784 FileExistsAndIsFifo,
1786 FileExistsAndIsReadable,
1788 FileExistsAndIsNotZeroLength,
1790 FdIsOpenTerminal,
1792 FileExistsAndIsSetuid,
1794 FileExistsAndIsWritable,
1796 FileExistsAndIsExecutable,
1798 FileExistsAndOwnedByEffectiveGroupId,
1801 FileExistsAndModifiedSinceLastRead,
1804 FileExistsAndOwnedByEffectiveUserId,
1807 FileExistsAndIsSocket,
1809 ShellOptionEnabled,
1811 ShellVariableIsSetAndAssigned,
1813 ShellVariableIsSetAndNameRef,
1815 StringHasZeroLength,
1817 StringHasNonZeroLength,
1819}
1820
1821impl Display for UnaryPredicate {
1822 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1823 match self {
1824 Self::FileExists => write!(f, "-e"),
1825 Self::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
1826 Self::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
1827 Self::FileExistsAndIsDir => write!(f, "-d"),
1828 Self::FileExistsAndIsRegularFile => write!(f, "-f"),
1829 Self::FileExistsAndIsSetgid => write!(f, "-g"),
1830 Self::FileExistsAndIsSymlink => write!(f, "-h"),
1831 Self::FileExistsAndHasStickyBit => write!(f, "-k"),
1832 Self::FileExistsAndIsFifo => write!(f, "-p"),
1833 Self::FileExistsAndIsReadable => write!(f, "-r"),
1834 Self::FileExistsAndIsNotZeroLength => write!(f, "-s"),
1835 Self::FdIsOpenTerminal => write!(f, "-t"),
1836 Self::FileExistsAndIsSetuid => write!(f, "-u"),
1837 Self::FileExistsAndIsWritable => write!(f, "-w"),
1838 Self::FileExistsAndIsExecutable => write!(f, "-x"),
1839 Self::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
1840 Self::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
1841 Self::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
1842 Self::FileExistsAndIsSocket => write!(f, "-S"),
1843 Self::ShellOptionEnabled => write!(f, "-o"),
1844 Self::ShellVariableIsSetAndAssigned => write!(f, "-v"),
1845 Self::ShellVariableIsSetAndNameRef => write!(f, "-R"),
1846 Self::StringHasZeroLength => write!(f, "-z"),
1847 Self::StringHasNonZeroLength => write!(f, "-n"),
1848 }
1849 }
1850}
1851
1852#[derive(Clone, Debug)]
1854#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1855#[cfg_attr(
1856 any(test, feature = "serde"),
1857 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1858)]
1859pub enum BinaryPredicate {
1860 FilesReferToSameDeviceAndInodeNumbers,
1862 LeftFileIsNewerOrExistsWhenRightDoesNot,
1864 LeftFileIsOlderOrDoesNotExistWhenRightDoes,
1866 StringExactlyMatchesPattern,
1868 StringDoesNotExactlyMatchPattern,
1870 StringMatchesRegex,
1872 StringExactlyMatchesString,
1874 StringDoesNotExactlyMatchString,
1876 StringContainsSubstring,
1878 LeftSortsBeforeRight,
1880 LeftSortsAfterRight,
1882 ArithmeticEqualTo,
1884 ArithmeticNotEqualTo,
1886 ArithmeticLessThan,
1888 ArithmeticLessThanOrEqualTo,
1890 ArithmeticGreaterThan,
1892 ArithmeticGreaterThanOrEqualTo,
1894}
1895
1896impl Display for BinaryPredicate {
1897 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1898 match self {
1899 Self::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
1900 Self::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
1901 Self::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
1902 Self::StringExactlyMatchesPattern => write!(f, "=="),
1903 Self::StringDoesNotExactlyMatchPattern => write!(f, "!="),
1904 Self::StringMatchesRegex => write!(f, "=~"),
1905 Self::StringContainsSubstring => write!(f, "=~"),
1906 Self::StringExactlyMatchesString => write!(f, "=="),
1907 Self::StringDoesNotExactlyMatchString => write!(f, "!="),
1908 Self::LeftSortsBeforeRight => write!(f, "<"),
1909 Self::LeftSortsAfterRight => write!(f, ">"),
1910 Self::ArithmeticEqualTo => write!(f, "-eq"),
1911 Self::ArithmeticNotEqualTo => write!(f, "-ne"),
1912 Self::ArithmeticLessThan => write!(f, "-lt"),
1913 Self::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
1914 Self::ArithmeticGreaterThan => write!(f, "-gt"),
1915 Self::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
1916 }
1917 }
1918}
1919
1920#[derive(Clone, Debug)]
1922#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1923#[cfg_attr(
1924 any(test, feature = "serde"),
1925 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1926)]
1927pub struct Word {
1928 pub value: String,
1930 pub loc: Option<SourceSpan>,
1932}
1933
1934impl Node for Word {}
1935
1936impl SourceLocation for Word {
1937 fn location(&self) -> Option<SourceSpan> {
1938 self.loc.clone()
1939 }
1940}
1941
1942impl Display for Word {
1943 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1944 write!(f, "{}", self.value)
1945 }
1946}
1947
1948impl From<&tokenizer::Token> for Word {
1949 fn from(t: &tokenizer::Token) -> Self {
1950 match t {
1951 tokenizer::Token::Word(value, loc) => Self {
1952 value: value.clone(),
1953 loc: Some(loc.clone()),
1954 },
1955 tokenizer::Token::Operator(value, loc) => Self {
1956 value: value.clone(),
1957 loc: Some(loc.clone()),
1958 },
1959 }
1960 }
1961}
1962
1963impl From<String> for Word {
1964 fn from(s: String) -> Self {
1965 Self {
1966 value: s,
1967 loc: None,
1968 }
1969 }
1970}
1971
1972impl AsRef<str> for Word {
1973 fn as_ref(&self) -> &str {
1974 &self.value
1975 }
1976}
1977
1978impl Word {
1979 pub fn new(s: &str) -> Self {
1981 Self {
1982 value: s.to_owned(),
1983 loc: None,
1984 }
1985 }
1986
1987 pub fn with_location(s: &str, loc: &SourceSpan) -> Self {
1989 Self {
1990 value: s.to_owned(),
1991 loc: Some(loc.to_owned()),
1992 }
1993 }
1994
1995 pub fn flatten(&self) -> String {
1997 self.value.clone()
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 UnexpandedArithmeticExpr {
2009 pub value: String,
2011}
2012
2013impl Display for UnexpandedArithmeticExpr {
2014 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2015 write!(f, "{}", self.value)
2016 }
2017}
2018
2019#[derive(Clone, Debug)]
2021#[cfg_attr(
2022 any(test, feature = "serde"),
2023 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2024)]
2025pub enum ArithmeticExpr {
2026 Literal(i64),
2028 Reference(ArithmeticTarget),
2030 UnaryOp(UnaryOperator, Box<Self>),
2032 BinaryOp(BinaryOperator, Box<Self>, Box<Self>),
2034 Conditional(Box<Self>, Box<Self>, Box<Self>),
2036 Assignment(ArithmeticTarget, Box<Self>),
2038 BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<Self>),
2040 UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
2042}
2043
2044impl Node for ArithmeticExpr {}
2045
2046impl SourceLocation for ArithmeticExpr {
2047 fn location(&self) -> Option<SourceSpan> {
2048 None
2050 }
2051}
2052
2053#[cfg(feature = "arbitrary")]
2054impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
2055 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2056 let variant = u.choose(&[
2057 "Literal",
2058 "Reference",
2059 "UnaryOp",
2060 "BinaryOp",
2061 "Conditional",
2062 "Assignment",
2063 "BinaryAssignment",
2064 "UnaryAssignment",
2065 ])?;
2066
2067 match *variant {
2068 "Literal" => Ok(Self::Literal(i64::arbitrary(u)?)),
2069 "Reference" => Ok(Self::Reference(ArithmeticTarget::arbitrary(u)?)),
2070 "UnaryOp" => Ok(Self::UnaryOp(
2071 UnaryOperator::arbitrary(u)?,
2072 Box::new(Self::arbitrary(u)?),
2073 )),
2074 "BinaryOp" => Ok(Self::BinaryOp(
2075 BinaryOperator::arbitrary(u)?,
2076 Box::new(Self::arbitrary(u)?),
2077 Box::new(Self::arbitrary(u)?),
2078 )),
2079 "Conditional" => Ok(Self::Conditional(
2080 Box::new(Self::arbitrary(u)?),
2081 Box::new(Self::arbitrary(u)?),
2082 Box::new(Self::arbitrary(u)?),
2083 )),
2084 "Assignment" => Ok(Self::Assignment(
2085 ArithmeticTarget::arbitrary(u)?,
2086 Box::new(Self::arbitrary(u)?),
2087 )),
2088 "BinaryAssignment" => Ok(Self::BinaryAssignment(
2089 BinaryOperator::arbitrary(u)?,
2090 ArithmeticTarget::arbitrary(u)?,
2091 Box::new(Self::arbitrary(u)?),
2092 )),
2093 "UnaryAssignment" => Ok(Self::UnaryAssignment(
2094 UnaryAssignmentOperator::arbitrary(u)?,
2095 ArithmeticTarget::arbitrary(u)?,
2096 )),
2097 _ => unreachable!(),
2098 }
2099 }
2100}
2101
2102impl Display for ArithmeticExpr {
2103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2104 match self {
2105 Self::Literal(literal) => write!(f, "{literal}"),
2106 Self::Reference(target) => write!(f, "{target}"),
2107 Self::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
2108 Self::BinaryOp(op, left, right) => {
2109 if matches!(op, BinaryOperator::Comma) {
2110 write!(f, "{left}{op} {right}")
2111 } else {
2112 write!(f, "{left} {op} {right}")
2113 }
2114 }
2115 Self::Conditional(condition, if_branch, else_branch) => {
2116 write!(f, "{condition} ? {if_branch} : {else_branch}")
2117 }
2118 Self::Assignment(target, value) => write!(f, "{target} = {value}"),
2119 Self::BinaryAssignment(op, target, operand) => {
2120 write!(f, "{target} {op}= {operand}")
2121 }
2122 Self::UnaryAssignment(op, target) => match op {
2123 UnaryAssignmentOperator::PrefixIncrement
2124 | UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
2125 UnaryAssignmentOperator::PostfixIncrement
2126 | UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
2127 },
2128 }
2129 }
2130}
2131
2132#[derive(Clone, Copy, Debug)]
2134#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2135#[cfg_attr(
2136 any(test, feature = "serde"),
2137 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2138)]
2139pub enum BinaryOperator {
2140 Power,
2142 Multiply,
2144 Divide,
2146 Modulo,
2148 Comma,
2150 Add,
2152 Subtract,
2154 ShiftLeft,
2156 ShiftRight,
2158 LessThan,
2160 LessThanOrEqualTo,
2162 GreaterThan,
2164 GreaterThanOrEqualTo,
2166 Equals,
2168 NotEquals,
2170 BitwiseAnd,
2172 BitwiseXor,
2174 BitwiseOr,
2176 LogicalAnd,
2178 LogicalOr,
2180}
2181
2182impl Display for BinaryOperator {
2183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2184 match self {
2185 Self::Power => write!(f, "**"),
2186 Self::Multiply => write!(f, "*"),
2187 Self::Divide => write!(f, "/"),
2188 Self::Modulo => write!(f, "%"),
2189 Self::Comma => write!(f, ","),
2190 Self::Add => write!(f, "+"),
2191 Self::Subtract => write!(f, "-"),
2192 Self::ShiftLeft => write!(f, "<<"),
2193 Self::ShiftRight => write!(f, ">>"),
2194 Self::LessThan => write!(f, "<"),
2195 Self::LessThanOrEqualTo => write!(f, "<="),
2196 Self::GreaterThan => write!(f, ">"),
2197 Self::GreaterThanOrEqualTo => write!(f, ">="),
2198 Self::Equals => write!(f, "=="),
2199 Self::NotEquals => write!(f, "!="),
2200 Self::BitwiseAnd => write!(f, "&"),
2201 Self::BitwiseXor => write!(f, "^"),
2202 Self::BitwiseOr => write!(f, "|"),
2203 Self::LogicalAnd => write!(f, "&&"),
2204 Self::LogicalOr => write!(f, "||"),
2205 }
2206 }
2207}
2208
2209#[derive(Clone, Copy, Debug)]
2211#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2212#[cfg_attr(
2213 any(test, feature = "serde"),
2214 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2215)]
2216pub enum UnaryOperator {
2217 UnaryPlus,
2219 UnaryMinus,
2221 BitwiseNot,
2223 LogicalNot,
2225}
2226
2227impl Display for UnaryOperator {
2228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2229 match self {
2230 Self::UnaryPlus => write!(f, "+"),
2231 Self::UnaryMinus => write!(f, "-"),
2232 Self::BitwiseNot => write!(f, "~"),
2233 Self::LogicalNot => write!(f, "!"),
2234 }
2235 }
2236}
2237
2238#[derive(Clone, Copy, Debug)]
2240#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2241#[cfg_attr(
2242 any(test, feature = "serde"),
2243 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2244)]
2245pub enum UnaryAssignmentOperator {
2246 PrefixIncrement,
2248 PrefixDecrement,
2250 PostfixIncrement,
2252 PostfixDecrement,
2254}
2255
2256impl Display for UnaryAssignmentOperator {
2257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2258 match self {
2259 Self::PrefixIncrement => write!(f, "++"),
2260 Self::PrefixDecrement => write!(f, "--"),
2261 Self::PostfixIncrement => write!(f, "++"),
2262 Self::PostfixDecrement => write!(f, "--"),
2263 }
2264 }
2265}
2266
2267#[derive(Clone, Debug)]
2269#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2270#[cfg_attr(
2271 any(test, feature = "serde"),
2272 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2273)]
2274pub enum ArithmeticTarget {
2275 Variable(String),
2277 ArrayElement(String, Box<ArithmeticExpr>),
2279}
2280
2281impl Node for ArithmeticTarget {}
2282
2283impl SourceLocation for ArithmeticTarget {
2284 fn location(&self) -> Option<SourceSpan> {
2285 None
2287 }
2288}
2289
2290impl Display for ArithmeticTarget {
2291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2292 match self {
2293 Self::Variable(name) => write!(f, "{name}"),
2294 Self::ArrayElement(name, index) => write!(f, "{name}[{index}]"),
2295 }
2296 }
2297}
2298
2299#[cfg(test)]
2300#[allow(clippy::panic)]
2301mod tests {
2302 use super::*;
2303 use crate::{ParserOptions, SourcePosition};
2304 use std::io::BufReader;
2305
2306 fn parse(input: &str) -> Program {
2307 let reader = BufReader::new(input.as_bytes());
2308 let mut parser = crate::Parser::new(reader, &ParserOptions::default());
2309 parser.parse_program().unwrap()
2310 }
2311
2312 #[test]
2313 fn program_source_loc() {
2314 const INPUT: &str = r"echo hi
2315echo there
2316";
2317
2318 let loc = parse(INPUT).location().unwrap();
2319
2320 assert_eq!(
2321 *(loc.start),
2322 SourcePosition {
2323 line: 1,
2324 column: 1,
2325 index: 0
2326 }
2327 );
2328 assert_eq!(
2329 *(loc.end),
2330 SourcePosition {
2331 line: 2,
2332 column: 11,
2333 index: 18
2334 }
2335 );
2336 }
2337
2338 #[test]
2339 fn function_def_loc() {
2340 const INPUT: &str = r"my_func() {
2341 echo hi
2342 echo there
2343}
2344
2345my_func
2346";
2347
2348 let program = parse(INPUT);
2349
2350 let Command::Function(func_def) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2351 panic!("expected function definition");
2352 };
2353
2354 let loc = func_def.location().unwrap();
2355
2356 assert_eq!(
2357 *(loc.start),
2358 SourcePosition {
2359 line: 1,
2360 column: 1,
2361 index: 0
2362 }
2363 );
2364 assert_eq!(
2365 *(loc.end),
2366 SourcePosition {
2367 line: 4,
2368 column: 2,
2369 index: 36
2370 }
2371 );
2372 }
2373
2374 #[test]
2375 fn simple_cmd_loc() {
2376 const INPUT: &str = r"var=value somecmd arg1 arg2
2377";
2378
2379 let program = parse(INPUT);
2380
2381 let Command::Simple(cmd) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2382 panic!("expected function definition");
2383 };
2384
2385 let loc = cmd.location().unwrap();
2386
2387 assert_eq!(
2388 *(loc.start),
2389 SourcePosition {
2390 line: 1,
2391 column: 1,
2392 index: 0
2393 }
2394 );
2395 assert_eq!(
2396 *(loc.end),
2397 SourcePosition {
2398 line: 1,
2399 column: 28,
2400 index: 27
2401 }
2402 );
2403 }
2404}