yash_syntax/parser/
pipeline.rs1use 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, OpenParen};
26use super::lex::TokenId::{Operator, Token};
27use crate::syntax::Pipeline;
28use std::rc::Rc;
29
30impl Parser<'_, '_> {
31 pub async fn pipeline(&mut self) -> Result<Rec<Option<Pipeline>>> {
36 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 if self.peek_token().await?.id != Token(Some(Bang)) {
43 return Ok(Rec::Parsed(None));
44 }
45 let bang = self.take_token_raw().await?;
46
47 if self.mode().portable {
50 let next = self.peek_token().await?;
51 if next.id == Operator(OpenParen) && next.index == bang.index + 1 {
52 let location = next.word.location.clone();
53 let cause = SyntaxError::UnsupportedExtendedGlob.into();
54 return Err(Error { cause, location });
55 }
56 }
57
58 loop {
60 match self.command().await? {
61 Rec::AliasSubstituted => continue,
62 Rec::Parsed(Some(first)) => break (first, true),
63 Rec::Parsed(None) => {
64 let next = self.take_token_raw().await?;
66 let cause = if next.id == Token(Some(Bang)) {
67 SyntaxError::DoubleNegation.into()
68 } else {
69 SyntaxError::MissingCommandAfterBang.into()
70 };
71 let location = next.word.location;
72 return Err(Error { cause, location });
73 }
74 }
75 }
76 }
77 };
78
79 let mut commands = vec![Rc::new(first)];
81 while self.peek_token().await?.id == Operator(Bar) {
82 self.take_token_raw().await?;
83
84 let next = loop {
86 while self.newline_and_here_doc_contents().await? {}
87
88 match self.command().await? {
89 Rec::AliasSubstituted => continue,
90 Rec::Parsed(Some(next)) => break next,
91 Rec::Parsed(None) => {
92 let next = self.take_token_raw().await?;
94 let cause = if next.id == Token(Some(Bang)) {
95 SyntaxError::BangAfterBar.into()
96 } else {
97 SyntaxError::MissingCommandAfterBar.into()
98 };
99 let location = next.word.location;
100 return Err(Error { cause, location });
101 }
102 }
103 };
104 commands.push(Rc::new(next));
105 }
106
107 Ok(Rec::Parsed(Some(Pipeline { commands, negation })))
108 }
109}
110
111#[allow(
112 clippy::bool_assert_comparison,
113 reason = "to make the expected values clearer"
114)]
115#[cfg(test)]
116mod tests {
117 use super::super::error::ErrorCause;
118 use super::super::lex::Lexer;
119 use super::*;
120 use crate::alias::{AliasSet, HashEntry};
121 use crate::source::Location;
122 use crate::source::Source;
123 use futures_util::FutureExt as _;
124
125 #[test]
126 fn parser_pipeline_eof() {
127 let mut lexer = Lexer::with_code("");
128 let mut parser = Parser::new(&mut lexer);
129
130 let option = parser.pipeline().now_or_never().unwrap().unwrap().unwrap();
131 assert_eq!(option, None);
132 }
133
134 #[test]
135 fn parser_pipeline_one() {
136 let mut lexer = Lexer::with_code("foo");
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(), 1);
143 assert_eq!(p.commands[0].to_string(), "foo");
144 }
145
146 #[test]
147 fn parser_pipeline_many() {
148 let mut lexer = Lexer::with_code("one | two | \n\t\n three");
149 let mut parser = Parser::new(&mut lexer);
150
151 let result = parser.pipeline().now_or_never().unwrap();
152 let p = result.unwrap().unwrap().unwrap();
153 assert_eq!(p.negation, false);
154 assert_eq!(p.commands.len(), 3);
155 assert_eq!(p.commands[0].to_string(), "one");
156 assert_eq!(p.commands[1].to_string(), "two");
157 assert_eq!(p.commands[2].to_string(), "three");
158 }
159
160 #[test]
161 fn parser_pipeline_negated() {
162 let mut lexer = Lexer::with_code("! foo");
163 let mut parser = Parser::new(&mut lexer);
164
165 let result = parser.pipeline().now_or_never().unwrap();
166 let p = result.unwrap().unwrap().unwrap();
167 assert_eq!(p.negation, true);
168 assert_eq!(p.commands.len(), 1);
169 assert_eq!(p.commands[0].to_string(), "foo");
170 }
171
172 #[test]
173 fn parser_pipeline_double_negation() {
174 let mut lexer = Lexer::with_code(" ! !");
175 let mut parser = Parser::new(&mut lexer);
176
177 let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
178 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::DoubleNegation));
179 assert_eq!(*e.location.code.value.borrow(), " ! !");
180 assert_eq!(e.location.code.start_line_number.get(), 1);
181 assert_eq!(*e.location.code.source, Source::Unknown);
182 assert_eq!(e.location.range, 4..5);
183 }
184
185 #[test]
186 fn parser_pipeline_missing_command_after_negation() {
187 let mut lexer = Lexer::with_code("!\nfoo");
188 let mut parser = Parser::new(&mut lexer);
189
190 let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
191 assert_eq!(
192 e.cause,
193 ErrorCause::Syntax(SyntaxError::MissingCommandAfterBang)
194 );
195 assert_eq!(*e.location.code.value.borrow(), "!\n");
196 assert_eq!(e.location.code.start_line_number.get(), 1);
197 assert_eq!(*e.location.code.source, Source::Unknown);
198 assert_eq!(e.location.range, 1..2);
199 }
200
201 #[test]
202 fn parser_pipeline_missing_command_after_bar() {
203 let mut lexer = Lexer::with_code("foo | ;");
204 let mut parser = Parser::new(&mut lexer);
205
206 let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
207 assert_eq!(
208 e.cause,
209 ErrorCause::Syntax(SyntaxError::MissingCommandAfterBar)
210 );
211 assert_eq!(*e.location.code.value.borrow(), "foo | ;");
212 assert_eq!(e.location.code.start_line_number.get(), 1);
213 assert_eq!(*e.location.code.source, Source::Unknown);
214 assert_eq!(e.location.range, 6..7);
215 }
216
217 #[test]
218 fn parser_pipeline_bang_after_bar() {
219 let mut lexer = Lexer::with_code("foo | !");
220 let mut parser = Parser::new(&mut lexer);
221
222 let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
223 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::BangAfterBar));
224 assert_eq!(*e.location.code.value.borrow(), "foo | !");
225 assert_eq!(e.location.code.start_line_number.get(), 1);
226 assert_eq!(*e.location.code.source, Source::Unknown);
227 assert_eq!(e.location.range, 6..7);
228 }
229
230 #[test]
231 fn parser_pipeline_no_aliasing_of_bang() {
232 let mut lexer = Lexer::with_code("! ok");
233 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
234 let mut aliases = AliasSet::new();
235 let origin = Location::dummy("");
236 aliases.insert(HashEntry::new(
237 "!".to_string(),
238 "; ; ;".to_string(),
239 true,
240 origin,
241 ));
242 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
243
244 let result = parser.pipeline().now_or_never().unwrap();
245 let p = result.unwrap().unwrap().unwrap();
246 assert_eq!(p.negation, true);
247 assert_eq!(p.commands.len(), 1);
248 assert_eq!(p.commands[0].to_string(), "ok");
249 }
250
251 #[test]
252 fn parser_alias_substitution_to_newline_after_bar() {
253 let mut lexer = Lexer::with_code("foo | X\n bar");
254 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
255 let mut aliases = AliasSet::new();
256 aliases.insert(HashEntry::new(
257 "X".to_string(),
258 "\n".to_string(),
259 false,
260 Location::dummy(""),
261 ));
262 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
263
264 let result = parser.pipeline().now_or_never().unwrap();
265 let p = result.unwrap().unwrap().unwrap();
266 assert_eq!(p.negation, false);
267 assert_eq!(p.commands.len(), 2);
268 assert_eq!(p.commands[0].to_string(), "foo");
269 assert_eq!(p.commands[1].to_string(), "bar");
270 }
271
272 fn portable_mode() -> yash_env::parser::Mode {
273 let mut mode = yash_env::parser::Mode::default();
274 mode.portable = true;
275 mode
276 }
277
278 #[test]
279 fn parser_pipeline_bang_open_paren_rejected_in_portable_mode() {
280 let mut lexer = Lexer::with_code("!(:)");
281 lexer.set_mode(portable_mode());
282 let mut parser = Parser::new(&mut lexer);
283
284 let e = parser.pipeline().now_or_never().unwrap().unwrap_err();
285 assert_eq!(
286 e.cause,
287 ErrorCause::Syntax(SyntaxError::UnsupportedExtendedGlob)
288 );
289 assert_eq!(e.location.range, 1..2);
291 }
292
293 #[test]
294 fn parser_pipeline_bang_spaced_open_paren_accepted_in_portable_mode() {
295 let mut lexer = Lexer::with_code("! (:)");
297 lexer.set_mode(portable_mode());
298 let mut parser = Parser::new(&mut lexer);
299
300 let result = parser.pipeline().now_or_never().unwrap();
301 let p = result.unwrap().unwrap().unwrap();
302 assert_eq!(p.negation, true);
303 assert_eq!(p.commands[0].to_string(), "(:)");
304 }
305
306 #[test]
307 fn parser_pipeline_bang_open_paren_accepted_without_portable() {
308 let mut lexer = Lexer::with_code("!(:)");
310 let mut parser = Parser::new(&mut lexer);
311
312 let result = parser.pipeline().now_or_never().unwrap();
313 let p = result.unwrap().unwrap().unwrap();
314 assert_eq!(p.negation, true);
315 assert_eq!(p.commands[0].to_string(), "(:)");
316 }
317}