Skip to main content

wasmsh_ast/
lib.rs

1//! AST types for the wasmsh shell.
2//!
3//! This crate defines the abstract syntax tree produced by the parser.
4//! Words remain structured (no premature stringification) so that
5//! expansion phases can operate on typed segments.
6
7#![warn(missing_docs)]
8
9use smol_str::SmolStr;
10
11/// A span marking the byte range of a syntax element in source.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Span {
14    /// Byte offset of the first character (inclusive).
15    pub start: u32,
16    /// Byte offset past the last character (exclusive).
17    pub end: u32,
18}
19
20/// A complete shell program (list of commands).
21#[derive(Debug, Clone, PartialEq)]
22pub struct Program {
23    /// The top-level complete commands in the program.
24    pub commands: Vec<CompleteCommand>,
25}
26
27/// A complete command terminated by a newline or `;`.
28#[derive(Debug, Clone, PartialEq)]
29pub struct CompleteCommand {
30    /// The and/or lists that make up this command.
31    pub list: Vec<AndOrList>,
32    /// Source span of the complete command.
33    pub span: Span,
34}
35
36/// A chain of pipelines joined by `&&` or `||`.
37#[derive(Debug, Clone, PartialEq)]
38pub struct AndOrList {
39    /// The first pipeline in the chain.
40    pub first: Pipeline,
41    /// Subsequent pipelines paired with their connecting operator.
42    pub rest: Vec<(AndOrOp, Pipeline)>,
43}
44
45/// `&&` or `||` operator.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[non_exhaustive]
48pub enum AndOrOp {
49    /// `&&` — run the right side only if the left side succeeded.
50    And,
51    /// `||` — run the right side only if the left side failed.
52    Or,
53}
54
55/// A pipeline of one or more commands connected by `|`.
56#[derive(Debug, Clone, PartialEq)]
57pub struct Pipeline {
58    /// True when the pipeline is prefixed with `!` (logical negation).
59    pub negated: bool,
60    /// The commands in the pipeline.
61    pub commands: Vec<Command>,
62    /// Per-stage flags: `pipe_stderr[i]` is true when stage `i` uses `|&`
63    /// (its stderr should also be piped to the next stage's stdin).
64    pub pipe_stderr: Vec<bool>,
65}
66
67/// A single command in the AST.
68#[derive(Debug, Clone, PartialEq)]
69#[non_exhaustive]
70pub enum Command {
71    /// A simple command with optional assignments, words, and redirections.
72    Simple(SimpleCommand),
73    /// A `( compound_list )` subshell.
74    Subshell(SubshellCommand),
75    /// A `{ compound_list ; }` brace group.
76    Group(GroupCommand),
77    /// An `if / elif / else / fi` construct.
78    If(IfCommand),
79    /// A `while condition; do body; done` loop.
80    While(WhileCommand),
81    /// An `until condition; do body; done` loop.
82    Until(UntilCommand),
83    /// A `for name in words; do body; done` loop.
84    For(ForCommand),
85    /// A C-style `for (( init; cond; step )) do body done` loop.
86    ArithFor(ArithForCommand),
87    /// A function definition.
88    FunctionDef(FunctionDef),
89    /// A `case word in ... esac` statement.
90    Case(CaseCommand),
91    /// A `[[ expression ]]` extended test.
92    DoubleBracket(DoubleBracketCommand),
93    /// A `(( expr ))` arithmetic command.
94    ArithCommand(ArithCommandNode),
95    /// A `select name in words; do body; done` menu loop.
96    Select(SelectCommand),
97}
98
99/// A C-style `for (( init; cond; step )) do body done` command.
100#[derive(Debug, Clone, PartialEq)]
101pub struct ArithForCommand {
102    /// The initializer expression.
103    pub init: SmolStr,
104    /// The loop condition expression.
105    pub cond: SmolStr,
106    /// The step expression evaluated after each iteration.
107    pub step: SmolStr,
108    /// The loop body.
109    pub body: Vec<CompleteCommand>,
110    /// Source span.
111    pub span: Span,
112}
113
114/// A `(( expr ))` arithmetic command.
115#[derive(Debug, Clone, PartialEq)]
116pub struct ArithCommandNode {
117    /// The arithmetic expression text.
118    pub expr: SmolStr,
119    /// Source span.
120    pub span: Span,
121}
122
123/// A `select name [in word ...]; do body; done` command.
124#[derive(Debug, Clone, PartialEq)]
125pub struct SelectCommand {
126    /// The loop variable name.
127    pub var_name: SmolStr,
128    /// `None` means iterate over `"$@"` (no `in` clause).
129    pub words: Option<Vec<Word>>,
130    /// The loop body.
131    pub body: Vec<CompleteCommand>,
132    /// Trailing redirections (e.g., `done <<< "input"`).
133    pub redirections: Vec<Redirection>,
134    /// Source span.
135    pub span: Span,
136}
137
138/// A `[[ expression ]]` extended test command.
139#[derive(Debug, Clone, PartialEq)]
140pub struct DoubleBracketCommand {
141    /// The words inside `[[ ... ]]`.
142    pub words: Vec<Word>,
143    /// Source span.
144    pub span: Span,
145}
146
147/// A subshell command `( compound_list )`.
148#[derive(Debug, Clone, PartialEq)]
149pub struct SubshellCommand {
150    /// The commands inside the subshell.
151    pub body: Vec<CompleteCommand>,
152    /// Source span.
153    pub span: Span,
154}
155
156/// A brace group `{ compound_list ; }`.
157#[derive(Debug, Clone, PartialEq)]
158pub struct GroupCommand {
159    /// The commands inside the brace group.
160    pub body: Vec<CompleteCommand>,
161    /// Source span.
162    pub span: Span,
163}
164
165/// An `if` / `elif` / `else` / `fi` command.
166#[derive(Debug, Clone, PartialEq)]
167pub struct IfCommand {
168    /// The condition commands.
169    pub condition: Vec<CompleteCommand>,
170    /// The body to run when the condition is true.
171    pub then_body: Vec<CompleteCommand>,
172    /// Zero or more `elif` clauses.
173    pub elifs: Vec<ElifClause>,
174    /// Optional `else` body.
175    pub else_body: Option<Vec<CompleteCommand>>,
176    /// Source span.
177    pub span: Span,
178}
179
180/// A single `elif condition; then body` clause.
181#[derive(Debug, Clone, PartialEq)]
182pub struct ElifClause {
183    /// The condition commands.
184    pub condition: Vec<CompleteCommand>,
185    /// The body to run when the condition is true.
186    pub then_body: Vec<CompleteCommand>,
187}
188
189/// A `while condition; do body; done` command.
190#[derive(Debug, Clone, PartialEq)]
191pub struct WhileCommand {
192    /// The loop condition.
193    pub condition: Vec<CompleteCommand>,
194    /// The loop body.
195    pub body: Vec<CompleteCommand>,
196    /// Source span.
197    pub span: Span,
198}
199
200/// An `until condition; do body; done` command.
201#[derive(Debug, Clone, PartialEq)]
202pub struct UntilCommand {
203    /// The loop condition (runs until this is true).
204    pub condition: Vec<CompleteCommand>,
205    /// The loop body.
206    pub body: Vec<CompleteCommand>,
207    /// Source span.
208    pub span: Span,
209}
210
211/// A `for name in words; do body; done` command.
212#[derive(Debug, Clone, PartialEq)]
213pub struct ForCommand {
214    /// The loop variable name.
215    pub var_name: SmolStr,
216    /// `None` means iterate over `"$@"` (no `in` clause).
217    pub words: Option<Vec<Word>>,
218    /// The loop body.
219    pub body: Vec<CompleteCommand>,
220    /// Source span.
221    pub span: Span,
222}
223
224/// A `case word in pattern) body ;; ... esac` command.
225#[derive(Debug, Clone, PartialEq)]
226pub struct CaseCommand {
227    /// The word being tested.
228    pub word: Word,
229    /// The list of pattern arms.
230    pub items: Vec<CaseItem>,
231    /// Source span.
232    pub span: Span,
233}
234
235/// Terminator for a case item arm.
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum CaseTerminator {
238    /// `;;` — stop matching after this arm.
239    Break,
240    /// `;&` — fall through to the next arm's body unconditionally.
241    Fallthrough,
242    /// `;;&` — continue testing remaining patterns.
243    ContinueTesting,
244}
245
246/// A single `pattern) body ;;` arm in a case statement.
247#[derive(Debug, Clone, PartialEq)]
248pub struct CaseItem {
249    /// One or more glob patterns for this arm.
250    pub patterns: Vec<Word>,
251    /// The body to execute when a pattern matches.
252    pub body: Vec<CompleteCommand>,
253    /// How to proceed after this arm executes.
254    pub terminator: CaseTerminator,
255}
256
257/// A function definition: `name() body` or `function name body`.
258#[derive(Debug, Clone, PartialEq)]
259pub struct FunctionDef {
260    /// The function name.
261    pub name: SmolStr,
262    /// The function body (typically a `Group` command).
263    pub body: Box<Command>,
264    /// Source span.
265    pub span: Span,
266}
267
268/// A simple command: optional assignments, words (argv), and redirections.
269#[derive(Debug, Clone, PartialEq)]
270pub struct SimpleCommand {
271    /// Variable assignments prefixed before the command (e.g., `FOO=1`).
272    pub assignments: Vec<Assignment>,
273    /// The command name and arguments.
274    pub words: Vec<Word>,
275    /// Redirections attached to this command.
276    pub redirections: Vec<Redirection>,
277    /// Source span.
278    pub span: Span,
279}
280
281/// A variable assignment (`name=value`).
282#[derive(Debug, Clone, PartialEq)]
283pub struct Assignment {
284    /// The variable name.
285    pub name: SmolStr,
286    /// The assigned value (`None` for `name=` with empty value).
287    pub value: Option<Word>,
288    /// Source span.
289    pub span: Span,
290}
291
292/// A structured word composed of parts that preserve quoting and expansion boundaries.
293#[derive(Debug, Clone, PartialEq)]
294pub struct Word {
295    /// The constituent parts of this word.
296    pub parts: Vec<WordPart>,
297    /// Source span.
298    pub span: Span,
299}
300
301/// A segment of a word — literals, quoted strings, expansions, etc.
302#[derive(Debug, Clone, PartialEq)]
303#[non_exhaustive]
304pub enum WordPart {
305    /// Unquoted literal text.
306    Literal(SmolStr),
307    /// Content inside single quotes.
308    SingleQuoted(SmolStr),
309    /// Content inside double quotes (may contain nested expansions).
310    DoubleQuoted(Vec<WordPart>),
311    /// `$name` or `${...}` parameter expansion. Stores the name or full
312    /// expansion text (e.g. `"var"` for `$var`, `"var:-default"` for `${var:-default}`).
313    Parameter(SmolStr),
314    /// `$(...)` command substitution. Stores the inner source text (not yet parsed).
315    CommandSubstitution(SmolStr),
316    /// `$((...))` arithmetic expansion. Stores the inner expression text.
317    Arithmetic(SmolStr),
318    // Glob and tilde expansion handled at runtime/expansion layers.
319}
320
321/// A redirection (`>`, `<`, `>>`, `<<`, etc.).
322#[derive(Debug, Clone, PartialEq)]
323pub struct Redirection {
324    /// Explicit file descriptor number (e.g., `2>` has `fd = Some(2)`).
325    pub fd: Option<u32>,
326    /// The redirection operator.
327    pub op: RedirectionOp,
328    /// The target word (filename, fd number, or here-string content).
329    pub target: Word,
330    /// For here-doc redirections, the body content (filled in after the command line).
331    pub here_doc_body: Option<HereDocBody>,
332    /// Source span.
333    pub span: Span,
334}
335
336/// Redirection operator.
337#[derive(Debug, Clone, Copy, PartialEq, Eq)]
338#[non_exhaustive]
339pub enum RedirectionOp {
340    /// `<`
341    Input,
342    /// `>`
343    Output,
344    /// `>>`
345    Append,
346    /// `<>`
347    ReadWrite,
348    /// `<<` (here-doc)
349    HereDoc,
350    /// `<<-` (here-doc with tab stripping)
351    HereDocStrip,
352    /// `<<<` (here-string)
353    HereString,
354    /// `>&N` or `N>&M` (duplicate output fd)
355    DupOutput,
356    /// `<&N` or `N<&M` (duplicate input fd)
357    DupInput,
358}
359
360/// The body of a here-document.
361#[derive(Debug, Clone, PartialEq)]
362pub struct HereDocBody {
363    /// The literal here-doc text (after delimiter stripping).
364    pub content: SmolStr,
365    /// Source span of the here-doc body.
366    pub span: Span,
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    fn span_equality() {
375        let a = Span { start: 0, end: 5 };
376        let b = Span { start: 0, end: 5 };
377        assert_eq!(a, b);
378    }
379
380    #[test]
381    fn word_with_parts() {
382        let word = Word {
383            parts: vec![
384                WordPart::Literal("hello".into()),
385                WordPart::Parameter("USER".into()),
386            ],
387            span: Span { start: 0, end: 11 },
388        };
389        assert_eq!(word.parts.len(), 2);
390    }
391}