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}