brush_parser/
ast.rs

1//! Defines the Abstract Syntax Tree (ast) for shell programs. Includes types and utilities
2//! for manipulating the AST.
3
4use std::fmt::{Display, Write};
5
6use crate::tokenizer;
7
8const DISPLAY_INDENT: &str = "    ";
9
10/// Represents a complete shell program.
11#[derive(Clone, Debug)]
12#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
13#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
14pub struct Program {
15    /// A sequence of complete shell commands.
16    #[cfg_attr(test, serde(rename = "cmds"))]
17    pub complete_commands: Vec<CompleteCommand>,
18}
19
20impl Display for Program {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        for complete_command in &self.complete_commands {
23            write!(f, "{complete_command}")?;
24        }
25        Ok(())
26    }
27}
28
29/// Represents a complete shell command.
30pub type CompleteCommand = CompoundList;
31
32/// Represents a complete shell command item.
33pub type CompleteCommandItem = CompoundListItem;
34
35/// Indicates whether the preceding command is executed synchronously or asynchronously.
36#[derive(Clone, Debug)]
37#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
38#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
39pub enum SeparatorOperator {
40    /// The preceding command is executed asynchronously.
41    Async,
42    /// The preceding command is executed synchronously.
43    Sequence,
44}
45
46impl Display for SeparatorOperator {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            Self::Async => write!(f, "&"),
50            Self::Sequence => write!(f, ";"),
51        }
52    }
53}
54
55/// Represents a sequence of command pipelines connected by boolean operators.
56#[derive(Clone, Debug)]
57#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
58#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
59#[cfg_attr(test, serde(rename = "AndOr"))]
60pub struct AndOrList {
61    /// The first command pipeline.
62    pub first: Pipeline,
63    /// Any additional command pipelines, in sequence order.
64    #[cfg_attr(test, serde(skip_serializing_if = "Vec::is_empty"))]
65    pub additional: Vec<AndOr>,
66}
67
68impl Display for AndOrList {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        write!(f, "{}", self.first)?;
71        for item in &self.additional {
72            write!(f, "{item}")?;
73        }
74
75        Ok(())
76    }
77}
78
79/// Represents a boolean operator used to connect command pipelines in an [`AndOrList`]
80#[derive(PartialEq, Eq)]
81pub enum PipelineOperator {
82    /// The command pipelines are connected by a boolean AND operator.
83    And,
84    /// The command pipelines are connected by a boolean OR operator.
85    Or,
86}
87
88impl PartialEq<AndOr> for PipelineOperator {
89    fn eq(&self, other: &AndOr) -> bool {
90        matches!(
91            (self, other),
92            (Self::And, AndOr::And(_)) | (Self::Or, AndOr::Or(_))
93        )
94    }
95}
96
97// We cannot losslessly convert into `AndOr`, hence we can only do `Into`.
98#[expect(clippy::from_over_into)]
99impl Into<PipelineOperator> for AndOr {
100    fn into(self) -> PipelineOperator {
101        match self {
102            Self::And(_) => PipelineOperator::And,
103            Self::Or(_) => PipelineOperator::Or,
104        }
105    }
106}
107
108/// An iterator over the pipelines in an [`AndOrList`].
109pub struct AndOrListIter<'a> {
110    first: Option<&'a Pipeline>,
111    additional_iter: std::slice::Iter<'a, AndOr>,
112}
113
114impl<'a> Iterator for AndOrListIter<'a> {
115    type Item = (PipelineOperator, &'a Pipeline);
116
117    fn next(&mut self) -> Option<Self::Item> {
118        if let Some(first) = self.first.take() {
119            Some((PipelineOperator::And, first))
120        } else {
121            self.additional_iter.next().map(|and_or| match and_or {
122                AndOr::And(pipeline) => (PipelineOperator::And, pipeline),
123                AndOr::Or(pipeline) => (PipelineOperator::Or, pipeline),
124            })
125        }
126    }
127}
128
129impl<'a> IntoIterator for &'a AndOrList {
130    type Item = (PipelineOperator, &'a Pipeline);
131    type IntoIter = AndOrListIter<'a>;
132
133    fn into_iter(self) -> Self::IntoIter {
134        AndOrListIter {
135            first: Some(&self.first),
136            additional_iter: self.additional.iter(),
137        }
138    }
139}
140
141impl<'a> From<(PipelineOperator, &'a Pipeline)> for AndOr {
142    fn from(value: (PipelineOperator, &'a Pipeline)) -> Self {
143        match value.0 {
144            PipelineOperator::Or => Self::Or(value.1.to_owned()),
145            PipelineOperator::And => Self::And(value.1.to_owned()),
146        }
147    }
148}
149
150impl AndOrList {
151    /// Returns an iterator over the pipelines in this `AndOrList`.
152    pub fn iter(&self) -> AndOrListIter<'_> {
153        self.into_iter()
154    }
155}
156
157/// Represents a boolean operator used to connect command pipelines, along with the
158/// succeeding pipeline.
159#[derive(Clone, Debug)]
160#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
161#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
162pub enum AndOr {
163    /// Boolean AND operator; the embedded pipeline is only to be executed if the
164    /// preceding command has succeeded.
165    And(Pipeline),
166    /// Boolean OR operator; the embedded pipeline is only to be executed if the
167    /// preceding command has not succeeded.
168    Or(Pipeline),
169}
170
171impl Display for AndOr {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        match self {
174            Self::And(pipeline) => write!(f, " && {pipeline}"),
175            Self::Or(pipeline) => write!(f, " || {pipeline}"),
176        }
177    }
178}
179
180/// The type of timing requested for a pipeline.
181#[derive(Clone, Debug)]
182#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
183#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
184pub enum PipelineTimed {
185    /// The pipeline should be timed with bash-like output.
186    Timed,
187    /// The pipeline should be timed with POSIX-like output.
188    TimedWithPosixOutput,
189}
190
191/// A pipeline of commands, where each command's output is passed as standard input
192/// to the command that follows it.
193#[derive(Clone, Debug)]
194#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
195#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
196pub struct Pipeline {
197    /// Indicates whether the pipeline's execution should be timed with reported
198    /// timings in output.
199    #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
200    pub timed: Option<PipelineTimed>,
201    /// Indicates whether the result of the overall pipeline should be the logical
202    /// negation of the result of the pipeline.
203    #[cfg_attr(test, serde(skip_serializing_if = "<&bool as std::ops::Not>::not"))]
204    pub bang: bool,
205    /// The sequence of commands in the pipeline.
206    pub seq: Vec<Command>,
207}
208
209impl Display for Pipeline {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        if self.bang {
212            write!(f, "!")?;
213        }
214        for (i, command) in self.seq.iter().enumerate() {
215            if i > 0 {
216                write!(f, " |")?;
217            }
218            write!(f, "{command}")?;
219        }
220
221        Ok(())
222    }
223}
224
225/// Represents a shell command.
226#[derive(Clone, Debug)]
227#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
228#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
229pub enum Command {
230    /// A simple command, directly invoking an external command, a built-in command,
231    /// a shell function, or similar.
232    Simple(SimpleCommand),
233    /// A compound command, composed of multiple commands.
234    Compound(CompoundCommand, Option<RedirectList>),
235    /// A command whose side effect is to define a shell function.
236    Function(FunctionDefinition),
237    /// A command that evaluates an extended test expression.
238    ExtendedTest(ExtendedTestExpr),
239}
240
241impl Display for Command {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        match self {
244            Self::Simple(simple_command) => write!(f, "{simple_command}"),
245            Self::Compound(compound_command, redirect_list) => {
246                write!(f, "{compound_command}")?;
247                if let Some(redirect_list) = redirect_list {
248                    write!(f, "{redirect_list}")?;
249                }
250                Ok(())
251            }
252            Self::Function(function_definition) => write!(f, "{function_definition}"),
253            Self::ExtendedTest(extended_test_expr) => {
254                write!(f, "[[ {extended_test_expr} ]]")
255            }
256        }
257    }
258}
259
260/// Represents a compound command, potentially made up of multiple nested commands.
261#[derive(Clone, Debug)]
262#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
263#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
264pub enum CompoundCommand {
265    /// An arithmetic command, evaluating an arithmetic expression.
266    Arithmetic(ArithmeticCommand),
267    /// An arithmetic for clause, which loops until an arithmetic condition is reached.
268    ArithmeticForClause(ArithmeticForClauseCommand),
269    /// A brace group, which groups commands together.
270    BraceGroup(BraceGroupCommand),
271    /// A subshell, which executes commands in a subshell.
272    Subshell(SubshellCommand),
273    /// A for clause, which loops over a set of values.
274    ForClause(ForClauseCommand),
275    /// A case clause, which selects a command based on a value and a set of
276    /// pattern-based filters.
277    CaseClause(CaseClauseCommand),
278    /// An if clause, which conditionally executes a command.
279    IfClause(IfClauseCommand),
280    /// A while clause, which loops while a condition is met.
281    WhileClause(WhileOrUntilClauseCommand),
282    /// An until clause, which loops until a condition is met.
283    UntilClause(WhileOrUntilClauseCommand),
284}
285
286impl Display for CompoundCommand {
287    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288        match self {
289            Self::Arithmetic(arithmetic_command) => write!(f, "{arithmetic_command}"),
290            Self::ArithmeticForClause(arithmetic_for_clause_command) => {
291                write!(f, "{arithmetic_for_clause_command}")
292            }
293            Self::BraceGroup(brace_group_command) => {
294                write!(f, "{brace_group_command}")
295            }
296            Self::Subshell(subshell_command) => write!(f, "{subshell_command}"),
297            Self::ForClause(for_clause_command) => write!(f, "{for_clause_command}"),
298            Self::CaseClause(case_clause_command) => {
299                write!(f, "{case_clause_command}")
300            }
301            Self::IfClause(if_clause_command) => write!(f, "{if_clause_command}"),
302            Self::WhileClause(while_or_until_clause_command) => {
303                write!(f, "while {while_or_until_clause_command}")
304            }
305            Self::UntilClause(while_or_until_clause_command) => {
306                write!(f, "until {while_or_until_clause_command}")
307            }
308        }
309    }
310}
311
312/// An arithmetic command, evaluating an arithmetic expression.
313#[derive(Clone, Debug)]
314#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
315#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
316pub struct ArithmeticCommand {
317    /// The raw, unparsed and unexpanded arithmetic expression.
318    pub expr: UnexpandedArithmeticExpr,
319}
320
321impl Display for ArithmeticCommand {
322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        write!(f, "(({}))", self.expr)
324    }
325}
326
327/// A subshell, which executes commands in a subshell.
328#[derive(Clone, Debug)]
329#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
330#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
331pub struct SubshellCommand(pub CompoundList);
332
333impl Display for SubshellCommand {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        write!(f, "( ")?;
336        write!(f, "{}", self.0)?;
337        write!(f, " )")
338    }
339}
340
341/// A for clause, which loops over a set of values.
342#[derive(Clone, Debug)]
343#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
344#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
345pub struct ForClauseCommand {
346    /// The name of the iterator variable.
347    pub variable_name: String,
348    /// The values being iterated over.
349    pub values: Option<Vec<Word>>,
350    /// The command to run for each iteration of the loop.
351    pub body: DoGroupCommand,
352}
353
354impl Display for ForClauseCommand {
355    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356        write!(f, "for {} in ", self.variable_name)?;
357
358        if let Some(values) = &self.values {
359            for (i, value) in values.iter().enumerate() {
360                if i > 0 {
361                    write!(f, " ")?;
362                }
363
364                write!(f, "{value}")?;
365            }
366        }
367
368        writeln!(f, ";")?;
369
370        write!(f, "{}", self.body)
371    }
372}
373
374/// An arithmetic for clause, which loops until an arithmetic condition is reached.
375#[derive(Clone, Debug)]
376#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
377#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
378pub struct ArithmeticForClauseCommand {
379    /// Optionally, the initializer expression evaluated before the first iteration of the loop.
380    pub initializer: Option<UnexpandedArithmeticExpr>,
381    /// Optionally, the expression evaluated as the exit condition of the loop.
382    pub condition: Option<UnexpandedArithmeticExpr>,
383    /// Optionally, the expression evaluated after each iteration of the loop.
384    pub updater: Option<UnexpandedArithmeticExpr>,
385    /// The command to run for each iteration of the loop.
386    pub body: DoGroupCommand,
387}
388
389impl Display for ArithmeticForClauseCommand {
390    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391        write!(f, "for ((")?;
392
393        if let Some(initializer) = &self.initializer {
394            write!(f, "{initializer}")?;
395        }
396
397        write!(f, "; ")?;
398
399        if let Some(condition) = &self.condition {
400            write!(f, "{condition}")?;
401        }
402
403        write!(f, "; ")?;
404
405        if let Some(updater) = &self.updater {
406            write!(f, "{updater}")?;
407        }
408
409        writeln!(f, "))")?;
410
411        write!(f, "{}", self.body)
412    }
413}
414
415/// A case clause, which selects a command based on a value and a set of
416/// pattern-based filters.
417#[derive(Clone, Debug)]
418#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
419#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
420pub struct CaseClauseCommand {
421    /// The value being matched on.
422    pub value: Word,
423    /// The individual case branches.
424    pub cases: Vec<CaseItem>,
425}
426
427impl Display for CaseClauseCommand {
428    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429        write!(f, "case {} in", self.value)?;
430        for case in &self.cases {
431            write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{case}")?;
432        }
433        writeln!(f)?;
434        write!(f, "esac")
435    }
436}
437
438/// A sequence of commands.
439#[derive(Clone, Debug)]
440#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
441#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
442#[cfg_attr(test, serde(rename = "List"))]
443pub struct CompoundList(pub Vec<CompoundListItem>);
444
445impl Display for CompoundList {
446    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
447        for (i, item) in self.0.iter().enumerate() {
448            if i > 0 {
449                writeln!(f)?;
450            }
451
452            // Write the and-or list.
453            write!(f, "{}", item.0)?;
454
455            // Write the separator... unless we're on the list item and it's a ';'.
456            if i == self.0.len() - 1 && matches!(item.1, SeparatorOperator::Sequence) {
457                // Skip
458            } else {
459                write!(f, "{}", item.1)?;
460            }
461        }
462
463        Ok(())
464    }
465}
466
467/// An element of a compound command list.
468#[derive(Clone, Debug)]
469#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
470#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
471#[cfg_attr(test, serde(rename = "Item"))]
472pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
473
474impl Display for CompoundListItem {
475    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476        write!(f, "{}", self.0)?;
477        write!(f, "{}", self.1)?;
478        Ok(())
479    }
480}
481
482/// An if clause, which conditionally executes a command.
483#[derive(Clone, Debug)]
484#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
485#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
486pub struct IfClauseCommand {
487    /// The command whose execution result is inspected.
488    pub condition: CompoundList,
489    /// The command to execute if the condition is true.
490    pub then: CompoundList,
491    /// Optionally, `else` clauses that will be evaluated if the condition is false.
492    #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
493    pub elses: Option<Vec<ElseClause>>,
494}
495
496impl Display for IfClauseCommand {
497    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
498        writeln!(f, "if {}; then", self.condition)?;
499        write!(
500            indenter::indented(f).with_str(DISPLAY_INDENT),
501            "{}",
502            self.then
503        )?;
504        if let Some(elses) = &self.elses {
505            for else_clause in elses {
506                write!(f, "{else_clause}")?;
507            }
508        }
509
510        writeln!(f)?;
511        write!(f, "fi")?;
512
513        Ok(())
514    }
515}
516
517/// Represents the `else` clause of a conditional command.
518#[derive(Clone, Debug)]
519#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
520#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
521pub struct ElseClause {
522    /// If present, the condition that must be met for this `else` clause to be executed.
523    #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
524    pub condition: Option<CompoundList>,
525    /// The commands to execute if this `else` clause is selected.
526    pub body: CompoundList,
527}
528
529impl Display for ElseClause {
530    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531        writeln!(f)?;
532        if let Some(condition) = &self.condition {
533            writeln!(f, "elif {condition}; then")?;
534        } else {
535            writeln!(f, "else")?;
536        }
537
538        write!(
539            indenter::indented(f).with_str(DISPLAY_INDENT),
540            "{}",
541            self.body
542        )
543    }
544}
545
546/// An individual matching case item in a case clause.
547#[derive(Clone, Debug)]
548#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
549#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
550pub struct CaseItem {
551    /// The patterns that select this case branch.
552    pub patterns: Vec<Word>,
553    /// The commands to execute if this case branch is selected.
554    pub cmd: Option<CompoundList>,
555    /// When the case branch is selected, the action to take after the command is executed.
556    pub post_action: CaseItemPostAction,
557}
558
559impl Display for CaseItem {
560    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561        writeln!(f)?;
562        for (i, pattern) in self.patterns.iter().enumerate() {
563            if i > 0 {
564                write!(f, "|")?;
565            }
566            write!(f, "{pattern}")?;
567        }
568        writeln!(f, ")")?;
569
570        if let Some(cmd) = &self.cmd {
571            write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{cmd}")?;
572        }
573        writeln!(f)?;
574        write!(f, "{}", self.post_action)
575    }
576}
577
578/// Describes the action to take after executing the body command of a case clause.
579#[derive(Clone, Debug)]
580#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
581#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
582pub enum CaseItemPostAction {
583    /// The containing case should be exited.
584    ExitCase,
585    /// If one is present, the command body of the succeeding case item should be
586    /// executed (without evaluating its pattern).
587    UnconditionallyExecuteNextCaseItem,
588    /// The case should continue evaluating the remaining case items, as if this
589    /// item had not been executed.
590    ContinueEvaluatingCases,
591}
592
593impl Display for CaseItemPostAction {
594    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
595        match self {
596            Self::ExitCase => write!(f, ";;"),
597            Self::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
598            Self::ContinueEvaluatingCases => write!(f, ";;&"),
599        }
600    }
601}
602
603/// A while or until clause, whose looping is controlled by a condition.
604#[derive(Clone, Debug)]
605#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
606#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
607pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand);
608
609impl Display for WhileOrUntilClauseCommand {
610    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
611        write!(f, "{}; {}", self.0, self.1)
612    }
613}
614
615/// Encapsulates the definition of a shell function.
616#[derive(Clone, Debug)]
617#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
618#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
619pub struct FunctionDefinition {
620    /// The name of the function.
621    pub fname: String,
622    /// The body of the function.
623    pub body: FunctionBody,
624    /// The source of the function definition.
625    pub source: String,
626}
627
628impl Display for FunctionDefinition {
629    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630        writeln!(f, "{} () ", self.fname)?;
631        write!(f, "{}", self.body)?;
632        Ok(())
633    }
634}
635
636/// Encapsulates the body of a function definition.
637#[derive(Clone, Debug)]
638#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
639#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
640pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
641
642impl Display for FunctionBody {
643    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644        write!(f, "{}", self.0)?;
645        if let Some(redirect_list) = &self.1 {
646            write!(f, "{redirect_list}")?;
647        }
648
649        Ok(())
650    }
651}
652
653/// A brace group, which groups commands together.
654#[derive(Clone, Debug)]
655#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
656#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
657pub struct BraceGroupCommand(pub CompoundList);
658
659impl Display for BraceGroupCommand {
660    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
661        writeln!(f, "{{ ")?;
662        write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{}", self.0)?;
663        writeln!(f)?;
664        write!(f, "}}")?;
665
666        Ok(())
667    }
668}
669
670/// A do group, which groups commands together.
671#[derive(Clone, Debug)]
672#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
673#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
674pub struct DoGroupCommand(pub CompoundList);
675
676impl Display for DoGroupCommand {
677    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
678        writeln!(f, "do")?;
679        write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{}", self.0)?;
680        writeln!(f)?;
681        write!(f, "done")
682    }
683}
684
685/// Represents the invocation of a simple command.
686#[derive(Clone, Debug)]
687#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
688#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
689#[cfg_attr(test, serde(rename = "Simple"))]
690pub struct SimpleCommand {
691    /// Optionally, a prefix to the command.
692    #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
693    pub prefix: Option<CommandPrefix>,
694    /// The name of the command to execute.
695    #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
696    #[cfg_attr(test, serde(rename = "w"))]
697    pub word_or_name: Option<Word>,
698    /// Optionally, a suffix to the command.
699    #[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
700    pub suffix: Option<CommandSuffix>,
701}
702
703impl Display for SimpleCommand {
704    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
705        let mut wrote_something = false;
706
707        if let Some(prefix) = &self.prefix {
708            if wrote_something {
709                write!(f, " ")?;
710            }
711
712            write!(f, "{prefix}")?;
713            wrote_something = true;
714        }
715
716        if let Some(word_or_name) = &self.word_or_name {
717            if wrote_something {
718                write!(f, " ")?;
719            }
720
721            write!(f, "{word_or_name}")?;
722            wrote_something = true;
723        }
724
725        if let Some(suffix) = &self.suffix {
726            if wrote_something {
727                write!(f, " ")?;
728            }
729
730            write!(f, "{suffix}")?;
731        }
732
733        Ok(())
734    }
735}
736
737/// Represents a prefix to a simple command.
738#[derive(Clone, Debug, Default)]
739#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
740#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
741#[cfg_attr(test, serde(rename = "Prefix"))]
742pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
743
744impl Display for CommandPrefix {
745    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
746        for (i, item) in self.0.iter().enumerate() {
747            if i > 0 {
748                write!(f, " ")?;
749            }
750
751            write!(f, "{item}")?;
752        }
753        Ok(())
754    }
755}
756
757/// Represents a suffix to a simple command; a word argument, declaration, or I/O redirection.
758#[derive(Clone, Default, Debug)]
759#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
760#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
761#[cfg_attr(test, serde(rename = "Suffix"))]
762pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
763
764impl Display for CommandSuffix {
765    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
766        for (i, item) in self.0.iter().enumerate() {
767            if i > 0 {
768                write!(f, " ")?;
769            }
770
771            write!(f, "{item}")?;
772        }
773        Ok(())
774    }
775}
776
777/// Represents the I/O direction of a process substitution.
778#[derive(Clone, Debug)]
779#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
780#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
781pub enum ProcessSubstitutionKind {
782    /// The process is read from.
783    Read,
784    /// The process is written to.
785    Write,
786}
787
788impl Display for ProcessSubstitutionKind {
789    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
790        match self {
791            Self::Read => write!(f, "<"),
792            Self::Write => write!(f, ">"),
793        }
794    }
795}
796
797/// A prefix or suffix for a simple command.
798#[derive(Clone, Debug)]
799#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
800#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
801pub enum CommandPrefixOrSuffixItem {
802    /// An I/O redirection.
803    IoRedirect(IoRedirect),
804    /// A word.
805    Word(Word),
806    /// An assignment/declaration word.
807    #[cfg_attr(test, serde(rename = "Assign"))]
808    AssignmentWord(Assignment, Word),
809    /// A process substitution.
810    ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
811}
812
813impl Display for CommandPrefixOrSuffixItem {
814    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
815        match self {
816            Self::IoRedirect(io_redirect) => write!(f, "{io_redirect}"),
817            Self::Word(word) => write!(f, "{word}"),
818            Self::AssignmentWord(_assignment, word) => write!(f, "{word}"),
819            Self::ProcessSubstitution(kind, subshell_command) => {
820                write!(f, "{kind}({subshell_command})")
821            }
822        }
823    }
824}
825
826/// Encapsulates an assignment declaration.
827#[derive(Clone, Debug)]
828#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
829#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
830#[cfg_attr(test, serde(rename = "Assign"))]
831pub struct Assignment {
832    /// Name being assigned to.
833    pub name: AssignmentName,
834    /// Value being assigned.
835    pub value: AssignmentValue,
836    /// Whether or not to append to the preexisting value associated with the named variable.
837    #[cfg_attr(test, serde(skip_serializing_if = "<&bool as std::ops::Not>::not"))]
838    pub append: bool,
839}
840
841impl Display for Assignment {
842    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
843        write!(f, "{}", self.name)?;
844        if self.append {
845            write!(f, "+")?;
846        }
847        write!(f, "={}", self.value)
848    }
849}
850
851/// The target of an assignment.
852#[derive(Clone, Debug)]
853#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
854#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
855pub enum AssignmentName {
856    /// A named variable.
857    #[cfg_attr(test, serde(rename = "Var"))]
858    VariableName(String),
859    /// An element in a named array.
860    ArrayElementName(String, String),
861}
862
863impl Display for AssignmentName {
864    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
865        match self {
866            Self::VariableName(name) => write!(f, "{name}"),
867            Self::ArrayElementName(name, index) => {
868                write!(f, "{name}[{index}]")
869            }
870        }
871    }
872}
873
874/// A value being assigned to a variable.
875#[derive(Clone, Debug)]
876#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
877#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
878pub enum AssignmentValue {
879    /// A scalar (word) value.
880    Scalar(Word),
881    /// An array of elements.
882    Array(Vec<(Option<Word>, Word)>),
883}
884
885impl Display for AssignmentValue {
886    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
887        match self {
888            Self::Scalar(word) => write!(f, "{word}"),
889            Self::Array(words) => {
890                write!(f, "(")?;
891                for (i, value) in words.iter().enumerate() {
892                    if i > 0 {
893                        write!(f, " ")?;
894                    }
895                    match value {
896                        (Some(key), value) => write!(f, "[{key}]={value}")?,
897                        (None, value) => write!(f, "{value}")?,
898                    }
899                }
900                write!(f, ")")
901            }
902        }
903    }
904}
905
906/// A list of I/O redirections to be applied to a command.
907#[derive(Clone, Debug)]
908#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
909#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
910pub struct RedirectList(pub Vec<IoRedirect>);
911
912impl Display for RedirectList {
913    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
914        for item in &self.0 {
915            write!(f, "{item}")?;
916        }
917        Ok(())
918    }
919}
920
921/// An I/O redirection.
922#[derive(Clone, Debug)]
923#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
924#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
925pub enum IoRedirect {
926    /// Redirection to a file.
927    File(Option<u32>, IoFileRedirectKind, IoFileRedirectTarget),
928    /// Redirection from a here-document.
929    HereDocument(Option<u32>, IoHereDocument),
930    /// Redirection from a here-string.
931    HereString(Option<u32>, Word),
932    /// Redirection of both standard output and standard error (with optional append).
933    OutputAndError(Word, bool),
934}
935
936impl Display for IoRedirect {
937    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
938        match self {
939            Self::File(fd_num, kind, target) => {
940                if let Some(fd_num) = fd_num {
941                    write!(f, "{fd_num}")?;
942                }
943
944                write!(f, "{kind} {target}")?;
945            }
946            Self::OutputAndError(target, append) => {
947                write!(f, "&>")?;
948                if *append {
949                    write!(f, ">")?;
950                }
951                write!(f, " {target}")?;
952            }
953            Self::HereDocument(
954                fd_num,
955                IoHereDocument {
956                    remove_tabs,
957                    here_end,
958                    doc,
959                    ..
960                },
961            ) => {
962                if let Some(fd_num) = fd_num {
963                    write!(f, "{fd_num}")?;
964                }
965
966                write!(f, "<<")?;
967                if *remove_tabs {
968                    write!(f, "-")?;
969                }
970
971                writeln!(f, "{here_end}")?;
972
973                write!(f, "{doc}")?;
974                writeln!(f, "{here_end}")?;
975            }
976            Self::HereString(fd_num, s) => {
977                if let Some(fd_num) = fd_num {
978                    write!(f, "{fd_num}")?;
979                }
980
981                write!(f, "<<< {s}")?;
982            }
983        }
984
985        Ok(())
986    }
987}
988
989/// Kind of file I/O redirection.
990#[derive(Clone, Debug)]
991#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
992#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
993pub enum IoFileRedirectKind {
994    /// Read (`<`).
995    Read,
996    /// Write (`>`).
997    Write,
998    /// Append (`>>`).
999    Append,
1000    /// Read and write (`<>`).
1001    ReadAndWrite,
1002    /// Clobber (`>|`).
1003    Clobber,
1004    /// Duplicate input (`<&`).
1005    DuplicateInput,
1006    /// Duplicate output (`>&`).
1007    DuplicateOutput,
1008}
1009
1010impl Display for IoFileRedirectKind {
1011    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1012        match self {
1013            Self::Read => write!(f, "<"),
1014            Self::Write => write!(f, ">"),
1015            Self::Append => write!(f, ">>"),
1016            Self::ReadAndWrite => write!(f, "<>"),
1017            Self::Clobber => write!(f, ">|"),
1018            Self::DuplicateInput => write!(f, "<&"),
1019            Self::DuplicateOutput => write!(f, ">&"),
1020        }
1021    }
1022}
1023
1024/// Target for an I/O file redirection.
1025#[derive(Clone, Debug)]
1026#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1027#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1028pub enum IoFileRedirectTarget {
1029    /// Path to a file.
1030    Filename(Word),
1031    /// File descriptor number.
1032    Fd(u32),
1033    /// Process substitution: substitution with the results of executing the given
1034    /// command in a subshell.
1035    ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1036    /// Item to duplicate in a word redirection. After expansion, this could be a
1037    /// filename, a file descriptor, or a file descriptor and a "-" to indicate
1038    /// requested closure.
1039    Duplicate(Word),
1040}
1041
1042impl Display for IoFileRedirectTarget {
1043    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1044        match self {
1045            Self::Filename(word) => write!(f, "{word}"),
1046            Self::Fd(fd) => write!(f, "{fd}"),
1047            Self::ProcessSubstitution(kind, subshell_command) => {
1048                write!(f, "{kind}{subshell_command}")
1049            }
1050            Self::Duplicate(word) => write!(f, "{word}"),
1051        }
1052    }
1053}
1054
1055/// Represents an I/O here document.
1056#[derive(Clone, Debug)]
1057#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1058#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1059pub struct IoHereDocument {
1060    /// Whether to remove leading tabs from the here document.
1061    #[cfg_attr(test, serde(skip_serializing_if = "<&bool as std::ops::Not>::not"))]
1062    pub remove_tabs: bool,
1063    /// Whether to basic-expand the contents of the here document.
1064    #[cfg_attr(test, serde(skip_serializing_if = "<&bool as std::ops::Not>::not"))]
1065    pub requires_expansion: bool,
1066    /// The delimiter marking the end of the here document.
1067    pub here_end: Word,
1068    /// The contents of the here document.
1069    pub doc: Word,
1070}
1071
1072/// A (non-extended) test expression.
1073#[derive(Clone, Debug)]
1074#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1075pub enum TestExpr {
1076    /// Always evaluates to false.
1077    False,
1078    /// A literal string.
1079    Literal(String),
1080    /// Logical AND operation on two nested expressions.
1081    And(Box<TestExpr>, Box<TestExpr>),
1082    /// Logical OR operation on two nested expressions.
1083    Or(Box<TestExpr>, Box<TestExpr>),
1084    /// Logical NOT operation on a nested expression.
1085    Not(Box<TestExpr>),
1086    /// A parenthesized expression.
1087    Parenthesized(Box<TestExpr>),
1088    /// A unary test operation.
1089    UnaryTest(UnaryPredicate, String),
1090    /// A binary test operation.
1091    BinaryTest(BinaryPredicate, String, String),
1092}
1093
1094impl Display for TestExpr {
1095    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1096        match self {
1097            Self::False => Ok(()),
1098            Self::Literal(s) => write!(f, "{s}"),
1099            Self::And(left, right) => write!(f, "{left} -a {right}"),
1100            Self::Or(left, right) => write!(f, "{left} -o {right}"),
1101            Self::Not(expr) => write!(f, "! {expr}"),
1102            Self::Parenthesized(expr) => write!(f, "( {expr} )"),
1103            Self::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
1104            Self::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
1105        }
1106    }
1107}
1108
1109/// An extended test expression.
1110#[derive(Clone, Debug)]
1111#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1112#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1113pub enum ExtendedTestExpr {
1114    /// Logical AND operation on two nested expressions.
1115    And(Box<ExtendedTestExpr>, Box<ExtendedTestExpr>),
1116    /// Logical OR operation on two nested expressions.
1117    Or(Box<ExtendedTestExpr>, Box<ExtendedTestExpr>),
1118    /// Logical NOT operation on a nested expression.
1119    Not(Box<ExtendedTestExpr>),
1120    /// A parenthesized expression.
1121    Parenthesized(Box<ExtendedTestExpr>),
1122    /// A unary test operation.
1123    UnaryTest(UnaryPredicate, Word),
1124    /// A binary test operation.
1125    BinaryTest(BinaryPredicate, Word, Word),
1126}
1127
1128impl Display for ExtendedTestExpr {
1129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1130        match self {
1131            Self::And(left, right) => {
1132                write!(f, "{left} && {right}")
1133            }
1134            Self::Or(left, right) => {
1135                write!(f, "{left} || {right}")
1136            }
1137            Self::Not(expr) => {
1138                write!(f, "! {expr}")
1139            }
1140            Self::Parenthesized(expr) => {
1141                write!(f, "( {expr} )")
1142            }
1143            Self::UnaryTest(pred, word) => {
1144                write!(f, "{pred} {word}")
1145            }
1146            Self::BinaryTest(pred, left, right) => {
1147                write!(f, "{left} {pred} {right}")
1148            }
1149        }
1150    }
1151}
1152
1153/// A unary predicate usable in an extended test expression.
1154#[derive(Clone, Debug)]
1155#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1156#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1157pub enum UnaryPredicate {
1158    /// Computes if the operand is a path to an existing file.
1159    FileExists,
1160    /// Computes if the operand is a path to an existing block device file.
1161    FileExistsAndIsBlockSpecialFile,
1162    /// Computes if the operand is a path to an existing character device file.
1163    FileExistsAndIsCharSpecialFile,
1164    /// Computes if the operand is a path to an existing directory.
1165    FileExistsAndIsDir,
1166    /// Computes if the operand is a path to an existing regular file.
1167    FileExistsAndIsRegularFile,
1168    /// Computes if the operand is a path to an existing file with the setgid bit set.
1169    FileExistsAndIsSetgid,
1170    /// Computes if the operand is a path to an existing symbolic link.
1171    FileExistsAndIsSymlink,
1172    /// Computes if the operand is a path to an existing file with the sticky bit set.
1173    FileExistsAndHasStickyBit,
1174    /// Computes if the operand is a path to an existing FIFO file.
1175    FileExistsAndIsFifo,
1176    /// Computes if the operand is a path to an existing file that is readable.
1177    FileExistsAndIsReadable,
1178    /// Computes if the operand is a path to an existing file with a non-zero length.
1179    FileExistsAndIsNotZeroLength,
1180    /// Computes if the operand is a file descriptor that is an open terminal.
1181    FdIsOpenTerminal,
1182    /// Computes if the operand is a path to an existing file with the setuid bit set.
1183    FileExistsAndIsSetuid,
1184    /// Computes if the operand is a path to an existing file that is writable.
1185    FileExistsAndIsWritable,
1186    /// Computes if the operand is a path to an existing file that is executable.
1187    FileExistsAndIsExecutable,
1188    /// Computes if the operand is a path to an existing file owned by the current context's
1189    /// effective group ID.
1190    FileExistsAndOwnedByEffectiveGroupId,
1191    /// Computes if the operand is a path to an existing file that has been modified since last
1192    /// being read.
1193    FileExistsAndModifiedSinceLastRead,
1194    /// Computes if the operand is a path to an existing file owned by the current context's
1195    /// effective user ID.
1196    FileExistsAndOwnedByEffectiveUserId,
1197    /// Computes if the operand is a path to an existing socket file.
1198    FileExistsAndIsSocket,
1199    /// Computes if the operand is a 'set -o' option that is enabled.
1200    ShellOptionEnabled,
1201    /// Computes if the operand names a shell variable that is set and assigned a value.
1202    ShellVariableIsSetAndAssigned,
1203    /// Computes if the operand names a shell variable that is set and of nameref type.
1204    ShellVariableIsSetAndNameRef,
1205    /// Computes if the operand is a string with zero length.
1206    StringHasZeroLength,
1207    /// Computes if the operand is a string with non-zero length.
1208    StringHasNonZeroLength,
1209}
1210
1211impl Display for UnaryPredicate {
1212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1213        match self {
1214            Self::FileExists => write!(f, "-e"),
1215            Self::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
1216            Self::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
1217            Self::FileExistsAndIsDir => write!(f, "-d"),
1218            Self::FileExistsAndIsRegularFile => write!(f, "-f"),
1219            Self::FileExistsAndIsSetgid => write!(f, "-g"),
1220            Self::FileExistsAndIsSymlink => write!(f, "-h"),
1221            Self::FileExistsAndHasStickyBit => write!(f, "-k"),
1222            Self::FileExistsAndIsFifo => write!(f, "-p"),
1223            Self::FileExistsAndIsReadable => write!(f, "-r"),
1224            Self::FileExistsAndIsNotZeroLength => write!(f, "-s"),
1225            Self::FdIsOpenTerminal => write!(f, "-t"),
1226            Self::FileExistsAndIsSetuid => write!(f, "-u"),
1227            Self::FileExistsAndIsWritable => write!(f, "-w"),
1228            Self::FileExistsAndIsExecutable => write!(f, "-x"),
1229            Self::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
1230            Self::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
1231            Self::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
1232            Self::FileExistsAndIsSocket => write!(f, "-S"),
1233            Self::ShellOptionEnabled => write!(f, "-o"),
1234            Self::ShellVariableIsSetAndAssigned => write!(f, "-v"),
1235            Self::ShellVariableIsSetAndNameRef => write!(f, "-R"),
1236            Self::StringHasZeroLength => write!(f, "-z"),
1237            Self::StringHasNonZeroLength => write!(f, "-n"),
1238        }
1239    }
1240}
1241
1242/// A binary predicate usable in an extended test expression.
1243#[derive(Clone, Debug)]
1244#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1245#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1246pub enum BinaryPredicate {
1247    /// Computes if two files refer to the same device and inode numbers.
1248    FilesReferToSameDeviceAndInodeNumbers,
1249    /// Computes if the left file is newer than the right, or exists when the right does not.
1250    LeftFileIsNewerOrExistsWhenRightDoesNot,
1251    /// Computes if the left file is older than the right, or does not exist when the right does.
1252    LeftFileIsOlderOrDoesNotExistWhenRightDoes,
1253    /// Computes if a string exactly matches a pattern.
1254    StringExactlyMatchesPattern,
1255    /// Computes if a string does not exactly match a pattern.
1256    StringDoesNotExactlyMatchPattern,
1257    /// Computes if a string matches a regular expression.
1258    StringMatchesRegex,
1259    /// Computes if a string exactly matches another string.
1260    StringExactlyMatchesString,
1261    /// Computes if a string does not exactly match another string.
1262    StringDoesNotExactlyMatchString,
1263    /// Computes if a string contains a substring.
1264    StringContainsSubstring,
1265    /// Computes if the left value sorts before the right.
1266    LeftSortsBeforeRight,
1267    /// Computes if the left value sorts after the right.
1268    LeftSortsAfterRight,
1269    /// Computes if two values are equal via arithmetic comparison.
1270    ArithmeticEqualTo,
1271    /// Computes if two values are not equal via arithmetic comparison.
1272    ArithmeticNotEqualTo,
1273    /// Computes if the left value is less than the right via arithmetic comparison.
1274    ArithmeticLessThan,
1275    /// Computes if the left value is less than or equal to the right via arithmetic comparison.
1276    ArithmeticLessThanOrEqualTo,
1277    /// Computes if the left value is greater than the right via arithmetic comparison.
1278    ArithmeticGreaterThan,
1279    /// Computes if the left value is greater than or equal to the right via arithmetic comparison.
1280    ArithmeticGreaterThanOrEqualTo,
1281}
1282
1283impl Display for BinaryPredicate {
1284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1285        match self {
1286            Self::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
1287            Self::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
1288            Self::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
1289            Self::StringExactlyMatchesPattern => write!(f, "=="),
1290            Self::StringDoesNotExactlyMatchPattern => write!(f, "!="),
1291            Self::StringMatchesRegex => write!(f, "=~"),
1292            Self::StringContainsSubstring => write!(f, "=~"),
1293            Self::StringExactlyMatchesString => write!(f, "=="),
1294            Self::StringDoesNotExactlyMatchString => write!(f, "!="),
1295            Self::LeftSortsBeforeRight => write!(f, "<"),
1296            Self::LeftSortsAfterRight => write!(f, ">"),
1297            Self::ArithmeticEqualTo => write!(f, "-eq"),
1298            Self::ArithmeticNotEqualTo => write!(f, "-ne"),
1299            Self::ArithmeticLessThan => write!(f, "-lt"),
1300            Self::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
1301            Self::ArithmeticGreaterThan => write!(f, "-gt"),
1302            Self::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
1303        }
1304    }
1305}
1306
1307/// Represents a shell word.
1308#[derive(Clone, Debug)]
1309#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1310#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1311#[cfg_attr(test, serde(rename = "W"))]
1312pub struct Word {
1313    /// Raw text of the word.
1314    #[cfg_attr(test, serde(rename = "v"))]
1315    pub value: String,
1316}
1317
1318impl Display for Word {
1319    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1320        write!(f, "{}", self.value)
1321    }
1322}
1323
1324impl From<&tokenizer::Token> for Word {
1325    fn from(t: &tokenizer::Token) -> Self {
1326        match t {
1327            tokenizer::Token::Word(value, _) => Self {
1328                value: value.clone(),
1329            },
1330            tokenizer::Token::Operator(value, _) => Self {
1331                value: value.clone(),
1332            },
1333        }
1334    }
1335}
1336
1337impl From<String> for Word {
1338    fn from(s: String) -> Self {
1339        Self { value: s }
1340    }
1341}
1342
1343impl Word {
1344    /// Constructs a new `Word` from a given string.
1345    pub fn new(s: &str) -> Self {
1346        Self {
1347            value: s.to_owned(),
1348        }
1349    }
1350
1351    /// Returns the raw text of the word, consuming the `Word`.
1352    pub fn flatten(&self) -> String {
1353        self.value.clone()
1354    }
1355}
1356
1357/// Encapsulates an unparsed arithmetic expression.
1358#[derive(Clone, Debug)]
1359#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1360#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1361pub struct UnexpandedArithmeticExpr {
1362    /// The raw text of the expression.
1363    pub value: String,
1364}
1365
1366impl Display for UnexpandedArithmeticExpr {
1367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1368        write!(f, "{}", self.value)
1369    }
1370}
1371
1372/// An arithmetic expression.
1373#[derive(Clone, Debug)]
1374#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1375pub enum ArithmeticExpr {
1376    /// A literal integer value.
1377    Literal(i64),
1378    /// A dereference of a variable or array element.
1379    Reference(ArithmeticTarget),
1380    /// A unary operation on an the result of a given nested expression.
1381    UnaryOp(UnaryOperator, Box<ArithmeticExpr>),
1382    /// A binary operation on two nested expressions.
1383    BinaryOp(BinaryOperator, Box<ArithmeticExpr>, Box<ArithmeticExpr>),
1384    /// A ternary conditional expression.
1385    Conditional(
1386        Box<ArithmeticExpr>,
1387        Box<ArithmeticExpr>,
1388        Box<ArithmeticExpr>,
1389    ),
1390    /// An assignment operation.
1391    Assignment(ArithmeticTarget, Box<ArithmeticExpr>),
1392    /// A binary assignment operation.
1393    BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<ArithmeticExpr>),
1394    /// A unary assignment operation.
1395    UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
1396}
1397
1398#[cfg(feature = "fuzz-testing")]
1399impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
1400    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1401        let variant = u.choose(&[
1402            "Literal",
1403            "Reference",
1404            "UnaryOp",
1405            "BinaryOp",
1406            "Conditional",
1407            "Assignment",
1408            "BinaryAssignment",
1409            "UnaryAssignment",
1410        ])?;
1411
1412        match *variant {
1413            "Literal" => Ok(Self::Literal(i64::arbitrary(u)?)),
1414            "Reference" => Ok(Self::Reference(ArithmeticTarget::arbitrary(u)?)),
1415            "UnaryOp" => Ok(Self::UnaryOp(
1416                UnaryOperator::arbitrary(u)?,
1417                Box::new(Self::arbitrary(u)?),
1418            )),
1419            "BinaryOp" => Ok(Self::BinaryOp(
1420                BinaryOperator::arbitrary(u)?,
1421                Box::new(Self::arbitrary(u)?),
1422                Box::new(Self::arbitrary(u)?),
1423            )),
1424            "Conditional" => Ok(Self::Conditional(
1425                Box::new(Self::arbitrary(u)?),
1426                Box::new(Self::arbitrary(u)?),
1427                Box::new(Self::arbitrary(u)?),
1428            )),
1429            "Assignment" => Ok(Self::Assignment(
1430                ArithmeticTarget::arbitrary(u)?,
1431                Box::new(Self::arbitrary(u)?),
1432            )),
1433            "BinaryAssignment" => Ok(Self::BinaryAssignment(
1434                BinaryOperator::arbitrary(u)?,
1435                ArithmeticTarget::arbitrary(u)?,
1436                Box::new(Self::arbitrary(u)?),
1437            )),
1438            "UnaryAssignment" => Ok(Self::UnaryAssignment(
1439                UnaryAssignmentOperator::arbitrary(u)?,
1440                ArithmeticTarget::arbitrary(u)?,
1441            )),
1442            _ => unreachable!(),
1443        }
1444    }
1445}
1446
1447impl Display for ArithmeticExpr {
1448    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1449        match self {
1450            Self::Literal(literal) => write!(f, "{literal}"),
1451            Self::Reference(target) => write!(f, "{target}"),
1452            Self::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
1453            Self::BinaryOp(op, left, right) => {
1454                if matches!(op, BinaryOperator::Comma) {
1455                    write!(f, "{left}{op} {right}")
1456                } else {
1457                    write!(f, "{left} {op} {right}")
1458                }
1459            }
1460            Self::Conditional(condition, if_branch, else_branch) => {
1461                write!(f, "{condition} ? {if_branch} : {else_branch}")
1462            }
1463            Self::Assignment(target, value) => write!(f, "{target} = {value}"),
1464            Self::BinaryAssignment(op, target, operand) => {
1465                write!(f, "{target} {op}= {operand}")
1466            }
1467            Self::UnaryAssignment(op, target) => match op {
1468                UnaryAssignmentOperator::PrefixIncrement
1469                | UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
1470                UnaryAssignmentOperator::PostfixIncrement
1471                | UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
1472            },
1473        }
1474    }
1475}
1476
1477/// A binary arithmetic operator.
1478#[derive(Clone, Copy, Debug)]
1479#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1480#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1481pub enum BinaryOperator {
1482    /// Exponentiation (e.g., `x ** y`).
1483    Power,
1484    /// Multiplication (e.g., `x * y`).
1485    Multiply,
1486    /// Division (e.g., `x / y`).
1487    Divide,
1488    /// Modulo (e.g., `x % y`).
1489    Modulo,
1490    /// Comma (e.g., `x, y`).
1491    Comma,
1492    /// Addition (e.g., `x + y`).
1493    Add,
1494    /// Subtraction (e.g., `x - y`).
1495    Subtract,
1496    /// Bitwise left shift (e.g., `x << y`).
1497    ShiftLeft,
1498    /// Bitwise right shift (e.g., `x >> y`).
1499    ShiftRight,
1500    /// Less than (e.g., `x < y`).
1501    LessThan,
1502    /// Less than or equal to (e.g., `x <= y`).
1503    LessThanOrEqualTo,
1504    /// Greater than (e.g., `x > y`).
1505    GreaterThan,
1506    /// Greater than or equal to (e.g., `x >= y`).
1507    GreaterThanOrEqualTo,
1508    /// Equals (e.g., `x == y`).
1509    Equals,
1510    /// Not equals (e.g., `x != y`).
1511    NotEquals,
1512    /// Bitwise AND (e.g., `x & y`).
1513    BitwiseAnd,
1514    /// Bitwise exclusive OR (xor) (e.g., `x ^ y`).
1515    BitwiseXor,
1516    /// Bitwise OR (e.g., `x | y`).
1517    BitwiseOr,
1518    /// Logical AND (e.g., `x && y`).
1519    LogicalAnd,
1520    /// Logical OR (e.g., `x || y`).
1521    LogicalOr,
1522}
1523
1524impl Display for BinaryOperator {
1525    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1526        match self {
1527            Self::Power => write!(f, "**"),
1528            Self::Multiply => write!(f, "*"),
1529            Self::Divide => write!(f, "/"),
1530            Self::Modulo => write!(f, "%"),
1531            Self::Comma => write!(f, ","),
1532            Self::Add => write!(f, "+"),
1533            Self::Subtract => write!(f, "-"),
1534            Self::ShiftLeft => write!(f, "<<"),
1535            Self::ShiftRight => write!(f, ">>"),
1536            Self::LessThan => write!(f, "<"),
1537            Self::LessThanOrEqualTo => write!(f, "<="),
1538            Self::GreaterThan => write!(f, ">"),
1539            Self::GreaterThanOrEqualTo => write!(f, ">="),
1540            Self::Equals => write!(f, "=="),
1541            Self::NotEquals => write!(f, "!="),
1542            Self::BitwiseAnd => write!(f, "&"),
1543            Self::BitwiseXor => write!(f, "^"),
1544            Self::BitwiseOr => write!(f, "|"),
1545            Self::LogicalAnd => write!(f, "&&"),
1546            Self::LogicalOr => write!(f, "||"),
1547        }
1548    }
1549}
1550
1551/// A unary arithmetic operator.
1552#[derive(Clone, Copy, Debug)]
1553#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1554#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1555pub enum UnaryOperator {
1556    /// Unary plus (e.g., `+x`).
1557    UnaryPlus,
1558    /// Unary minus (e.g., `-x`).
1559    UnaryMinus,
1560    /// Bitwise not (e.g., `~x`).
1561    BitwiseNot,
1562    /// Logical not (e.g., `!x`).
1563    LogicalNot,
1564}
1565
1566impl Display for UnaryOperator {
1567    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1568        match self {
1569            Self::UnaryPlus => write!(f, "+"),
1570            Self::UnaryMinus => write!(f, "-"),
1571            Self::BitwiseNot => write!(f, "~"),
1572            Self::LogicalNot => write!(f, "!"),
1573        }
1574    }
1575}
1576
1577/// A unary arithmetic assignment operator.
1578#[derive(Clone, Copy, Debug)]
1579#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1580#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1581pub enum UnaryAssignmentOperator {
1582    /// Prefix increment (e.g., `++x`).
1583    PrefixIncrement,
1584    /// Prefix increment (e.g., `--x`).
1585    PrefixDecrement,
1586    /// Postfix increment (e.g., `x++`).
1587    PostfixIncrement,
1588    /// Postfix decrement (e.g., `x--`).
1589    PostfixDecrement,
1590}
1591
1592impl Display for UnaryAssignmentOperator {
1593    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1594        match self {
1595            Self::PrefixIncrement => write!(f, "++"),
1596            Self::PrefixDecrement => write!(f, "--"),
1597            Self::PostfixIncrement => write!(f, "++"),
1598            Self::PostfixDecrement => write!(f, "--"),
1599        }
1600    }
1601}
1602
1603/// Identifies the target of an arithmetic assignment expression.
1604#[derive(Clone, Debug)]
1605#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1606#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
1607pub enum ArithmeticTarget {
1608    /// A named variable.
1609    Variable(String),
1610    /// An element in an array.
1611    ArrayElement(String, Box<ArithmeticExpr>),
1612}
1613
1614impl Display for ArithmeticTarget {
1615    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1616        match self {
1617            Self::Variable(name) => write!(f, "{name}"),
1618            Self::ArrayElement(name, index) => write!(f, "{name}[{index}]"),
1619        }
1620    }
1621}