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}