brush_parser/
ast.rs

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