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