1#[derive(Debug, Clone, PartialEq, Eq)]
3pub struct Span {
4 pub start: usize,
5 pub end: usize,
6}
7
8impl Span {
9 pub const fn new(start: usize, end: usize) -> Self {
11 Self { start, end }
12 }
13
14 pub const fn empty() -> Self {
16 Self { start: 0, end: 0 }
17 }
18
19 pub const fn is_empty(&self) -> bool {
21 self.start >= self.end
22 }
23}
24
25#[derive(Debug, Clone, PartialEq)]
27pub struct Node {
28 pub kind: NodeKind,
29 pub span: Span,
30}
31
32impl Node {
33 pub const fn new(kind: NodeKind, span: Span) -> Self {
35 Self { kind, span }
36 }
37
38 pub const fn empty(kind: NodeKind) -> Self {
40 Self {
41 kind,
42 span: Span::empty(),
43 }
44 }
45
46 pub fn source_text<'a>(&self, source: &'a str) -> &'a str {
51 if self.span.is_empty() {
52 return "";
53 }
54 let byte_start = source.char_indices().nth(self.span.start).map(|(i, _)| i);
56 let byte_end = source
57 .char_indices()
58 .nth(self.span.end)
59 .map_or(source.len(), |(i, _)| i);
60 match byte_start {
61 Some(s) if byte_end <= source.len() => &source[s..byte_end],
62 _ => "",
63 }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq)]
72#[allow(clippy::use_self)]
73pub enum NodeKind {
74 Word {
76 value: String,
77 parts: Vec<Node>,
78 spans: Vec<crate::lexer::word_builder::WordSpan>,
79 },
80
81 WordLiteral { value: String },
83
84 Command {
86 assignments: Vec<Node>,
87 words: Vec<Node>,
88 redirects: Vec<Node>,
89 },
90
91 Pipeline {
93 commands: Vec<Node>,
94 separators: Vec<PipeSep>,
95 },
96
97 List { items: Vec<ListItem> },
99
100 If {
103 condition: Box<Node>,
104 then_body: Box<Node>,
105 else_body: Option<Box<Node>>,
106 redirects: Vec<Node>,
107 },
108
109 While {
111 condition: Box<Node>,
112 body: Box<Node>,
113 redirects: Vec<Node>,
114 },
115
116 Until {
118 condition: Box<Node>,
119 body: Box<Node>,
120 redirects: Vec<Node>,
121 },
122
123 For {
125 var: String,
126 words: Option<Vec<Node>>,
127 body: Box<Node>,
128 redirects: Vec<Node>,
129 },
130
131 ForArith {
133 init: String,
134 cond: String,
135 incr: String,
136 body: Box<Node>,
137 redirects: Vec<Node>,
138 },
139
140 Select {
142 var: String,
143 words: Option<Vec<Node>>,
144 body: Box<Node>,
145 redirects: Vec<Node>,
146 },
147
148 Case {
150 word: Box<Node>,
151 patterns: Vec<CasePattern>,
152 redirects: Vec<Node>,
153 },
154
155 Function { name: String, body: Box<Node> },
157
158 Subshell {
160 body: Box<Node>,
161 redirects: Vec<Node>,
162 },
163
164 BraceGroup {
166 body: Box<Node>,
167 redirects: Vec<Node>,
168 },
169
170 Coproc {
172 name: Option<String>,
173 command: Box<Node>,
174 },
175
176 Redirect {
179 op: String,
180 target: Box<Node>,
181 fd: i32,
182 },
183
184 HereDoc {
186 delimiter: String,
187 content: String,
188 strip_tabs: bool,
189 quoted: bool,
190 fd: i32,
191 complete: bool,
192 },
193
194 ParamExpansion {
197 param: String,
198 op: Option<String>,
199 arg: Option<String>,
200 },
201
202 ParamLength { param: String },
204
205 ParamIndirect {
207 param: String,
208 op: Option<String>,
209 arg: Option<String>,
210 },
211
212 CommandSubstitution { command: Box<Node>, brace: bool },
214
215 ProcessSubstitution {
217 direction: String,
218 command: Box<Node>,
219 },
220
221 AnsiCQuote { content: String },
223
224 LocaleString { content: String },
226
227 ArithmeticExpansion { expression: Option<Box<Node>> },
229
230 ArithmeticCommand {
232 expression: Option<Box<Node>>,
233 redirects: Vec<Node>,
234 raw_content: String,
235 },
236
237 ArithNumber { value: String },
240
241 ArithVar { name: String },
243
244 ArithBinaryOp {
246 op: String,
247 left: Box<Node>,
248 right: Box<Node>,
249 },
250
251 ArithUnaryOp { op: String, operand: Box<Node> },
253
254 ArithPreIncr { operand: Box<Node> },
256
257 ArithPostIncr { operand: Box<Node> },
259
260 ArithPreDecr { operand: Box<Node> },
262
263 ArithPostDecr { operand: Box<Node> },
265
266 ArithAssign {
268 op: String,
269 target: Box<Node>,
270 value: Box<Node>,
271 },
272
273 ArithTernary {
275 condition: Box<Node>,
276 if_true: Option<Box<Node>>,
277 if_false: Option<Box<Node>>,
278 },
279
280 ArithComma { left: Box<Node>, right: Box<Node> },
282
283 ArithSubscript { array: String, index: Box<Node> },
285
286 ArithEmpty,
288
289 ArithEscape { ch: String },
291
292 ArithDeprecated { expression: String },
294
295 ArithConcat { parts: Vec<Node> },
297
298 ConditionalExpr {
301 body: Box<Node>,
302 redirects: Vec<Node>,
303 },
304
305 UnaryTest { op: String, operand: Box<Node> },
307
308 BinaryTest {
310 op: String,
311 left: Box<Node>,
312 right: Box<Node>,
313 },
314
315 CondAnd { left: Box<Node>, right: Box<Node> },
317
318 CondOr { left: Box<Node>, right: Box<Node> },
320
321 CondNot { operand: Box<Node> },
323
324 CondParen { inner: Box<Node> },
326
327 CondTerm {
329 value: String,
330 spans: Vec<crate::lexer::word_builder::WordSpan>,
331 },
332
333 Negation { pipeline: Box<Node> },
336
337 Time { pipeline: Box<Node>, posix: bool },
339
340 Array { elements: Vec<Node> },
342
343 Empty,
345
346 Comment { text: String },
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum ListOperator {
353 And,
355 Or,
357 Semi,
359 Background,
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum PipeSep {
366 Pipe,
368 PipeBoth,
370}
371
372#[derive(Debug, Clone, PartialEq)]
374pub struct ListItem {
375 pub command: Node,
376 pub operator: Option<ListOperator>,
377}
378
379#[derive(Debug, Clone, PartialEq)]
381pub struct CasePattern {
382 pub patterns: Vec<Node>,
383 pub body: Option<Node>,
384 pub terminator: String,
385}
386
387impl CasePattern {
388 pub const fn new(patterns: Vec<Node>, body: Option<Node>, terminator: String) -> Self {
389 Self {
390 patterns,
391 body,
392 terminator,
393 }
394 }
395}