1use std::fmt::{Display, Write};
5
6use crate::{TokenLocation, tokenizer};
7
8const DISPLAY_INDENT: &str = " ";
9
10pub trait SourceLocation {
12 fn location(&self) -> Option<TokenLocation>;
14}
15
16pub(crate) fn maybe_location(
17 start: Option<&TokenLocation>,
18 end: Option<&TokenLocation>,
19) -> Option<TokenLocation> {
20 if let (Some(s), Some(e)) = (start, end) {
21 Some(TokenLocation::within(s, e))
22 } else {
23 None
24 }
25}
26
27#[derive(Clone, Debug)]
29#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
30#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
31pub struct Program {
32 #[cfg_attr(test, serde(rename = "cmds"))]
34 pub complete_commands: Vec<CompleteCommand>,
35}
36
37impl SourceLocation for Program {
38 fn location(&self) -> Option<TokenLocation> {
39 let start = self
40 .complete_commands
41 .first()
42 .and_then(SourceLocation::location);
43 let end = self
44 .complete_commands
45 .last()
46 .and_then(SourceLocation::location);
47 maybe_location(start.as_ref(), end.as_ref())
48 }
49}
50
51impl Display for Program {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 for complete_command in &self.complete_commands {
54 write!(f, "{complete_command}")?;
55 }
56 Ok(())
57 }
58}
59
60pub type CompleteCommand = CompoundList;
62
63pub type CompleteCommandItem = CompoundListItem;
65
66#[derive(Clone, Debug)]
69#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
70#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
71pub enum SeparatorOperator {
72 Async,
74 Sequence,
76}
77
78impl Display for SeparatorOperator {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 Self::Async => write!(f, "&"),
82 Self::Sequence => write!(f, ";"),
83 }
84 }
85}
86
87#[derive(Clone, Debug)]
89#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
90#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
91#[cfg_attr(test, serde(rename = "AndOr"))]
92pub struct AndOrList {
93 pub first: Pipeline,
95 #[cfg_attr(test, serde(skip_serializing_if = "Vec::is_empty"))]
97 pub additional: Vec<AndOr>,
98}
99
100impl SourceLocation for AndOrList {
101 fn location(&self) -> Option<TokenLocation> {
102 let start = self.first.location();
103 let last = self.additional.last();
104 let end = last.and_then(SourceLocation::location);
105
106 match (start, end) {
107 (Some(s), Some(e)) => Some(TokenLocation::within(&s, &e)),
108 (start, _) => start,
109 }
110 }
111}
112
113impl Display for AndOrList {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 write!(f, "{}", self.first)?;
116 for item in &self.additional {
117 write!(f, "{item}")?;
118 }
119
120 Ok(())
121 }
122}
123
124#[derive(PartialEq, Eq)]
126pub enum PipelineOperator {
127 And,
129 Or,
131}
132
133impl PartialEq<AndOr> for PipelineOperator {
134 fn eq(&self, other: &AndOr) -> bool {
135 matches!(
136 (self, other),
137 (Self::And, AndOr::And(_)) | (Self::Or, AndOr::Or(_))
138 )
139 }
140}
141
142#[expect(clippy::from_over_into)]
144impl Into<PipelineOperator> for AndOr {
145 fn into(self) -> PipelineOperator {
146 match self {
147 Self::And(_) => PipelineOperator::And,
148 Self::Or(_) => PipelineOperator::Or,
149 }
150 }
151}
152
153pub struct AndOrListIter<'a> {
155 first: Option<&'a Pipeline>,
156 additional_iter: std::slice::Iter<'a, AndOr>,
157}
158
159impl<'a> Iterator for AndOrListIter<'a> {
160 type Item = (PipelineOperator, &'a Pipeline);
161
162 fn next(&mut self) -> Option<Self::Item> {
163 if let Some(first) = self.first.take() {
164 Some((PipelineOperator::And, first))
165 } else {
166 self.additional_iter.next().map(|and_or| match and_or {
167 AndOr::And(pipeline) => (PipelineOperator::And, pipeline),
168 AndOr::Or(pipeline) => (PipelineOperator::Or, pipeline),
169 })
170 }
171 }
172}
173
174impl<'a> IntoIterator for &'a AndOrList {
175 type Item = (PipelineOperator, &'a Pipeline);
176 type IntoIter = AndOrListIter<'a>;
177
178 fn into_iter(self) -> Self::IntoIter {
179 AndOrListIter {
180 first: Some(&self.first),
181 additional_iter: self.additional.iter(),
182 }
183 }
184}
185
186impl<'a> From<(PipelineOperator, &'a Pipeline)> for AndOr {
187 fn from(value: (PipelineOperator, &'a Pipeline)) -> Self {
188 match value.0 {
189 PipelineOperator::Or => Self::Or(value.1.to_owned()),
190 PipelineOperator::And => Self::And(value.1.to_owned()),
191 }
192 }
193}
194
195impl AndOrList {
196 pub fn iter(&self) -> AndOrListIter<'_> {
198 self.into_iter()
199 }
200}
201
202#[derive(Clone, Debug)]
205#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
206#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
207pub enum AndOr {
208 And(Pipeline),
211 Or(Pipeline),
214}
215
216impl SourceLocation for AndOr {
218 fn location(&self) -> Option<TokenLocation> {
219 match self {
220 Self::And(p) => p.location(),
221 Self::Or(p) => p.location(),
222 }
223 }
224}
225
226impl Display for AndOr {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 match self {
229 Self::And(pipeline) => write!(f, " && {pipeline}"),
230 Self::Or(pipeline) => write!(f, " || {pipeline}"),
231 }
232 }
233}
234
235#[derive(Clone, Debug)]
237#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
238#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
239pub enum PipelineTimed {
240 Timed(TokenLocation),
242 TimedWithPosixOutput(TokenLocation),
244}
245
246impl SourceLocation for PipelineTimed {
247 fn location(&self) -> Option<TokenLocation> {
248 match self {
249 Self::Timed(t) => Some(t.to_owned()),
250 Self::TimedWithPosixOutput(t) => Some(t.to_owned()),
251 }
252 }
253}
254
255impl PipelineTimed {
256 pub const fn is_posix_output(&self) -> bool {
258 matches!(self, Self::TimedWithPosixOutput(_))
259 }
260}
261
262#[derive(Clone, Debug)]
265#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
266#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
267pub struct Pipeline {
268 #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
271 pub timed: Option<PipelineTimed>,
272 #[cfg_attr(test, serde(skip_serializing_if = "<&bool as std::ops::Not>::not"))]
275 pub bang: bool,
276 pub seq: Vec<Command>,
278}
279
280impl SourceLocation for Pipeline {
281 fn location(&self) -> Option<TokenLocation> {
282 let start = self
283 .timed
284 .as_ref()
285 .and_then(SourceLocation::location)
286 .or_else(|| self.seq.first().and_then(SourceLocation::location));
287 let end = self.seq.last().and_then(SourceLocation::location);
288
289 maybe_location(start.as_ref(), end.as_ref())
290 }
291}
292
293impl Display for Pipeline {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 if self.bang {
296 write!(f, "!")?;
297 }
298 for (i, command) in self.seq.iter().enumerate() {
299 if i > 0 {
300 write!(f, " |")?;
301 }
302 write!(f, "{command}")?;
303 }
304
305 Ok(())
306 }
307}
308
309#[derive(Clone, Debug)]
311#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
312#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
313pub enum Command {
314 Simple(SimpleCommand),
317 Compound(CompoundCommand, Option<RedirectList>),
319 Function(FunctionDefinition),
321 ExtendedTest(ExtendedTestExprCommand),
323}
324
325impl SourceLocation for Command {
326 fn location(&self) -> Option<TokenLocation> {
327 match self {
328 Self::Simple(s) => s.location(),
329 Self::Compound(c, r) => {
330 match (c.location(), r.as_ref().and_then(SourceLocation::location)) {
331 (Some(s), Some(e)) => Some(TokenLocation::within(&s, &e)),
332 (s, _) => s,
333 }
334 }
335 Self::Function(f) => f.location(),
336 Self::ExtendedTest(e) => e.location(),
337 }
338 }
339}
340
341impl Display for Command {
342 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343 match self {
344 Self::Simple(simple_command) => write!(f, "{simple_command}"),
345 Self::Compound(compound_command, redirect_list) => {
346 write!(f, "{compound_command}")?;
347 if let Some(redirect_list) = redirect_list {
348 write!(f, "{redirect_list}")?;
349 }
350 Ok(())
351 }
352 Self::Function(function_definition) => write!(f, "{function_definition}"),
353 Self::ExtendedTest(extended_test_expr) => {
354 write!(f, "[[ {extended_test_expr} ]]")
355 }
356 }
357 }
358}
359
360#[derive(Clone, Debug)]
362#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
363#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
364pub enum CompoundCommand {
365 Arithmetic(ArithmeticCommand),
367 ArithmeticForClause(ArithmeticForClauseCommand),
369 BraceGroup(BraceGroupCommand),
371 Subshell(SubshellCommand),
373 ForClause(ForClauseCommand),
375 CaseClause(CaseClauseCommand),
378 IfClause(IfClauseCommand),
380 WhileClause(WhileOrUntilClauseCommand),
382 UntilClause(WhileOrUntilClauseCommand),
384}
385
386impl SourceLocation for CompoundCommand {
388 fn location(&self) -> Option<TokenLocation> {
389 match self {
390 Self::Arithmetic(a) => a.location(),
391 Self::ArithmeticForClause(a) => a.location(),
392 Self::BraceGroup(b) => b.location(),
393 Self::Subshell(s) => s.location(),
394 Self::ForClause(f) => f.location(),
395 Self::CaseClause(c) => c.location(),
396 Self::IfClause(i) => i.location(),
397 Self::WhileClause(w) => w.location(),
398 Self::UntilClause(u) => u.location(),
399 }
400 }
401}
402
403impl Display for CompoundCommand {
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405 match self {
406 Self::Arithmetic(arithmetic_command) => write!(f, "{arithmetic_command}"),
407 Self::ArithmeticForClause(arithmetic_for_clause_command) => {
408 write!(f, "{arithmetic_for_clause_command}")
409 }
410 Self::BraceGroup(brace_group_command) => {
411 write!(f, "{brace_group_command}")
412 }
413 Self::Subshell(subshell_command) => write!(f, "{subshell_command}"),
414 Self::ForClause(for_clause_command) => write!(f, "{for_clause_command}"),
415 Self::CaseClause(case_clause_command) => {
416 write!(f, "{case_clause_command}")
417 }
418 Self::IfClause(if_clause_command) => write!(f, "{if_clause_command}"),
419 Self::WhileClause(while_or_until_clause_command) => {
420 write!(f, "while {while_or_until_clause_command}")
421 }
422 Self::UntilClause(while_or_until_clause_command) => {
423 write!(f, "until {while_or_until_clause_command}")
424 }
425 }
426 }
427}
428
429#[derive(Clone, Debug)]
431#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
432#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
433pub struct ArithmeticCommand {
434 pub expr: UnexpandedArithmeticExpr,
436 pub loc: TokenLocation,
438}
439
440impl SourceLocation for ArithmeticCommand {
441 fn location(&self) -> Option<TokenLocation> {
442 Some(self.loc.clone())
443 }
444}
445
446impl Display for ArithmeticCommand {
447 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448 write!(f, "(({}))", self.expr)
449 }
450}
451
452#[derive(Clone, Debug)]
454#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
455#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
456pub struct SubshellCommand {
457 pub list: CompoundList,
459 pub loc: TokenLocation,
461}
462
463impl SourceLocation for SubshellCommand {
464 fn location(&self) -> Option<TokenLocation> {
465 Some(self.loc.clone())
466 }
467}
468
469impl Display for SubshellCommand {
470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471 write!(f, "( ")?;
472 write!(f, "{}", self.list)?;
473 write!(f, " )")
474 }
475}
476
477#[derive(Clone, Debug)]
479#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
480#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
481pub struct ForClauseCommand {
482 pub variable_name: String,
484 pub values: Option<Vec<Word>>,
486 pub body: DoGroupCommand,
488 pub loc: TokenLocation,
490}
491
492impl SourceLocation for ForClauseCommand {
493 fn location(&self) -> Option<TokenLocation> {
494 Some(self.loc.clone())
495 }
496}
497
498impl Display for ForClauseCommand {
499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
500 write!(f, "for {} in ", self.variable_name)?;
501
502 if let Some(values) = &self.values {
503 for (i, value) in values.iter().enumerate() {
504 if i > 0 {
505 write!(f, " ")?;
506 }
507
508 write!(f, "{value}")?;
509 }
510 }
511
512 writeln!(f, ";")?;
513
514 write!(f, "{}", self.body)
515 }
516}
517
518#[derive(Clone, Debug)]
520#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
521#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
522pub struct ArithmeticForClauseCommand {
523 pub initializer: Option<UnexpandedArithmeticExpr>,
525 pub condition: Option<UnexpandedArithmeticExpr>,
527 pub updater: Option<UnexpandedArithmeticExpr>,
529 pub body: DoGroupCommand,
531 pub loc: TokenLocation,
533}
534
535impl SourceLocation for ArithmeticForClauseCommand {
536 fn location(&self) -> Option<TokenLocation> {
537 Some(self.loc.clone())
538 }
539}
540
541impl Display for ArithmeticForClauseCommand {
542 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
543 write!(f, "for ((")?;
544
545 if let Some(initializer) = &self.initializer {
546 write!(f, "{initializer}")?;
547 }
548
549 write!(f, "; ")?;
550
551 if let Some(condition) = &self.condition {
552 write!(f, "{condition}")?;
553 }
554
555 write!(f, "; ")?;
556
557 if let Some(updater) = &self.updater {
558 write!(f, "{updater}")?;
559 }
560
561 writeln!(f, "))")?;
562
563 write!(f, "{}", self.body)
564 }
565}
566
567#[derive(Clone, Debug)]
570#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
571#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
572pub struct CaseClauseCommand {
573 pub value: Word,
575 pub cases: Vec<CaseItem>,
577}
578
579impl SourceLocation for CaseClauseCommand {
580 fn location(&self) -> Option<TokenLocation> {
581 let start = self.value.location();
582 let end = self.cases.last().and_then(SourceLocation::location);
583
584 maybe_location(start.as_ref(), end.as_ref())
585 }
586}
587
588impl Display for CaseClauseCommand {
589 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
590 write!(f, "case {} in", self.value)?;
591 for case in &self.cases {
592 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{case}")?;
593 }
594 writeln!(f)?;
595 write!(f, "esac")
596 }
597}
598
599#[derive(Clone, Debug)]
601#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
602#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
603#[cfg_attr(test, serde(rename = "List"))]
604pub struct CompoundList(pub Vec<CompoundListItem>);
605
606impl SourceLocation for CompoundList {
608 fn location(&self) -> Option<TokenLocation> {
609 let start = self.0.first().and_then(SourceLocation::location);
610 let end = self.0.last().and_then(SourceLocation::location);
611
612 if let (Some(s), Some(e)) = (start, end) {
613 Some(TokenLocation::within(&s, &e))
614 } else {
615 None
616 }
617 }
618}
619
620impl Display for CompoundList {
621 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622 for (i, item) in self.0.iter().enumerate() {
623 if i > 0 {
624 writeln!(f)?;
625 }
626
627 write!(f, "{}", item.0)?;
629
630 if i == self.0.len() - 1 && matches!(item.1, SeparatorOperator::Sequence) {
632 } else {
634 write!(f, "{}", item.1)?;
635 }
636 }
637
638 Ok(())
639 }
640}
641
642#[derive(Clone, Debug)]
644#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
645#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
646#[cfg_attr(test, serde(rename = "Item"))]
647pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
648
649impl SourceLocation for CompoundListItem {
650 fn location(&self) -> Option<TokenLocation> {
651 self.0.location()
652 }
653}
654
655impl Display for CompoundListItem {
656 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
657 write!(f, "{}", self.0)?;
658 write!(f, "{}", self.1)?;
659 Ok(())
660 }
661}
662
663#[derive(Clone, Debug)]
665#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
666#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
667pub struct IfClauseCommand {
668 pub condition: CompoundList,
670 pub then: CompoundList,
672 #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
674 pub elses: Option<Vec<ElseClause>>,
675 pub loc: TokenLocation,
677}
678
679impl SourceLocation for IfClauseCommand {
680 fn location(&self) -> Option<TokenLocation> {
681 Some(self.loc.clone())
682 }
683}
684
685impl Display for IfClauseCommand {
686 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
687 writeln!(f, "if {}; then", self.condition)?;
688 write!(
689 indenter::indented(f).with_str(DISPLAY_INDENT),
690 "{}",
691 self.then
692 )?;
693 if let Some(elses) = &self.elses {
694 for else_clause in elses {
695 write!(f, "{else_clause}")?;
696 }
697 }
698
699 writeln!(f)?;
700 write!(f, "fi")?;
701
702 Ok(())
703 }
704}
705
706#[derive(Clone, Debug)]
708#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
709#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
710pub struct ElseClause {
711 #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
713 pub condition: Option<CompoundList>,
714 pub body: CompoundList,
716}
717
718impl Display for ElseClause {
719 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
720 writeln!(f)?;
721 if let Some(condition) = &self.condition {
722 writeln!(f, "elif {condition}; then")?;
723 } else {
724 writeln!(f, "else")?;
725 }
726
727 write!(
728 indenter::indented(f).with_str(DISPLAY_INDENT),
729 "{}",
730 self.body
731 )
732 }
733}
734
735#[derive(Clone, Debug)]
737#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
738#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
739pub struct CaseItem {
740 pub patterns: Vec<Word>,
742 pub cmd: Option<CompoundList>,
744 pub post_action: CaseItemPostAction,
746 pub loc: Option<TokenLocation>,
748}
749
750impl SourceLocation for CaseItem {
751 fn location(&self) -> Option<TokenLocation> {
752 self.loc.clone()
753 }
754}
755
756impl Display for CaseItem {
757 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
758 writeln!(f)?;
759 for (i, pattern) in self.patterns.iter().enumerate() {
760 if i > 0 {
761 write!(f, "|")?;
762 }
763 write!(f, "{pattern}")?;
764 }
765 writeln!(f, ")")?;
766
767 if let Some(cmd) = &self.cmd {
768 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{cmd}")?;
769 }
770 writeln!(f)?;
771 write!(f, "{}", self.post_action)
772 }
773}
774
775#[derive(Clone, Debug)]
777#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
778#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
779pub enum CaseItemPostAction {
780 ExitCase,
782 UnconditionallyExecuteNextCaseItem,
785 ContinueEvaluatingCases,
788}
789
790impl Display for CaseItemPostAction {
791 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
792 match self {
793 Self::ExitCase => write!(f, ";;"),
794 Self::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
795 Self::ContinueEvaluatingCases => write!(f, ";;&"),
796 }
797 }
798}
799
800#[derive(Clone, Debug)]
802#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
803#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
804pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand, pub TokenLocation);
805
806impl SourceLocation for WhileOrUntilClauseCommand {
807 fn location(&self) -> Option<TokenLocation> {
808 Some(self.2.clone())
809 }
810}
811
812impl Display for WhileOrUntilClauseCommand {
813 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
814 write!(f, "{}; {}", self.0, self.1)
815 }
816}
817
818#[derive(Clone, Debug)]
820#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
821#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
822pub struct FunctionDefinition {
823 pub fname: Word,
825 pub body: FunctionBody,
827 pub source: String,
829}
830
831impl SourceLocation for FunctionDefinition {
832 fn location(&self) -> Option<TokenLocation> {
833 let start = self.fname.location();
834 let end = self.body.location();
835
836 if let (Some(s), Some(e)) = (start, end) {
837 Some(TokenLocation::within(&s, &e))
838 } else {
839 None
840 }
841 }
842}
843
844impl Display for FunctionDefinition {
845 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
846 writeln!(f, "{} () ", self.fname.value)?;
847 write!(f, "{}", self.body)?;
848 Ok(())
849 }
850}
851
852#[derive(Clone, Debug)]
854#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
855#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
856pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
857
858impl SourceLocation for FunctionBody {
859 fn location(&self) -> Option<TokenLocation> {
860 let start = self.0.location();
861
862 let end = self.1.as_ref().and_then(SourceLocation::location);
863
864 if let (Some(s), Some(e)) = (start, end) {
865 Some(TokenLocation::within(&s, &e))
866 } else {
867 None
868 }
869 }
870}
871
872impl Display for FunctionBody {
873 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
874 write!(f, "{}", self.0)?;
875 if let Some(redirect_list) = &self.1 {
876 write!(f, "{redirect_list}")?;
877 }
878
879 Ok(())
880 }
881}
882
883#[derive(Clone, Debug)]
885#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
886#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
887pub struct BraceGroupCommand {
888 pub list: CompoundList,
890 pub loc: TokenLocation,
892}
893
894impl SourceLocation for BraceGroupCommand {
895 fn location(&self) -> Option<TokenLocation> {
896 Some(self.loc.clone())
897 }
898}
899
900impl Display for BraceGroupCommand {
901 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
902 writeln!(f, "{{ ")?;
903 write!(
904 indenter::indented(f).with_str(DISPLAY_INDENT),
905 "{}",
906 self.list
907 )?;
908 writeln!(f)?;
909 write!(f, "}}")?;
910
911 Ok(())
912 }
913}
914
915#[derive(Clone, Debug)]
917#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
918#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
919pub struct DoGroupCommand {
920 pub list: CompoundList,
922 pub loc: TokenLocation,
924}
925
926impl Display for DoGroupCommand {
927 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
928 writeln!(f, "do")?;
929 write!(
930 indenter::indented(f).with_str(DISPLAY_INDENT),
931 "{}",
932 self.list
933 )?;
934 writeln!(f)?;
935 write!(f, "done")
936 }
937}
938
939#[derive(Clone, Debug)]
941#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
942#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
943#[cfg_attr(test, serde(rename = "Simple"))]
944pub struct SimpleCommand {
945 #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
947 pub prefix: Option<CommandPrefix>,
948 #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
950 #[cfg_attr(test, serde(rename = "w"))]
951 pub word_or_name: Option<Word>,
952 #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
954 pub suffix: Option<CommandSuffix>,
955}
956
957impl SourceLocation for SimpleCommand {
958 fn location(&self) -> Option<TokenLocation> {
959 let mid = &self
960 .word_or_name
961 .as_ref()
962 .and_then(SourceLocation::location);
963 let start = self.prefix.as_ref().and_then(SourceLocation::location);
964 let end = self.suffix.as_ref().and_then(SourceLocation::location);
965
966 maybe_location(
967 start.as_ref().or(mid.as_ref()),
968 end.as_ref().or(mid.as_ref()),
969 )
970 }
971}
972
973impl Display for SimpleCommand {
974 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
975 let mut wrote_something = false;
976
977 if let Some(prefix) = &self.prefix {
978 if wrote_something {
979 write!(f, " ")?;
980 }
981
982 write!(f, "{prefix}")?;
983 wrote_something = true;
984 }
985
986 if let Some(word_or_name) = &self.word_or_name {
987 if wrote_something {
988 write!(f, " ")?;
989 }
990
991 write!(f, "{word_or_name}")?;
992 wrote_something = true;
993 }
994
995 if let Some(suffix) = &self.suffix {
996 if wrote_something {
997 write!(f, " ")?;
998 }
999
1000 write!(f, "{suffix}")?;
1001 }
1002
1003 Ok(())
1004 }
1005}
1006
1007#[derive(Clone, Debug, Default)]
1009#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1010#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1011#[cfg_attr(test, serde(rename = "Prefix"))]
1012pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
1013
1014impl SourceLocation for CommandPrefix {
1015 fn location(&self) -> Option<TokenLocation> {
1016 let start = self.0.first().and_then(SourceLocation::location);
1017 let end = self.0.last().and_then(SourceLocation::location);
1018
1019 maybe_location(start.as_ref(), end.as_ref())
1020 }
1021}
1022
1023impl Display for CommandPrefix {
1024 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1025 for (i, item) in self.0.iter().enumerate() {
1026 if i > 0 {
1027 write!(f, " ")?;
1028 }
1029
1030 write!(f, "{item}")?;
1031 }
1032 Ok(())
1033 }
1034}
1035
1036#[derive(Clone, Default, Debug)]
1038#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1039#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1040#[cfg_attr(test, serde(rename = "Suffix"))]
1041pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
1042
1043impl SourceLocation for CommandSuffix {
1044 fn location(&self) -> Option<TokenLocation> {
1045 let start = self.0.first().and_then(SourceLocation::location);
1046 let end = self.0.last().and_then(SourceLocation::location);
1047
1048 maybe_location(start.as_ref(), end.as_ref())
1049 }
1050}
1051
1052impl Display for CommandSuffix {
1053 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1054 for (i, item) in self.0.iter().enumerate() {
1055 if i > 0 {
1056 write!(f, " ")?;
1057 }
1058
1059 write!(f, "{item}")?;
1060 }
1061 Ok(())
1062 }
1063}
1064
1065#[derive(Clone, Debug)]
1067#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1068#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1069pub enum ProcessSubstitutionKind {
1070 Read,
1072 Write,
1074}
1075
1076impl Display for ProcessSubstitutionKind {
1077 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1078 match self {
1079 Self::Read => write!(f, "<"),
1080 Self::Write => write!(f, ">"),
1081 }
1082 }
1083}
1084
1085#[derive(Clone, Debug)]
1087#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1088#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1089pub enum CommandPrefixOrSuffixItem {
1090 IoRedirect(IoRedirect),
1092 Word(Word),
1094 #[cfg_attr(test, serde(rename = "Assign"))]
1096 AssignmentWord(Assignment, Word),
1097 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1099}
1100
1101impl SourceLocation for CommandPrefixOrSuffixItem {
1103 fn location(&self) -> Option<TokenLocation> {
1104 match self {
1105 Self::Word(w) => w.location(),
1106 _ => None,
1107 }
1108 }
1109}
1110
1111impl Display for CommandPrefixOrSuffixItem {
1112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1113 match self {
1114 Self::IoRedirect(io_redirect) => write!(f, "{io_redirect}"),
1115 Self::Word(word) => write!(f, "{word}"),
1116 Self::AssignmentWord(_assignment, word) => write!(f, "{word}"),
1117 Self::ProcessSubstitution(kind, subshell_command) => {
1118 write!(f, "{kind}({subshell_command})")
1119 }
1120 }
1121 }
1122}
1123
1124#[derive(Clone, Debug)]
1126#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1127#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1128#[cfg_attr(test, serde(rename = "Assign"))]
1129pub struct Assignment {
1130 pub name: AssignmentName,
1132 pub value: AssignmentValue,
1134 #[cfg_attr(test, serde(skip_serializing_if = "<&bool as std::ops::Not>::not"))]
1136 pub append: bool,
1137 pub loc: TokenLocation,
1139}
1140
1141impl SourceLocation for Assignment {
1142 fn location(&self) -> Option<TokenLocation> {
1143 Some(self.loc.clone())
1144 }
1145}
1146
1147impl Display for Assignment {
1148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1149 write!(f, "{}", self.name)?;
1150 if self.append {
1151 write!(f, "+")?;
1152 }
1153 write!(f, "={}", self.value)
1154 }
1155}
1156
1157#[derive(Clone, Debug)]
1159#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1160#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1161pub enum AssignmentName {
1162 #[cfg_attr(test, serde(rename = "Var"))]
1164 VariableName(String),
1165 ArrayElementName(String, String),
1167}
1168
1169impl Display for AssignmentName {
1170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1171 match self {
1172 Self::VariableName(name) => write!(f, "{name}"),
1173 Self::ArrayElementName(name, index) => {
1174 write!(f, "{name}[{index}]")
1175 }
1176 }
1177 }
1178}
1179
1180#[derive(Clone, Debug)]
1182#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1183#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1184pub enum AssignmentValue {
1185 Scalar(Word),
1187 Array(Vec<(Option<Word>, Word)>),
1189}
1190
1191impl SourceLocation for AssignmentValue {
1193 fn location(&self) -> Option<TokenLocation> {
1194 None
1195 }
1196}
1197
1198impl Display for AssignmentValue {
1199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1200 match self {
1201 Self::Scalar(word) => write!(f, "{word}"),
1202 Self::Array(words) => {
1203 write!(f, "(")?;
1204 for (i, value) in words.iter().enumerate() {
1205 if i > 0 {
1206 write!(f, " ")?;
1207 }
1208 match value {
1209 (Some(key), value) => write!(f, "[{key}]={value}")?,
1210 (None, value) => write!(f, "{value}")?,
1211 }
1212 }
1213 write!(f, ")")
1214 }
1215 }
1216 }
1217}
1218
1219#[derive(Clone, Debug)]
1221#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1222#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1223pub struct RedirectList(pub Vec<IoRedirect>);
1224
1225impl SourceLocation for RedirectList {
1227 fn location(&self) -> Option<TokenLocation> {
1228 None
1229 }
1230}
1231
1232impl Display for RedirectList {
1233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1234 for item in &self.0 {
1235 write!(f, "{item}")?;
1236 }
1237 Ok(())
1238 }
1239}
1240
1241pub type IoFd = i32;
1243
1244#[derive(Clone, Debug)]
1246#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1247#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1248pub enum IoRedirect {
1249 File(Option<IoFd>, IoFileRedirectKind, IoFileRedirectTarget),
1251 HereDocument(Option<IoFd>, IoHereDocument),
1253 HereString(Option<IoFd>, Word),
1255 OutputAndError(Word, bool),
1257}
1258
1259impl SourceLocation for IoRedirect {
1261 fn location(&self) -> Option<TokenLocation> {
1262 None
1263 }
1264}
1265
1266impl Display for IoRedirect {
1267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1268 match self {
1269 Self::File(fd_num, kind, target) => {
1270 if let Some(fd_num) = fd_num {
1271 write!(f, "{fd_num}")?;
1272 }
1273
1274 write!(f, "{kind} {target}")?;
1275 }
1276 Self::OutputAndError(target, append) => {
1277 write!(f, "&>")?;
1278 if *append {
1279 write!(f, ">")?;
1280 }
1281 write!(f, " {target}")?;
1282 }
1283 Self::HereDocument(
1284 fd_num,
1285 IoHereDocument {
1286 remove_tabs,
1287 here_end,
1288 doc,
1289 ..
1290 },
1291 ) => {
1292 if let Some(fd_num) = fd_num {
1293 write!(f, "{fd_num}")?;
1294 }
1295
1296 write!(f, "<<")?;
1297 if *remove_tabs {
1298 write!(f, "-")?;
1299 }
1300
1301 writeln!(f, "{here_end}")?;
1302
1303 write!(f, "{doc}")?;
1304 writeln!(f, "{here_end}")?;
1305 }
1306 Self::HereString(fd_num, s) => {
1307 if let Some(fd_num) = fd_num {
1308 write!(f, "{fd_num}")?;
1309 }
1310
1311 write!(f, "<<< {s}")?;
1312 }
1313 }
1314
1315 Ok(())
1316 }
1317}
1318
1319#[derive(Clone, Debug)]
1321#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1322#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1323pub enum IoFileRedirectKind {
1324 Read,
1326 Write,
1328 Append,
1330 ReadAndWrite,
1332 Clobber,
1334 DuplicateInput,
1336 DuplicateOutput,
1338}
1339
1340impl Display for IoFileRedirectKind {
1341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1342 match self {
1343 Self::Read => write!(f, "<"),
1344 Self::Write => write!(f, ">"),
1345 Self::Append => write!(f, ">>"),
1346 Self::ReadAndWrite => write!(f, "<>"),
1347 Self::Clobber => write!(f, ">|"),
1348 Self::DuplicateInput => write!(f, "<&"),
1349 Self::DuplicateOutput => write!(f, ">&"),
1350 }
1351 }
1352}
1353
1354#[derive(Clone, Debug)]
1356#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1357#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1358pub enum IoFileRedirectTarget {
1359 Filename(Word),
1361 Fd(IoFd),
1363 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1366 Duplicate(Word),
1370}
1371
1372impl Display for IoFileRedirectTarget {
1373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1374 match self {
1375 Self::Filename(word) => write!(f, "{word}"),
1376 Self::Fd(fd) => write!(f, "{fd}"),
1377 Self::ProcessSubstitution(kind, subshell_command) => {
1378 write!(f, "{kind}{subshell_command}")
1379 }
1380 Self::Duplicate(word) => write!(f, "{word}"),
1381 }
1382 }
1383}
1384
1385#[derive(Clone, Debug)]
1387#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1388#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1389pub struct IoHereDocument {
1390 #[cfg_attr(test, serde(skip_serializing_if = "<&bool as std::ops::Not>::not"))]
1392 pub remove_tabs: bool,
1393 #[cfg_attr(test, serde(skip_serializing_if = "<&bool as std::ops::Not>::not"))]
1395 pub requires_expansion: bool,
1396 pub here_end: Word,
1398 pub doc: Word,
1400}
1401
1402impl SourceLocation for IoHereDocument {
1404 fn location(&self) -> Option<TokenLocation> {
1405 None
1406 }
1407}
1408
1409#[derive(Clone, Debug)]
1411#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1412pub enum TestExpr {
1413 False,
1415 Literal(String),
1417 And(Box<Self>, Box<Self>),
1419 Or(Box<Self>, Box<Self>),
1421 Not(Box<Self>),
1423 Parenthesized(Box<Self>),
1425 UnaryTest(UnaryPredicate, String),
1427 BinaryTest(BinaryPredicate, String, String),
1429}
1430
1431impl SourceLocation for TestExpr {
1433 fn location(&self) -> Option<TokenLocation> {
1434 None
1435 }
1436}
1437
1438impl Display for TestExpr {
1439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1440 match self {
1441 Self::False => Ok(()),
1442 Self::Literal(s) => write!(f, "{s}"),
1443 Self::And(left, right) => write!(f, "{left} -a {right}"),
1444 Self::Or(left, right) => write!(f, "{left} -o {right}"),
1445 Self::Not(expr) => write!(f, "! {expr}"),
1446 Self::Parenthesized(expr) => write!(f, "( {expr} )"),
1447 Self::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
1448 Self::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
1449 }
1450 }
1451}
1452
1453#[derive(Clone, Debug)]
1455#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1456#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1457pub enum ExtendedTestExpr {
1458 And(Box<Self>, Box<Self>),
1460 Or(Box<Self>, Box<Self>),
1462 Not(Box<Self>),
1464 Parenthesized(Box<Self>),
1466 UnaryTest(UnaryPredicate, Word),
1468 BinaryTest(BinaryPredicate, Word, Word),
1470}
1471
1472impl Display for ExtendedTestExpr {
1473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1474 match self {
1475 Self::And(left, right) => {
1476 write!(f, "{left} && {right}")
1477 }
1478 Self::Or(left, right) => {
1479 write!(f, "{left} || {right}")
1480 }
1481 Self::Not(expr) => {
1482 write!(f, "! {expr}")
1483 }
1484 Self::Parenthesized(expr) => {
1485 write!(f, "( {expr} )")
1486 }
1487 Self::UnaryTest(pred, word) => {
1488 write!(f, "{pred} {word}")
1489 }
1490 Self::BinaryTest(pred, left, right) => {
1491 write!(f, "{left} {pred} {right}")
1492 }
1493 }
1494 }
1495}
1496
1497#[derive(Clone, Debug)]
1499#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1500#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1501pub struct ExtendedTestExprCommand {
1502 pub expr: ExtendedTestExpr,
1504 pub loc: TokenLocation,
1506}
1507
1508impl SourceLocation for ExtendedTestExprCommand {
1509 fn location(&self) -> Option<TokenLocation> {
1510 Some(self.loc.clone())
1511 }
1512}
1513
1514impl Display for ExtendedTestExprCommand {
1515 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1516 self.expr.fmt(f)
1517 }
1518}
1519
1520#[derive(Clone, Debug)]
1522#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1523#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1524pub enum UnaryPredicate {
1525 FileExists,
1527 FileExistsAndIsBlockSpecialFile,
1529 FileExistsAndIsCharSpecialFile,
1531 FileExistsAndIsDir,
1533 FileExistsAndIsRegularFile,
1535 FileExistsAndIsSetgid,
1537 FileExistsAndIsSymlink,
1539 FileExistsAndHasStickyBit,
1541 FileExistsAndIsFifo,
1543 FileExistsAndIsReadable,
1545 FileExistsAndIsNotZeroLength,
1547 FdIsOpenTerminal,
1549 FileExistsAndIsSetuid,
1551 FileExistsAndIsWritable,
1553 FileExistsAndIsExecutable,
1555 FileExistsAndOwnedByEffectiveGroupId,
1558 FileExistsAndModifiedSinceLastRead,
1561 FileExistsAndOwnedByEffectiveUserId,
1564 FileExistsAndIsSocket,
1566 ShellOptionEnabled,
1568 ShellVariableIsSetAndAssigned,
1570 ShellVariableIsSetAndNameRef,
1572 StringHasZeroLength,
1574 StringHasNonZeroLength,
1576}
1577
1578impl Display for UnaryPredicate {
1579 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1580 match self {
1581 Self::FileExists => write!(f, "-e"),
1582 Self::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
1583 Self::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
1584 Self::FileExistsAndIsDir => write!(f, "-d"),
1585 Self::FileExistsAndIsRegularFile => write!(f, "-f"),
1586 Self::FileExistsAndIsSetgid => write!(f, "-g"),
1587 Self::FileExistsAndIsSymlink => write!(f, "-h"),
1588 Self::FileExistsAndHasStickyBit => write!(f, "-k"),
1589 Self::FileExistsAndIsFifo => write!(f, "-p"),
1590 Self::FileExistsAndIsReadable => write!(f, "-r"),
1591 Self::FileExistsAndIsNotZeroLength => write!(f, "-s"),
1592 Self::FdIsOpenTerminal => write!(f, "-t"),
1593 Self::FileExistsAndIsSetuid => write!(f, "-u"),
1594 Self::FileExistsAndIsWritable => write!(f, "-w"),
1595 Self::FileExistsAndIsExecutable => write!(f, "-x"),
1596 Self::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
1597 Self::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
1598 Self::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
1599 Self::FileExistsAndIsSocket => write!(f, "-S"),
1600 Self::ShellOptionEnabled => write!(f, "-o"),
1601 Self::ShellVariableIsSetAndAssigned => write!(f, "-v"),
1602 Self::ShellVariableIsSetAndNameRef => write!(f, "-R"),
1603 Self::StringHasZeroLength => write!(f, "-z"),
1604 Self::StringHasNonZeroLength => write!(f, "-n"),
1605 }
1606 }
1607}
1608
1609#[derive(Clone, Debug)]
1611#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1612#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1613pub enum BinaryPredicate {
1614 FilesReferToSameDeviceAndInodeNumbers,
1616 LeftFileIsNewerOrExistsWhenRightDoesNot,
1618 LeftFileIsOlderOrDoesNotExistWhenRightDoes,
1620 StringExactlyMatchesPattern,
1622 StringDoesNotExactlyMatchPattern,
1624 StringMatchesRegex,
1626 StringExactlyMatchesString,
1628 StringDoesNotExactlyMatchString,
1630 StringContainsSubstring,
1632 LeftSortsBeforeRight,
1634 LeftSortsAfterRight,
1636 ArithmeticEqualTo,
1638 ArithmeticNotEqualTo,
1640 ArithmeticLessThan,
1642 ArithmeticLessThanOrEqualTo,
1644 ArithmeticGreaterThan,
1646 ArithmeticGreaterThanOrEqualTo,
1648}
1649
1650impl Display for BinaryPredicate {
1651 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1652 match self {
1653 Self::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
1654 Self::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
1655 Self::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
1656 Self::StringExactlyMatchesPattern => write!(f, "=="),
1657 Self::StringDoesNotExactlyMatchPattern => write!(f, "!="),
1658 Self::StringMatchesRegex => write!(f, "=~"),
1659 Self::StringContainsSubstring => write!(f, "=~"),
1660 Self::StringExactlyMatchesString => write!(f, "=="),
1661 Self::StringDoesNotExactlyMatchString => write!(f, "!="),
1662 Self::LeftSortsBeforeRight => write!(f, "<"),
1663 Self::LeftSortsAfterRight => write!(f, ">"),
1664 Self::ArithmeticEqualTo => write!(f, "-eq"),
1665 Self::ArithmeticNotEqualTo => write!(f, "-ne"),
1666 Self::ArithmeticLessThan => write!(f, "-lt"),
1667 Self::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
1668 Self::ArithmeticGreaterThan => write!(f, "-gt"),
1669 Self::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
1670 }
1671 }
1672}
1673
1674#[derive(Clone, Debug)]
1676#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1677#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1678#[cfg_attr(test, serde(rename = "W"))]
1679pub struct Word {
1680 #[cfg_attr(test, serde(rename = "v"))]
1682 pub value: String,
1683 pub loc: Option<TokenLocation>,
1685}
1686
1687impl SourceLocation for Word {
1688 fn location(&self) -> Option<TokenLocation> {
1689 self.loc.clone()
1690 }
1691}
1692
1693impl Display for Word {
1694 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1695 write!(f, "{}", self.value)
1696 }
1697}
1698
1699impl From<&tokenizer::Token> for Word {
1700 fn from(t: &tokenizer::Token) -> Self {
1701 match t {
1702 tokenizer::Token::Word(value, loc) => Self {
1703 value: value.clone(),
1704 loc: Some(loc.clone()),
1705 },
1706 tokenizer::Token::Operator(value, loc) => Self {
1707 value: value.clone(),
1708 loc: Some(loc.clone()),
1709 },
1710 }
1711 }
1712}
1713
1714impl From<String> for Word {
1715 fn from(s: String) -> Self {
1716 Self {
1717 value: s,
1718 loc: None,
1719 }
1720 }
1721}
1722
1723impl Word {
1724 pub fn new(s: &str) -> Self {
1726 Self {
1727 value: s.to_owned(),
1728 loc: None,
1729 }
1730 }
1731
1732 pub fn with_location(s: &str, loc: &TokenLocation) -> Self {
1734 Self {
1735 value: s.to_owned(),
1736 loc: Some(loc.to_owned()),
1737 }
1738 }
1739
1740 pub fn flatten(&self) -> String {
1742 self.value.clone()
1743 }
1744}
1745
1746#[derive(Clone, Debug)]
1748#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1749#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1750pub struct UnexpandedArithmeticExpr {
1751 pub value: String,
1753}
1754
1755impl Display for UnexpandedArithmeticExpr {
1756 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1757 write!(f, "{}", self.value)
1758 }
1759}
1760
1761#[derive(Clone, Debug)]
1763#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1764pub enum ArithmeticExpr {
1765 Literal(i64),
1767 Reference(ArithmeticTarget),
1769 UnaryOp(UnaryOperator, Box<Self>),
1771 BinaryOp(BinaryOperator, Box<Self>, Box<Self>),
1773 Conditional(Box<Self>, Box<Self>, Box<Self>),
1775 Assignment(ArithmeticTarget, Box<Self>),
1777 BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<Self>),
1779 UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
1781}
1782
1783impl SourceLocation for ArithmeticExpr {
1785 fn location(&self) -> Option<TokenLocation> {
1786 None
1787 }
1788}
1789
1790#[cfg(feature = "fuzz-testing")]
1791impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
1792 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1793 let variant = u.choose(&[
1794 "Literal",
1795 "Reference",
1796 "UnaryOp",
1797 "BinaryOp",
1798 "Conditional",
1799 "Assignment",
1800 "BinaryAssignment",
1801 "UnaryAssignment",
1802 ])?;
1803
1804 match *variant {
1805 "Literal" => Ok(Self::Literal(i64::arbitrary(u)?)),
1806 "Reference" => Ok(Self::Reference(ArithmeticTarget::arbitrary(u)?)),
1807 "UnaryOp" => Ok(Self::UnaryOp(
1808 UnaryOperator::arbitrary(u)?,
1809 Box::new(Self::arbitrary(u)?),
1810 )),
1811 "BinaryOp" => Ok(Self::BinaryOp(
1812 BinaryOperator::arbitrary(u)?,
1813 Box::new(Self::arbitrary(u)?),
1814 Box::new(Self::arbitrary(u)?),
1815 )),
1816 "Conditional" => Ok(Self::Conditional(
1817 Box::new(Self::arbitrary(u)?),
1818 Box::new(Self::arbitrary(u)?),
1819 Box::new(Self::arbitrary(u)?),
1820 )),
1821 "Assignment" => Ok(Self::Assignment(
1822 ArithmeticTarget::arbitrary(u)?,
1823 Box::new(Self::arbitrary(u)?),
1824 )),
1825 "BinaryAssignment" => Ok(Self::BinaryAssignment(
1826 BinaryOperator::arbitrary(u)?,
1827 ArithmeticTarget::arbitrary(u)?,
1828 Box::new(Self::arbitrary(u)?),
1829 )),
1830 "UnaryAssignment" => Ok(Self::UnaryAssignment(
1831 UnaryAssignmentOperator::arbitrary(u)?,
1832 ArithmeticTarget::arbitrary(u)?,
1833 )),
1834 _ => unreachable!(),
1835 }
1836 }
1837}
1838
1839impl Display for ArithmeticExpr {
1840 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1841 match self {
1842 Self::Literal(literal) => write!(f, "{literal}"),
1843 Self::Reference(target) => write!(f, "{target}"),
1844 Self::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
1845 Self::BinaryOp(op, left, right) => {
1846 if matches!(op, BinaryOperator::Comma) {
1847 write!(f, "{left}{op} {right}")
1848 } else {
1849 write!(f, "{left} {op} {right}")
1850 }
1851 }
1852 Self::Conditional(condition, if_branch, else_branch) => {
1853 write!(f, "{condition} ? {if_branch} : {else_branch}")
1854 }
1855 Self::Assignment(target, value) => write!(f, "{target} = {value}"),
1856 Self::BinaryAssignment(op, target, operand) => {
1857 write!(f, "{target} {op}= {operand}")
1858 }
1859 Self::UnaryAssignment(op, target) => match op {
1860 UnaryAssignmentOperator::PrefixIncrement
1861 | UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
1862 UnaryAssignmentOperator::PostfixIncrement
1863 | UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
1864 },
1865 }
1866 }
1867}
1868
1869#[derive(Clone, Copy, Debug)]
1871#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1872#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1873pub enum BinaryOperator {
1874 Power,
1876 Multiply,
1878 Divide,
1880 Modulo,
1882 Comma,
1884 Add,
1886 Subtract,
1888 ShiftLeft,
1890 ShiftRight,
1892 LessThan,
1894 LessThanOrEqualTo,
1896 GreaterThan,
1898 GreaterThanOrEqualTo,
1900 Equals,
1902 NotEquals,
1904 BitwiseAnd,
1906 BitwiseXor,
1908 BitwiseOr,
1910 LogicalAnd,
1912 LogicalOr,
1914}
1915
1916impl Display for BinaryOperator {
1917 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1918 match self {
1919 Self::Power => write!(f, "**"),
1920 Self::Multiply => write!(f, "*"),
1921 Self::Divide => write!(f, "/"),
1922 Self::Modulo => write!(f, "%"),
1923 Self::Comma => write!(f, ","),
1924 Self::Add => write!(f, "+"),
1925 Self::Subtract => write!(f, "-"),
1926 Self::ShiftLeft => write!(f, "<<"),
1927 Self::ShiftRight => write!(f, ">>"),
1928 Self::LessThan => write!(f, "<"),
1929 Self::LessThanOrEqualTo => write!(f, "<="),
1930 Self::GreaterThan => write!(f, ">"),
1931 Self::GreaterThanOrEqualTo => write!(f, ">="),
1932 Self::Equals => write!(f, "=="),
1933 Self::NotEquals => write!(f, "!="),
1934 Self::BitwiseAnd => write!(f, "&"),
1935 Self::BitwiseXor => write!(f, "^"),
1936 Self::BitwiseOr => write!(f, "|"),
1937 Self::LogicalAnd => write!(f, "&&"),
1938 Self::LogicalOr => write!(f, "||"),
1939 }
1940 }
1941}
1942
1943#[derive(Clone, Copy, Debug)]
1945#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1946#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1947pub enum UnaryOperator {
1948 UnaryPlus,
1950 UnaryMinus,
1952 BitwiseNot,
1954 LogicalNot,
1956}
1957
1958impl Display for UnaryOperator {
1959 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1960 match self {
1961 Self::UnaryPlus => write!(f, "+"),
1962 Self::UnaryMinus => write!(f, "-"),
1963 Self::BitwiseNot => write!(f, "~"),
1964 Self::LogicalNot => write!(f, "!"),
1965 }
1966 }
1967}
1968
1969#[derive(Clone, Copy, Debug)]
1971#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1972#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1973pub enum UnaryAssignmentOperator {
1974 PrefixIncrement,
1976 PrefixDecrement,
1978 PostfixIncrement,
1980 PostfixDecrement,
1982}
1983
1984impl Display for UnaryAssignmentOperator {
1985 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1986 match self {
1987 Self::PrefixIncrement => write!(f, "++"),
1988 Self::PrefixDecrement => write!(f, "--"),
1989 Self::PostfixIncrement => write!(f, "++"),
1990 Self::PostfixDecrement => write!(f, "--"),
1991 }
1992 }
1993}
1994
1995#[derive(Clone, Debug)]
1997#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1998#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1999pub enum ArithmeticTarget {
2000 Variable(String),
2002 ArrayElement(String, Box<ArithmeticExpr>),
2004}
2005
2006impl SourceLocation for ArithmeticTarget {
2008 fn location(&self) -> Option<TokenLocation> {
2009 None
2010 }
2011}
2012
2013impl Display for ArithmeticTarget {
2014 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2015 match self {
2016 Self::Variable(name) => write!(f, "{name}"),
2017 Self::ArrayElement(name, index) => write!(f, "{name}[{index}]"),
2018 }
2019 }
2020}