1#![deny(missing_docs)]
52
53mod parse;
54
55use proc_macro::TokenStream;
56
57use parse::{Field, Struct};
58
59#[proc_macro_derive(Yo, attributes(yo))]
63pub fn yo(input: TokenStream) -> TokenStream {
64 match parse::parse(input).and_then(emit) {
65 Ok(out) => out,
66 Err(why) => complain(&why),
67 }
68}
69
70fn complain(why: &str) -> TokenStream {
72 format!("::core::compile_error!{{ {why:?} }}")
73 .parse()
74 .expect("a compile_error with a string in it")
75}
76
77fn emit(s: Struct) -> Result<TokenStream, String> {
78 let name = &s.name;
79 let mut out = String::new();
80
81 out.push_str(&shape(&s));
82 out.push_str(&field(&s));
83 out.push_str(&indexed(&s));
84 if let Some(id) = the_id(&s)? {
85 out.push_str(&document(&s, id));
86 }
87 out.push_str(&paths(&s));
88
89 out.parse().map_err(|e| {
90 format!("Yo wrote something the compiler would not take for {name}, which is a bug in the derive: {e}")
91 })
92}
93
94fn the_id(s: &Struct) -> Result<Option<&Field>, String> {
96 let mut marked = s.fields.iter().filter(|f| f.id);
97 let Some(first) = marked.next() else {
98 return Ok(None);
99 };
100 if let Some(second) = marked.next() {
101 return Err(format!(
102 "{} marks both {} and {} as its id, and a document is stored under one",
103 s.name, first.label, second.label
104 ));
105 }
106 Ok(Some(first))
107}
108
109fn shape(s: &Struct) -> String {
110 let mut fields = String::new();
111 for f in &s.fields {
112 let (label, ty) = (&f.label, &f.ty);
113 fields.push_str(&format!("({label:?}, <{ty} as ::yo::Shape>::describe),"));
114 }
115 let (name, label) = (&s.name, &s.name);
116 format!(
117 "#[automatically_derived]
118impl ::yo::Shape for {name} {{
119 fn describe(d: &mut ::yo::Desc) {{
120 d.strukt({label:?}, &[{fields}]);
121 }}
122}}
123"
124 )
125}
126
127fn field(s: &Struct) -> String {
128 let mut write = String::new();
129 let mut read = String::new();
130 for f in &s.fields {
131 let (name, label) = (&f.name, &f.label);
132 write.push_str(&format!(
133 "b.key({label:?}.as_bytes())?; ::yo::doc::Field::write(&self.{name}, b)?;"
134 ));
135 read.push_str(&format!("{name}: ::yo::doc::at(d, {label:?})?,"));
136 }
137 let name = &s.name;
138 format!(
139 "#[automatically_derived]
140impl ::yo::doc::Field for {name} {{
141 fn write(&self, b: &mut ::yo::doc::Builder) -> ::yo::Result<()> {{
142 b.begin_object()?;
143 {write}
144 b.end_object()
145 }}
146
147 fn read(d: ::yo::doc::Doc<'_>) -> ::yo::Result<{name}> {{
148 ::yo::doc::expect_object(d, {name:?})?;
149 Ok({name} {{ {read} }})
150 }}
151}}
152"
153 )
154}
155
156fn indexed(s: &Struct) -> String {
160 let mut indexes = String::new();
161 for f in &s.fields {
162 if let Some(kind) = f.kind {
163 let path = format!("$.{}", f.label);
164 indexes.push_str(&format!("({path:?}, ::yo::doc::IndexKind::{kind}),"));
165 }
166 }
167 let name = &s.name;
168 format!(
169 "#[automatically_derived]
170impl ::yo::doc::Indexed for {name} {{
171 const INDEXES: &'static [(&'static str, ::yo::doc::IndexKind)] = &[{indexes}];
172}}
173"
174 )
175}
176
177fn document(s: &Struct, id: &Field) -> String {
178 let (name, ty, at) = (&s.name, &id.ty, &id.name);
179 format!(
180 "#[automatically_derived]
181impl ::yo::doc::Document for {name} {{
182 type Id = {ty};
183
184 fn id(&self) -> &{ty} {{
185 &self.{at}
186 }}
187}}
188"
189 )
190}
191
192fn paths(s: &Struct) -> String {
195 let mut consts = String::new();
196 for f in &s.fields {
197 let Some(kind) = f.kind else { continue };
198 let asked = match (kind, &f.elem) {
201 ("Array", Some(elem)) => elem,
202 _ => &f.ty,
203 };
204 let (upper, label, path) = (f.label.to_uppercase(), &f.label, format!("$.{}", f.label));
205 let name = &s.name;
206 let held = if kind == "Ordered" {
210 format!("::yo::doc::Ordered<{name}, {asked}>")
211 } else {
212 format!("::yo::doc::Path<{name}, {asked}>")
213 };
214 let built = if kind == "Ordered" {
215 format!("::yo::doc::Ordered::new({path:?})")
216 } else {
217 format!("::yo::doc::Path::new({path:?}, ::yo::doc::IndexKind::{kind})")
218 };
219 consts.push_str(&format!(
220 " /// The `{path}` path, which is indexed for {kind} and named
221 /// `{label}` on this type.
222 pub const {upper}: {held} = {built};
223"
224 ));
225 }
226 if consts.is_empty() {
227 return String::new();
228 }
229 let name = &s.name;
230 format!("impl {name} {{\n{consts}}}\n")
231}