1use super::core::Parser;
20use super::core::Rec;
21use super::core::Result;
22use super::error::Error;
23use super::error::SyntaxError;
24use super::lex::Keyword::{Case, Esac, In};
25use super::lex::Operator::{Bar, CloseParen, Newline, OpenParen};
26use super::lex::TokenId::{self, EndOfInput, Operator, Token};
27use crate::syntax::CaseItem;
28use crate::syntax::CompoundCommand;
29
30impl Parser<'_, '_> {
31 pub async fn case_item(&mut self) -> Result<Option<(CaseItem, bool)>> {
36 fn pattern_error_cause(token_id: TokenId) -> SyntaxError {
37 match token_id {
38 Token(_) => unreachable!(),
39 Operator(CloseParen) | Operator(Bar) | Operator(Newline) | EndOfInput => {
40 SyntaxError::MissingPattern
41 }
42 _ => SyntaxError::InvalidPattern,
43 }
44 }
45
46 let first_token = loop {
47 while self.newline_and_here_doc_contents().await? {}
48
49 if self.peek_token().await?.id == Token(Some(Esac)) {
50 return Ok(None);
51 }
52
53 match self.take_token_manual(false).await? {
54 Rec::AliasSubstituted => (),
55 Rec::Parsed(token) => break token,
56 }
57 };
58
59 let first_pattern = match first_token.id {
60 Token(_) => first_token.word,
61 Operator(OpenParen) => {
62 let next_token = self.take_token_auto(&[Esac]).await?;
63 match next_token.id {
64 Token(_) => next_token.word,
65 _ => {
66 let cause = pattern_error_cause(next_token.id).into();
67 let location = next_token.word.location;
68 return Err(Error { cause, location });
69 }
70 }
71 }
72 _ => {
73 let cause = pattern_error_cause(first_token.id).into();
74 let location = first_token.word.location;
75 return Err(Error { cause, location });
76 }
77 };
78
79 let mut patterns = vec![first_pattern];
80 loop {
81 let separator = self.take_token_auto(&[]).await?;
82 match separator.id {
83 Operator(CloseParen) => break,
84 Operator(Bar) => {
85 let pattern = self.take_token_auto(&[]).await?;
86 match pattern.id {
87 Token(_) => patterns.push(pattern.word),
88 _ => {
89 let cause = pattern_error_cause(pattern.id).into();
90 let location = pattern.word.location;
91 return Err(Error { cause, location });
92 }
93 }
94 }
95 _ => {
96 let cause = SyntaxError::UnclosedPatternList.into();
97 let location = separator.word.location;
98 return Err(Error { cause, location });
99 }
100 }
101 }
102
103 let body = self.maybe_compound_list_boxed().await?;
104
105 let continuation = match self.peek_token().await?.id {
106 Operator(op) => op.try_into().ok(),
107 _ => None,
108 };
109 let continued = continuation.is_some();
110 let continuation = continuation.unwrap_or_default();
111 if continued {
112 self.take_token_raw().await?;
113 }
115
116 Ok(Some((
117 CaseItem {
118 patterns,
119 body,
120 continuation,
121 },
122 continued,
123 )))
124 }
125
126 pub async fn case_command(&mut self) -> Result<CompoundCommand> {
134 let open = self.take_token_raw().await?;
135 assert_eq!(open.id, Token(Some(Case)));
136
137 let subject = self.take_token_auto(&[]).await?;
138 match subject.id {
139 Token(_) => (),
140 Operator(Newline) | EndOfInput => {
141 let cause = SyntaxError::MissingCaseSubject.into();
142 let location = subject.word.location;
143 return Err(Error { cause, location });
144 }
145 _ => {
146 let cause = SyntaxError::InvalidCaseSubject.into();
147 let location = subject.word.location;
148 return Err(Error { cause, location });
149 }
150 }
151 let subject = subject.word;
152
153 loop {
154 while self.newline_and_here_doc_contents().await? {}
155
156 let next_token = self.take_token_auto(&[In]).await?;
157 match next_token.id {
158 Token(Some(In)) => break,
159 Operator(Newline) => (),
160 _ => {
161 let opening_location = open.word.location;
162 let cause = SyntaxError::MissingIn { opening_location }.into();
163 let location = next_token.word.location;
164 return Err(Error { cause, location });
165 }
166 }
167 }
168
169 let mut items = Vec::new();
170 while let Some((item, continued)) = self.case_item().await? {
171 items.push(item);
172 if !continued {
173 break;
174 }
175 }
176
177 let close = self.take_token_raw().await?;
178 if close.id != Token(Some(Esac)) {
179 let opening_location = open.word.location;
180 let cause = SyntaxError::UnclosedCase { opening_location }.into();
181 let location = close.word.location;
182 return Err(Error { cause, location });
183 }
184
185 Ok(CompoundCommand::Case { subject, items })
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::super::error::ErrorCause;
192 use super::super::lex::Lexer;
193 use super::*;
194 use crate::alias::{AliasSet, HashEntry};
195 use crate::source::Location;
196 use crate::source::Source;
197 use crate::syntax::CaseContinuation;
198 use assert_matches::assert_matches;
199 use futures_util::FutureExt;
200
201 #[test]
202 fn parser_case_item_esac() {
203 let mut lexer = Lexer::with_code("\nESAC");
204 #[allow(clippy::mutable_key_type)]
205 let mut aliases = AliasSet::new();
206 let origin = Location::dummy("");
207 aliases.insert(HashEntry::new(
208 "ESAC".to_string(),
209 "\n\nesac".to_string(),
210 true,
211 origin.clone(),
212 ));
213 aliases.insert(HashEntry::new(
214 "esac".to_string(),
215 "&&".to_string(),
216 true,
217 origin,
218 ));
219 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
220
221 let option = parser.case_item().now_or_never().unwrap().unwrap();
222 assert_eq!(option, None);
223
224 let next = parser.peek_token().now_or_never().unwrap().unwrap();
225 assert_eq!(next.id, Token(Some(Esac)));
226 }
227
228 #[test]
229 fn parser_case_item_minimum() {
230 let mut lexer = Lexer::with_code("foo)");
231 let mut parser = Parser::new(&mut lexer);
232
233 let (item, continued) = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
234 assert_eq!(item.patterns.len(), 1);
235 assert_eq!(item.patterns[0].to_string(), "foo");
236 assert_eq!(item.body.0, []);
237 assert_eq!(item.continuation, CaseContinuation::Break);
238 assert!(!continued);
239
240 let next = parser.peek_token().now_or_never().unwrap().unwrap();
241 assert_eq!(next.id, EndOfInput);
242 }
243
244 #[test]
245 fn parser_case_item_with_open_paren() {
246 let mut lexer = Lexer::with_code("(foo)");
247 let mut parser = Parser::new(&mut lexer);
248
249 let (item, continued) = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
250 assert_eq!(item.patterns.len(), 1);
251 assert_eq!(item.patterns[0].to_string(), "foo");
252 assert_eq!(item.body.0, []);
253 assert_eq!(item.continuation, CaseContinuation::Break);
254 assert!(!continued);
255
256 let next = parser.peek_token().now_or_never().unwrap().unwrap();
257 assert_eq!(next.id, EndOfInput);
258 }
259
260 #[test]
261 fn parser_case_item_many_patterns() {
262 let mut lexer = Lexer::with_code("1 | esac | $three)");
263 let mut parser = Parser::new(&mut lexer);
264
265 let (item, continued) = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
266 assert_eq!(item.patterns.len(), 3);
267 assert_eq!(item.patterns[0].to_string(), "1");
268 assert_eq!(item.patterns[1].to_string(), "esac");
269 assert_eq!(item.patterns[2].to_string(), "$three");
270 assert_eq!(item.body.0, []);
271 assert_eq!(item.continuation, CaseContinuation::Break);
272 assert!(!continued);
273
274 let next = parser.peek_token().now_or_never().unwrap().unwrap();
275 assert_eq!(next.id, EndOfInput);
276 }
277
278 #[test]
279 fn parser_case_item_non_empty_body() {
280 let mut lexer = Lexer::with_code("foo)\necho ok\n:&\n");
281 let mut parser = Parser::new(&mut lexer);
282
283 let (item, continued) = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
284 assert_eq!(item.patterns.len(), 1);
285 assert_eq!(item.patterns[0].to_string(), "foo");
286 assert_eq!(item.body.0.len(), 2);
287 assert_eq!(item.body.0[0].to_string(), "echo ok");
288 assert_eq!(item.body.0[1].to_string(), ":&");
289 assert_eq!(item.continuation, CaseContinuation::Break);
290 assert!(!continued);
291
292 let next = parser.peek_token().now_or_never().unwrap().unwrap();
293 assert_eq!(next.id, EndOfInput);
294 }
295
296 #[test]
297 fn parser_case_item_with_double_semicolon() {
298 let mut lexer = Lexer::with_code("foo);;");
299 let mut parser = Parser::new(&mut lexer);
300
301 let (item, continued) = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
302 assert_eq!(item.patterns.len(), 1);
303 assert_eq!(item.patterns[0].to_string(), "foo");
304 assert_eq!(item.body.0, []);
305 assert_eq!(item.continuation, CaseContinuation::Break);
306 assert!(continued);
307
308 let next = parser.peek_token().now_or_never().unwrap().unwrap();
309 assert_eq!(next.id, EndOfInput);
310 }
311
312 #[test]
313 fn parser_case_item_with_non_empty_body_and_double_semicolon() {
314 let mut lexer = Lexer::with_code("foo):;\n;;");
315 let mut parser = Parser::new(&mut lexer);
316
317 let (item, continued) = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
318 assert_eq!(item.patterns.len(), 1);
319 assert_eq!(item.patterns[0].to_string(), "foo");
320 assert_eq!(item.body.0.len(), 1);
321 assert_eq!(item.body.0[0].to_string(), ":");
322 assert_eq!(item.continuation, CaseContinuation::Break);
323 assert!(continued);
324
325 let next = parser.peek_token().now_or_never().unwrap().unwrap();
326 assert_eq!(next.id, EndOfInput);
327 }
328
329 #[test]
330 fn parser_case_item_with_semicolon_and() {
331 let mut lexer = Lexer::with_code("foo);&");
332 let mut parser = Parser::new(&mut lexer);
333
334 let (item, continued) = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
335 assert_eq!(item.patterns.len(), 1);
336 assert_eq!(item.patterns[0].to_string(), "foo");
337 assert_eq!(item.body.0, []);
338 assert_eq!(item.continuation, CaseContinuation::FallThrough);
339 assert!(continued);
340
341 let next = parser.peek_token().now_or_never().unwrap().unwrap();
342 assert_eq!(next.id, EndOfInput);
343 }
344
345 #[test]
346 fn parser_case_item_missing_pattern_without_open_paren() {
347 let mut lexer = Lexer::with_code(")");
348 let mut parser = Parser::new(&mut lexer);
349
350 let e = parser.case_item().now_or_never().unwrap().unwrap_err();
351 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingPattern));
352 assert_eq!(*e.location.code.value.borrow(), ")");
353 assert_eq!(e.location.code.start_line_number.get(), 1);
354 assert_eq!(*e.location.code.source, Source::Unknown);
355 assert_eq!(e.location.range, 0..1);
356 }
357
358 #[test]
359 fn parser_case_item_esac_after_paren() {
360 let mut lexer = Lexer::with_code("(esac)");
361 let mut parser = Parser::new(&mut lexer);
362
363 let (item, continued) = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
364 assert_eq!(item.patterns.len(), 1);
365 assert_eq!(item.patterns[0].to_string(), "esac");
366 assert_eq!(item.body.0, []);
367 assert_eq!(item.continuation, CaseContinuation::Break);
368 assert!(!continued);
369 }
370
371 #[test]
372 fn parser_case_item_first_pattern_not_word_after_open_paren() {
373 let mut lexer = Lexer::with_code("(&");
374 let mut parser = Parser::new(&mut lexer);
375
376 let e = parser.case_item().now_or_never().unwrap().unwrap_err();
377 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidPattern));
378 assert_eq!(*e.location.code.value.borrow(), "(&");
379 assert_eq!(e.location.code.start_line_number.get(), 1);
380 assert_eq!(*e.location.code.source, Source::Unknown);
381 assert_eq!(e.location.range, 1..2);
382 }
383
384 #[test]
385 fn parser_case_item_missing_pattern_after_bar() {
386 let mut lexer = Lexer::with_code("(foo| |");
387 let mut parser = Parser::new(&mut lexer);
388
389 let e = parser.case_item().now_or_never().unwrap().unwrap_err();
390 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingPattern));
391 assert_eq!(*e.location.code.value.borrow(), "(foo| |");
392 assert_eq!(e.location.code.start_line_number.get(), 1);
393 assert_eq!(*e.location.code.source, Source::Unknown);
394 assert_eq!(e.location.range, 6..7);
395 }
396
397 #[test]
398 fn parser_case_item_missing_close_paren() {
399 let mut lexer = Lexer::with_code("(foo bar");
400 let mut parser = Parser::new(&mut lexer);
401
402 let e = parser.case_item().now_or_never().unwrap().unwrap_err();
403 assert_eq!(
404 e.cause,
405 ErrorCause::Syntax(SyntaxError::UnclosedPatternList)
406 );
407 assert_eq!(*e.location.code.value.borrow(), "(foo bar");
408 assert_eq!(e.location.code.start_line_number.get(), 1);
409 assert_eq!(*e.location.code.source, Source::Unknown);
410 assert_eq!(e.location.range, 5..8);
411 }
412
413 #[test]
414 fn parser_case_command_minimum() {
415 let mut lexer = Lexer::with_code("case foo in esac");
416 let mut parser = Parser::new(&mut lexer);
417
418 let result = parser.compound_command().now_or_never().unwrap();
419 let compound_command = result.unwrap().unwrap();
420 assert_matches!(compound_command, CompoundCommand::Case { subject, items } => {
421 assert_eq!(subject.to_string(), "foo");
422 assert_eq!(items, []);
423 });
424
425 let next = parser.peek_token().now_or_never().unwrap().unwrap();
426 assert_eq!(next.id, EndOfInput);
427 }
428
429 #[test]
430 fn parser_case_command_newline_before_in() {
431 let mut lexer = Lexer::with_code("CASE_X IN_ESAC");
433 #[allow(clippy::mutable_key_type)]
434 let mut aliases = AliasSet::new();
435 let origin = Location::dummy("");
436 aliases.insert(HashEntry::new(
437 "CASE_X".to_string(),
438 " case x \n\n ".to_string(),
439 false,
440 origin.clone(),
441 ));
442 aliases.insert(HashEntry::new(
443 "IN_ESAC".to_string(),
444 "\nin esac".to_string(),
445 false,
446 origin,
447 ));
448 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
449
450 let result = parser.take_token_manual(true).now_or_never().unwrap();
451 assert_matches!(result, Ok(Rec::AliasSubstituted));
452
453 let result = parser.compound_command().now_or_never().unwrap();
454 let compound_command = result.unwrap().unwrap();
455 assert_matches!(compound_command, CompoundCommand::Case { subject, items } => {
456 assert_eq!(subject.to_string(), "x");
457 assert_eq!(items, []);
458 });
459
460 let next = parser.peek_token().now_or_never().unwrap().unwrap();
461 assert_eq!(next.id, EndOfInput);
462 }
463
464 #[test]
465 fn parser_case_command_alias_on_subject() {
466 let mut lexer = Lexer::with_code("CASE in a|b) esac");
468 #[allow(clippy::mutable_key_type)]
469 let mut aliases = AliasSet::new();
470 let origin = Location::dummy("");
471 aliases.insert(HashEntry::new(
472 "CASE".to_string(),
473 " case ".to_string(),
474 false,
475 origin.clone(),
476 ));
477 aliases.insert(HashEntry::new(
478 "in".to_string(),
479 " in in ".to_string(),
480 false,
481 origin,
482 ));
483 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
484
485 let result = parser.take_token_manual(true).now_or_never().unwrap();
486 assert_matches!(result, Ok(Rec::AliasSubstituted));
487
488 let result = parser.compound_command().now_or_never().unwrap();
489 let compound_command = result.unwrap().unwrap();
490 assert_matches!(compound_command, CompoundCommand::Case { subject, items } => {
491 assert_eq!(subject.to_string(), "in");
492 assert_eq!(items.len(), 1);
493 assert_eq!(items[0].to_string(), "(a | b) ;;");
494 });
495
496 let next = parser.peek_token().now_or_never().unwrap().unwrap();
497 assert_eq!(next.id, EndOfInput);
498 }
499
500 #[test]
501 fn parser_case_command_alias_on_in() {
502 let mut lexer = Lexer::with_code("CASE_X in esac");
504 #[allow(clippy::mutable_key_type)]
505 let mut aliases = AliasSet::new();
506 let origin = Location::dummy("");
507 aliases.insert(HashEntry::new(
508 "CASE_X".to_string(),
509 "case x ".to_string(),
510 false,
511 origin.clone(),
512 ));
513 aliases.insert(HashEntry::new(
514 "in".to_string(),
515 "in a)".to_string(),
516 false,
517 origin,
518 ));
519 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
520
521 let result = parser.take_token_manual(true).now_or_never().unwrap();
522 assert_matches!(result, Ok(Rec::AliasSubstituted));
523
524 let result = parser.compound_command().now_or_never().unwrap();
525 let compound_command = result.unwrap().unwrap();
526 assert_matches!(compound_command, CompoundCommand::Case { subject, items } => {
527 assert_eq!(subject.to_string(), "x");
528 assert_eq!(items, []);
529 });
530
531 let next = parser.peek_token().now_or_never().unwrap().unwrap();
532 assert_eq!(next.id, EndOfInput);
533 }
534
535 #[test]
536 fn parser_case_command_one_item() {
537 let mut lexer = Lexer::with_code("case foo in bar) esac");
538 let mut parser = Parser::new(&mut lexer);
539
540 let result = parser.compound_command().now_or_never().unwrap();
541 let compound_command = result.unwrap().unwrap();
542 assert_matches!(compound_command, CompoundCommand::Case { subject, items } => {
543 assert_eq!(subject.to_string(), "foo");
544 assert_eq!(items.len(), 1);
545 assert_eq!(items[0].to_string(), "(bar) ;;");
546 });
547
548 let next = parser.peek_token().now_or_never().unwrap().unwrap();
549 assert_eq!(next.id, EndOfInput);
550 }
551
552 #[test]
553 fn parser_case_command_many_items_without_final_double_semicolon() {
554 let mut lexer = Lexer::with_code("case x in\n\na) ;; (b|c):&:; ;;\n d)echo\nesac");
555 let mut parser = Parser::new(&mut lexer);
556
557 let result = parser.compound_command().now_or_never().unwrap();
558 let compound_command = result.unwrap().unwrap();
559 assert_matches!(compound_command, CompoundCommand::Case { subject, items } => {
560 assert_eq!(subject.to_string(), "x");
561 assert_eq!(items.len(), 3);
562 assert_eq!(items[0].to_string(), "(a) ;;");
563 assert_eq!(items[1].to_string(), "(b | c) :& :;;");
564 assert_eq!(items[2].to_string(), "(d) echo;;");
565 });
566
567 let next = parser.peek_token().now_or_never().unwrap().unwrap();
568 assert_eq!(next.id, EndOfInput);
569 }
570
571 #[test]
572 fn parser_case_command_many_items_with_final_double_semicolon() {
573 let mut lexer = Lexer::with_code("case x in(1);; 2)echo\n\n;;\n\nesac");
574 let mut parser = Parser::new(&mut lexer);
575
576 let result = parser.compound_command().now_or_never().unwrap();
577 let compound_command = result.unwrap().unwrap();
578 assert_matches!(compound_command, CompoundCommand::Case { subject, items } => {
579 assert_eq!(subject.to_string(), "x");
580 assert_eq!(items.len(), 2);
581 assert_eq!(items[0].to_string(), "(1) ;;");
582 assert_eq!(items[1].to_string(), "(2) echo;;");
583 });
584
585 let next = parser.peek_token().now_or_never().unwrap().unwrap();
586 assert_eq!(next.id, EndOfInput);
587 }
588
589 #[test]
590 fn parser_case_command_missing_subject() {
591 let mut lexer = Lexer::with_code(" case ");
592 let mut parser = Parser::new(&mut lexer);
593
594 let result = parser.compound_command().now_or_never().unwrap();
595 let e = result.unwrap_err();
596 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingCaseSubject));
597 assert_eq!(*e.location.code.value.borrow(), " case ");
598 assert_eq!(e.location.code.start_line_number.get(), 1);
599 assert_eq!(*e.location.code.source, Source::Unknown);
600 assert_eq!(e.location.range, 7..7);
601 }
602
603 #[test]
604 fn parser_case_command_invalid_subject() {
605 let mut lexer = Lexer::with_code(" case ; ");
606 let mut parser = Parser::new(&mut lexer);
607
608 let result = parser.compound_command().now_or_never().unwrap();
609 let e = result.unwrap_err();
610 assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidCaseSubject));
611 assert_eq!(*e.location.code.value.borrow(), " case ; ");
612 assert_eq!(e.location.code.start_line_number.get(), 1);
613 assert_eq!(*e.location.code.source, Source::Unknown);
614 assert_eq!(e.location.range, 6..7);
615 }
616
617 #[test]
618 fn parser_case_command_missing_in() {
619 let mut lexer = Lexer::with_code(" case x esac");
620 let mut parser = Parser::new(&mut lexer);
621
622 let result = parser.compound_command().now_or_never().unwrap();
623 let e = result.unwrap_err();
624 assert_matches!(e.cause,
625 ErrorCause::Syntax(SyntaxError::MissingIn { opening_location }) => {
626 assert_eq!(*opening_location.code.value.borrow(), " case x esac");
627 assert_eq!(opening_location.code.start_line_number.get(), 1);
628 assert_eq!(*opening_location.code.source, Source::Unknown);
629 assert_eq!(opening_location.range, 1..5);
630 });
631 assert_eq!(*e.location.code.value.borrow(), " case x esac");
632 assert_eq!(e.location.code.start_line_number.get(), 1);
633 assert_eq!(*e.location.code.source, Source::Unknown);
634 assert_eq!(e.location.range, 8..12);
635 }
636
637 #[test]
638 fn parser_case_command_missing_esac() {
639 let mut lexer = Lexer::with_code("case x in a) }");
640 let mut parser = Parser::new(&mut lexer);
641
642 let result = parser.compound_command().now_or_never().unwrap();
643 let e = result.unwrap_err();
644 assert_matches!(e.cause,
645 ErrorCause::Syntax(SyntaxError::UnclosedCase { opening_location }) => {
646 assert_eq!(*opening_location.code.value.borrow(), "case x in a) }");
647 assert_eq!(opening_location.code.start_line_number.get(), 1);
648 assert_eq!(*opening_location.code.source, Source::Unknown);
649 assert_eq!(opening_location.range, 0..4);
650 });
651 assert_eq!(*e.location.code.value.borrow(), "case x in a) }");
652 assert_eq!(e.location.code.start_line_number.get(), 1);
653 assert_eq!(*e.location.code.source, Source::Unknown);
654 assert_eq!(e.location.range, 13..14);
655 }
656}