use std::{collections::BTreeMap, fs::read_to_string, path::Path};
use heck::{ToSnakeCase, ToUpperCamelCase};
use parser::{parse_string, AnyType, PrimitiveType, StructField};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
mod parser;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {}
impl Default for Config {
fn default() -> Self {
Self {}
}
}
fn ident_from_string(s: &String) -> Ident {
Ident::new(s, Span::call_site())
}
#[derive(Clone, Copy)]
struct Caps {
eq: bool,
hash: bool,
}
impl Caps {
const ALL: Caps = Caps {
eq: true,
hash: true,
};
const NONE: Caps = Caps {
eq: false,
hash: false,
};
fn and(self, other: Caps) -> Caps {
Caps {
eq: self.eq && other.eq,
hash: self.hash && other.hash,
}
}
fn derive_tokens(self) -> TokenStream {
match (self.eq, self.hash) {
(true, true) => quote! { , Eq, Hash },
(true, false) => quote! { , Eq },
(false, true) => quote! { , Hash },
(false, false) => quote! {},
}
}
}
pub fn bare_schema(schema_path: &Path, _config: Config) -> proc_macro2::TokenStream {
let file = read_to_string(schema_path).unwrap();
let mut schema_generator = SchemaGenerator {
global_output: Default::default(),
user_type_registry: parse_string(&file),
};
for (name, user_type) in &schema_generator.user_type_registry.clone() {
schema_generator.gen_user_type(&name, &user_type);
}
schema_generator.complete()
}
struct SchemaGenerator {
global_output: Vec<TokenStream>,
user_type_registry: BTreeMap<String, AnyType>,
}
impl SchemaGenerator {
fn complete(self) -> TokenStream {
let SchemaGenerator { global_output, .. } = self;
quote! {
#[allow(unused_imports)]
use serde::{Serialize, Deserialize};
#[allow(unused_imports)]
use serde_bare::{Uint, Int};
#(#global_output)*
}
}
fn gen_user_type(&mut self, name: &String, t: &AnyType) {
#[allow(unused_assignments)]
use AnyType::*;
let def = match t {
Primitive(p) => {
let def = gen_primative_type_def(p);
let ident = ident_from_string(name);
quote! {
pub type #ident = #def;
}
}
List { inner, length } => {
let def = self.gen_list(name, inner.as_ref(), length);
let ident = ident_from_string(name);
quote! {
pub type #ident = #def;
}
}
Struct(fields) => {
self.gen_struct(name, fields);
TokenStream::new()
}
Map { key, value } => {
let map_def = self.gen_map(name, key.as_ref(), value.as_ref());
let ident = ident_from_string(name);
quote! {
pub type #ident = #map_def;
}
}
Optional(inner) => {
let inner_def = self.dispatch_type(name, inner);
let ident = ident_from_string(name);
quote! {
pub type #ident = #inner_def;
}
}
TypeReference(reference) => {
panic!("Type reference is not valid as a top level definition: {reference}")
}
Enum(members) => {
self.gen_enum(name, members);
TokenStream::new()
}
Union(members) => {
self.gen_union(name, members);
TokenStream::new()
}
};
self.global_output.push(def);
}
fn caps_of(&self, t: &AnyType) -> Caps {
match t {
AnyType::Primitive(p) => match p {
PrimitiveType::F32 | PrimitiveType::F64 => Caps::NONE,
PrimitiveType::UInt | PrimitiveType::Int => Caps {
eq: true,
hash: false,
},
_ => Caps::ALL,
},
AnyType::List { inner, .. } => self.caps_of(inner),
AnyType::Optional(inner) => self.caps_of(inner),
AnyType::Map { key, value } => Caps {
eq: self.caps_of(key).eq && self.caps_of(value).eq,
hash: false,
},
AnyType::Struct(fields) => fields
.iter()
.map(|f| self.caps_of(&f.type_r))
.fold(Caps::ALL, Caps::and),
AnyType::Union(members) => members
.iter()
.map(|m| self.caps_of(m))
.fold(Caps::ALL, Caps::and),
AnyType::Enum(_) => Caps::ALL,
AnyType::TypeReference(name) => match self.user_type_registry.get(name) {
Some(t) => self.caps_of(t),
None => Caps::ALL,
},
}
}
fn is_bytes_type(&self, t: &AnyType) -> bool {
match t {
AnyType::Primitive(PrimitiveType::Data(size)) => match size {
Some(size) => *size > MAX_INLINE_DATA_LEN,
None => true,
},
AnyType::Optional(inner) => self.is_bytes_type(inner),
AnyType::TypeReference(name) => match self.user_type_registry.get(name) {
Some(t) => self.is_bytes_type(t),
None => false,
},
AnyType::Primitive(_)
| AnyType::List { .. }
| AnyType::Struct(_)
| AnyType::Enum(_)
| AnyType::Map { .. }
| AnyType::Union(_) => false,
}
}
fn dispatch_type(&mut self, name: &String, any_type: &AnyType) -> TokenStream {
match any_type {
AnyType::Primitive(p) => gen_primative_type_def(p),
AnyType::List { inner, length } => self.gen_list(name, inner.as_ref(), length),
AnyType::Struct(fields) => self.gen_struct(name, fields),
AnyType::Enum(members) => self.gen_enum(name, members),
AnyType::Map { key, value } => self.gen_map(name, key.as_ref(), value.as_ref()),
AnyType::Union(members) => self.gen_union(name, members),
AnyType::Optional(inner) => self.gen_option(name, inner),
AnyType::TypeReference(i) => {
let ident = ident_from_string(i);
quote! { #ident }
}
}
}
fn gen_map(&mut self, name: &String, key: &AnyType, value: &AnyType) -> TokenStream {
let key_def = self.dispatch_type(name, key);
let val_def = self.dispatch_type(name, value);
quote! {
std::collections::HashMap<#key_def, #val_def>
}
}
fn gen_list(
&mut self,
name: &String,
inner_type: &AnyType,
size: &Option<usize>,
) -> TokenStream {
let inner_def = self.dispatch_type(name, inner_type);
match *size {
Some(size) if size <= 32 => quote! {
[#inner_def; #size]
},
_ => quote! {
Vec<#inner_def>
},
}
}
fn gen_struct(&mut self, name: &String, fields: &Vec<StructField>) -> TokenStream {
let extra = fields
.iter()
.map(|f| self.caps_of(&f.type_r))
.fold(Caps::ALL, Caps::and)
.derive_tokens();
let fields_clone = fields.clone();
let fields_gen = self.gen_struct_field(name, fields_clone);
self.gen_anonymous(name, |ident| {
quote! {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone #extra)]
pub struct #ident {
#(#fields_gen),*
}
}
})
}
fn gen_union(&mut self, name: &String, members: &Vec<AnyType>) -> TokenStream {
let mut members_def: Vec<TokenStream> = Vec::with_capacity(members.len());
for (i, member) in members.iter().enumerate() {
let is_void_type = match member {
AnyType::TypeReference(i) if self.user_type_registry.get(i).is_some() => {
let reference = self.user_type_registry.get(i).unwrap();
matches!(reference, AnyType::Primitive(PrimitiveType::Void))
}
_ => false,
};
#[allow(unused_assignments)]
let mut member_def = TokenStream::new();
member_def = match member {
AnyType::Struct(fields) => {
let fields_defs = self.gen_struct_field(name, fields.clone());
quote! {
{
#(#fields_defs),*
}
}
}
AnyType::TypeReference(i) if is_void_type => {
let inner_def = ident_from_string(i);
quote! {
#inner_def
}
}
_ => {
let bytes_attr = if self.is_bytes_type(member) {
quote! { #[serde(with = "serde_bytes")] }
} else {
quote! {}
};
let inner_def = self.dispatch_type(&format!("{name}Member{i}"), member);
quote! {
#bytes_attr
#inner_def(#inner_def)
}
}
};
members_def.push(member_def);
}
let extra = members
.iter()
.map(|m| self.caps_of(m))
.fold(Caps::ALL, Caps::and)
.derive_tokens();
self.gen_anonymous(name, |ident| {
quote! {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone #extra)]
pub enum #ident {
#(#members_def),*
}
}
})
}
fn gen_option(&mut self, name: &String, inner: &AnyType) -> TokenStream {
let inner_def = self.dispatch_type(name, inner);
quote! {
Option<#inner_def>
}
}
fn gen_struct_field(
&mut self,
struct_name: &String,
fields: Vec<StructField>,
) -> Vec<TokenStream> {
let mut fields_gen: Vec<TokenStream> = Vec::with_capacity(fields.len());
for StructField { name, type_r } in fields {
let name = name.to_snake_case();
let bytes_attr = if self.is_bytes_type(&type_r) {
quote! { #[serde(with = "serde_bytes")] }
} else {
quote! {}
};
#[allow(unused_assignments)]
let field_gen = self.dispatch_type(&format!("{struct_name}{name}"), &type_r);
let ident = ident_from_string(&name);
fields_gen.push(quote! {
#bytes_attr
pub #ident: #field_gen
})
}
fields_gen
}
fn gen_enum(&mut self, name: &String, members: &Vec<(String, Option<usize>)>) -> TokenStream {
let member_defs = members.iter().map(|(name, val)| {
let ident = ident_from_string(&name.to_upper_camel_case());
if let Some(val) = val {
quote! {
#ident = #val
}
} else {
quote! {
#ident
}
}
});
self.gen_anonymous(name, |ident| {
quote! {
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug, PartialOrd, Clone)]
#[repr(usize)]
pub enum #ident {
#(#member_defs),*
}
}
})
}
fn gen_anonymous(
&mut self,
name: &String,
inner: impl FnOnce(Ident) -> TokenStream,
) -> TokenStream {
let ident = ident_from_string(name);
self.global_output.push(inner(ident.clone()));
quote! {
#ident
}
}
}
const MAX_INLINE_DATA_LEN: usize = 32;
fn gen_primative_type_def(p: &PrimitiveType) -> TokenStream {
use PrimitiveType::*;
match p {
UInt => quote! { Uint },
U64 => quote! { u64 },
U32 => quote! { u32 },
U16 => quote! { u16 },
U8 => quote! { u8 },
Int => quote! { Int },
I64 => quote! { i64 },
I32 => quote! { i32 },
I16 => quote! { i16 },
I8 => quote! { i8 },
F64 => quote! { f64 },
F32 => quote! { f32 },
Str => quote! { String },
Data(s) => match s {
Some(size) if *size <= MAX_INLINE_DATA_LEN => quote! { [u8; #size] },
_ => quote! { Vec<u8> },
},
Void => quote! { () },
Bool => quote! { bool },
}
}