rush_sh/parser/ast.rs
1//! Abstract Syntax Tree (AST) definitions for the Rush shell parser.
2//!
3//! This module contains the core data structures that represent parsed shell commands
4//! and constructs. The AST serves as an intermediate representation between the lexical
5//! tokens and the execution engine.
6//!
7//! # AST Structure
8//!
9//! The AST is built around the [`Ast`] enum, which represents different types of shell
10//! constructs:
11//!
12//! ## Simple Commands
13//! - **Pipeline**: A sequence of commands connected by pipes (`|`)
14//! - **Assignment**: Variable assignment (`VAR=value`)
15//! - **LocalAssignment**: Local variable declaration (`local VAR=value`)
16//!
17//! ## Control Flow
18//! - **If**: Conditional execution with optional elif/else branches
19//! - **Case**: Pattern matching construct
20//! - **For**: Iteration over a list of items
21//! - **While**: Loop while condition is true
22//! - **Until**: Loop until condition is true
23//!
24//! ## Functions
25//! - **FunctionDefinition**: Function declaration (`name() { ... }`)
26//! - **FunctionCall**: Function invocation with arguments
27//! - **Return**: Early return from function
28//!
29//! ## Logical Operators
30//! - **And**: Short-circuit AND (`&&`)
31//! - **Or**: Short-circuit OR (`||`)
32//! - **Negation**: Logical NOT (`!`)
33//!
34//! ## Compound Commands
35//! - **Subshell**: Commands executed in isolated state (`(...)`)
36//! - **CommandGroup**: Commands executed in current state (`{...}`)
37//! - **Sequence**: Multiple commands separated by `;` or newlines
38//!
39//! # Redirections
40//!
41//! The [`Redirection`] enum represents I/O redirection operations:
42//! - Basic redirections: `<`, `>`, `>>`
43//! - File descriptor operations: `N<`, `N>`, `N>>`, `N>&M`, `N<&M`, `N>&-`, `N<&-`, `N<>`
44//! - Here-documents: `<<EOF`, `<<<string`
45//! - Noclobber override: `>|`
46//!
47//! # Shell Commands
48//!
49//! The [`ShellCommand`] struct represents a single command in a pipeline, containing:
50//! - Command arguments
51//! - Ordered list of redirections (processed left-to-right per POSIX)
52//! - Optional compound command (for subshells, groups, etc.)
53//!
54//! # Examples
55//!
56//! ```rust,ignore
57//! // Simple command: echo hello
58//! Ast::Pipeline(vec![ShellCommand {
59//! args: vec!["echo".to_string(), "hello".to_string()],
60//! redirections: vec![],
61//! compound: None,
62//! }])
63//!
64//! // Pipeline: ls | grep txt
65//! Ast::Pipeline(vec![
66//! ShellCommand { args: vec!["ls".to_string()], ... },
67//! ShellCommand { args: vec!["grep".to_string(), "txt".to_string()], ... },
68//! ])
69//!
70//! // Conditional: if true; then echo yes; fi
71//! Ast::If {
72//! branches: vec![(
73//! Box::new(Ast::Pipeline(...)), // condition
74//! Box::new(Ast::Pipeline(...)), // then branch
75//! )],
76//! else_branch: None,
77//! }
78//! ```
79
80/// Abstract Syntax Tree node representing a parsed shell construct.
81///
82/// Each variant represents a different type of shell command or control structure.
83/// The AST is designed to be executed by the executor module, which traverses the
84/// tree and performs the corresponding operations.
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub enum Ast {
87 /// A pipeline of one or more commands connected by pipes.
88 ///
89 /// Each command in the pipeline receives input from the previous command's
90 /// output (except the first) and sends output to the next command (except the last).
91 ///
92 /// # Examples
93 /// - Single command: `ls`
94 /// - Pipeline: `ls | grep txt | sort`
95 Pipeline(Vec<ShellCommand>),
96
97 /// A sequence of commands separated by semicolons or newlines.
98 ///
99 /// Commands are executed in order, regardless of their exit status.
100 ///
101 /// # Examples
102 /// - `cmd1; cmd2; cmd3`
103 /// - Multiple lines in a script
104 Sequence(Vec<Ast>),
105
106 /// Variable assignment in the global scope.
107 ///
108 /// # Examples
109 /// - `VAR=value`
110 /// - `PATH=/usr/bin:$PATH`
111 Assignment {
112 /// Variable name
113 var: String,
114 /// Value to assign
115 value: String,
116 },
117
118 /// Local variable assignment (function scope).
119 ///
120 /// Only valid within function bodies. Creates a variable that is local
121 /// to the function and its callees.
122 ///
123 /// # Examples
124 /// - `local VAR=value`
125 /// - `local COUNT=0`
126 LocalAssignment {
127 /// Variable name
128 var: String,
129 /// Value to assign
130 value: String,
131 },
132
133 /// Conditional execution with optional elif and else branches.
134 ///
135 /// Each branch consists of a condition and a body. Branches are evaluated
136 /// in order until one condition succeeds, then its body is executed.
137 ///
138 /// # Examples
139 /// - `if test -f file; then echo exists; fi`
140 /// - `if [ $x -eq 0 ]; then echo zero; elif [ $x -gt 0 ]; then echo positive; else echo negative; fi`
141 If {
142 /// List of (condition, then-body) pairs for if/elif branches
143 branches: Vec<(Box<Ast>, Box<Ast>)>,
144 /// Optional else branch
145 else_branch: Option<Box<Ast>>,
146 },
147
148 /// Pattern matching construct.
149 ///
150 /// Matches a word against a series of patterns and executes the corresponding
151 /// commands for the first match. Supports glob patterns.
152 ///
153 /// # Examples
154 /// - `case $var in pattern1) cmd1 ;; pattern2|pattern3) cmd2 ;; esac`
155 Case {
156 /// Word to match against patterns
157 word: String,
158 /// List of (patterns, commands) pairs
159 cases: Vec<(Vec<String>, Ast)>,
160 /// Optional default case (pattern: `*`)
161 default: Option<Box<Ast>>,
162 },
163
164 /// For loop iterating over a list of items.
165 ///
166 /// Executes the body once for each item, with the loop variable set to
167 /// the current item.
168 ///
169 /// # Examples
170 /// - `for i in 1 2 3; do echo $i; done`
171 /// - `for file in *.txt; do cat "$file"; done`
172 For {
173 /// Loop variable name
174 variable: String,
175 /// List of items to iterate over
176 items: Vec<String>,
177 /// Loop body
178 body: Box<Ast>,
179 },
180
181 /// While loop executing while condition is true.
182 ///
183 /// Evaluates the condition before each iteration. Continues looping
184 /// as long as the condition exits with status 0.
185 ///
186 /// # Examples
187 /// - `while true; do echo loop; done`
188 /// - `while [ $count -lt 10 ]; do count=$((count + 1)); done`
189 While {
190 /// Condition to evaluate
191 condition: Box<Ast>,
192 /// Loop body
193 body: Box<Ast>,
194 },
195
196 /// Until loop executing until condition is true.
197 ///
198 /// Evaluates the condition before each iteration. Continues looping
199 /// as long as the condition exits with non-zero status.
200 ///
201 /// # Examples
202 /// - `until false; do echo loop; done`
203 /// - `until [ -f ready.txt ]; do sleep 1; done`
204 Until {
205 /// Condition to evaluate
206 condition: Box<Ast>,
207 /// Loop body
208 body: Box<Ast>,
209 },
210
211 /// Function definition.
212 ///
213 /// Defines a named function that can be called later. The function body
214 /// is stored as an AST and executed when the function is invoked.
215 ///
216 /// # Examples
217 /// - `myfunc() { echo hello; }`
218 /// - `greet() { echo "Hello, $1"; }`
219 FunctionDefinition {
220 /// Function name
221 name: String,
222 /// Function body
223 body: Box<Ast>,
224 },
225
226 /// Function call with arguments.
227 ///
228 /// Invokes a previously defined function with the given arguments.
229 /// Arguments are accessible as positional parameters ($1, $2, etc.).
230 ///
231 /// # Examples
232 /// - `myfunc`
233 /// - `greet Alice`
234 FunctionCall {
235 /// Function name
236 name: String,
237 /// Arguments to pass
238 args: Vec<String>,
239 },
240
241 /// Return statement for early exit from function.
242 ///
243 /// Exits the current function with an optional exit code.
244 /// If no value is provided, returns the exit code of the last command.
245 ///
246 /// # Examples
247 /// - `return`
248 /// - `return 0`
249 /// - `return 1`
250 Return {
251 /// Optional exit code (defaults to last command's exit code)
252 value: Option<String>,
253 },
254
255 /// Logical AND operator (short-circuit evaluation).
256 ///
257 /// Executes the right side only if the left side succeeds (exit code 0).
258 ///
259 /// # Examples
260 /// - `cmd1 && cmd2`
261 /// - `test -f file && cat file`
262 And {
263 /// Left operand
264 left: Box<Ast>,
265 /// Right operand (executed only if left succeeds)
266 right: Box<Ast>,
267 },
268
269 /// Logical OR operator (short-circuit evaluation).
270 ///
271 /// Executes the right side only if the left side fails (non-zero exit code).
272 ///
273 /// # Examples
274 /// - `cmd1 || cmd2`
275 /// - `test -f file || touch file`
276 Or {
277 /// Left operand
278 left: Box<Ast>,
279 /// Right operand (executed only if left fails)
280 right: Box<Ast>,
281 },
282
283 /// Subshell execution: `(commands)`.
284 ///
285 /// Commands execute in an isolated copy of the shell state. Changes to
286 /// variables, directory, etc. do not affect the parent shell.
287 ///
288 /// # Examples
289 /// - `(cd /tmp; ls)`
290 /// - `(export VAR=value; cmd)`
291 Subshell {
292 /// Commands to execute in subshell
293 body: Box<Ast>,
294 },
295
296 /// Command group execution: `{ commands; }`.
297 ///
298 /// Commands execute in the current shell state. Changes to variables,
299 /// directory, etc. affect the current shell.
300 ///
301 /// # Examples
302 /// - `{ cmd1; cmd2; }`
303 /// - `{ echo start; cmd; echo end; }`
304 CommandGroup {
305 /// Commands to execute in current shell
306 body: Box<Ast>,
307 },
308
309 /// Command negation: `! command`.
310 ///
311 /// Inverts the exit code of the command (0 becomes non-zero, non-zero becomes 0).
312 /// Also exempts the command from errexit behavior.
313 ///
314 /// # Examples
315 /// - `! false`
316 /// - `! grep pattern file`
317 Negation {
318 /// Command to negate
319 command: Box<Ast>,
320 },
321
322 /// Asynchronous command execution: `command &`.
323 ///
324 /// Executes the command in the background, allowing the shell to continue
325 /// processing subsequent commands without waiting for completion.
326 ///
327 /// # Examples
328 /// - `sleep 10 &`
329 /// - `long_running_task &`
330 AsyncCommand {
331 /// Command to execute asynchronously
332 command: Box<Ast>,
333 },
334}
335
336/// Represents a single I/O redirection operation.
337///
338/// Redirections are processed in left-to-right order as they appear in the command,
339/// per POSIX specification. Each redirection modifies the file descriptor table
340/// before command execution.
341#[derive(Debug, Clone, PartialEq, Eq)]
342pub enum Redirection {
343 /// Input from file: `< file` or `0< file`.
344 ///
345 /// Redirects standard input (fd 0) from the specified file.
346 Input(String),
347
348 /// Output to file: `> file` or `1> file`.
349 ///
350 /// Redirects standard output (fd 1) to the specified file, truncating it.
351 /// Respects the noclobber option if set.
352 Output(String),
353
354 /// Output to file with noclobber override: `>| file`.
355 ///
356 /// Redirects standard output to the specified file, truncating it.
357 /// Ignores the noclobber option.
358 OutputClobber(String),
359
360 /// Append to file: `>> file` or `1>> file`.
361 ///
362 /// Redirects standard output (fd 1) to the specified file, appending to it.
363 Append(String),
364
365 /// Input from file with explicit fd: `N< file`.
366 ///
367 /// Redirects the specified file descriptor from the file.
368 FdInput(i32, String),
369
370 /// Output to file with explicit fd: `N> file`.
371 ///
372 /// Redirects the specified file descriptor to the file, truncating it.
373 /// Respects the noclobber option if set.
374 FdOutput(i32, String),
375
376 /// Output to file with explicit fd and noclobber override: `N>| file`.
377 ///
378 /// Redirects the specified file descriptor to the file, truncating it.
379 /// Ignores the noclobber option.
380 FdOutputClobber(i32, String),
381
382 /// Append to file with explicit fd: `N>> file`.
383 ///
384 /// Redirects the specified file descriptor to the file, appending to it.
385 FdAppend(i32, String),
386
387 /// Duplicate file descriptor: `N>&M` or `N<&M`.
388 ///
389 /// Makes file descriptor N a copy of file descriptor M.
390 /// Both descriptors refer to the same open file description.
391 FdDuplicate(i32, i32),
392
393 /// Close file descriptor: `N>&-` or `N<&-`.
394 ///
395 /// Closes the specified file descriptor.
396 FdClose(i32),
397
398 /// Open file for read/write: `N<> file`.
399 ///
400 /// Opens the file for both reading and writing on the specified fd.
401 FdInputOutput(i32, String),
402
403 /// Here-document: `<< EOF ... EOF`.
404 ///
405 /// Provides input from a multi-line string literal.
406 /// The first string is the delimiter, the second is the content.
407 /// The boolean indicates whether the delimiter was quoted (affects expansion).
408 HereDoc(String, String),
409
410 /// Here-string: `<<< "string"`.
411 ///
412 /// Provides input from a single-line string.
413 HereString(String),
414}
415
416/// Represents a single command in a pipeline.
417///
418/// A shell command consists of:
419/// - Arguments (command name and parameters)
420/// - Redirections (I/O redirection operations)
421/// - Optional compound command (for subshells, groups, etc.)
422///
423/// If `compound` is present, it takes precedence over `args` during execution.
424#[derive(Debug, Clone, PartialEq, Eq, Default)]
425pub struct ShellCommand {
426 /// Command arguments (first element is the command name).
427 ///
428 /// For simple commands like `ls -la`, this would be `["ls", "-la"]`.
429 pub args: Vec<String>,
430
431 /// All redirections in order of appearance.
432 ///
433 /// Redirections are processed left-to-right per POSIX specification.
434 /// For example, in `cmd >file1 2>&1 >file2`, the redirections are:
435 /// 1. Redirect stdout to file1
436 /// 2. Duplicate stderr to stdout (which points to file1)
437 /// 3. Redirect stdout to file2 (stderr still points to file1)
438 pub redirections: Vec<Redirection>,
439
440 /// Optional compound command (subshell, command group, etc.).
441 ///
442 /// If present, this takes precedence over `args` during execution.
443 /// Used for constructs like `(subshell) | cmd` or `{ group; } | cmd`.
444 pub compound: Option<Box<Ast>>,
445}