Skip to main content

bash_ast/
ast.rs

1//! Typed AST for bash, mirroring `tree-sitter-bash`'s `node-types.json` 1:1.
2//!
3//! Every named node kind in the grammar has a representation here. The three
4//! supertypes (`_statement`, `_expression`, `_primary_expression`) become Rust
5//! enums; the 12 nodes with named fields become structs with field-named
6//! members; everything else gets a leaf struct carrying its text and span.
7//!
8//! Where the grammar allows a wider type set than a supertype enum covers
9//! (e.g. `binary_expression.left` permits `subscript` and `variable_name` in
10//! addition to `_expression`), we define small ad-hoc operand enums rather
11//! than widening `Expression` itself — keeps the mapping faithful.
12//!
13//! @yah:ticket(R155-T1, "Add Deserialize + PartialEq to bash-ast::ast::Program + BashShape; round-trip JSON test")
14//! @yah:assignee(agent:claude)
15//! @yah:at(2026-05-13T00:20:06Z)
16//! @yah:status(review)
17//! @yah:phase(P1)
18//! @yah:parent(R155)
19//! @arch:see(.yah/docs/working/W113-yah-bash-snapshot-and-permissions.md)
20
21use serde::{Deserialize, Serialize};
22
23/// Source byte + (row, column) span. Owned (no lifetime) so the AST can be
24/// passed around freely.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26pub struct Span {
27    pub byte_start: usize,
28    pub byte_end: usize,
29    pub start: Point,
30    pub end: Point,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34pub struct Point {
35    pub row: usize,
36    pub column: usize,
37}
38
39impl From<tree_sitter::Range> for Span {
40    fn from(r: tree_sitter::Range) -> Self {
41        Span {
42            byte_start: r.start_byte,
43            byte_end: r.end_byte,
44            start: Point {
45                row: r.start_point.row,
46                column: r.start_point.column,
47            },
48            end: Point {
49                row: r.end_point.row,
50                column: r.end_point.column,
51            },
52        }
53    }
54}
55
56// ============================================================================
57// Root
58// ============================================================================
59
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61pub struct Program {
62    pub span: Span,
63    pub statements: Vec<Statement>,
64    pub comments: Vec<Comment>,
65}
66
67// ============================================================================
68// Supertype: Statement (`_statement`)
69// ============================================================================
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub enum Statement {
73    Command(Command),
74    Pipeline(Pipeline),
75    List(List),
76    CompoundStatement(CompoundStatement),
77    Subshell(Subshell),
78    If(IfStatement),
79    While(WhileStatement),
80    For(ForStatement),
81    CStyleFor(CStyleForStatement),
82    Case(CaseStatement),
83    FunctionDefinition(FunctionDefinition),
84    Redirected(RedirectedStatement),
85    Declaration(DeclarationCommand),
86    Unset(UnsetCommand),
87    Test(TestCommand),
88    Negated(NegatedCommand),
89    VariableAssignment(VariableAssignment),
90    VariableAssignments(VariableAssignments),
91}
92
93// ============================================================================
94// Supertype: Expression (`_expression`) — operands inside test/arithmetic contexts
95// ============================================================================
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub enum Expression {
99    Primary(PrimaryExpression),
100    Binary(BinaryExpression),
101    Concatenation(Concatenation),
102    Parenthesized(ParenthesizedExpression),
103    Postfix(PostfixExpression),
104    Ternary(TernaryExpression),
105    Unary(UnaryExpression),
106    /// `word` is listed directly under `_expression` in the grammar as well
107    /// as under `_primary_expression`; we collapse both occurrences into
108    /// `Primary(PrimaryExpression::Word(_))`. This variant exists only for
109    /// completeness if the converter ever sees a `word` at the expression
110    /// level outside a primary context.
111    Word(Word),
112}
113
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub enum PrimaryExpression {
116    AnsiCString(AnsiCString),
117    ArithmeticExpansion(ArithmeticExpansion),
118    BraceExpression(BraceExpression),
119    CommandSubstitution(CommandSubstitution),
120    Expansion(Expansion),
121    Number(Number),
122    ProcessSubstitution(ProcessSubstitution),
123    RawString(RawString),
124    SimpleExpansion(SimpleExpansion),
125    /// Renamed from grammar's `string` to avoid clashing with `std::String`.
126    StringNode(StringNode),
127    TranslatedString(TranslatedString),
128    Word(Word),
129}
130
131// ============================================================================
132// Operand enums for fields with mixed type sets
133// ============================================================================
134
135/// Permitted operand at `binary_expression.left`, `unary_expression`,
136/// `postfix_expression`, `ternary_expression.{condition,consequence,alternative}`.
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub enum TestOperand {
139    Expression(Box<Expression>),
140    CommandSubstitution(CommandSubstitution),
141    Expansion(Expansion),
142    Number(Number),
143    SimpleExpansion(SimpleExpansion),
144    StringNode(StringNode),
145    Subscript(Box<Subscript>),
146    VariableName(VariableName),
147}
148
149/// Permitted on `binary_expression.right` — adds `extglob_pattern` and `regex`.
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151pub enum BinaryRightOperand {
152    Expression(Box<Expression>),
153    CommandSubstitution(CommandSubstitution),
154    Expansion(Expansion),
155    ExtglobPattern(ExtglobPattern),
156    Number(Number),
157    Regex(Regex),
158    SimpleExpansion(SimpleExpansion),
159    StringNode(StringNode),
160    Subscript(Box<Subscript>),
161    VariableName(VariableName),
162}
163
164/// Permitted in the `value` field of `case_statement` and `case_item.value`.
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
166pub enum CasePattern {
167    Primary(PrimaryExpression),
168    Concatenation(Concatenation),
169    ExtglobPattern(ExtglobPattern),
170}
171
172/// Argument list slot of `command` — adds `$`, `==`, `=~`, `regex` as bare tokens.
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub enum CommandArgument {
175    Primary(PrimaryExpression),
176    Concatenation(Concatenation),
177    Regex(Regex),
178    /// A literal `$`, `==`, or `=~` appearing as a bare argument.
179    Operator { span: Span, text: String },
180}
181
182/// Permitted as the body of a `function_definition`.
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub enum FunctionBody {
185    Compound(CompoundStatement),
186    If(IfStatement),
187    Subshell(Subshell),
188    Test(TestCommand),
189}
190
191/// Body of a `for` / `while` loop.
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub enum LoopBody {
194    Compound(CompoundStatement),
195    DoGroup(DoGroup),
196}
197
198/// What appears in `variable_assignment.value`.
199#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
200pub enum AssignmentValue {
201    Primary(PrimaryExpression),
202    Array(Array),
203    Binary(BinaryExpression),
204    Concatenation(Concatenation),
205    Parenthesized(ParenthesizedExpression),
206    Postfix(PostfixExpression),
207    Unary(UnaryExpression),
208    Nested(Box<VariableAssignment>),
209}
210
211/// Element inside an `expansion` body.
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213pub enum ExpansionElement {
214    Primary(PrimaryExpression),
215    Array(Array),
216    Binary(BinaryExpression),
217    Concatenation(Concatenation),
218    Parenthesized(ParenthesizedExpression),
219    Regex(Regex),
220    SpecialVariableName(SpecialVariableName),
221    Subscript(Subscript),
222    VariableName(VariableName),
223}
224
225/// What `command_substitution` wraps: a sequence of statements.
226pub type CommandSubBody = Vec<Statement>;
227
228/// Permitted in a `redirected_statement.redirect` slot.
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
230pub enum Redirect {
231    File(FileRedirect),
232    Heredoc(HeredocRedirect),
233    Herestring(HerestringRedirect),
234}
235
236/// Restricted redirect set used by `command.redirect` and `function_definition.redirect`.
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
238pub enum SimpleRedirect {
239    File(FileRedirect),
240    Herestring(HerestringRedirect),
241}
242
243/// What `subscript.index` may contain.
244#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245pub enum SubscriptIndex {
246    Primary(PrimaryExpression),
247    Binary(BinaryExpression),
248    Concatenation(Concatenation),
249    Parenthesized(ParenthesizedExpression),
250    Unary(UnaryExpression),
251}
252
253/// Name slot of `variable_assignment` — either a plain name or a subscript.
254#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
255pub enum AssignmentTarget {
256    VariableName(VariableName),
257    Subscript(Subscript),
258}
259
260/// Single condition step in `if_statement.condition` / `while_statement.condition`
261/// (the grammar admits a statement plus a terminator token).
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263pub enum ConditionPart {
264    Statement(Box<Statement>),
265    /// A literal `&`, `;`, or `;;` terminator.
266    Terminator { span: Span, text: String },
267}
268
269// ============================================================================
270// Statement structs
271// ============================================================================
272
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274pub struct Command {
275    pub span: Span,
276    pub name: CommandName,
277    pub arguments: Vec<CommandArgument>,
278    pub redirects: Vec<SimpleRedirect>,
279    /// `var=val cmd` — leading assignments captured as anonymous children.
280    pub leading_assignments: Vec<VariableAssignment>,
281    /// Bare subshell children — rare but legal.
282    pub subshells: Vec<Subshell>,
283}
284
285#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286pub struct Pipeline {
287    pub span: Span,
288    pub stages: Vec<Statement>,
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292pub struct List {
293    pub span: Span,
294    pub left: Box<Statement>,
295    pub operator: ListOperator,
296    pub right: Box<Statement>,
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
300pub enum ListOperator {
301    /// `&&`
302    And,
303    /// `||`
304    Or,
305}
306
307#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
308pub struct CompoundStatement {
309    pub span: Span,
310    pub statements: Vec<Statement>,
311}
312
313#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
314pub struct Subshell {
315    pub span: Span,
316    pub statements: Vec<Statement>,
317}
318
319#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
320pub struct IfStatement {
321    pub span: Span,
322    pub condition: Vec<ConditionPart>,
323    pub body: Vec<Statement>,
324    pub elif_clauses: Vec<ElifClause>,
325    pub else_clause: Option<ElseClause>,
326}
327
328#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
329pub struct ElifClause {
330    pub span: Span,
331    pub condition: Vec<ConditionPart>,
332    pub body: Vec<Statement>,
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336pub struct ElseClause {
337    pub span: Span,
338    pub body: Vec<Statement>,
339}
340
341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
342pub struct WhileStatement {
343    pub span: Span,
344    pub condition: Vec<ConditionPart>,
345    pub body: LoopBody,
346}
347
348#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
349pub struct ForStatement {
350    pub span: Span,
351    pub variable: VariableName,
352    pub values: Vec<CasePattern>,
353    pub body: LoopBody,
354}
355
356#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
357pub struct CStyleForStatement {
358    pub span: Span,
359    pub initializer: Vec<CStyleForPart>,
360    pub condition: Vec<CStyleForPart>,
361    pub update: Vec<CStyleForPart>,
362    pub body: LoopBody,
363}
364
365/// Element inside the parenthesized header of a C-style `for ((..;..;..))`.
366#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
367pub enum CStyleForPart {
368    Comma { span: Span },
369    Binary(BinaryExpression),
370    CommandSubstitution(CommandSubstitution),
371    Expansion(Expansion),
372    Number(Number),
373    Parenthesized(ParenthesizedExpression),
374    Postfix(PostfixExpression),
375    SimpleExpansion(SimpleExpansion),
376    StringNode(StringNode),
377    Unary(UnaryExpression),
378    VariableAssignment(VariableAssignment),
379    Word(Word),
380}
381
382#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
383pub struct CaseStatement {
384    pub span: Span,
385    pub value: CasePattern,
386    pub items: Vec<CaseItem>,
387}
388
389#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
390pub struct CaseItem {
391    pub span: Span,
392    pub patterns: Vec<CasePattern>,
393    pub body: Vec<Statement>,
394    pub termination: Option<CaseTermination>,
395    pub fallthrough: Option<CaseFallthrough>,
396}
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
399pub enum CaseTermination {
400    /// `;;`
401    DoubleSemi,
402}
403
404#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
405pub enum CaseFallthrough {
406    /// `;&`
407    Semiamp,
408    /// `;;&`
409    DoubleSemiAmp,
410}
411
412#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
413pub struct FunctionDefinition {
414    pub span: Span,
415    pub name: Word,
416    pub body: FunctionBody,
417    pub redirect: Option<SimpleRedirect>,
418}
419
420#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
421pub struct RedirectedStatement {
422    pub span: Span,
423    pub body: Option<Box<Statement>>,
424    pub redirects: Vec<Redirect>,
425    /// A bare herestring redirect that appears as a direct child (no `body`).
426    pub bare_herestring: Option<HerestringRedirect>,
427}
428
429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
430pub struct DeclarationCommand {
431    pub span: Span,
432    /// `declare`, `typeset`, `export`, `readonly`, `local`, plus flag words
433    /// and the resulting assignments — the grammar bundles them as ordered
434    /// anonymous children, so we keep them as a flat list.
435    pub parts: Vec<DeclarationPart>,
436}
437
438#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
439pub enum DeclarationPart {
440    Word(Word),
441    VariableName(VariableName),
442    VariableAssignment(VariableAssignment),
443}
444
445#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
446pub struct UnsetCommand {
447    pub span: Span,
448    pub parts: Vec<UnsetPart>,
449}
450
451#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
452pub enum UnsetPart {
453    Word(Word),
454    VariableName(VariableName),
455}
456
457#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
458pub struct TestCommand {
459    pub span: Span,
460    /// Body of `[ … ]`, `[[ … ]]`, or `(( … ))` — one or more expressions.
461    pub expressions: Vec<Expression>,
462}
463
464#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
465pub struct NegatedCommand {
466    pub span: Span,
467    pub inner: Box<Statement>,
468}
469
470#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
471pub struct VariableAssignment {
472    pub span: Span,
473    pub name: AssignmentTarget,
474    pub value: AssignmentValue,
475}
476
477/// `a=1 b=2 c=3` as a free-standing prefix without a command.
478#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
479pub struct VariableAssignments {
480    pub span: Span,
481    pub assignments: Vec<VariableAssignment>,
482}
483
484// ============================================================================
485// Expression structs
486// ============================================================================
487
488#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
489pub struct BinaryExpression {
490    pub span: Span,
491    pub left: Option<TestOperand>,
492    pub operator: BinaryOperator,
493    pub right: Vec<BinaryRightOperand>,
494    /// Anonymous children (binary_expression / expansion / number /
495    /// variable_name) that appear outside the named field slots.
496    pub extras: Vec<BinaryExtra>,
497}
498
499#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
500pub enum BinaryExtra {
501    Binary(Box<BinaryExpression>),
502    Expansion(Expansion),
503    Number(Number),
504    VariableName(VariableName),
505}
506
507/// All operators the grammar lists for `binary_expression`. We keep the raw
508/// `text` alongside so callers can pretty-print without a table lookup.
509#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
510pub struct BinaryOperator {
511    pub span: Span,
512    pub kind: BinaryOperatorKind,
513    /// Captured verbatim — for `test_operator` this is e.g. `-eq`, `-lt`, …
514    pub text: String,
515}
516
517#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
518pub enum BinaryOperatorKind {
519    NotEq, Mod, ModAssign, BitAnd, AndAnd, BitAndAssign, Mul, Pow, PowAssign,
520    MulAssign, Plus, PlusAssign, Minus, MinusAssign, DashA, DashO, Div,
521    DivAssign, Lt, ShlOrHeredoc, ShlAssign, Le, Assign, EqEq, MatchRegex,
522    Gt, Ge, ShrOrHeredocAppend, ShrAssign, BitXor, BitXorAssign, TestOp,
523    BitOr, BitOrAssign, OrOr,
524}
525
526#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
527pub struct Concatenation {
528    pub span: Span,
529    pub parts: Vec<PrimaryExpression>,
530}
531
532#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
533pub struct ParenthesizedExpression {
534    pub span: Span,
535    pub inner: Box<Expression>,
536}
537
538#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
539pub struct PostfixExpression {
540    pub span: Span,
541    pub inner: TestOperand,
542    pub operator: PostfixOperator,
543}
544
545#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
546pub enum PostfixOperator {
547    /// `++`
548    Inc,
549    /// `--`
550    Dec,
551}
552
553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
554pub struct TernaryExpression {
555    pub span: Span,
556    pub condition: TestOperand,
557    pub consequence: TestOperand,
558    pub alternative: TestOperand,
559}
560
561#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
562pub struct UnaryExpression {
563    pub span: Span,
564    pub operator: UnaryOperator,
565    pub inner: TestOperand,
566}
567
568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
569pub struct UnaryOperator {
570    pub span: Span,
571    pub kind: UnaryOperatorKind,
572    pub text: String,
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
576pub enum UnaryOperatorKind {
577    /// `!`
578    Not,
579    /// `+`
580    Plus,
581    /// `++`
582    Inc,
583    /// `-`
584    Minus,
585    /// `--`
586    Dec,
587    /// `~`
588    BitNot,
589    /// `test_operator`, e.g. `-z`, `-f`, `-d`, `-n`, …
590    TestOp,
591}
592
593// ============================================================================
594// Primary expression structs
595// ============================================================================
596
597#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
598pub struct AnsiCString {
599    pub span: Span,
600    pub text: String,
601}
602
603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
604pub struct ArithmeticExpansion {
605    pub span: Span,
606    pub inner: Vec<Expression>,
607}
608
609#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
610pub struct BraceExpression {
611    pub span: Span,
612    pub text: String,
613}
614
615#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
616pub struct CommandSubstitution {
617    pub span: Span,
618    pub body: CommandSubBody,
619    pub redirect: Option<FileRedirect>,
620}
621
622#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
623pub struct Expansion {
624    pub span: Span,
625    pub operators: Vec<ExpansionOperator>,
626    pub elements: Vec<ExpansionElement>,
627}
628
629#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
630pub struct ExpansionOperator {
631    pub span: Span,
632    pub text: String,
633}
634
635#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
636pub struct Number {
637    pub span: Span,
638    pub text: String,
639}
640
641#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
642pub struct ProcessSubstitution {
643    pub span: Span,
644    pub body: Vec<Statement>,
645    /// `<` or `>` direction captured verbatim.
646    pub direction: char,
647}
648
649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
650pub struct RawString {
651    pub span: Span,
652    pub text: String,
653}
654
655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
656pub struct SimpleExpansion {
657    pub span: Span,
658    /// The `$NAME` / `$1` / `$@` body, sans `$`.
659    pub element: SimpleExpansionElement,
660}
661
662#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
663pub enum SimpleExpansionElement {
664    VariableName(VariableName),
665    SpecialVariableName(SpecialVariableName),
666}
667
668#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
669pub struct StringNode {
670    pub span: Span,
671    /// Mix of literal `string_content` segments and embedded expansions.
672    pub parts: Vec<StringPart>,
673}
674
675#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
676pub enum StringPart {
677    Content(StringContent),
678    Expansion(Expansion),
679    SimpleExpansion(SimpleExpansion),
680    CommandSubstitution(CommandSubstitution),
681    ArithmeticExpansion(ArithmeticExpansion),
682    /// Backslash-escaped or other raw character segment we don't classify.
683    Raw { span: Span, text: String },
684}
685
686#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
687pub struct TranslatedString {
688    pub span: Span,
689    /// `$"…"` — same structure as a regular string.
690    pub parts: Vec<StringPart>,
691}
692
693#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
694pub struct Word {
695    pub span: Span,
696    pub text: String,
697}
698
699// ============================================================================
700// Redirects
701// ============================================================================
702
703#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
704pub struct FileRedirect {
705    pub span: Span,
706    pub descriptor: Option<FileDescriptor>,
707    /// `<`, `>`, `>>`, `&>`, `&>>`, `<&`, `>&`, `<>` — captured as a leading
708    /// anonymous token before the destination(s).
709    pub operator: String,
710    pub destinations: Vec<RedirectDestination>,
711}
712
713#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
714pub enum RedirectDestination {
715    Primary(PrimaryExpression),
716    Concatenation(Concatenation),
717}
718
719#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
720pub struct HeredocRedirect {
721    pub span: Span,
722    pub descriptor: Option<FileDescriptor>,
723    pub operator: Option<HeredocOperator>,
724    pub argument: Vec<RedirectDestination>,
725    pub redirects: Vec<SimpleRedirect>,
726    pub right: Option<Box<Statement>>,
727    pub start: Option<HeredocStart>,
728    pub body: Option<HeredocBody>,
729    pub end: Option<HeredocEnd>,
730    pub pipelines: Vec<Pipeline>,
731}
732
733#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
734pub enum HeredocOperator {
735    /// `&&`
736    AndAnd,
737    /// `||`
738    OrOr,
739}
740
741#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
742pub struct HerestringRedirect {
743    pub span: Span,
744    pub descriptor: Option<FileDescriptor>,
745    pub value: HerestringValue,
746}
747
748#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
749pub enum HerestringValue {
750    Primary(PrimaryExpression),
751    Concatenation(Concatenation),
752}
753
754// ============================================================================
755// Misc / leaves
756// ============================================================================
757
758#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
759pub struct Array {
760    pub span: Span,
761    /// Elements of `( … )` literal — primaries or concatenations.
762    pub elements: Vec<ArrayElement>,
763}
764
765#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
766pub enum ArrayElement {
767    Primary(PrimaryExpression),
768    Concatenation(Concatenation),
769    VariableAssignment(VariableAssignment),
770}
771
772#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
773pub struct DoGroup {
774    pub span: Span,
775    pub statements: Vec<Statement>,
776}
777
778#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
779pub struct Subscript {
780    pub span: Span,
781    pub name: VariableName,
782    pub index: SubscriptIndex,
783}
784
785#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
786pub struct Comment {
787    pub span: Span,
788    /// Includes the leading `#`.
789    pub text: String,
790}
791
792#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
793pub struct ExtglobPattern {
794    pub span: Span,
795    pub text: String,
796}
797
798#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
799pub struct FileDescriptor {
800    pub span: Span,
801    pub text: String,
802}
803
804#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
805pub struct HeredocBody {
806    pub span: Span,
807    pub text: String,
808}
809
810#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
811pub struct HeredocContent {
812    pub span: Span,
813    pub text: String,
814}
815
816#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
817pub struct HeredocEnd {
818    pub span: Span,
819    pub text: String,
820}
821
822#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
823pub struct HeredocStart {
824    pub span: Span,
825    pub text: String,
826}
827
828#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
829pub struct Regex {
830    pub span: Span,
831    pub text: String,
832}
833
834#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
835pub struct SpecialVariableName {
836    pub span: Span,
837    /// `$@`, `$*`, `$#`, `$?`, `$$`, `$!`, `$0`, `$_` — body sans `$`.
838    pub text: String,
839}
840
841#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
842pub struct StringContent {
843    pub span: Span,
844    pub text: String,
845}
846
847#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
848pub struct TestOperator {
849    pub span: Span,
850    /// `-eq`, `-lt`, `-z`, `-n`, `-f`, …
851    pub text: String,
852}
853
854#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
855pub struct VariableName {
856    pub span: Span,
857    pub text: String,
858}
859
860#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
861pub struct CommandName {
862    pub span: Span,
863    /// The grammar wraps a single primary/concatenation inside command_name.
864    pub inner: CommandNameInner,
865}
866
867#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
868pub enum CommandNameInner {
869    Primary(PrimaryExpression),
870    Concatenation(Concatenation),
871}