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