1mod attr_name;
26mod enums;
27
28use proc_macro::TokenStream;
29use proc_macro2::TokenStream as Tokens;
30use quote::quote;
31use syn::{Data, DeriveInput, Fields, Ident, LitStr, Type, parse_macro_input};
32
33thread_local! {
34 static KRATE: std::cell::RefCell<Tokens> = std::cell::RefCell::new(quote!(::yog_data));
37 static CAMEL: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
39}
40
41fn krate() -> Tokens {
43 KRATE.with(|k| k.borrow().clone())
44}
45
46#[proc_macro_derive(Schema, attributes(data))]
47pub fn derive_schema(input: TokenStream) -> TokenStream {
48 let input = parse_macro_input!(input as DeriveInput);
49 let container = match attrs(&input.attrs) {
50 Ok(a) => a,
51 Err(e) => return e.to_compile_error().into(),
52 };
53 let camel = match container.rename_all.as_deref() {
54 None => false,
55 Some(attr_name::CAMEL_CASE) => true,
56 Some(_) => {
57 return syn::Error::new_spanned(
58 &input.ident,
59 "rename_all = \"camelCase\" is the one case",
60 )
61 .to_compile_error()
62 .into();
63 }
64 };
65 CAMEL.with(|c| c.set(camel));
66 let path = container
67 .krate
68 .unwrap_or_else(|| syn::parse_quote!(::yog_data));
69 KRATE.with(|k| *k.borrow_mut() = quote!(#path));
70 match expand(&input) {
71 Ok(t) => t.into(),
72 Err(e) => e.to_compile_error().into(),
73 }
74}
75
76#[derive(Default)]
77struct Attrs {
78 rename: Option<String>,
79 rename_all: Option<String>,
81 default: Option<Option<syn::Path>>,
82 skip: bool,
83 tag: Option<String>,
84 with: Option<Type>,
85 range: Option<(f64, f64)>,
86 other: bool,
88 krate: Option<syn::Path>,
90 view: bool,
92}
93
94fn attrs(list: &[syn::Attribute]) -> syn::Result<Attrs> {
95 let mut a = Attrs::default();
96 for attr in list.iter().filter(|a| a.path().is_ident(attr_name::DATA)) {
97 attr.parse_nested_meta(|meta| {
98 if meta.path.is_ident(attr_name::RENAME) {
99 a.rename = Some(meta.value()?.parse::<LitStr>()?.value());
100 } else if meta.path.is_ident(attr_name::RENAME_ALL) {
101 a.rename_all = Some(meta.value()?.parse::<LitStr>()?.value());
102 } else if meta.path.is_ident(attr_name::TAG) {
103 a.tag = Some(meta.value()?.parse::<LitStr>()?.value());
104 } else if meta.path.is_ident(attr_name::DEFAULT) {
105 a.default = Some(if meta.input.peek(syn::Token![=]) {
106 Some(meta.value()?.parse::<LitStr>()?.parse()?)
107 } else {
108 None
109 });
110 } else if meta.path.is_ident(attr_name::RANGE) {
111 let lit = meta.value()?.parse::<LitStr>()?;
112 let text = lit.value();
113 let parsed = text.split_once("..=").and_then(|(lo, hi)| {
114 Some((
115 lo.trim().parse::<f64>().ok()?,
116 hi.trim().parse::<f64>().ok()?,
117 ))
118 });
119 a.range = Some(parsed.ok_or_else(|| {
120 syn::Error::new_spanned(&lit, "range = \"lo..=hi\" (numbers)")
121 })?);
122 } else if meta.path.is_ident(attr_name::WITH) {
123 a.with = Some(meta.value()?.parse::<LitStr>()?.parse()?);
124 } else if meta.path.is_ident(attr_name::SKIP) {
125 a.skip = true;
126 } else if meta.path.is_ident(attr_name::VIEW) {
127 a.view = true;
128 } else if meta.path.is_ident(attr_name::OTHER) {
129 a.other = true;
130 } else if meta.path.is_ident(attr_name::CRATE) {
131 a.krate = Some(meta.value()?.parse::<LitStr>()?.parse()?);
132 } else {
133 return Err(meta
134 .error("unknown `data` attribute (rename, rename_all, default, skip, tag, with, range, other, crate, view)"));
135 }
136 Ok(())
137 })?;
138 }
139 Ok(a)
140}
141
142fn docs(list: &[syn::Attribute]) -> String {
143 let mut lines = Vec::new();
144 for attr in list.iter().filter(|a| a.path().is_ident(attr_name::DOC)) {
145 if let syn::Meta::NameValue(nv) = &attr.meta
146 && let syn::Expr::Lit(syn::ExprLit {
147 lit: syn::Lit::Str(s),
148 ..
149 }) = &nv.value
150 {
151 lines.push(s.value().trim().to_string());
152 }
153 }
154 let mut out = String::new();
156 for line in lines {
157 if line.is_empty() {
158 if !out.ends_with("\n\n") && !out.is_empty() {
159 out.push_str("\n\n");
160 }
161 } else {
162 if !out.is_empty() && !out.ends_with('\n') {
163 out.push(' ');
164 }
165 out.push_str(&line);
166 }
167 }
168 out.trim_end().to_string()
169}
170
171fn field_key(ident: &Ident) -> String {
173 let name = ident.to_string().trim_start_matches("r#").to_string();
174 if !CAMEL.with(std::cell::Cell::get) {
175 return name;
176 }
177 let mut out = String::new();
178 let mut upper = false;
179 for c in name.chars() {
180 match c {
181 '_' => upper = !out.is_empty(),
182 c if upper => {
183 out.push(c.to_ascii_uppercase());
184 upper = false;
185 }
186 c => out.push(c),
187 }
188 }
189 out
190}
191
192fn fields_as_written<T>(f: impl FnOnce() -> T) -> T {
195 let camel = CAMEL.with(|c| c.replace(false));
196 let out = f();
197 CAMEL.with(|c| c.set(camel));
198 out
199}
200
201fn variant_key(ident: &Ident) -> String {
203 if !CAMEL.with(std::cell::Cell::get) {
204 return snake(ident);
205 }
206 let name = ident.to_string();
207 let mut chars = name.chars();
208 chars
209 .next()
210 .map(|first| first.to_ascii_lowercase().to_string() + chars.as_str())
211 .unwrap_or_default()
212}
213
214fn snake(ident: &Ident) -> String {
215 let mut out = String::new();
216 for (i, c) in ident.to_string().chars().enumerate() {
217 if c.is_ascii_uppercase() {
218 if i > 0 {
219 out.push('_');
220 }
221 out.push(c.to_ascii_lowercase());
222 } else {
223 out.push(c);
224 }
225 }
226 out
227}
228
229struct NamedField {
230 ident: Ident,
231 ty: Type,
232 key: String,
233 doc: String,
234 default: Option<Option<syn::Path>>,
235 skip: bool,
236 with: Type,
238 codec: bool,
240 range: Option<(f64, f64)>,
241}
242
243fn named_fields(fields: &syn::FieldsNamed) -> syn::Result<Vec<NamedField>> {
244 let krate = krate();
245 fields
246 .named
247 .iter()
248 .map(|f| {
249 let a = attrs(&f.attrs)?;
250 let Some(ident) = f.ident.clone() else {
251 return Err(syn::Error::new_spanned(
252 f,
253 "Schema: a named field without a name",
254 ));
255 };
256 let key = a.rename.unwrap_or_else(|| field_key(&ident));
257 Ok(NamedField {
258 ident,
259 ty: f.ty.clone(),
260 key,
261 doc: docs(&f.attrs),
262 default: a.default,
263 skip: a.skip,
264 codec: a.with.is_some(),
265 with: a
266 .with
267 .unwrap_or_else(|| syn::parse_quote!(#krate::with::Plain)),
268 range: a.range,
269 })
270 })
271 .collect()
272}
273
274fn field_shapes(fields: &[NamedField]) -> Tokens {
276 let krate = krate();
277 let items = fields.iter().filter(|f| !f.skip).map(|f| {
278 let (key, doc, ty, w) = (&f.key, &f.doc, &f.ty, &f.with);
279 let has_default = f.default.is_some();
280 let range = match f.range {
281 Some((lo, hi)) => quote! { ::std::option::Option::Some((#lo, #hi)) },
282 None => quote! { ::std::option::Option::None },
283 };
284 quote! {
285 #krate::schema::Field {
286 name: #key,
287 doc: #doc,
288 shape: <#w as #krate::with::With<#ty>>::shape,
289 required: !#has_default && <#w as #krate::with::With<#ty>>::absent().is_none(),
290 range: #range,
291 }
292 }
293 });
294 quote! { ::std::vec![#(#items),*] }
295}
296
297fn read_fields(fields: &[NamedField], ctor: Tokens) -> Tokens {
299 let keys = fields.iter().filter(|f| !f.skip).map(|f| &f.key);
300 let lets = fields.iter().map(|f| {
301 let (ident, ty, key, w) = (&f.ident, &f.ty, &f.key, &f.with);
302 if f.skip {
303 return quote! { let #ident: ::std::option::Option<#ty> = ::std::option::Option::Some(::std::default::Default::default()); };
304 }
305 match &f.default {
306 None => quote! { let #ident = cx.field_with::<#w, #ty>(map, #key); },
307 Some(None) => quote! { let #ident = cx.field_or_with::<#w, #ty>(map, #key, ::std::default::Default::default); },
308 Some(Some(path)) => quote! { let #ident = cx.field_or_with::<#w, #ty>(map, #key, #path); },
309 }
310 });
311 let ranges = fields.iter().filter_map(|f| {
312 let (ident, key) = (&f.ident, &f.key);
313 let (lo, hi) = f.range?;
314 Some(quote! { let #ident = cx.in_range(#key, #ident, #lo, #hi); })
315 });
316 let inits = fields.iter().map(|f| {
317 let ident = &f.ident;
318 quote! { #ident: #ident? }
319 });
320 quote! {
321 cx.unknown_keys(map, &[#(#keys),*]);
322 cx.own_keys(|cx| {
323 #(#lets)*
324 #(#ranges)*
325 ::std::option::Option::Some(#ctor { #(#inits),* })
326 })
327 }
328}
329
330fn write_fields(fields: &[NamedField]) -> Tokens {
332 let krate = krate();
333 let puts = fields.iter().filter(|f| !f.skip).map(|f| {
334 let (ident, key, ty, w) = (&f.ident, &f.key, &f.ty, &f.with);
335 quote! {
336 if !<#w as #krate::with::With<#ty>>::skip(#ident) {
337 m.insert(#key, <#w as #krate::with::With<#ty>>::write(#ident));
338 }
339 }
340 });
341 quote! { #(#puts)* }
342}
343
344fn encode_fields(fields: &[NamedField]) -> Tokens {
346 let krate = krate();
347 let written: Vec<_> = fields.iter().filter(|f| !f.skip).collect();
348 let counts = written.iter().map(|f| {
349 let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
350 quote! { + usize::from(!<#w as #krate::with::With<#ty>>::skip(#ident)) }
351 });
352 let puts = written.iter().map(|f| {
353 let (ident, key, ty, w) = (&f.ident, &f.key, &f.ty, &f.with);
354 quote! {
355 if !<#w as #krate::with::With<#ty>>::skip(#ident) {
356 __yog_w.key(#key);
357 <#w as #krate::with::With<#ty>>::encode(#ident, __yog_w);
358 }
359 }
360 });
361 quote! {
362 __yog_w.map(0 #(#counts)*);
363 #(#puts)*
364 }
365}
366
367fn decode_fields(fields: &[NamedField], ctor: Tokens) -> Option<Tokens> {
370 let krate = krate();
371 if fields.iter().any(|f| f.range.is_some()) {
372 return None;
373 }
374 let read: Vec<_> = fields.iter().filter(|f| !f.skip).collect();
375 let slot = |f: &NamedField| quote::format_ident!("__yog_field_{}", f.ident);
377 let slots = read.iter().map(|f| {
378 let (s, ty) = (slot(f), &f.ty);
379 quote! { let mut #s: ::std::option::Option<#ty> = ::std::option::Option::None; }
380 });
381 let arms = read.iter().map(|f| {
382 let (s, key, ty, w) = (slot(f), &f.key, &f.ty, &f.with);
383 quote! { #key => #s = ::std::option::Option::Some(<#w as #krate::with::With<#ty>>::decode(__yog_d)?), }
384 });
385 let inits = fields.iter().map(|f| {
386 let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
387 let value = if f.skip {
388 quote! { ::std::default::Default::default() }
389 } else {
390 let missing = match &f.default {
391 None => quote! { <#w as #krate::with::With<#ty>>::absent()? },
392 Some(None) => quote! { ::std::default::Default::default() },
393 Some(Some(path)) => quote! { #path() },
394 };
395 let s = slot(f);
396 quote! { match #s { ::std::option::Option::Some(__yog_v) => __yog_v, ::std::option::Option::None => #missing } }
397 };
398 quote! { #ident: #value }
399 });
400 Some(quote! {
401 let __yog_n = __yog_d.map()?;
402 #(#slots)*
403 for _ in 0..__yog_n {
404 match __yog_d.key()? {
405 #(#arms)*
406 _ => return ::std::option::Option::None,
407 }
408 }
409 __yog_d.leave();
410 ::std::option::Option::Some(#ctor { #(#inits),* })
411 })
412}
413
414fn pack_fields(fields: &[NamedField]) -> Tokens {
416 let krate = krate();
417 let puts = fields.iter().filter(|f| !f.skip).map(|f| {
418 let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
419 quote! { <#w as #krate::with::With<#ty>>::pack(#ident, __yog_p); }
420 });
421 quote! {
422 let __yog_at = __yog_p.begin();
423 #(#puts)*
424 __yog_p.end(__yog_at);
425 }
426}
427
428fn unpack_fields(fields: &[NamedField], ctor: Tokens) -> Tokens {
432 let krate = krate();
433 let slot = |f: &NamedField| quote::format_ident!("__yog_field_{}", f.ident);
434 let gets = fields.iter().filter(|f| !f.skip).map(|f| {
435 let (s, ty, w) = (slot(f), &f.ty, &f.with);
436 quote! {
437 let #s: ::std::option::Option<#ty> = if __yog_u.more(__yog_end) {
438 ::std::option::Option::Some(<#w as #krate::with::With<#ty>>::unpack(__yog_u)?)
439 } else {
440 ::std::option::Option::None
441 };
442 }
443 });
444 let inits = fields.iter().map(|f| {
445 let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
446 let value = if f.skip {
447 quote! { ::std::default::Default::default() }
448 } else {
449 let s = slot(f);
450 let missing = match &f.default {
451 None => quote! { <#w as #krate::with::With<#ty>>::absent()? },
452 Some(None) => quote! { ::std::default::Default::default() },
453 Some(Some(path)) => quote! { #path() },
454 };
455 quote! { match #s { ::std::option::Option::Some(__yog_v) => __yog_v, ::std::option::Option::None => #missing } }
456 };
457 quote! { #ident: #value }
458 });
459 quote! {
460 let __yog_end = __yog_u.begin()?;
461 #(#gets)*
462 __yog_u.leave(__yog_end)?;
463 ::std::option::Option::Some(#ctor { #(#inits),* })
464 }
465}
466
467fn expand_named(named: &syn::FieldsNamed, shape_name: &str, doc: &str) -> syn::Result<Tokens> {
469 let krate = krate();
470 let fields = named_fields(named)?;
471 let shapes = field_shapes(&fields);
472 let read = read_fields(&fields, quote! { Self });
473 let idents: Vec<_> = fields.iter().map(|f| &f.ident).collect();
474 let write = write_fields(&fields);
475 let encode = encode_fields(&fields);
476 let decode = decode_fields(&fields, quote! { Self }).map(|body| {
477 quote! {
478 fn decode(__yog_d: &mut #krate::binary::Decoder<'_>) -> ::std::option::Option<Self> {
479 #body
480 }
481 }
482 });
483 let pack = pack_fields(&fields);
484 let unpack = unpack_fields(&fields, quote! { Self });
485 Ok(quote! {
486 #[allow(unused_variables)]
488 fn encode(&self, __yog_w: &mut #krate::binary::Writer) {
489 let Self { #(#idents),* } = self;
490 #encode
491 }
492 #decode
493 #[allow(unused_variables)]
495 fn pack(&self, __yog_p: &mut #krate::frame::Packer) {
496 let Self { #(#idents),* } = self;
497 #pack
498 }
499 fn unpack(__yog_u: &mut #krate::frame::Unpacker<'_>) -> ::std::option::Option<Self> {
500 #unpack
501 }
502 fn shape() -> #krate::Shape {
503 #krate::Shape::Struct(#krate::schema::StructShape { name: #shape_name, doc: #doc, fields: #shapes })
504 }
505 fn read(v: &#krate::ValueRef<'_>, cx: &mut #krate::Reader) -> ::std::option::Option<Self> {
506 let map = cx.map(v)?;
507 #read
508 }
509 #[allow(unused_variables)]
511 fn write(&self) -> #krate::Value {
512 let Self { #(#idents),* } = self;
513 let mut m = #krate::Map::new();
514 #write
515 #krate::Value::Map(m)
516 }
517 })
518}
519
520fn view_items(input: &DeriveInput, fields: &[NamedField]) -> syn::Result<Tokens> {
524 let krate = krate();
525 let (name, vis) = (&input.ident, &input.vis);
526 if !input.generics.params.is_empty() {
527 return Err(syn::Error::new_spanned(
528 name,
529 "Schema: `view` is for a struct without generics",
530 ));
531 }
532 let view = quote::format_ident!("{}View", name);
533 let shown: Vec<&NamedField> = fields.iter().filter(|f| !f.skip).collect();
534 let types = shown.iter().map(|f| {
535 let (ident, ty) = (&f.ident, &f.ty);
536 if f.codec {
537 quote! { pub #ident: #ty, }
538 } else {
539 quote! { pub #ident: <#ty as #krate::frame::View<'a>>::View, }
540 }
541 });
542 let reads = shown.iter().map(|f| {
543 let (ident, ty, w) = (&f.ident, &f.ty, &f.with);
544 let read = if f.codec {
545 quote! { <#w as #krate::with::With<#ty>>::unpack(__yog_u)? }
546 } else {
547 quote! { <#ty as #krate::frame::View<'a>>::unpack_view(__yog_u)? }
548 };
549 quote! { let #ident = if __yog_u.more(__yog_end) { #read } else { return ::std::option::Option::None }; }
550 });
551 let idents = shown.iter().map(|f| &f.ident);
552 let doc =
553 format!("[`{name}`] viewed in a frame: strings borrowed, packed numbers read in place.");
554 Ok(quote! {
555 #[doc = #doc]
556 #vis struct #view<'a> {
557 #(#types)*
558 _frame: ::std::marker::PhantomData<&'a ()>,
559 }
560
561 impl<'a> #krate::frame::View<'a> for #name {
562 type View = #view<'a>;
563 type ListView = #krate::frame::List<'a, Self>;
564 fn unpack_view(__yog_u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<#view<'a>> {
565 let __yog_end = __yog_u.begin()?;
566 #(#reads)*
567 __yog_u.leave(__yog_end)?;
568 ::std::option::Option::Some(#view { #(#idents,)* _frame: ::std::marker::PhantomData })
569 }
570 fn unpack_list_view(__yog_u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<Self::ListView> {
571 #krate::frame::list(__yog_u)
572 }
573 }
574 })
575}
576
577fn expand(input: &DeriveInput) -> syn::Result<Tokens> {
578 let krate = krate();
579 let name = &input.ident;
580 let (impl_g, ty_g, where_g) = input.generics.split_for_impl();
581 let top = attrs(&input.attrs)?;
582 let shape_name = top.rename.clone().unwrap_or_else(|| name.to_string());
583 let doc = docs(&input.attrs);
584 let body = match &input.data {
585 Data::Struct(s) => match &s.fields {
586 Fields::Named(named) => expand_named(named, &shape_name, &doc)?,
587 Fields::Unnamed(u) if u.unnamed.len() == 1 => expand_newtype(&u.unnamed[0].ty),
588 _ => {
589 return Err(syn::Error::new_spanned(
590 name,
591 "Schema: a struct needs named fields or exactly one unnamed field",
592 ));
593 }
594 },
595 Data::Enum(e) => enums::expand_enum(e, &top, &shape_name, &doc)?,
596 Data::Union(_) => {
597 return Err(syn::Error::new_spanned(
598 name,
599 "Schema: unions aren't supported",
600 ));
601 }
602 };
603 let view = view_of(input, &top)?;
604 let fields = fields_of(input)?;
605 Ok(quote! {
606 impl #impl_g #krate::Schema for #name #ty_g #where_g {
607 #body
608 }
609 #view
610 #fields
611 })
612}
613
614fn fields_of(input: &DeriveInput) -> syn::Result<Tokens> {
616 let krate = krate();
617 let name = &input.ident;
618 let (impl_g, ty_g, where_g) = input.generics.split_for_impl();
619 let Data::Struct(syn::DataStruct {
620 fields: Fields::Named(named),
621 ..
622 }) = &input.data
623 else {
624 return Ok(quote! {});
625 };
626 let pairs = named_fields(named)?.into_iter().map(|f| {
627 let ident = f.ident.to_string();
628 let ident = ident.trim_start_matches("r#");
629 let key = &f.key;
630 quote! { (#ident, #key) }
631 });
632 Ok(quote! {
633 impl #impl_g #krate::schema::Fields for #name #ty_g #where_g {
634 const FIELDS: &'static [(&'static str, &'static str)] = &[#(#pairs),*];
635 }
636 })
637}
638
639fn expand_newtype(ty: &Type) -> Tokens {
641 let krate = krate();
642 quote! {
643 fn shape() -> #krate::Shape { <#ty as #krate::Schema>::shape() }
644 fn read(v: &#krate::ValueRef<'_>, cx: &mut #krate::Reader) -> ::std::option::Option<Self> {
645 <#ty as #krate::Schema>::read(v, cx).map(Self)
646 }
647 fn write(&self) -> #krate::Value { #krate::Schema::write(&self.0) }
648 fn encode(&self, w: &mut #krate::binary::Writer) { #krate::Schema::encode(&self.0, w) }
649 fn decode(d: &mut #krate::binary::Decoder<'_>) -> ::std::option::Option<Self> {
650 <#ty as #krate::Schema>::decode(d).map(Self)
651 }
652 fn pack(&self, p: &mut #krate::frame::Packer) { #krate::Schema::pack(&self.0, p) }
653 fn unpack(u: &mut #krate::frame::Unpacker<'_>) -> ::std::option::Option<Self> {
654 <#ty as #krate::Schema>::unpack(u).map(Self)
655 }
656 fn absent() -> ::std::option::Option<Self> { <#ty as #krate::Schema>::absent().map(Self) }
657 fn skip(&self) -> bool { #krate::Schema::skip(&self.0) }
658 }
659}
660
661fn view_of(input: &DeriveInput, top: &Attrs) -> syn::Result<Tokens> {
663 let krate = krate();
664 let name = &input.ident;
665 Ok(match (&input.data, top.view) {
666 (
667 Data::Struct(syn::DataStruct {
668 fields: Fields::Named(named),
669 ..
670 }),
671 true,
672 ) => view_items(input, &named_fields(named)?)?,
673 (_, true) => {
674 return Err(syn::Error::new_spanned(
675 name,
676 "Schema: `view` is for a struct with named fields",
677 ));
678 }
679 (Data::Enum(e), false)
680 if top.tag.is_none()
681 && input.generics.params.is_empty()
682 && e.variants.iter().all(|v| matches!(v.fields, Fields::Unit)) =>
683 {
684 quote! {
685 impl<'a> #krate::frame::View<'a> for #name {
686 type View = Self;
687 type ListView = ::std::vec::Vec<Self>;
688 fn unpack_view(u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<Self> {
689 <Self as #krate::Schema>::unpack(u)
690 }
691 fn unpack_list_view(u: &mut #krate::frame::Unpacker<'a>) -> ::std::option::Option<::std::vec::Vec<Self>> {
692 <Self as #krate::Schema>::unpack_list(u)
693 }
694 }
695 }
696 }
697 _ => quote! {},
698 })
699}