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