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