use anyhow::Context;
use geno::{ast, case};
use std::fmt::Write as _;
use std::io::{self, Read};
fn main() {
if let Err(err) = run() {
eprintln!("error: {err:#}");
std::process::exit(1);
}
std::process::exit(0);
}
fn run() -> anyhow::Result<()> {
let stdin = io::stdin();
let mut handle = stdin.lock();
let mut buffer = Vec::new();
handle
.read_to_end(&mut buffer)
.context("Unable to read AST from stdin")?;
let schema: ast::Schema =
rmp_serde::from_slice(&buffer).context("Unable to deserialize AST from stdin")?;
let output = generate(&schema);
print!("{}", output);
Ok(())
}
fn generate(schema: &ast::Schema) -> String {
let mut out = String::new();
writeln!(out, "#![allow(unused_imports)]").unwrap();
writeln!(out).unwrap();
writeln!(out, "use serde::{{Deserialize, Serialize}};").unwrap();
writeln!(out, "use std::collections::HashMap;").unwrap();
let decls = schema.flatten_decls();
for decl in decls {
writeln!(out).unwrap();
match decl {
ast::Declaration::Enum {
ident,
base_type,
variants,
} => generate_enum(&mut out, ident, base_type, variants),
ast::Declaration::Struct { ident, fields } => generate_struct(&mut out, ident, fields),
}
}
out
}
fn generate_enum(
out: &mut String,
ident: &ast::Ident,
base_type: &ast::IntegerType,
variants: &[(ast::Ident, ast::IntegerValue)],
) {
let rust_name = case::to_pascal(ident.as_str());
writeln!(
out,
"#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]"
)
.unwrap();
writeln!(out, "#[repr({})]", integer_type_str(base_type)).unwrap();
writeln!(out, "pub enum {rust_name} {{").unwrap();
let mut first = true;
for (variant_ident, value) in variants {
let rust_variant = case::to_pascal(variant_ident.as_str());
if first {
writeln!(out, " #[default]").unwrap();
first = false;
}
if rust_variant != *variant_ident.as_str() {
writeln!(
out,
" #[serde(rename = \"{0}\")]",
variant_ident.as_str()
)
.unwrap();
}
writeln!(out, " {rust_variant} = {},", integer_value_str(value)).unwrap()
}
writeln!(out, "}}").unwrap();
}
fn generate_struct(out: &mut String, ident: &ast::Ident, fields: &[(ast::Ident, ast::FieldType)]) {
let rust_name = case::to_pascal(ident.as_str());
writeln!(
out,
"#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]"
)
.unwrap();
writeln!(out, "pub struct {rust_name} {{").unwrap();
for (field_ident, field_type) in fields {
let rust_field = case::to_snake(field_ident.as_str());
if rust_field != *field_ident.as_str() {
writeln!(out, " #[serde(rename = \"{0}\")]", field_ident.as_str()).unwrap();
}
writeln!(out, " pub {rust_field}: {},", field_type_str(field_type)).unwrap();
}
writeln!(out, "}}").unwrap();
}
fn field_type_str(ft: &ast::FieldType) -> String {
match ft {
ast::FieldType::Builtin(bt, nullable) => {
let base = builtin_type_str(bt);
if *nullable {
format!("Option<{base}>")
} else {
base
}
}
ast::FieldType::UserDefined(ident, nullable) => {
let rust_name = case::to_pascal(ident.as_str());
if *nullable {
format!("Option<{rust_name}>")
} else {
rust_name
}
}
ast::FieldType::Array(inner, length, nullable) => {
let inner_str = field_type_str(inner);
let base = match length {
Some(len) => format!("[{inner_str}; {len}]"),
None => format!("Vec<{inner_str}>"),
};
if *nullable {
format!("Option<{base}>")
} else {
base
}
}
ast::FieldType::Map(key_type, value_type, nullable) => {
let key_str = builtin_type_str(key_type);
let value_str = field_type_str(value_type);
let base = format!("HashMap<{key_str}, {value_str}>");
if *nullable {
format!("Option<{base}>")
} else {
base
}
}
}
}
fn builtin_type_str(bt: &ast::BuiltinType) -> String {
match bt {
ast::BuiltinType::Integer(it) => integer_type_str(it).to_string(),
ast::BuiltinType::Float(ft) => match ft {
ast::FloatType::F32 => "f32".to_string(),
ast::FloatType::F64 => "f64".to_string(),
},
ast::BuiltinType::String => "String".to_string(),
ast::BuiltinType::Bool => "bool".to_string(),
}
}
fn integer_type_str(t: &ast::IntegerType) -> &'static str {
match t {
ast::IntegerType::I8 => "i8",
ast::IntegerType::I16 => "i16",
ast::IntegerType::I32 => "i32",
ast::IntegerType::I64 => "i64",
ast::IntegerType::U8 => "u8",
ast::IntegerType::U16 => "u16",
ast::IntegerType::U32 => "u32",
ast::IntegerType::U64 => "u64",
}
}
fn integer_value_str(v: &ast::IntegerValue) -> String {
match v {
ast::IntegerValue::I8(n) => n.to_string(),
ast::IntegerValue::I16(n) => n.to_string(),
ast::IntegerValue::I32(n) => n.to_string(),
ast::IntegerValue::I64(n) => n.to_string(),
ast::IntegerValue::U8(n) => n.to_string(),
ast::IntegerValue::U16(n) => n.to_string(),
ast::IntegerValue::U32(n) => n.to_string(),
ast::IntegerValue::U64(n) => n.to_string(),
}
}