1use proc_macro::TokenStream;
26use quote::quote;
27use syn::{parse_macro_input, DeriveInput, Data, Fields};
28
29struct ParsedField {
32 ident: syn::Ident,
34 ty: syn::Type,
36 pos_start: usize,
38 pos_end: usize,
40 kind: FieldKindMacro,
42}
43
44enum FieldKindMacro {
46 Alpha,
47 Numeric,
48 Decimal { scale: u8 },
49}
50
51fn parse_pos(lit: &syn::LitStr) -> syn::Result<(usize, usize)> {
56 let s = lit.value();
57 let parts: Vec<_> = s.split("..").collect();
58
59 if parts.len() != 2 {
60 return Err(syn::Error::new_spanned(lit, "pos deve estar no formato start..end"));
61 }
62 let start = parts[0].parse::<usize>().map_err(|_| syn::Error::new_spanned(lit, "start inválido"))?;
63 let end = parts[1].parse::<usize>().map_err(|_| syn::Error::new_spanned(lit, "end inválido"))?;
64
65 if start == 0 || end < start {
66 return Err(syn::Error::new_spanned(lit, "pos inválido: start deve ser >=1 e end >= start"));
67 }
68 Ok((start, end))
69}
70
71#[proc_macro_derive(FixedWidth, attributes(fw))]
81pub fn derive_fixed_width(input: TokenStream) -> TokenStream {
82 let input = parse_macro_input!(input as DeriveInput);
84 let name = &input.ident;
85
86 let fields = match &input.data {
88 Data::Struct(data) => match &data.fields {
89 Fields::Named(fields) => &fields.named,
90 _ => return syn::Error::new_spanned(&input.ident, "Apenas campos nomeados suportados").to_compile_error().into(),
91 },
92 _ => return syn::Error::new_spanned(&input.ident, "Apenas structs suportadas").to_compile_error().into(),
93 };
94
95 let mut parsed_fields = Vec::new();
96
97 for field in fields {
99 let ident = field.ident.clone().unwrap();
100 let ty = field.ty.clone();
101 let mut pos = None;
102 let mut kind = None;
103
104 for attr in &field.attrs {
106 if attr.path().is_ident("fw") {
107 attr.parse_nested_meta(|meta| {
108 let name = meta.path.get_ident().map(|i| i.to_string());
109 match name.as_deref() {
110 Some("pos") => {
112 let lit: syn::LitStr = meta.value()?.parse()?;
113 pos = Some(parse_pos(&lit)?);
114 }
115 Some("alpha") => kind = Some(FieldKindMacro::Alpha),
117 Some("numeric") => kind = Some(FieldKindMacro::Numeric),
119 Some("decimal") => {
121 let lit: syn::LitInt = meta.value()?.parse()?;
122 kind = Some(FieldKindMacro::Decimal { scale: lit.base10_parse::<u8>()? });
123 }
124 _ => return Err(syn::Error::new_spanned(meta.path, "atributo fw desconhecido")),
125 }
126 Ok(())
127 }).expect("parse failed");
128 }
129 }
130
131 let (start, end) = pos.expect("campo sem pos definido (ex: pos = \"1..10\")");
133 let kind = kind.expect("campo sem tipo definido (use alpha, numeric ou decimal)");
134
135 parsed_fields.push(ParsedField { ident, ty, pos_start: start, pos_end: end, kind });
136 }
137
138 for (i, f1) in parsed_fields.iter().enumerate() {
141 for f2 in &parsed_fields[i + 1..] {
142
143 let overlap_start = std::cmp::max(f1.pos_start, f2.pos_start);
144 let overlap_end = std::cmp::min(f1.pos_end, f2.pos_end);
145
146 if overlap_start <= overlap_end {
148 let err = syn::Error::new_spanned(
149 &f2.ident, format!(
151 "Conflito de Posição detectado!\nCampo A: '{}' ocupa {}..{}\nCampo B: '{}' ocupa {}..{}\nSobreposição nas posições: {}..{}",
152 f1.ident, f1.pos_start, f1.pos_end,
153 f2.ident, f2.pos_start, f2.pos_end,
154 overlap_start, overlap_end
155 )
156 );
157
158 return err.to_compile_error().into();
159 }
160 }
161 }
162
163 let field_specs = parsed_fields.iter().map(|f| {
168 let name = f.ident.to_string(); let start = f.pos_start;
170 let end = f.pos_end;
171
172 let kind = match &f.kind {
173 FieldKindMacro::Alpha => quote!(cnab_fixedwidth::FieldKind::Alpha),
174 FieldKindMacro::Numeric => quote!(cnab_fixedwidth::FieldKind::Numeric),
175 FieldKindMacro::Decimal { scale } => quote!(cnab_fixedwidth::FieldKind::Decimal { scale: #scale }),
176 };
177
178 quote! {
180 cnab_fixedwidth::FieldSpec {
181 name: #name,
182 pos: cnab_fixedwidth::FieldPos { start: #start, end: #end },
183 kind: #kind,
184 }
185 }
186 });
187
188 let field_inits = parsed_fields.iter().map(|f| {
191 let ident = &f.ident;
192 let name = ident.to_string();
193 let ty = &f.ty;
194
195 match f.kind {
196 FieldKindMacro::Alpha => quote! {
197 #ident: parsed[#name].as_str()
199 .ok_or(cnab_fixedwidth::FixedWidthError::InvalidUtf8)?
200 .to_string()
201 },
202 FieldKindMacro::Numeric => quote! {
203 #ident: parsed[#name].as_i64().ok_or(
206 cnab_fixedwidth::FixedWidthError::InvalidNumeric {
207 field: #name,
208 snippet: String::new(),
209 }
210 )? as #ty
211 },
212 FieldKindMacro::Decimal { scale: _ } => quote! {
213 #ident: parsed[#name].as_f64().ok_or(
215 cnab_fixedwidth::FixedWidthError::InvalidNumeric {
216 field: #name,
217 snippet: String::new(),
218 }
219 )?
220 },
221 }
222 });
223
224 quote! {
226 impl cnab_fixedwidth::FixedWidthParse for #name {
227 fn parse(line: &str) -> cnab_fixedwidth::Result<Self> {
228 let fields = vec![ #(#field_specs),* ];
230
231 let parsed = cnab_fixedwidth::parse_line(line, &fields)?;
233
234 Ok(Self {
236 #(#field_inits),*
237 })
238 }
239 }
240 }.into()
241}