Rust · 13564 bytes Raw Blame History
1 // Abstract Syntax Tree for Rush shell
2
3 #[derive(Debug, Clone, PartialEq, Eq)]
4 pub enum Statement {
5 /// Single complete command (simple, pipeline, control flow, etc.)
6 Complete(CompleteCommand),
7 /// Multiple complete commands (for scripts with multiple top-level commands)
8 Script(Vec<CompleteCommand>),
9 /// Empty line or comment
10 Empty,
11 }
12
13 /// A complete command with optional background execution flag
14 #[derive(Debug, Clone, PartialEq, Eq)]
15 pub struct CompleteCommand {
16 /// The command to execute
17 pub command: CommandType,
18 /// Whether to run in background (trailing &)
19 pub background: bool,
20 }
21
22 #[derive(Debug, Clone, PartialEq, Eq)]
23 pub enum CommandType {
24 /// Simple command with optional variable assignments
25 Simple(SimpleCommand),
26 /// Pipeline of commands connected by pipes
27 Pipeline(Pipeline),
28 /// Commands connected by && or ||
29 AndOrList(AndOrList),
30 /// If statement
31 If(IfStatement),
32 /// While loop
33 While(WhileStatement),
34 /// For loop
35 For(ForStatement),
36 /// Select loop (menu selection)
37 Select(SelectStatement),
38 /// Case statement
39 Case(CaseStatement),
40 /// Function definition
41 Function(FunctionDef),
42 /// Subshell: (commands)
43 Subshell(Subshell),
44 /// Extended test: [[ expression ]]
45 ExtendedTest(CondExpr),
46 }
47
48 #[derive(Debug, Clone, PartialEq, Eq)]
49 pub struct SimpleCommand {
50 /// Variable assignments (VAR=value)
51 pub assignments: Vec<Assignment>,
52 /// Command and arguments (words that may contain expansions)
53 pub words: Vec<Word>,
54 /// I/O redirections
55 pub redirects: Vec<Redirect>,
56 }
57
58 #[derive(Debug, Clone, PartialEq, Eq)]
59 pub struct Pipeline {
60 /// Commands connected by pipes
61 pub commands: Vec<PipelineElement>,
62 /// Whether the pipeline is negated with !
63 pub negated: bool,
64 }
65
66 /// An element in a pipeline - can be a simple command, subshell, or extended test
67 #[derive(Debug, Clone, PartialEq, Eq)]
68 pub enum PipelineElement {
69 Simple(SimpleCommand),
70 Subshell(Subshell),
71 ExtendedTest(CondExpr),
72 }
73
74 /// Conditional expression for [[ ]] extended test
75 #[derive(Debug, Clone, PartialEq, Eq)]
76 pub enum CondExpr {
77 /// Logical OR: expr1 || expr2
78 Or(Box<CondExpr>, Box<CondExpr>),
79 /// Logical AND: expr1 && expr2
80 And(Box<CondExpr>, Box<CondExpr>),
81 /// Logical NOT: ! expr
82 Not(Box<CondExpr>),
83 /// Unary test: -z, -n, -f, -d, etc.
84 Unary { op: String, operand: Word },
85 /// Binary test: ==, !=, =~, -eq, -lt, etc.
86 Binary { left: Word, op: String, right: Word },
87 /// Single word (true if non-empty after expansion)
88 Word(Word),
89 }
90
91 #[derive(Debug, Clone, PartialEq, Eq)]
92 pub enum Redirect {
93 /// Input redirection: <file
94 Input { file: Word },
95 /// Output redirection: >file or N>file
96 Output { fd: Option<u32>, file: Word },
97 /// Append redirection: >>file or N>>file
98 OutputAppend { fd: Option<u32>, file: Word },
99 /// Stderr to stdout: 2>&1
100 StderrToStdout,
101 /// All output: &>file or &>>file
102 AllOutput { file: Word, append: bool },
103 /// Here-document: <<DELIMITER or <<-DELIMITER
104 Heredoc {
105 delimiter: String,
106 content: Vec<String>,
107 strip_tabs: bool,
108 expand: bool,
109 },
110 /// Here-string: <<<string
111 Herestring { content: Word },
112 /// Process substitution input: <(command)
113 /// Creates a FIFO that reads from the command's stdout
114 ProcessSubstInput { command: String },
115 /// Process substitution output: >(command)
116 /// Creates a FIFO that writes to the command's stdin
117 ProcessSubstOutput { command: String },
118 }
119
120 #[derive(Debug, Clone, PartialEq, Eq)]
121 pub struct AndOrList {
122 /// First pipeline in the list
123 pub first: Pipeline,
124 /// Remaining pipelines with their operators
125 pub rest: Vec<(AndOrOp, Pipeline)>,
126 }
127
128 #[derive(Debug, Clone, PartialEq, Eq)]
129 pub enum AndOrOp {
130 /// && operator (execute next if previous succeeded)
131 And,
132 /// || operator (execute next if previous failed)
133 Or,
134 }
135
136 #[derive(Debug, Clone, PartialEq, Eq)]
137 pub struct IfStatement {
138 /// Condition to test
139 pub condition: Box<CompleteCommand>,
140 /// Commands to execute if condition is true
141 pub then_body: Vec<CompleteCommand>,
142 /// Elif clauses
143 pub elif_clauses: Vec<ElifClause>,
144 /// Else body (optional)
145 pub else_body: Option<Vec<CompleteCommand>>,
146 }
147
148 #[derive(Debug, Clone, PartialEq, Eq)]
149 pub struct ElifClause {
150 /// Condition to test
151 pub condition: Box<CompleteCommand>,
152 /// Commands to execute if condition is true
153 pub then_body: Vec<CompleteCommand>,
154 }
155
156 #[derive(Debug, Clone, PartialEq, Eq)]
157 pub struct WhileStatement {
158 /// Condition to test
159 pub condition: Box<CompleteCommand>,
160 /// Commands to execute while condition is true
161 pub body: Vec<CompleteCommand>,
162 }
163
164 #[derive(Debug, Clone, PartialEq, Eq)]
165 pub struct ForStatement {
166 /// Variable name to iterate over
167 pub var_name: String,
168 /// Words to iterate through
169 pub words: Vec<Word>,
170 /// Commands to execute for each word
171 pub body: Vec<CompleteCommand>,
172 }
173
174 #[derive(Debug, Clone, PartialEq, Eq)]
175 pub struct SelectStatement {
176 /// Variable name to store selected item
177 pub var_name: String,
178 /// Words to present as menu options
179 pub words: Vec<Word>,
180 /// Commands to execute after selection
181 pub body: Vec<CompleteCommand>,
182 }
183
184 #[derive(Debug, Clone, PartialEq, Eq)]
185 pub struct CaseStatement {
186 /// Word to match against patterns
187 pub word: Word,
188 /// Case clauses with patterns and bodies
189 pub clauses: Vec<CaseClause>,
190 }
191
192 #[derive(Debug, Clone, PartialEq, Eq)]
193 pub struct CaseClause {
194 /// Patterns to match (connected by |)
195 pub patterns: Vec<Word>,
196 /// Commands to execute if pattern matches
197 pub body: Vec<CompleteCommand>,
198 }
199
200 #[derive(Debug, Clone, PartialEq, Eq)]
201 pub struct FunctionDef {
202 /// Function name
203 pub name: String,
204 /// Function body (commands to execute)
205 pub body: Vec<CompleteCommand>,
206 }
207
208 #[derive(Debug, Clone, PartialEq, Eq)]
209 pub struct Subshell {
210 /// Commands to execute in a subshell
211 pub commands: Vec<CompleteCommand>,
212 }
213
214 #[derive(Debug, Clone, PartialEq, Eq)]
215 pub struct Assignment {
216 pub name: String,
217 /// Optional array index for array[index]=value
218 pub index: Option<String>,
219 pub value: Word,
220 }
221
222 /// A word is composed of one or more parts that will be concatenated
223 #[derive(Debug, Clone, PartialEq, Eq)]
224 pub struct Word {
225 pub parts: Vec<WordPart>,
226 }
227
228 #[derive(Debug, Clone, PartialEq, Eq)]
229 pub enum WordPart {
230 /// Literal text
231 Literal(String),
232 /// Variable expansion: $VAR or ${VAR}
233 VarExpansion(VarExpansion),
234 /// Command substitution: $(cmd)
235 CommandSubstitution(String),
236 /// Brace expansion: {a,b,c} or {1..5}
237 BraceExpansion(BraceExpansion),
238 /// Arithmetic expansion: $((expr))
239 ArithmeticExpansion(String),
240 /// Array literal: (one two three)
241 ArrayLiteral(Vec<Word>),
242 }
243
244 #[derive(Debug, Clone, PartialEq, Eq)]
245 pub enum BraceExpansion {
246 /// List: {a,b,c}
247 List(Vec<String>),
248 /// Sequence: {1..10} or {a..z}
249 Sequence {
250 start: String,
251 end: String,
252 increment: Option<i32>,
253 },
254 }
255
256 #[derive(Debug, Clone, PartialEq, Eq)]
257 pub enum VarExpansion {
258 /// Simple: $VAR
259 Simple(String),
260 /// Braced: ${VAR}
261 Braced(String),
262 /// With default (colon): ${VAR:-default} - use default if unset OR empty
263 WithDefault { name: String, default: Box<Word> },
264 /// With default (no colon): ${VAR-default} - use default only if unset
265 WithDefaultUnsetOnly { name: String, default: Box<Word> },
266 /// Assign default (colon): ${VAR:=default} - assign if unset OR empty
267 AssignDefault { name: String, default: Box<Word> },
268 /// Assign default (no colon): ${VAR=default} - assign only if unset
269 AssignDefaultUnsetOnly { name: String, default: Box<Word> },
270 /// Use alternate if set (colon): ${VAR:+alternate} - use if set AND non-empty
271 UseIfSet { name: String, alternate: Box<Word> },
272 /// Use alternate if set (no colon): ${VAR+alternate} - use if set (even if empty)
273 UseIfSetOnly { name: String, alternate: Box<Word> },
274 /// Error if unset (colon): ${VAR:?message} - error if unset OR empty
275 ErrorIfUnset { name: String, message: Box<Word> },
276 /// Error if unset (no colon): ${VAR?message} - error only if unset
277 ErrorIfUnsetOnly { name: String, message: Box<Word> },
278 /// Length: ${#VAR}
279 Length(String),
280 /// Remove shortest prefix: ${VAR#pattern}
281 RemoveShortestPrefix { name: String, pattern: String },
282 /// Remove longest prefix: ${VAR##pattern}
283 RemoveLongestPrefix { name: String, pattern: String },
284 /// Remove shortest suffix: ${VAR%pattern}
285 RemoveShortestSuffix { name: String, pattern: String },
286 /// Remove longest suffix: ${VAR%%pattern}
287 RemoveLongestSuffix { name: String, pattern: String },
288 /// Replace first: ${VAR/pattern/replacement}
289 ReplaceFirst { name: String, pattern: String, replacement: String },
290 /// Replace all: ${VAR//pattern/replacement}
291 ReplaceAll { name: String, pattern: String, replacement: String },
292 /// Substring: ${VAR:offset} or ${VAR:offset:length}
293 Substring { name: String, offset: i32, length: Option<usize> },
294 /// Uppercase first: ${VAR^}
295 UppercaseFirst(String),
296 /// Uppercase all: ${VAR^^}
297 UppercaseAll(String),
298 /// Lowercase first: ${VAR,}
299 LowercaseFirst(String),
300 /// Lowercase all: ${VAR,,}
301 LowercaseAll(String),
302 /// Array element: ${arr[index]}
303 ArrayElement { name: String, index: String },
304 /// Array all elements (separate words): ${arr[@]}
305 ArrayAll(String),
306 /// Array all elements (single word): ${arr[*]}
307 ArrayStar(String),
308 /// Array length: ${#arr[@]} or ${#arr[*]}
309 ArrayLength(String),
310 /// Array indices: ${!arr[@]}
311 ArrayIndices(String),
312 /// Indirect expansion: ${!var}
313 Indirect(String),
314 /// Transformation: ${var@Q}, ${var@E}, etc.
315 Transform { name: String, op: char },
316 }
317
318 impl Pipeline {
319 pub fn new(commands: Vec<PipelineElement>) -> Self {
320 Self { commands, negated: false }
321 }
322
323 pub fn new_negated(commands: Vec<PipelineElement>, negated: bool) -> Self {
324 Self { commands, negated }
325 }
326
327 /// Helper to create a pipeline from simple commands
328 pub fn from_simple_commands(commands: Vec<SimpleCommand>) -> Self {
329 Self {
330 commands: commands.into_iter().map(PipelineElement::Simple).collect(),
331 negated: false,
332 }
333 }
334
335 pub fn is_simple(&self) -> bool {
336 self.commands.len() == 1
337 }
338 }
339
340 impl SimpleCommand {
341 pub fn new(assignments: Vec<Assignment>, words: Vec<Word>) -> Self {
342 Self {
343 assignments,
344 words,
345 redirects: Vec::new(),
346 }
347 }
348
349 pub fn with_redirects(
350 assignments: Vec<Assignment>,
351 words: Vec<Word>,
352 redirects: Vec<Redirect>,
353 ) -> Self {
354 Self {
355 assignments,
356 words,
357 redirects,
358 }
359 }
360
361 pub fn has_command(&self) -> bool {
362 !self.words.is_empty()
363 }
364 }
365
366 impl Word {
367 pub fn new(parts: Vec<WordPart>) -> Self {
368 Self { parts }
369 }
370
371 pub fn from_literal(s: impl Into<String>) -> Self {
372 Self {
373 parts: vec![WordPart::Literal(s.into())],
374 }
375 }
376
377 pub fn is_literal(&self) -> bool {
378 self.parts.len() == 1 && matches!(self.parts[0], WordPart::Literal(_))
379 }
380 }
381
382 impl Assignment {
383 pub fn new(name: String, value: Word) -> Self {
384 Self { name, index: None, value }
385 }
386
387 pub fn new_array(name: String, index: String, value: Word) -> Self {
388 Self { name, index: Some(index), value }
389 }
390 }
391
392 impl AndOrList {
393 pub fn new(first: Pipeline, rest: Vec<(AndOrOp, Pipeline)>) -> Self {
394 Self { first, rest }
395 }
396 }
397
398 impl IfStatement {
399 pub fn new(
400 condition: Box<CompleteCommand>,
401 then_body: Vec<CompleteCommand>,
402 elif_clauses: Vec<ElifClause>,
403 else_body: Option<Vec<CompleteCommand>>,
404 ) -> Self {
405 Self {
406 condition,
407 then_body,
408 elif_clauses,
409 else_body,
410 }
411 }
412 }
413
414 impl ElifClause {
415 pub fn new(condition: Box<CompleteCommand>, then_body: Vec<CompleteCommand>) -> Self {
416 Self {
417 condition,
418 then_body,
419 }
420 }
421 }
422
423 impl WhileStatement {
424 pub fn new(condition: Box<CompleteCommand>, body: Vec<CompleteCommand>) -> Self {
425 Self { condition, body }
426 }
427 }
428
429 impl ForStatement {
430 pub fn new(var_name: String, words: Vec<Word>, body: Vec<CompleteCommand>) -> Self {
431 Self {
432 var_name,
433 words,
434 body,
435 }
436 }
437 }
438
439 impl CaseStatement {
440 pub fn new(word: Word, clauses: Vec<CaseClause>) -> Self {
441 Self { word, clauses }
442 }
443 }
444
445 impl CaseClause {
446 pub fn new(patterns: Vec<Word>, body: Vec<CompleteCommand>) -> Self {
447 Self { patterns, body }
448 }
449 }
450
451 impl FunctionDef {
452 pub fn new(name: String, body: Vec<CompleteCommand>) -> Self {
453 Self { name, body }
454 }
455 }
456
457 impl Subshell {
458 pub fn new(commands: Vec<CompleteCommand>) -> Self {
459 Self { commands }
460 }
461 }
462
463 impl CompleteCommand {
464 pub fn new(command: CommandType, background: bool) -> Self {
465 Self { command, background }
466 }
467
468 pub fn foreground(command: CommandType) -> Self {
469 Self {
470 command,
471 background: false,
472 }
473 }
474 }
475