1mod events;
7mod frames;
8mod mirrors;
9mod services;
10mod types;
11mod views;
12mod words;
13
14use std::fmt::Write;
15
16use yog_codegen::{Blocks, Header, Identifiers, Regenerate, camel, pascal};
17use yog_codegen_java::Java;
18use yog_data::Shape;
19use yog_data::schema::StructShape;
20
21use types::{TYPES, from_data, java_type, nullable, same, to_data};
22
23pub(super) const HEADER: Header = Header {
25 tool: "yog-event",
26 source: "Yog-Mod-Loader/rust/crates/yog-event",
27 regenerate: Regenerate::Command("cargo run -p yog-event --bin yog-event-export"),
28};
29
30const IMPORTS: &[(&str, &str)] = &[
33 ("YogBus.", "dev.yog.YogBus"),
34 ("YogData.", "dev.yog.data.YogData"),
35 ("YogFrame.", "dev.yog.data.YogFrame"),
36 ("YogMessage", "dev.yog.YogMessage"),
37];
38
39const INDENT: &str = Java::INDENT;
41
42fn field(snake: &str) -> String {
44 Java::ident(&camel(snake))
45}
46
47pub(super) fn class_name(segment: &str) -> String {
49 pascal(segment)
50}
51
52#[derive(Default)]
54struct Out {
55 text: String,
56 records: Vec<StructShape>,
57 members: String,
59}
60
61fn record(o: &mut Out, pad: &str, name: &str, s: &StructShape, body: &str) {
64 let Out {
65 text: out, records, ..
66 } = o;
67 let params: Vec<String> = s
68 .fields
69 .iter()
70 .map(|f| format!("{} {}", java_type(&(f.shape)(), records), field(f.name)))
71 .collect();
72 if !s.doc.is_empty() {
73 let _ = writeln!(out, "\n{pad}/** {} */", s.doc);
74 }
75 let _ = writeln!(out, "{pad}public record {name}({}) {{", params.join(", "));
76 let _ = writeln!(
77 out,
78 "{pad} public java.util.Map<String, Object> toData() {{\n{pad} java.util.Map<String, Object> m = new java.util.LinkedHashMap<>();"
79 );
80 for f in &s.fields {
81 let shape = (f.shape)();
82 let field = field(f.name);
83 let line = match nullable(&shape) {
84 Some(inner) => format!(
85 "if ({field} != null) m.put(\"{}\", {});",
86 f.name,
87 to_data(&inner, &field, 0)
88 ),
89 None => format!("m.put(\"{}\", {});", f.name, to_data(&shape, &field, 0)),
90 };
91 let _ = writeln!(out, "{pad} {line}");
92 }
93 let _ = writeln!(out, "{pad} return m;\n{pad} }}\n");
94 let args: Vec<String> = s
95 .fields
96 .iter()
97 .map(|f| from_data(&(f.shape)(), &format!("m.get(\"{}\")", f.name), 0))
98 .collect();
99 let _ = writeln!(
100 out,
101 "{pad} public static {name} fromData(Object data) {{\n{pad} java.util.Map<String, Object> m = YogData.map(data);\n{pad} return new {name}({});\n{pad} }}\n",
102 args.join(", ")
103 );
104 let _ = write!(
105 out,
106 "{pad} public static {name} fromBytes(byte[] bytes) {{\n{pad} return fromData(YogData.decode(bytes));\n{pad} }}"
107 );
108 out.push_str(&frames::codec(name, s, pad));
109 out.push_str(&views::view(name, s, pad));
110 let body = body.replace('\n', &format!("\n{pad}"));
111 let _ = writeln!(out, "{body}\n{pad}}}");
112}
113
114fn name(address: &str) -> String {
117 yog_ids::export::java_ref(yog_ids::export::JAVA_CLASS, address)
118 .unwrap_or_else(|| format!("{address:?}"))
119}
120
121fn target(address: &str, key: Option<&str>, constant: &str) -> String {
124 match key {
125 Some(k) => format!("YogBus.keyed({}, {})", name(address), field(k)),
126 None => format!("Address.{constant}"),
127 }
128}
129
130fn listen_target(address: &str, key: Option<&str>, constant: &str) -> String {
133 match key {
134 Some(_) => format!("YogBus.keyed({}, key)", name(address)),
135 None => format!("Address.{constant}"),
136 }
137}
138
139fn key_param(key: Option<&str>) -> &'static str {
141 if key.is_some() { "String key, " } else { "" }
142}
143
144struct Entry {
146 path: &'static str,
147 name: &'static str,
148 shape: Shape,
149 body: String,
150}
151
152fn close(out: &mut String, open: &mut Vec<String>, keep: usize) {
154 while open.len() > keep {
155 open.pop();
156 let _ = writeln!(out, "{}}}", INDENT.repeat(open.len() + 1));
157 }
158}
159
160fn file(
162 package: &str,
163 class: &str,
164 doc: &str,
165 mut o: Out,
166 entries: &[Entry],
167) -> (String, Vec<StructShape>) {
168 let _ = writeln!(
169 o.text,
170 "\n/** {doc} */\npublic final class {class} {{\n private {class}() {{}}"
171 );
172 let members = std::mem::take(&mut o.members);
173 o.text.push_str(&members);
174 let mut open: Vec<String> = Vec::new();
176 let mut opened: Vec<String> = Vec::new();
177 for e in entries {
178 let path: Vec<&str> = e.path.split('/').collect();
179 let common = open.iter().zip(&path).take_while(|(a, b)| a == *b).count();
180 close(&mut o.text, &mut open, common);
181 for (depth, seg) in path.iter().enumerate().skip(common) {
182 let whole = path[..=depth].join("/");
184 assert!(
185 !opened.contains(&whole),
186 "the entries of `{whole}` aren't together"
187 );
188 opened.push(whole);
189 let pad = INDENT.repeat(open.len() + 1);
190 let class = class_name(seg);
191 let _ = writeln!(
192 o.text,
193 "\n{pad}/** `{seg}`. */\n{pad}public static final class {class} {{\n{pad} private {class}() {{}}"
194 );
195 open.push((*seg).to_owned());
196 }
197 let Shape::Struct(s) = &e.shape else { continue };
198 assert!(
200 open.iter().all(|seg| class_name(seg) != e.name),
201 "{} is named like the class around it ({}): rename it",
202 e.name,
203 e.path
204 );
205 record(&mut o, &INDENT.repeat(open.len() + 1), e.name, s, &e.body);
206 }
207 close(&mut o.text, &mut open, 0);
208 finish(package, o)
209}
210
211fn types_file(package: &str, records: Vec<StructShape>) -> String {
213 let mut o = Out {
214 records,
215 ..Out::default()
216 };
217 let _ = writeln!(
218 o.text,
219 "\n/** The records the bus's messages share. */\npublic final class {TYPES} {{\n private {TYPES}() {{}}"
220 );
221 let mut done = 0;
222 while done < o.records.len() {
223 let s = o.records[done].clone();
224 record(&mut o, INDENT, s.name, &s, "");
225 done += 1;
226 }
227 unique(&o.records);
228 o.text.push_str(&words::classes(&o.records, INDENT));
229 finish(package, o).0
230}
231
232fn finish(package: &str, mut o: Out) -> (String, Vec<StructShape>) {
234 o.text.push_str("}\n");
235 let out = o.text;
236 let names = format!(
237 "{}.{}",
238 yog_ids::export::JAVA_PACKAGE,
239 yog_ids::export::JAVA_CLASS
240 );
241 let mut imports: Vec<String> = IMPORTS
242 .iter()
243 .filter(|(used, _)| out.contains(used))
244 .map(|(_, class)| (*class).to_owned())
245 .collect();
246 imports.push(names);
247 imports.sort();
248 let imports: String = imports
249 .iter()
250 .map(|class| format!("import {class};\n"))
251 .collect();
252 let text = format!(
253 "{}package {package};\n\n{imports}{out}",
254 HEADER.comment::<Java>()
255 );
256 (
257 text.lines()
258 .map(str::trim_end)
259 .collect::<Vec<_>>()
260 .join("\n")
261 + "\n",
262 o.records,
263 )
264}
265
266fn unique(records: &[StructShape]) {
269 for (i, a) in records.iter().enumerate() {
270 for b in &records[i + 1..] {
271 assert!(
272 a.name != b.name || same(a, b),
273 "two payload structs are named {}: rename one",
274 a.name
275 );
276 }
277 }
278}
279
280const PAYLOADS: &[fn() -> Shape] = &[
283 <crate::entity::Hanging as yog_data::Schema>::shape,
284 <crate::content::Content as yog_data::Schema>::shape,
286 <crate::mirror::Write as yog_data::Schema>::shape,
288 <crate::mirror::BlockScope as yog_data::Schema>::shape,
290 <crate::nanite::Mappings as yog_data::Schema>::shape,
292 <crate::nanite::Nanites as yog_data::Schema>::shape,
294];
295
296pub fn files(package: &str) -> [(&'static str, String); 4] {
298 let (events, mut records) = events::events(package);
299 let (services, more) = services::services(package);
300 for r in more {
301 if !records.iter().any(|have| same(have, &r)) {
302 records.push(r);
303 }
304 }
305 for payload in PAYLOADS {
306 let Shape::Struct(r) = payload() else {
307 continue;
308 };
309 if !records.iter().any(|have| same(have, &r)) {
310 records.push(r);
311 }
312 }
313 unique(&records);
314 [
315 (events::CLASS, events),
316 (services::CLASS, services),
317 (TYPES, types_file(package, records)),
318 (mirrors::CLASS, mirrors::file(package)),
319 ]
320}
321
322#[cfg(test)]
323mod tests {
324 use yog_data::{Schema, Shape};
325
326 use super::unique;
327
328 mod a {
329 #[derive(yog_data::Schema)]
330 pub struct Declare {
331 pub name: String,
332 }
333 }
334
335 mod b {
336 #[derive(yog_data::Schema)]
337 pub struct Declare {
338 pub label: String,
339 }
340 }
341
342 fn shape<T: Schema>() -> yog_data::schema::StructShape {
343 let Shape::Struct(s) = T::shape() else {
344 unreachable!("a derived struct")
345 };
346 s
347 }
348
349 #[test]
350 #[should_panic(expected = "two payload structs are named Declare")]
351 fn two_structs_of_one_name_are_refused() {
352 unique(&[shape::<a::Declare>(), shape::<b::Declare>()]);
353 }
354
355 #[test]
356 fn one_struct_seen_twice_is_fine() {
357 unique(&[shape::<a::Declare>(), shape::<a::Declare>()]);
358 }
359}