Skip to main content

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