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