Skip to main content

shrub_parser/
ast.rs

1//! Defines the Abstract Syntax Tree (ast) for shell programs. Includes types
2//! and utilities for manipulating the AST.
3
4use std::fmt::{Display, Write};
5
6use crate::{SourceSpan, tokenizer};
7
8const DISPLAY_INDENT: &str = "    ";
9
10/// Trait implemented by all AST nodes. Used to aggregate traits expected
11/// to be implemented.
12pub trait Node: Display + SourceLocation {}
13
14/// Provides the source location for the syntax item
15pub trait SourceLocation {
16    /// The location of the syntax item, when known
17    fn location(&self) -> Option<SourceSpan>;
18}
19
20pub(crate) fn maybe_location(
21    start: Option<&SourceSpan>,
22    end: Option<&SourceSpan>,
23) -> Option<SourceSpan> {
24    if let (Some(s), Some(e)) = (start, end) {
25        Some(SourceSpan::within(s, e))
26    } else {
27        None
28    }
29}
30
31/// Represents a complete shell program.
32#[derive(Clone, Debug)]
33#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
34#[cfg_attr(
35    any(test, feature = "serde"),
36    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
37)]
38pub struct Program {
39    /// A sequence of complete shell commands.
40    pub complete_commands: Vec<CompleteCommand>,
41}
42
43impl Node for Program {}
44
45impl SourceLocation for Program {
46    fn location(&self) -> Option<SourceSpan> {
47        let start = self
48            .complete_commands
49            .first()
50            .and_then(SourceLocation::location);
51        let end = self
52            .complete_commands
53            .last()
54            .and_then(SourceLocation::location);
55        maybe_location(start.as_ref(), end.as_ref())
56    }
57}
58
59impl Display for Program {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        for complete_command in &self.complete_commands {
62            write!(f, "{complete_command}")?;
63        }
64        Ok(())
65    }
66}
67
68/// Represents a complete shell command.
69pub type CompleteCommand = CompoundList;
70
71/// Represents a complete shell command item.
72pub type CompleteCommandItem = CompoundListItem;
73
74// TODO(tracing): decide if we want to trace this location or consider it a
75// whitespace separator
76/// Indicates whether the preceding command is executed synchronously or
77/// asynchronously.
78#[derive(Clone, Debug)]
79#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
80#[cfg_attr(
81    any(test, feature = "serde"),
82    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
83)]
84pub enum SeparatorOperator {
85    /// The preceding command is executed asynchronously.
86    Async,
87    /// The preceding command is executed synchronously.
88    Sequence,
89}
90
91impl Display for SeparatorOperator {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        match self {
94            Self::Async => write!(f, "&"),
95            Self::Sequence => write!(f, ";"),
96        }
97    }
98}
99
100/// Represents a sequence of command pipelines connected by boolean operators.
101#[derive(Clone, Debug)]
102#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
103#[cfg_attr(
104    any(test, feature = "serde"),
105    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
106)]
107pub struct AndOrList {
108    /// The first command pipeline.
109    pub first: Pipeline,
110    /// Any additional command pipelines, in sequence order.
111    #[cfg_attr(
112        any(test, feature = "serde"),
113        serde(skip_serializing_if = "Vec::is_empty", default)
114    )]
115    pub additional: Vec<AndOr>,
116}
117
118impl Node for AndOrList {}
119
120impl SourceLocation for AndOrList {
121    fn location(&self) -> Option<SourceSpan> {
122        let start = self.first.location();
123        let last = self.additional.last();
124        let end = last.and_then(SourceLocation::location);
125
126        match (start, end) {
127            (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
128            (start, _) => start,
129        }
130    }
131}
132
133impl Display for AndOrList {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        write!(f, "{}", self.first)?;
136        for item in &self.additional {
137            write!(f, "{item}")?;
138        }
139
140        Ok(())
141    }
142}
143
144/// Represents a boolean operator used to connect command pipelines in an
145/// [`AndOrList`]
146#[derive(PartialEq, Eq)]
147pub enum PipelineOperator {
148    /// The command pipelines are connected by a boolean AND operator.
149    And,
150    /// The command pipelines are connected by a boolean OR operator.
151    Or,
152}
153
154impl PartialEq<AndOr> for PipelineOperator {
155    fn eq(&self, other: &AndOr) -> bool {
156        matches!(
157            (self, other),
158            (Self::And, AndOr::And(_)) | (Self::Or, AndOr::Or(_))
159        )
160    }
161}
162
163// We cannot losslessly convert into `AndOr`, hence we can only do `Into`.
164#[expect(clippy::from_over_into)]
165impl Into<PipelineOperator> for AndOr {
166    fn into(self) -> PipelineOperator {
167        match self {
168            Self::And(_) => PipelineOperator::And,
169            Self::Or(_) => PipelineOperator::Or,
170        }
171    }
172}
173
174/// An iterator over the pipelines in an [`AndOrList`].
175pub struct AndOrListIter<'a> {
176    first: Option<&'a Pipeline>,
177    additional_iter: std::slice::Iter<'a, AndOr>,
178}
179
180impl<'a> Iterator for AndOrListIter<'a> {
181    type Item = (PipelineOperator, &'a Pipeline);
182
183    fn next(&mut self) -> Option<Self::Item> {
184        if let Some(first) = self.first.take() {
185            Some((PipelineOperator::And, first))
186        } else {
187            self.additional_iter.next().map(|and_or| match and_or {
188                AndOr::And(pipeline) => (PipelineOperator::And, pipeline),
189                AndOr::Or(pipeline) => (PipelineOperator::Or, pipeline),
190            })
191        }
192    }
193}
194
195impl<'a> IntoIterator for &'a AndOrList {
196    type Item = (PipelineOperator, &'a Pipeline);
197    type IntoIter = AndOrListIter<'a>;
198
199    fn into_iter(self) -> Self::IntoIter {
200        AndOrListIter {
201            first: Some(&self.first),
202            additional_iter: self.additional.iter(),
203        }
204    }
205}
206
207impl<'a> From<(PipelineOperator, &'a Pipeline)> for AndOr {
208    fn from(value: (PipelineOperator, &'a Pipeline)) -> Self {
209        match value.0 {
210            PipelineOperator::Or => Self::Or(value.1.to_owned()),
211            PipelineOperator::And => Self::And(value.1.to_owned()),
212        }
213    }
214}
215
216impl AndOrList {
217    /// Returns an iterator over the pipelines in this `AndOrList`.
218    pub fn iter(&self) -> AndOrListIter<'_> {
219        self.into_iter()
220    }
221}
222
223/// Represents a boolean operator used to connect command pipelines, along with
224/// the succeeding pipeline.
225#[derive(Clone, Debug)]
226#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
227#[cfg_attr(
228    any(test, feature = "serde"),
229    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
230)]
231pub enum AndOr {
232    /// Boolean AND operator; the embedded pipeline is only to be executed if
233    /// the preceding command has succeeded.
234    And(Pipeline),
235    /// Boolean OR operator; the embedded pipeline is only to be executed if the
236    /// preceding command has not succeeded.
237    Or(Pipeline),
238}
239
240impl Node for AndOr {}
241
242// TODO(source-location): add a loc to account for the operator
243impl SourceLocation for AndOr {
244    fn location(&self) -> Option<SourceSpan> {
245        match self {
246            Self::And(p) => p.location(),
247            Self::Or(p) => p.location(),
248        }
249    }
250}
251
252impl Display for AndOr {
253    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254        match self {
255            Self::And(pipeline) => write!(f, " && {pipeline}"),
256            Self::Or(pipeline) => write!(f, " || {pipeline}"),
257        }
258    }
259}
260
261/// The type of timing requested for a pipeline.
262#[derive(Clone, Debug)]
263#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
264#[cfg_attr(
265    any(test, feature = "serde"),
266    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
267)]
268pub enum PipelineTimed {
269    /// The pipeline should be timed with bash-like output.
270    Timed(SourceSpan),
271    /// The pipeline should be timed with POSIX-like output.
272    TimedWithPosixOutput(SourceSpan),
273}
274
275impl Node for PipelineTimed {}
276
277impl SourceLocation for PipelineTimed {
278    fn location(&self) -> Option<SourceSpan> {
279        match self {
280            Self::Timed(t) => Some(t.to_owned()),
281            Self::TimedWithPosixOutput(t) => Some(t.to_owned()),
282        }
283    }
284}
285
286impl Display for PipelineTimed {
287    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288        match self {
289            Self::Timed(_) => write!(f, "time"),
290            Self::TimedWithPosixOutput(_) => write!(f, "time -p"),
291        }
292    }
293}
294
295impl PipelineTimed {
296    /// Returns true if the pipeline should be timed with POSIX-like output.
297    pub const fn is_posix_output(&self) -> bool {
298        matches!(self, Self::TimedWithPosixOutput(_))
299    }
300}
301
302/// A pipeline of commands, where each command's output is passed as standard
303/// input to the command that follows it.
304#[derive(Clone, Debug)]
305#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
306#[cfg_attr(
307    any(test, feature = "serde"),
308    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
309)]
310pub struct Pipeline {
311    /// Indicates whether the pipeline's execution should be timed with reported
312    /// timings in output.
313    #[cfg_attr(
314        any(test, feature = "serde"),
315        serde(skip_serializing_if = "Option::is_none", default)
316    )]
317    pub timed: Option<PipelineTimed>,
318    /// Indicates whether the result of the overall pipeline should be the
319    /// logical negation of the result of the pipeline.
320    #[cfg_attr(
321        any(test, feature = "serde"),
322        serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
323    )]
324    pub bang: bool,
325    /// The sequence of commands in the pipeline.
326    pub seq: Vec<Command>,
327}
328
329impl Node for Pipeline {}
330
331// TODO(source-location): Handle the case where `self.timed` is `None` but there
332// is a bang.
333impl SourceLocation for Pipeline {
334    fn location(&self) -> Option<SourceSpan> {
335        let start = self
336            .timed
337            .as_ref()
338            .and_then(SourceLocation::location)
339            .or_else(|| self.seq.first().and_then(SourceLocation::location));
340        let end = self.seq.last().and_then(SourceLocation::location);
341
342        maybe_location(start.as_ref(), end.as_ref())
343    }
344}
345
346impl Display for Pipeline {
347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348        if let Some(timed) = &self.timed {
349            write!(f, "{timed} ")?;
350        }
351
352        if self.bang {
353            write!(f, "! ")?;
354        }
355        for (i, command) in self.seq.iter().enumerate() {
356            if i > 0 {
357                write!(f, " |")?;
358            }
359            write!(f, "{command}")?;
360        }
361
362        Ok(())
363    }
364}
365
366/// Represents a shell command.
367#[derive(Clone, Debug)]
368#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
369#[cfg_attr(
370    any(test, feature = "serde"),
371    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
372)]
373pub enum Command {
374    /// A simple command, directly invoking an external command, a built-in
375    /// command, a shell function, or similar.
376    Simple(SimpleCommand),
377    /// A compound command, composed of multiple commands.
378    Compound(CompoundCommand, Option<RedirectList>),
379    /// A command whose side effect is to define a shell function.
380    Function(FunctionDefinition),
381}
382
383impl Node for Command {}
384
385impl SourceLocation for Command {
386    fn location(&self) -> Option<SourceSpan> {
387        match self {
388            Self::Simple(s) => s.location(),
389            Self::Compound(c, r) => {
390                match (c.location(), r.as_ref().and_then(SourceLocation::location)) {
391                    (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
392                    (s, _) => s,
393                }
394            }
395            Self::Function(f) => f.location(),
396        }
397    }
398}
399
400impl Display for Command {
401    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402        match self {
403            Self::Simple(simple_command) => write!(f, "{simple_command}"),
404            Self::Compound(compound_command, redirect_list) => {
405                write!(f, "{compound_command}")?;
406                if let Some(redirect_list) = redirect_list {
407                    write!(f, "{redirect_list}")?;
408                }
409                Ok(())
410            }
411            Self::Function(function_definition) => write!(f, "{function_definition}"),
412        }
413    }
414}
415
416/// Represents a compound command, potentially made up of multiple nested
417/// commands.
418#[derive(Clone, Debug)]
419#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
420#[cfg_attr(
421    any(test, feature = "serde"),
422    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
423)]
424pub enum CompoundCommand {
425    /// An arithmetic command, evaluating an arithmetic expression.
426    Arithmetic(ArithmeticCommand),
427    /// An arithmetic for clause, which loops until an arithmetic condition is
428    /// reached.
429    ArithmeticForClause(ArithmeticForClauseCommand),
430    /// A brace group, which groups commands together.
431    BraceGroup(BraceGroupCommand),
432    /// A subshell, which executes commands in a subshell.
433    Subshell(SubshellCommand),
434    /// A for clause, which loops over a set of values.
435    ForClause(ForClauseCommand),
436    /// A case clause, which selects a command based on a value and a set of
437    /// pattern-based filters.
438    CaseClause(CaseClauseCommand),
439    /// An if clause, which conditionally executes a command.
440    IfClause(IfClauseCommand),
441    /// A while clause, which loops while a condition is met.
442    WhileClause(WhileOrUntilClauseCommand),
443    /// An until clause, which loops until a condition is met.
444    UntilClause(WhileOrUntilClauseCommand),
445    /// A coprocess, which runs a command asynchronously in a subshell.
446    Coprocess(CoprocessCommand),
447    /// An extended test command, evaluating an extended test expression.
448    ExtendedTest(ExtendedTestExprCommand),
449}
450
451impl Node for CompoundCommand {}
452
453impl SourceLocation for CompoundCommand {
454    fn location(&self) -> Option<SourceSpan> {
455        match self {
456            Self::Arithmetic(a) => a.location(),
457            Self::ArithmeticForClause(a) => a.location(),
458            Self::BraceGroup(b) => b.location(),
459            Self::Subshell(s) => s.location(),
460            Self::ForClause(f) => f.location(),
461            Self::CaseClause(c) => c.location(),
462            Self::IfClause(i) => i.location(),
463            Self::WhileClause(w) => w.location(),
464            Self::UntilClause(u) => u.location(),
465            Self::Coprocess(c) => c.location(),
466            Self::ExtendedTest(e) => e.location(),
467        }
468    }
469}
470
471impl Display for CompoundCommand {
472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473        match self {
474            Self::Arithmetic(arithmetic_command) => write!(f, "{arithmetic_command}"),
475            Self::ArithmeticForClause(arithmetic_for_clause_command) => {
476                write!(f, "{arithmetic_for_clause_command}")
477            }
478            Self::BraceGroup(brace_group_command) => {
479                write!(f, "{brace_group_command}")
480            }
481            Self::Subshell(subshell_command) => write!(f, "{subshell_command}"),
482            Self::ForClause(for_clause_command) => write!(f, "{for_clause_command}"),
483            Self::CaseClause(case_clause_command) => {
484                write!(f, "{case_clause_command}")
485            }
486            Self::IfClause(if_clause_command) => write!(f, "{if_clause_command}"),
487            Self::WhileClause(while_or_until_clause_command) => {
488                write!(f, "while {while_or_until_clause_command}")
489            }
490            Self::UntilClause(while_or_until_clause_command) => {
491                write!(f, "until {while_or_until_clause_command}")
492            }
493            Self::Coprocess(coproc_clause_command) => {
494                write!(f, "{coproc_clause_command}")
495            }
496            Self::ExtendedTest(extended_test_expr_command) => {
497                write!(f, "[[ {extended_test_expr_command} ]]")
498            }
499        }
500    }
501}
502
503/// An arithmetic command, evaluating an arithmetic expression.
504#[derive(Clone, Debug)]
505#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
506#[cfg_attr(
507    any(test, feature = "serde"),
508    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
509)]
510pub struct ArithmeticCommand {
511    /// The raw, unparsed and unexpanded arithmetic expression.
512    pub expr: UnexpandedArithmeticExpr,
513    /// Location of the command
514    pub loc: SourceSpan,
515}
516
517impl Node for ArithmeticCommand {}
518
519impl SourceLocation for ArithmeticCommand {
520    fn location(&self) -> Option<SourceSpan> {
521        Some(self.loc.clone())
522    }
523}
524
525impl Display for ArithmeticCommand {
526    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
527        write!(f, "(({}))", self.expr)
528    }
529}
530
531/// A subshell, which executes commands in a subshell.
532#[derive(Clone, Debug)]
533#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
534#[cfg_attr(
535    any(test, feature = "serde"),
536    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
537)]
538pub struct SubshellCommand {
539    /// Command list in the subshell
540    pub list: CompoundList,
541    /// Location of the subshell
542    pub loc: SourceSpan,
543}
544
545impl Node for SubshellCommand {}
546
547impl SourceLocation for SubshellCommand {
548    fn location(&self) -> Option<SourceSpan> {
549        Some(self.loc.clone())
550    }
551}
552
553impl Display for SubshellCommand {
554    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
555        write!(f, "( ")?;
556        write!(f, "{}", self.list)?;
557        write!(f, " )")
558    }
559}
560
561/// A for clause, which loops over a set of values.
562#[derive(Clone, Debug)]
563#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
564#[cfg_attr(
565    any(test, feature = "serde"),
566    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
567)]
568pub struct ForClauseCommand {
569    /// The name of the iterator variable.
570    pub variable_name: String,
571    /// The values being iterated over.
572    pub values: Option<Vec<Word>>,
573    /// The command to run for each iteration of the loop.
574    pub body: DoGroupCommand,
575    /// Location of the for command.
576    pub loc: SourceSpan,
577}
578
579impl Node for ForClauseCommand {}
580
581impl SourceLocation for ForClauseCommand {
582    fn location(&self) -> Option<SourceSpan> {
583        Some(self.loc.clone())
584    }
585}
586
587impl Display for ForClauseCommand {
588    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589        write!(f, "for {} in ", self.variable_name)?;
590
591        if let Some(values) = &self.values {
592            for (i, value) in values.iter().enumerate() {
593                if i > 0 {
594                    write!(f, " ")?;
595                }
596
597                write!(f, "{value}")?;
598            }
599        }
600
601        writeln!(f, ";")?;
602
603        write!(f, "{}", self.body)
604    }
605}
606
607/// An arithmetic for clause, which loops until an arithmetic condition is
608/// reached.
609#[derive(Clone, Debug)]
610#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
611#[cfg_attr(
612    any(test, feature = "serde"),
613    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
614)]
615pub struct ArithmeticForClauseCommand {
616    /// Optionally, the initializer expression evaluated before the first
617    /// iteration of the loop.
618    pub initializer: Option<UnexpandedArithmeticExpr>,
619    /// Optionally, the expression evaluated as the exit condition of the loop.
620    pub condition: Option<UnexpandedArithmeticExpr>,
621    /// Optionally, the expression evaluated after each iteration of the loop.
622    pub updater: Option<UnexpandedArithmeticExpr>,
623    /// The command to run for each iteration of the loop.
624    pub body: DoGroupCommand,
625    /// Location of the clause
626    pub loc: SourceSpan,
627}
628
629impl Node for ArithmeticForClauseCommand {}
630
631impl SourceLocation for ArithmeticForClauseCommand {
632    fn location(&self) -> Option<SourceSpan> {
633        Some(self.loc.clone())
634    }
635}
636
637impl Display for ArithmeticForClauseCommand {
638    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
639        write!(f, "for ((")?;
640
641        if let Some(initializer) = &self.initializer {
642            write!(f, "{initializer}")?;
643        }
644
645        write!(f, "; ")?;
646
647        if let Some(condition) = &self.condition {
648            write!(f, "{condition}")?;
649        }
650
651        write!(f, "; ")?;
652
653        if let Some(updater) = &self.updater {
654            write!(f, "{updater}")?;
655        }
656
657        writeln!(f, "))")?;
658
659        write!(f, "{}", self.body)
660    }
661}
662
663/// A case clause, which selects a command based on a value and a set of
664/// pattern-based filters.
665#[derive(Clone, Debug)]
666#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
667#[cfg_attr(
668    any(test, feature = "serde"),
669    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
670)]
671pub struct CaseClauseCommand {
672    /// The value being matched on.
673    pub value: Word,
674    /// The individual case branches.
675    pub cases: Vec<CaseItem>,
676    /// Location of the case command.
677    pub loc: SourceSpan,
678}
679
680impl Node for CaseClauseCommand {}
681
682impl SourceLocation for CaseClauseCommand {
683    fn location(&self) -> Option<SourceSpan> {
684        Some(self.loc.clone())
685    }
686}
687
688impl Display for CaseClauseCommand {
689    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
690        // Note the trailing space, which the shell emits when printing a case
691        // clause.
692        write!(f, "case {} in ", self.value)?;
693        for case in &self.cases {
694            write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{case}")?;
695        }
696        writeln!(f)?;
697        write!(f, "esac")
698    }
699}
700
701/// A sequence of commands.
702#[derive(Clone, Debug)]
703#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
704#[cfg_attr(
705    any(test, feature = "serde"),
706    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
707)]
708pub struct CompoundList(pub Vec<CompoundListItem>);
709
710impl Node for CompoundList {}
711
712// TODO(source-location): Handle the optional trailing separator.
713impl SourceLocation for CompoundList {
714    fn location(&self) -> Option<SourceSpan> {
715        let start = self.0.first().and_then(SourceLocation::location);
716        let end = self.0.last().and_then(SourceLocation::location);
717
718        if let (Some(s), Some(e)) = (start, end) {
719            Some(SourceSpan::within(&s, &e))
720        } else {
721            None
722        }
723    }
724}
725
726impl CompoundList {
727    /// Displays the list with its trailing `;` retained, the way the shell
728    /// prints the body of a block closed by a reserved word (`fi`, `else`,
729    /// `done`).
730    fn terminated(&self) -> impl Display + '_ {
731        TerminatedCompoundList(self)
732    }
733
734    fn fmt_items(
735        &self,
736        f: &mut std::fmt::Formatter<'_>,
737        keep_trailing_separator: bool,
738    ) -> std::fmt::Result {
739        for (i, item) in self.0.iter().enumerate() {
740            if i > 0 {
741                writeln!(f)?;
742            }
743
744            // Write the and-or list.
745            write!(f, "{}", item.0)?;
746
747            // Write the separator... unless we're on the last list item and
748            // it's a ';' that the enclosing construct doesn't want.
749            if keep_trailing_separator
750                || i < self.0.len() - 1
751                || !matches!(item.1, SeparatorOperator::Sequence)
752            {
753                write!(f, "{}", item.1)?;
754            }
755        }
756
757        Ok(())
758    }
759}
760
761impl Display for CompoundList {
762    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
763        self.fmt_items(f, false)
764    }
765}
766
767struct TerminatedCompoundList<'a>(&'a CompoundList);
768
769impl Display for TerminatedCompoundList<'_> {
770    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
771        self.0.fmt_items(f, true)
772    }
773}
774
775/// An element of a compound command list.
776#[derive(Clone, Debug)]
777#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
778#[cfg_attr(
779    any(test, feature = "serde"),
780    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
781)]
782pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
783
784impl Node for CompoundListItem {}
785
786// TODO(source-location): Account for the location of the separator operator.
787impl SourceLocation for CompoundListItem {
788    fn location(&self) -> Option<SourceSpan> {
789        self.0.location()
790    }
791}
792
793impl Display for CompoundListItem {
794    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
795        write!(f, "{}", self.0)?;
796        write!(f, "{}", self.1)?;
797        Ok(())
798    }
799}
800
801/// An if clause, which conditionally executes a command.
802#[derive(Clone, Debug)]
803#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
804#[cfg_attr(
805    any(test, feature = "serde"),
806    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
807)]
808pub struct IfClauseCommand {
809    /// The command whose execution result is inspected.
810    pub condition: CompoundList,
811    /// The command to execute if the condition is true.
812    pub then: CompoundList,
813    /// Optionally, `else` clauses that will be evaluated if the condition is
814    /// false.
815    #[cfg_attr(
816        any(test, feature = "serde"),
817        serde(skip_serializing_if = "Option::is_none", default)
818    )]
819    pub elses: Option<Vec<ElseClause>>,
820    /// Location of the if clause
821    pub loc: SourceSpan,
822}
823
824impl Node for IfClauseCommand {}
825
826impl SourceLocation for IfClauseCommand {
827    fn location(&self) -> Option<SourceSpan> {
828        Some(self.loc.clone())
829    }
830}
831
832impl Display for IfClauseCommand {
833    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
834        writeln!(f, "if {}; then", self.condition)?;
835        write!(
836            indenter::indented(f).with_str(DISPLAY_INDENT),
837            "{}",
838            self.then.terminated()
839        )?;
840        if let Some(elses) = &self.elses {
841            for else_clause in elses {
842                write!(f, "{else_clause}")?;
843            }
844        }
845
846        writeln!(f)?;
847        write!(f, "fi")?;
848
849        Ok(())
850    }
851}
852
853/// Represents the `else` clause of a conditional command.
854#[derive(Clone, Debug)]
855#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
856#[cfg_attr(
857    any(test, feature = "serde"),
858    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
859)]
860pub struct ElseClause {
861    /// If present, the condition that must be met for this `else` clause to be
862    /// executed.
863    #[cfg_attr(
864        any(test, feature = "serde"),
865        serde(skip_serializing_if = "Option::is_none", default)
866    )]
867    pub condition: Option<CompoundList>,
868    /// The commands to execute if this `else` clause is selected.
869    pub body: CompoundList,
870}
871
872impl Display for ElseClause {
873    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
874        writeln!(f)?;
875        if let Some(condition) = &self.condition {
876            writeln!(f, "elif {condition}; then")?;
877        } else {
878            writeln!(f, "else")?;
879        }
880
881        write!(
882            indenter::indented(f).with_str(DISPLAY_INDENT),
883            "{}",
884            self.body.terminated()
885        )
886    }
887}
888
889/// A coprocess command, which runs a command asynchronously in a subshell.
890#[derive(Clone, Debug)]
891#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
892#[cfg_attr(
893    any(test, feature = "serde"),
894    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
895)]
896pub struct CoprocessCommand {
897    /// The optional name for the coprocess.
898    #[cfg_attr(
899        any(test, feature = "serde"),
900        serde(skip_serializing_if = "Option::is_none", default)
901    )]
902    pub name: Option<Word>,
903    /// The command to run as a coprocess (can be simple or compound).
904    pub body: Box<Command>,
905    /// The location of this command in the source.
906    #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing, default))]
907    pub loc: SourceSpan,
908}
909
910impl Node for CoprocessCommand {}
911
912impl SourceLocation for CoprocessCommand {
913    fn location(&self) -> Option<SourceSpan> {
914        Some(self.loc.clone())
915    }
916}
917
918impl Display for CoprocessCommand {
919    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
920        write!(f, "coproc")?;
921        if let Some(name) = &self.name {
922            write!(f, " {name}")?;
923        }
924        write!(f, " {}", self.body)?;
925        Ok(())
926    }
927}
928
929/// An individual matching case item in a case clause.
930#[derive(Clone, Debug)]
931#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
932#[cfg_attr(
933    any(test, feature = "serde"),
934    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
935)]
936pub struct CaseItem {
937    /// The patterns that select this case branch.
938    pub patterns: Vec<Word>,
939    /// The commands to execute if this case branch is selected.
940    pub cmd: Option<CompoundList>,
941    /// When the case branch is selected, the action to take after the command
942    /// is executed.
943    pub post_action: CaseItemPostAction,
944    /// Location of the item
945    pub loc: Option<SourceSpan>,
946}
947
948impl Node for CaseItem {}
949
950impl SourceLocation for CaseItem {
951    fn location(&self) -> Option<SourceSpan> {
952        self.loc.clone()
953    }
954}
955
956impl Display for CaseItem {
957    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
958        writeln!(f)?;
959        for (i, pattern) in self.patterns.iter().enumerate() {
960            if i > 0 {
961                // Note the spaces, which the shell puts around the pattern
962                // separator.
963                write!(f, " | ")?;
964            }
965            write!(f, "{pattern}")?;
966        }
967        writeln!(f, ")")?;
968
969        if let Some(cmd) = &self.cmd {
970            write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{cmd}")?;
971        }
972        writeln!(f)?;
973        write!(f, "{}", self.post_action)
974    }
975}
976
977/// Describes the action to take after executing the body command of a case
978/// clause.
979#[derive(Clone, Debug)]
980#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
981#[cfg_attr(
982    any(test, feature = "serde"),
983    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
984)]
985pub enum CaseItemPostAction {
986    /// The containing case should be exited.
987    ExitCase,
988    /// If one is present, the command body of the succeeding case item should
989    /// be executed (without evaluating its pattern).
990    UnconditionallyExecuteNextCaseItem,
991    /// The case should continue evaluating the remaining case items, as if this
992    /// item had not been executed.
993    ContinueEvaluatingCases,
994}
995
996impl Display for CaseItemPostAction {
997    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
998        match self {
999            Self::ExitCase => write!(f, ";;"),
1000            Self::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
1001            Self::ContinueEvaluatingCases => write!(f, ";;&"),
1002        }
1003    }
1004}
1005
1006/// A while or until clause, whose looping is controlled by a condition.
1007#[derive(Clone, Debug)]
1008#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1009#[cfg_attr(
1010    any(test, feature = "serde"),
1011    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1012)]
1013pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand, pub SourceSpan);
1014
1015impl Node for WhileOrUntilClauseCommand {}
1016
1017impl SourceLocation for WhileOrUntilClauseCommand {
1018    fn location(&self) -> Option<SourceSpan> {
1019        Some(self.2.clone())
1020    }
1021}
1022
1023impl Display for WhileOrUntilClauseCommand {
1024    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1025        write!(f, "{}; {}", self.0, self.1)
1026    }
1027}
1028
1029/// Encapsulates the definition of a shell function.
1030#[derive(Clone, Debug)]
1031#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1032#[cfg_attr(
1033    any(test, feature = "serde"),
1034    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1035)]
1036pub struct FunctionDefinition {
1037    /// The name of the function.
1038    pub fname: Word,
1039    /// The body of the function.
1040    pub body: FunctionBody,
1041}
1042
1043impl Node for FunctionDefinition {}
1044
1045// TODO(source-location): Account for the optional 'function' keyword that may
1046// precede the function name.
1047impl SourceLocation for FunctionDefinition {
1048    fn location(&self) -> Option<SourceSpan> {
1049        let start = self.fname.location();
1050        let end = self.body.location();
1051
1052        if let (Some(s), Some(e)) = (start, end) {
1053            Some(SourceSpan::within(&s, &e))
1054        } else {
1055            None
1056        }
1057    }
1058}
1059
1060impl Display for FunctionDefinition {
1061    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1062        writeln!(f, "{} () ", self.fname.value)?;
1063        write!(f, "{}", self.body)?;
1064        Ok(())
1065    }
1066}
1067
1068/// Encapsulates the body of a function definition.
1069#[derive(Clone, Debug)]
1070#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1071#[cfg_attr(
1072    any(test, feature = "serde"),
1073    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1074)]
1075pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
1076
1077impl Node for FunctionBody {}
1078
1079impl SourceLocation for FunctionBody {
1080    fn location(&self) -> Option<SourceSpan> {
1081        let cmd_span = self.0.location();
1082        let redirect_span = self.1.as_ref().and_then(SourceLocation::location);
1083
1084        match (cmd_span, redirect_span) {
1085            // If there's a redirect, include it in the span.
1086            (Some(cmd_span), Some(redirect_span)) => {
1087                Some(SourceSpan::within(&cmd_span, &redirect_span))
1088            }
1089            // Otherwise, just return the command span.
1090            (Some(cmd_span), None) => Some(cmd_span),
1091            _ => None,
1092        }
1093    }
1094}
1095
1096impl Display for FunctionBody {
1097    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1098        write!(f, "{}", self.0)?;
1099        if let Some(redirect_list) = &self.1 {
1100            write!(f, "{redirect_list}")?;
1101        }
1102
1103        Ok(())
1104    }
1105}
1106
1107/// A brace group, which groups commands together.
1108#[derive(Clone, Debug)]
1109#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1110#[cfg_attr(
1111    any(test, feature = "serde"),
1112    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1113)]
1114pub struct BraceGroupCommand {
1115    /// List of commands
1116    pub list: CompoundList,
1117    /// Location of the group
1118    pub loc: SourceSpan,
1119}
1120
1121impl Node for BraceGroupCommand {}
1122
1123impl SourceLocation for BraceGroupCommand {
1124    fn location(&self) -> Option<SourceSpan> {
1125        Some(self.loc.clone())
1126    }
1127}
1128
1129impl Display for BraceGroupCommand {
1130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1131        writeln!(f, "{{ ")?;
1132        write!(
1133            indenter::indented(f).with_str(DISPLAY_INDENT),
1134            "{}",
1135            self.list
1136        )?;
1137        writeln!(f)?;
1138        write!(f, "}}")?;
1139
1140        Ok(())
1141    }
1142}
1143
1144/// A do group, which groups commands together.
1145#[derive(Clone, Debug)]
1146#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1147#[cfg_attr(
1148    any(test, feature = "serde"),
1149    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1150)]
1151pub struct DoGroupCommand {
1152    /// List of commands
1153    pub list: CompoundList,
1154    /// Location of the group
1155    pub loc: SourceSpan,
1156}
1157
1158impl Display for DoGroupCommand {
1159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1160        writeln!(f, "do")?;
1161        write!(
1162            indenter::indented(f).with_str(DISPLAY_INDENT),
1163            "{}",
1164            self.list.terminated()
1165        )?;
1166        writeln!(f)?;
1167        write!(f, "done")
1168    }
1169}
1170
1171/// Represents the invocation of a simple command.
1172#[derive(Clone, Debug)]
1173#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1174#[cfg_attr(
1175    any(test, feature = "serde"),
1176    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1177)]
1178pub struct SimpleCommand {
1179    /// Optionally, a prefix to the command.
1180    #[cfg_attr(
1181        any(test, feature = "serde"),
1182        serde(skip_serializing_if = "Option::is_none", default)
1183    )]
1184    pub prefix: Option<CommandPrefix>,
1185    /// The name of the command to execute.
1186    #[cfg_attr(
1187        any(test, feature = "serde"),
1188        serde(skip_serializing_if = "Option::is_none", default)
1189    )]
1190    pub word_or_name: Option<Word>,
1191    /// Optionally, a suffix to the command.
1192    #[cfg_attr(
1193        any(test, feature = "serde"),
1194        serde(skip_serializing_if = "Option::is_none", default)
1195    )]
1196    pub suffix: Option<CommandSuffix>,
1197}
1198
1199impl Node for SimpleCommand {}
1200
1201impl SourceLocation for SimpleCommand {
1202    fn location(&self) -> Option<SourceSpan> {
1203        let mid = &self
1204            .word_or_name
1205            .as_ref()
1206            .and_then(SourceLocation::location);
1207        let start = self.prefix.as_ref().and_then(SourceLocation::location);
1208        let end = self.suffix.as_ref().and_then(SourceLocation::location);
1209
1210        match (start, mid, end) {
1211            (Some(start), _, Some(end)) => Some(SourceSpan::within(&start, &end)),
1212            (Some(start), Some(mid), None) => Some(SourceSpan::within(&start, mid)),
1213            (Some(start), None, None) => Some(start),
1214            (None, Some(mid), Some(end)) => Some(SourceSpan::within(mid, &end)),
1215            (None, Some(mid), None) => Some(mid.clone()),
1216            _ => None,
1217        }
1218    }
1219}
1220
1221impl Display for SimpleCommand {
1222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1223        let mut wrote_something = false;
1224
1225        if let Some(prefix) = &self.prefix {
1226            if wrote_something {
1227                write!(f, " ")?;
1228            }
1229
1230            write!(f, "{prefix}")?;
1231            wrote_something = true;
1232        }
1233
1234        if let Some(word_or_name) = &self.word_or_name {
1235            if wrote_something {
1236                write!(f, " ")?;
1237            }
1238
1239            write!(f, "{word_or_name}")?;
1240            wrote_something = true;
1241        }
1242
1243        if let Some(suffix) = &self.suffix {
1244            if wrote_something {
1245                write!(f, " ")?;
1246            }
1247
1248            write!(f, "{suffix}")?;
1249        }
1250
1251        Ok(())
1252    }
1253}
1254
1255/// Represents a prefix to a simple command.
1256#[derive(Clone, Debug, Default)]
1257#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1258#[cfg_attr(
1259    any(test, feature = "serde"),
1260    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1261)]
1262pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
1263
1264impl Node for CommandPrefix {}
1265
1266impl SourceLocation for CommandPrefix {
1267    fn location(&self) -> Option<SourceSpan> {
1268        let start = self.0.first().and_then(SourceLocation::location);
1269        let end = self.0.last().and_then(SourceLocation::location);
1270
1271        maybe_location(start.as_ref(), end.as_ref())
1272    }
1273}
1274
1275impl Display for CommandPrefix {
1276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1277        for (i, item) in self.0.iter().enumerate() {
1278            if i > 0 {
1279                write!(f, " ")?;
1280            }
1281
1282            write!(f, "{item}")?;
1283        }
1284        Ok(())
1285    }
1286}
1287
1288/// Represents a suffix to a simple command; a word argument, declaration, or
1289/// I/O redirection.
1290#[derive(Clone, Default, Debug)]
1291#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1292#[cfg_attr(
1293    any(test, feature = "serde"),
1294    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1295)]
1296pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
1297
1298impl Node for CommandSuffix {}
1299
1300impl SourceLocation for CommandSuffix {
1301    fn location(&self) -> Option<SourceSpan> {
1302        let start = self.0.first().and_then(SourceLocation::location);
1303        let end = self.0.last().and_then(SourceLocation::location);
1304
1305        maybe_location(start.as_ref(), end.as_ref())
1306    }
1307}
1308
1309impl Display for CommandSuffix {
1310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1311        for (i, item) in self.0.iter().enumerate() {
1312            if i > 0 {
1313                write!(f, " ")?;
1314            }
1315
1316            write!(f, "{item}")?;
1317        }
1318        Ok(())
1319    }
1320}
1321
1322/// Represents the I/O direction of a process substitution.
1323#[derive(Clone, Debug)]
1324#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1325#[cfg_attr(
1326    any(test, feature = "serde"),
1327    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1328)]
1329pub enum ProcessSubstitutionKind {
1330    /// The process is read from.
1331    Read,
1332    /// The process is written to.
1333    Write,
1334}
1335
1336impl Display for ProcessSubstitutionKind {
1337    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1338        match self {
1339            Self::Read => write!(f, "<"),
1340            Self::Write => write!(f, ">"),
1341        }
1342    }
1343}
1344
1345/// A prefix or suffix for a simple command.
1346#[derive(Clone, Debug)]
1347#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1348#[cfg_attr(
1349    any(test, feature = "serde"),
1350    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1351)]
1352pub enum CommandPrefixOrSuffixItem {
1353    /// An I/O redirection.
1354    IoRedirect(IoRedirect),
1355    /// A word.
1356    Word(Word),
1357    /// An assignment/declaration word.
1358    AssignmentWord(Assignment, Word),
1359    /// A process substitution.
1360    ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1361}
1362
1363impl Node for CommandPrefixOrSuffixItem {}
1364
1365impl SourceLocation for CommandPrefixOrSuffixItem {
1366    fn location(&self) -> Option<SourceSpan> {
1367        match self {
1368            Self::Word(w) => w.location(),
1369            Self::IoRedirect(io_redirect) => io_redirect.location(),
1370            Self::AssignmentWord(assignment, _word) => assignment.location(),
1371            // TODO(source-location): account for the kind token
1372            Self::ProcessSubstitution(_kind, cmd) => cmd.location(),
1373        }
1374    }
1375}
1376
1377impl Display for CommandPrefixOrSuffixItem {
1378    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1379        match self {
1380            Self::IoRedirect(io_redirect) => write!(f, "{io_redirect}"),
1381            Self::Word(word) => write!(f, "{word}"),
1382            Self::AssignmentWord(_assignment, word) => write!(f, "{word}"),
1383            Self::ProcessSubstitution(kind, subshell_command) => {
1384                write!(f, "{kind}({subshell_command})")
1385            }
1386        }
1387    }
1388}
1389
1390/// Encapsulates an assignment declaration.
1391#[derive(Clone, Debug)]
1392#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1393#[cfg_attr(
1394    any(test, feature = "serde"),
1395    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1396)]
1397pub struct Assignment {
1398    /// Name being assigned to.
1399    pub name: AssignmentName,
1400    /// Value being assigned.
1401    pub value: AssignmentValue,
1402    /// Whether or not to append to the preexisting value associated with the
1403    /// named variable.
1404    #[cfg_attr(
1405        any(test, feature = "serde"),
1406        serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1407    )]
1408    pub append: bool,
1409    /// Location of the assignment
1410    pub loc: SourceSpan,
1411}
1412
1413impl Node for Assignment {}
1414
1415impl SourceLocation for Assignment {
1416    fn location(&self) -> Option<SourceSpan> {
1417        Some(self.loc.clone())
1418    }
1419}
1420
1421impl Display for Assignment {
1422    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1423        write!(f, "{}", self.name)?;
1424        if self.append {
1425            write!(f, "+")?;
1426        }
1427        write!(f, "={}", self.value)
1428    }
1429}
1430
1431/// The target of an assignment.
1432#[derive(Clone, Debug)]
1433#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1434#[cfg_attr(
1435    any(test, feature = "serde"),
1436    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1437)]
1438pub enum AssignmentName {
1439    /// A named variable.
1440    VariableName(String),
1441    /// An element in a named array.
1442    ArrayElementName(String, String),
1443}
1444
1445impl AssignmentName {
1446    /// Returns the base name of the assignment, without any array indexing
1447    /// if present.
1448    pub fn base_name(&self) -> &str {
1449        match self {
1450            Self::VariableName(name) => name,
1451            Self::ArrayElementName(name, _index) => name,
1452        }
1453    }
1454}
1455
1456impl Display for AssignmentName {
1457    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1458        match self {
1459            Self::VariableName(name) => write!(f, "{name}"),
1460            Self::ArrayElementName(name, index) => {
1461                write!(f, "{name}[{index}]")
1462            }
1463        }
1464    }
1465}
1466
1467/// A value being assigned to a variable.
1468#[derive(Clone, Debug)]
1469#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1470#[cfg_attr(
1471    any(test, feature = "serde"),
1472    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1473)]
1474pub enum AssignmentValue {
1475    /// A scalar (word) value.
1476    Scalar(Word),
1477    /// An array of elements.
1478    Array(Vec<(Option<Word>, Word)>),
1479}
1480
1481impl Node for AssignmentValue {}
1482
1483impl SourceLocation for AssignmentValue {
1484    fn location(&self) -> Option<SourceSpan> {
1485        match self {
1486            Self::Scalar(word) => word.location(),
1487            Self::Array(words) => {
1488                // TODO(source-location): account for the surrounding
1489                // parentheses
1490                let first = words.first().and_then(|(_key, value)| value.location());
1491                let last = words.last().and_then(|(_key, value)| value.location());
1492                maybe_location(first.as_ref(), last.as_ref())
1493            }
1494        }
1495    }
1496}
1497
1498impl Display for AssignmentValue {
1499    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1500        match self {
1501            Self::Scalar(word) => write!(f, "{word}"),
1502            Self::Array(words) => {
1503                write!(f, "(")?;
1504                for (i, value) in words.iter().enumerate() {
1505                    if i > 0 {
1506                        write!(f, " ")?;
1507                    }
1508                    match value {
1509                        (Some(key), value) => write!(f, "[{key}]={value}")?,
1510                        (None, value) => write!(f, "{value}")?,
1511                    }
1512                }
1513                write!(f, ")")
1514            }
1515        }
1516    }
1517}
1518
1519/// A list of I/O redirections to be applied to a command.
1520#[derive(Clone, Debug)]
1521#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1522#[cfg_attr(
1523    any(test, feature = "serde"),
1524    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1525)]
1526pub struct RedirectList(pub Vec<IoRedirect>);
1527
1528impl Node for RedirectList {}
1529
1530impl SourceLocation for RedirectList {
1531    fn location(&self) -> Option<SourceSpan> {
1532        let first = self.0.first().and_then(SourceLocation::location);
1533        let last = self.0.last().and_then(SourceLocation::location);
1534        maybe_location(first.as_ref(), last.as_ref())
1535    }
1536}
1537
1538impl Display for RedirectList {
1539    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1540        for item in &self.0 {
1541            write!(f, "{item}")?;
1542        }
1543        Ok(())
1544    }
1545}
1546
1547/// A file descriptor number.
1548pub type IoFd = i32;
1549
1550/// An I/O redirection.
1551#[derive(Clone, Debug)]
1552#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1553#[cfg_attr(
1554    any(test, feature = "serde"),
1555    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1556)]
1557pub enum IoRedirect {
1558    /// Redirection to a file.
1559    File(Option<IoFd>, IoFileRedirectKind, IoFileRedirectTarget),
1560    /// Redirection from a here-document.
1561    HereDocument(Option<IoFd>, IoHereDocument),
1562    /// Redirection from a here-string.
1563    HereString(Option<IoFd>, Word),
1564    /// Redirection of both standard output and standard error (with optional
1565    /// append).
1566    OutputAndError(Word, bool),
1567}
1568
1569impl Node for IoRedirect {}
1570
1571impl SourceLocation for IoRedirect {
1572    fn location(&self) -> Option<SourceSpan> {
1573        // TODO(source-location): complete
1574        None
1575    }
1576}
1577
1578impl Display for IoRedirect {
1579    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1580        match self {
1581            Self::File(fd_num, kind, target) => {
1582                if let Some(fd_num) = fd_num {
1583                    write!(f, "{fd_num}")?;
1584                }
1585
1586                write!(f, "{kind} {target}")?;
1587            }
1588            Self::OutputAndError(target, append) => {
1589                write!(f, "&>")?;
1590                if *append {
1591                    write!(f, ">")?;
1592                }
1593                write!(f, " {target}")?;
1594            }
1595            Self::HereDocument(fd_num, here_doc) => {
1596                if let Some(fd_num) = fd_num {
1597                    write!(f, "{fd_num}")?;
1598                }
1599
1600                write!(f, "<<{here_doc}")?;
1601            }
1602            Self::HereString(fd_num, s) => {
1603                if let Some(fd_num) = fd_num {
1604                    write!(f, "{fd_num}")?;
1605                }
1606
1607                write!(f, "<<< {s}")?;
1608            }
1609        }
1610
1611        Ok(())
1612    }
1613}
1614
1615/// Kind of file I/O redirection.
1616#[derive(Clone, Debug)]
1617#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1618#[cfg_attr(
1619    any(test, feature = "serde"),
1620    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1621)]
1622pub enum IoFileRedirectKind {
1623    /// Read (`<`).
1624    Read,
1625    /// Write (`>`).
1626    Write,
1627    /// Append (`>>`).
1628    Append,
1629    /// Read and write (`<>`).
1630    ReadAndWrite,
1631    /// Clobber (`>|`).
1632    Clobber,
1633    /// Duplicate input (`<&`).
1634    DuplicateInput,
1635    /// Duplicate output (`>&`).
1636    DuplicateOutput,
1637}
1638
1639impl Display for IoFileRedirectKind {
1640    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1641        match self {
1642            Self::Read => write!(f, "<"),
1643            Self::Write => write!(f, ">"),
1644            Self::Append => write!(f, ">>"),
1645            Self::ReadAndWrite => write!(f, "<>"),
1646            Self::Clobber => write!(f, ">|"),
1647            Self::DuplicateInput => write!(f, "<&"),
1648            Self::DuplicateOutput => write!(f, ">&"),
1649        }
1650    }
1651}
1652
1653/// Target for an I/O file redirection.
1654#[derive(Clone, Debug)]
1655#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1656#[cfg_attr(
1657    any(test, feature = "serde"),
1658    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1659)]
1660pub enum IoFileRedirectTarget {
1661    /// Path to a file.
1662    Filename(Word),
1663    /// File descriptor number.
1664    Fd(IoFd),
1665    /// Process substitution: substitution with the results of executing the
1666    /// given command in a subshell.
1667    ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1668    /// Item to duplicate in a word redirection. After expansion, this could be
1669    /// a filename, a file descriptor, or a file descriptor and a "-" to
1670    /// indicate requested closure.
1671    Duplicate(Word),
1672}
1673
1674impl Display for IoFileRedirectTarget {
1675    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1676        match self {
1677            Self::Filename(word) => write!(f, "{word}"),
1678            Self::Fd(fd) => write!(f, "{fd}"),
1679            Self::ProcessSubstitution(kind, subshell_command) => {
1680                write!(f, "{kind}{subshell_command}")
1681            }
1682            Self::Duplicate(word) => write!(f, "{word}"),
1683        }
1684    }
1685}
1686
1687/// Represents an I/O here document.
1688#[derive(Clone, Debug)]
1689#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1690#[cfg_attr(
1691    any(test, feature = "serde"),
1692    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1693)]
1694pub struct IoHereDocument {
1695    /// Whether to remove leading tabs from the here document.
1696    #[cfg_attr(
1697        any(test, feature = "serde"),
1698        serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1699    )]
1700    pub remove_tabs: bool,
1701    /// Whether to basic-expand the contents of the here document.
1702    #[cfg_attr(
1703        any(test, feature = "serde"),
1704        serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1705    )]
1706    pub requires_expansion: bool,
1707    /// The delimiter marking the end of the here document.
1708    pub here_end: Word,
1709    /// The contents of the here document.
1710    pub doc: Word,
1711}
1712
1713impl Node for IoHereDocument {}
1714
1715impl SourceLocation for IoHereDocument {
1716    fn location(&self) -> Option<SourceSpan> {
1717        // TODO(source-location): complete
1718        None
1719    }
1720}
1721
1722impl Display for IoHereDocument {
1723    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1724        if self.remove_tabs {
1725            write!(f, "-")?;
1726        }
1727
1728        writeln!(f, "{}", self.here_end)?;
1729        write!(f, "{}", self.doc)?;
1730        writeln!(f, "{}", self.here_end)?;
1731
1732        Ok(())
1733    }
1734}
1735
1736/// A (non-extended) test expression.
1737#[derive(Clone, Debug)]
1738#[cfg_attr(
1739    any(test, feature = "serde"),
1740    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1741)]
1742pub enum TestExpr {
1743    /// Always evaluates to false.
1744    False,
1745    /// A literal string.
1746    Literal(String),
1747    /// Logical AND operation on two nested expressions.
1748    And(Box<Self>, Box<Self>),
1749    /// Logical OR operation on two nested expressions.
1750    Or(Box<Self>, Box<Self>),
1751    /// Logical NOT operation on a nested expression.
1752    Not(Box<Self>),
1753    /// A parenthesized expression.
1754    Parenthesized(Box<Self>),
1755    /// A unary test operation.
1756    UnaryTest(UnaryPredicate, String),
1757    /// A binary test operation.
1758    BinaryTest(BinaryPredicate, String, String),
1759}
1760
1761impl Node for TestExpr {}
1762
1763impl SourceLocation for TestExpr {
1764    fn location(&self) -> Option<SourceSpan> {
1765        // TODO(source-location): complete
1766        None
1767    }
1768}
1769
1770impl Display for TestExpr {
1771    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1772        match self {
1773            Self::False => Ok(()),
1774            Self::Literal(s) => write!(f, "{s}"),
1775            Self::And(left, right) => write!(f, "{left} -a {right}"),
1776            Self::Or(left, right) => write!(f, "{left} -o {right}"),
1777            Self::Not(expr) => write!(f, "! {expr}"),
1778            Self::Parenthesized(expr) => write!(f, "( {expr} )"),
1779            Self::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
1780            Self::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
1781        }
1782    }
1783}
1784
1785/// An extended test expression.
1786#[derive(Clone, Debug)]
1787#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1788#[cfg_attr(
1789    any(test, feature = "serde"),
1790    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1791)]
1792pub enum ExtendedTestExpr {
1793    /// Logical AND operation on two nested expressions.
1794    And(Box<Self>, Box<Self>),
1795    /// Logical OR operation on two nested expressions.
1796    Or(Box<Self>, Box<Self>),
1797    /// Logical NOT operation on a nested expression.
1798    Not(Box<Self>),
1799    /// A parenthesized expression.
1800    Parenthesized(Box<Self>),
1801    /// A unary test operation.
1802    UnaryTest(UnaryPredicate, Word),
1803    /// A binary test operation.
1804    BinaryTest(BinaryPredicate, Word, Word),
1805}
1806
1807impl Display for ExtendedTestExpr {
1808    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1809        match self {
1810            Self::And(left, right) => {
1811                write!(f, "{left} && {right}")
1812            }
1813            Self::Or(left, right) => {
1814                write!(f, "{left} || {right}")
1815            }
1816            Self::Not(expr) => {
1817                write!(f, "! {expr}")
1818            }
1819            Self::Parenthesized(expr) => {
1820                write!(f, "( {expr} )")
1821            }
1822            Self::UnaryTest(pred, word) => {
1823                write!(f, "{pred} {word}")
1824            }
1825            Self::BinaryTest(pred, left, right) => {
1826                write!(f, "{left} {pred} {right}")
1827            }
1828        }
1829    }
1830}
1831
1832/// An extended test expression command.
1833#[derive(Clone, Debug)]
1834#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1835#[cfg_attr(
1836    any(test, feature = "serde"),
1837    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1838)]
1839pub struct ExtendedTestExprCommand {
1840    /// The extended test expression
1841    pub expr: ExtendedTestExpr,
1842    /// Location of the expression
1843    pub loc: SourceSpan,
1844}
1845
1846impl Node for ExtendedTestExprCommand {}
1847
1848impl SourceLocation for ExtendedTestExprCommand {
1849    fn location(&self) -> Option<SourceSpan> {
1850        Some(self.loc.clone())
1851    }
1852}
1853
1854impl Display for ExtendedTestExprCommand {
1855    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1856        self.expr.fmt(f)
1857    }
1858}
1859
1860/// A unary predicate usable in an extended test expression.
1861#[derive(Clone, Debug)]
1862#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1863#[cfg_attr(
1864    any(test, feature = "serde"),
1865    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1866)]
1867pub enum UnaryPredicate {
1868    /// Computes if the operand is a path to an existing file.
1869    FileExists,
1870    /// Computes if the operand is a path to an existing block device file.
1871    FileExistsAndIsBlockSpecialFile,
1872    /// Computes if the operand is a path to an existing character device file.
1873    FileExistsAndIsCharSpecialFile,
1874    /// Computes if the operand is a path to an existing directory.
1875    FileExistsAndIsDir,
1876    /// Computes if the operand is a path to an existing regular file.
1877    FileExistsAndIsRegularFile,
1878    /// Computes if the operand is a path to an existing file with the setgid
1879    /// bit set.
1880    FileExistsAndIsSetgid,
1881    /// Computes if the operand is a path to an existing symbolic link.
1882    FileExistsAndIsSymlink,
1883    /// Computes if the operand is a path to an existing file with the sticky
1884    /// bit set.
1885    FileExistsAndHasStickyBit,
1886    /// Computes if the operand is a path to an existing FIFO file.
1887    FileExistsAndIsFifo,
1888    /// Computes if the operand is a path to an existing file that is readable.
1889    FileExistsAndIsReadable,
1890    /// Computes if the operand is a path to an existing file with a non-zero
1891    /// length.
1892    FileExistsAndIsNotZeroLength,
1893    /// Computes if the operand is a file descriptor that is an open terminal.
1894    FdIsOpenTerminal,
1895    /// Computes if the operand is a path to an existing file with the setuid
1896    /// bit set.
1897    FileExistsAndIsSetuid,
1898    /// Computes if the operand is a path to an existing file that is writable.
1899    FileExistsAndIsWritable,
1900    /// Computes if the operand is a path to an existing file that is
1901    /// executable.
1902    FileExistsAndIsExecutable,
1903    /// Computes if the operand is a path to an existing file owned by the
1904    /// current context's effective group ID.
1905    FileExistsAndOwnedByEffectiveGroupId,
1906    /// Computes if the operand is a path to an existing file that has been
1907    /// modified since last being read.
1908    FileExistsAndModifiedSinceLastRead,
1909    /// Computes if the operand is a path to an existing file owned by the
1910    /// current context's effective user ID.
1911    FileExistsAndOwnedByEffectiveUserId,
1912    /// Computes if the operand is a path to an existing socket file.
1913    FileExistsAndIsSocket,
1914    /// Computes if the operand is a 'set -o' option that is enabled.
1915    ShellOptionEnabled,
1916    /// Computes if the operand names a shell variable that is set and assigned
1917    /// a value.
1918    ShellVariableIsSetAndAssigned,
1919    /// Computes if the operand names a shell variable that is set and of
1920    /// nameref type.
1921    ShellVariableIsSetAndNameRef,
1922    /// Computes if the operand is a string with zero length.
1923    StringHasZeroLength,
1924    /// Computes if the operand is a string with non-zero length.
1925    StringHasNonZeroLength,
1926}
1927
1928impl Display for UnaryPredicate {
1929    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1930        match self {
1931            Self::FileExists => write!(f, "-e"),
1932            Self::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
1933            Self::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
1934            Self::FileExistsAndIsDir => write!(f, "-d"),
1935            Self::FileExistsAndIsRegularFile => write!(f, "-f"),
1936            Self::FileExistsAndIsSetgid => write!(f, "-g"),
1937            Self::FileExistsAndIsSymlink => write!(f, "-h"),
1938            Self::FileExistsAndHasStickyBit => write!(f, "-k"),
1939            Self::FileExistsAndIsFifo => write!(f, "-p"),
1940            Self::FileExistsAndIsReadable => write!(f, "-r"),
1941            Self::FileExistsAndIsNotZeroLength => write!(f, "-s"),
1942            Self::FdIsOpenTerminal => write!(f, "-t"),
1943            Self::FileExistsAndIsSetuid => write!(f, "-u"),
1944            Self::FileExistsAndIsWritable => write!(f, "-w"),
1945            Self::FileExistsAndIsExecutable => write!(f, "-x"),
1946            Self::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
1947            Self::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
1948            Self::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
1949            Self::FileExistsAndIsSocket => write!(f, "-S"),
1950            Self::ShellOptionEnabled => write!(f, "-o"),
1951            Self::ShellVariableIsSetAndAssigned => write!(f, "-v"),
1952            Self::ShellVariableIsSetAndNameRef => write!(f, "-R"),
1953            Self::StringHasZeroLength => write!(f, "-z"),
1954            Self::StringHasNonZeroLength => write!(f, "-n"),
1955        }
1956    }
1957}
1958
1959/// A binary predicate usable in an extended test expression.
1960#[derive(Clone, Debug)]
1961#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1962#[cfg_attr(
1963    any(test, feature = "serde"),
1964    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1965)]
1966pub enum BinaryPredicate {
1967    /// Computes if two files refer to the same device and inode numbers.
1968    FilesReferToSameDeviceAndInodeNumbers,
1969    /// Computes if the left file is newer than the right, or exists when the
1970    /// right does not.
1971    LeftFileIsNewerOrExistsWhenRightDoesNot,
1972    /// Computes if the left file is older than the right, or does not exist
1973    /// when the right does.
1974    LeftFileIsOlderOrDoesNotExistWhenRightDoes,
1975    /// Computes if a string exactly matches a pattern.
1976    StringExactlyMatchesPattern,
1977    /// Computes if a string does not exactly match a pattern.
1978    StringDoesNotExactlyMatchPattern,
1979    /// Computes if a string matches a regular expression.
1980    StringMatchesRegex,
1981    /// Computes if a string exactly matches another string.
1982    StringExactlyMatchesString,
1983    /// Computes if a string does not exactly match another string.
1984    StringDoesNotExactlyMatchString,
1985    /// Computes if a string contains a substring.
1986    StringContainsSubstring,
1987    /// Computes if the left value sorts before the right.
1988    LeftSortsBeforeRight,
1989    /// Computes if the left value sorts after the right.
1990    LeftSortsAfterRight,
1991    /// Computes if two values are equal via arithmetic comparison.
1992    ArithmeticEqualTo,
1993    /// Computes if two values are not equal via arithmetic comparison.
1994    ArithmeticNotEqualTo,
1995    /// Computes if the left value is less than the right via arithmetic
1996    /// comparison.
1997    ArithmeticLessThan,
1998    /// Computes if the left value is less than or equal to the right via
1999    /// arithmetic comparison.
2000    ArithmeticLessThanOrEqualTo,
2001    /// Computes if the left value is greater than the right via arithmetic
2002    /// comparison.
2003    ArithmeticGreaterThan,
2004    /// Computes if the left value is greater than or equal to the right via
2005    /// arithmetic comparison.
2006    ArithmeticGreaterThanOrEqualTo,
2007}
2008
2009impl Display for BinaryPredicate {
2010    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2011        match self {
2012            Self::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
2013            Self::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
2014            Self::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
2015            Self::StringExactlyMatchesPattern => write!(f, "=="),
2016            Self::StringDoesNotExactlyMatchPattern => write!(f, "!="),
2017            Self::StringMatchesRegex => write!(f, "=~"),
2018            Self::StringContainsSubstring => write!(f, "=~"),
2019            Self::StringExactlyMatchesString => write!(f, "=="),
2020            Self::StringDoesNotExactlyMatchString => write!(f, "!="),
2021            Self::LeftSortsBeforeRight => write!(f, "<"),
2022            Self::LeftSortsAfterRight => write!(f, ">"),
2023            Self::ArithmeticEqualTo => write!(f, "-eq"),
2024            Self::ArithmeticNotEqualTo => write!(f, "-ne"),
2025            Self::ArithmeticLessThan => write!(f, "-lt"),
2026            Self::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
2027            Self::ArithmeticGreaterThan => write!(f, "-gt"),
2028            Self::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
2029        }
2030    }
2031}
2032
2033/// Represents a shell word.
2034#[derive(Clone, Debug)]
2035#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2036#[cfg_attr(
2037    any(test, feature = "serde"),
2038    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2039)]
2040pub struct Word {
2041    /// Raw text of the word.
2042    pub value: String,
2043    /// Location of the word
2044    pub loc: Option<SourceSpan>,
2045}
2046
2047impl Node for Word {}
2048
2049impl SourceLocation for Word {
2050    fn location(&self) -> Option<SourceSpan> {
2051        self.loc.clone()
2052    }
2053}
2054
2055impl Display for Word {
2056    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2057        write!(f, "{}", self.value)
2058    }
2059}
2060
2061impl From<&tokenizer::Token> for Word {
2062    fn from(t: &tokenizer::Token) -> Self {
2063        match t {
2064            tokenizer::Token::Word(value, loc) => Self {
2065                value: value.clone(),
2066                loc: Some(loc.clone()),
2067            },
2068            tokenizer::Token::Operator(value, loc) => Self {
2069                value: value.clone(),
2070                loc: Some(loc.clone()),
2071            },
2072        }
2073    }
2074}
2075
2076impl From<String> for Word {
2077    fn from(s: String) -> Self {
2078        Self {
2079            value: s,
2080            loc: None,
2081        }
2082    }
2083}
2084
2085impl AsRef<str> for Word {
2086    fn as_ref(&self) -> &str {
2087        &self.value
2088    }
2089}
2090
2091impl Word {
2092    /// Constructs a new `Word` from a given string.
2093    pub fn new(s: &str) -> Self {
2094        Self {
2095            value: s.to_owned(),
2096            loc: None,
2097        }
2098    }
2099
2100    /// Constructs a new `Word` from a given string and location.
2101    pub fn with_location(s: &str, loc: &SourceSpan) -> Self {
2102        Self {
2103            value: s.to_owned(),
2104            loc: Some(loc.to_owned()),
2105        }
2106    }
2107
2108    /// Returns the raw text of the word, consuming the `Word`.
2109    pub fn flatten(&self) -> String {
2110        self.value.clone()
2111    }
2112}
2113
2114/// Encapsulates an unparsed arithmetic expression.
2115#[derive(Clone, Debug)]
2116#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2117#[cfg_attr(
2118    any(test, feature = "serde"),
2119    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2120)]
2121pub struct UnexpandedArithmeticExpr {
2122    /// The raw text of the expression.
2123    pub value: String,
2124}
2125
2126impl Display for UnexpandedArithmeticExpr {
2127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2128        write!(f, "{}", self.value)
2129    }
2130}
2131
2132/// An arithmetic expression.
2133#[derive(Clone, Debug)]
2134#[cfg_attr(
2135    any(test, feature = "serde"),
2136    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2137)]
2138pub enum ArithmeticExpr {
2139    /// A literal integer value.
2140    Literal(i64),
2141    /// A dereference of a variable or array element.
2142    Reference(ArithmeticTarget),
2143    /// A unary operation on an the result of a given nested expression.
2144    UnaryOp(UnaryOperator, Box<Self>),
2145    /// A binary operation on two nested expressions.
2146    BinaryOp(BinaryOperator, Box<Self>, Box<Self>),
2147    /// A ternary conditional expression.
2148    Conditional(Box<Self>, Box<Self>, Box<Self>),
2149    /// An assignment operation.
2150    Assignment(ArithmeticTarget, Box<Self>),
2151    /// A binary assignment operation.
2152    BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<Self>),
2153    /// A unary assignment operation.
2154    UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
2155}
2156
2157impl Node for ArithmeticExpr {}
2158
2159impl SourceLocation for ArithmeticExpr {
2160    fn location(&self) -> Option<SourceSpan> {
2161        // TODO(source-location): complete and add loc for literal
2162        None
2163    }
2164}
2165
2166#[cfg(feature = "arbitrary")]
2167impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
2168    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2169        let variant = u.choose(&[
2170            "Literal",
2171            "Reference",
2172            "UnaryOp",
2173            "BinaryOp",
2174            "Conditional",
2175            "Assignment",
2176            "BinaryAssignment",
2177            "UnaryAssignment",
2178        ])?;
2179
2180        match *variant {
2181            "Literal" => Ok(Self::Literal(i64::arbitrary(u)?)),
2182            "Reference" => Ok(Self::Reference(ArithmeticTarget::arbitrary(u)?)),
2183            "UnaryOp" => Ok(Self::UnaryOp(
2184                UnaryOperator::arbitrary(u)?,
2185                Box::new(Self::arbitrary(u)?),
2186            )),
2187            "BinaryOp" => Ok(Self::BinaryOp(
2188                BinaryOperator::arbitrary(u)?,
2189                Box::new(Self::arbitrary(u)?),
2190                Box::new(Self::arbitrary(u)?),
2191            )),
2192            "Conditional" => Ok(Self::Conditional(
2193                Box::new(Self::arbitrary(u)?),
2194                Box::new(Self::arbitrary(u)?),
2195                Box::new(Self::arbitrary(u)?),
2196            )),
2197            "Assignment" => Ok(Self::Assignment(
2198                ArithmeticTarget::arbitrary(u)?,
2199                Box::new(Self::arbitrary(u)?),
2200            )),
2201            "BinaryAssignment" => Ok(Self::BinaryAssignment(
2202                BinaryOperator::arbitrary(u)?,
2203                ArithmeticTarget::arbitrary(u)?,
2204                Box::new(Self::arbitrary(u)?),
2205            )),
2206            "UnaryAssignment" => Ok(Self::UnaryAssignment(
2207                UnaryAssignmentOperator::arbitrary(u)?,
2208                ArithmeticTarget::arbitrary(u)?,
2209            )),
2210            _ => unreachable!(),
2211        }
2212    }
2213}
2214
2215impl Display for ArithmeticExpr {
2216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2217        match self {
2218            Self::Literal(literal) => write!(f, "{literal}"),
2219            Self::Reference(target) => write!(f, "{target}"),
2220            Self::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
2221            Self::BinaryOp(op, left, right) => {
2222                if matches!(op, BinaryOperator::Comma) {
2223                    write!(f, "{left}{op} {right}")
2224                } else {
2225                    write!(f, "{left} {op} {right}")
2226                }
2227            }
2228            Self::Conditional(condition, if_branch, else_branch) => {
2229                write!(f, "{condition} ? {if_branch} : {else_branch}")
2230            }
2231            Self::Assignment(target, value) => write!(f, "{target} = {value}"),
2232            Self::BinaryAssignment(op, target, operand) => {
2233                write!(f, "{target} {op}= {operand}")
2234            }
2235            Self::UnaryAssignment(op, target) => match op {
2236                UnaryAssignmentOperator::PrefixIncrement
2237                | UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
2238                UnaryAssignmentOperator::PostfixIncrement
2239                | UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
2240            },
2241        }
2242    }
2243}
2244
2245/// A binary arithmetic operator.
2246#[derive(Clone, Copy, Debug)]
2247#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2248#[cfg_attr(
2249    any(test, feature = "serde"),
2250    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2251)]
2252pub enum BinaryOperator {
2253    /// Exponentiation (e.g., `x ** y`).
2254    Power,
2255    /// Multiplication (e.g., `x * y`).
2256    Multiply,
2257    /// Division (e.g., `x / y`).
2258    Divide,
2259    /// Modulo (e.g., `x % y`).
2260    Modulo,
2261    /// Comma (e.g., `x, y`).
2262    Comma,
2263    /// Addition (e.g., `x + y`).
2264    Add,
2265    /// Subtraction (e.g., `x - y`).
2266    Subtract,
2267    /// Bitwise left shift (e.g., `x << y`).
2268    ShiftLeft,
2269    /// Bitwise right shift (e.g., `x >> y`).
2270    ShiftRight,
2271    /// Less than (e.g., `x < y`).
2272    LessThan,
2273    /// Less than or equal to (e.g., `x <= y`).
2274    LessThanOrEqualTo,
2275    /// Greater than (e.g., `x > y`).
2276    GreaterThan,
2277    /// Greater than or equal to (e.g., `x >= y`).
2278    GreaterThanOrEqualTo,
2279    /// Equals (e.g., `x == y`).
2280    Equals,
2281    /// Not equals (e.g., `x != y`).
2282    NotEquals,
2283    /// Bitwise AND (e.g., `x & y`).
2284    BitwiseAnd,
2285    /// Bitwise exclusive OR (xor) (e.g., `x ^ y`).
2286    BitwiseXor,
2287    /// Bitwise OR (e.g., `x | y`).
2288    BitwiseOr,
2289    /// Logical AND (e.g., `x && y`).
2290    LogicalAnd,
2291    /// Logical OR (e.g., `x || y`).
2292    LogicalOr,
2293}
2294
2295impl Display for BinaryOperator {
2296    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2297        match self {
2298            Self::Power => write!(f, "**"),
2299            Self::Multiply => write!(f, "*"),
2300            Self::Divide => write!(f, "/"),
2301            Self::Modulo => write!(f, "%"),
2302            Self::Comma => write!(f, ","),
2303            Self::Add => write!(f, "+"),
2304            Self::Subtract => write!(f, "-"),
2305            Self::ShiftLeft => write!(f, "<<"),
2306            Self::ShiftRight => write!(f, ">>"),
2307            Self::LessThan => write!(f, "<"),
2308            Self::LessThanOrEqualTo => write!(f, "<="),
2309            Self::GreaterThan => write!(f, ">"),
2310            Self::GreaterThanOrEqualTo => write!(f, ">="),
2311            Self::Equals => write!(f, "=="),
2312            Self::NotEquals => write!(f, "!="),
2313            Self::BitwiseAnd => write!(f, "&"),
2314            Self::BitwiseXor => write!(f, "^"),
2315            Self::BitwiseOr => write!(f, "|"),
2316            Self::LogicalAnd => write!(f, "&&"),
2317            Self::LogicalOr => write!(f, "||"),
2318        }
2319    }
2320}
2321
2322/// A unary arithmetic operator.
2323#[derive(Clone, Copy, Debug)]
2324#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2325#[cfg_attr(
2326    any(test, feature = "serde"),
2327    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2328)]
2329pub enum UnaryOperator {
2330    /// Unary plus (e.g., `+x`).
2331    UnaryPlus,
2332    /// Unary minus (e.g., `-x`).
2333    UnaryMinus,
2334    /// Bitwise not (e.g., `~x`).
2335    BitwiseNot,
2336    /// Logical not (e.g., `!x`).
2337    LogicalNot,
2338}
2339
2340impl Display for UnaryOperator {
2341    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2342        match self {
2343            Self::UnaryPlus => write!(f, "+"),
2344            Self::UnaryMinus => write!(f, "-"),
2345            Self::BitwiseNot => write!(f, "~"),
2346            Self::LogicalNot => write!(f, "!"),
2347        }
2348    }
2349}
2350
2351/// A unary arithmetic assignment operator.
2352#[derive(Clone, Copy, Debug)]
2353#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2354#[cfg_attr(
2355    any(test, feature = "serde"),
2356    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2357)]
2358pub enum UnaryAssignmentOperator {
2359    /// Prefix increment (e.g., `++x`).
2360    PrefixIncrement,
2361    /// Prefix increment (e.g., `--x`).
2362    PrefixDecrement,
2363    /// Postfix increment (e.g., `x++`).
2364    PostfixIncrement,
2365    /// Postfix decrement (e.g., `x--`).
2366    PostfixDecrement,
2367}
2368
2369impl Display for UnaryAssignmentOperator {
2370    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2371        match self {
2372            Self::PrefixIncrement => write!(f, "++"),
2373            Self::PrefixDecrement => write!(f, "--"),
2374            Self::PostfixIncrement => write!(f, "++"),
2375            Self::PostfixDecrement => write!(f, "--"),
2376        }
2377    }
2378}
2379
2380/// Identifies the target of an arithmetic assignment expression.
2381#[derive(Clone, Debug)]
2382#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2383#[cfg_attr(
2384    any(test, feature = "serde"),
2385    derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2386)]
2387pub enum ArithmeticTarget {
2388    /// A named variable.
2389    Variable(String),
2390    /// An element in an array.
2391    ArrayElement(String, Box<ArithmeticExpr>),
2392}
2393
2394impl Node for ArithmeticTarget {}
2395
2396impl SourceLocation for ArithmeticTarget {
2397    fn location(&self) -> Option<SourceSpan> {
2398        // TODO(source-location): complete and add loc
2399        None
2400    }
2401}
2402
2403impl Display for ArithmeticTarget {
2404    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2405        match self {
2406            Self::Variable(name) => write!(f, "{name}"),
2407            Self::ArrayElement(name, index) => write!(f, "{name}[{index}]"),
2408        }
2409    }
2410}
2411
2412#[cfg(test)]
2413#[allow(clippy::panic)]
2414mod tests {
2415    use std::io::BufReader;
2416
2417    use super::*;
2418    use crate::{ParserOptions, SourcePosition};
2419
2420    fn parse(input: &str) -> Program {
2421        let reader = BufReader::new(input.as_bytes());
2422        let mut parser = crate::Parser::new(reader, &ParserOptions::default());
2423        parser.parse_program().unwrap()
2424    }
2425
2426    #[test]
2427    fn program_source_loc() {
2428        const INPUT: &str = r"echo hi
2429echo there
2430";
2431
2432        let loc = parse(INPUT).location().unwrap();
2433
2434        assert_eq!(
2435            *(loc.start),
2436            SourcePosition {
2437                line: 1,
2438                column: 1,
2439                index: 0
2440            }
2441        );
2442        assert_eq!(
2443            *(loc.end),
2444            SourcePosition {
2445                line: 2,
2446                column: 11,
2447                index: 18
2448            }
2449        );
2450    }
2451
2452    #[test]
2453    fn function_def_loc() {
2454        const INPUT: &str = r"my_func() {
2455  echo hi
2456  echo there
2457}
2458
2459my_func
2460";
2461
2462        let program = parse(INPUT);
2463
2464        let Command::Function(func_def) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2465            panic!("expected function definition");
2466        };
2467
2468        let loc = func_def.location().unwrap();
2469
2470        assert_eq!(
2471            *(loc.start),
2472            SourcePosition {
2473                line: 1,
2474                column: 1,
2475                index: 0
2476            }
2477        );
2478        assert_eq!(
2479            *(loc.end),
2480            SourcePosition {
2481                line: 4,
2482                column: 2,
2483                index: 36
2484            }
2485        );
2486    }
2487
2488    #[test]
2489    fn simple_cmd_loc() {
2490        const INPUT: &str = r"var=value somecmd arg1 arg2
2491";
2492
2493        let program = parse(INPUT);
2494
2495        let Command::Simple(cmd) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2496            panic!("expected function definition");
2497        };
2498
2499        let loc = cmd.location().unwrap();
2500
2501        assert_eq!(
2502            *(loc.start),
2503            SourcePosition {
2504                line: 1,
2505                column: 1,
2506                index: 0
2507            }
2508        );
2509        assert_eq!(
2510            *(loc.end),
2511            SourcePosition {
2512                line: 1,
2513                column: 28,
2514                index: 27
2515            }
2516        );
2517    }
2518}