use std::collections::BTreeSet;
use std::fmt::Write as _;
use crate::error::{Error, Result};
use crate::schema::{Schema, StructDef, Type, TypeDef};
macro_rules! w {
($out:expr, $($arg:tt)*) => { let _ = writeln!($out, $($arg)*); };
}
pub fn generate_rust(schema: &Schema) -> Result<String> {
check_supported(schema)?;
let mut out = String::new();
w!(out, "// @generated by verit::codegen — do not edit.");
w!(out, "// schema id: {:#034x}", schema.id());
w!(out, "#![allow(dead_code, unused_imports, clippy::all)]");
w!(out,);
w!(out, "use verit::{{wire, Budget, Error, ListReader, Message, Ref, Resolver, Result, SchemaMode, StructReader}};");
w!(out,);
w!(out, "pub const SCHEMA_ID: u128 = {:#034x};", schema.id());
let bytes: Vec<String> = schema
.canonical_bytes()
.iter()
.map(|b| b.to_string())
.collect();
w!(
out,
"pub const SCHEMA_BYTES: &[u8] = &[{}];",
bytes.join(", ")
);
w!(out,);
w!(
out,
"/// The generated schema, decoded from its embedded canonical bytes."
);
w!(out, "pub fn schema() -> verit::Schema {{");
w!(
out,
" verit::Schema::from_canonical(SCHEMA_BYTES).expect(\"embedded canonical schema\")"
);
w!(out, "}}");
w!(out,);
w!(out, "fn type_err(expected: &str, got: &str) -> Error {{");
w!(
out,
" Error::TypeMismatch {{ expected: expected.into(), got: got.into() }}"
);
w!(out, "}}");
let mut scalar_lists: BTreeSet<&'static str> = BTreeSet::new();
let mut str_list = false;
for idx in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
for f in &sd.fields {
if let Type::List(elem) = &f.ty {
match elem.as_ref() {
Type::String => str_list = true,
Type::Struct(_) => {}
other => {
if let Some(info) = scalar_info(other) {
scalar_lists.insert(info.rust);
}
}
}
}
}
}
}
for rust in &scalar_lists {
emit_scalar_list(&mut out, rust);
}
if str_list {
emit_str_list(&mut out);
}
for idx in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
emit_struct_reader(&mut out, schema, idx, sd)?;
emit_struct_writer(&mut out, schema, idx, sd)?;
}
}
Ok(out)
}
fn type_has_map(ty: &Type) -> bool {
match ty {
Type::Map(_, _) | Type::Union(_) => true,
Type::List(e) => type_has_map(e),
_ => false,
}
}
fn reject_maps(schema: &Schema) -> Result<()> {
for idx in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
for f in &sd.fields {
if type_has_map(&f.ty) {
return Err(Error::BadSchema(format!(
"codegen does not yet support map/union types (field {} of {}); \
read them through the dynamic API",
f.name, sd.name
)));
}
}
}
}
Ok(())
}
fn check_supported(schema: &Schema) -> Result<()> {
reject_maps(schema)?;
for idx in 0..schema.type_count() {
let td = schema.type_def(idx).unwrap();
check_ident(td.name())?;
if let TypeDef::Struct(sd) = td {
if sd.is_packed() {
return Err(Error::BadSchema(format!(
"codegen v1 does not support packed struct {}: packed layout \
has per-message dynamic offsets — use the dynamic reader API",
sd.name
)));
}
for f in &sd.fields {
check_ident(&f.name)?;
if let Type::List(elem) = &f.ty {
match elem.as_ref() {
Type::Bytes | Type::Enum(_) | Type::List(_) => {
return Err(Error::BadSchema(format!(
"codegen v1 does not support field {} of {}: \
list<bytes>, list<enum>, and nested lists need the dynamic API",
f.name, sd.name
)))
}
_ => {}
}
}
}
}
}
Ok(())
}
const KEYWORDS: &[&str] = &[
"as", "async", "await", "box", "break", "const", "continue", "crate", "dyn", "else", "enum",
"extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move",
"mut", "pub", "ref", "return", "self", "static", "struct", "super", "trait", "true", "type",
"unsafe", "use", "where", "while",
];
fn check_ident(name: &str) -> Result<()> {
let mut chars = name.chars();
let ok = matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
&& !KEYWORDS.contains(&name);
if ok {
Ok(())
} else {
Err(Error::BadSchema(format!(
"name {name:?} is not a usable Rust identifier for codegen"
)))
}
}
fn snake(name: &str) -> String {
let mut s = String::with_capacity(name.len() + 4);
for (i, c) in name.chars().enumerate() {
if c.is_ascii_uppercase() {
if i > 0 {
s.push('_');
}
s.push(c.to_ascii_lowercase());
} else {
s.push(c);
}
}
s
}
struct ScalarInfo {
rust: &'static str,
read: &'static str,
put: &'static str,
push_slice: &'static str,
dyn_get: &'static str,
size: u32,
align: u32,
}
fn scalar_info(ty: &Type) -> Option<ScalarInfo> {
macro_rules! s {
($rust:literal, $suffix:literal, $n:literal) => {
Some(ScalarInfo {
rust: $rust,
read: concat!("read_", $suffix),
put: concat!("put_", $suffix),
push_slice: concat!("push_", $suffix, "_slice"),
dyn_get: concat!("get_", $suffix),
size: $n,
align: $n,
})
};
}
match ty {
Type::Bool => s!("bool", "bool", 1),
Type::U8 => s!("u8", "u8", 1),
Type::U16 => s!("u16", "u16", 2),
Type::U32 => s!("u32", "u32", 4),
Type::U64 => s!("u64", "u64", 8),
Type::I8 => s!("i8", "i8", 1),
Type::I16 => s!("i16", "i16", 2),
Type::I32 => s!("i32", "i32", 4),
Type::I64 => s!("i64", "i64", 8),
Type::F32 => s!("f32", "f32", 4),
Type::F64 => s!("f64", "f64", 8),
Type::Enum(_) => Some(ScalarInfo {
rust: "u32",
read: "read_u32",
put: "put_u32",
push_slice: "push_u32_slice",
dyn_get: "get_enum",
size: 4,
align: 4,
}),
_ => None,
}
}
fn list_ref_type(schema: &Schema, elem: &Type) -> String {
match elem {
Type::Struct(i) => format!("{}List", schema.type_name(*i)),
Type::String => "StrList".to_string(),
other => {
let info = scalar_info(other).expect("checked supported");
format!("{}List", pascal(info.rust))
}
}
}
fn pascal(s: &str) -> String {
let mut c = s.chars();
match c.next() {
Some(first) => first.to_ascii_uppercase().to_string() + c.as_str(),
None => String::new(),
}
}
fn args_needs_lifetime(schema: &Schema, idx: u16) -> bool {
let sd = match schema.type_def(idx) {
Some(TypeDef::Struct(s)) => s,
_ => return false,
};
sd.fields.iter().any(|f| scalar_info(&f.ty).is_none())
}
fn emit_scalar_list(out: &mut String, rust: &'static str) {
let name = format!("{}List", pascal(rust));
let info = scalar_info(&rust_to_type(rust)).unwrap();
let variant = pascal(rust);
w!(out,);
w!(out, "#[derive(Clone)]");
w!(out, "pub enum {name}<'b, 'r> {{");
w!(
out,
" // On a bounded read the whole element region was charged when the"
);
w!(
out,
" // list was opened, so element access here is charge-free."
);
w!(out, " Fast {{ buf: &'b [u8], elems: u64, count: u32 }},");
w!(out, " Dynamic(ListReader<'b, 'r>),");
w!(out, "}}");
w!(out,);
w!(out, "impl<'b, 'r> {name}<'b, 'r> {{");
w!(out, " pub fn len(&self) -> u32 {{");
w!(out, " match self {{ Self::Fast {{ count, .. }} => *count, Self::Dynamic(l) => l.len() }}");
w!(out, " }}");
w!(
out,
" pub fn is_empty(&self) -> bool {{ self.len() == 0 }}"
);
w!(out, " pub fn get(&self, i: u32) -> Result<{rust}> {{");
w!(out, " match self {{");
w!(out, " Self::Fast {{ buf, elems, count }} => {{");
w!(
out,
" if i >= *count {{ return Err(Error::IndexOutOfBounds); }}"
);
w!(
out,
" wire::{}(*buf, *elems + i as u64 * {})",
info.read,
info.size
);
w!(out, " }}");
w!(out, " Self::Dynamic(l) => match l.get(i)? {{");
if rust == "u32" {
w!(out, " Ref::U32(v) => Ok(v),");
w!(out, " Ref::Enum(v) => Ok(v),");
} else {
w!(out, " Ref::{variant}(v) => Ok(v),");
}
w!(
out,
" other => Err(type_err(\"{rust}\", other.kind())),"
);
w!(out, " }},");
w!(out, " }}");
w!(out, " }}");
let decode = if rust == "bool" {
"|c| c[0] != 0".to_string()
} else {
format!("|c| {rust}::from_le_bytes(c.try_into().unwrap())")
};
w!(
out,
" pub fn values(&self) -> Option<impl Iterator<Item = {rust}> + 'b> {{"
);
w!(out, " match self {{");
w!(out, " Self::Fast {{ buf, elems, count }} => {{");
w!(out, " let start = *elems as usize;");
w!(out, " (*count as usize)");
w!(out, " .checked_mul({})", info.size);
w!(
out,
" .and_then(|n| start.checked_add(n))"
);
w!(
out,
" .and_then(|end| buf.get(start..end))"
);
w!(
out,
" .map(|region| region.chunks_exact({}).map({decode}))",
info.size
);
w!(out, " }}");
w!(out, " Self::Dynamic(_) => None,");
w!(out, " }}");
w!(out, " }}");
w!(
out,
" /// General fallible iterator (fast + dynamic). Prefer `values()`"
);
w!(out, " /// for a hot scan on the identity path.");
w!(
out,
" pub fn iter(&self) -> impl Iterator<Item = Result<{rust}>> + '_ {{"
);
w!(out, " (0..self.len()).map(move |i| self.get(i))");
w!(out, " }}");
w!(out, "}}");
}
fn rust_to_type(rust: &str) -> Type {
match rust {
"bool" => Type::Bool,
"u8" => Type::U8,
"u16" => Type::U16,
"u32" => Type::U32,
"u64" => Type::U64,
"i8" => Type::I8,
"i16" => Type::I16,
"i32" => Type::I32,
"i64" => Type::I64,
"f32" => Type::F32,
"f64" => Type::F64,
_ => unreachable!("scalar rust type"),
}
}
fn emit_str_list(out: &mut String) {
w!(out,);
w!(out, "#[derive(Clone)]");
w!(out, "pub enum StrList<'b, 'r> {{");
w!(
out,
" // String elements follow per-element offsets to payloads, so each"
);
w!(
out,
" // `get` charges its payload against the budget on a bounded read"
);
w!(
out,
" // (aliased element offsets recharge on every visit)."
);
w!(
out,
" Fast {{ buf: &'b [u8], elems: u64, count: u32, budget: Option<&'r Budget> }},"
);
w!(out, " Dynamic(ListReader<'b, 'r>),");
w!(out, "}}");
w!(out,);
w!(out, "impl<'b, 'r> StrList<'b, 'r> {{");
w!(out, " pub fn len(&self) -> u32 {{");
w!(out, " match self {{ Self::Fast {{ count, .. }} => *count, Self::Dynamic(l) => l.len() }}");
w!(out, " }}");
w!(
out,
" pub fn is_empty(&self) -> bool {{ self.len() == 0 }}"
);
w!(out, " pub fn get(&self, i: u32) -> Result<&'b str> {{");
w!(out, " match self {{");
w!(
out,
" Self::Fast {{ buf, elems, count, budget }} => {{"
);
w!(
out,
" if i >= *count {{ return Err(Error::IndexOutOfBounds); }}"
);
w!(
out,
" wire::read_str_budgeted(*buf, *elems + i as u64 * 4, *budget)"
);
w!(out, " }}");
w!(out, " Self::Dynamic(l) => match l.get(i)? {{");
w!(out, " Ref::Str(v) => Ok(v),");
w!(
out,
" other => Err(type_err(\"string\", other.kind())),"
);
w!(out, " }},");
w!(out, " }}");
w!(out, " }}");
w!(out, "}}");
}
fn emit_struct_reader(out: &mut String, schema: &Schema, idx: u16, sd: &StructDef) -> Result<()> {
let name = &sd.name;
let lay = schema.layout_unchecked(idx).as_fixed();
let is_root = idx == schema.root_index();
let used_in_list = struct_used_in_list(schema, idx);
w!(out,);
w!(out, "#[derive(Clone)]");
w!(out, "pub enum {name}Ref<'b, 'r> {{");
w!(
out,
" // `budget` is Some on a bounded (untrusted-input) read: offset-follows"
);
w!(
out,
" // — string/bytes payloads, nested blocks, list regions — charge it, so"
);
w!(
out,
" // aliased offsets can't amplify work (the wire spec §5.2). None = trusted,"
);
w!(
out,
" // zero-cost. Reads within an already-charged block are charge-free."
);
w!(
out,
" Fast {{ buf: &'b [u8], base: u64, budget: Option<&'r Budget> }},"
);
w!(out, " Dynamic(StructReader<'b, 'r>),");
w!(out, "}}");
w!(out,);
w!(out, "impl<'b, 'r> {name}Ref<'b, 'r> {{");
if is_root {
w!(
out,
" /// Open the root struct. Takes the identity fast path (constant"
);
w!(
out,
" /// offsets, resolver untouched) when the message was written with"
);
w!(
out,
" /// exactly this generated schema; otherwise falls back to the"
);
w!(
out,
" /// resolver's access plans, keeping full schema evolution."
);
w!(
out,
" /// **Unbounded** (no traversal-work cap): for trusted input, or"
);
w!(
out,
" /// untrusted input behind [`Self::read_bounded`] / an upstream size cap."
);
w!(
out,
" pub fn read(msg: &Message<'b>, resolver: &'r Resolver) -> Result<Self> {{"
);
w!(out, " if msg.schema_id() == SCHEMA_ID {{");
w!(out, " Ok(Self::Fast {{ buf: msg.buffer(), base: msg.root_offset() as u64, budget: None }})");
w!(out, " }} else {{");
w!(out, " Ok(Self::Dynamic(msg.root(resolver)?))");
w!(out, " }}");
w!(out, " }}");
w!(
out,
" /// Like [`Self::read`], but every offset-follow charges `budget`, so a"
);
w!(
out,
" /// crafted offset-aliasing message trips `TraversalBudgetExceeded`"
);
w!(
out,
" /// instead of amplifying work (the wire spec §5.2). Fast-path getters stay"
);
w!(
out,
" /// constant-offset loads; the only added cost is one budget decrement"
);
w!(
out,
" /// per heap follow. Pair with `Message::suggested_budget()`."
);
w!(out, " pub fn read_bounded(");
w!(out, " msg: &Message<'b>,");
w!(out, " resolver: &'r Resolver,");
w!(out, " budget: &'r Budget,");
w!(out, " ) -> Result<Self> {{");
w!(out, " if msg.schema_id() == SCHEMA_ID {{");
w!(
out,
" budget.charge({})?; // root block",
lay.size
);
w!(out, " Ok(Self::Fast {{ buf: msg.buffer(), base: msg.root_offset() as u64, budget: Some(budget) }})");
w!(out, " }} else {{");
w!(
out,
" Ok(Self::Dynamic(msg.root_bounded(resolver, budget)?))"
);
w!(out, " }}");
w!(out, " }}");
}
for (pos, f) in sd.fields.iter().enumerate() {
let slot = lay.slots[pos];
let fname = &f.name;
let id = f.id;
let guard = if sd.is_dense() {
String::new()
} else {
format!(
"if wire::read_u8(*buf, *base + {})? & {} == 0 {{ return Ok(None); }}\n ",
pos / 8,
1u8 << (pos % 8)
)
};
match &f.ty {
Type::String => {
w!(
out,
" pub fn {fname}(&self) -> Result<Option<&'b str>> {{"
);
w!(out, " match self {{");
w!(out, " Self::Fast {{ buf, base, budget }} => {{");
w!(out, " {guard}Ok(Some(wire::read_str_budgeted(*buf, *base + {slot}, *budget)?))");
w!(out, " }}");
w!(out, " Self::Dynamic(r) => r.get_str({id}),");
w!(out, " }}");
w!(out, " }}");
}
Type::Bytes => {
w!(
out,
" pub fn {fname}(&self) -> Result<Option<&'b [u8]>> {{"
);
w!(out, " match self {{");
w!(out, " Self::Fast {{ buf, base, budget }} => {{");
w!(out, " {guard}Ok(Some(wire::read_bytes_budgeted(*buf, *base + {slot}, *budget)?))");
w!(out, " }}");
w!(out, " Self::Dynamic(r) => r.get_bytes({id}),");
w!(out, " }}");
w!(out, " }}");
}
Type::Struct(ci) => {
let cname = schema.type_name(*ci);
let csize = schema.layout_unchecked(*ci).as_fixed().size;
w!(
out,
" pub fn {fname}(&self) -> Result<Option<{cname}Ref<'b, 'r>>> {{"
);
w!(out, " match self {{");
w!(out, " Self::Fast {{ buf, base, budget }} => {{");
w!(out, " {guard}let off = wire::read_u32(*buf, *base + {slot})? as u64;");
w!(
out,
" wire::charge(*budget, {csize})?; // child block"
);
w!(out, " Ok(Some({cname}Ref::Fast {{ buf: *buf, base: off, budget: *budget }}))");
w!(out, " }}");
w!(out, " Self::Dynamic(r) => Ok(r.get_struct({id})?.map({cname}Ref::Dynamic)),");
w!(out, " }}");
w!(out, " }}");
}
Type::List(elem) => {
let lty = list_ref_type(schema, elem);
let (estride, ealign) = elem_stride_align(schema, elem);
let ctor_budget = match elem.as_ref() {
Type::String | Type::Struct(_) => ", budget: *budget",
_ => "",
};
w!(
out,
" pub fn {fname}(&self) -> Result<Option<{lty}<'b, 'r>>> {{"
);
w!(out, " match self {{");
w!(out, " Self::Fast {{ buf, base, budget }} => {{");
w!(out, " {guard}let (elems, count) = wire::list_header(*buf, *base + {slot}, {ealign})?;");
w!(
out,
" // Charge the element region once at open; element access"
);
w!(out, " // within it is then charge-free (a hostile count trips here).");
w!(
out,
" wire::charge(*budget, 4 + count as u64 * {estride})?;"
);
w!(out, " Ok(Some({lty}::Fast {{ buf: *buf, elems, count{ctor_budget} }}))");
w!(out, " }}");
w!(
out,
" Self::Dynamic(r) => Ok(r.get_list({id})?.map({lty}::Dynamic)),"
);
w!(out, " }}");
w!(out, " }}");
}
other => {
let info = scalar_info(other).expect("scalar");
let rust = info.rust;
let (read, dyn_get) = (info.read, info.dyn_get);
w!(
out,
" pub fn {fname}(&self) -> Result<Option<{rust}>> {{"
);
w!(out, " match self {{");
w!(out, " Self::Fast {{ buf, base, .. }} => {{");
w!(
out,
" {guard}Ok(Some(wire::{read}(*buf, *base + {slot})?))"
);
w!(out, " }}");
w!(out, " Self::Dynamic(r) => r.{dyn_get}({id}),");
w!(out, " }}");
w!(out, " }}");
}
}
}
w!(out, "}}");
if used_in_list {
let (stride, _) = struct_stride_align(schema, idx);
w!(out,);
w!(out, "#[derive(Clone)]");
w!(out, "pub enum {name}List<'b, 'r> {{");
w!(
out,
" // The element region was charged when the list was opened; the budget"
);
w!(
out,
" // rides along so element refs can charge *their* offset-follows."
);
w!(
out,
" Fast {{ buf: &'b [u8], elems: u64, count: u32, budget: Option<&'r Budget> }},"
);
w!(out, " Dynamic(ListReader<'b, 'r>),");
w!(out, "}}");
w!(out,);
w!(out, "impl<'b, 'r> {name}List<'b, 'r> {{");
w!(out, " pub fn len(&self) -> u32 {{");
w!(out, " match self {{ Self::Fast {{ count, .. }} => *count, Self::Dynamic(l) => l.len() }}");
w!(out, " }}");
w!(
out,
" pub fn is_empty(&self) -> bool {{ self.len() == 0 }}"
);
w!(
out,
" pub fn get(&self, i: u32) -> Result<{name}Ref<'b, 'r>> {{"
);
w!(out, " match self {{");
w!(
out,
" Self::Fast {{ buf, elems, count, budget }} => {{"
);
w!(
out,
" if i >= *count {{ return Err(Error::IndexOutOfBounds); }}"
);
w!(out, " Ok({name}Ref::Fast {{ buf: *buf, base: *elems + i as u64 * {stride}, budget: *budget }})");
w!(out, " }}");
w!(out, " Self::Dynamic(l) => match l.get(i)? {{");
w!(
out,
" Ref::Struct(s) => Ok({name}Ref::Dynamic(s)),"
);
w!(
out,
" other => Err(type_err(\"struct\", other.kind())),"
);
w!(out, " }},");
w!(out, " }}");
w!(out, " }}");
let all_fixed = sd.fields.iter().all(|f| scalar_info(&f.ty).is_some());
if all_fixed {
w!(
out,
" /// Fast bulk scan on the identity path: validate the element region"
);
w!(
out,
" /// once, then yield infallible `{name}Block`s. `None` on the evolved"
);
w!(out, " /// path (use `get`/`iter`) or a corrupt region.");
w!(
out,
" pub fn blocks(&self) -> Option<impl Iterator<Item = {name}Block<'b>> + 'b> {{"
);
w!(out, " match self {{");
w!(
out,
" Self::Fast {{ buf, elems, count, .. }} => {{"
);
w!(out, " let start = *elems as usize;");
w!(out, " (*count as usize)");
w!(out, " .checked_mul({stride})");
w!(
out,
" .and_then(|n| start.checked_add(n))"
);
w!(
out,
" .and_then(|end| buf.get(start..end))"
);
w!(out, " .map(|region| region.chunks_exact({stride}).map(|c| {name}Block(c.try_into().unwrap())))");
w!(out, " }}");
w!(out, " Self::Dynamic(_) => None,");
w!(out, " }}");
w!(out, " }}");
}
w!(
out,
" /// General fallible iterator (fast + dynamic). Prefer `blocks()` for a"
);
w!(out, " /// hot scan on the identity path.");
w!(
out,
" pub fn iter(&self) -> impl Iterator<Item = Result<{name}Ref<'b, 'r>>> + '_ {{"
);
w!(out, " (0..self.len()).map(move |i| self.get(i))");
w!(out, " }}");
w!(out, "}}");
if all_fixed {
emit_struct_block(out, schema, idx, sd, stride);
}
}
Ok(())
}
fn emit_struct_block(out: &mut String, schema: &Schema, idx: u16, sd: &StructDef, stride: u32) {
let name = &sd.name;
let lay = schema.layout_unchecked(idx).as_fixed();
w!(out,);
w!(out, "#[derive(Clone, Copy)]");
w!(out, "pub struct {name}Block<'b>(&'b [u8; {stride}]);");
w!(out,);
w!(out, "impl<'b> {name}Block<'b> {{");
for (pos, f) in sd.fields.iter().enumerate() {
let info = scalar_info(&f.ty).expect("all_fixed");
let rust = info.rust;
let slot = lay.slots[pos] as usize;
let sz = info.size as usize;
let read = if rust == "bool" {
format!("self.0[{slot}] != 0")
} else if sz == 1 {
format!("self.0[{slot}] as {rust}")
} else {
format!(
"{rust}::from_le_bytes(self.0[{slot}..{}].try_into().unwrap())",
slot + sz
)
};
let fname = &f.name;
if sd.is_dense() {
w!(out, " pub fn {fname}(&self) -> {rust} {{ {read} }}");
} else {
let (byte, mask) = (pos / 8, 1u8 << (pos % 8));
w!(out, " pub fn {fname}(&self) -> Option<{rust}> {{");
w!(
out,
" if self.0[{byte}] & {mask} == 0 {{ return None; }}"
);
w!(out, " Some({read})");
w!(out, " }}");
}
}
w!(out, "}}");
}
fn struct_used_in_list(schema: &Schema, idx: u16) -> bool {
for i in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(i) {
for f in &sd.fields {
if matches!(&f.ty, Type::List(e) if matches!(e.as_ref(), Type::Struct(ci) if *ci == idx))
{
return true;
}
}
}
}
false
}
fn struct_stride_align(schema: &Schema, idx: u16) -> (u32, u32) {
let lay = schema.layout_unchecked(idx).as_fixed();
(lay.size, lay.align)
}
fn elem_stride_align(schema: &Schema, elem: &Type) -> (u32, u32) {
match elem {
Type::Struct(i) => struct_stride_align(schema, *i),
Type::String => (4, 4),
other => {
let info = scalar_info(other).expect("scalar");
(info.size, info.align)
}
}
}
fn emit_struct_writer(out: &mut String, schema: &Schema, idx: u16, sd: &StructDef) -> Result<()> {
let name = &sd.name;
let fn_name = snake(name);
let lay = schema.layout_unchecked(idx).as_fixed();
let needs_lt = args_needs_lifetime(schema, idx);
let lt_decl = if needs_lt { "<'a>" } else { "" };
let is_root = idx == schema.root_index();
w!(out,);
if sd.is_dense() {
w!(
out,
"/// Typed writer args for dense struct `{name}` (all fields required)."
);
} else {
w!(
out,
"/// Typed writer args for `{name}` (`None` = field absent)."
);
}
w!(out, "pub struct {name}Args{lt_decl} {{");
for f in &sd.fields {
let fty = args_field_type(schema, &f.ty, sd.is_dense());
w!(out, " pub {}: {},", f.name, fty);
}
w!(out, "}}");
let args_ty = if needs_lt {
format!("{name}Args<'_>")
} else {
format!("{name}Args")
};
w!(out,);
w!(
out,
"fn fill_{fn_name}_at(buf: &mut Vec<u8>, base: u32, args: &{args_ty}) -> Result<()> {{"
);
for (pos, f) in sd.fields.iter().enumerate() {
let slot = lay.slots[pos];
let (open, val, indent, close) = if sd.is_dense() {
(
String::new(),
format!("args.{}", f.name),
" ",
String::new(),
)
} else {
(
format!(
" if let Some(v) = args.{} {{\n wire::set_presence_bit(buf, base, {pos})?;",
f.name
),
"v".to_string(),
" ",
" }".to_string(),
)
};
if !open.is_empty() {
w!(out, "{open}");
}
match &f.ty {
Type::String => {
w!(
out,
"{indent}let off = wire::write_blob(buf, {val}.as_bytes())?;"
);
w!(out, "{indent}wire::patch_u32(buf, base + {slot}, off)?;");
}
Type::Bytes => {
w!(out, "{indent}let off = wire::write_blob(buf, {val})?;");
w!(out, "{indent}wire::patch_u32(buf, base + {slot}, off)?;");
}
Type::Struct(ci) => {
let cfn = snake(schema.type_name(*ci));
w!(out, "{indent}let off = write_{cfn}(buf, {val})?;");
w!(out, "{indent}wire::patch_u32(buf, base + {slot}, off)?;");
}
Type::List(elem) => {
emit_list_write(out, schema, elem, &val, slot, indent)?;
}
other => {
let info = scalar_info(other).expect("scalar");
w!(
out,
"{indent}wire::{}(buf, base + {slot}, {val})?;",
info.put
);
}
}
if !close.is_empty() {
w!(out, "{close}");
}
}
w!(out, " Ok(())");
w!(out, "}}");
w!(out,);
w!(
out,
"fn write_{fn_name}(buf: &mut Vec<u8>, args: &{args_ty}) -> Result<u32> {{"
);
w!(
out,
" let base = wire::alloc_block(buf, {}, {})?;",
lay.size,
lay.align
);
w!(out, " fill_{fn_name}_at(buf, base, args)?;");
w!(out, " Ok(base)");
w!(out, "}}");
if is_root {
w!(out,);
w!(
out,
"/// Encode a message directly from typed args — single pass, no"
);
w!(out, "/// dynamic value tree.");
w!(
out,
"pub fn encode_{fn_name}(args: &{args_ty}, mode: SchemaMode) -> Result<Vec<u8>> {{"
);
w!(out, " let inline = matches!(mode, SchemaMode::Inline);");
w!(out, " let mut buf = wire::message_header(");
w!(out, " SCHEMA_ID,");
w!(
out,
" if inline {{ Some(SCHEMA_BYTES) }} else {{ None }},"
);
w!(
out,
" {},",
24 + schema.canonical_bytes().len() + lay.size as usize + 232
);
w!(out, " )?;");
w!(out, " let root = write_{fn_name}(&mut buf, args)?;");
w!(out, " wire::finish_message(&mut buf, root);");
w!(out, " Ok(buf)");
w!(out, "}}");
}
Ok(())
}
fn emit_list_write(
out: &mut String,
schema: &Schema,
elem: &Type,
val: &str,
slot: u32,
indent: &str,
) -> Result<()> {
let (stride, align) = elem_stride_align(schema, elem);
w!(
out,
"{indent}let count = u32::try_from({val}.len()).map_err(|_| Error::MessageTooLarge)?;"
);
w!(
out,
"{indent}let loff = wire::begin_list(buf, count, {align})?;"
);
match elem {
Type::Struct(ci) => {
let sd = schema.struct_def_unchecked(*ci);
let dense_scalar =
sd.is_dense() && sd.fields.iter().all(|f| scalar_info(&f.ty).is_some());
if dense_scalar {
let elay = schema.layout_unchecked(*ci).as_fixed();
w!(out, "{indent}let total = {val}.len().checked_mul({stride}).ok_or(Error::MessageTooLarge)?;");
w!(out, "{indent}let ebase = wire::alloc_bytes(buf, total)?;");
w!(out, "{indent}let region = &mut buf[ebase as usize..];");
w!(out, "{indent}for (i, a) in {val}.iter().enumerate() {{");
w!(out, "{indent} let e = i * {stride};");
for (pos, f) in sd.fields.iter().enumerate() {
let fslot = elay.slots[pos];
if matches!(f.ty, Type::Bool) {
w!(out, "{indent} region[e + {fslot}] = a.{} as u8;", f.name);
} else {
let n = scalar_info(&f.ty).expect("scalar").size;
w!(out, "{indent} region[e + {fslot}..e + {fslot} + {n}].copy_from_slice(&a.{}.to_le_bytes());", f.name);
}
}
w!(out, "{indent}}}");
} else {
let cfn = snake(schema.type_name(*ci));
w!(out, "{indent}let total = {val}.len().checked_mul({stride}).ok_or(Error::MessageTooLarge)?;");
w!(out, "{indent}let ebase = wire::alloc_bytes(buf, total)?;");
w!(out, "{indent}for (i, a) in {val}.iter().enumerate() {{");
w!(
out,
"{indent} fill_{cfn}_at(buf, ebase + i as u32 * {stride}, a)?;"
);
w!(out, "{indent}}}");
}
}
Type::String => {
w!(
out,
"{indent}let total = {val}.len().checked_mul(4).ok_or(Error::MessageTooLarge)?;"
);
w!(out, "{indent}let slots = wire::alloc_bytes(buf, total)?;");
w!(out, "{indent}for (i, s) in {val}.iter().enumerate() {{");
w!(
out,
"{indent} let off = wire::write_blob(buf, s.as_bytes())?;"
);
w!(
out,
"{indent} wire::patch_u32(buf, slots + i as u32 * 4, off)?;"
);
w!(out, "{indent}}}");
}
other => {
let info = scalar_info(other).expect("scalar");
w!(out, "{indent}wire::{}(buf, {val});", info.push_slice);
}
}
w!(out, "{indent}wire::patch_u32(buf, base + {slot}, loff)?;");
Ok(())
}
fn args_field_type(schema: &Schema, ty: &Type, dense: bool) -> String {
let inner = match ty {
Type::String => "&'a str".to_string(),
Type::Bytes => "&'a [u8]".to_string(),
Type::Struct(i) => {
let cname = schema.type_name(*i);
if args_needs_lifetime(schema, *i) {
format!("&'a {cname}Args<'a>")
} else {
format!("&'a {cname}Args")
}
}
Type::List(elem) => match elem.as_ref() {
Type::Struct(i) => {
let cname = schema.type_name(*i);
if args_needs_lifetime(schema, *i) {
format!("&'a [{cname}Args<'a>]")
} else {
format!("&'a [{cname}Args]")
}
}
Type::String => "&'a [&'a str]".to_string(),
other => format!("&'a [{}]", scalar_info(other).expect("scalar").rust),
},
other => scalar_info(other).expect("scalar").rust.to_string(),
};
if dense {
inner
} else {
format!("Option<{inner}>")
}
}
fn cpp_base_type(schema: &Schema, ty: &Type) -> String {
match ty {
Type::Bool => "bool".into(),
Type::U8 => "uint8_t".into(),
Type::U16 => "uint16_t".into(),
Type::U32 => "uint32_t".into(),
Type::U64 => "uint64_t".into(),
Type::I8 => "int8_t".into(),
Type::I16 => "int16_t".into(),
Type::I32 => "int32_t".into(),
Type::I64 => "int64_t".into(),
Type::F32 => "float".into(),
Type::F64 => "double".into(),
Type::Enum(_) => "uint32_t".into(),
Type::String => "std::string".into(),
Type::Bytes => "std::vector<uint8_t>".into(),
Type::Struct(ci) => schema.type_name(*ci).to_string(),
Type::List(elem) => format!("std::vector<{}>", cpp_base_type(schema, elem)),
Type::Map(_, _) | Type::Union(_) => {
unreachable!("map/union types are rejected by reject_maps before emission")
}
}
}
fn cpp_pack_expr(ty: &Type, e: &str, depth: u32) -> String {
match ty {
Type::Bool => format!("veritate::Value::boolean({e})"),
Type::U8 | Type::U16 | Type::U32 | Type::U64 => {
format!("veritate::Value::u64(uint64_t({e}))")
}
Type::I8 | Type::I16 | Type::I32 | Type::I64 => {
format!("veritate::Value::i64(int64_t({e}))")
}
Type::F32 | Type::F64 => format!("veritate::Value::f64(double({e}))"),
Type::Enum(_) => format!("veritate::Value::en({e})"),
Type::String => format!("veritate::Value::str({e})"),
Type::Bytes => format!("veritate::Value::of_bytes({e})"),
Type::Struct(_) => format!("({e})._pack()"),
Type::List(elem) => {
let inner = cpp_pack_expr(elem, &format!("__x{depth}"), depth + 1);
format!(
"[&]{{ std::vector<veritate::Value> __l{depth}; for (auto& __x{depth} : {e}) \
__l{depth}.push_back({inner}); return veritate::Value::of_list(std::move(__l{depth})); }}()"
)
}
Type::Map(_, _) | Type::Union(_) => {
unreachable!("map/union types are rejected by reject_maps before emission")
}
}
}
fn cpp_unpack_expr(schema: &Schema, ty: &Type, r: &str, depth: u32) -> String {
match ty {
Type::Bool => format!("{r}.b"),
Type::U8 => format!("uint8_t({r}.u)"),
Type::U16 => format!("uint16_t({r}.u)"),
Type::U32 => format!("uint32_t({r}.u)"),
Type::U64 => format!("uint64_t({r}.u)"),
Type::I8 => format!("int8_t({r}.i)"),
Type::I16 => format!("int16_t({r}.i)"),
Type::I32 => format!("int32_t({r}.i)"),
Type::I64 => format!("int64_t({r}.i)"),
Type::F32 => format!("float({r}.d)"),
Type::F64 => format!("double({r}.d)"),
Type::Enum(_) => format!("{r}.e"),
Type::String => format!("std::string({r}.str)"),
Type::Bytes => {
format!("std::vector<uint8_t>({r}.bytes_ptr, {r}.bytes_ptr + {r}.bytes_len)")
}
Type::Struct(ci) => format!("{}::_unpack(*{r}.st)", schema.type_name(*ci)),
Type::List(elem) => {
let base = cpp_base_type(schema, elem);
let inner = cpp_unpack_expr(schema, elem, &format!("__e{depth}"), depth + 1);
format!(
"[&]{{ std::vector<{base}> __o{depth}; for (uint32_t __i{depth} = 0; \
__i{depth} < {r}.ls->size(); __i{depth}++) {{ auto __e{depth} = {r}.ls->get(__i{depth}); \
__o{depth}.push_back({inner}); }} return __o{depth}; }}()"
)
}
Type::Map(_, _) | Type::Union(_) => {
unreachable!("map/union types are rejected by reject_maps before emission")
}
}
}
fn cpp_struct_order(schema: &Schema) -> Vec<u16> {
let mut order = Vec::new();
let mut visited = BTreeSet::new();
fn visit(schema: &Schema, idx: u16, visited: &mut BTreeSet<u16>, order: &mut Vec<u16>) {
if !visited.insert(idx) {
return;
}
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
for f in &sd.fields {
if let Type::Struct(ci) = &f.ty {
visit(schema, *ci, visited, order);
}
}
order.push(idx);
}
}
for idx in 0..schema.type_count() {
if matches!(schema.type_def(idx), Some(TypeDef::Struct(_))) {
visit(schema, idx, &mut visited, &mut order);
}
}
order
}
pub fn generate_cpp(schema: &Schema) -> Result<String> {
reject_maps(schema)?;
for idx in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
check_ident(&sd.name)?;
for f in &sd.fields {
check_ident(&f.name)?;
}
}
}
let mut out = String::new();
w!(out, "// @generated by verit::codegen — do not edit.");
w!(out, "// schema id: {:#034x}", schema.id());
w!(out, "#pragma once");
w!(out, "#include <cstdint>");
w!(out, "#include <optional>");
w!(out, "#include <string>");
w!(out, "#include <vector>");
w!(out, "#include \"veritate.hpp\"");
w!(out,);
w!(out, "namespace veritgen {{");
w!(out,);
let bytes: Vec<String> = schema
.canonical_bytes()
.iter()
.map(|b| b.to_string())
.collect();
w!(
out,
"inline const char* SCHEMA_ID = \"{:032x}\";",
schema.id()
);
w!(out, "inline const std::vector<uint8_t>& SCHEMA_BYTES() {{");
w!(
out,
" static const std::vector<uint8_t> b = {{{}}};",
bytes.join(", ")
);
w!(out, " return b;");
w!(out, "}}");
w!(out, "inline const veritate::Schema& _schema() {{");
w!(
out,
" static veritate::Schema s = veritate::Schema::from_canonical(SCHEMA_BYTES());"
);
w!(out, " return s;");
w!(out, "}}");
w!(out, "inline const veritate::Resolver& _resolver() {{");
w!(
out,
" static veritate::Resolver r = veritate::Resolver::identity(_schema());"
);
w!(out, " return r;");
w!(out, "}}");
w!(out,);
for idx in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
w!(out, "struct {};", sd.name);
}
}
w!(out,);
let order = cpp_struct_order(schema);
for &idx in &order {
let sd = schema.struct_def_unchecked(idx);
w!(out, "struct {} {{", sd.name);
for f in &sd.fields {
w!(
out,
" std::optional<{}> {};",
cpp_base_type(schema, &f.ty),
f.name
);
}
w!(out, " veritate::Value _pack() const;");
w!(
out,
" static {} _unpack(const veritate::StructReader& __r);",
sd.name
);
w!(
out,
" std::vector<uint8_t> to_verit(veritate::SchemaMode mode = veritate::SchemaMode::Inline) const;"
);
w!(
out,
" static {} from_verit(const std::vector<uint8_t>& __buf);",
sd.name
);
w!(out, "}};");
w!(out,);
}
for &idx in &order {
let sd = schema.struct_def_unchecked(idx);
let name = &sd.name;
w!(out, "inline veritate::Value {name}::_pack() const {{");
w!(
out,
" std::vector<std::pair<uint16_t, veritate::Value>> __f;"
);
for f in &sd.fields {
let e = cpp_pack_expr(&f.ty, &format!("*{}", f.name), 0);
w!(
out,
" if ({}.has_value()) __f.push_back({{{}, {e}}});",
f.name,
f.id
);
}
w!(out, " return veritate::Value::strct(std::move(__f));");
w!(out, "}}");
w!(
out,
"inline {name} {name}::_unpack(const veritate::StructReader& __r) {{"
);
w!(out, " {name} __out;");
for f in &sd.fields {
let val = cpp_unpack_expr(schema, &f.ty, "(*__v)", 0);
w!(
out,
" {{ auto __v = __r.get({}); if (__v) __out.{} = {val}; }}",
f.id,
f.name
);
}
w!(out, " return __out;");
w!(out, "}}");
w!(
out,
"inline std::vector<uint8_t> {name}::to_verit(veritate::SchemaMode mode) const {{"
);
w!(
out,
" return veritate::encode(_schema(), _pack(), mode);"
);
w!(out, "}}");
w!(
out,
"inline {name} {name}::from_verit(const std::vector<uint8_t>& __buf) {{"
);
w!(
out,
" veritate::Message __msg = veritate::Message::parse(__buf);"
);
w!(
out,
" veritate::StructReader __root = __msg.root(_resolver());"
);
w!(out, " return _unpack(__root);");
w!(out, "}}");
w!(out,);
}
w!(out, "}} // namespace veritgen");
Ok(out)
}
fn py_desc(schema: &Schema, ty: &Type) -> String {
match ty {
Type::Struct(ci) => format!("(\"struct\", {})", schema.type_name(*ci)),
Type::List(elem) => format!("(\"list\", {})", py_desc(schema, elem)),
_ => "(\"leaf\", None)".to_string(),
}
}
fn py_hint(schema: &Schema, ty: &Type) -> String {
match ty {
Type::Bool => "bool".into(),
Type::F32 | Type::F64 => "float".into(),
Type::String => "str".into(),
Type::Bytes => "bytes".into(),
Type::Struct(ci) => schema.type_name(*ci).to_string(),
Type::List(_) => "list".into(),
_ => "int".into(), }
}
pub fn generate_python(schema: &Schema) -> Result<String> {
reject_maps(schema)?;
for idx in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
check_ident(&sd.name)?;
for f in &sd.fields {
check_ident(&f.name)?;
}
}
}
let mut out = String::new();
w!(out, "# @generated by verit::codegen — do not edit.");
w!(out, "# schema id: {:#034x}", schema.id());
w!(out, "from __future__ import annotations");
w!(out, "from dataclasses import dataclass");
w!(out, "import veritate as _v");
w!(out,);
let bytes: Vec<String> = schema
.canonical_bytes()
.iter()
.map(|b| b.to_string())
.collect();
w!(out, "SCHEMA_ID = \"{:032x}\"", schema.id());
w!(out, "SCHEMA_BYTES = bytes([{}])", bytes.join(", "));
w!(out, "_SCHEMA = _v.Schema.from_canonical(SCHEMA_BYTES)");
w!(out, "_RESOLVER = _v.Resolver.identity(_SCHEMA)");
w!(out,);
w!(out, "def _pack(obj):");
w!(out, " out = {{}}");
w!(out, " for (attr, fid, desc) in obj._VERIT:");
w!(out, " v = getattr(obj, attr)");
w!(out, " if v is not None:");
w!(out, " out[fid] = _pack_val(v, desc)");
w!(out, " return out");
w!(out,);
w!(out, "def _pack_val(v, desc):");
w!(out, " kind, extra = desc");
w!(out, " if kind == \"struct\":");
w!(out, " return _pack(v)");
w!(out, " if kind == \"list\":");
w!(out, " return [_pack_val(x, extra) for x in v]");
w!(out, " return v");
w!(out,);
w!(out, "def _unpack(cls, reader):");
w!(out, " kwargs = {{}}");
w!(out, " for (attr, fid, desc) in cls._VERIT:");
w!(out, " val = reader.get(fid)");
w!(
out,
" kwargs[attr] = None if val is None else _unpack_val(val, desc)"
);
w!(out, " return cls(**kwargs)");
w!(out,);
w!(out, "def _unpack_val(val, desc):");
w!(out, " kind, extra = desc");
w!(out, " if kind == \"struct\":");
w!(out, " return _unpack(extra, val)");
w!(out, " if kind == \"list\":");
w!(
out,
" return [_unpack_val(val.get(i), extra) for i in range(len(val))]"
);
w!(out, " return val");
w!(out,);
for idx in 0..schema.type_count() {
let sd = match schema.type_def(idx) {
Some(TypeDef::Struct(sd)) => sd,
_ => continue,
};
w!(out, "@dataclass");
w!(out, "class {}:", sd.name);
if sd.fields.is_empty() {
w!(out, " pass");
}
for f in &sd.fields {
w!(out, " {}: {} = None", f.name, py_hint(schema, &f.ty));
}
w!(out, " def to_verit(self, mode=_v.INLINE) -> bytes:");
w!(out, " return _v.encode(_SCHEMA, _pack(self), mode)");
w!(out, " @classmethod");
w!(out, " def from_verit(cls, buf) -> \"{}\":", sd.name);
w!(out, " msg = _v.Message.parse(buf)");
w!(out, " return _unpack(cls, msg.root(_RESOLVER))");
w!(out,);
}
for idx in 0..schema.type_count() {
let sd = match schema.type_def(idx) {
Some(TypeDef::Struct(sd)) => sd,
_ => continue,
};
let entries: Vec<String> = sd
.fields
.iter()
.map(|f| format!("(\"{}\", {}, {})", f.name, f.id, py_desc(schema, &f.ty)))
.collect();
w!(out, "{}._VERIT = [{}]", sd.name, entries.join(", "));
}
Ok(out)
}
fn ts_desc(schema: &Schema, ty: &Type) -> String {
match ty {
Type::Struct(ci) => format!("[\"struct\", {}]", schema.type_name(*ci)),
Type::List(elem) => format!("[\"list\", {}]", ts_desc(schema, elem)),
_ => "[\"leaf\", null]".to_string(),
}
}
fn ts_hint(schema: &Schema, ty: &Type) -> String {
match ty {
Type::Bool => "boolean".into(),
Type::U64 | Type::I64 => "bigint".into(),
Type::F32 | Type::F64 => "number".into(),
Type::String => "string".into(),
Type::Bytes => "Uint8Array".into(),
Type::Struct(ci) => schema.type_name(*ci).to_string(),
Type::List(elem) => format!("Array<{}>", ts_hint(schema, elem)),
_ => "number".into(), }
}
pub fn generate_ts(schema: &Schema) -> Result<String> {
reject_maps(schema)?;
for idx in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
check_ident(&sd.name)?;
for f in &sd.fields {
check_ident(&f.name)?;
}
}
}
let mut out = String::new();
w!(out, "// @generated by verit::codegen — do not edit.");
w!(out, "// schema id: {:#034x}", schema.id());
w!(out, "import * as _v from \"./veritate.ts\";");
w!(out,);
let bytes: Vec<String> = schema
.canonical_bytes()
.iter()
.map(|b| b.to_string())
.collect();
w!(out, "export const SCHEMA_ID = \"{:032x}\";", schema.id());
w!(
out,
"export const SCHEMA_BYTES = new Uint8Array([{}]);",
bytes.join(", ")
);
w!(
out,
"const _SCHEMA = _v.Schema.fromCanonical(SCHEMA_BYTES);"
);
w!(out, "const _RESOLVER = _v.Resolver.identity(_SCHEMA);");
w!(out,);
w!(
out,
"type _Desc = readonly [\"leaf\", null] | readonly [\"struct\", any] | readonly [\"list\", _Desc];"
);
w!(out,);
w!(out, "function _pack(obj: any): _v.StructVal {{");
w!(out, " const out: Array<[number, _v.Value]> = [];");
w!(
out,
" for (const [attr, fid, desc] of obj.constructor._VERIT as Array<[string, number, _Desc]>) {{"
);
w!(out, " const v = obj[attr];");
w!(
out,
" if (v !== null && v !== undefined) out.push([fid, _packVal(v, desc)]);"
);
w!(out, " }}");
w!(out, " return _v.st(out);");
w!(out, "}}");
w!(out, "function _packVal(v: any, desc: _Desc): _v.Value {{");
w!(out, " if (desc[0] === \"struct\") return _pack(v);");
w!(
out,
" if (desc[0] === \"list\") return (v as any[]).map((x) => _packVal(x, desc[1]));"
);
w!(out, " return v;");
w!(out, "}}");
w!(out, "function _unpack(cls: any, reader: any): any {{");
w!(out, " const obj = new cls();");
w!(
out,
" for (const [attr, fid, desc] of cls._VERIT as Array<[string, number, _Desc]>) {{"
);
w!(out, " const val = reader.get(fid);");
w!(
out,
" obj[attr] = val === null || val === undefined ? null : _unpackVal(val, desc);"
);
w!(out, " }}");
w!(out, " return obj;");
w!(out, "}}");
w!(out, "function _unpackVal(val: any, desc: _Desc): any {{");
w!(
out,
" if (desc[0] === \"struct\") return _unpack(desc[1], val);"
);
w!(out, " if (desc[0] === \"list\") {{");
w!(out, " const out: any[] = [];");
w!(
out,
" for (let i = 0; i < val.length; i++) out.push(_unpackVal(val.get(i), desc[1]));"
);
w!(out, " return out;");
w!(out, " }}");
w!(out, " return val;");
w!(out, "}}");
w!(out,);
for idx in 0..schema.type_count() {
let sd = match schema.type_def(idx) {
Some(TypeDef::Struct(sd)) => sd,
_ => continue,
};
w!(out, "export class {} {{", sd.name);
for f in &sd.fields {
w!(
out,
" {}: {} | null = null;",
f.name,
ts_hint(schema, &f.ty)
);
}
w!(out, " static _VERIT: Array<[string, number, _Desc]> = [];");
w!(
out,
" toVerit(mode: _v.SchemaMode = \"inline\"): Uint8Array {{ return _v.encode(_SCHEMA, _pack(this), mode); }}"
);
w!(
out,
" static fromVerit(buf: Uint8Array): {} {{ return _unpack({}, _v.Message.parse(buf).root(_RESOLVER)); }}",
sd.name,
sd.name
);
w!(out, "}}");
}
w!(out,);
for idx in 0..schema.type_count() {
let sd = match schema.type_def(idx) {
Some(TypeDef::Struct(sd)) => sd,
_ => continue,
};
let entries: Vec<String> = sd
.fields
.iter()
.map(|f| format!("[\"{}\", {}, {}]", f.name, f.id, ts_desc(schema, &f.ty)))
.collect();
w!(out, "{}._VERIT = [{}];", sd.name, entries.join(", "));
}
Ok(out)
}
fn go_export(name: &str) -> String {
let mut c = name.chars();
match c.next() {
Some(f) => f.to_ascii_uppercase().to_string() + c.as_str(),
None => String::new(),
}
}
fn go_elem_type(schema: &Schema, ty: &Type) -> String {
match ty {
Type::Bool => "bool".into(),
Type::U8 => "uint8".into(),
Type::U16 => "uint16".into(),
Type::U32 => "uint32".into(),
Type::U64 => "uint64".into(),
Type::I8 => "int8".into(),
Type::I16 => "int16".into(),
Type::I32 => "int32".into(),
Type::I64 => "int64".into(),
Type::F32 => "float32".into(),
Type::F64 => "float64".into(),
Type::Enum(_) => "uint32".into(),
Type::String => "string".into(),
Type::Bytes => "[]byte".into(),
Type::Struct(ci) => schema.type_name(*ci).to_string(),
Type::List(elem) => format!("[]{}", go_elem_type(schema, elem)),
Type::Map(_, _) | Type::Union(_) => {
unreachable!("map/union types are rejected by reject_maps before emission")
}
}
}
fn go_field_type(schema: &Schema, ty: &Type) -> String {
match ty {
Type::Bytes => "[]byte".into(),
Type::List(elem) => format!("[]{}", go_elem_type(schema, elem)),
Type::Struct(ci) => format!("*{}", schema.type_name(*ci)),
other => format!("*{}", go_elem_type(schema, other)),
}
}
fn go_desc(schema: &Schema, ty: &Type) -> String {
match ty {
Type::Struct(ci) => format!(
"_desc{{kind: 1, typ: reflect.TypeOf({}{{}})}}",
schema.type_name(*ci)
),
Type::List(elem) => {
format!("_desc{{kind: 2, sub: &{}}}", go_desc(schema, elem))
}
_ => "_desc{kind: 0}".to_string(),
}
}
pub fn generate_go(schema: &Schema) -> Result<String> {
reject_maps(schema)?;
for idx in 0..schema.type_count() {
if let Some(TypeDef::Struct(sd)) = schema.type_def(idx) {
check_ident(&sd.name)?;
for f in &sd.fields {
check_ident(&f.name)?;
}
}
}
let mut out = String::new();
w!(out, "// @generated by verit::codegen — do not edit.");
w!(out, "// schema id: {:#034x}", schema.id());
w!(out, "package veritgen");
w!(out,);
w!(out, "import (");
w!(out, "\t\"reflect\"");
w!(out,);
w!(out, "\tv \"veritate\"");
w!(out, ")");
w!(out,);
let bytes: Vec<String> = schema
.canonical_bytes()
.iter()
.map(|b| b.to_string())
.collect();
w!(out, "const SchemaID = \"{:032x}\"", schema.id());
w!(out,);
w!(out, "var SchemaBytes = []byte{{{}}}", bytes.join(", "));
w!(out,);
w!(out, "var (");
w!(out, "\t_schema *v.Schema");
w!(out, "\t_resolver *v.Resolver");
w!(out, "\t_specs = map[reflect.Type][]_fld{{}}");
w!(out, ")");
w!(out,);
w!(out, "// _desc.kind: 0 leaf, 1 struct, 2 list.");
w!(out, "type _desc struct {{");
w!(out, "\tkind int");
w!(out, "\tsub *_desc");
w!(out, "\ttyp reflect.Type");
w!(out, "}}");
w!(out, "type _fld struct {{");
w!(out, "\tfield string");
w!(out, "\tid uint16");
w!(out, "\td _desc");
w!(out, "}}");
w!(out,);
w!(out, "func _absent(fv reflect.Value) bool {{");
w!(out, "\tswitch fv.Kind() {{");
w!(
out,
"\tcase reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface:"
);
w!(out, "\t\treturn fv.IsNil()");
w!(out, "\tdefault:");
w!(out, "\t\treturn false");
w!(out, "\t}}");
w!(out, "}}");
w!(
out,
"func _conv(x reflect.Value, t reflect.Type) reflect.Value {{"
);
w!(out, "\tif x.Type() == t {{");
w!(out, "\t\treturn x");
w!(out, "\t}}");
w!(out, "\tif x.Type().ConvertibleTo(t) {{");
w!(out, "\t\treturn x.Convert(t)");
w!(out, "\t}}");
w!(out, "\treturn x");
w!(out, "}}");
w!(out, "func _packObj(rv reflect.Value) v.Struct {{");
w!(out, "\tif rv.Kind() == reflect.Ptr {{");
w!(out, "\t\trv = rv.Elem()");
w!(out, "\t}}");
w!(out, "\tvar out []v.KV");
w!(out, "\tfor _, f := range _specs[rv.Type()] {{");
w!(out, "\t\tfv := rv.FieldByName(f.field)");
w!(out, "\t\tif _absent(fv) {{");
w!(out, "\t\t\tcontinue");
w!(out, "\t\t}}");
w!(
out,
"\t\tout = append(out, v.KV{{ID: f.id, V: _packVal(fv, f.d)}})"
);
w!(out, "\t}}");
w!(out, "\treturn v.St(out...)");
w!(out, "}}");
w!(out, "func _packVal(fv reflect.Value, d _desc) any {{");
w!(out, "\tif fv.Kind() == reflect.Ptr {{");
w!(out, "\t\tfv = fv.Elem()");
w!(out, "\t}}");
w!(out, "\tswitch d.kind {{");
w!(out, "\tcase 1:");
w!(out, "\t\treturn _packObj(fv)");
w!(out, "\tcase 2:");
w!(out, "\t\tn := fv.Len()");
w!(out, "\t\tarr := make([]any, n)");
w!(out, "\t\tfor i := 0; i < n; i++ {{");
w!(out, "\t\t\tarr[i] = _packVal(fv.Index(i), *d.sub)");
w!(out, "\t\t}}");
w!(out, "\t\treturn arr");
w!(out, "\tdefault:");
w!(out, "\t\treturn fv.Interface()");
w!(out, "\t}}");
w!(out, "}}");
w!(
out,
"func _unpackObj(rt reflect.Type, r *v.StructReader) reflect.Value {{"
);
w!(out, "\tptr := reflect.New(rt)");
w!(out, "\trv := ptr.Elem()");
w!(out, "\tfor _, f := range _specs[rt] {{");
w!(out, "\t\tval, ok := r.Get(f.id)");
w!(out, "\t\tif !ok {{");
w!(out, "\t\t\tcontinue");
w!(out, "\t\t}}");
w!(out, "\t\t_set(rv.FieldByName(f.field), f.d, val)");
w!(out, "\t}}");
w!(out, "\treturn ptr");
w!(out, "}}");
w!(out, "func _set(dst reflect.Value, d _desc, val any) {{");
w!(out, "\tswitch d.kind {{");
w!(out, "\tcase 1:");
w!(out, "\t\tsr := val.(*v.StructReader)");
w!(out, "\t\tet := dst.Type()");
w!(out, "\t\tif et.Kind() == reflect.Ptr {{");
w!(out, "\t\t\tet = et.Elem()");
w!(out, "\t\t}}");
w!(out, "\t\tobj := _unpackObj(et, sr)");
w!(out, "\t\tif dst.Kind() == reflect.Ptr {{");
w!(out, "\t\t\tdst.Set(obj)");
w!(out, "\t\t}} else {{");
w!(out, "\t\t\tdst.Set(obj.Elem())");
w!(out, "\t\t}}");
w!(out, "\tcase 2:");
w!(out, "\t\tlr := val.(*v.ListReader)");
w!(out, "\t\tn := lr.Len()");
w!(out, "\t\tslice := reflect.MakeSlice(dst.Type(), n, n)");
w!(out, "\t\tfor i := 0; i < n; i++ {{");
w!(out, "\t\t\t_set(slice.Index(i), *d.sub, lr.Get(i))");
w!(out, "\t\t}}");
w!(out, "\t\tdst.Set(slice)");
w!(out, "\tdefault:");
w!(out, "\t\trv := reflect.ValueOf(val)");
w!(out, "\t\tif dst.Kind() == reflect.Ptr {{");
w!(out, "\t\t\tet := dst.Type().Elem()");
w!(out, "\t\t\tp := reflect.New(et)");
w!(out, "\t\t\tp.Elem().Set(_conv(rv, et))");
w!(out, "\t\t\tdst.Set(p)");
w!(out, "\t\t}} else {{");
w!(out, "\t\t\tdst.Set(_conv(rv, dst.Type()))");
w!(out, "\t\t}}");
w!(out, "\t}}");
w!(out, "}}");
w!(out,);
w!(out, "func init() {{");
w!(out, "\t_schema, _ = v.SchemaFromCanonical(SchemaBytes)");
w!(out, "\t_resolver = v.Identity(_schema)");
for idx in 0..schema.type_count() {
let sd = match schema.type_def(idx) {
Some(TypeDef::Struct(sd)) => sd,
_ => continue,
};
w!(out, "\t_specs[reflect.TypeOf({}{{}})] = []_fld{{", sd.name);
for f in &sd.fields {
w!(
out,
"\t\t{{field: \"{}\", id: {}, d: {}}},",
go_export(&f.name),
f.id,
go_desc(schema, &f.ty)
);
}
w!(out, "\t}}");
}
w!(out, "}}");
w!(out,);
for idx in 0..schema.type_count() {
let sd = match schema.type_def(idx) {
Some(TypeDef::Struct(sd)) => sd,
_ => continue,
};
w!(out, "type {} struct {{", sd.name);
for f in &sd.fields {
w!(
out,
"\t{} {}",
go_export(&f.name),
go_field_type(schema, &f.ty)
);
}
w!(out, "}}");
w!(out,);
w!(
out,
"func (x *{}) ToVerit(mode v.SchemaMode) ([]byte, error) {{",
sd.name
);
w!(
out,
"\treturn v.Encode(_schema, _packObj(reflect.ValueOf(x)), mode)"
);
w!(out, "}}");
w!(
out,
"func {}FromVerit(buf []byte) (out *{}, err error) {{",
sd.name,
sd.name
);
w!(out, "\tdefer func() {{");
w!(out, "\t\tif r := recover(); r != nil {{");
w!(out, "\t\t\tif e, ok := r.(error); ok {{");
w!(out, "\t\t\t\terr = e");
w!(out, "\t\t\t}} else {{");
w!(out, "\t\t\t\tpanic(r)");
w!(out, "\t\t\t}}");
w!(out, "\t\t}}");
w!(out, "\t}}()");
w!(out, "\tmsg, err := v.Parse(buf)");
w!(out, "\tif err != nil {{");
w!(out, "\t\treturn nil, err");
w!(out, "\t}}");
w!(out, "\troot, err := msg.Root(_resolver)");
w!(out, "\tif err != nil {{");
w!(out, "\t\treturn nil, err");
w!(out, "\t}}");
w!(
out,
"\treturn _unpackObj(reflect.TypeOf({}{{}}), root).Interface().(*{}), nil",
sd.name,
sd.name
);
w!(out, "}}");
w!(out,);
}
Ok(out)
}