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;
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 self.take_token_raw().await?;
46 loop {
51 match self.command().await? {
52 Rec::AliasSubstituted => continue,
53 Rec::Parsed(Some(first)) => break (first, true),
54 Rec::Parsed(None) => {
55 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 let mut commands = vec![Rc::new(first)];
72 while self.peek_token().await?.id == Operator(Bar) {
73 self.take_token_raw().await?;
74
75 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 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}