1#[cfg(test)]
50#[macro_use]
51extern crate quote;
52
53use std::fmt::Formatter;
54use std::{
55 borrow::Cow,
56 fmt,
57 iter::Peekable,
58 ops::Range,
59 str::{self, CharIndices},
60};
61
62use proc_macro2::{token_stream, Literal, Span, TokenStream, TokenTree};
63use quote::ToTokens;
64use syn::{spanned::Spanned, FieldValue};
65
66pub struct Template {
70 before_template: Vec<FieldValue>,
71 literal: Vec<LiteralPart>,
72 after_template: Vec<FieldValue>,
73}
74
75pub trait LiteralVisitor {
79 fn visit_text(&mut self, text: &str);
83
84 fn visit_hole(&mut self, hole: &FieldValue);
88}
89
90impl<'a, V: ?Sized> LiteralVisitor for &'a mut V
91where
92 V: LiteralVisitor,
93{
94 fn visit_text(&mut self, text: &str) {
95 (**self).visit_text(text)
96 }
97
98 fn visit_hole(&mut self, hole: &FieldValue) {
99 (**self).visit_hole(hole)
100 }
101}
102
103impl Template {
104 pub fn parse2(input: TokenStream) -> Result<Self, Error> {
110 let mut scan = ScanTemplate::new(input);
111
112 let mut parsing_value = false;
115 let (before_template, template) = scan.take_until(|tt| {
116 if parsing_value {
119 parsing_value = false;
120 return false;
121 }
122
123 match tt {
124 TokenTree::Literal(_) => true,
126 TokenTree::Punct(p) if p.as_char() == ':' => {
129 parsing_value = true;
130 false
131 }
132 _ => false,
134 }
135 });
136
137 let after_template = if scan.has_input() {
139 scan.expect_punct(',')?;
140 scan.rest.collect()
141 } else {
142 TokenStream::new()
143 };
144
145 let literal = if let Some(template) = template {
147 LiteralPart::parse_lit2(ScanTemplate::take_literal(template)?)?
148 } else {
149 Vec::new()
150 };
151
152 let before_template = ScanTemplate::new(before_template).collect_field_values()?;
153 let after_template = ScanTemplate::new(after_template).collect_field_values()?;
154
155 Ok(Template {
156 before_template,
157 literal,
158 after_template,
159 })
160 }
161
162 pub fn before_literal_field_values<'a>(&'a self) -> impl Iterator<Item = &'a FieldValue> {
166 self.before_template.iter()
167 }
168
169 pub fn literal_field_values<'a>(&'a self) -> impl Iterator<Item = &'a FieldValue> {
175 self.literal.iter().filter_map(|part| {
176 if let LiteralPart::Hole { expr, .. } = part {
177 Some(expr)
178 } else {
179 None
180 }
181 })
182 }
183
184 pub fn has_literal(&self) -> bool {
188 !self.literal.is_empty()
189 }
190
191 pub fn after_literal_field_values<'a>(&'a self) -> impl Iterator<Item = &'a FieldValue> {
195 self.after_template.iter()
196 }
197
198 pub fn visit_literal(&self, mut visitor: impl LiteralVisitor) {
220 for part in &self.literal {
221 match part {
222 LiteralPart::Text { text, .. } => visitor.visit_text(text),
223 LiteralPart::Hole { expr, .. } => visitor.visit_hole(&expr),
224 }
225 }
226 }
227}
228
229enum LiteralPart {
233 Text {
237 text: String,
241 range: Range<usize>,
245 },
246 Hole {
250 expr: FieldValue,
254 range: Range<usize>,
258 },
259}
260
261impl fmt::Debug for LiteralPart {
262 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263 match self {
264 LiteralPart::Text { text, range } => f
265 .debug_struct("Text")
266 .field("text", text)
267 .field("range", range)
268 .finish(),
269 LiteralPart::Hole { expr, range } => f
270 .debug_struct("Hole")
271 .field("expr", &format_args!("`{}`", expr.to_token_stream()))
272 .field("range", range)
273 .finish(),
274 }
275 }
276}
277
278impl LiteralPart {
279 fn parse_lit2(lit: Literal) -> Result<Vec<Self>, Error> {
280 enum Expecting {
281 TextOrEOF,
282 Hole,
283 }
284
285 let input = lit.to_string();
286
287 let mut parts = Vec::new();
288 let mut expecting = Expecting::TextOrEOF;
289
290 let mut scan = ScanPart::new(lit, &input)?;
291
292 if !scan.has_input() {
295 return Ok(vec![LiteralPart::Text {
296 text: String::new(),
297 range: 0..0,
298 }]);
299 }
300
301 while scan.has_input() {
302 match expecting {
303 Expecting::TextOrEOF => {
304 if let Some((text, range)) = scan.take_until_eof_or_hole_start()? {
305 parts.push(LiteralPart::Text {
306 text: text.into_owned(),
307 range,
308 });
309 }
310
311 expecting = Expecting::Hole;
312 continue;
313 }
314 Expecting::Hole => {
315 let (expr, range) = scan.take_until_hole_end()?;
316
317 let expr_span = scan.lit.subspan(range.start..range.end);
318
319 let tokens = {
320 let tokens: TokenStream = str::parse(&*expr).map_err(|e| {
321 Error::lex_fv_expr(expr_span.unwrap_or(scan.lit.span()), &*expr, e)
322 })?;
323
324 if let Some(span) = scan.lit.subspan(range.start..range.end) {
327 tokens
328 .into_iter()
329 .map(|mut tt| {
330 tt.set_span(span);
331 tt
332 })
333 .collect()
334 } else {
335 tokens
336 }
337 };
338
339 let expr = syn::parse2(tokens).map_err(|e| {
340 Error::parse_fv_expr(expr_span.unwrap_or(scan.lit.span()), &*expr, e)
341 })?;
342
343 parts.push(LiteralPart::Hole { expr, range });
344
345 expecting = Expecting::TextOrEOF;
346 continue;
347 }
348 }
349 }
350
351 Ok(parts)
352 }
353}
354
355struct ScanTemplate {
356 span: Span,
357 rest: Peekable<token_stream::IntoIter>,
358}
359
360impl ScanTemplate {
361 fn new(input: TokenStream) -> Self {
362 ScanTemplate {
363 span: input.span(),
364 rest: input.into_iter().peekable(),
365 }
366 }
367
368 fn has_input(&mut self) -> bool {
369 self.rest.peek().is_some()
370 }
371
372 fn take_until(
373 &mut self,
374 mut until_true: impl FnMut(&TokenTree) -> bool,
375 ) -> (TokenStream, Option<TokenTree>) {
376 let mut taken = TokenStream::new();
377
378 while let Some(tt) = self.rest.next() {
379 if until_true(&tt) {
380 return (taken, Some(tt));
381 }
382
383 taken.extend(Some(tt));
384 }
385
386 (taken, None)
387 }
388
389 fn is_punct(input: &TokenTree, c: char) -> bool {
390 match input {
391 TokenTree::Punct(p) if p.as_char() == c => true,
392 _ => false,
393 }
394 }
395
396 fn expect_punct(&mut self, c: char) -> Result<TokenTree, Error> {
397 match self.rest.next() {
398 Some(tt) => {
399 if Self::is_punct(&tt, c) {
400 Ok(tt)
401 } else {
402 Err(Error::invalid_char(tt.span(), &[c]))
403 }
404 }
405 None => Err(Error::invalid_char_eof(self.span, &[c])),
406 }
407 }
408
409 fn take_literal(tt: TokenTree) -> Result<Literal, Error> {
410 match tt {
411 TokenTree::Literal(l) => Ok(l),
412 _ => Err(Error::invalid_literal(tt.span())),
413 }
414 }
415
416 fn collect_field_values(mut self) -> Result<Vec<FieldValue>, Error> {
417 let mut result = Vec::new();
418
419 while self.has_input() {
420 let (arg, _) = self.take_until(|tt| Self::is_punct(&tt, ','));
421
422 if !arg.is_empty() {
423 let expr_span = arg.span();
424
425 result.push(syn::parse2::<FieldValue>(arg).map_err(|e| {
426 Error::parse_fv_expr(expr_span, expr_span.source_text().as_deref(), e)
427 })?);
428 }
429 }
430
431 Ok(result)
432 }
433}
434
435struct ScanPart<'input> {
436 lit: Literal,
437 input: &'input str,
438 start: usize,
439 end: usize,
440 rest: Peekable<CharIndices<'input>>,
441}
442
443struct TakeUntil<'a, 'input> {
444 current: char,
445 current_idx: usize,
446 rest: &'a mut Peekable<CharIndices<'input>>,
447 lit: &'a Literal,
448}
449
450impl<'input> ScanPart<'input> {
451 fn new(lit: Literal, input: &'input str) -> Result<Self, Error> {
452 let mut iter = input.char_indices();
453 let start = iter.next();
454 let end = iter.next_back();
455
456 if start.map(|(_, c)| c) != Some('"') || end.map(|(_, c)| c) != Some('"') {
460 return Err(Error::invalid_literal(lit.span()));
461 }
462
463 Ok(ScanPart {
464 lit,
465 input: &input,
466 start: 1,
467 end: input.len() - 1,
468 rest: iter.peekable(),
469 })
470 }
471
472 fn has_input(&mut self) -> bool {
473 self.rest.peek().is_some()
474 }
475
476 fn take_until(
477 &mut self,
478 mut until_true: impl FnMut(TakeUntil<'_, 'input>) -> Result<bool, Error>,
479 ) -> Result<Option<(Cow<'input, str>, Range<usize>)>, Error> {
480 let mut scan = || {
481 while let Some((i, c)) = self.rest.next() {
482 if until_true(TakeUntil {
483 current: c,
484 current_idx: i,
485 rest: &mut self.rest,
486 lit: &self.lit,
487 })? {
488 let start = self.start;
489 let end = i;
490
491 self.start = end + 1;
492
493 let range = start..end;
494
495 return Ok((Cow::Borrowed(&self.input[range.clone()]), range));
496 }
497 }
498
499 let range = self.start..self.end;
500
501 Ok((Cow::Borrowed(&self.input[range.clone()]), range))
502 };
503
504 match scan()? {
505 (s, r) if s.len() > 0 => Ok(Some((s, r))),
506 _ => Ok(None),
507 }
508 }
509
510 fn take_until_eof_or_hole_start(
511 &mut self,
512 ) -> Result<Option<(Cow<'input, str>, Range<usize>)>, Error> {
513 let mut escaped = false;
514 let scanned = self.take_until(|state| match state.current {
515 '{' => {
519 let start = state.current_idx;
520
521 match state.rest.peek().map(|(_, peeked)| *peeked) {
522 Some('{') => {
523 escaped = true;
524 let _ = state.rest.next();
525 Ok(false)
526 }
527 Some(_) => Ok(true),
528 None => Err(Error::incomplete_hole(
529 state
530 .lit
531 .subspan(start..start + 1)
532 .unwrap_or(state.lit.span()),
533 )),
534 }
535 }
536 '}' => match state.rest.peek().map(|(_, peeked)| *peeked) {
541 Some('}') => {
542 escaped = true;
543 let _ = state.rest.next();
544 Ok(false)
545 }
546 Some(_) => Err(Error::unescaped_hole(
547 state
548 .lit
549 .subspan(state.current_idx..state.current_idx + 1)
550 .unwrap_or(state.lit.span()),
551 )),
552 None => Err(Error::unescaped_hole(
553 state
554 .lit
555 .subspan(state.current_idx..state.current_idx + 1)
556 .unwrap_or(state.lit.span()),
557 )),
558 },
559 _ => Ok(false),
560 })?;
561
562 match scanned {
563 Some((input, range)) if escaped => {
564 let input = (&*input).replace("{{", "{").replace("}}", "}");
566 Ok(Some((Cow::Owned(input), range)))
567 }
568 scanned => Ok(scanned),
569 }
570 }
571
572 fn take_until_hole_end(&mut self) -> Result<(Cow<'input, str>, Range<usize>), Error> {
573 let mut depth = 1;
574 let mut matched_hole_end = false;
575 let mut escaped = false;
576 let mut next_terminator_escaped = false;
577 let mut terminator = None;
578
579 let start = self.start - 1;
582
583 let scanned = self.take_until(|state| {
584 match state.current {
585 '}' if terminator.is_none() && depth == 1 => {
587 matched_hole_end = true;
588 Ok(true)
589 }
590 '}' if terminator.is_none() => {
592 depth -= 1;
593 Ok(false)
594 }
595 '{' if terminator.is_none() => {
597 depth += 1;
598 Ok(false)
599 }
600 '"' if terminator.is_none() => {
603 terminator = Some('"');
604 Ok(false)
605 }
606 '\'' if terminator.is_none() => {
609 terminator = Some('\'');
610 Ok(false)
611 }
612 '\\' if state
616 .rest
617 .peek()
618 .map(|(_, peeked)| *peeked == '\\')
619 .unwrap_or(false) =>
620 {
621 next_terminator_escaped = !next_terminator_escaped;
622 escaped = true;
623 Ok(false)
624 }
625 '\\' => {
626 escaped = true;
627 Ok(false)
628 }
629 '/' if state
632 .rest
633 .peek()
634 .map(|(_, peeked)| *peeked == '/' || *peeked == '*')
635 .unwrap_or(false) =>
636 {
637 Err(Error::unsupported_comment(
638 state
639 .lit
640 .subspan(state.current_idx..state.current_idx + 1)
641 .unwrap_or(state.lit.span()),
642 ))
643 }
644 c if Some(c) == terminator && !next_terminator_escaped => {
647 terminator = None;
648 Ok(false)
649 }
650 _ => {
653 next_terminator_escaped = false;
654 Ok(false)
655 }
656 }
657 })?;
658
659 if !matched_hole_end {
660 Err(Error::incomplete_hole(
661 self.lit
662 .subspan(start..self.start)
663 .unwrap_or(self.lit.span()),
664 ))?;
665 }
666
667 match scanned {
668 Some((input, range)) if escaped => {
669 let input = (&*input).replace("\\\"", "\"");
671 Ok((Cow::Owned(input), range))
672 }
673 Some((input, range)) => Ok((input, range)),
674 None => Err(Error::missing_expr(
675 self.lit
676 .subspan(start..self.start)
677 .unwrap_or(self.lit.span()),
678 ))?,
679 }
680 }
681}
682
683#[derive(Debug)]
687pub struct Error {
688 reason: String,
689 source: Option<Box<dyn std::error::Error>>,
690 span: Span,
691}
692
693impl std::error::Error for Error {
694 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
695 self.source.as_deref()
696 }
697}
698
699impl fmt::Display for Error {
700 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
701 write!(f, "parsing failed: {}", self.reason)
702 }
703}
704
705impl Error {
706 pub fn span(&self) -> Span {
707 self.span
708 }
709
710 fn incomplete_hole(span: Span) -> Self {
711 Error {
712 reason: format!("unexpected end of input, expected `}}`"),
713 source: None,
714 span,
715 }
716 }
717
718 fn unescaped_hole(span: Span) -> Self {
719 Error {
720 reason: format!("`{{` and `}}` characters must be escaped as `{{{{` and `}}}}`"),
721 source: None,
722 span,
723 }
724 }
725
726 fn missing_expr(span: Span) -> Self {
727 Error {
728 reason: format!("empty replacements (`{{}}`) aren't supported, put the replacement inside like `{{some_value}}`"),
729 source: None,
730 span,
731 }
732 }
733
734 fn lex_fv_expr(span: Span, expr: &str, err: proc_macro2::LexError) -> Self {
735 Error {
736 reason: format!("failed to parse `{}` as a field-value expression", expr),
737 span,
738 source: Some(format!("{:?}", err).into()),
739 }
740 }
741
742 fn parse_fv_expr<'a>(span: Span, expr: impl Into<Option<&'a str>>, err: syn::Error) -> Self {
743 Error {
744 reason: if let Some(expr) = expr.into() {
745 format!("failed to parse `{}` as a field-value expression", expr)
746 } else {
747 format!("failed to parse field-value expression")
748 },
749 span,
750 source: Some(err.into()),
751 }
752 }
753
754 fn invalid_literal(span: Span) -> Self {
755 Error {
756 reason: format!("templates must be parsed from string literals"),
757 source: None,
758 span,
759 }
760 }
761
762 fn invalid_char(span: Span, expected: &[char]) -> Self {
763 Error {
764 reason: format!(
765 "invalid character, expected: {}",
766 Error::display_list(expected)
767 ),
768 source: None,
769 span,
770 }
771 }
772
773 fn invalid_char_eof(span: Span, expected: &[char]) -> Self {
774 Error {
775 reason: format!(
776 "unexpected end-of-input, expected: {}",
777 Error::display_list(expected)
778 ),
779 source: None,
780 span,
781 }
782 }
783
784 fn unsupported_comment(span: Span) -> Self {
785 Error {
786 reason: format!("comments within expressions are not supported"),
787 source: None,
788 span,
789 }
790 }
791
792 fn display_list<'a>(l: &'a [impl fmt::Display]) -> impl fmt::Display + 'a {
793 struct DisplayList<'a, T>(&'a [T]);
794
795 impl<'a, T: fmt::Display> fmt::Display for DisplayList<'a, T> {
796 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
797 match self.0.len() {
798 1 => write!(f, "`{}`", self.0[0]),
799 _ => {
800 let mut first = true;
801
802 for item in self.0 {
803 if !first {
804 write!(f, ", ")?;
805 }
806 first = false;
807
808 write!(f, "`{}`", item)?;
809 }
810
811 Ok(())
812 }
813 }
814 }
815 }
816
817 DisplayList(l)
818 }
819}
820
821#[cfg(test)]
822mod tests {
823 use super::*;
824 use syn::Member;
825
826 #[test]
827 fn parse_ok() {
828 let cases = vec![
829 (quote!(), None::<&str>),
830 (quote!(""), Some("")),
831 (quote!("template"), Some("template")),
832 (quote!(a: 42, "template"), Some("template")),
833 (quote!("template", a: 42), Some("template")),
834 (quote!(a: 42, "template", b: 42), Some("template")),
835 ];
836
837 for (case, expected) in cases {
838 let tpl = Template::parse2(case).unwrap();
839
840 if let Some(expected) = expected {
841 let Some(LiteralPart::Text { ref text, .. }) = tpl.literal.get(0) else {
842 panic!(
843 "unexpected template {:?} (expected {:?})",
844 tpl.literal, expected
845 );
846 };
847
848 assert_eq!(expected, text);
849 } else {
850 assert_eq!(0, tpl.literal.len(), "expected an empty template");
851 }
852 }
853 }
854
855 #[test]
856 fn parse_err() {
857 let cases = vec![
858 (
859 quote!(42),
860 "parsing failed: templates must be parsed from string literals",
861 ),
862 (
863 quote!(a: 42, true),
864 "parsing failed: failed to parse field-value expression",
865 ),
866 (
867 quote!(fn x() {}, "template"),
868 "parsing failed: failed to parse field-value expression",
869 ),
870 (
871 quote!("template", fn x() {}),
872 "parsing failed: failed to parse field-value expression",
873 ),
874 ];
875
876 for (input, expected) in cases {
877 let actual = match Template::parse2(input.clone()) {
878 Err(e) => e,
879 Ok(_) => panic!("parsing {} should've failed but produced a value", input),
880 };
881
882 assert_eq!(expected, actual.to_string(),);
883 }
884 }
885
886 #[test]
887 fn template_parse_ok() {
888 let cases = vec![
889 ("", vec![text("", 0..0)]),
890 ("Hello world 🎈📌", vec![text("Hello world 🎈📌", 1..21)]),
891 (
892 "Hello {world} 🎈📌",
893 vec![
894 text("Hello ", 1..7),
895 hole("world", 8..13),
896 text(" 🎈📌", 14..23),
897 ],
898 ),
899 ("{world}", vec![hole("world", 2..7)]),
900 (
901 "Hello {#[log::debug] world} 🎈📌",
902 vec![
903 text("Hello ", 1..7),
904 hole("#[log::debug] world", 8..27),
905 text(" 🎈📌", 28..37),
906 ],
907 ),
908 (
909 "Hello {#[log::debug] world: 42} 🎈📌",
910 vec![
911 text("Hello ", 1..7),
912 hole("#[log::debug] world: 42", 8..31),
913 text(" 🎈📌", 32..41),
914 ],
915 ),
916 (
917 "Hello {#[log::debug] world: \"is text\"} 🎈📌",
918 vec![
919 text("Hello ", 1..7),
920 hole("#[log::debug] world: \"is text\"", 8..40),
921 text(" 🎈📌", 41..50),
922 ],
923 ),
924 (
925 "{Hello} {world}",
926 vec![hole("Hello", 2..7), text(" ", 8..9), hole("world", 10..15)],
927 ),
928 (
929 "{a}{b}{c}",
930 vec![hole("a", 2..3), hole("b", 5..6), hole("c", 8..9)],
931 ),
932 (
933 "🎈📌{a}🎈📌{b}🎈📌{c}🎈📌",
934 vec![
935 text("🎈📌", 1..9),
936 hole("a", 10..11),
937 text("🎈📌", 12..20),
938 hole("b", 21..22),
939 text("🎈📌", 23..31),
940 hole("c", 32..33),
941 text("🎈📌", 34..42),
942 ],
943 ),
944 (
945 "Hello 🎈📌 {{world}}",
946 vec![text("Hello 🎈📌 {world}", 1..25)],
947 ),
948 (
949 "🎈📌 Hello world {{}}",
950 vec![text("🎈📌 Hello world {}", 1..26)],
951 ),
952 (
953 "Hello {#[log::debug] world: \"{\"} 🎈📌",
954 vec![
955 text("Hello ", 1..7),
956 hole("#[log::debug] world: \"{\"", 8..34),
957 text(" 🎈📌", 35..44),
958 ],
959 ),
960 (
961 "Hello {#[log::debug] world: '{'} 🎈📌",
962 vec![
963 text("Hello ", 1..7),
964 hole("#[log::debug] world: '{'", 8..32),
965 text(" 🎈📌", 33..42),
966 ],
967 ),
968 (
969 "Hello {#[log::debug] world: \"is text with 'embedded' stuff\"} 🎈📌",
970 vec![
971 text("Hello ", 1..7),
972 hole(
973 "#[log::debug] world: \"is text with 'embedded' stuff\"",
974 8..62,
975 ),
976 text(" 🎈📌", 63..72),
977 ],
978 ),
979 ("{{", vec![text("{", 1..3)]),
980 ("}}", vec![text("}", 1..3)]),
981 ];
982
983 for (template, expected) in cases {
984 let actual = match LiteralPart::parse_lit2(Literal::string(template)) {
985 Ok(template) => template,
986 Err(e) => panic!("failed to parse {:?}: {}", template, e),
987 };
988
989 assert_eq!(
990 format!("{:?}", expected),
991 format!("{:?}", actual),
992 "parsing template: {:?}",
993 template
994 );
995 }
996 }
997
998 #[test]
999 fn template_parse_err() {
1000 let cases = vec![
1001 ("{", "parsing failed: unexpected end of input, expected `}`"),
1002 ("a {", "parsing failed: unexpected end of input, expected `}`"),
1003 ("a { a", "parsing failed: unexpected end of input, expected `}`"),
1004 ("{ a", "parsing failed: unexpected end of input, expected `}`"),
1005 ("}", "parsing failed: `{` and `}` characters must be escaped as `{{` and `}}`"),
1006 ("} a", "parsing failed: `{` and `}` characters must be escaped as `{{` and `}}`"),
1007 ("a } a", "parsing failed: `{` and `}` characters must be escaped as `{{` and `}}`"),
1008 ("a }", "parsing failed: `{` and `}` characters must be escaped as `{{` and `}}`"),
1009 ("{}", "parsing failed: empty replacements (`{}`) aren\'t supported, put the replacement inside like `{some_value}`"),
1010 ("{not real rust}", "parsing failed: failed to parse `not real rust` as a field-value expression"),
1011 ("{// a comment!}", "parsing failed: comments within expressions are not supported"),
1012 ("{/* a comment! */}", "parsing failed: comments within expressions are not supported"),
1013 ];
1014
1015 for (template, expected) in cases {
1016 let actual = match LiteralPart::parse_lit2(Literal::string(template)) {
1017 Err(e) => e,
1018 Ok(actual) => panic!(
1019 "parsing {:?} should've failed but produced {:?}",
1020 template, actual
1021 ),
1022 };
1023
1024 assert_eq!(
1025 expected,
1026 actual.to_string(),
1027 "parsing template: {:?}",
1028 template
1029 );
1030 }
1031 }
1032
1033 fn text(text: &str, range: Range<usize>) -> LiteralPart {
1034 LiteralPart::Text {
1035 text: text.to_owned(),
1036 range,
1037 }
1038 }
1039
1040 fn hole(expr: &str, range: Range<usize>) -> LiteralPart {
1041 LiteralPart::Hole {
1042 expr: syn::parse_str(expr)
1043 .unwrap_or_else(|e| panic!("failed to parse {:?} ({})", expr, e)),
1044 range,
1045 }
1046 }
1047
1048 #[test]
1049 fn visit_literal() {
1050 fn to_rt_tokens(template: &Template, base: TokenStream) -> TokenStream {
1051 struct DefaultVisitor {
1052 base: TokenStream,
1053 parts: Vec<TokenStream>,
1054 }
1055
1056 impl LiteralVisitor for DefaultVisitor {
1057 fn visit_text(&mut self, text: &str) {
1058 let base = &self.base;
1059
1060 self.parts.push(quote!(#base::Part::Text(#text)));
1061 }
1062
1063 fn visit_hole(&mut self, hole: &FieldValue) {
1064 let hole = match hole.member {
1065 Member::Named(ref member) => member.to_string(),
1066 Member::Unnamed(ref member) => member.index.to_string(),
1067 };
1068
1069 let base = &self.base;
1070
1071 self.parts.push(quote!(#base::Part::Hole(#hole)));
1072 }
1073 }
1074
1075 let mut visitor = DefaultVisitor {
1076 base,
1077 parts: Vec::new(),
1078 };
1079 template.visit_literal(&mut visitor);
1080
1081 let base = &visitor.base;
1082 let parts = &visitor.parts;
1083
1084 quote!(
1085 #base::Template(&[#(#parts),*])
1086 )
1087 }
1088
1089 let cases = vec![(
1090 quote!("text and {label} and {more: 42}"),
1091 quote!(crate::rt::Template(&[
1092 crate::rt::Part::Text("text and "),
1093 crate::rt::Part::Hole("label"),
1094 crate::rt::Part::Text(" and "),
1095 crate::rt::Part::Hole("more")
1096 ])),
1097 )];
1098
1099 for (template, expected) in cases {
1100 let template = Template::parse2(template).unwrap();
1101
1102 assert!(template.has_literal());
1103
1104 assert_eq!(
1105 expected.to_string(),
1106 to_rt_tokens(&template, quote!(crate::rt)).to_string()
1107 );
1108 }
1109 }
1110
1111 #[test]
1112 fn visit_literal_empty() {
1113 struct DefaultVisitor {
1114 called: bool,
1115 }
1116
1117 impl LiteralVisitor for DefaultVisitor {
1118 fn visit_text(&mut self, _: &str) {
1119 self.called = true;
1120 }
1121
1122 fn visit_hole(&mut self, _: &FieldValue) {
1123 unreachable!()
1124 }
1125 }
1126
1127 let mut visitor = DefaultVisitor { called: false };
1128
1129 let template = Template::parse2(quote!("")).unwrap();
1130
1131 template.visit_literal(&mut visitor);
1132
1133 assert!(template.has_literal());
1134 assert!(visitor.called);
1135 }
1136
1137 #[test]
1138 fn visit_literal_none() {
1139 struct DefaultVisitor {
1140 called: bool,
1141 }
1142
1143 impl LiteralVisitor for DefaultVisitor {
1144 fn visit_text(&mut self, _: &str) {
1145 unreachable!()
1146 }
1147
1148 fn visit_hole(&mut self, _: &FieldValue) {
1149 unreachable!()
1150 }
1151 }
1152
1153 let mut visitor = DefaultVisitor { called: false };
1154
1155 let template = Template::parse2(quote!()).unwrap();
1156
1157 template.visit_literal(&mut visitor);
1158
1159 assert!(!template.has_literal());
1160 assert!(!visitor.called);
1161 }
1162}