1use super::framework::*;
4
5#[derive(Debug)]
11pub struct Items {
12 span: Span,
14 items: Vec<Item>,
15 errors: Vec<syn::Error>,
16 atoms: Vec<AtomForReport>,
17}
18
19#[derive(Debug, Clone)]
24pub struct AtomForReport {
25 text: String,
26 span: Span,
27}
28
29#[derive(Debug)]
30enum Item {
36 Plain {
37 text: String,
38 span: Option<Span>,
39 },
40 Complex {
41 surround: Surround,
42 text: String,
43 te_span: Span,
44 },
45}
46
47#[derive(Debug)]
48struct Surround {
49 pre: TokenStream,
50 post: TokenStream,
51 grouping: Grouping,
52}
53
54#[derive(Copy, Clone, Debug)]
58pub struct IdentFragInfallible(pub Void);
59
60impl From<IdentFragInfallible> for syn::Error {
61 fn from(i: IdentFragInfallible) -> syn::Error {
62 void::unreachable(i.0)
63 }
64}
65
66impl IdentFragInfallible {
67 pub fn unreachable(&self) -> ! {
68 void::unreachable(self.0)
69 }
70}
71
72pub trait IdentFrag: Spanned {
98 type BadIdent: Clone;
99
100 fn frag_to_tokens(
104 &self,
105 out: &mut TokenStream,
106 ) -> Result<(), Self::BadIdent>;
107
108 fn fragment(&self) -> String;
110
111 fn note_atoms(&self, out: &mut Vec<AtomForReport>) {
117 out.push(AtomForReport {
118 text: self.fragment(),
119 span: self.span(),
120 });
121 }
122}
123
124impl<T: ToTokens + quote::IdentFragment + Display> IdentFrag for T {
125 type BadIdent = IdentFragInfallible;
126 fn frag_to_tokens(
127 &self,
128 out: &mut TokenStream,
129 ) -> Result<(), IdentFragInfallible> {
130 Ok(self.to_tokens(out))
131 }
132
133 fn fragment(&self) -> String {
134 let s = self.to_string();
135 if let Some(s) = s.strip_prefix("r#") {
136 s.to_string()
137 } else {
138 s
139 }
140 }
141}
142
143#[derive(Debug)]
153pub struct ChangeCaseOkIn<O: SubstParseContext> {
154 pub not_in_bool: O::NotInBool,
155 #[allow(dead_code)]
156 paste_context_checked: (),
157}
158
159impl<O: SubstParseContext> ChangeCaseOkIn<O> {
160 pub fn new_checked(case: ChangeCase, span: Span) -> syn::Result<Self> {
161 let not_in_bool = O::not_in_bool(&span)?;
162
163 let paste_context_checked = case
164 .ok_in_paste()
165 .or_else(|()| {
167 let _: O::ConcatOnly = O::concat_only(&span)?;
168 let _: beta::Enabled = beta::Enabled::new_for_syntax(span)?;
169 Ok::<_, syn::Error>(())
170 })?;
171
172 Ok(ChangeCaseOkIn {
173 not_in_bool,
174 paste_context_checked,
175 })
176 }
177}
178
179macro_rules! define_cases { {
195 $(
196 $heck:ident $( $keyword:literal )* $in_paste:ident,
197 )*
198} => {
199 #[derive(Debug, Clone, Copy)]
200 pub enum ChangeCase {
201 $(
202 #[cfg(feature = "case")]
203 $heck,
204 )*
205 }
206
207 impl FromStr for ChangeCase {
208 type Err = ();
209 #[allow(unreachable_code)]
210 fn from_str(s: &str) -> Result<Self, ()> {
211 Ok(match s {
212 $(
213 $( $keyword )|* => {
214 #[cfg(not(feature = "case"))]
216 panic!(
217 "case changing not supported, enable `case` feature of `derive-deftly`"
218 );
219
220 #[cfg(feature = "case")]
221 ChangeCase::$heck
222 },
223 )*
224 _ => return Err(()),
225 })
226 }
227 }
228
229 impl ChangeCase {
230 fn apply(self, #[allow(unused)] input: &str) -> String {
231 match self {
232 $(
233 #[cfg(feature = "case")]
234 ChangeCase::$heck => heck::$heck(input).to_string(),
235 )*
236 }
237 }
238
239 fn ok_in_paste(self) -> Result<(), ()> {
240 match self {
241 $(
242 #[cfg(feature = "case")]
243 ChangeCase::$heck => $in_paste(()),
244 )*
245 }
246 }
247 }
248} }
249
250define_cases! {
251 AsUpperCamelCase "pascal_case" "upper_camel_case" Ok,
253 AsLowerCamelCase "lower_camel_case" Ok,
254 AsSnakeCase "snake_case" Ok,
255 AsShoutySnakeCase "shouty_snake_case" Ok,
256 AsKebabCase "kebab_case" Err,
257 AsShoutyKebabCase "shouty_kebab_case" Err,
258 AsTitleCase "title_case" Err,
259 AsTrainCase "train_case" Err,
260}
261
262pub struct TokenPastesAsIdent<T>(pub T);
270
271impl<T: ToTokens> Spanned for TokenPastesAsIdent<T> {
272 fn span(&self) -> Span {
273 self.0.span()
274 }
275}
276
277impl<T: ToTokens> IdentFrag for TokenPastesAsIdent<T> {
278 type BadIdent = IdentFragInfallible;
279
280 fn frag_to_tokens(
281 &self,
282 out: &mut TokenStream,
283 ) -> Result<(), Self::BadIdent> {
284 Ok(self.0.to_tokens(out))
285 }
286
287 fn fragment(&self) -> String {
288 self.0.to_token_stream().to_string()
289 }
290}
291
292impl Items {
295 pub fn new(span: Span) -> Self {
296 Items {
297 span,
298 items: vec![],
299 errors: vec![],
300 atoms: vec![],
301 }
302 }
303}
304
305#[derive(Debug)]
307struct Pasted {
308 whole: String,
310 span: Span,
312 atoms: Vec<AtomForReport>,
314}
315
316impl Spanned for Pasted {
317 fn span(&self) -> Span {
318 self.span
319 }
320}
321impl IdentFrag for Pasted {
322 type BadIdent = syn::Error;
323
324 fn fragment(&self) -> String {
325 self.whole.clone()
326 }
327
328 fn note_atoms(&self, atoms: &mut Vec<AtomForReport>) {
329 atoms.extend(self.atoms.iter().cloned())
330 }
331
332 fn frag_to_tokens(&self, out: &mut TokenStream) -> syn::Result<()> {
333 let ident = convert_to_ident(self)?;
334 ident.to_tokens(out);
335 Ok(())
336 }
337}
338
339type Piece<'i> = (&'i str, Option<Span>);
344
345enum AssemblyInstruction {
346 Plain(Pasted),
347 Complex {
348 tspan: Span,
349 surround: Surround,
350 ident: Pasted,
351 },
352}
353
354use AssemblyInstruction as AI;
355
356fn mk_ident<'i>(
363 span: Span,
364 change_case: Option<ChangeCase>,
365 pieces: impl Iterator<Item = Piece<'i>> + Clone,
366 atoms: Vec<AtomForReport>,
367) -> Pasted {
368 let whole = pieces.clone().map(|i| i.0).collect::<String>();
369 let whole = if let Some(change_case) = change_case {
370 change_case.apply(&whole)
371 } else {
372 whole
373 };
374 Pasted { span, whole, atoms }
375}
376
377#[derive(Eq, PartialEq, Debug)]
382struct InvalidIdent;
383
384fn convert_to_ident(pasted: &Pasted) -> syn::Result<syn::Ident> {
389 let mut ident = (|| {
420 let s = &pasted.whole;
421
422 let prefixed;
423 let (ident, comparator) = match syn::parse_str::<Ident>(s) {
424 Ok(ident) => {
425 (ident, s)
427 }
428 Err(_) => {
429 prefixed = format!("r#{}", s);
431 let ident = syn::parse_str::<Ident>(&prefixed)
432 .map_err(|_| InvalidIdent)?;
434 (ident, &prefixed)
435 }
436 };
437
438 if &ident.to_string() != comparator {
440 return Err(InvalidIdent);
441 }
442
443 Ok(ident)
444 })()
445 .map_err(|_| {
446 let mut err = pasted.span.error(format_args!(
448 "constructed identifier {:?} is invalid",
449 &pasted.whole,
450 ));
451 for (
459 AtomForReport {
460 text: piece,
461 span: pspan,
462 },
463 pfx,
464 ) in izip!(
465 &pasted.atoms,
466 chain!(iter::once(""), iter::repeat("X")),
472 ) {
473 match syn::parse_str(&format!("{}{}", pfx, piece)) {
482 Ok::<IdentAny, _>(_) => {}
483 Err(_) => err
484 .combine(pspan.error(
485 "probably-invalid input to identifier pasting",
486 )),
487 }
488 }
489 err
490 })?;
491
492 ident.set_span(pasted.span);
493 Ok(ident)
494}
495
496#[test]
497fn ident_from_str() {
498 let span = Span::call_site();
499 let chk = |s: &str, exp: Result<&str, _>| {
500 let p = Pasted {
501 whole: s.to_string(),
502 span,
503 atoms: vec![],
504 };
505 let parsed = convert_to_ident(&p)
506 .map(|i| i.to_string())
507 .map_err(|_| InvalidIdent);
508 let exp = exp.map(|i| i.to_string());
509 assert_eq!(parsed, exp);
510 };
511 let chk_ok = |s| chk(s, Ok(s));
512 let chk_err = |s| chk(s, Err(InvalidIdent));
513
514 chk("for", Ok("r#for"));
515 chk_ok("r#for");
516 chk_ok("_thing");
517 chk_ok("thing_");
518 chk_ok("r#raw");
519 chk_err("");
520 chk_err("a b");
521 chk_err("spc ");
522 chk_err(" spc");
523 chk_err("r#a spc");
524 chk_err(" r#a ");
525 chk_err(" r#for ");
526 chk_err("r#r#doubly_raw");
527 chk_err("0");
528}
529
530pub fn expand<'c>(
531 ctx: GeneralContext<'c>,
532 tspan: Span,
533 content: &Template<paste::Items>,
534 out: &mut impl ExpansionOutput,
535) -> syn::Result<()> {
536 let mut items = paste::Items::new(tspan);
537 content.expand(ctx, &mut items);
538 items.assemble(out, None)
539}
540
541impl Items {
542 fn append_atom(&mut self, item: Item) {
543 match &item {
544 Item::Plain {
545 text,
546 span: Some(span),
547 ..
548 }
549 | Item::Complex {
550 text,
551 te_span: span,
552 ..
553 } => {
554 self.atoms.push(AtomForReport {
555 text: text.clone(),
556 span: *span,
557 });
558 }
559 Item::Plain { span: None, .. } => {}
560 };
561 self.items.push(item);
562 }
563 fn append_item_raw(&mut self, item: Item) {
564 self.items.push(item);
565 }
566 fn append_plain<V: Display>(&mut self, span: Span, v: V) {
570 self.append_atom(Item::Plain {
571 text: v.to_string(),
572 span: Some(span),
573 })
574 }
575 pub fn append_fixed_string(&mut self, text: &'static str) {
576 self.append_atom(Item::Plain {
577 text: text.into(),
578 span: None,
579 })
580 }
581
582 pub fn assemble(
591 self,
592 out: &mut impl ExpansionOutput,
593 change_case: Option<ChangeCase>,
594 ) -> syn::Result<()> {
595 if !self.errors.is_empty() {
596 for error in self.errors {
597 out.record_error(error);
598 }
599 return Ok(());
600 }
601
602 match Self::assemble_inner(
603 self.span,
604 self.items,
605 change_case,
606 self.atoms,
607 )? {
608 AI::Plain(ident) => out.append_identfrag_toks(
609 &ident, )?,
611 AI::Complex {
612 tspan,
613 surround,
614 ident,
615 } => out.append_idpath(
616 tspan,
617 |ta| ta.append(surround.pre),
618 &ident,
619 |ta| ta.append(surround.post),
620 surround.grouping,
621 )?,
622 }
623
624 Ok(())
625 }
626
627 fn assemble_inner(
637 tspan: Span,
638 items: Vec<Item>,
639 change_case: Option<ChangeCase>,
640 atoms: Vec<AtomForReport>,
641 ) -> syn::Result<AssemblyInstruction> {
642 let out_span = tspan;
658
659 let nontrivial = items
660 .iter()
661 .enumerate()
662 .filter_map(|(pos, it)| match it {
663 Item::Plain { .. } => None,
664 Item::Complex { te_span, .. } => Some((pos, te_span)),
665 })
666 .at_most_one()
667 .map_err(|several| {
668 let mut several = several.map(|(_pos, span)| {
670 span.error("multiple nontrivial entries in ${paste ...}")
671 });
672 let mut collect = several.next().unwrap();
673 collect.extend(several);
674 collect
675 })?
676 .map(|(pos, _)| pos);
677
678 fn plain_strs(
679 items: &[Item],
680 ) -> impl Iterator<Item = Piece<'_>> + Clone {
681 items.iter().map(|item| match item {
682 Item::Plain { text, span } => (text.as_str(), *span),
683 _ => panic!("non plain item"),
684 })
685 }
686
687 if let Some(nontrivial) = nontrivial {
688 let mut items = items;
693 let item_nontrivial = {
694 let dummy_item = Item::Plain {
695 text: String::new(),
696 span: None,
697 };
698 mem::replace(&mut items[nontrivial], dummy_item)
699 };
700 let items_before = &items[0..nontrivial];
701 let items_after = &items[nontrivial + 1..];
702
703 let mk_ident_nt = |(text, txspan): Piece, atoms| {
704 mk_ident(
705 out_span,
706 change_case,
707 chain!(
708 plain_strs(items_before),
709 iter::once((text, txspan)),
710 plain_strs(items_after),
711 ),
712 atoms,
713 )
714 };
715
716 match item_nontrivial {
717 Item::Complex {
718 surround,
719 text,
720 te_span,
721 } => {
722 return Ok(AI::Complex {
723 tspan,
724 surround,
725 ident: mk_ident_nt((&text, Some(te_span)), atoms),
726 })
727 }
728 Item::Plain { .. } => panic!("trivial nontrivial"),
729 }
730 } else {
731 return Ok(AI::Plain(
732 mk_ident(out_span, change_case, plain_strs(&items), atoms), ));
734 }
735 }
736}
737
738impl SubstParseContext for Items {
739 type NotInPaste = Void;
740 type NotInConcat = ();
741 type NotInBool = ();
742 type BoolOnly = Void;
743 type ConcatOnly = Void;
744 type DbgContent = Template<Self>;
745
746 fn not_in_bool(_: &impl Spanned) -> syn::Result<()> {
747 Ok(())
748 }
749 fn not_in_concat(_: &impl Spanned) -> syn::Result<()> {
750 Ok(())
751 }
752
753 fn not_in_paste(span: &impl Spanned) -> syn::Result<Void> {
754 Err(span
755 .error("not allowed in within ${paste ...} (or case_changing)"))
756 }
757
758 type SpecialParseContext = Option<AngleBrackets>;
759}
760
761impl SpecialParseContext for Option<AngleBrackets> {
762 fn before_element_hook(
763 &mut self,
764 input: ParseStream,
765 ) -> syn::Result<Option<SpecialInstructions>> {
766 if input.peek(Token![>]) {
767 if let Some(state) = self {
768 let _: Token![>] = input.parse()?;
769 state.found_close = true;
770 return Ok(Some(SpecialInstructions::EndOfTemplate));
771 } else {
772 return Err(
773 input.error("stray > within curly-bracketed ${paste }")
774 );
775 }
776 }
777 return Ok(None);
778 }
779}
780
781#[derive(Default)]
783pub struct AngleBrackets {
784 found_close: bool,
785}
786
787impl AngleBrackets {
788 pub fn finish(self, start_span: Span) -> syn::Result<()> {
789 if !self.found_close {
790 return Err(
791 start_span.error("unmatched paste $< start - missing >")
792 );
793 }
794 Ok(())
795 }
796}
797
798impl ExpansionOutput for Items {
799 fn append_identfrag_toks<I: IdentFrag>(
800 &mut self,
801 ident: &I,
802 ) -> Result<(), I::BadIdent> {
803 ident.note_atoms(&mut self.atoms);
804 self.append_plain(ident.span(), ident.fragment());
805 Ok(())
806 }
807 fn append_idpath<A, B, I>(
808 &mut self,
809 te_span: Span,
810 pre_: A,
811 ident: &I,
812 post_: B,
813 grouping: Grouping,
814 ) -> Result<(), I::BadIdent>
815 where
816 A: FnOnce(&mut TokenAccumulator),
817 B: FnOnce(&mut TokenAccumulator),
818 I: IdentFrag,
819 {
820 let mut pre = TokenAccumulator::new();
821 pre_(&mut pre);
822 let mut post = TokenAccumulator::new();
823 post_(&mut post);
824 let text = ident.fragment();
825 let mut handle_err = |prepost: TokenAccumulator| {
826 prepost.tokens().unwrap_or_else(|err| {
827 self.record_error(err);
828 TokenStream::new()
829 })
830 };
831 let pre = handle_err(pre);
832 let post = handle_err(post);
833 ident.note_atoms(&mut self.atoms);
834 let surround = Surround {
835 pre,
836 post,
837 grouping,
838 };
839 self.append_item_raw(Item::Complex {
840 surround,
841 text,
842 te_span,
843 });
844 Ok(())
845 }
846 fn append_syn_litstr(&mut self, lit: &syn::LitStr) {
847 self.append_plain(lit.span(), lit.value());
848 }
849 fn append_syn_type_inner(
850 &mut self,
851 te_span: Span,
852 ty: syn::Type,
853 grouping: Grouping,
854 ) {
855 (|| {
856 match ty {
857 syn::Type::Path(mut path) => {
858 let (last_segment, last_punct) = path
859 .path
860 .segments
861 .pop()
862 .ok_or_else(|| {
863 te_span.error(
864 "derive-deftly token pasting applied to path with no components",
865 )
866 })?
867 .into_tuple();
868 let syn::PathSegment { ident, arguments } = last_segment;
869 let mut pre = TokenStream::new();
870 path.to_tokens(&mut pre);
871 let text = ident.to_string();
872 let mut post = TokenStream::new();
873 arguments.to_tokens(&mut post);
874 last_punct.to_tokens(&mut post);
875 let surround = Surround { pre, post, grouping };
876 let item = Item::Complex {
877 surround,
878 text,
879 te_span,
880 };
881 self.append_atom(item)
882 }
883 x => {
884 return Err(x.error(
885 "derive-deftly macro wanted to do identifier pasting, but complex type provided",
886 ))
887 }
888 }
889 Ok::<_, syn::Error>(())
890 })()
891 .unwrap_or_else(|e| self.record_error(e));
892 }
893 fn append_tokens_with(
894 &mut self,
895 (not_in_paste, _not_in_compat): &(Void, ()),
896 _: impl FnOnce(&mut TokenAccumulator) -> syn::Result<()>,
897 ) -> syn::Result<()> {
898 void::unreachable(*not_in_paste)
899 }
900
901 fn append_bool_only(&mut self, bool_only: &Self::BoolOnly) -> ! {
902 void::unreachable(*bool_only)
903 }
904
905 fn record_error(&mut self, err: syn::Error) {
906 self.errors.push(err);
907 }
908
909 fn new_with_span(kw_span: Span) -> Self {
910 Self::new(kw_span)
911 }
912
913 fn default_subst_meta_as(_: Span) -> syn::Result<meta::SubstAs<Self>> {
914 Ok(meta::SubstAs::str(()))
915 }
916
917 fn ignore_impl(self) -> syn::Result<()> {
918 let Items {
919 span: _,
920 items: _,
921 atoms: _,
922 errors,
923 } = self;
924 let mut accum = ErrorAccumulator::default();
925 for e in errors {
926 accum.push(e);
927 }
928 accum.finish()
929 }
930
931 fn dbg_expand<'c>(
932 &mut self,
933 kw_span: Span,
934 ctx: GeneralContext<'c>,
935 msg: &mut String,
936 content: &Template<Items>,
937 ) -> fmt::Result {
938 let mut child = Items::new(kw_span);
939 content.expand(ctx, &mut child);
940 let Items {
941 span: _,
942 items,
943 errors,
944 atoms: _,
945 } = child;
946
947 for e in &errors {
948 writeln!(msg, "ERROR: {}", e)?;
949 }
950 for (sep, i) in izip!(
951 chain!([""], iter::repeat(" ")), &items,
953 ) {
954 write!(msg, "{}", sep)?;
955 match i {
956 Item::Plain { text, .. } => write!(msg, "{:?}", text)?,
957 Item::Complex { surround, text, .. } => {
958 write!(
959 msg,
960 "{:?}+{:?}+{:?}",
961 surround.pre.to_string(),
962 text,
963 surround.post.to_string(),
964 )?;
965 }
966 }
967 }
968
969 self.items.extend(items);
970 self.errors.extend(errors);
971 Ok(())
972 }
973}
974
975impl Expand<Items> for TemplateElement<Items> {
976 fn expand<'c>(
977 &self,
978 ctx: GeneralContext<'c>,
979 out: &mut Items,
980 ) -> syn::Result<()> {
981 match self {
982 TE::Ident(ident, ..) => out.append_identfrag_toks(&ident)?,
983 TE::LitStr(lit) => out.append_syn_litstr(&lit),
984 TE::Subst(e) => e.expand(ctx, out)?,
985 TE::Repeat(e) => e.expand(ctx, out),
986 TE::Literal(_, allow_tokens)
987 | TE::Punct(_, allow_tokens)
988 | TE::Group { allow_tokens, .. } => {
989 void::unreachable(allow_tokens.0)
990 }
991 }
992 Ok(())
993 }
994}