1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Attribute, Data, DataEnum, DeriveInput, Fields, Ident, Lit, Meta};
4
5#[proc_macro_derive(Bundle, attributes(bundle))]
6pub fn strand_derive(input: TokenStream) -> TokenStream {
7 let input = parse_macro_input!(input as DeriveInput);
8 let name = &input.ident;
9
10 let state = extract_attr_ident(&input, "state");
11
12 let mut alternative = None;
13
14 let mut prefixes: Vec<_> = Vec::new();
15
16 let variants: Vec<_> = if let Data::Enum(DataEnum { variants, .. }) = &input.data {
17 variants.iter().filter_map(|variant| {
18 match &variant.fields {
19 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
20 let field_ty = &fields.unnamed[0].ty;
21
22 if attr_exist(&variant.attrs, "other") {
23 alternative = Some(field_ty);
24 return None
25 }
26
27 if let Some(prefix) = extract_attr_name(&variant.attrs, "prefix") {
28 prefixes.push((prefix, field_ty));
29 }
30
31 let scope_name = extract_attr_name(&variant.attrs, "name")?;
32
33 Some((scope_name, field_ty))
34 }
35 _ => panic!("Strand derive only supports enums with tuple variants containing a single field"),
36 }
37 }).collect()
38 } else {
39 panic!("Strand derive only supports enums");
40 };
41
42 let prefix_variants = prefixes.iter().map(|(prefix, field_ty)| {
43 quote! {
44 s if s.starts_with(#prefix) => return #field_ty::run(state, input.strip_prefix(#prefix).unwrap(), ws_chars),
45 }
46 });
47
48 let parse_variants = variants.iter().map(|(scope_name, field_ty)| {
49 quote! {
50 #scope_name => return #field_ty::run(state, residue, ws_chars),
51 }
52 });
53
54 let match_other = if alternative.is_none() {
55 quote!(
56 _ => return Err(format!("Invalid scope: {}", arg)),
57 )
58 } else {
59 let other = alternative.unwrap();
60 quote!(
61 _ => return #other::run(state, input, ws_chars),
62 )
63 };
64
65 let gen = quote! {
66 impl Bundle for #name {
67 type State = #state;
68
69 fn run(state: &mut Self::State, input: &str, ws_chars: &[char]) -> Result<(), String> {
70 fn split_at_char<'a>(input_raw: &'a str, splits: &[char]) -> (&'a str, &'a str) {
71 fn trim_chars<'a>(input: &'a str, splits: &[char]) -> &'a str {
72 let start_trimmed = input.trim_start_matches(|c| splits.contains(&c));
73 let trimmed = start_trimmed.trim_end_matches(|c| splits.contains(&c));
74 trimmed
75 }
76
77 let input = trim_chars(input_raw, splits);
78
79 let mut out = ("", input);
80
81 for (index, char) in input.char_indices() {
82 if splits.contains(&char) {
83 let (a, b) = input.split_at(index);
84 out = (b, trim_chars(a, splits));
85 break;
86 }
87 }
88
89 out
90 }
91
92 let (residue, arg) = split_at_char(input, ws_chars);
93 if arg == "" {
94 return Err("Not enough arguments".into())
95 }
96
97 match arg {
98 #( #parse_variants )*
99 #( #prefix_variants)*
100 #match_other
101 }
102 }
103 }
104 };
105
106 gen.into()
107}
108
109fn attr_exist(attrs: &Vec<Attribute>, ident: &str) -> bool {
110 for attr in attrs {
111 if attr.path.is_ident("bundle") {
112 if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
113 for nested_meta in meta_list.nested {
114 if let syn::NestedMeta::Meta(meta) = nested_meta {
115 if let Meta::Path(path) = meta {
116 if path.is_ident(ident) {
117 return true
118 }
119 }
120 }
121 }
122 }
123 }
124 }
125
126 false
127}
128
129fn extract_attr_name(attrs: &Vec<Attribute>, ident: &str) -> Option<String> {
130 let mut action_function_name = attrs.iter().filter_map(|attr| {
131 if attr.path.is_ident("bundle") {
132 if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
133 for nested_meta in meta_list.nested {
134 if let syn::NestedMeta::Meta(meta) = nested_meta {
135 if let Meta::NameValue(name_value) = meta {
136 if name_value.path.is_ident(ident) {
137 if let Lit::Str(s) = name_value.lit {
138 return Some(s.value());
139 }
140 }
141 }
142 }
143 }
144 }
145 }
146 None
147 });
148
149 action_function_name
150 .next()
151}
152
153fn extract_attr_ident(input: &DeriveInput, ident: &str) -> Ident {
154 let mut action_function_name = input.attrs.iter().filter_map(|attr| {
155 if attr.path.is_ident("bundle") {
156 if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
157 for nested_meta in meta_list.nested {
158 if let syn::NestedMeta::Meta(meta) = nested_meta {
159 if let Meta::NameValue(name_value) = meta {
160 if name_value.path.is_ident(ident) {
161 if let Lit::Str(s) = name_value.lit {
162 return Some(Ident::new(&s.value(), s.span()));
163 }
164 }
165 }
166 }
167 }
168 }
169 }
170 None
171 });
172
173 action_function_name
174 .next()
175 .expect(&format!("Couldn't find attribute: {}", ident))
176}