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