wasmsh-ast 0.7.0

Abstract syntax tree types for the wasmsh shell parser
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//! AST types for the wasmsh shell.
//!
//! This crate defines the abstract syntax tree produced by the parser.
//! Words remain structured (no premature stringification) so that
//! expansion phases can operate on typed segments.

#![warn(missing_docs)]

use smol_str::SmolStr;

/// A span marking the byte range of a syntax element in source.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
    /// Byte offset of the first character (inclusive).
    pub start: u32,
    /// Byte offset past the last character (exclusive).
    pub end: u32,
}

/// A complete shell program (list of commands).
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
    /// The top-level complete commands in the program.
    pub commands: Vec<CompleteCommand>,
}

/// A complete command terminated by a newline or `;`.
#[derive(Debug, Clone, PartialEq)]
pub struct CompleteCommand {
    /// The and/or lists that make up this command.
    pub list: Vec<AndOrList>,
    /// Source span of the complete command.
    pub span: Span,
}

/// A chain of pipelines joined by `&&` or `||`.
#[derive(Debug, Clone, PartialEq)]
pub struct AndOrList {
    /// The first pipeline in the chain.
    pub first: Pipeline,
    /// Subsequent pipelines paired with their connecting operator.
    pub rest: Vec<(AndOrOp, Pipeline)>,
}

/// `&&` or `||` operator.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AndOrOp {
    /// `&&` — run the right side only if the left side succeeded.
    And,
    /// `||` — run the right side only if the left side failed.
    Or,
}

/// A pipeline of one or more commands connected by `|`.
#[derive(Debug, Clone, PartialEq)]
pub struct Pipeline {
    /// True when the pipeline is prefixed with `time`.
    pub timed: bool,
    /// True when `time -p` was used.
    pub time_posix: bool,
    /// True when the pipeline is prefixed with `!` (logical negation).
    pub negated: bool,
    /// The commands in the pipeline.
    pub commands: Vec<Command>,
    /// Per-stage flags: `pipe_stderr[i]` is true when stage `i` uses `|&`
    /// (its stderr should also be piped to the next stage's stdin).
    pub pipe_stderr: Vec<bool>,
}

/// A single command in the AST.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Command {
    /// A simple command with optional assignments, words, and redirections.
    Simple(SimpleCommand),
    /// A `( compound_list )` subshell.
    Subshell(SubshellCommand),
    /// A `{ compound_list ; }` brace group.
    Group(GroupCommand),
    /// An `if / elif / else / fi` construct.
    If(IfCommand),
    /// A `while condition; do body; done` loop.
    While(WhileCommand),
    /// An `until condition; do body; done` loop.
    Until(UntilCommand),
    /// A `for name in words; do body; done` loop.
    For(ForCommand),
    /// A C-style `for (( init; cond; step )) do body done` loop.
    ArithFor(ArithForCommand),
    /// A function definition.
    FunctionDef(FunctionDef),
    /// A `case word in ... esac` statement.
    Case(CaseCommand),
    /// A `[[ expression ]]` extended test.
    DoubleBracket(DoubleBracketCommand),
    /// A `(( expr ))` arithmetic command.
    ArithCommand(ArithCommandNode),
    /// A `select name in words; do body; done` menu loop.
    Select(SelectCommand),
}

/// A C-style `for (( init; cond; step )) do body done` command.
#[derive(Debug, Clone, PartialEq)]
pub struct ArithForCommand {
    /// The initializer expression.
    pub init: SmolStr,
    /// The loop condition expression.
    pub cond: SmolStr,
    /// The step expression evaluated after each iteration.
    pub step: SmolStr,
    /// The loop body.
    pub body: Vec<CompleteCommand>,
    /// Source span.
    pub span: Span,
}

/// A `(( expr ))` arithmetic command.
#[derive(Debug, Clone, PartialEq)]
pub struct ArithCommandNode {
    /// The arithmetic expression text.
    pub expr: SmolStr,
    /// Source span.
    pub span: Span,
}

