yash_syntax/
syntax.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2020 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Shell command language syntax
18//!
19//! This module contains types that represent abstract syntax trees (ASTs) of
20//! the shell language.
21//!
22//! ## Syntactic elements
23//!
24//! The AST type that represents the whole shell script is [`List`], which is a
25//! vector of [`Item`]s. An `Item` is a possibly asynchronous [`AndOrList`],
26//! which is a sequence of conditionally executed [`Pipeline`]s. A `Pipeline` is
27//! a sequence of [`Command`]s separated by `|`.
28//!
29//! There are several types of `Command`s, namely [`SimpleCommand`],
30//! [`CompoundCommand`] and [`FunctionDefinition`], where `CompoundCommand` in
31//! turn comes in many variants.
32//!
33//! ## Lexical elements
34//!
35//! Tokens that make up commands may contain quotations and expansions. A
36//! [`Word`], a sequence of [`WordUnit`]s, represents such a token that appears
37//! in a simple command and some kinds of other commands.
38//!
39//! In some contexts, tilde expansion and single- and double-quotes are not
40//! recognized while other kinds of expansions are allowed. Such part is
41//! represented as [`Text`], a sequence of [`TextUnit`]s.
42//!
43//! ## Parsing
44//!
45//! Most AST types defined in this module implement the [`FromStr`] trait, which
46//! means you can easily get an AST out of source code by calling `parse` on a
47//! `&str`. However, all [location](crate::source::Location)s in ASTs
48//! constructed this way will only have
49//! [unknown source](crate::source::Source::Unknown).
50//!
51//! ```
52//! use std::str::FromStr;
53//! # use yash_syntax::syntax::List;
54//! let list: List = "diff foo bar; echo $?".parse().unwrap();
55//! assert_eq!(list.to_string(), "diff foo bar; echo $?");
56//!
57//! use yash_syntax::source::Source;
58//! # use yash_syntax::syntax::Word;
59//! let word: Word = "foo".parse().unwrap();
60//! assert_eq!(*word.location.code.source, Source::Unknown);
61//! ```
62//!
63//! To include substantial source information in the AST, you need to prepare a
64//! [lexer](crate::parser::lex::Lexer) with source information and then use it
65//! to parse the source code. See the [`parser`](crate::parser) module for
66//! details.
67//!
68//! ## Displaying
69//!
70//! Most AST types support the [`Display`](std::fmt::Display) trait, which
71//! allows you to convert an AST to a source code string. Note that the
72//! `Display` trait implementations always produce single-line source code with
73//! here-document contents omitted. To pretty-format an AST in multiple lines
74//! with here-document contents included, you can use ... TODO TBD.
75
76use crate::parser::lex::Keyword;
77use crate::parser::lex::Operator;
78use crate::parser::lex::TryFromOperatorError;
79use crate::source::Location;
80use std::cell::OnceCell;
81#[cfg(unix)]
82use std::os::unix::io::RawFd;
83use std::rc::Rc;
84use std::str::FromStr;
85
86#[cfg(not(unix))]
87type RawFd = i32;
88
89/// Special parameter
90///
91/// This enum value identifies a special parameter in the shell language.
92/// Each special parameter is a single character that has a special meaning in
93/// the shell language. For example, `@` represents all positional parameters.
94///
95/// See [`ParamType`] for other types of parameters.
96#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
97pub enum SpecialParam {
98    /// `@` (all positional parameters)
99    At,
100    /// `*` (all positional parameters)
101    Asterisk,
102    /// `#` (number of positional parameters)
103    Number,
104    /// `?` (exit status of the last command)
105    Question,
106    /// `-` (active shell options)
107    Hyphen,
108    /// `$` (process ID of the shell)
109    Dollar,
110    /// `!` (process ID of the last asynchronous command)
111    Exclamation,
112    /// `0` (name of the shell or shell script)
113    Zero,
114}
115
116/// Type of a parameter
117///
118/// This enum distinguishes three types of [parameters](Param): named, special and
119/// positional parameters. However, this value does not include the actual
120/// parameter name as a string. The actual name is stored in a separate field in
121/// the AST node that contains this value.
122///
123/// Note the careful use of the term "name" here. In POSIX terminology, a
124/// "name" identifies a named parameter (that is, a variable) and does not
125/// include special or positional parameters. An identifier that refers to any
126/// kind of parameter is called a "parameter".
127#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
128pub enum ParamType {
129    /// Named parameter
130    Variable,
131    /// Special parameter
132    Special(SpecialParam),
133    /// Positional parameter
134    ///
135    /// Positional parameters are indexed starting from 1, so the index of `0`
136    /// always refers to a non-existent parameter. If the string form of a
137    /// positional parameter represents an index that is too large to fit in a
138    /// `usize`, the index should be `usize::MAX`, which is also guaranteed to
139    /// spot a non-existent parameter since a `Vec` cannot have more than
140    /// `isize::MAX` elements.
141    Positional(usize),
142}
143
144/// Parameter
145///
146/// A parameter is an identifier that appears in a parameter expansion
147/// ([`TextUnit::RawParam`] and [`BracedParam`]). There are three
148/// [types](ParamType) of parameters depending on the character category of the
149/// identifier.
150#[derive(Clone, Debug, Eq, Hash, PartialEq)]
151pub struct Param {
152    /// Literal representation of the parameter name
153    ///
154    /// This is the raw string form of the parameter as it appears in the source
155    /// code. Examples include `foo`, `@`, `#`, `0`, and `12`.
156    pub id: String,
157
158    /// Type of the parameter
159    ///
160    /// This precomputed value is used to optimize the evaluation of parameter
161    /// expansions by avoiding the need to parse the `id` field every time.
162    ///
163    /// It is your responsibility to ensure that the `type` field is consistent
164    /// with the `id` field. For example, if the `id` field is `"@"`, the `type`
165    /// field must be `Special(At)`. The [parser](crate::parser) ensures this
166    /// invariant when it constructs a `Param` value.
167    pub r#type: ParamType,
168}
169
170// TODO Consider implementing FromStr for Param
171
172/// Flag that specifies how the value is substituted in a [switch](Switch)
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
174pub enum SwitchType {
175    /// Alter an existing value, if any. (`+`)
176    Alter,
177    /// Substitute a missing value with a default. (`-`)
178    Default,
179    /// Assign a default to the variable if the value is missing. (`=`)
180    Assign,
181    /// Error out if the value is missing. (`?`)
182    Error,
183}
184
185/// Condition that triggers a [switch](Switch)
186///
187/// In the lexical grammar of the shell language, a switch condition is an
188/// optional colon that precedes a switch type.
189#[derive(Clone, Copy, Debug, Eq, PartialEq)]
190pub enum SwitchCondition {
191    /// Without a colon, the switch is triggered if the parameter is unset.
192    Unset,
193    /// With a colon, the switch is triggered if the parameter is unset or
194    /// empty.
195    UnsetOrEmpty,
196}
197
198/// Parameter expansion [modifier](Modifier) that conditionally substitutes the
199/// value being expanded
200///
201/// Examples of switches include `+foo`, `:-bar` and `:=baz`.
202///
203/// A switch is composed of a [condition](SwitchCondition) (an optional `:`), a
204/// [type](SwitchType) (one of `+`, `-`, `=` and `?`) and a [word](Word).
205#[derive(Clone, Debug, Eq, PartialEq)]
206pub struct Switch {
207    /// How the value is substituted
208    pub r#type: SwitchType,
209    /// Condition that determines whether the value is substituted or not
210    pub condition: SwitchCondition,
211    /// Word that substitutes the parameter value
212    pub word: Word,
213}
214
215/// Flag that specifies which side of the expanded value is removed in a
216/// [trim](Trim)
217#[derive(Clone, Copy, Debug, Eq, PartialEq)]
218pub enum TrimSide {
219    /// Beginning of the value
220    Prefix,
221    /// End of the value
222    Suffix,
223}
224
225/// Flag that specifies pattern matching strategy in a [trim](Trim)
226#[derive(Clone, Copy, Debug, Eq, PartialEq)]
227pub enum TrimLength {
228    /// Match as small number of characters as possible.
229    Shortest,
230    /// Match as large number of characters as possible.
231    Longest,
232}
233
234/// Parameter expansion [modifier](Modifier) that removes the beginning or end
235/// of the value being expanded
236///
237/// Examples of trims include `#foo`, `##bar` and `%%baz*`.
238///
239/// A trim is composed of a side, length and pattern.
240#[derive(Clone, Debug, Eq, PartialEq)]
241pub struct Trim {
242    /// Which side of the value should be removed?
243    pub side: TrimSide,
244    /// How long the pattern should match?
245    pub length: TrimLength,
246    /// Pattern to be matched with the expanded value
247    pub pattern: Word,
248}
249
250/// Attribute that modifies a parameter expansion
251#[derive(Clone, Debug, Eq, PartialEq)]
252pub enum Modifier {
253    /// No modifier
254    None,
255    /// `#` prefix (`${#foo}`)
256    Length,
257    /// `+`, `-`, `=` or `?` suffix, optionally with `:` (`${foo:-bar}`)
258    Switch(Switch),
259    /// `#`, `##`, `%` or `%%` suffix
260    Trim(Trim),
261    // TODO Subst
262}
263
264/// Parameter expansion enclosed in braces
265///
266/// This struct is used only for parameter expansions that are enclosed braces.
267/// Expansions that are not enclosed in braces are directly encoded with
268/// [`TextUnit::RawParam`].
269#[derive(Clone, Debug, Eq, PartialEq)]
270pub struct BracedParam {
271    // TODO recursive expansion
272    /// Parameter to be expanded
273    pub param: Param,
274    // TODO index
275    /// Modifier
276    pub modifier: Modifier,
277    /// Position of this parameter expansion in the source code
278    pub location: Location,
279}
280
281/// Element of [`TextUnit::Backquote`]
282#[derive(Clone, Copy, Debug, Eq, PartialEq)]
283pub enum BackquoteUnit {
284    /// Literal single character
285    Literal(char),
286    /// Backslash-escaped single character
287    Backslashed(char),
288}
289
290/// Element of a [Text], i.e., something that can be expanded
291#[derive(Clone, Debug, Eq, PartialEq)]
292pub enum TextUnit {
293    /// Literal single character
294    Literal(char),
295    /// Backslash-escaped single character
296    Backslashed(char),
297    /// Parameter expansion that is not enclosed in braces
298    RawParam {
299        /// Parameter to be expanded
300        param: Param,
301        /// Position of this parameter expansion in the source code
302        location: Location,
303    },
304    /// Parameter expansion that is enclosed in braces
305    BracedParam(BracedParam),
306    /// Command substitution of the form `$(...)`
307    CommandSubst {
308        /// Command string that will be parsed and executed when the command
309        /// substitution is expanded
310        ///
311        /// This value is reference-counted so that the shell does not have to
312        /// clone the entire string when it is passed to a subshell to execute
313        /// the command substitution.
314        content: Rc<str>,
315        /// Position of this command substitution in the source code
316        location: Location,
317    },
318    /// Command substitution of the form `` `...` ``
319    Backquote {
320        /// Command string that will be parsed and executed when the command
321        /// substitution is expanded
322        content: Vec<BackquoteUnit>,
323        /// Position of this command substitution in the source code
324        location: Location,
325    },
326    /// Arithmetic expansion
327    Arith {
328        /// Expression that is to be evaluated
329        content: Text,
330        /// Position of this arithmetic expansion in the source code
331        location: Location,
332    },
333}
334
335pub use TextUnit::*;
336
337/// String that may contain some expansions
338///
339/// A text is a sequence of [text unit](TextUnit)s, which may contain some kinds
340/// of expansions.
341#[derive(Clone, Debug, Default, Eq, PartialEq)]
342pub struct Text(pub Vec<TextUnit>);
343
344/// Element of an [`EscapedString`]
345#[derive(Clone, Debug, Eq, PartialEq)]
346pub enum EscapeUnit {
347    /// Literal single character
348    Literal(char),
349    /// Backslash-escaped double-quote character (`\"`)
350    DoubleQuote,
351    /// Backslash-escaped single-quote character (`\'`)
352    SingleQuote,
353    /// Backslash-escaped backslash character (`\\`)
354    Backslash,
355    /// Backslash-escaped question mark character (`\?`)
356    Question,
357    /// Backslash notation for the bell character (`\a`, ASCII 7)
358    Alert,
359    /// Backslash notation for the backspace character (`\b`, ASCII 8)
360    Backspace,
361    /// Backslash notation for the escape character (`\e`, ASCII 27)
362    Escape,
363    /// Backslash notation for the form feed character (`\f`, ASCII 12)
364    FormFeed,
365    /// Backslash notation for the newline character (`\n`, ASCII 10)
366    Newline,
367    /// Backslash notation for the carriage return character (`\r`, ASCII 13)
368    CarriageReturn,
369    /// Backslash notation for the horizontal tab character (`\t`, ASCII 9)
370    Tab,
371    /// Backslash notation for the vertical tab character (`\v`, ASCII 11)
372    VerticalTab,
373    /// Control character notation (`\c...`)
374    ///
375    /// The associated value is the control character represented by the
376    /// following character in the input.
377    Control(u8),
378    /// Single-byte octal notation (`\OOO`)
379    ///
380    /// The associated value is the byte represented by the three octal digits
381    /// following the backslash.
382    Octal(u8),
383    /// Single-byte hexadecimal notation (`\xHH`)
384    ///
385    /// The associated value is the byte represented by the two hexadecimal
386    /// digits following the `x`.
387    Hex(u8),
388    /// Unicode notation (`\uHHHH` or `\UHHHHHHHH`)
389    ///
390    /// The associated value is the Unicode scalar value represented by the four
391    /// or eight hexadecimal digits following the `u` or `U`.
392    Unicode(char),
393}
394
395/// String that may contain some escapes
396///
397/// An escaped string is a sequence of [escape unit](EscapeUnit)s, which may
398/// contain some kinds of escapes. This type is used for the value of a
399/// [dollar-single-quoted string](WordUnit::DollarSingleQuote).
400#[derive(Clone, Debug, Default, Eq, PartialEq)]
401pub struct EscapedString(pub Vec<EscapeUnit>);
402
403/// Element of a [Word], i.e., text with quotes and tilde expansion
404#[derive(Clone, Debug, Eq, PartialEq)]
405pub enum WordUnit {
406    /// Unquoted [`TextUnit`] as a word unit
407    Unquoted(TextUnit),
408    /// String surrounded with a pair of single quotations
409    SingleQuote(String),
410    /// Text surrounded with a pair of double quotations
411    DoubleQuote(Text),
412    /// String surrounded with a pair of single quotations and preceded by a dollar sign
413    DollarSingleQuote(EscapedString),
414    /// Tilde expansion
415    Tilde {
416        /// User name part of the tilde expansion
417        ///
418        /// This is the string that follows the tilde in the source code, up to
419        /// (but not including) the following delimiter. The name may be empty.
420        name: String,
421        /// Whether the tilde expansion is followed by a slash
422        ///
423        /// This value is `true` if and only if this word unit is followed by
424        /// `WordUnit::Unquoted(TextUnit::Literal('/'))`. It affects the
425        /// expansion to directory names that end with a slash.
426        followed_by_slash: bool,
427    },
428}
429
430pub use WordUnit::*;
431
432/// Token that may involve expansions and quotes
433///
434/// A word is a sequence of [word unit](WordUnit)s. It depends on context whether
435/// an empty word is valid or not. It is your responsibility to ensure a word is
436/// non-empty in a context where it cannot.
437///
438/// The difference between words and [text](Text)s is that only words can contain
439/// single- and double-quotes and tilde expansions. Compare [`WordUnit`] and [`TextUnit`].
440#[derive(Clone, Debug, Eq, PartialEq)]
441pub struct Word {
442    /// Word units that constitute the word
443    pub units: Vec<WordUnit>,
444    /// Position of the word in the source code
445    pub location: Location,
446}
447
448/// Value of an [assignment](Assign)
449#[derive(Clone, Debug, Eq, PartialEq)]
450pub enum Value {
451    /// Scalar value, a possibly empty word
452    ///
453    /// Note: Because a scalar assignment value is created from a normal command
454    /// word, the location of the word in the scalar value refers to the entire
455    /// assignment word rather than the assigned value.
456    Scalar(Word),
457
458    /// Array, possibly empty list of non-empty words
459    ///
460    /// Array assignment is a POSIXly non-portable extension.
461    Array(Vec<Word>),
462}
463
464pub use Value::*;
465
466/// Assignment word
467#[derive(Clone, Debug, Eq, PartialEq)]
468pub struct Assign {
469    /// Name of the variable to assign to
470    ///
471    /// In the valid assignment syntax, the name must not be empty.
472    pub name: String,
473    /// Value assigned to the variable
474    pub value: Value,
475    /// Location of the assignment word
476    pub location: Location,
477}
478
479/// File descriptor
480///
481/// This is the `newtype` pattern applied to [`RawFd`], which is merely a type
482/// alias.
483#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
484pub struct Fd(pub RawFd);
485
486impl Fd {
487    /// File descriptor for the standard input
488    pub const STDIN: Fd = Fd(0);
489    /// File descriptor for the standard output
490    pub const STDOUT: Fd = Fd(1);
491    /// File descriptor for the standard error
492    pub const STDERR: Fd = Fd(2);
493}
494
495/// Redirection operators
496///
497/// This enum defines the redirection operator types except here-document and
498/// process redirection.
499#[derive(Clone, Copy, Debug, Eq, PartialEq)]
500pub enum RedirOp {
501    /// `<` (open a file for input)
502    FileIn,
503    /// `<>` (open a file for input and output)
504    FileInOut,
505    /// `>` (open a file for output; truncate or fail if existing)
506    FileOut,
507    /// `>>` (open a file for output; append if existing)
508    FileAppend,
509    /// `>|` (open a file for output; always truncate if existing)
510    FileClobber,
511    /// `<&` (copy or close a file descriptor for input)
512    FdIn,
513    /// `>&` (copy or close a file descriptor for output)
514    FdOut,
515    /// `>>|` (open a pipe, one end for input and the other output)
516    Pipe,
517    /// `<<<` (here-string)
518    String,
519}
520
521/// Here-document
522#[derive(Clone, Debug, Eq, PartialEq)]
523pub struct HereDoc {
524    /// Token that marks the end of the content of the here-document
525    pub delimiter: Word,
526
527    /// Whether leading tab characters should be removed from each line of the
528    /// here-document content
529    ///
530    /// This value is `true` for the `<<-` operator and `false` for `<<`.
531    pub remove_tabs: bool,
532
533    /// Content of the here-document
534    ///
535    /// The content ends with a newline unless it is empty. If the delimiter is
536    /// quoted, the content must be all literal. If `remove_tabs` is `true`,
537    /// each content line does not start with tabs as they are removed when
538    /// parsed.
539    ///
540    /// This value is wrapped in `OnceCell` because the here-doc content is
541    /// parsed separately from the here-doc operator. When the operator is
542    /// parsed, the `HereDoc` instance is created with an empty content. The
543    /// content is filled to the cell when it is parsed later. When accessing
544    /// the parsed content, you can safely unwrap the cell.
545    pub content: OnceCell<Text>,
546}
547
548/// Part of a redirection that defines the nature of the resulting file descriptor
549#[derive(Clone, Debug, Eq, PartialEq)]
550pub enum RedirBody {
551    /// Normal redirection
552    Normal { operator: RedirOp, operand: Word },
553    /// Here-document
554    HereDoc(Rc<HereDoc>),
555    // TODO process redirection
556}
557
558impl RedirBody {
559    /// Returns the operand word of the redirection.
560    pub fn operand(&self) -> &Word {
561        match self {
562            RedirBody::Normal { operand, .. } => operand,
563            RedirBody::HereDoc(here_doc) => &here_doc.delimiter,
564        }
565    }
566}
567
568/// Redirection
569#[derive(Clone, Debug, Eq, PartialEq)]
570pub struct Redir {
571    /// File descriptor that is modified by this redirection
572    pub fd: Option<Fd>,
573    /// Nature of the resulting file descriptor
574    pub body: RedirBody,
575}
576
577impl Redir {
578    /// Computes the file descriptor that is modified by this redirection.
579    ///
580    /// If `self.fd` is `Some(_)`, the `RawFd` value is returned intact. Otherwise,
581    /// the default file descriptor is selected depending on the type of `self.body`.
582    pub fn fd_or_default(&self) -> Fd {
583        use RedirOp::*;
584        self.fd.unwrap_or(match self.body {
585            RedirBody::Normal { operator, .. } => match operator {
586                FileIn | FileInOut | FdIn | String => Fd::STDIN,
587                FileOut | FileAppend | FileClobber | FdOut | Pipe => Fd::STDOUT,
588            },
589            RedirBody::HereDoc { .. } => Fd::STDIN,
590        })
591    }
592}
593
594/// Expansion style of a simple command word
595///
596/// This enum specifies how a [`Word`] in a [`SimpleCommand`] should be expanded
597/// at runtime. The expansion mode is determined by whether the command name is
598/// a declaration utility and whether the word is in the form of an assignment.
599/// See the [`decl_util` module](crate::decl_util) for details.
600#[derive(Clone, Copy, Debug, Eq, PartialEq)]
601pub enum ExpansionMode {
602    /// Expand the word to a single field
603    Single,
604    /// Expand the word to multiple fields
605    Multiple,
606}
607
608/// Command that involves assignments, redirections, and word expansions
609///
610/// In the shell language syntax, a valid simple command must contain at least one of assignments,
611/// redirections, and words. The parser must not produce a completely empty simple command.
612#[derive(Clone, Debug, Eq, PartialEq)]
613pub struct SimpleCommand {
614    /// Assignments
615    pub assigns: Vec<Assign>,
616    /// Command name and arguments
617    pub words: Vec<(Word, ExpansionMode)>,
618    /// Redirections
619    pub redirs: Rc<Vec<Redir>>,
620}
621
622impl SimpleCommand {
623    /// Returns true if the simple command does not contain any assignments,
624    /// words, or redirections.
625    pub fn is_empty(&self) -> bool {
626        self.assigns.is_empty() && self.words.is_empty() && self.redirs.is_empty()
627    }
628
629    /// Returns true if the simple command contains only one word.
630    pub fn is_one_word(&self) -> bool {
631        self.assigns.is_empty() && self.words.len() == 1 && self.redirs.is_empty()
632    }
633
634    /// Tests whether the first word of the simple command is a keyword.
635    #[must_use]
636    fn first_word_is_keyword(&self) -> bool {
637        let Some((word, _)) = self.words.first() else {
638            return false;
639        };
640        let Some(literal) = word.to_string_if_literal() else {
641            return false;
642        };
643        literal.parse::<Keyword>().is_ok()
644    }
645}
646
647/// `elif-then` clause
648#[derive(Clone, Debug, Eq, PartialEq)]
649pub struct ElifThen {
650    pub condition: List,
651    pub body: List,
652}
653
654/// Symbol that terminates the body of a case branch and determines what to do
655/// after executing it
656#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
657pub enum CaseContinuation {
658    /// `;;` (terminate the case construct)
659    #[default]
660    Break,
661    /// `;&` (unconditionally execute the body of the next case branch)
662    FallThrough,
663    /// `;|` or `;;&` (resume with the next case branch, performing pattern matching again)
664    Continue,
665}
666
667/// Branch item of a `case` compound command
668#[derive(Clone, Debug, Eq, PartialEq)]
669pub struct CaseItem {
670    /// Array of patterns that are matched against the main word of the case
671    /// compound command to decide if the body of this item should be executed
672    ///
673    /// A syntactically valid case item must have at least one pattern.
674    pub patterns: Vec<Word>,
675    /// Commands that are executed if any of the patterns matched
676    pub body: List,
677    /// What to do after executing the body of this item
678    pub continuation: CaseContinuation,
679}
680
681/// Command that contains other commands
682#[derive(Clone, Debug, Eq, PartialEq)]
683pub enum CompoundCommand {
684    /// List as a command
685    Grouping(List),
686    /// Command for executing commands in a subshell
687    Subshell { body: Rc<List>, location: Location },
688    /// For loop
689    For {
690        name: Word,
691        values: Option<Vec<Word>>,
692        body: List,
693    },
694    /// While loop
695    While { condition: List, body: List },
696    /// Until loop
697    Until { condition: List, body: List },
698    /// If conditional construct
699    If {
700        condition: List,
701        body: List,
702        elifs: Vec<ElifThen>,
703        r#else: Option<List>,
704    },
705    /// Case conditional construct
706    Case { subject: Word, items: Vec<CaseItem> },
707    // TODO [[ ]]
708}
709
710/// Compound command with redirections
711#[derive(Clone, Debug, Eq, PartialEq)]
712pub struct FullCompoundCommand {
713    /// The main part
714    pub command: CompoundCommand,
715    /// Redirections
716    pub redirs: Vec<Redir>,
717}
718
719/// Function definition command
720#[derive(Clone, Debug, Eq, PartialEq)]
721pub struct FunctionDefinition {
722    /// Whether the function definition command starts with the `function` reserved word
723    pub has_keyword: bool,
724    /// Function name
725    pub name: Word,
726    /// Function body
727    pub body: Rc<FullCompoundCommand>,
728}
729
730/// Element of a pipe sequence
731#[derive(Clone, Debug, Eq, PartialEq)]
732pub enum Command {
733    /// Simple command
734    Simple(SimpleCommand),
735    /// Compound command
736    Compound(FullCompoundCommand),
737    /// Function definition command
738    Function(FunctionDefinition),
739}
740
741/// Commands separated by `|`
742#[derive(Clone, Debug, Eq, PartialEq)]
743pub struct Pipeline {
744    /// Elements of the pipeline
745    ///
746    /// A valid pipeline must have at least one command.
747    ///
748    /// The commands are contained in `Rc` to allow executing them
749    /// asynchronously without cloning them.
750    pub commands: Vec<Rc<Command>>,
751    /// Whether the pipeline begins with a `!`
752    pub negation: bool,
753}
754
755/// Condition that decides if a [Pipeline] in an [and-or list](AndOrList) should be executed
756#[derive(Clone, Copy, Debug, Eq, PartialEq)]
757pub enum AndOr {
758    /// `&&`
759    AndThen,
760    /// `||`
761    OrElse,
762}
763
764/// Pipelines separated by `&&` and `||`
765#[derive(Clone, Debug, Eq, PartialEq)]
766pub struct AndOrList {
767    pub first: Pipeline,
768    pub rest: Vec<(AndOr, Pipeline)>,
769}
770
771/// Element of a [List]
772#[derive(Clone, Debug, Eq, PartialEq)]
773pub struct Item {
774    /// Main part of this item
775    ///
776    /// The and-or list is contained in `Rc` to allow executing it
777    /// asynchronously without cloning it.
778    pub and_or: Rc<AndOrList>,
779    /// Location of the `&` operator for this item, if any
780    pub async_flag: Option<Location>,
781}
782
783/// Sequence of [and-or lists](AndOrList) separated by `;` or `&`
784///
785/// It depends on context whether an empty list is a valid syntax.
786#[derive(Clone, Debug, Eq, PartialEq)]
787pub struct List(pub Vec<Item>);
788
789/// Definitions and implementations of the [Unquote] and [MaybeLiteral] traits,
790/// and other conversions between types
791mod conversions;
792/// Implementations of [std::fmt::Display] for the shell language syntax types
793mod impl_display;
794
795pub use conversions::{MaybeLiteral, NotLiteral, NotSpecialParam, Unquote};