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(clippy::bool_assert_comparison)]
103#[cfg(test)]
104mod tests {
105    use super::super::error::ErrorCause;
106    use super::super::lex::Lexer;
107    use super::*;
108    use crate::alias::{AliasSet, HashEntry};
109    use crate::source::Location;
110    use crate::source::Source;
111    use futures_util::FutureExt;
112
113    #[test]
114    fn parser_pipeline_eof() {
115        let mut lexer = Lexer::with_code("");
116        let mut parser = Parser::new(&mut lexer);
117
118        let option = parser.pipeline().now_or_never().unwrap().unwrap().unwrap();
119        assert_eq!(option, None);
120    }
121
122    #[test]
123    fn parser_pipeline_one() {
124        let mut lexer = Lexer::with_code("foo");
125        let mut parser = Parser::new(&mut lexer);
126
127        let result = parser.pipeline().now_or_never().unwrap();
128        let p = result.unwrap().unwrap().unwrap();
129        assert_eq!(p.negation, false);
130        assert_eq!(p.commands.len(), 1);
131        assert_eq!(p.commands[0].to_string(), "foo");
132    }
133
134    #[test]
135    fn parser_pipeline_many() {
136        let mut lexer = Lexer::with_code("one | two | \n\t\n three");
137        let mut parser = Parser::new(&mut lexer);
138
139        let result = parser.pipeline().now_or_never().unwrap();
140        let p = result.unwrap().unwrap().unwrap();
141        assert_eq!(p.negation, false);
142        assert_eq!(p.commands.len(), 3);
143        assert_eq!(p.commands[0].to_string(), "one");
144        assert_eq!(p.commands[1].to_string(), "two");
145        assert_eq!(p.commands[2].to_string(), "three");
146    }
147
148    #[test]
149    fn parser_pipeline_negated() {
150        let mut lexer = Lexer::with_code("! foo");
151        let mut parser = Parser::new(&mut lexer);
152
153        let result = parser.pipeline().now_or_never().unwrap();
154        let p = result.unwrap().unwrap().unwrap();
155        assert_eq!(p.negation, true);
156        assert_eq!(p.commands.len(), 1);
157        assert_eq!(p.commands[0].to_string(), "foo");
158    }
159
160    #[test]
161    fn parser_pipeline_double_negation() {
162        let mut lexer = Lexer::with_code(" !  !");
163        let mut parser = Parser::new(&mut lexer);
164
165        let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
166        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::DoubleNegation));
167        assert_eq!(*e.location.code.value.borrow(), " !  !");
168        assert_eq!(e.location.code.start_line_number.get(), 1);
169        assert_eq!(*e.location.code.source, Source::Unknown);
170        assert_eq!(e.location.range, 4..5);
171    }
172
173    #[test]
174    fn parser_pipeline_missing_command_after_negation() {
175        let mut lexer = Lexer::with_code("!\nfoo");
176        let mut parser = Parser::new(&mut lexer);
177
178        let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
179        assert_eq!(
180            e.cause,
181            ErrorCause::Syntax(SyntaxError::MissingCommandAfterBang)
182        );
183        assert_eq!(*e.location.code.value.borrow(), "!\n");
184        assert_eq!(e.location.code.start_line_number.get(), 1);
185        assert_eq!(*e.location.code.source, Source::Unknown);
186        assert_eq!(e.location.range, 1..2);
187    }
188
189    #[test]
190    fn parser_pipeline_missing_command_after_bar() {
191        let mut lexer = Lexer::with_code("foo | ;");
192        let mut parser = Parser::new(&mut lexer);
193
194        let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
195        assert_eq!(
196            e.cause,
197            ErrorCause::Syntax(SyntaxError::MissingCommandAfterBar)
198        );
199        assert_eq!(*e.location.code.value.borrow(), "foo | ;");
200        assert_eq!(e.location.code.start_line_number.get(), 1);
201        assert_eq!(*e.location.code.source, Source::Unknown);
202        assert_eq!(e.location.range, 6..7);
203    }
204
205    #[test]
206    fn parser_pipeline_bang_after_bar() {
207        let mut lexer = Lexer::with_code("foo | !");
208        let mut parser = Parser::new(&mut lexer);
209
210        let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
211        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::BangAfterBar));
212        assert_eq!(*e.location.code.value.borrow(), "foo | !");
213        assert_eq!(e.location.code.start_line_number.get(), 1);
214        assert_eq!(*e.location.code.source, Source::Unknown);
215        assert_eq!(e.location.range, 6..7);
216    }
217
218    #[test]
219    fn parser_pipeline_no_aliasing_of_bang() {
220        let mut lexer = Lexer::with_code("! ok");
221        #[allow(clippy::mutable_key_type)]
222        let mut aliases = AliasSet::new();
223        let origin = Location::dummy("");
224        aliases.insert(HashEntry::new(
225            "!".to_string(),
226            "; ; ;".to_string(),
227            true,
228            origin,
229        ));
230        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
231
232        let result = parser.pipeline().now_or_never().unwrap();
233        let p = result.unwrap().unwrap().unwrap();
234        assert_eq!(p.negation, true);
235        assert_eq!(p.commands.len(), 1);
236        assert_eq!(p.commands[0].to_string(), "ok");
237    }
238
239    #[test]
240    fn parser_alias_substitution_to_newline_after_bar() {
241        let mut lexer = Lexer::with_code("foo | X\n bar");
242        #[allow(clippy::mutable_key_type)]
243        let mut aliases = AliasSet::new();
244        aliases.insert(HashEntry::new(
245            "X".to_string(),
246            "\n".to_string(),
247            false,
248            Location::dummy(""),
249        ));
250        let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
251
252        let result = parser.pipeline().now_or_never().unwrap();
253        let p = result.unwrap().unwrap().unwrap();
254        assert_eq!(p.negation, false);
255        assert_eq!(p.commands.len(), 2);
256        assert_eq!(p.commands[0].to_string(), "foo");
257        assert_eq!(p.commands[1].to_string(), "bar");
258    }
259}