1use std::collections::BTreeSet;
20use std::fmt::Write as _;
21
22use crate::error::{Error, Result};
23use crate::schema::{Schema, StructDef, Type, TypeDef};
24
25macro_rules! w {
26 ($out:expr, $($arg:tt)*) => { let _ = writeln!($out, $($arg)*); };
27}
28
29pub fn generate_rust(schema: &Schema) -> Result<String> {
33 check_supported(schema)?;
34 let mut out = String::new();
35 w!(out, "// @generated by verit::codegen — do not edit.");
36 w!(out, "// schema id: {:#034x}", schema.id());
37 w!(out, "#![allow(dead_code, unused_imports, clippy::all)]");
38 w!(out,);
39 w!(out, "use verit::{{wire, Budget, Error, ListReader, Message, Ref, Resolver, Result, SchemaMode, StructReader}};");
40 w!(out,);
41 w!(out, "pub const SCHEMA_ID: u128 = {:#034x};", schema.id());
42 let bytes: Vec<String> = schema
43 .canonical_bytes()
44 .iter()
45 .map(|b| b.to_string())
46 .collect();
47 w!(
48 out,
49 "pub const SCHEMA_BYTES: &[u8] = &[{}];",
50 bytes.join(", ")
51 );
52 w!(out,);
53 w!(
54 out,
55 "/// The generated schema, decoded from its embedded canonical bytes."
56 );
57 w!(out, "pub fn schema() -> verit::Schema {{");
58 w!(
59 out,
60 " verit::Schema::from_canonical(SCHEMA_BYTES).expect(\"embedded canonical schema\")"
61 );
62 w!(out, "}}");
63 w!(out,);
64 w!(out, "fn type_err(expected: &str, got: &str) -> Error {{");
65 w!(
66 out,
67 " Error::TypeMismatch {{ expected: expected.into(), got: got.into() }}"
68 );
69 w!(out, "}}");
70
71 let mut scalar_lists: BTreeSet<&'static str> = BTreeSet::new();
73 let mut str_list = false;
74 for idx in 0..schema.type_count() {
75 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
76 for f in &sd.fields {
77 if let Type::List(elem) = &f.ty {
78 match elem.as_ref() {
79 Type::String => str_list = true,
80 Type::Struct(_) => {}
81 other => {
82 if let Some(info) = scalar_info(other) {
83 scalar_lists.insert(info.rust);
84 }
85 }
86 }
87 }
88 }
89 }
90 }
91 for rust in &scalar_lists {
92 emit_scalar_list(&mut out, rust);
93 }
94 if str_list {
95 emit_str_list(&mut out);
96 }
97
98 for idx in 0..schema.type_count() {
99 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
100 emit_struct_reader(&mut out, schema, idx, sd)?;
101 emit_struct_writer(&mut out, schema, idx, sd)?;
102 }
103 }
104 Ok(out)
105}
106
107fn type_has_map(ty: &Type) -> bool {
115 match ty {
116 Type::Map(_, _) | Type::Union(_) => true,
117 Type::List(e) => type_has_map(e),
118 _ => false,
119 }
120}
121
122fn reject_maps(schema: &Schema) -> Result<()> {
125 for idx in 0..schema.type_count() {
126 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
127 for f in &sd.fields {
128 if type_has_map(&f.ty) {
129 return Err(Error::BadSchema(format!(
130 "codegen does not yet support map/union types (field {} of {}); \
131 read them through the dynamic API",
132 f.name, sd.name
133 )));
134 }
135 }
136 }
137 }
138 Ok(())
139}
140
141fn check_supported(schema: &Schema) -> Result<()> {
142 reject_maps(schema)?;
143 for idx in 0..schema.type_count() {
144 let td = schema.type_def(idx).unwrap();
145 check_ident(td.name())?;
146 if let TypeDef::Struct(sd) = td {
147 if sd.is_packed() {
148 return Err(Error::BadSchema(format!(
149 "codegen v1 does not support packed struct {}: packed layout \
150 has per-message dynamic offsets — use the dynamic reader API",
151 sd.name
152 )));
153 }
154 for f in &sd.fields {
155 check_ident(&f.name)?;
156 if let Type::List(elem) = &f.ty {
157 match elem.as_ref() {
158 Type::Bytes | Type::Enum(_) | Type::List(_) => {
159 return Err(Error::BadSchema(format!(
160 "codegen v1 does not support field {} of {}: \
161 list<bytes>, list<enum>, and nested lists need the dynamic API",
162 f.name, sd.name
163 )))
164 }
165 _ => {}
166 }
167 }
168 }
169 }
170 }
171 Ok(())
172}
173
174const KEYWORDS: &[&str] = &[
175 "as", "async", "await", "box", "break", "const", "continue", "crate", "dyn", "else", "enum",
176 "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move",
177 "mut", "pub", "ref", "return", "self", "static", "struct", "super", "trait", "true", "type",
178 "unsafe", "use", "where", "while",
179];
180
181fn check_ident(name: &str) -> Result<()> {
182 let mut chars = name.chars();
183 let ok = matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_')
184 && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
185 && !KEYWORDS.contains(&name);
186 if ok {
187 Ok(())
188 } else {
189 Err(Error::BadSchema(format!(
190 "name {name:?} is not a usable Rust identifier for codegen"
191 )))
192 }
193}
194
195fn snake(name: &str) -> String {
196 let mut s = String::with_capacity(name.len() + 4);
197 for (i, c) in name.chars().enumerate() {
198 if c.is_ascii_uppercase() {
199 if i > 0 {
200 s.push('_');
201 }
202 s.push(c.to_ascii_lowercase());
203 } else {
204 s.push(c);
205 }
206 }
207 s
208}
209
210struct ScalarInfo {
215 rust: &'static str,
216 read: &'static str,
217 put: &'static str,
218 push_slice: &'static str,
219 dyn_get: &'static str,
220 size: u32,
221 align: u32,
222}
223
224fn scalar_info(ty: &Type) -> Option<ScalarInfo> {
225 macro_rules! s {
226 ($rust:literal, $suffix:literal, $n:literal) => {
227 Some(ScalarInfo {
228 rust: $rust,
229 read: concat!("read_", $suffix),
230 put: concat!("put_", $suffix),
231 push_slice: concat!("push_", $suffix, "_slice"),
232 dyn_get: concat!("get_", $suffix),
233 size: $n,
234 align: $n,
235 })
236 };
237 }
238 match ty {
239 Type::Bool => s!("bool", "bool", 1),
240 Type::U8 => s!("u8", "u8", 1),
241 Type::U16 => s!("u16", "u16", 2),
242 Type::U32 => s!("u32", "u32", 4),
243 Type::U64 => s!("u64", "u64", 8),
244 Type::I8 => s!("i8", "i8", 1),
245 Type::I16 => s!("i16", "i16", 2),
246 Type::I32 => s!("i32", "i32", 4),
247 Type::I64 => s!("i64", "i64", 8),
248 Type::F32 => s!("f32", "f32", 4),
249 Type::F64 => s!("f64", "f64", 8),
250 Type::Enum(_) => Some(ScalarInfo {
251 rust: "u32",
252 read: "read_u32",
253 put: "put_u32",
254 push_slice: "push_u32_slice",
255 dyn_get: "get_enum",
256 size: 4,
257 align: 4,
258 }),
259 _ => None,
260 }
261}
262
263fn list_ref_type(schema: &Schema, elem: &Type) -> String {
264 match elem {
265 Type::Struct(i) => format!("{}List", schema.type_name(*i)),
266 Type::String => "StrList".to_string(),
267 other => {
268 let info = scalar_info(other).expect("checked supported");
269 format!("{}List", pascal(info.rust))
270 }
271 }
272}
273
274fn pascal(s: &str) -> String {
275 let mut c = s.chars();
276 match c.next() {
277 Some(first) => first.to_ascii_uppercase().to_string() + c.as_str(),
278 None => String::new(),
279 }
280}
281
282fn args_needs_lifetime(schema: &Schema, idx: u16) -> bool {
284 let sd = match schema.type_def(idx) {
285 Some(TypeDef::Struct(s)) => s,
286 _ => return false,
287 };
288 sd.fields.iter().any(|f| scalar_info(&f.ty).is_none())
289}
290
291fn emit_scalar_list(out: &mut String, rust: &'static str) {
296 let name = format!("{}List", pascal(rust));
297 let info = scalar_info(&rust_to_type(rust)).unwrap();
298 let variant = pascal(rust);
299 w!(out,);
300 w!(out, "#[derive(Clone)]");
301 w!(out, "pub enum {name}<'b, 'r> {{");
302 w!(
303 out,
304 " // On a bounded read the whole element region was charged when the"
305 );
306 w!(
307 out,
308 " // list was opened, so element access here is charge-free."
309 );
310 w!(out, " Fast {{ buf: &'b [u8], elems: u64, count: u32 }},");
311 w!(out, " Dynamic(ListReader<'b, 'r>),");
312 w!(out, "}}");
313 w!(out,);
314 w!(out, "impl<'b, 'r> {name}<'b, 'r> {{");
315 w!(out, " pub fn len(&self) -> u32 {{");
316 w!(out, " match self {{ Self::Fast {{ count, .. }} => *count, Self::Dynamic(l) => l.len() }}");
317 w!(out, " }}");
318 w!(
319 out,
320 " pub fn is_empty(&self) -> bool {{ self.len() == 0 }}"
321 );
322 w!(out, " pub fn get(&self, i: u32) -> Result<{rust}> {{");
323 w!(out, " match self {{");
324 w!(out, " Self::Fast {{ buf, elems, count }} => {{");
325 w!(
326 out,
327 " if i >= *count {{ return Err(Error::IndexOutOfBounds); }}"
328 );
329 w!(
330 out,
331 " wire::{}(*buf, *elems + i as u64 * {})",
332 info.read,
333 info.size
334 );
335 w!(out, " }}");
336 w!(out, " Self::Dynamic(l) => match l.get(i)? {{");
337 if rust == "u32" {
338 w!(out, " Ref::U32(v) => Ok(v),");
340 w!(out, " Ref::Enum(v) => Ok(v),");
341 } else {
342 w!(out, " Ref::{variant}(v) => Ok(v),");
343 }
344 w!(
345 out,
346 " other => Err(type_err(\"{rust}\", other.kind())),"
347 );
348 w!(out, " }},");
349 w!(out, " }}");
350 w!(out, " }}");
351 let decode = if rust == "bool" {
356 "|c| c[0] != 0".to_string()
357 } else {
358 format!("|c| {rust}::from_le_bytes(c.try_into().unwrap())")
359 };
360 w!(
361 out,
362 " pub fn values(&self) -> Option<impl Iterator<Item = {rust}> + 'b> {{"
363 );
364 w!(out, " match self {{");
365 w!(out, " Self::Fast {{ buf, elems, count }} => {{");
366 w!(out, " let start = *elems as usize;");
367 w!(out, " (*count as usize)");
368 w!(out, " .checked_mul({})", info.size);
369 w!(
370 out,
371 " .and_then(|n| start.checked_add(n))"
372 );
373 w!(
374 out,
375 " .and_then(|end| buf.get(start..end))"
376 );
377 w!(
378 out,
379 " .map(|region| region.chunks_exact({}).map({decode}))",
380 info.size
381 );
382 w!(out, " }}");
383 w!(out, " Self::Dynamic(_) => None,");
384 w!(out, " }}");
385 w!(out, " }}");
386 w!(
387 out,
388 " /// General fallible iterator (fast + dynamic). Prefer `values()`"
389 );
390 w!(out, " /// for a hot scan on the identity path.");
391 w!(
392 out,
393 " pub fn iter(&self) -> impl Iterator<Item = Result<{rust}>> + '_ {{"
394 );
395 w!(out, " (0..self.len()).map(move |i| self.get(i))");
396 w!(out, " }}");
397 w!(out, "}}");
398}
399
400fn rust_to_type(rust: &str) -> Type {
401 match rust {
402 "bool" => Type::Bool,
403 "u8" => Type::U8,
404 "u16" => Type::U16,
405 "u32" => Type::U32,
406 "u64" => Type::U64,
407 "i8" => Type::I8,
408 "i16" => Type::I16,
409 "i32" => Type::I32,
410 "i64" => Type::I64,
411 "f32" => Type::F32,
412 "f64" => Type::F64,
413 _ => unreachable!("scalar rust type"),
414 }
415}
416
417fn emit_str_list(out: &mut String) {
418 w!(out,);
419 w!(out, "#[derive(Clone)]");
420 w!(out, "pub enum StrList<'b, 'r> {{");
421 w!(
422 out,
423 " // String elements follow per-element offsets to payloads, so each"
424 );
425 w!(
426 out,
427 " // `get` charges its payload against the budget on a bounded read"
428 );
429 w!(
430 out,
431 " // (aliased element offsets recharge on every visit)."
432 );
433 w!(
434 out,
435 " Fast {{ buf: &'b [u8], elems: u64, count: u32, budget: Option<&'r Budget> }},"
436 );
437 w!(out, " Dynamic(ListReader<'b, 'r>),");
438 w!(out, "}}");
439 w!(out,);
440 w!(out, "impl<'b, 'r> StrList<'b, 'r> {{");
441 w!(out, " pub fn len(&self) -> u32 {{");
442 w!(out, " match self {{ Self::Fast {{ count, .. }} => *count, Self::Dynamic(l) => l.len() }}");
443 w!(out, " }}");
444 w!(
445 out,
446 " pub fn is_empty(&self) -> bool {{ self.len() == 0 }}"
447 );
448 w!(out, " pub fn get(&self, i: u32) -> Result<&'b str> {{");
449 w!(out, " match self {{");
450 w!(
451 out,
452 " Self::Fast {{ buf, elems, count, budget }} => {{"
453 );
454 w!(
455 out,
456 " if i >= *count {{ return Err(Error::IndexOutOfBounds); }}"
457 );
458 w!(
459 out,
460 " wire::read_str_budgeted(*buf, *elems + i as u64 * 4, *budget)"
461 );
462 w!(out, " }}");
463 w!(out, " Self::Dynamic(l) => match l.get(i)? {{");
464 w!(out, " Ref::Str(v) => Ok(v),");
465 w!(
466 out,
467 " other => Err(type_err(\"string\", other.kind())),"
468 );
469 w!(out, " }},");
470 w!(out, " }}");
471 w!(out, " }}");
472 w!(out, "}}");
473}
474
475fn emit_struct_reader(out: &mut String, schema: &Schema, idx: u16, sd: &StructDef) -> Result<()> {
476 let name = &sd.name;
477 let lay = schema.layout_unchecked(idx).as_fixed();
478 let is_root = idx == schema.root_index();
479
480 let used_in_list = struct_used_in_list(schema, idx);
482
483 w!(out,);
484 w!(out, "#[derive(Clone)]");
485 w!(out, "pub enum {name}Ref<'b, 'r> {{");
486 w!(
487 out,
488 " // `budget` is Some on a bounded (untrusted-input) read: offset-follows"
489 );
490 w!(
491 out,
492 " // — string/bytes payloads, nested blocks, list regions — charge it, so"
493 );
494 w!(
495 out,
496 " // aliased offsets can't amplify work (the wire spec §5.2). None = trusted,"
497 );
498 w!(
499 out,
500 " // zero-cost. Reads within an already-charged block are charge-free."
501 );
502 w!(
503 out,
504 " Fast {{ buf: &'b [u8], base: u64, budget: Option<&'r Budget> }},"
505 );
506 w!(out, " Dynamic(StructReader<'b, 'r>),");
507 w!(out, "}}");
508 w!(out,);
509 w!(out, "impl<'b, 'r> {name}Ref<'b, 'r> {{");
510 if is_root {
511 w!(
512 out,
513 " /// Open the root struct. Takes the identity fast path (constant"
514 );
515 w!(
516 out,
517 " /// offsets, resolver untouched) when the message was written with"
518 );
519 w!(
520 out,
521 " /// exactly this generated schema; otherwise falls back to the"
522 );
523 w!(
524 out,
525 " /// resolver's access plans, keeping full schema evolution."
526 );
527 w!(
528 out,
529 " /// **Unbounded** (no traversal-work cap): for trusted input, or"
530 );
531 w!(
532 out,
533 " /// untrusted input behind [`Self::read_bounded`] / an upstream size cap."
534 );
535 w!(
536 out,
537 " pub fn read(msg: &Message<'b>, resolver: &'r Resolver) -> Result<Self> {{"
538 );
539 w!(out, " if msg.schema_id() == SCHEMA_ID {{");
540 w!(out, " Ok(Self::Fast {{ buf: msg.buffer(), base: msg.root_offset() as u64, budget: None }})");
541 w!(out, " }} else {{");
542 w!(out, " Ok(Self::Dynamic(msg.root(resolver)?))");
543 w!(out, " }}");
544 w!(out, " }}");
545 w!(
546 out,
547 " /// Like [`Self::read`], but every offset-follow charges `budget`, so a"
548 );
549 w!(
550 out,
551 " /// crafted offset-aliasing message trips `TraversalBudgetExceeded`"
552 );
553 w!(
554 out,
555 " /// instead of amplifying work (the wire spec §5.2). Fast-path getters stay"
556 );
557 w!(
558 out,
559 " /// constant-offset loads; the only added cost is one budget decrement"
560 );
561 w!(
562 out,
563 " /// per heap follow. Pair with `Message::suggested_budget()`."
564 );
565 w!(out, " pub fn read_bounded(");
566 w!(out, " msg: &Message<'b>,");
567 w!(out, " resolver: &'r Resolver,");
568 w!(out, " budget: &'r Budget,");
569 w!(out, " ) -> Result<Self> {{");
570 w!(out, " if msg.schema_id() == SCHEMA_ID {{");
571 w!(
572 out,
573 " budget.charge({})?; // root block",
574 lay.size
575 );
576 w!(out, " Ok(Self::Fast {{ buf: msg.buffer(), base: msg.root_offset() as u64, budget: Some(budget) }})");
577 w!(out, " }} else {{");
578 w!(
579 out,
580 " Ok(Self::Dynamic(msg.root_bounded(resolver, budget)?))"
581 );
582 w!(out, " }}");
583 w!(out, " }}");
584 }
585 for (pos, f) in sd.fields.iter().enumerate() {
586 let slot = lay.slots[pos];
587 let fname = &f.name;
588 let id = f.id;
589 let guard = if sd.is_dense() {
591 String::new()
592 } else {
593 format!(
594 "if wire::read_u8(*buf, *base + {})? & {} == 0 {{ return Ok(None); }}\n ",
595 pos / 8,
596 1u8 << (pos % 8)
597 )
598 };
599 match &f.ty {
600 Type::String => {
601 w!(
602 out,
603 " pub fn {fname}(&self) -> Result<Option<&'b str>> {{"
604 );
605 w!(out, " match self {{");
606 w!(out, " Self::Fast {{ buf, base, budget }} => {{");
607 w!(out, " {guard}Ok(Some(wire::read_str_budgeted(*buf, *base + {slot}, *budget)?))");
608 w!(out, " }}");
609 w!(out, " Self::Dynamic(r) => r.get_str({id}),");
610 w!(out, " }}");
611 w!(out, " }}");
612 }
613 Type::Bytes => {
614 w!(
615 out,
616 " pub fn {fname}(&self) -> Result<Option<&'b [u8]>> {{"
617 );
618 w!(out, " match self {{");
619 w!(out, " Self::Fast {{ buf, base, budget }} => {{");
620 w!(out, " {guard}Ok(Some(wire::read_bytes_budgeted(*buf, *base + {slot}, *budget)?))");
621 w!(out, " }}");
622 w!(out, " Self::Dynamic(r) => r.get_bytes({id}),");
623 w!(out, " }}");
624 w!(out, " }}");
625 }
626 Type::Struct(ci) => {
627 let cname = schema.type_name(*ci);
628 let csize = schema.layout_unchecked(*ci).as_fixed().size;
629 w!(
630 out,
631 " pub fn {fname}(&self) -> Result<Option<{cname}Ref<'b, 'r>>> {{"
632 );
633 w!(out, " match self {{");
634 w!(out, " Self::Fast {{ buf, base, budget }} => {{");
635 w!(out, " {guard}let off = wire::read_u32(*buf, *base + {slot})? as u64;");
636 w!(
637 out,
638 " wire::charge(*budget, {csize})?; // child block"
639 );
640 w!(out, " Ok(Some({cname}Ref::Fast {{ buf: *buf, base: off, budget: *budget }}))");
641 w!(out, " }}");
642 w!(out, " Self::Dynamic(r) => Ok(r.get_struct({id})?.map({cname}Ref::Dynamic)),");
643 w!(out, " }}");
644 w!(out, " }}");
645 }
646 Type::List(elem) => {
647 let lty = list_ref_type(schema, elem);
648 let (estride, ealign) = elem_stride_align(schema, elem);
649 let ctor_budget = match elem.as_ref() {
653 Type::String | Type::Struct(_) => ", budget: *budget",
654 _ => "",
655 };
656 w!(
657 out,
658 " pub fn {fname}(&self) -> Result<Option<{lty}<'b, 'r>>> {{"
659 );
660 w!(out, " match self {{");
661 w!(out, " Self::Fast {{ buf, base, budget }} => {{");
662 w!(out, " {guard}let (elems, count) = wire::list_header(*buf, *base + {slot}, {ealign})?;");
663 w!(
664 out,
665 " // Charge the element region once at open; element access"
666 );
667 w!(out, " // within it is then charge-free (a hostile count trips here).");
668 w!(
669 out,
670 " wire::charge(*budget, 4 + count as u64 * {estride})?;"
671 );
672 w!(out, " Ok(Some({lty}::Fast {{ buf: *buf, elems, count{ctor_budget} }}))");
673 w!(out, " }}");
674 w!(
675 out,
676 " Self::Dynamic(r) => Ok(r.get_list({id})?.map({lty}::Dynamic)),"
677 );
678 w!(out, " }}");
679 w!(out, " }}");
680 }
681 other => {
682 let info = scalar_info(other).expect("scalar");
683 let rust = info.rust;
684 let (read, dyn_get) = (info.read, info.dyn_get);
685 w!(
686 out,
687 " pub fn {fname}(&self) -> Result<Option<{rust}>> {{"
688 );
689 w!(out, " match self {{");
690 w!(out, " Self::Fast {{ buf, base, .. }} => {{");
691 w!(
692 out,
693 " {guard}Ok(Some(wire::{read}(*buf, *base + {slot})?))"
694 );
695 w!(out, " }}");
696 w!(out, " Self::Dynamic(r) => r.{dyn_get}({id}),");
697 w!(out, " }}");
698 w!(out, " }}");
699 }
700 }
701 }
702 w!(out, "}}");
703
704 if used_in_list {
705 let (stride, _) = struct_stride_align(schema, idx);
706 w!(out,);
707 w!(out, "#[derive(Clone)]");
708 w!(out, "pub enum {name}List<'b, 'r> {{");
709 w!(
710 out,
711 " // The element region was charged when the list was opened; the budget"
712 );
713 w!(
714 out,
715 " // rides along so element refs can charge *their* offset-follows."
716 );
717 w!(
718 out,
719 " Fast {{ buf: &'b [u8], elems: u64, count: u32, budget: Option<&'r Budget> }},"
720 );
721 w!(out, " Dynamic(ListReader<'b, 'r>),");
722 w!(out, "}}");
723 w!(out,);
724 w!(out, "impl<'b, 'r> {name}List<'b, 'r> {{");
725 w!(out, " pub fn len(&self) -> u32 {{");
726 w!(out, " match self {{ Self::Fast {{ count, .. }} => *count, Self::Dynamic(l) => l.len() }}");
727 w!(out, " }}");
728 w!(
729 out,
730 " pub fn is_empty(&self) -> bool {{ self.len() == 0 }}"
731 );
732 w!(
733 out,
734 " pub fn get(&self, i: u32) -> Result<{name}Ref<'b, 'r>> {{"
735 );
736 w!(out, " match self {{");
737 w!(
738 out,
739 " Self::Fast {{ buf, elems, count, budget }} => {{"
740 );
741 w!(
742 out,
743 " if i >= *count {{ return Err(Error::IndexOutOfBounds); }}"
744 );
745 w!(out, " Ok({name}Ref::Fast {{ buf: *buf, base: *elems + i as u64 * {stride}, budget: *budget }})");
746 w!(out, " }}");
747 w!(out, " Self::Dynamic(l) => match l.get(i)? {{");
748 w!(
749 out,
750 " Ref::Struct(s) => Ok({name}Ref::Dynamic(s)),"
751 );
752 w!(
753 out,
754 " other => Err(type_err(\"struct\", other.kind())),"
755 );
756 w!(out, " }},");
757 w!(out, " }}");
758 w!(out, " }}");
759
760 let all_fixed = sd.fields.iter().all(|f| scalar_info(&f.ty).is_some());
765 if all_fixed {
766 w!(
767 out,
768 " /// Fast bulk scan on the identity path: validate the element region"
769 );
770 w!(
771 out,
772 " /// once, then yield infallible `{name}Block`s. `None` on the evolved"
773 );
774 w!(out, " /// path (use `get`/`iter`) or a corrupt region.");
775 w!(
776 out,
777 " pub fn blocks(&self) -> Option<impl Iterator<Item = {name}Block<'b>> + 'b> {{"
778 );
779 w!(out, " match self {{");
780 w!(
781 out,
782 " Self::Fast {{ buf, elems, count, .. }} => {{"
783 );
784 w!(out, " let start = *elems as usize;");
785 w!(out, " (*count as usize)");
786 w!(out, " .checked_mul({stride})");
787 w!(
788 out,
789 " .and_then(|n| start.checked_add(n))"
790 );
791 w!(
792 out,
793 " .and_then(|end| buf.get(start..end))"
794 );
795 w!(out, " .map(|region| region.chunks_exact({stride}).map(|c| {name}Block(c.try_into().unwrap())))");
796 w!(out, " }}");
797 w!(out, " Self::Dynamic(_) => None,");
798 w!(out, " }}");
799 w!(out, " }}");
800 }
801 w!(
802 out,
803 " /// General fallible iterator (fast + dynamic). Prefer `blocks()` for a"
804 );
805 w!(out, " /// hot scan on the identity path.");
806 w!(
807 out,
808 " pub fn iter(&self) -> impl Iterator<Item = Result<{name}Ref<'b, 'r>>> + '_ {{"
809 );
810 w!(out, " (0..self.len()).map(move |i| self.get(i))");
811 w!(out, " }}");
812 w!(out, "}}");
813
814 if all_fixed {
815 emit_struct_block(out, schema, idx, sd, stride);
816 }
817 }
818 Ok(())
819}
820
821fn emit_struct_block(out: &mut String, schema: &Schema, idx: u16, sd: &StructDef, stride: u32) {
827 let name = &sd.name;
828 let lay = schema.layout_unchecked(idx).as_fixed();
829 w!(out,);
830 w!(out, "#[derive(Clone, Copy)]");
831 w!(out, "pub struct {name}Block<'b>(&'b [u8; {stride}]);");
832 w!(out,);
833 w!(out, "impl<'b> {name}Block<'b> {{");
834 for (pos, f) in sd.fields.iter().enumerate() {
835 let info = scalar_info(&f.ty).expect("all_fixed");
836 let rust = info.rust;
837 let slot = lay.slots[pos] as usize;
838 let sz = info.size as usize;
839 let read = if rust == "bool" {
841 format!("self.0[{slot}] != 0")
842 } else if sz == 1 {
843 format!("self.0[{slot}] as {rust}")
844 } else {
845 format!(
846 "{rust}::from_le_bytes(self.0[{slot}..{}].try_into().unwrap())",
847 slot + sz
848 )
849 };
850 let fname = &f.name;
851 if sd.is_dense() {
852 w!(out, " pub fn {fname}(&self) -> {rust} {{ {read} }}");
853 } else {
854 let (byte, mask) = (pos / 8, 1u8 << (pos % 8));
855 w!(out, " pub fn {fname}(&self) -> Option<{rust}> {{");
856 w!(
857 out,
858 " if self.0[{byte}] & {mask} == 0 {{ return None; }}"
859 );
860 w!(out, " Some({read})");
861 w!(out, " }}");
862 }
863 }
864 w!(out, "}}");
865}
866
867fn struct_used_in_list(schema: &Schema, idx: u16) -> bool {
868 for i in 0..schema.type_count() {
869 if let Some(TypeDef::Struct(sd)) = schema.type_def(i) {
870 for f in &sd.fields {
871 if matches!(&f.ty, Type::List(e) if matches!(e.as_ref(), Type::Struct(ci) if *ci == idx))
872 {
873 return true;
874 }
875 }
876 }
877 }
878 false
879}
880
881fn struct_stride_align(schema: &Schema, idx: u16) -> (u32, u32) {
882 let lay = schema.layout_unchecked(idx).as_fixed();
883 (lay.size, lay.align)
884}
885
886fn elem_stride_align(schema: &Schema, elem: &Type) -> (u32, u32) {
887 match elem {
888 Type::Struct(i) => struct_stride_align(schema, *i),
889 Type::String => (4, 4),
890 other => {
891 let info = scalar_info(other).expect("scalar");
892 (info.size, info.align)
893 }
894 }
895}
896
897fn emit_struct_writer(out: &mut String, schema: &Schema, idx: u16, sd: &StructDef) -> Result<()> {
902 let name = &sd.name;
903 let fn_name = snake(name);
904 let lay = schema.layout_unchecked(idx).as_fixed();
905 let needs_lt = args_needs_lifetime(schema, idx);
906 let lt_decl = if needs_lt { "<'a>" } else { "" };
907 let is_root = idx == schema.root_index();
908
909 w!(out,);
911 if sd.is_dense() {
912 w!(
913 out,
914 "/// Typed writer args for dense struct `{name}` (all fields required)."
915 );
916 } else {
917 w!(
918 out,
919 "/// Typed writer args for `{name}` (`None` = field absent)."
920 );
921 }
922 w!(out, "pub struct {name}Args{lt_decl} {{");
923 for f in &sd.fields {
924 let fty = args_field_type(schema, &f.ty, sd.is_dense());
925 w!(out, " pub {}: {},", f.name, fty);
926 }
927 w!(out, "}}");
928
929 let args_ty = if needs_lt {
931 format!("{name}Args<'_>")
932 } else {
933 format!("{name}Args")
934 };
935 w!(out,);
936 w!(
937 out,
938 "fn fill_{fn_name}_at(buf: &mut Vec<u8>, base: u32, args: &{args_ty}) -> Result<()> {{"
939 );
940 for (pos, f) in sd.fields.iter().enumerate() {
941 let slot = lay.slots[pos];
942 let (open, val, indent, close) = if sd.is_dense() {
943 (
944 String::new(),
945 format!("args.{}", f.name),
946 " ",
947 String::new(),
948 )
949 } else {
950 (
951 format!(
952 " if let Some(v) = args.{} {{\n wire::set_presence_bit(buf, base, {pos})?;",
953 f.name
954 ),
955 "v".to_string(),
956 " ",
957 " }".to_string(),
958 )
959 };
960 if !open.is_empty() {
961 w!(out, "{open}");
962 }
963 match &f.ty {
964 Type::String => {
965 w!(
966 out,
967 "{indent}let off = wire::write_blob(buf, {val}.as_bytes())?;"
968 );
969 w!(out, "{indent}wire::patch_u32(buf, base + {slot}, off)?;");
970 }
971 Type::Bytes => {
972 w!(out, "{indent}let off = wire::write_blob(buf, {val})?;");
973 w!(out, "{indent}wire::patch_u32(buf, base + {slot}, off)?;");
974 }
975 Type::Struct(ci) => {
976 let cfn = snake(schema.type_name(*ci));
977 w!(out, "{indent}let off = write_{cfn}(buf, {val})?;");
978 w!(out, "{indent}wire::patch_u32(buf, base + {slot}, off)?;");
979 }
980 Type::List(elem) => {
981 emit_list_write(out, schema, elem, &val, slot, indent)?;
982 }
983 other => {
984 let info = scalar_info(other).expect("scalar");
985 w!(
986 out,
987 "{indent}wire::{}(buf, base + {slot}, {val})?;",
988 info.put
989 );
990 }
991 }
992 if !close.is_empty() {
993 w!(out, "{close}");
994 }
995 }
996 w!(out, " Ok(())");
997 w!(out, "}}");
998
999 w!(out,);
1001 w!(
1002 out,
1003 "fn write_{fn_name}(buf: &mut Vec<u8>, args: &{args_ty}) -> Result<u32> {{"
1004 );
1005 w!(
1006 out,
1007 " let base = wire::alloc_block(buf, {}, {})?;",
1008 lay.size,
1009 lay.align
1010 );
1011 w!(out, " fill_{fn_name}_at(buf, base, args)?;");
1012 w!(out, " Ok(base)");
1013 w!(out, "}}");
1014
1015 if is_root {
1016 w!(out,);
1017 w!(
1018 out,
1019 "/// Encode a message directly from typed args — single pass, no"
1020 );
1021 w!(out, "/// dynamic value tree.");
1022 w!(
1023 out,
1024 "pub fn encode_{fn_name}(args: &{args_ty}, mode: SchemaMode) -> Result<Vec<u8>> {{"
1025 );
1026 w!(out, " let inline = matches!(mode, SchemaMode::Inline);");
1027 w!(out, " let mut buf = wire::message_header(");
1028 w!(out, " SCHEMA_ID,");
1029 w!(
1030 out,
1031 " if inline {{ Some(SCHEMA_BYTES) }} else {{ None }},"
1032 );
1033 w!(
1034 out,
1035 " {},",
1036 24 + schema.canonical_bytes().len() + lay.size as usize + 232
1037 );
1038 w!(out, " )?;");
1039 w!(out, " let root = write_{fn_name}(&mut buf, args)?;");
1040 w!(out, " wire::finish_message(&mut buf, root);");
1041 w!(out, " Ok(buf)");
1042 w!(out, "}}");
1043 }
1044 Ok(())
1045}
1046
1047fn emit_list_write(
1048 out: &mut String,
1049 schema: &Schema,
1050 elem: &Type,
1051 val: &str,
1052 slot: u32,
1053 indent: &str,
1054) -> Result<()> {
1055 let (stride, align) = elem_stride_align(schema, elem);
1056 w!(
1057 out,
1058 "{indent}let count = u32::try_from({val}.len()).map_err(|_| Error::MessageTooLarge)?;"
1059 );
1060 w!(
1061 out,
1062 "{indent}let loff = wire::begin_list(buf, count, {align})?;"
1063 );
1064 match elem {
1065 Type::Struct(ci) => {
1066 let sd = schema.struct_def_unchecked(*ci);
1067 let dense_scalar =
1074 sd.is_dense() && sd.fields.iter().all(|f| scalar_info(&f.ty).is_some());
1075 if dense_scalar {
1076 let elay = schema.layout_unchecked(*ci).as_fixed();
1077 w!(out, "{indent}let total = {val}.len().checked_mul({stride}).ok_or(Error::MessageTooLarge)?;");
1078 w!(out, "{indent}let ebase = wire::alloc_bytes(buf, total)?;");
1079 w!(out, "{indent}let region = &mut buf[ebase as usize..];");
1080 w!(out, "{indent}for (i, a) in {val}.iter().enumerate() {{");
1081 w!(out, "{indent} let e = i * {stride};");
1082 for (pos, f) in sd.fields.iter().enumerate() {
1083 let fslot = elay.slots[pos];
1084 if matches!(f.ty, Type::Bool) {
1085 w!(out, "{indent} region[e + {fslot}] = a.{} as u8;", f.name);
1086 } else {
1087 let n = scalar_info(&f.ty).expect("scalar").size;
1088 w!(out, "{indent} region[e + {fslot}..e + {fslot} + {n}].copy_from_slice(&a.{}.to_le_bytes());", f.name);
1089 }
1090 }
1091 w!(out, "{indent}}}");
1092 } else {
1093 let cfn = snake(schema.type_name(*ci));
1094 w!(out, "{indent}let total = {val}.len().checked_mul({stride}).ok_or(Error::MessageTooLarge)?;");
1095 w!(out, "{indent}let ebase = wire::alloc_bytes(buf, total)?;");
1096 w!(out, "{indent}for (i, a) in {val}.iter().enumerate() {{");
1097 w!(
1098 out,
1099 "{indent} fill_{cfn}_at(buf, ebase + i as u32 * {stride}, a)?;"
1100 );
1101 w!(out, "{indent}}}");
1102 }
1103 }
1104 Type::String => {
1105 w!(
1106 out,
1107 "{indent}let total = {val}.len().checked_mul(4).ok_or(Error::MessageTooLarge)?;"
1108 );
1109 w!(out, "{indent}let slots = wire::alloc_bytes(buf, total)?;");
1110 w!(out, "{indent}for (i, s) in {val}.iter().enumerate() {{");
1111 w!(
1112 out,
1113 "{indent} let off = wire::write_blob(buf, s.as_bytes())?;"
1114 );
1115 w!(
1116 out,
1117 "{indent} wire::patch_u32(buf, slots + i as u32 * 4, off)?;"
1118 );
1119 w!(out, "{indent}}}");
1120 }
1121 other => {
1122 let info = scalar_info(other).expect("scalar");
1123 w!(out, "{indent}wire::{}(buf, {val});", info.push_slice);
1126 }
1127 }
1128 w!(out, "{indent}wire::patch_u32(buf, base + {slot}, loff)?;");
1129 Ok(())
1130}
1131
1132fn args_field_type(schema: &Schema, ty: &Type, dense: bool) -> String {
1133 let inner = match ty {
1134 Type::String => "&'a str".to_string(),
1135 Type::Bytes => "&'a [u8]".to_string(),
1136 Type::Struct(i) => {
1137 let cname = schema.type_name(*i);
1138 if args_needs_lifetime(schema, *i) {
1139 format!("&'a {cname}Args<'a>")
1140 } else {
1141 format!("&'a {cname}Args")
1142 }
1143 }
1144 Type::List(elem) => match elem.as_ref() {
1145 Type::Struct(i) => {
1146 let cname = schema.type_name(*i);
1147 if args_needs_lifetime(schema, *i) {
1148 format!("&'a [{cname}Args<'a>]")
1149 } else {
1150 format!("&'a [{cname}Args]")
1151 }
1152 }
1153 Type::String => "&'a [&'a str]".to_string(),
1154 other => format!("&'a [{}]", scalar_info(other).expect("scalar").rust),
1155 },
1156 other => scalar_info(other).expect("scalar").rust.to_string(),
1157 };
1158 if dense {
1159 inner
1160 } else {
1161 format!("Option<{inner}>")
1162 }
1163}
1164
1165fn cpp_base_type(schema: &Schema, ty: &Type) -> String {
1171 match ty {
1172 Type::Bool => "bool".into(),
1173 Type::U8 => "uint8_t".into(),
1174 Type::U16 => "uint16_t".into(),
1175 Type::U32 => "uint32_t".into(),
1176 Type::U64 => "uint64_t".into(),
1177 Type::I8 => "int8_t".into(),
1178 Type::I16 => "int16_t".into(),
1179 Type::I32 => "int32_t".into(),
1180 Type::I64 => "int64_t".into(),
1181 Type::F32 => "float".into(),
1182 Type::F64 => "double".into(),
1183 Type::Enum(_) => "uint32_t".into(),
1184 Type::String => "std::string".into(),
1185 Type::Bytes => "std::vector<uint8_t>".into(),
1186 Type::Struct(ci) => schema.type_name(*ci).to_string(),
1187 Type::List(elem) => format!("std::vector<{}>", cpp_base_type(schema, elem)),
1188 Type::Map(_, _) | Type::Union(_) => {
1189 unreachable!("map/union types are rejected by reject_maps before emission")
1190 }
1191 }
1192}
1193
1194fn cpp_pack_expr(ty: &Type, e: &str, depth: u32) -> String {
1197 match ty {
1198 Type::Bool => format!("veritate::Value::boolean({e})"),
1199 Type::U8 | Type::U16 | Type::U32 | Type::U64 => {
1200 format!("veritate::Value::u64(uint64_t({e}))")
1201 }
1202 Type::I8 | Type::I16 | Type::I32 | Type::I64 => {
1203 format!("veritate::Value::i64(int64_t({e}))")
1204 }
1205 Type::F32 | Type::F64 => format!("veritate::Value::f64(double({e}))"),
1206 Type::Enum(_) => format!("veritate::Value::en({e})"),
1207 Type::String => format!("veritate::Value::str({e})"),
1208 Type::Bytes => format!("veritate::Value::of_bytes({e})"),
1209 Type::Struct(_) => format!("({e})._pack()"),
1210 Type::List(elem) => {
1211 let inner = cpp_pack_expr(elem, &format!("__x{depth}"), depth + 1);
1212 format!(
1213 "[&]{{ std::vector<veritate::Value> __l{depth}; for (auto& __x{depth} : {e}) \
1214 __l{depth}.push_back({inner}); return veritate::Value::of_list(std::move(__l{depth})); }}()"
1215 )
1216 }
1217 Type::Map(_, _) | Type::Union(_) => {
1218 unreachable!("map/union types are rejected by reject_maps before emission")
1219 }
1220 }
1221}
1222
1223fn cpp_unpack_expr(schema: &Schema, ty: &Type, r: &str, depth: u32) -> String {
1225 match ty {
1226 Type::Bool => format!("{r}.b"),
1227 Type::U8 => format!("uint8_t({r}.u)"),
1228 Type::U16 => format!("uint16_t({r}.u)"),
1229 Type::U32 => format!("uint32_t({r}.u)"),
1230 Type::U64 => format!("uint64_t({r}.u)"),
1231 Type::I8 => format!("int8_t({r}.i)"),
1232 Type::I16 => format!("int16_t({r}.i)"),
1233 Type::I32 => format!("int32_t({r}.i)"),
1234 Type::I64 => format!("int64_t({r}.i)"),
1235 Type::F32 => format!("float({r}.d)"),
1236 Type::F64 => format!("double({r}.d)"),
1237 Type::Enum(_) => format!("{r}.e"),
1238 Type::String => format!("std::string({r}.str)"),
1239 Type::Bytes => {
1240 format!("std::vector<uint8_t>({r}.bytes_ptr, {r}.bytes_ptr + {r}.bytes_len)")
1241 }
1242 Type::Struct(ci) => format!("{}::_unpack(*{r}.st)", schema.type_name(*ci)),
1243 Type::List(elem) => {
1244 let base = cpp_base_type(schema, elem);
1245 let inner = cpp_unpack_expr(schema, elem, &format!("__e{depth}"), depth + 1);
1246 format!(
1247 "[&]{{ std::vector<{base}> __o{depth}; for (uint32_t __i{depth} = 0; \
1248 __i{depth} < {r}.ls->size(); __i{depth}++) {{ auto __e{depth} = {r}.ls->get(__i{depth}); \
1249 __o{depth}.push_back({inner}); }} return __o{depth}; }}()"
1250 )
1251 }
1252 Type::Map(_, _) | Type::Union(_) => {
1253 unreachable!("map/union types are rejected by reject_maps before emission")
1254 }
1255 }
1256}
1257
1258fn cpp_struct_order(schema: &Schema) -> Vec<u16> {
1264 let mut order = Vec::new();
1265 let mut visited = BTreeSet::new();
1266 fn visit(schema: &Schema, idx: u16, visited: &mut BTreeSet<u16>, order: &mut Vec<u16>) {
1267 if !visited.insert(idx) {
1268 return;
1269 }
1270 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
1271 for f in &sd.fields {
1272 if let Type::Struct(ci) = &f.ty {
1273 visit(schema, *ci, visited, order);
1274 }
1275 }
1276 order.push(idx);
1277 }
1278 }
1279 for idx in 0..schema.type_count() {
1280 if matches!(schema.type_def(idx), Some(TypeDef::Struct(_))) {
1281 visit(schema, idx, &mut visited, &mut order);
1282 }
1283 }
1284 order
1285}
1286
1287pub fn generate_cpp(schema: &Schema) -> Result<String> {
1294 reject_maps(schema)?;
1295 for idx in 0..schema.type_count() {
1296 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
1297 check_ident(&sd.name)?;
1298 for f in &sd.fields {
1299 check_ident(&f.name)?;
1300 }
1301 }
1302 }
1303
1304 let mut out = String::new();
1305 w!(out, "// @generated by verit::codegen — do not edit.");
1306 w!(out, "// schema id: {:#034x}", schema.id());
1307 w!(out, "#pragma once");
1308 w!(out, "#include <cstdint>");
1309 w!(out, "#include <optional>");
1310 w!(out, "#include <string>");
1311 w!(out, "#include <vector>");
1312 w!(out, "#include \"veritate.hpp\"");
1313 w!(out,);
1314 w!(out, "namespace veritgen {{");
1315 w!(out,);
1316 let bytes: Vec<String> = schema
1317 .canonical_bytes()
1318 .iter()
1319 .map(|b| b.to_string())
1320 .collect();
1321 w!(
1322 out,
1323 "inline const char* SCHEMA_ID = \"{:032x}\";",
1324 schema.id()
1325 );
1326 w!(out, "inline const std::vector<uint8_t>& SCHEMA_BYTES() {{");
1327 w!(
1328 out,
1329 " static const std::vector<uint8_t> b = {{{}}};",
1330 bytes.join(", ")
1331 );
1332 w!(out, " return b;");
1333 w!(out, "}}");
1334 w!(out, "inline const veritate::Schema& _schema() {{");
1335 w!(
1336 out,
1337 " static veritate::Schema s = veritate::Schema::from_canonical(SCHEMA_BYTES());"
1338 );
1339 w!(out, " return s;");
1340 w!(out, "}}");
1341 w!(out, "inline const veritate::Resolver& _resolver() {{");
1342 w!(
1343 out,
1344 " static veritate::Resolver r = veritate::Resolver::identity(_schema());"
1345 );
1346 w!(out, " return r;");
1347 w!(out, "}}");
1348 w!(out,);
1349
1350 for idx in 0..schema.type_count() {
1353 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
1354 w!(out, "struct {};", sd.name);
1355 }
1356 }
1357 w!(out,);
1358
1359 let order = cpp_struct_order(schema);
1360
1361 for &idx in &order {
1363 let sd = schema.struct_def_unchecked(idx);
1364 w!(out, "struct {} {{", sd.name);
1365 for f in &sd.fields {
1366 w!(
1367 out,
1368 " std::optional<{}> {};",
1369 cpp_base_type(schema, &f.ty),
1370 f.name
1371 );
1372 }
1373 w!(out, " veritate::Value _pack() const;");
1374 w!(
1375 out,
1376 " static {} _unpack(const veritate::StructReader& __r);",
1377 sd.name
1378 );
1379 w!(
1380 out,
1381 " std::vector<uint8_t> to_verit(veritate::SchemaMode mode = veritate::SchemaMode::Inline) const;"
1382 );
1383 w!(
1384 out,
1385 " static {} from_verit(const std::vector<uint8_t>& __buf);",
1386 sd.name
1387 );
1388 w!(out, "}};");
1389 w!(out,);
1390 }
1391
1392 for &idx in &order {
1394 let sd = schema.struct_def_unchecked(idx);
1395 let name = &sd.name;
1396 w!(out, "inline veritate::Value {name}::_pack() const {{");
1397 w!(
1398 out,
1399 " std::vector<std::pair<uint16_t, veritate::Value>> __f;"
1400 );
1401 for f in &sd.fields {
1402 let e = cpp_pack_expr(&f.ty, &format!("*{}", f.name), 0);
1403 w!(
1404 out,
1405 " if ({}.has_value()) __f.push_back({{{}, {e}}});",
1406 f.name,
1407 f.id
1408 );
1409 }
1410 w!(out, " return veritate::Value::strct(std::move(__f));");
1411 w!(out, "}}");
1412
1413 w!(
1414 out,
1415 "inline {name} {name}::_unpack(const veritate::StructReader& __r) {{"
1416 );
1417 w!(out, " {name} __out;");
1418 for f in &sd.fields {
1419 let val = cpp_unpack_expr(schema, &f.ty, "(*__v)", 0);
1421 w!(
1422 out,
1423 " {{ auto __v = __r.get({}); if (__v) __out.{} = {val}; }}",
1424 f.id,
1425 f.name
1426 );
1427 }
1428 w!(out, " return __out;");
1429 w!(out, "}}");
1430
1431 w!(
1432 out,
1433 "inline std::vector<uint8_t> {name}::to_verit(veritate::SchemaMode mode) const {{"
1434 );
1435 w!(
1436 out,
1437 " return veritate::encode(_schema(), _pack(), mode);"
1438 );
1439 w!(out, "}}");
1440
1441 w!(
1442 out,
1443 "inline {name} {name}::from_verit(const std::vector<uint8_t>& __buf) {{"
1444 );
1445 w!(
1446 out,
1447 " veritate::Message __msg = veritate::Message::parse(__buf);"
1448 );
1449 w!(
1450 out,
1451 " veritate::StructReader __root = __msg.root(_resolver());"
1452 );
1453 w!(out, " return _unpack(__root);");
1454 w!(out, "}}");
1455 w!(out,);
1456 }
1457
1458 w!(out, "}} // namespace veritgen");
1459 Ok(out)
1460}
1461
1462fn py_desc(schema: &Schema, ty: &Type) -> String {
1471 match ty {
1472 Type::Struct(ci) => format!("(\"struct\", {})", schema.type_name(*ci)),
1473 Type::List(elem) => format!("(\"list\", {})", py_desc(schema, elem)),
1474 _ => "(\"leaf\", None)".to_string(),
1475 }
1476}
1477
1478fn py_hint(schema: &Schema, ty: &Type) -> String {
1481 match ty {
1482 Type::Bool => "bool".into(),
1483 Type::F32 | Type::F64 => "float".into(),
1484 Type::String => "str".into(),
1485 Type::Bytes => "bytes".into(),
1486 Type::Struct(ci) => schema.type_name(*ci).to_string(),
1487 Type::List(_) => "list".into(),
1488 _ => "int".into(), }
1490}
1491
1492pub fn generate_python(schema: &Schema) -> Result<String> {
1499 reject_maps(schema)?;
1500 for idx in 0..schema.type_count() {
1501 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
1502 check_ident(&sd.name)?;
1503 for f in &sd.fields {
1504 check_ident(&f.name)?;
1505 }
1506 }
1507 }
1508
1509 let mut out = String::new();
1510 w!(out, "# @generated by verit::codegen — do not edit.");
1511 w!(out, "# schema id: {:#034x}", schema.id());
1512 w!(out, "from __future__ import annotations");
1513 w!(out, "from dataclasses import dataclass");
1514 w!(out, "import veritate as _v");
1515 w!(out,);
1516 let bytes: Vec<String> = schema
1517 .canonical_bytes()
1518 .iter()
1519 .map(|b| b.to_string())
1520 .collect();
1521 w!(out, "SCHEMA_ID = \"{:032x}\"", schema.id());
1522 w!(out, "SCHEMA_BYTES = bytes([{}])", bytes.join(", "));
1523 w!(out, "_SCHEMA = _v.Schema.from_canonical(SCHEMA_BYTES)");
1524 w!(out, "_RESOLVER = _v.Resolver.identity(_SCHEMA)");
1525 w!(out,);
1526
1527 w!(out, "def _pack(obj):");
1529 w!(out, " out = {{}}");
1530 w!(out, " for (attr, fid, desc) in obj._VERIT:");
1531 w!(out, " v = getattr(obj, attr)");
1532 w!(out, " if v is not None:");
1533 w!(out, " out[fid] = _pack_val(v, desc)");
1534 w!(out, " return out");
1535 w!(out,);
1536 w!(out, "def _pack_val(v, desc):");
1537 w!(out, " kind, extra = desc");
1538 w!(out, " if kind == \"struct\":");
1539 w!(out, " return _pack(v)");
1540 w!(out, " if kind == \"list\":");
1541 w!(out, " return [_pack_val(x, extra) for x in v]");
1542 w!(out, " return v");
1543 w!(out,);
1544 w!(out, "def _unpack(cls, reader):");
1545 w!(out, " kwargs = {{}}");
1546 w!(out, " for (attr, fid, desc) in cls._VERIT:");
1547 w!(out, " val = reader.get(fid)");
1548 w!(
1549 out,
1550 " kwargs[attr] = None if val is None else _unpack_val(val, desc)"
1551 );
1552 w!(out, " return cls(**kwargs)");
1553 w!(out,);
1554 w!(out, "def _unpack_val(val, desc):");
1555 w!(out, " kind, extra = desc");
1556 w!(out, " if kind == \"struct\":");
1557 w!(out, " return _unpack(extra, val)");
1558 w!(out, " if kind == \"list\":");
1559 w!(
1560 out,
1561 " return [_unpack_val(val.get(i), extra) for i in range(len(val))]"
1562 );
1563 w!(out, " return val");
1564 w!(out,);
1565
1566 for idx in 0..schema.type_count() {
1568 let sd = match schema.type_def(idx) {
1569 Some(TypeDef::Struct(sd)) => sd,
1570 _ => continue,
1571 };
1572 w!(out, "@dataclass");
1573 w!(out, "class {}:", sd.name);
1574 if sd.fields.is_empty() {
1575 w!(out, " pass");
1576 }
1577 for f in &sd.fields {
1578 w!(out, " {}: {} = None", f.name, py_hint(schema, &f.ty));
1579 }
1580 w!(out, " def to_verit(self, mode=_v.INLINE) -> bytes:");
1581 w!(out, " return _v.encode(_SCHEMA, _pack(self), mode)");
1582 w!(out, " @classmethod");
1583 w!(out, " def from_verit(cls, buf) -> \"{}\":", sd.name);
1584 w!(out, " msg = _v.Message.parse(buf)");
1585 w!(out, " return _unpack(cls, msg.root(_RESOLVER))");
1586 w!(out,);
1587 }
1588
1589 for idx in 0..schema.type_count() {
1591 let sd = match schema.type_def(idx) {
1592 Some(TypeDef::Struct(sd)) => sd,
1593 _ => continue,
1594 };
1595 let entries: Vec<String> = sd
1596 .fields
1597 .iter()
1598 .map(|f| format!("(\"{}\", {}, {})", f.name, f.id, py_desc(schema, &f.ty)))
1599 .collect();
1600 w!(out, "{}._VERIT = [{}]", sd.name, entries.join(", "));
1601 }
1602 Ok(out)
1603}
1604
1605fn ts_desc(schema: &Schema, ty: &Type) -> String {
1613 match ty {
1614 Type::Struct(ci) => format!("[\"struct\", {}]", schema.type_name(*ci)),
1615 Type::List(elem) => format!("[\"list\", {}]", ts_desc(schema, elem)),
1616 _ => "[\"leaf\", null]".to_string(),
1617 }
1618}
1619
1620fn ts_hint(schema: &Schema, ty: &Type) -> String {
1622 match ty {
1623 Type::Bool => "boolean".into(),
1624 Type::U64 | Type::I64 => "bigint".into(),
1625 Type::F32 | Type::F64 => "number".into(),
1626 Type::String => "string".into(),
1627 Type::Bytes => "Uint8Array".into(),
1628 Type::Struct(ci) => schema.type_name(*ci).to_string(),
1629 Type::List(elem) => format!("Array<{}>", ts_hint(schema, elem)),
1630 _ => "number".into(), }
1632}
1633
1634pub fn generate_ts(schema: &Schema) -> Result<String> {
1641 reject_maps(schema)?;
1642 for idx in 0..schema.type_count() {
1643 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
1644 check_ident(&sd.name)?;
1645 for f in &sd.fields {
1646 check_ident(&f.name)?;
1647 }
1648 }
1649 }
1650
1651 let mut out = String::new();
1652 w!(out, "// @generated by verit::codegen — do not edit.");
1653 w!(out, "// schema id: {:#034x}", schema.id());
1654 w!(out, "import * as _v from \"./veritate.ts\";");
1655 w!(out,);
1656 let bytes: Vec<String> = schema
1657 .canonical_bytes()
1658 .iter()
1659 .map(|b| b.to_string())
1660 .collect();
1661 w!(out, "export const SCHEMA_ID = \"{:032x}\";", schema.id());
1662 w!(
1663 out,
1664 "export const SCHEMA_BYTES = new Uint8Array([{}]);",
1665 bytes.join(", ")
1666 );
1667 w!(
1668 out,
1669 "const _SCHEMA = _v.Schema.fromCanonical(SCHEMA_BYTES);"
1670 );
1671 w!(out, "const _RESOLVER = _v.Resolver.identity(_SCHEMA);");
1672 w!(out,);
1673
1674 w!(
1676 out,
1677 "type _Desc = readonly [\"leaf\", null] | readonly [\"struct\", any] | readonly [\"list\", _Desc];"
1678 );
1679 w!(out,);
1680 w!(out, "function _pack(obj: any): _v.StructVal {{");
1681 w!(out, " const out: Array<[number, _v.Value]> = [];");
1682 w!(
1683 out,
1684 " for (const [attr, fid, desc] of obj.constructor._VERIT as Array<[string, number, _Desc]>) {{"
1685 );
1686 w!(out, " const v = obj[attr];");
1687 w!(
1688 out,
1689 " if (v !== null && v !== undefined) out.push([fid, _packVal(v, desc)]);"
1690 );
1691 w!(out, " }}");
1692 w!(out, " return _v.st(out);");
1693 w!(out, "}}");
1694 w!(out, "function _packVal(v: any, desc: _Desc): _v.Value {{");
1695 w!(out, " if (desc[0] === \"struct\") return _pack(v);");
1696 w!(
1697 out,
1698 " if (desc[0] === \"list\") return (v as any[]).map((x) => _packVal(x, desc[1]));"
1699 );
1700 w!(out, " return v;");
1701 w!(out, "}}");
1702 w!(out, "function _unpack(cls: any, reader: any): any {{");
1703 w!(out, " const obj = new cls();");
1704 w!(
1705 out,
1706 " for (const [attr, fid, desc] of cls._VERIT as Array<[string, number, _Desc]>) {{"
1707 );
1708 w!(out, " const val = reader.get(fid);");
1709 w!(
1710 out,
1711 " obj[attr] = val === null || val === undefined ? null : _unpackVal(val, desc);"
1712 );
1713 w!(out, " }}");
1714 w!(out, " return obj;");
1715 w!(out, "}}");
1716 w!(out, "function _unpackVal(val: any, desc: _Desc): any {{");
1717 w!(
1718 out,
1719 " if (desc[0] === \"struct\") return _unpack(desc[1], val);"
1720 );
1721 w!(out, " if (desc[0] === \"list\") {{");
1722 w!(out, " const out: any[] = [];");
1723 w!(
1724 out,
1725 " for (let i = 0; i < val.length; i++) out.push(_unpackVal(val.get(i), desc[1]));"
1726 );
1727 w!(out, " return out;");
1728 w!(out, " }}");
1729 w!(out, " return val;");
1730 w!(out, "}}");
1731 w!(out,);
1732
1733 for idx in 0..schema.type_count() {
1735 let sd = match schema.type_def(idx) {
1736 Some(TypeDef::Struct(sd)) => sd,
1737 _ => continue,
1738 };
1739 w!(out, "export class {} {{", sd.name);
1740 for f in &sd.fields {
1741 w!(
1742 out,
1743 " {}: {} | null = null;",
1744 f.name,
1745 ts_hint(schema, &f.ty)
1746 );
1747 }
1748 w!(out, " static _VERIT: Array<[string, number, _Desc]> = [];");
1749 w!(
1750 out,
1751 " toVerit(mode: _v.SchemaMode = \"inline\"): Uint8Array {{ return _v.encode(_SCHEMA, _pack(this), mode); }}"
1752 );
1753 w!(
1754 out,
1755 " static fromVerit(buf: Uint8Array): {} {{ return _unpack({}, _v.Message.parse(buf).root(_RESOLVER)); }}",
1756 sd.name,
1757 sd.name
1758 );
1759 w!(out, "}}");
1760 }
1761 w!(out,);
1762
1763 for idx in 0..schema.type_count() {
1765 let sd = match schema.type_def(idx) {
1766 Some(TypeDef::Struct(sd)) => sd,
1767 _ => continue,
1768 };
1769 let entries: Vec<String> = sd
1770 .fields
1771 .iter()
1772 .map(|f| format!("[\"{}\", {}, {}]", f.name, f.id, ts_desc(schema, &f.ty)))
1773 .collect();
1774 w!(out, "{}._VERIT = [{}];", sd.name, entries.join(", "));
1775 }
1776 Ok(out)
1777}
1778
1779fn go_export(name: &str) -> String {
1786 let mut c = name.chars();
1787 match c.next() {
1788 Some(f) => f.to_ascii_uppercase().to_string() + c.as_str(),
1789 None => String::new(),
1790 }
1791}
1792
1793fn go_elem_type(schema: &Schema, ty: &Type) -> String {
1796 match ty {
1797 Type::Bool => "bool".into(),
1798 Type::U8 => "uint8".into(),
1799 Type::U16 => "uint16".into(),
1800 Type::U32 => "uint32".into(),
1801 Type::U64 => "uint64".into(),
1802 Type::I8 => "int8".into(),
1803 Type::I16 => "int16".into(),
1804 Type::I32 => "int32".into(),
1805 Type::I64 => "int64".into(),
1806 Type::F32 => "float32".into(),
1807 Type::F64 => "float64".into(),
1808 Type::Enum(_) => "uint32".into(),
1809 Type::String => "string".into(),
1810 Type::Bytes => "[]byte".into(),
1811 Type::Struct(ci) => schema.type_name(*ci).to_string(),
1812 Type::List(elem) => format!("[]{}", go_elem_type(schema, elem)),
1813 Type::Map(_, _) | Type::Union(_) => {
1814 unreachable!("map/union types are rejected by reject_maps before emission")
1815 }
1816 }
1817}
1818
1819fn go_field_type(schema: &Schema, ty: &Type) -> String {
1822 match ty {
1823 Type::Bytes => "[]byte".into(),
1824 Type::List(elem) => format!("[]{}", go_elem_type(schema, elem)),
1825 Type::Struct(ci) => format!("*{}", schema.type_name(*ci)),
1826 other => format!("*{}", go_elem_type(schema, other)),
1827 }
1828}
1829
1830fn go_desc(schema: &Schema, ty: &Type) -> String {
1832 match ty {
1833 Type::Struct(ci) => format!(
1834 "_desc{{kind: 1, typ: reflect.TypeOf({}{{}})}}",
1835 schema.type_name(*ci)
1836 ),
1837 Type::List(elem) => {
1838 format!("_desc{{kind: 2, sub: &{}}}", go_desc(schema, elem))
1839 }
1840 _ => "_desc{kind: 0}".to_string(),
1841 }
1842}
1843
1844pub fn generate_go(schema: &Schema) -> Result<String> {
1851 reject_maps(schema)?;
1852 for idx in 0..schema.type_count() {
1853 if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
1854 check_ident(&sd.name)?;
1855 for f in &sd.fields {
1856 check_ident(&f.name)?;
1857 }
1858 }
1859 }
1860
1861 let mut out = String::new();
1862 w!(out, "// @generated by verit::codegen — do not edit.");
1863 w!(out, "// schema id: {:#034x}", schema.id());
1864 w!(out, "package veritgen");
1865 w!(out,);
1866 w!(out, "import (");
1867 w!(out, "\t\"reflect\"");
1868 w!(out,);
1869 w!(out, "\tv \"veritate\"");
1870 w!(out, ")");
1871 w!(out,);
1872 let bytes: Vec<String> = schema
1873 .canonical_bytes()
1874 .iter()
1875 .map(|b| b.to_string())
1876 .collect();
1877 w!(out, "const SchemaID = \"{:032x}\"", schema.id());
1878 w!(out,);
1879 w!(out, "var SchemaBytes = []byte{{{}}}", bytes.join(", "));
1880 w!(out,);
1881 w!(out, "var (");
1882 w!(out, "\t_schema *v.Schema");
1883 w!(out, "\t_resolver *v.Resolver");
1884 w!(out, "\t_specs = map[reflect.Type][]_fld{{}}");
1885 w!(out, ")");
1886 w!(out,);
1887 w!(out, "// _desc.kind: 0 leaf, 1 struct, 2 list.");
1889 w!(out, "type _desc struct {{");
1890 w!(out, "\tkind int");
1891 w!(out, "\tsub *_desc");
1892 w!(out, "\ttyp reflect.Type");
1893 w!(out, "}}");
1894 w!(out, "type _fld struct {{");
1895 w!(out, "\tfield string");
1896 w!(out, "\tid uint16");
1897 w!(out, "\td _desc");
1898 w!(out, "}}");
1899 w!(out,);
1900 w!(out, "func _absent(fv reflect.Value) bool {{");
1901 w!(out, "\tswitch fv.Kind() {{");
1902 w!(
1903 out,
1904 "\tcase reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface:"
1905 );
1906 w!(out, "\t\treturn fv.IsNil()");
1907 w!(out, "\tdefault:");
1908 w!(out, "\t\treturn false");
1909 w!(out, "\t}}");
1910 w!(out, "}}");
1911 w!(
1912 out,
1913 "func _conv(x reflect.Value, t reflect.Type) reflect.Value {{"
1914 );
1915 w!(out, "\tif x.Type() == t {{");
1916 w!(out, "\t\treturn x");
1917 w!(out, "\t}}");
1918 w!(out, "\tif x.Type().ConvertibleTo(t) {{");
1919 w!(out, "\t\treturn x.Convert(t)");
1920 w!(out, "\t}}");
1921 w!(out, "\treturn x");
1922 w!(out, "}}");
1923 w!(out, "func _packObj(rv reflect.Value) v.Struct {{");
1924 w!(out, "\tif rv.Kind() == reflect.Ptr {{");
1925 w!(out, "\t\trv = rv.Elem()");
1926 w!(out, "\t}}");
1927 w!(out, "\tvar out []v.KV");
1928 w!(out, "\tfor _, f := range _specs[rv.Type()] {{");
1929 w!(out, "\t\tfv := rv.FieldByName(f.field)");
1930 w!(out, "\t\tif _absent(fv) {{");
1931 w!(out, "\t\t\tcontinue");
1932 w!(out, "\t\t}}");
1933 w!(
1934 out,
1935 "\t\tout = append(out, v.KV{{ID: f.id, V: _packVal(fv, f.d)}})"
1936 );
1937 w!(out, "\t}}");
1938 w!(out, "\treturn v.St(out...)");
1939 w!(out, "}}");
1940 w!(out, "func _packVal(fv reflect.Value, d _desc) any {{");
1941 w!(out, "\tif fv.Kind() == reflect.Ptr {{");
1942 w!(out, "\t\tfv = fv.Elem()");
1943 w!(out, "\t}}");
1944 w!(out, "\tswitch d.kind {{");
1945 w!(out, "\tcase 1:");
1946 w!(out, "\t\treturn _packObj(fv)");
1947 w!(out, "\tcase 2:");
1948 w!(out, "\t\tn := fv.Len()");
1949 w!(out, "\t\tarr := make([]any, n)");
1950 w!(out, "\t\tfor i := 0; i < n; i++ {{");
1951 w!(out, "\t\t\tarr[i] = _packVal(fv.Index(i), *d.sub)");
1952 w!(out, "\t\t}}");
1953 w!(out, "\t\treturn arr");
1954 w!(out, "\tdefault:");
1955 w!(out, "\t\treturn fv.Interface()");
1956 w!(out, "\t}}");
1957 w!(out, "}}");
1958 w!(
1959 out,
1960 "func _unpackObj(rt reflect.Type, r *v.StructReader) reflect.Value {{"
1961 );
1962 w!(out, "\tptr := reflect.New(rt)");
1963 w!(out, "\trv := ptr.Elem()");
1964 w!(out, "\tfor _, f := range _specs[rt] {{");
1965 w!(out, "\t\tval, ok := r.Get(f.id)");
1966 w!(out, "\t\tif !ok {{");
1967 w!(out, "\t\t\tcontinue");
1968 w!(out, "\t\t}}");
1969 w!(out, "\t\t_set(rv.FieldByName(f.field), f.d, val)");
1970 w!(out, "\t}}");
1971 w!(out, "\treturn ptr");
1972 w!(out, "}}");
1973 w!(out, "func _set(dst reflect.Value, d _desc, val any) {{");
1974 w!(out, "\tswitch d.kind {{");
1975 w!(out, "\tcase 1:");
1976 w!(out, "\t\tsr := val.(*v.StructReader)");
1977 w!(out, "\t\tet := dst.Type()");
1978 w!(out, "\t\tif et.Kind() == reflect.Ptr {{");
1979 w!(out, "\t\t\tet = et.Elem()");
1980 w!(out, "\t\t}}");
1981 w!(out, "\t\tobj := _unpackObj(et, sr)");
1982 w!(out, "\t\tif dst.Kind() == reflect.Ptr {{");
1983 w!(out, "\t\t\tdst.Set(obj)");
1984 w!(out, "\t\t}} else {{");
1985 w!(out, "\t\t\tdst.Set(obj.Elem())");
1986 w!(out, "\t\t}}");
1987 w!(out, "\tcase 2:");
1988 w!(out, "\t\tlr := val.(*v.ListReader)");
1989 w!(out, "\t\tn := lr.Len()");
1990 w!(out, "\t\tslice := reflect.MakeSlice(dst.Type(), n, n)");
1991 w!(out, "\t\tfor i := 0; i < n; i++ {{");
1992 w!(out, "\t\t\t_set(slice.Index(i), *d.sub, lr.Get(i))");
1993 w!(out, "\t\t}}");
1994 w!(out, "\t\tdst.Set(slice)");
1995 w!(out, "\tdefault:");
1996 w!(out, "\t\trv := reflect.ValueOf(val)");
1997 w!(out, "\t\tif dst.Kind() == reflect.Ptr {{");
1998 w!(out, "\t\t\tet := dst.Type().Elem()");
1999 w!(out, "\t\t\tp := reflect.New(et)");
2000 w!(out, "\t\t\tp.Elem().Set(_conv(rv, et))");
2001 w!(out, "\t\t\tdst.Set(p)");
2002 w!(out, "\t\t}} else {{");
2003 w!(out, "\t\t\tdst.Set(_conv(rv, dst.Type()))");
2004 w!(out, "\t\t}}");
2005 w!(out, "\t}}");
2006 w!(out, "}}");
2007 w!(out,);
2008
2009 w!(out, "func init() {{");
2011 w!(out, "\t_schema, _ = v.SchemaFromCanonical(SchemaBytes)");
2012 w!(out, "\t_resolver = v.Identity(_schema)");
2013 for idx in 0..schema.type_count() {
2014 let sd = match schema.type_def(idx) {
2015 Some(TypeDef::Struct(sd)) => sd,
2016 _ => continue,
2017 };
2018 w!(out, "\t_specs[reflect.TypeOf({}{{}})] = []_fld{{", sd.name);
2019 for f in &sd.fields {
2020 w!(
2021 out,
2022 "\t\t{{field: \"{}\", id: {}, d: {}}},",
2023 go_export(&f.name),
2024 f.id,
2025 go_desc(schema, &f.ty)
2026 );
2027 }
2028 w!(out, "\t}}");
2029 }
2030 w!(out, "}}");
2031 w!(out,);
2032
2033 for idx in 0..schema.type_count() {
2035 let sd = match schema.type_def(idx) {
2036 Some(TypeDef::Struct(sd)) => sd,
2037 _ => continue,
2038 };
2039 w!(out, "type {} struct {{", sd.name);
2040 for f in &sd.fields {
2041 w!(
2042 out,
2043 "\t{} {}",
2044 go_export(&f.name),
2045 go_field_type(schema, &f.ty)
2046 );
2047 }
2048 w!(out, "}}");
2049 w!(out,);
2050 w!(
2051 out,
2052 "func (x *{}) ToVerit(mode v.SchemaMode) ([]byte, error) {{",
2053 sd.name
2054 );
2055 w!(
2056 out,
2057 "\treturn v.Encode(_schema, _packObj(reflect.ValueOf(x)), mode)"
2058 );
2059 w!(out, "}}");
2060 w!(
2061 out,
2062 "func {}FromVerit(buf []byte) (out *{}, err error) {{",
2063 sd.name,
2064 sd.name
2065 );
2066 w!(out, "\tdefer func() {{");
2067 w!(out, "\t\tif r := recover(); r != nil {{");
2068 w!(out, "\t\t\tif e, ok := r.(error); ok {{");
2069 w!(out, "\t\t\t\terr = e");
2070 w!(out, "\t\t\t}} else {{");
2071 w!(out, "\t\t\t\tpanic(r)");
2072 w!(out, "\t\t\t}}");
2073 w!(out, "\t\t}}");
2074 w!(out, "\t}}()");
2075 w!(out, "\tmsg, err := v.Parse(buf)");
2076 w!(out, "\tif err != nil {{");
2077 w!(out, "\t\treturn nil, err");
2078 w!(out, "\t}}");
2079 w!(out, "\troot, err := msg.Root(_resolver)");
2080 w!(out, "\tif err != nil {{");
2081 w!(out, "\t\treturn nil, err");
2082 w!(out, "\t}}");
2083 w!(
2084 out,
2085 "\treturn _unpackObj(reflect.TypeOf({}{{}}), root).Interface().(*{}), nil",
2086 sd.name,
2087 sd.name
2088 );
2089 w!(out, "}}");
2090 w!(out,);
2091 }
2092 Ok(out)
2093}