Skip to main content

yash_syntax/parser/
pipeline.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//! Syntax parser for pipeline
18
19use super::core::Parser;
20use super::core::Rec;
21use super::core::Result;
22use super::error::Error;
23use super::error::SyntaxError;
24use super::lex::Keyword::Bang;
25use super::lex::Operator::Bar;
26use super::lex::TokenId::{Operator, Token};
27use crate::syntax::Pipeline;
28use std::rc::Rc;
29
30impl Parser<'_, '_> {
31    /// Parses a pipeline.
32    ///
33    /// If there is no valid pipeline at the current position, this function
34    /// returns `Ok(Rec::Parsed(None))`.
35    pub async fn pipeline(&mut self) -> Result<Rec<Option<Pipeline>>> {
36        // Parse the first command
37        let (first, negation) = match self.command().await? {
38            Rec::AliasSubstituted => return Ok(Rec::AliasSubstituted),
39            Rec::Parsed(Some(first)) => (first, false),
40            Rec::Parsed(None) => {
41                // Parse the `!` reserved word
42                if self.peek_token().await?.id != Token(Some(Bang)) {
43                    return Ok(Rec::Parsed(None));
44                }
45                self.take_token_raw().await?;
46                // TODO Warn if `!` is immediately followed by `(`, which is
47                // not POSIXly portable.
48
49                // Parse the command after the `!`
50                loop {
51                    match self.command().await? {
52                        Rec::AliasSubstituted => continue,
53                        Rec::Parsed(Some(first)) => break (first, true),
54                        Rec::Parsed(None) => {
55                            // Error: the command is missing
56                            let next = self.take_token_raw().await?;
57                            let cause = if next.id == Token(Some(Bang)) {
58                                SyntaxError::DoubleNegation.into()
59                            } else {
60                                SyntaxError::MissingCommandAfterBang.into()
61                            };
62                            let location = next.word.location;
63                            return Err(Error { cause, location });
64                        }
65                    }
66                }
67            }
68        };
69
70        // Parse `|`
71        let mut commands = vec![Rc::new(first)];
72        while self.peek_token().await?.id == Operator(Bar) {
73            self.take_token_raw().await?;
74
75            // Parse the next command
76            let next = loop {
77                while self.newline_and_here_doc_contents().await? {}
78
79                match self.command().await? {
80                    Rec::AliasSubstituted => continue,
81                    Rec::Parsed(Some(next)) => break next,
82                    Rec::Parsed(None) => {
83                        // Error: the command is missing
84                        let next = self.take_token_raw().await?;
85                        let cause = if next.id == Token(Some(Bang)) {
86                            SyntaxError::BangAfterBar.into()
87                        } else {
88                            SyntaxError::MissingCommandAfterBar.into()
89                        };
90                        let location = next.word.location;
91                        return Err(Error { cause, location });
92                    }
93                }
94            };
95            commands.push(Rc::new(next));
96        }
97
98        Ok(Rec::Parsed(Some(Pipeline { commands, negation })))
99    }
100}
101
102#[allow(
103    clippy::bool_assert_comparison,
104    reason = "to make the expected values clearer"
105)]
106#[cfg(test)]
107mod tests {
108    use super::super::error::ErrorCause;
109    use super::super::lex::Lexer;
110    use super::*;
111    use crate::alias::{AliasSet, HashEntry};
112    use crate::source::Location;
113    use crate::source::Source;
114    use futures_util::FutureExt as _;
115
116    #[test]
117    fn parser_pipeline_eof() {
118        let mut lexer = Lexer::with_code("");
119        let mut parser = Parser::new(&mut lexer);
120
121        let option = parser.pipeline().now_or_never().unwrap().unwrap().unwrap();
122        assert_eq!(option, None);
123    }
124
125    #[test]
126    fn parser_pipeline_one() {
127        let mut lexer = Lexer::with_code("foo");
128        let mut parser = Parser::new(&mut lexer);
129
130        let result = parser.pipeline().now_or_never().unwrap();
131        let p = result.unwrap().unwrap().unwrap();
132        assert_eq!(p.negation, false);
133        assert_eq!(p.commands.len(), 1);
134        assert_eq!(p.commands[0].to_string(), "foo");
135    }
136
137    #[test]
138    fn parser_pipeline_many() {
139        let mut lexer = Lexer::with_code("one | two | \n\t\n three");
140        let mut parser = Parser::new(&mut lexer);
141
142        let result = parser.pipeline().now_or_never().unwrap();
143        let p = result.unwrap().unwrap().unwrap();
144        assert_eq!(p.negation, false);
145        assert_eq!(p.commands.len(), 3);
146        assert_eq!(p.commands[0].to_string(), "one");
147        assert_eq!(p.commands[1].to_string(), "two");
148        assert_eq!(p.commands[2].to_string(), "three");
149    }
150
151    #[test]
152    fn parser_pipeline_negated() {
153        let mut lexer = Lexer::with_code("! foo");
154        let mut parser = Parser::new(&mut lexer);
155
156        let result = parser.pipeline().now_or_never().unwrap();
157        let p = result.unwrap().unwrap().unwrap();
158        assert_eq!(p.negation, true);
159        assert_eq!(p.commands.len(), 1);
160        assert_eq!(p.commands[0].to_string(), "foo");
161    }
162
163    #[test]
164    fn parser_pipeline_double_negation() {
165        let mut lexer = Lexer::with_code(" !  !");
166        let mut parser = Parser::new(&mut lexer);
167
168        let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
169        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::DoubleNegation));
170        assert_eq!(*e.location.code.value.borrow(), " !  !");
171        assert_eq!(e.location.code.start_line_number.get(), 1);
172        assert_eq!(*e.location.code.source, Source::Unknown);
173        assert_eq!(e.location.range, 4..5);
174    }
175
176    #[test]
177    fn parser_pipeline_missing_command_after_negation() {
178        let mut lexer = Lexer::with_code("!\nfoo");
179        let mut parser = Parser::new(&mut lexer);
180
181        let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
182        assert_eq!(
183            e.cause,
184            ErrorCause::Syntax(SyntaxError::MissingCommandAfterBang)
185        );
186        assert_eq!(*e.location.code.value.borrow(), "!\n");
187        assert_eq!(e.location.code.start_line_number.get(), 1);
188        assert_eq!(*e.location.code.source, Source::Unknown);
189        assert_eq!(e.location.range, 1..2);
190    }
191
192    #[test]
193    fn parser_pipeline_missing_command_after_bar() {
194        let mut lexer = Lexer::with_code("foo | ;");
195        let mut parser = Parser::new(&mut lexer);
196
197        let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
198        assert_eq!(
199            e.cause,
200            ErrorCause::Syntax(SyntaxError::MissingCommandAfterBar)
201        );
202        assert_eq!(*e.location.code.value.borrow(), "foo | ;");
203        assert_eq!(e.location.code.start_line_number.get(), 1);
204        assert_eq!(*e.location.code.source, Source::Unknown);
205        assert_eq!(e.location.range, 6..7);
206    }
207
208    #[test]
209    fn parser_pipeline_bang_after_bar() {
210        let mut lexer = Lexer::with_code("foo | !");
211        let mut parser = Parser::new(&mut lexer);
212
213        let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
214        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::BangAfterBar));
215        assert_eq!(*e.location.code.value.borrow(), "foo | !");
216        assert_eq!(e.location.code.start_line_number.get(), 1);
217        assert_eq!(*e.location.code.source, Source::Unknown);
218        assert_eq!(e.location.range, 6..7);
219    }
220
221    #[test]
222    fn parser_pipeline_no_aliasing_of_bang() {
223        let mut lexer = Lexer::with_code("! ok");
224        #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
225        let mut aliases = AliasSet::new();
226        let origin = Location::dummy("");
227        aliases.insert(HashEntry::new(
228            "!".to_string(),
229            "; ; ;".to_string(),
230            true,
231            origin,
232        ));
233        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
234
235        let result = parser.pipeline().now_or_never().unwrap();
236        let p = result.unwrap().unwrap().unwrap();
237        assert_eq!(p.negation, true);
238        assert_eq!(p.commands.len(), 1);
239        assert_eq!(p.commands[0].to_string(), "ok");
240    }
241
242    #[test]
243    fn parser_alias_substitution_to_newline_after_bar() {
244        let mut lexer = Lexer::with_code("foo | X\n bar");
245        #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
246        let mut aliases = AliasSet::new();
247        aliases.insert(HashEntry::new(
248            "X".to_string(),
249            "\n".to_string(),
250            false,
251            Location::dummy(""),
252        ));
253        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
254
255        let result = parser.pipeline().now_or_never().unwrap();
256        let p = result.unwrap().unwrap().unwrap();
257        assert_eq!(p.negation, false);
258        assert_eq!(p.commands.len(), 2);
259        assert_eq!(p.commands[0].to_string(), "foo");
260        assert_eq!(p.commands[1].to_string(), "bar");
261    }
262}