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(
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}