/// A `select name [in word ...]; do body; done` command.
#[derive(Debug, Clone, PartialEq)]
pub struct SelectCommand {
    /// The loop variable name.
    pub var_name: SmolStr,
    /// `None` means iterate over `"$@"` (no `in` clause).
    pub words: Option<Vec<Word>>,
    /// The loop body.
    pub body: Vec<CompleteCommand>,
    /// Trailing redirections (e.g., `done <<< "input"`).
    pub redirections: Vec<Redirection>,
    /// Source span.
    pub span: Span,
}

/// A `[[ expression ]]` extended test command.
#[derive(Debug, Clone, PartialEq)]
pub struct DoubleBracketCommand {
    /// The words inside `[[ ... ]]`.
    pub words: Vec<Word>,
    /// Source span.
    pub span: Span,
}

/// A subshell command `( compound_list )`.
#[derive(Debug, Clone, PartialEq)]
pub struct SubshellCommand {
    /// The commands inside the subshell.
    pub body: Vec<CompleteCommand>,
    /// Source span.
    pub span: Span,
}

/// A brace group `{ compound_list ; }`.
#[derive(Debug, Clone, PartialEq)]
pub struct GroupCommand {
    /// The commands inside the brace group.
    pub body: Vec<CompleteCommand>,
    /// Source span.
    pub span: Span,
}

/// An `if` / `elif` / `else` / `fi` command.
#[derive(Debug, Clone, PartialEq)]
pub struct IfCommand {
    /// The condition commands.
    pub condition: Vec<CompleteCommand>,
    /// The body to run when the condition is true.
    pub then_body: Vec<CompleteCommand>,
    /// Zero or more `elif` clauses.
    pub elifs: Vec<ElifClause>,
    /// Optional `else` body.
    pub else_body: Option<Vec<CompleteCommand>>,
    /// Source span.
    pub span: Span,
}

/// A single `elif condition; then body` clause.
#[derive(Debug, Clone, PartialEq)]
pub struct ElifClause {
    /// The condition commands.
    pub condition: Vec<CompleteCommand>,
    /// The body to run when the condition is true.
    pub then_body: Vec<CompleteCommand>,
}

/// A `while condition; do body; done` command.
#[derive(Debug, Clone, PartialEq)]
pub struct WhileCommand {
    /// The loop condition.
    pub condition: Vec<CompleteCommand>,
    /// The loop body.
    pub body: Vec<CompleteCommand>,
    /// Source span.
    pub span: Span,
}

/// An `until condition; do body; done` command.
#[derive(Debug, Clone, PartialEq)]
pub struct UntilCommand {
    /// The loop condition (runs until this is true).
    pub condition: Vec<CompleteCommand>,
    /// The loop body.
    pub body: Vec<CompleteCommand>,
    /// Source span.
    pub span: Span,
}

/// A `for name in words; do body; done` command.
#[derive(Debug, Clone, PartialEq)]
pub struct ForCommand {
    /// The loop variable name.
    pub var_name: SmolStr,
    /// `None` means iterate over `"$@"` (no `in` clause).
    pub words: Option<Vec<Word>>,
    /// The loop body.
    pub body: Vec<CompleteCommand>,
    /// Source span.
    pub span: Span,
}

/// A `case word in pattern) body ;; ... esac` command.
#[derive(Debug, Clone, PartialEq)]
pub struct CaseCommand {
    /// The word being tested.
    pub word: Word,
    /// The list of pattern arms.
    pub items: Vec<CaseItem>,
    /// Source span.
    pub span: Span,
}

/// Terminator for a case item arm.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaseTerminator {
    /// `;;` — stop matching after this arm.
    Break,
    /// `;&` — fall through to the next arm's body unconditionally.
    Fallthrough,
    /// `;;&` — continue testing remaining patterns.
    ContinueTesting,
}

/// A single `pattern) body ;;` arm in a case statement.
#[derive(Debug, Clone, PartialEq)]
pub struct CaseItem {
    /// One or more glob patterns for this arm.
    pub patterns: Vec<Word>,
    /// The body to execute when a pattern matches.
    pub body: Vec<CompleteCommand>,
    /// How to proceed after this arm executes.
    pub terminator: CaseTerminator,
}

