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}