1use super::core::Lexer;
20use crate::parser::core::Result;
21use crate::parser::{Error, SyntaxError};
22use crate::syntax::EscapeUnit::{self, *};
23use crate::syntax::EscapedString;
24
25impl Lexer<'_> {
26 async fn hex_digit(&mut self) -> Result<Option<u32>> {
28 if let Some(c) = self.peek_char().await?
29 && let Some(digit) = c.to_digit(16)
30 {
31 self.consume_char();
32 return Ok(Some(digit));
33 }
34 Ok(None)
35 }
36
37 async fn hex_digits(&mut self, count: usize) -> Result<Option<u32>> {
44 let Some(digit) = self.hex_digit().await? else {
45 return Ok(None);
46 };
47 let mut value = digit;
48 for _ in 1..count {
49 let Some(digit) = self.hex_digit().await? else {
50 break;
51 };
52 value = (value << 4) | digit;
53 }
54 Ok(Some(value))
55 }
56
57 #[must_use]
60 fn non_portable_escape(&self, start_index: usize) -> Error {
61 Error {
62 cause: SyntaxError::NonPortableEscape.into(),
63 location: self.location_range(start_index..self.index()),
64 }
65 }
66
67 pub async fn escape_unit(&mut self) -> Result<Option<EscapeUnit>> {
79 let Some(c1) = self.peek_char().await? else {
80 return Ok(None);
81 };
82 let start_index = self.index();
83 self.consume_char();
84 if c1 != '\\' {
85 return Ok(Some(Literal(c1)));
86 }
87
88 let Some(c2) = self.peek_char().await? else {
89 let cause = SyntaxError::IncompleteEscape.into();
90 let location = self.location().await?.clone();
91 return Err(Error { cause, location });
92 };
93 self.consume_char();
94
95 let portable = self.mode().portable;
96 match c2 {
97 '"' => Ok(Some(DoubleQuote)),
98 '\'' => Ok(Some(SingleQuote)),
99 '\\' => Ok(Some(Backslash)),
100 '?' if portable => Err(self.non_portable_escape(start_index)),
101 '?' => Ok(Some(Question)),
102 'a' => Ok(Some(Alert)),
103 'b' => Ok(Some(Backspace)),
104 'e' => Ok(Some(Escape)),
105 'E' if portable => Err(self.non_portable_escape(start_index)),
106 'E' => Ok(Some(Escape)),
107 'f' => Ok(Some(FormFeed)),
108 'n' => Ok(Some(Newline)),
109 'r' => Ok(Some(CarriageReturn)),
110 't' => Ok(Some(Tab)),
111 'v' => Ok(Some(VerticalTab)),
112
113 'c' => {
114 let start_index = self.index();
115 let Some(c3) = self.peek_char().await? else {
116 let cause = SyntaxError::IncompleteControlEscape.into();
117 let location = self.location().await?.clone();
118 return Err(Error { cause, location });
119 };
120 self.consume_char();
121 match c3.to_ascii_uppercase() {
122 '\\' => {
123 let Some('\\') = self.peek_char().await? else {
124 let cause = SyntaxError::IncompleteControlBackslashEscape.into();
125 let location = self.location().await?.clone();
126 return Err(Error { cause, location });
127 };
128 self.consume_char();
129 Ok(Some(Control(0x1C)))
130 }
131
132 '@' if portable => Err(self.non_portable_escape(start_index)),
136
137 c3 @ ('\u{3F}'..'\u{60}') => Ok(Some(Control(c3 as u8 ^ 0x40))),
138
139 _ => {
140 let cause = SyntaxError::InvalidControlEscape.into();
141 let location = self.location_range(start_index..self.index());
142 Err(Error { cause, location })
143 }
144 }
145 }
146
147 'x' => {
148 let Some(value) = self.hex_digits(2).await? else {
152 let cause = SyntaxError::IncompleteHexEscape.into();
153 let location = self.location().await?.clone();
154 return Err(Error { cause, location });
155 };
156 if portable && self.hex_digits(usize::MAX).await?.is_some() {
158 let cause = SyntaxError::TooLongHexEscape.into();
159 let location = self.location_range(start_index..self.index());
160 return Err(Error { cause, location });
161 }
162 Ok(Some(Hex(value as u8)))
163 }
164
165 'u' if portable => Err(self.non_portable_escape(start_index)),
166 'u' => {
167 let Some(value) = self.hex_digits(4).await? else {
168 let cause = SyntaxError::IncompleteShortUnicodeEscape.into();
169 let location = self.location().await?.clone();
170 return Err(Error { cause, location });
171 };
172 if let Some(c) = char::from_u32(value) {
173 Ok(Some(Unicode(c)))
174 } else {
175 let cause = SyntaxError::UnicodeEscapeOutOfRange.into();
176 let location = self.location_range(start_index..self.index());
177 Err(Error { cause, location })
178 }
179 }
180
181 'U' if portable => Err(self.non_portable_escape(start_index)),
182 'U' => {
183 let Some(value) = self.hex_digits(8).await? else {
184 let cause = SyntaxError::IncompleteLongUnicodeEscape.into();
185 let location = self.location().await?.clone();
186 return Err(Error { cause, location });
187 };
188 if let Some(c) = char::from_u32(value) {
189 Ok(Some(Unicode(c)))
190 } else {
191 let cause = SyntaxError::UnicodeEscapeOutOfRange.into();
192 let location = self.location_range(start_index..self.index());
193 Err(Error { cause, location })
194 }
195 }
196
197 _ => {
198 let Some(mut value) = c2.to_digit(8) else {
200 let cause = SyntaxError::InvalidEscape.into();
201 let location = self.location_range(start_index..self.index());
202 return Err(Error { cause, location });
203 };
204 for _ in 0..2 {
205 let Some(digit) = self.peek_char().await? else {
206 break;
207 };
208 let Some(digit) = digit.to_digit(8) else {
209 break;
210 };
211 value = value * 8 + digit;
212 self.consume_char();
213 }
214 if let Ok(value) = value.try_into() {
215 Ok(Some(Octal(value)))
216 } else {
217 let cause = SyntaxError::OctalEscapeOutOfRange.into();
218 let location = self.location_range(start_index..self.index());
219 Err(Error { cause, location })
220 }
221 }
222 }
223 }
224
225 pub async fn escaped_string<F>(&mut self, mut is_delimiter: F) -> Result<EscapedString>
239 where
240 F: FnMut(char) -> bool,
241 {
242 self.escaped_string_dyn(&mut is_delimiter).await
243 }
244
245 async fn escaped_string_dyn(
247 &mut self,
248 is_delimiter: &mut dyn FnMut(char) -> bool,
249 ) -> Result<EscapedString> {
250 let mut this = self.disable_line_continuation();
251 let mut units = Vec::new();
252
253 while let Some(c) = this.peek_char().await? {
254 if is_delimiter(c) {
255 break;
256 }
257 let Some(unit) = this.escape_unit().await? else {
258 break;
259 };
260 units.push(unit);
261 }
262
263 Ok(EscapedString(units))
264 }
265
266 pub(super) async fn single_quoted_escaped_string(&mut self) -> Result<Option<EscapedString>> {
277 let is_single_quote = |c| c == '\'';
278
279 let Some(quote) = self.consume_char_if(is_single_quote).await? else {
281 return Ok(None);
282 };
283 let opening_location = quote.location.clone();
284
285 let content = self.escaped_string(is_single_quote).await?;
286
287 if let Some(quote) = self.peek_char().await? {
289 debug_assert_eq!(quote, '\'');
290 self.consume_char();
291 Ok(Some(content))
292 } else {
293 let cause = SyntaxError::UnclosedDollarSingleQuote { opening_location }.into();
294 let location = self.location().await?.clone();
295 Err(Error { cause, location })
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::parser::ErrorCause;
304 use crate::source::Source;
305 use assert_matches::assert_matches;
306 use futures_util::FutureExt as _;
307
308 #[test]
309 fn escape_unit_literal() {
310 let mut lexer = Lexer::with_code("bar");
311 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
312 assert_eq!(result, Some(Literal('b')));
313 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('a')));
314 }
315
316 #[test]
317 fn escape_unit_named_escapes() {
318 let mut lexer = Lexer::with_code(r#"\""\'\\\?\a\b\e\E\f\n\r\t\v"#);
319 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
320 assert_eq!(result, Some(DoubleQuote));
321 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
322 assert_eq!(result, Some(Literal('"')));
323 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
324 assert_eq!(result, Some(SingleQuote));
325 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
326 assert_eq!(result, Some(Backslash));
327 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
328 assert_eq!(result, Some(Question));
329 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
330 assert_eq!(result, Some(Alert));
331 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
332 assert_eq!(result, Some(Backspace));
333 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
334 assert_eq!(result, Some(Escape));
335 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
336 assert_eq!(result, Some(Escape));
337 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
338 assert_eq!(result, Some(FormFeed));
339 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
340 assert_eq!(result, Some(Newline));
341 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
342 assert_eq!(result, Some(CarriageReturn));
343 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
344 assert_eq!(result, Some(Tab));
345 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
346 assert_eq!(result, Some(VerticalTab));
347 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
348 }
349
350 #[test]
351 fn escape_unit_incomplete_escapes() {
352 let mut lexer = Lexer::with_code(r"\");
353 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
354 assert_matches!(
355 error.cause,
356 ErrorCause::Syntax(SyntaxError::IncompleteEscape)
357 );
358 assert_eq!(*error.location.code.value.borrow(), r"\");
359 assert_eq!(error.location.code.start_line_number.get(), 1);
360 assert_eq!(*error.location.code.source, Source::Unknown);
361 assert_eq!(error.location.range, 1..1);
362 }
363
364 #[test]
365 fn escape_unit_control_escapes() {
366 let mut lexer = Lexer::with_code(r"\c@\cA\cz\c^\c?\c\\");
367 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
368 assert_eq!(result, Some(Control(0x00)));
369 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
370 assert_eq!(result, Some(Control(0x01)));
371 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
372 assert_eq!(result, Some(Control(0x1A)));
373 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
374 assert_eq!(result, Some(Control(0x1E)));
375 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
376 assert_eq!(result, Some(Control(0x7F)));
377 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
378 assert_eq!(result, Some(Control(0x1C)));
379 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(None));
380 }
381
382 #[test]
383 fn escape_unit_incomplete_control_escape() {
384 let mut lexer = Lexer::with_code(r"\c");
385 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
386 assert_matches!(
387 error.cause,
388 ErrorCause::Syntax(SyntaxError::IncompleteControlEscape)
389 );
390 assert_eq!(*error.location.code.value.borrow(), r"\c");
391 assert_eq!(error.location.code.start_line_number.get(), 1);
392 assert_eq!(*error.location.code.source, Source::Unknown);
393 assert_eq!(error.location.range, 2..2);
394 }
395
396 #[test]
397 fn escape_unit_incomplete_control_backslash_escapes() {
398 let mut lexer = Lexer::with_code(r"\c\");
399 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
400 assert_matches!(
401 error.cause,
402 ErrorCause::Syntax(SyntaxError::IncompleteControlBackslashEscape)
403 );
404 assert_eq!(*error.location.code.value.borrow(), r"\c\");
405 assert_eq!(error.location.code.start_line_number.get(), 1);
406 assert_eq!(*error.location.code.source, Source::Unknown);
407 assert_eq!(error.location.range, 3..3);
408
409 let mut lexer = Lexer::with_code(r"\c\a");
410 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
411 assert_matches!(
412 error.cause,
413 ErrorCause::Syntax(SyntaxError::IncompleteControlBackslashEscape)
414 );
415 assert_eq!(*error.location.code.value.borrow(), r"\c\a");
416 assert_eq!(error.location.code.start_line_number.get(), 1);
417 assert_eq!(*error.location.code.source, Source::Unknown);
418 assert_eq!(error.location.range, 3..4);
419 }
420
421 #[test]
422 fn escape_unit_unknown_control_escape() {
423 let mut lexer = Lexer::with_code(r"\c!`");
424 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
425 assert_matches!(
426 error.cause,
427 ErrorCause::Syntax(SyntaxError::InvalidControlEscape)
428 );
429 assert_eq!(*error.location.code.value.borrow(), r"\c!`");
430 assert_eq!(error.location.code.start_line_number.get(), 1);
431 assert_eq!(*error.location.code.source, Source::Unknown);
432 assert_eq!(error.location.range, 2..3);
433 }
434
435 #[test]
436 fn escape_unit_octal_escapes() {
437 let mut lexer = Lexer::with_code(r"\0\07\234\0123");
438 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
439 assert_eq!(result, Some(Octal(0o0)));
440 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
441 assert_eq!(result, Some(Octal(0o7)));
442 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
443 assert_eq!(result, Some(Octal(0o234)));
444 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
445 assert_eq!(result, Some(Octal(0o12)));
446 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('3')));
448
449 let mut lexer = Lexer::with_code(r"\787");
450 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
451 assert_eq!(result, Some(Octal(0o7)));
453
454 let mut lexer = Lexer::with_code(r"\12");
455 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
456 assert_eq!(result, Some(Octal(0o12)));
458 }
459
460 #[test]
461 fn escape_unit_non_byte_octal_escape() {
462 let mut lexer = Lexer::with_code(r"\400");
463 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
464 assert_matches!(
465 error.cause,
466 ErrorCause::Syntax(SyntaxError::OctalEscapeOutOfRange)
467 );
468 assert_eq!(*error.location.code.value.borrow(), r"\400");
469 assert_eq!(error.location.code.start_line_number.get(), 1);
470 assert_eq!(*error.location.code.source, Source::Unknown);
471 assert_eq!(error.location.range, 0..4);
472 }
473
474 #[test]
475 fn escape_unit_hexadecimal_escapes() {
476 let mut lexer = Lexer::with_code(r"\x0\x7F\xd4A");
477 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
478 assert_eq!(result, Some(Hex(0x0)));
479 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
480 assert_eq!(result, Some(Hex(0x7F)));
481 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
482 assert_eq!(result, Some(Hex(0xD4)));
484 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('A')));
485
486 let mut lexer = Lexer::with_code(r"\xb");
487 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
488 assert_eq!(result, Some(Hex(0xB)));
490 }
491
492 #[test]
493 fn escape_unit_incomplete_hexadecimal_escape() {
494 let mut lexer = Lexer::with_code(r"\x");
495 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
496 assert_matches!(
497 error.cause,
498 ErrorCause::Syntax(SyntaxError::IncompleteHexEscape)
499 );
500 assert_eq!(*error.location.code.value.borrow(), r"\x");
501 assert_eq!(error.location.code.start_line_number.get(), 1);
502 assert_eq!(*error.location.code.source, Source::Unknown);
503 assert_eq!(error.location.range, 2..2);
504 }
505
506 #[test]
507 fn escape_unit_unicode_escapes() {
508 let mut lexer = Lexer::with_code(r"\u20\u4B9d0");
509 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
510 assert_eq!(result, Some(Unicode('\u{20}')));
511 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
512 assert_eq!(result, Some(Unicode('\u{4B9D}')));
513 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('0')));
515
516 let mut lexer = Lexer::with_code(r"\U42\U0001f4A9b");
517 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
518 assert_eq!(result, Some(Unicode('\u{42}')));
519 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
520 assert_eq!(result, Some(Unicode('\u{1F4A9}')));
521 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('b')));
523 }
524
525 #[test]
526 fn escape_unit_incomplete_unicode_escapes() {
527 let mut lexer = Lexer::with_code(r"\u");
528 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
529 assert_matches!(
530 error.cause,
531 ErrorCause::Syntax(SyntaxError::IncompleteShortUnicodeEscape)
532 );
533 assert_eq!(*error.location.code.value.borrow(), r"\u");
534 assert_eq!(error.location.code.start_line_number.get(), 1);
535 assert_eq!(*error.location.code.source, Source::Unknown);
536 assert_eq!(error.location.range, 2..2);
537
538 let mut lexer = Lexer::with_code(r"\U");
539 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
540 assert_matches!(
541 error.cause,
542 ErrorCause::Syntax(SyntaxError::IncompleteLongUnicodeEscape)
543 );
544 assert_eq!(*error.location.code.value.borrow(), r"\U");
545 assert_eq!(error.location.code.start_line_number.get(), 1);
546 assert_eq!(*error.location.code.source, Source::Unknown);
547 assert_eq!(error.location.range, 2..2);
548 }
549
550 #[test]
551 fn escape_unit_invalid_unicode_escapes() {
552 let mut lexer = Lexer::with_code(r"\uD800");
554 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
555 assert_matches!(
556 error.cause,
557 ErrorCause::Syntax(SyntaxError::UnicodeEscapeOutOfRange)
558 );
559 assert_eq!(*error.location.code.value.borrow(), r"\uD800");
560 assert_eq!(error.location.code.start_line_number.get(), 1);
561 assert_eq!(*error.location.code.source, Source::Unknown);
562 assert_eq!(error.location.range, 0..6);
563 }
564
565 #[test]
566 fn escape_unit_unknown_escape() {
567 let mut lexer = Lexer::with_code(r"\!");
568 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
569 assert_matches!(error.cause, ErrorCause::Syntax(SyntaxError::InvalidEscape));
570 assert_eq!(*error.location.code.value.borrow(), r"\!");
571 assert_eq!(error.location.code.start_line_number.get(), 1);
572 assert_eq!(*error.location.code.source, Source::Unknown);
573 assert_eq!(error.location.range, 0..2);
574 }
575
576 fn portable_mode() -> yash_env::parser::Mode {
577 let mut mode = yash_env::parser::Mode::default();
578 mode.portable = true;
579 mode
580 }
581
582 #[test]
583 fn escape_unit_non_portable_escapes_rejected_in_portable_mode() {
584 for (code, range) in [
585 (r"\E", 0..2),
586 (r"\?", 0..2),
587 (r"\u0041", 0..2),
588 (r"\U00000041", 0..2),
589 (r"\c@", 2..3),
590 ] {
591 let mut lexer = Lexer::with_code(code);
592 lexer.set_mode(portable_mode());
593 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
594 assert_matches!(
595 error.cause,
596 ErrorCause::Syntax(SyntaxError::NonPortableEscape),
597 "code={code:?}"
598 );
599 assert_eq!(error.location.range, range, "code={code:?}");
600 }
601 }
602
603 #[test]
604 fn escape_unit_too_long_hex_escape_rejected_in_portable_mode() {
605 let mut lexer = Lexer::with_code(r"\xABC");
607 lexer.set_mode(portable_mode());
608 let error = lexer.escape_unit().now_or_never().unwrap().unwrap_err();
609 assert_matches!(
610 error.cause,
611 ErrorCause::Syntax(SyntaxError::TooLongHexEscape)
612 );
613 assert_eq!(error.location.range, 0..5);
614 }
615
616 #[test]
617 fn escape_unit_portable_escapes_accepted_in_portable_mode() {
618 for (code, expected) in [
620 (r"\e", Escape),
621 (r"\xAB", Hex(0xAB)),
622 (r"\cA", Control(1)),
623 (r"\n", Newline),
624 ] {
625 let mut lexer = Lexer::with_code(code);
626 lexer.set_mode(portable_mode());
627 let result = lexer.escape_unit().now_or_never().unwrap().unwrap();
628 assert_eq!(result, Some(expected), "code={code:?}");
629 }
630 }
631
632 #[test]
633 fn escaped_string_literals() {
634 let mut lexer = Lexer::with_code("foo");
635 let EscapedString(content) = lexer
636 .escaped_string(|_| false)
637 .now_or_never()
638 .unwrap()
639 .unwrap();
640 assert_eq!(content, [Literal('f'), Literal('o'), Literal('o')]);
641 }
642
643 #[test]
644 fn escaped_string_mixed() {
645 let mut lexer = Lexer::with_code(r"foo\bar");
646 let EscapedString(content) = lexer
647 .escaped_string(|_| false)
648 .now_or_never()
649 .unwrap()
650 .unwrap();
651 assert_eq!(
652 content,
653 [
654 Literal('f'),
655 Literal('o'),
656 Literal('o'),
657 Backspace,
658 Literal('a'),
659 Literal('r')
660 ]
661 );
662 }
663
664 #[test]
665 fn no_line_continuations_in_escaped_string() {
666 let mut lexer = Lexer::with_code("\\\\\n");
667 let EscapedString(content) = lexer
668 .escaped_string(|_| false)
669 .now_or_never()
670 .unwrap()
671 .unwrap();
672 assert_eq!(content, [Backslash, Literal('\n')]);
673
674 let mut lexer = Lexer::with_code("\\\n");
675 let error = lexer
676 .escaped_string(|_| false)
677 .now_or_never()
678 .unwrap()
679 .unwrap_err();
680 assert_matches!(error.cause, ErrorCause::Syntax(SyntaxError::InvalidEscape));
681 assert_eq!(*error.location.code.value.borrow(), "\\\n");
682 assert_eq!(error.location.code.start_line_number.get(), 1);
683 assert_eq!(*error.location.code.source, Source::Unknown);
684 assert_eq!(error.location.range, 0..2);
685 }
686
687 #[test]
688 fn single_quoted_escaped_string_empty() {
689 let mut lexer = Lexer::with_code("''");
690 let result = lexer
691 .single_quoted_escaped_string()
692 .now_or_never()
693 .unwrap()
694 .unwrap();
695 assert_eq!(result, Some(EscapedString(vec![])));
696 }
697
698 #[test]
699 fn single_quoted_escaped_string_nonempty() {
700 let mut lexer = Lexer::with_code(r"'foo\e'x");
701 let result = lexer
702 .single_quoted_escaped_string()
703 .now_or_never()
704 .unwrap()
705 .unwrap();
706 assert_matches!(result, Some(EscapedString(content)) => {
707 assert_eq!(
708 content,
709 [
710 Literal('f'),
711 Literal('o'),
712 Literal('o'),
713 Escape,
714 ]
715 );
716 });
717 assert_eq!(lexer.peek_char().now_or_never().unwrap(), Ok(Some('x')));
718 }
719
720 #[test]
721 fn single_quoted_escaped_string_unclosed() {
722 let mut lexer = Lexer::with_code("'foo");
723 let error = lexer
724 .single_quoted_escaped_string()
725 .now_or_never()
726 .unwrap()
727 .unwrap_err();
728 assert_matches!(
729 error.cause,
730 ErrorCause::Syntax(SyntaxError::UnclosedDollarSingleQuote { opening_location }) => {
731 assert_eq!(*opening_location.code.value.borrow(), "'foo");
732 assert_eq!(opening_location.code.start_line_number.get(), 1);
733 assert_eq!(*opening_location.code.source, Source::Unknown);
734 assert_eq!(opening_location.range, 0..1);
735 }
736 );
737 assert_eq!(*error.location.code.value.borrow(), "'foo");
738 assert_eq!(error.location.code.start_line_number.get(), 1);
739 assert_eq!(*error.location.code.source, Source::Unknown);
740 assert_eq!(error.location.range, 4..4);
741 }
742}