/// A function definition: `name() body` or `function name body`.
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionDef {
    /// The function name.
    pub name: SmolStr,
    /// The function body (typically a `Group` command).
    pub body: Box<Command>,
    /// Source span.
    pub span: Span,
}

/// A simple command: optional assignments, words (argv), and redirections.
#[derive(Debug, Clone, PartialEq)]
pub struct SimpleCommand {
    /// Variable assignments prefixed before the command (e.g., `FOO=1`).
    pub assignments: Vec<Assignment>,
    /// The command name and arguments.
    pub words: Vec<Word>,
    /// Redirections attached to this command.
    pub redirections: Vec<Redirection>,
    /// Source span.
    pub span: Span,
}

/// A variable assignment (`name=value`).
#[derive(Debug, Clone, PartialEq)]
pub struct Assignment {
    /// The variable name.
    pub name: SmolStr,
    /// The assigned value (`None` for `name=` with empty value).
    pub value: Option<Word>,
    /// Source span.
    pub span: Span,
}

/// A structured word composed of parts that preserve quoting and expansion boundaries.
#[derive(Debug, Clone, PartialEq)]
pub struct Word {
    /// The constituent parts of this word.
    pub parts: Vec<WordPart>,
    /// Source span.
    pub span: Span,
}

/// A segment of a word — literals, quoted strings, expansions, etc.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum WordPart {
    /// Unquoted literal text.
    Literal(SmolStr),
    /// Content inside single quotes.
    SingleQuoted(SmolStr),
    /// Content inside double quotes (may contain nested expansions).
    DoubleQuoted(Vec<WordPart>),
    /// `$name` or `${...}` parameter expansion. Stores the name or full
    /// expansion text (e.g. `"var"` for `$var`, `"var:-default"` for `${var:-default}`).
    Parameter(SmolStr),
    /// `$(...)` command substitution. Stores the inner source text (not yet parsed).
    CommandSubstitution(SmolStr),
    /// `$((...))` arithmetic expansion. Stores the inner expression text.
    Arithmetic(SmolStr),
    /// `<(cmd)` process substitution (input). Stores the inner command text.
    ProcessSubstIn(SmolStr),
    /// `>(cmd)` process substitution (output). Stores the inner command text.
    ProcessSubstOut(SmolStr),
    // Glob and tilde expansion handled at runtime/expansion layers.
}

/// A redirection (`>`, `<`, `>>`, `<<`, etc.).
#[derive(Debug, Clone, PartialEq)]
pub struct Redirection {
    /// Explicit file descriptor number (e.g., `2>` has `fd = Some(2)`).
    pub fd: Option<u32>,
    /// The redirection operator.
    pub op: RedirectionOp,
    /// The target word (filename, fd number, or here-string content).
    pub target: Word,
    /// For here-doc redirections, the body content (filled in after the command line).
    pub here_doc_body: Option<HereDocBody>,
    /// Source span.
    pub span: Span,
}

/// Redirection operator.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RedirectionOp {
    /// `<`
    Input,
    /// `>`
    Output,
    /// `>>`
    Append,
    /// `>|`
    Clobber,
    /// `&>>`
    AppendBoth,
    /// `<>`
    ReadWrite,
    /// `<<` (here-doc)
    HereDoc,
    /// `<<-` (here-doc with tab stripping)
    HereDocStrip,
    /// `<<<` (here-string)
    HereString,
    /// `>&N` or `N>&M` (duplicate output fd)
    DupOutput,
    /// `<&N` or `N<&M` (duplicate input fd)
    DupInput,
}

/// The body of a here-document.
#[derive(Debug, Clone, PartialEq)]
pub struct HereDocBody {
    /// The literal here-doc text (after delimiter stripping).
    pub content: SmolStr,
    /// Source span of the here-doc body.
    pub span: Span,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn span_equality() {
        let a = Span { start: 0, end: 5 };
        let b = Span { start: 0, end: 5 };
        assert_eq!(a, b);
    }

    #[test]
    fn word_with_parts() {
        let word = Word {
            parts: vec![
                WordPart::Literal("hello".into()),
                WordPart::Parameter("USER".into()),
            ],
            span: Span { start: 0, end: 11 },
        };
        assert_eq!(word.parts.len(), 2);
    }
}