1use super::core::Parser;
23use super::core::Result;
24use super::error::Error;
25use super::error::SyntaxError;
26use super::lex::Keyword::{Case, Do, Done, For, If, OpenBrace, Until, While};
27use super::lex::Operator::OpenParen;
28use super::lex::TokenId::{Operator, Token};
29use crate::syntax::CompoundCommand;
30use crate::syntax::FullCompoundCommand;
31use crate::syntax::List;
32
33impl Parser<'_, '_> {
34 pub async fn do_clause(&mut self) -> Result<Option<List>> {
38 if self.peek_token().await?.id != Token(Some(Do)) {
39 return Ok(None);
40 }
41
42 let open = self.take_token_raw().await?;
43
44 let list = self.maybe_compound_list_boxed().await?;
45
46 let close = self.take_token_raw().await?;
47 if close.id != Token(Some(Done)) {
48 let opening_location = open.word.location;
49 let cause = SyntaxError::UnclosedDoClause { opening_location }.into();
50 let location = close.word.location;
51 return Err(Error { cause, location });
52 }
53
54 if list.0.is_empty() {
56 let cause = SyntaxError::EmptyDoClause.into();
57 let location = close.word.location;
58 return Err(Error { cause, location });
59 }
60
61 Ok(Some(list))
62 }
63
64 pub async fn compound_command(&mut self) -> Result<Option<CompoundCommand>> {
66 match self.peek_token().await?.id {
67 Token(Some(OpenBrace)) => self.grouping().await.map(Some),
68 Operator(OpenParen) => self.subshell().await.map(Some),
69 Token(Some(For)) => self.for_loop().await.map(Some),
70 Token(Some(While)) => self.while_loop().await.map(Some),
71 Token(Some(Until)) => self.until_loop().await.map(Some),
72 Token(Some(If)) => self.if_command().await.map(Some),
73 Token(Some(Case)) => self.case_command().await.map(Some),
74 _ => Ok(None),
75 }
76 }
77
78 pub async fn full_compound_command(&mut self) -> Result<Option<FullCompoundCommand>> {
80 let command = match self.compound_command().await? {
81 Some(command) => command,
82 None => return Ok(None),
83 };
84 let redirs = self.redirections().await?;
85
86 let ends_with_reserved_word =
94 redirs.is_empty() && !matches!(command, CompoundCommand::Subshell { .. });
95 if self.mode().portable && !ends_with_reserved_word {
96 let next = self.peek_token().await?;
97 if let Token(Some(keyword)) = next.id
98 && keyword.is_clause_delimiter()
99 {
100 let location = next.word.location.clone();
101 return Err(Error {
102 cause: SyntaxError::MissingSeparatorBeforeReservedWord.into(),
103 location,
104 });
105 }
106 }
107
108 Ok(Some(FullCompoundCommand { command, redirs }))
109 }
110}
111
112#[allow(
113 clippy::bool_assert_comparison,
114 reason = "to make the expected values clearer"
115)]
116#[cfg(test)]
117mod tests {
118 use super::super::error::ErrorCause;
119 use super::super::lex::Keyword::CloseBrace;
120 use super::super::lex::Lexer;
121 use super::super::lex::Operator::Semicolon;
122 use super::super::lex::TokenId::EndOfInput;
123 use super::*;
124 use crate::alias::{AliasSet, HashEntry};
125 use crate::source::Location;
126 use crate::source::Source;
127 use crate::syntax::Command;
128 use crate::syntax::ExpansionMode;
129 use crate::syntax::SimpleCommand;
130 use assert_matches::assert_matches;
131 use futures_util::FutureExt as _;
132
133 #[test]
134 fn parser_do_clause_none() {
135 let mut lexer = Lexer::with_code("done");
136 let mut parser = Parser::new(&mut lexer);
137
138 let result = parser.do_clause().now_or_never().unwrap().unwrap();
139 assert!(result.is_none(), "result should be none: {result:?}");
140 }
141
142 #[test]
143 fn parser_do_clause_short() {
144 let mut lexer = Lexer::with_code("do :; done");
145 let mut parser = Parser::new(&mut lexer);
146
147 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
148 assert_eq!(result.to_string(), ":");
149
150 let next = parser.peek_token().now_or_never().unwrap().unwrap();
151 assert_eq!(next.id, EndOfInput);
152 }
153
154 #[test]
155 fn parser_do_clause_long() {
156 let mut lexer = Lexer::with_code("do foo; bar& done");
157 let mut parser = Parser::new(&mut lexer);
158
159 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
160 assert_eq!(result.to_string(), "foo; bar&");
161
162 let next = parser.peek_token().now_or_never().unwrap().unwrap();
163 assert_eq!(next.id, EndOfInput);
164 }
165
166 #[test]
167 fn parser_do_clause_unclosed() {
168 let mut lexer = Lexer::with_code(" do not close ");
169 let mut parser = Parser::new(&mut lexer);
170
171 let e = parser.do_clause().now_or_never().unwrap().unwrap_err();
172 assert_matches!(e.cause,
173 ErrorCause::Syntax(SyntaxError::UnclosedDoClause { opening_location }) => {
174 assert_eq!(*opening_location.code.value.borrow(), " do not close ");
175 assert_eq!(opening_location.code.start_line_number.get(), 1);
176 assert_eq!(*opening_location.code.source, Source::Unknown);
177 assert_eq!(opening_location.range, 1..3);
178 });
179 assert_eq!(*e.location.code.value.borrow(), " do not close ");
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, 14..14);
183 }
184
185 #[test]
186 fn parser_do_clause_empty_posix() {
187 let mut lexer = Lexer::with_code("do done");
188 let mut parser = Parser::new(&mut lexer);
189
190 let e = parser.do_clause().now_or_never().unwrap().unwrap_err();
191 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EmptyDoClause));
192 assert_eq!(*e.location.code.value.borrow(), "do done");
193 assert_eq!(e.location.code.start_line_number.get(), 1);
194 assert_eq!(*e.location.code.source, Source::Unknown);
195 assert_eq!(e.location.range, 3..7);
196 }
197
198 #[test]
199 fn parser_do_clause_aliasing() {
200 let mut lexer = Lexer::with_code(" do :; end ");
201 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
202 let mut aliases = AliasSet::new();
203 let origin = Location::dummy("");
204 aliases.insert(HashEntry::new(
205 "do".to_string(),
206 "".to_string(),
207 false,
208 origin.clone(),
209 ));
210 aliases.insert(HashEntry::new(
211 "done".to_string(),
212 "".to_string(),
213 false,
214 origin.clone(),
215 ));
216 aliases.insert(HashEntry::new(
217 "end".to_string(),
218 "done".to_string(),
219 false,
220 origin,
221 ));
222 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
223
224 let result = parser.do_clause().now_or_never().unwrap().unwrap().unwrap();
225 assert_eq!(result.to_string(), ":");
226
227 let next = parser.peek_token().now_or_never().unwrap().unwrap();
228 assert_eq!(next.id, EndOfInput);
229 }
230
231 #[test]
232 fn parser_compound_command_none() {
233 let mut lexer = Lexer::with_code("}");
234 let mut parser = Parser::new(&mut lexer);
235
236 let option = parser.compound_command().now_or_never().unwrap().unwrap();
237 assert_eq!(option, None);
238 }
239
240 #[test]
241 fn parser_full_compound_command_without_redirections() {
242 let mut lexer = Lexer::with_code("(:)");
243 let mut parser = Parser::new(&mut lexer);
244
245 let result = parser.full_compound_command().now_or_never().unwrap();
246 let FullCompoundCommand { command, redirs } = result.unwrap().unwrap();
247 assert_eq!(command.to_string(), "(:)");
248 assert_eq!(redirs, []);
249 }
250
251 #[test]
252 fn parser_full_compound_command_with_redirections() {
253 let mut lexer = Lexer::with_code("(command) <foo >bar ;");
254 let mut parser = Parser::new(&mut lexer);
255
256 let result = parser.full_compound_command().now_or_never().unwrap();
257 let FullCompoundCommand { command, redirs } = result.unwrap().unwrap();
258 assert_eq!(command.to_string(), "(command)");
259 assert_eq!(redirs.len(), 2);
260 assert_eq!(redirs[0].to_string(), "<foo");
261 assert_eq!(redirs[1].to_string(), ">bar");
262
263 let next = parser.peek_token().now_or_never().unwrap().unwrap();
264 assert_eq!(next.id, Operator(Semicolon));
265 }
266
267 fn portable_mode() -> yash_env::parser::Mode {
268 let mut mode = yash_env::parser::Mode::default();
269 mode.portable = true;
270 mode
271 }
272
273 #[test]
274 fn parser_full_compound_command_close_brace_after_subshell_rejected_in_portable_mode() {
275 let mut lexer = Lexer::with_code("( : ) }");
277 lexer.set_mode(portable_mode());
278 let mut parser = Parser::new(&mut lexer);
279
280 let e = parser
281 .full_compound_command()
282 .now_or_never()
283 .unwrap()
284 .unwrap_err();
285 assert_eq!(
286 e.cause,
287 ErrorCause::Syntax(SyntaxError::MissingSeparatorBeforeReservedWord)
288 );
289 assert_eq!(*e.location.code.value.borrow(), "( : ) }");
290 assert_eq!(e.location.range, 6..7);
291 }
292
293 #[test]
294 fn parser_full_compound_command_done_after_subshell_rejected_in_portable_mode() {
295 let mut lexer = Lexer::with_code("( : ) done");
297 lexer.set_mode(portable_mode());
298 let mut parser = Parser::new(&mut lexer);
299
300 let e = parser
301 .full_compound_command()
302 .now_or_never()
303 .unwrap()
304 .unwrap_err();
305 assert_eq!(
306 e.cause,
307 ErrorCause::Syntax(SyntaxError::MissingSeparatorBeforeReservedWord)
308 );
309 assert_eq!(e.location.range, 6..10);
310 }
311
312 #[test]
313 fn parser_full_compound_command_then_after_subshell_rejected_in_portable_mode() {
314 let mut lexer = Lexer::with_code("( : ) then");
315 lexer.set_mode(portable_mode());
316 let mut parser = Parser::new(&mut lexer);
317
318 let e = parser
319 .full_compound_command()
320 .now_or_never()
321 .unwrap()
322 .unwrap_err();
323 assert_eq!(
324 e.cause,
325 ErrorCause::Syntax(SyntaxError::MissingSeparatorBeforeReservedWord)
326 );
327 }
328
329 #[test]
330 fn parser_full_compound_command_close_brace_after_redirection_rejected_in_portable_mode() {
331 let mut lexer = Lexer::with_code("{ :; } >foo }");
332 lexer.set_mode(portable_mode());
333 let mut parser = Parser::new(&mut lexer);
334
335 let e = parser
336 .full_compound_command()
337 .now_or_never()
338 .unwrap()
339 .unwrap_err();
340 assert_eq!(
341 e.cause,
342 ErrorCause::Syntax(SyntaxError::MissingSeparatorBeforeReservedWord)
343 );
344 }
345
346 #[test]
347 fn parser_full_compound_command_close_brace_allowed_without_portable() {
348 let mut lexer = Lexer::with_code("( : ) }");
349 let mut parser = Parser::new(&mut lexer);
350
351 let result = parser.full_compound_command().now_or_never().unwrap();
352 let FullCompoundCommand { command, .. } = result.unwrap().unwrap();
353 assert_eq!(command.to_string(), "(:)");
354
355 let next = parser.peek_token().now_or_never().unwrap().unwrap();
357 assert_eq!(next.id, Token(Some(CloseBrace)));
358 }
359
360 #[test]
361 fn parser_full_compound_command_separator_before_close_brace_allowed_in_portable_mode() {
362 let mut lexer = Lexer::with_code("( : ) ;");
363 lexer.set_mode(portable_mode());
364 let mut parser = Parser::new(&mut lexer);
365
366 let result = parser.full_compound_command().now_or_never().unwrap();
367 let FullCompoundCommand { command, .. } = result.unwrap().unwrap();
368 assert_eq!(command.to_string(), "(:)");
369 }
370
371 #[test]
372 fn parser_full_compound_command_close_brace_after_grouping_allowed_in_portable_mode() {
373 let mut lexer = Lexer::with_code("{ :; } }");
376 lexer.set_mode(portable_mode());
377 let mut parser = Parser::new(&mut lexer);
378
379 let result = parser.full_compound_command().now_or_never().unwrap();
380 let FullCompoundCommand { command, redirs } = result.unwrap().unwrap();
381 assert_matches!(command, CompoundCommand::Grouping(_));
382 assert_eq!(redirs, []);
383
384 let next = parser.peek_token().now_or_never().unwrap().unwrap();
386 assert_eq!(next.id, Token(Some(CloseBrace)));
387 }
388
389 #[test]
390 fn parser_full_compound_command_close_brace_after_if_allowed_in_portable_mode() {
391 let mut lexer = Lexer::with_code("if true; then :; fi }");
393 lexer.set_mode(portable_mode());
394 let mut parser = Parser::new(&mut lexer);
395
396 let result = parser.full_compound_command().now_or_never().unwrap();
397 let FullCompoundCommand { command, redirs } = result.unwrap().unwrap();
398 assert_matches!(command, CompoundCommand::If { .. });
399 assert_eq!(redirs, []);
400
401 let next = parser.peek_token().now_or_never().unwrap().unwrap();
402 assert_eq!(next.id, Token(Some(CloseBrace)));
403 }
404
405 #[test]
406 fn parser_full_compound_command_none() {
407 let mut lexer = Lexer::with_code("}");
408 let mut parser = Parser::new(&mut lexer);
409
410 let result = parser.full_compound_command().now_or_never().unwrap();
411 assert_eq!(result, Ok(None));
412 }
413
414 #[test]
415 fn parser_short_function_definition_ok() {
416 let mut lexer = Lexer::with_code(" ( ) ( : ) > /dev/null ");
417 let mut parser = Parser::new(&mut lexer);
418 let c = SimpleCommand {
419 assigns: vec![],
420 words: vec![("foo".parse().unwrap(), ExpansionMode::Multiple)],
421 redirs: vec![].into(),
422 };
423
424 let result = parser.short_function_definition(c).now_or_never().unwrap();
425 let command = result.unwrap();
426 assert_matches!(command, Command::Function(f) => {
427 assert_eq!(f.has_keyword, false);
428 assert_eq!(f.name.to_string(), "foo");
429 assert_eq!(f.body.to_string(), "(:) >/dev/null");
430 });
431
432 let next = parser.peek_token().now_or_never().unwrap().unwrap();
433 assert_eq!(next.id, EndOfInput);
434 }
435}