use crate::parser::{FieldType, Schema, Struct};
use std::fmt::Write;
pub fn emit_rust(schema: &Schema) -> String {
let mut out = String::new();
writeln!(
out,
"// AUTO-GENERATED by zapc — do not edit by hand.\n\
// Schema file id: 0x{:016x}\n\
use crate::wire::WireError;\n",
schema.file_id
)
.ok();
writeln!(out, "pub const FILE_ID: u64 = 0x{:016x};\n", schema.file_id).ok();
for st in &schema.structs {
emit_struct(&mut out, st);
}
out
}
fn emit_struct(out: &mut String, st: &Struct) {
let name = &st.name;
writeln!(
out,
"// ── {} ────────────────────────────────────────",
name
)
.ok();
writeln!(out, "pub mod {} {{", to_snake(name)).ok();
writeln!(out, " use super::*;").ok();
writeln!(out, " pub const SLOT_SIZE: usize = {};", st.slot_size).ok();
writeln!(
out,
" /// Zero-copy view into the {} slot region of the data segment.",
name
)
.ok();
writeln!(out, " #[derive(Debug, Clone, Copy)]").ok();
writeln!(out, " pub struct Reader<'a> {{ pub buf: &'a [u8] }}").ok();
writeln!(out, " impl<'a> Reader<'a> {{").ok();
writeln!(
out,
" pub fn from_slice(buf: &'a [u8]) -> Result<Self, WireError> {{"
)
.ok();
writeln!(out, " if buf.len() < {} {{", st.slot_size).ok();
writeln!(
out,
" return Err(WireError::ShortBody {{ want: {}, got: buf.len() }});",
st.slot_size
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, " Ok(Self {{ buf }})").ok();
writeln!(out, " }}").ok();
for f in &st.fields {
emit_reader_method(out, f);
}
writeln!(out, " }}").ok();
writeln!(out, " /// Owned builder for the {} struct.", name).ok();
writeln!(out, " #[derive(Default, Debug, Clone)]").ok();
writeln!(out, " pub struct Writer {{").ok();
for f in &st.fields {
let ty = match f.ty {
FieldType::Text => "String".to_string(),
FieldType::Data => "Vec<u8>".to_string(),
_ => f.ty.rust_read_type().to_string(),
};
writeln!(out, " pub {}: {},", to_snake(&f.name), ty).ok();
}
writeln!(out, " }}").ok();
writeln!(out, " impl Writer {{").ok();
writeln!(
out,
" /// Serialise to the canonical ZAP body bytes."
)
.ok();
writeln!(
out,
" /// (Wrap with `zapwire::wire::write_frame` to add the 16-byte header.)"
)
.ok();
writeln!(out, " pub fn to_bytes(&self) -> Vec<u8> {{").ok();
writeln!(out, " let payload_len: usize = 0").ok();
for f in &st.fields {
match f.ty {
FieldType::Text => {
writeln!(out, " + self.{}.len()", to_snake(&f.name)).ok()
}
FieldType::Data => {
writeln!(out, " + self.{}.len()", to_snake(&f.name)).ok()
}
_ => None,
};
}
writeln!(out, " ;").ok();
writeln!(
out,
" let total = (SLOT_SIZE + payload_len + 7) & !7;"
)
.ok();
writeln!(out, " let mut buf = vec![0u8; total];").ok();
writeln!(out, " let mut tail_off = SLOT_SIZE;").ok();
for f in &st.fields {
emit_writer_set(out, f);
}
writeln!(out, " let _ = tail_off;").ok();
writeln!(out, " buf").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out, "}}\n").ok();
}
fn emit_reader_method(out: &mut String, f: &crate::parser::Field) {
let snake = to_snake(&f.name);
let off = f.offset;
match f.ty {
FieldType::Bool => {
writeln!(
out,
" pub fn {}(&self) -> bool {{ self.buf[{}] != 0 }}",
snake, off
)
.ok();
}
FieldType::UInt8 => {
writeln!(
out,
" pub fn {}(&self) -> u8 {{ self.buf[{}] }}",
snake, off
)
.ok();
}
FieldType::Int8 => {
writeln!(
out,
" pub fn {}(&self) -> i8 {{ self.buf[{}] as i8 }}",
snake, off
)
.ok();
}
FieldType::UInt16 => {
writeln!(out, " pub fn {}(&self) -> u16 {{ u16::from_le_bytes([self.buf[{}], self.buf[{}]]) }}", snake, off, off + 1).ok();
}
FieldType::Int16 => {
writeln!(out, " pub fn {}(&self) -> i16 {{ i16::from_le_bytes([self.buf[{}], self.buf[{}]]) }}", snake, off, off + 1).ok();
}
FieldType::UInt32 => {
writeln!(out, " pub fn {}(&self) -> u32 {{ u32::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) }}", snake, off, off + 4).ok();
}
FieldType::Int32 => {
writeln!(out, " pub fn {}(&self) -> i32 {{ i32::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) }}", snake, off, off + 4).ok();
}
FieldType::Float32 => {
writeln!(out, " pub fn {}(&self) -> f32 {{ f32::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) }}", snake, off, off + 4).ok();
}
FieldType::UInt64 => {
writeln!(out, " pub fn {}(&self) -> u64 {{ u64::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) }}", snake, off, off + 8).ok();
}
FieldType::Int64 => {
writeln!(out, " pub fn {}(&self) -> i64 {{ i64::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) }}", snake, off, off + 8).ok();
}
FieldType::Float64 => {
writeln!(out, " pub fn {}(&self) -> f64 {{ f64::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) }}", snake, off, off + 8).ok();
}
FieldType::Text => {
writeln!(out, " pub fn {}(&self) -> &'a str {{", snake).ok();
writeln!(out, " let bytes = self.{}_bytes();", snake).ok();
writeln!(
out,
" std::str::from_utf8(bytes).unwrap_or(\"\")"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, " pub fn {}_bytes(&self) -> &'a [u8] {{", snake).ok();
writeln!(out, " let rel = i32::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) as isize;", off, off + 4).ok();
writeln!(out, " let len = u32::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) as usize;", off + 4, off + 8).ok();
writeln!(
out,
" if rel == 0 || len == 0 {{ return b\"\"; }}"
)
.ok();
writeln!(
out,
" let start = ({} as isize + rel) as usize;",
off
)
.ok();
writeln!(
out,
" if start + len > self.buf.len() {{ return b\"\"; }}"
)
.ok();
writeln!(out, " &self.buf[start..start + len]").ok();
writeln!(out, " }}").ok();
}
FieldType::Data => {
writeln!(out, " pub fn {}(&self) -> &'a [u8] {{", snake).ok();
writeln!(out, " let rel = i32::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) as isize;", off, off + 4).ok();
writeln!(out, " let len = u32::from_le_bytes(self.buf[{}..{}].try_into().unwrap()) as usize;", off + 4, off + 8).ok();
writeln!(
out,
" if rel == 0 || len == 0 {{ return b\"\"; }}"
)
.ok();
writeln!(
out,
" let start = ({} as isize + rel) as usize;",
off
)
.ok();
writeln!(
out,
" if start + len > self.buf.len() {{ return b\"\"; }}"
)
.ok();
writeln!(out, " &self.buf[start..start + len]").ok();
writeln!(out, " }}").ok();
}
}
}
fn emit_writer_set(out: &mut String, f: &crate::parser::Field) {
let snake = to_snake(&f.name);
let off = f.offset;
let _ = match f.ty {
FieldType::Bool => {
writeln!(
out,
" buf[{}] = if self.{} {{ 1 }} else {{ 0 }};",
off, snake
)
}
FieldType::UInt8 => writeln!(out, " buf[{}] = self.{};", off, snake),
FieldType::Int8 => writeln!(out, " buf[{}] = self.{} as u8;", off, snake),
FieldType::UInt16 => writeln!(
out,
" buf[{}..{}].copy_from_slice(&self.{}.to_le_bytes());",
off,
off + 2,
snake
),
FieldType::Int16 => writeln!(
out,
" buf[{}..{}].copy_from_slice(&self.{}.to_le_bytes());",
off,
off + 2,
snake
),
FieldType::UInt32 => writeln!(
out,
" buf[{}..{}].copy_from_slice(&self.{}.to_le_bytes());",
off,
off + 4,
snake
),
FieldType::Int32 => writeln!(
out,
" buf[{}..{}].copy_from_slice(&self.{}.to_le_bytes());",
off,
off + 4,
snake
),
FieldType::Float32 => writeln!(
out,
" buf[{}..{}].copy_from_slice(&self.{}.to_le_bytes());",
off,
off + 4,
snake
),
FieldType::UInt64 => writeln!(
out,
" buf[{}..{}].copy_from_slice(&self.{}.to_le_bytes());",
off,
off + 8,
snake
),
FieldType::Int64 => writeln!(
out,
" buf[{}..{}].copy_from_slice(&self.{}.to_le_bytes());",
off,
off + 8,
snake
),
FieldType::Float64 => writeln!(
out,
" buf[{}..{}].copy_from_slice(&self.{}.to_le_bytes());",
off,
off + 8,
snake
),
FieldType::Text => write_var_field(out, &snake, off, true),
FieldType::Data => write_var_field(out, &snake, off, false),
};
}
fn write_var_field(out: &mut String, snake: &str, off: usize, as_bytes: bool) -> std::fmt::Result {
writeln!(out, " {{")?;
if as_bytes {
writeln!(
out,
" let bytes = self.{}.as_bytes();",
snake
)?;
} else {
writeln!(out, " let bytes: &[u8] = &self.{};", snake)?;
}
writeln!(out, " if !bytes.is_empty() {{")?;
writeln!(
out,
" let rel = (tail_off - {}) as i32;",
off
)?;
writeln!(
out,
" buf[{}..{}].copy_from_slice(&rel.to_le_bytes());",
off,
off + 4
)?;
writeln!(
out,
" buf[{}..{}].copy_from_slice(&(bytes.len() as u32).to_le_bytes());",
off + 4,
off + 8
)?;
writeln!(
out,
" buf[tail_off..tail_off + bytes.len()].copy_from_slice(bytes);"
)?;
writeln!(out, " tail_off += bytes.len();")?;
writeln!(out, " }}")?;
writeln!(out, " }}")?;
Ok(())
}
fn to_snake(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 4);
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() && i > 0 {
out.push('_');
}
out.push(c.to_ascii_lowercase());
}
out
}