Skip to main content

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