Skip to main content

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