use super::prefix::Prefix;
use super::utils::method_attrs;
use crate::cddl::*;
use crate::parse::Language;
use heck::*;
use indoc::formatdoc;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use std::borrow::Cow;
use syn::{Ident, LitStr};
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum Totality {
Complete,
Partial,
}
impl Totality {
fn is_partial(&self) -> bool {
match self {
Totality::Complete => false,
Totality::Partial => true,
}
}
}
pub struct Struct<'a> {
pub name: &'a str,
pub prefix: Option<&'a LitStr>,
pub fields: Cow<'a, Fields>,
pub language: Language,
}
impl<'a> Struct<'a> {
fn render(&self) -> TokenStream {
match self.language {
Language::C => self.render_c(),
Language::Rust => self.render_rust(),
Language::Typescript => self.render_typescript(),
}
}
fn render_c(&self) -> TokenStream {
let language = self.language;
let prefix = self
.prefix
.map(|v| Prefix::new(v.value()))
.unwrap_or_else(|| Prefix::new(""));
let struct_ident = Ident::new(
&format!("{}{}", prefix, self.name).to_snake_case(),
Span::call_site(),
);
let fields = self
.fields
.members
.iter()
.enumerate()
.map(|(n, LinkedKeyVal(key, node))| FieldTokens {
node,
prefix: &prefix,
language,
key,
totality: Totality::Complete,
n,
});
let default_impl = DefaultImpl {
name: self.name,
prefix: &prefix,
fields: &self.fields,
language,
};
let ffi_impl = CApiImpl {
name: self.name,
prefix: &prefix,
language,
};
let struct_attrs = quote! {
#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, minicbor::CborLen, minicbor::Encode, minicbor::Decode)]
#[cfg_attr(feature="testing", derive(Debug, PartialEq))]
};
let struct_impl = quote! {
pub struct #struct_ident{
#(#fields),*
}
};
quote! {
#struct_attrs
#struct_impl
#default_impl
#ffi_impl
}
}
fn render_rust(&self) -> TokenStream {
let language = self.language;
let prefix = self
.prefix
.map(|v| Prefix::new(v.value()))
.unwrap_or_else(|| Prefix::new(""));
let struct_ident = Ident::new(
&format!("{}{}", prefix, self.name).to_upper_camel_case(),
Span::call_site(),
);
let fields = self
.fields
.members
.iter()
.enumerate()
.map(|(n, LinkedKeyVal(key, node))| FieldTokens {
node,
prefix: &prefix,
language,
key,
totality: Totality::Complete,
n,
});
let default_impl = DefaultImpl {
name: self.name,
prefix: &prefix,
fields: &self.fields,
language,
};
let struct_attrs = quote! {
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, minicbor::CborLen, minicbor::Encode, minicbor::Decode, PartialEq)]
};
let struct_impl = quote! {
pub struct #struct_ident{
#(#fields),*
}
};
quote! {
#struct_attrs
#struct_impl
#default_impl
}
}
fn render_typescript(&self) -> TokenStream {
let language = self.language;
let prefix = self
.prefix
.map(|v| Prefix::new(v.value()))
.unwrap_or_else(|| Prefix::new(""));
let struct_ident_complete = Ident::new(
&format!("{}{}", prefix, self.name).to_upper_camel_case(),
Span::call_site(),
);
let struct_ident_partial = Ident::new(
&format!("Partial_{}{}", prefix, self.name).to_upper_camel_case(),
Span::call_site(),
);
let serde_rename_ts = proc_macro2::Literal::string("camelCase");
let fields_complete =
self.fields
.members
.iter()
.enumerate()
.map(|(n, LinkedKeyVal(key, node))| FieldTokens {
node,
prefix: &prefix,
language,
key,
totality: Totality::Complete,
n,
});
let fields_partial =
self.fields
.members
.iter()
.enumerate()
.map(|(n, LinkedKeyVal(key, node))| FieldTokens {
node,
prefix: &prefix,
language,
key,
totality: Totality::Partial,
n,
});
let ffi_impl = CApiImpl {
name: self.name,
prefix: &prefix,
language,
};
let default_impl = DefaultImpl {
name: self.name,
prefix: &prefix,
fields: &self.fields,
language,
};
let from_impl = FromImpl {
name: self.name,
prefix: &prefix,
fields: &self.fields,
};
let wasm_impl = WasmImpl {
name: self.name,
prefix: &prefix,
fields: &self.fields,
};
let struct_attrs = quote! {
#[wasm_bindgen]
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, minicbor::CborLen, minicbor::Encode, minicbor::Decode)]
#[serde(rename_all=#serde_rename_ts)]
};
let struct_complete_impl = quote! {
pub struct #struct_ident_complete {
#(#fields_complete),*
}
};
let struct_partial_impl = quote! {
pub struct #struct_ident_partial {
#(#fields_partial),*
}
};
quote! {
#struct_attrs
#struct_complete_impl
#struct_attrs
#[derive(Default)]
#struct_partial_impl
#ffi_impl
#default_impl
#from_impl
#wasm_impl
}
}
}
impl<'a> ToTokens for Struct<'a> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.render().to_tokens(tokens);
}
}
pub struct FieldTokens<'a> {
node: &'a LinkedNode,
prefix: &'a Prefix,
language: Language,
totality: Totality,
n: usize,
key: &'a str,
}
impl<'a> ToTokens for FieldTokens<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let attr = FieldAttrTokens {
node: self.node,
language: self.language,
totality: self.totality,
n: self.n,
};
let field = FieldPropTokens {
node: self.node,
prefix: self.prefix,
language: self.language,
totality: self.totality,
key: self.key,
};
quote! {
#attr
#field
}
.to_tokens(tokens)
}
}
pub struct FieldAttrTokens<'a> {
node: &'a LinkedNode,
language: Language,
n: usize,
totality: Totality,
}
impl<'a> ToTokens for FieldAttrTokens<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let n = proc_macro2::Literal::usize_unsuffixed(self.n);
let bytes = proc_macro2::Literal::string("minicbor::bytes");
let ser = proc_macro2::Literal::string("seedle::serde::ser_bytes_as_str");
let de;
let def;
if self.totality.is_partial() {
de = proc_macro2::Literal::string("seedle::serde::de_option_str_as_bytes");
def = proc_macro2::Literal::string("seedle::serde::make_option_default_bytes");
} else {
de = proc_macro2::Literal::string("seedle::serde::de_str_as_bytes");
def = proc_macro2::Literal::string("seedle::serde::make_default_bytes");
}
match self.language {
Language::C => match self.node {
LinkedNode::Array(LinkedArray { ty, .. }) => match ty.as_ref() {
LinkedNode::Primative(ConstrainedPrimative::U8) => {
quote! {#[cbor(n(#n), with=#bytes)]}.to_tokens(tokens)
}
_ => quote! {#[n(#n)]}.to_tokens(tokens),
},
LinkedNode::Primative(ConstrainedPrimative::Str(_)) => {
quote! {#[cbor(n(#n), with=#bytes)]}.to_tokens(tokens)
}
_ => quote! {#[n(#n)]}.to_tokens(tokens),
},
Language::Rust | Language::Typescript => match self.node {
LinkedNode::Array(LinkedArray { ty, len }) => match ty.as_ref() {
LinkedNode::Primative(ConstrainedPrimative::U8) if *len <= 32 => {
quote! {#[cbor(n(#n), with=#bytes)]}.to_tokens(tokens)
}
LinkedNode::Primative(ConstrainedPrimative::U8) => quote! {
#[cbor(n(#n), with=#bytes)]
#[serde(serialize_with=#ser)]
#[serde(deserialize_with=#de)]
}
.to_tokens(tokens),
_ => quote! {#[n(#n)]}.to_tokens(tokens),
},
LinkedNode::Primative(ConstrainedPrimative::Str(_)) => quote! {
#[cbor(n(#n), with=#bytes)]
#[serde(default=#def)]
#[serde(serialize_with=#ser)]
#[serde(deserialize_with=#de)]
}
.to_tokens(tokens),
_ => quote! {#[n(#n)]}.to_tokens(tokens),
},
}
}
}
pub struct FieldPropTokens<'a> {
node: &'a LinkedNode,
prefix: &'a Prefix,
language: Language,
key: &'a str,
totality: Totality,
}
impl<'a> ToTokens for FieldPropTokens<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let key = quote::format_ident!("{}", self.key.to_snake_case());
let ty = TypeTokens {
node: self.node,
prefix: self.prefix,
language: self.language,
totality: self.totality,
};
match (self.language, self.totality) {
(Language::Typescript, Totality::Partial) => quote! {#key: Option<#ty>},
(Language::Typescript, Totality::Complete) => quote! {#key: #ty},
(Language::C | Language::Rust, Totality::Partial) => quote! {pub #key: Option<#ty>},
(Language::C | Language::Rust, Totality::Complete) => quote! {pub #key: #ty},
}
.to_tokens(tokens);
}
}
pub struct TypeTokens<'a> {
node: &'a LinkedNode,
prefix: &'a Prefix,
language: Language,
totality: Totality,
}
impl<'a> ToTokens for TypeTokens<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let language = self.language;
let totality = self.totality;
let prefix = self.prefix;
match self.node {
LinkedNode::Primative(node) => PrimativeTokens {
prefix,
language,
totality,
node: *node,
}
.to_tokens(tokens),
LinkedNode::Array(node) => ArrayTokens {
prefix,
totality,
language,
node,
}
.to_tokens(tokens),
LinkedNode::ForeignStruct(node) => StructTokens {
prefix,
language,
totality,
node,
}
.to_tokens(tokens),
field => syn::Error::new(Span::call_site(), format!("Invalid field! {:?}", field))
.to_compile_error()
.to_tokens(tokens),
}
}
}
pub struct PrimativeTokens<'a> {
language: Language,
prefix: &'a Prefix,
node: ConstrainedPrimative,
totality: Totality,
}
impl<'a> ToTokens for PrimativeTokens<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self.node {
ConstrainedPrimative::U8 => quote::format_ident!("u8").to_tokens(tokens),
ConstrainedPrimative::U16 => quote::format_ident!("u16").to_tokens(tokens),
ConstrainedPrimative::U32 => quote::format_ident!("u32").to_tokens(tokens),
ConstrainedPrimative::U64 => quote::format_ident!("u64").to_tokens(tokens),
ConstrainedPrimative::I8 => quote::format_ident!("i8").to_tokens(tokens),
ConstrainedPrimative::I16 => quote::format_ident!("i16").to_tokens(tokens),
ConstrainedPrimative::I32 => quote::format_ident!("i32").to_tokens(tokens),
ConstrainedPrimative::I64 => quote::format_ident!("i64").to_tokens(tokens),
ConstrainedPrimative::Bool => quote::format_ident!("bool").to_tokens(tokens),
ConstrainedPrimative::Bytes(n) | ConstrainedPrimative::Str(n) => ArrayTokens {
language: self.language,
prefix: self.prefix,
node: &LinkedArray::new(ConstrainedPrimative::U8.into(), n as usize),
totality: self.totality,
}
.to_tokens(tokens),
};
}
}
pub struct StructTokens<'a> {
node: &'a str,
prefix: &'a Prefix,
language: Language,
totality: Totality,
}
impl<'a> ToTokens for StructTokens<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let span = Span::call_site();
let prefix = self.prefix;
let node = self.node;
match (self.language, self.totality) {
(Language::C, Totality::Complete) => {
Ident::new(&format!("{}{}", prefix, node).to_snake_case(), span)
}
(Language::C, Totality::Partial) => {
Ident::new(&format!("partial_{}{}", prefix, node).to_snake_case(), span)
}
(_, Totality::Complete) => {
Ident::new(&format!("{}{}", prefix, node).to_upper_camel_case(), span)
}
(_, Totality::Partial) => Ident::new(
&format!("Partial_{}{}", prefix, node).to_upper_camel_case(),
span,
),
}
.to_tokens(tokens)
}
}
pub struct ArrayTokens<'a> {
language: Language,
prefix: &'a Prefix,
node: &'a LinkedArray,
totality: Totality,
}
impl<'a> ToTokens for ArrayTokens<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let node = TypeTokens {
prefix: self.prefix,
language: self.language,
node: self.node.ty.as_ref(),
totality: self.totality,
};
let len = proc_macro2::Literal::usize_unsuffixed(self.node.len);
quote! {[ #node; #len ]}.to_tokens(tokens);
}
}
pub struct DefaultImpl<'a> {
name: &'a str,
prefix: &'a Prefix,
fields: &'a Fields,
language: Language,
}
impl<'a> ToTokens for DefaultImpl<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ident = match self.language {
Language::C => Ident::new(
&format!("{}{}", self.prefix, self.name).to_snake_case(),
Span::call_site(),
),
Language::Rust | Language::Typescript => Ident::new(
&format!("{}{}", self.prefix, self.name).to_upper_camel_case(),
Span::call_site(),
),
};
let fields = self
.fields
.members
.iter()
.map(|LinkedKeyVal(key, node)| {
let key = quote::format_ident!("{}", key.to_snake_case());
let default_impl = match node {
LinkedNode::Primative(ConstrainedPrimative::Str(n)) => {
let init = proc_macro2::Literal::u8_unsuffixed(0);
let len = proc_macro2::Literal::u64_unsuffixed(*n);
quote! {[ #init; #len ]}
}
LinkedNode::Array(LinkedArray { ty, len }) => match ty.as_ref() {
LinkedNode::Primative(ConstrainedPrimative::U8)
| LinkedNode::Primative(ConstrainedPrimative::U16)
| LinkedNode::Primative(ConstrainedPrimative::U32)
| LinkedNode::Primative(ConstrainedPrimative::U64)
| LinkedNode::Primative(ConstrainedPrimative::I8)
| LinkedNode::Primative(ConstrainedPrimative::I16)
| LinkedNode::Primative(ConstrainedPrimative::I32)
| LinkedNode::Primative(ConstrainedPrimative::I64) => {
let init = proc_macro2::Literal::u8_unsuffixed(0);
let len = proc_macro2::Literal::u64_unsuffixed(*len as u64);
quote! {[ #init; #len ]}
}
_ => quote! {[Default::default(); #len]},
},
_ => quote! {Default::default()},
};
quote! {#key: #default_impl}
})
.collect::<Vec<TokenStream>>();
quote! {
impl Default for #ident {
fn default() -> #ident {
#ident {
#(#fields),*
}
}
}
}
.to_tokens(tokens)
}
}
struct CApiImpl<'a> {
name: &'a str,
prefix: &'a Prefix,
language: Language,
}
impl<'a> ToTokens for CApiImpl<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
use quote::format_ident;
let name = self.name;
let p = self.prefix;
let span = Span::call_site();
let lang = self.language;
let (func, strct) = match self.language {
Language::C => (
Ident::new(&format!("{}", name).to_snake_case(), span),
Ident::new(&format!("{}{}", p, name).to_snake_case(), span),
),
_ => (
Ident::new(&format!("{}", name).to_snake_case(), span),
Ident::new(&format!("{}{}", p, name).to_upper_camel_case(), span),
),
};
let (enc, enc_attrs) = method_attrs(lang, format_ident!("{p}encode_{func}"));
let (enc_arr, enc_arr_attrs) = method_attrs(lang, format_ident!("{p}encode_{func}_array"));
let (dec, dec_attrs) = method_attrs(lang, format_ident!("{p}decode_{func}"));
let (dec_arr, dec_arr_attrs) = method_attrs(lang, format_ident!("{p}decode_{func}_array"));
let (len, len_attrs) = method_attrs(lang, format_ident!("{p}len_{func}"));
let (len_arr, len_arr_attrs) = method_attrs(lang, format_ident!("{p}array_len_{func}"));
quote! {
#enc_attrs
fn #enc(dst: *mut u8, dstlen: u32, src: &#strct) -> i32 {
unsafe {
let slice = core::slice::from_raw_parts_mut(dst, dstlen as usize);
let cursor = minicbor::encode::write::Cursor::new(slice.as_mut());
let mut encoder = minicbor::Encoder::new(cursor);
encoder
.encode(&*(src as *const #strct))
.map_or(-1, |encoder| encoder.writer().position() as i32)
}
}
#enc_arr_attrs
fn #enc_arr (dst: *mut u8, dstlen: u32, src: &#strct, srclen: u32) -> i32 {
unsafe {
let slice = core::slice::from_raw_parts_mut(dst, dstlen as usize);
let cursor = minicbor::encode::write::Cursor::new(slice.as_mut());
let mut encoder = minicbor::Encoder::new(cursor);
let src_slice = core::slice::from_raw_parts(src as *const #strct, srclen as usize);
encoder
.encode(&src_slice)
.map_or(-1, |encoder| encoder.writer().position() as i32)
}
}
#dec_attrs
fn #dec (dst: &mut #strct, src: *const u8, srclen: u32) -> i32 {
unsafe {
let slice = core::slice::from_raw_parts(src, srclen as usize);
let mut decoder = minicbor::Decoder::new(slice);
if let Ok(t) = decoder.decode::<#strct>() {
*(dst as *mut #strct) = t;
decoder.position() as i32
} else {
-1
}
}
}
#dec_arr_attrs
fn #dec_arr (dst: &mut #strct, dstlen: u32, src: *const u8, srclen: u32) -> i32 {
seedle::ffi::cbor_dec_slice::<#strct>(dst as *mut #strct as *mut core::ffi::c_void, dstlen, src, srclen)
.unwrap_or(-1)
}
#len_attrs
fn #len(src: &#strct) -> u32 {
unsafe {
<#strct as minicbor::CborLen<()>>::cbor_len(&*(src as *const #strct),&mut ()) as u32
}
}
#len_arr_attrs
fn #len_arr(src: &#strct, srclen: u32) -> u32 {
unsafe {
let slice = core::slice::from_raw_parts(src as *const #strct, srclen as usize);
minicbor::len(&slice) as u32
}
}
}.to_tokens(tokens);
}
}
struct FromImpl<'a> {
name: &'a str,
prefix: &'a Prefix,
fields: &'a Fields,
}
impl<'a> ToTokens for FromImpl<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
use proc_macro2::Literal;
let span = Span::call_site();
let name = self.name;
let prefix = self.prefix;
let from_partial_fields = self.fields.members.iter().map(|LinkedKeyVal(key, val)| {
let member = quote::format_ident!("{}", key.to_snake_case());
match val {
LinkedNode::Primative(ConstrainedPrimative::Str(n)) => {
let size = Literal::u64_unsuffixed(*n);
quote! {#member: value.#member.unwrap_or_else(|| [0; #size])}
},
LinkedNode::ForeignStruct(s)=>{
let foreign = Ident::new(&format!("{prefix}{s}").to_upper_camel_case(), span);
quote!{#member: value.#member.map(#foreign::from).unwrap_or_else(|| #foreign::default())}
},
LinkedNode::Array(LinkedArray { ty, len }) => {
let size = Literal::usize_unsuffixed(*len);
match &**ty {
LinkedNode::Primative(ConstrainedPrimative::U8)
| LinkedNode::Primative(ConstrainedPrimative::U16)
| LinkedNode::Primative(ConstrainedPrimative::U32)
| LinkedNode::Primative(ConstrainedPrimative::U64)
| LinkedNode::Primative(ConstrainedPrimative::I8)
| LinkedNode::Primative(ConstrainedPrimative::I16)
| LinkedNode::Primative(ConstrainedPrimative::I32)
| LinkedNode::Primative(ConstrainedPrimative::I64) => quote! {
#member: value.#member.unwrap_or_else(|| [0; #size])
},
LinkedNode::ForeignStruct(s)=>{
let foreign = Ident::new(&format!("{prefix}{s}").to_upper_camel_case(), span);
let vec = vec![0;*len as usize];
let mapper = vec.iter().enumerate().map(|(idx,_)| {
let idx = proc_macro2::Literal::usize_unsuffixed(idx);
quote!{#foreign::from(other[#idx])}
});
quote!{#member: value.#member.map(|other| [#(#mapper),*]).unwrap_or_else(|| [#foreign::default();#size])}
},
_ => quote! {
#member: value.#member.unwrap_or_else(|| [Default::default(); #size])
},
}
}
_ => quote! {#member: value.#member.unwrap_or_else(|| Default::default())},
}
});
let struct_ident = Ident::new(&format!("{prefix}{name}").to_upper_camel_case(), span);
let struct_partial_ident = Ident::new(
&format!("Partial_{prefix}{name}").to_upper_camel_case(),
span,
);
quote! {
impl From<#struct_partial_ident> for #struct_ident {
fn from(value: #struct_partial_ident) -> #struct_ident {
#struct_ident {
#(#from_partial_fields),*
}
}
}
}
.to_tokens(tokens);
}
}
struct WasmImpl<'a> {
name: &'a str,
prefix: &'a Prefix,
fields: &'a Fields,
}
impl<'a> ToTokens for WasmImpl<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
use proc_macro2::Literal;
let fields = self
.fields
.members
.iter()
.map(|LinkedKeyVal(prop, val)| {
let key = quote::format_ident!("{}", prop.to_lower_camel_case());
match val {
LinkedNode::Primative(ConstrainedPrimative::U8)
| LinkedNode::Primative(ConstrainedPrimative::I8)
| LinkedNode::Primative(ConstrainedPrimative::U16)
| LinkedNode::Primative(ConstrainedPrimative::I16)
| LinkedNode::Primative(ConstrainedPrimative::U32)
| LinkedNode::Primative(ConstrainedPrimative::I32)
| LinkedNode::Primative(ConstrainedPrimative::U64)
| LinkedNode::Primative(ConstrainedPrimative::I64) => {
quote! {#key: number}
}
LinkedNode::Primative(ConstrainedPrimative::Bool) => {
quote! {#key: boolean}
}
LinkedNode::Primative(ConstrainedPrimative::Str(_len)) => {
quote! {#key: string}
}
LinkedNode::ForeignStruct(s) => {
let ident = quote::format_ident!("Partial{}Props", s.to_upper_camel_case());
quote! {#key: #ident}
}
LinkedNode::Array(LinkedArray { ty, .. }) => match &**ty {
LinkedNode::Primative(ConstrainedPrimative::I8) => {
quote! {#key: Int8Array}
}
LinkedNode::Primative(ConstrainedPrimative::U8) => {
quote! {#key: Uint8Array}
}
LinkedNode::Primative(ConstrainedPrimative::I16) => {
quote! {#key: Int16Array}
}
LinkedNode::Primative(ConstrainedPrimative::U16) => {
quote! {#key: Uint16Array}
}
LinkedNode::Primative(ConstrainedPrimative::I32) => {
quote! {#key: Int32Array}
}
LinkedNode::Primative(ConstrainedPrimative::U32) => {
quote! {#key: Uint32Array}
}
LinkedNode::Primative(ConstrainedPrimative::I64) => {
quote! {#key: Int64Array}
}
LinkedNode::Primative(ConstrainedPrimative::U64) => {
quote! {#key: Uint64Array}
}
LinkedNode::Primative(ConstrainedPrimative::Bool) => {
quote! {#key: boolean[]}
}
LinkedNode::ForeignStruct(s) => {
let ident =
quote::format_ident!("Partial{}Props", s.to_upper_camel_case());
quote! {#key: #ident[]}
}
s => syn::Error::new(
Span::call_site(),
&format!("Unexpected wasm field type {:?}", s),
)
.to_compile_error(),
},
s => syn::Error::new(
Span::call_site(),
&format!("Unexpected wasm field type {:?}", s),
)
.to_compile_error(),
}
.to_string()
})
.collect::<Vec<String>>()
.join(",");
let setter_getters = self
.fields
.members
.iter()
.map(|LinkedKeyVal(key, val)| WasmSetterGetter::new(self.prefix, key, val));
let s = Span::call_site();
let p = self.prefix;
let n = self.name;
let id = Ident::new(&format!("{p}{n}").to_upper_camel_case(), s);
let props_id = Ident::new(&format!("{p}{n}Props").to_upper_camel_case(), s);
let props_str = Literal::string(&props_id.to_string());
let partial_id = Ident::new(&format!("Partial_{p}{n}").to_upper_camel_case(), s);
let _partial_str = Literal::string(&partial_id.to_string());
let partial_props_id = Ident::new(&format!("Partial_{p}{n}Props").to_upper_camel_case(), s);
let partial_props_str = Literal::string(&partial_props_id.to_string());
let arr_id = Ident::new(&format!("{p}{n}Array").to_upper_camel_case(), s);
let arr_str = Literal::string(&format!("{p}{n}Array").to_upper_camel_case());
let ts_append_content_id = Ident::new(
&format!("TS_APPEND_CONTENT_{p}{n}").to_shouty_snake_case(),
s,
);
let ts_append_content = Literal::string(&formatdoc! {r#"
export type {props_id} = {{ {fields} }};
export type Partial{props_id} = RecursivePartial<{props_id}>;
export type {props_id}Array = Array<{props_id}>;
export type {id}Array = Array<{id}>;"#,
});
quote! {
#[wasm_bindgen(typescript_custom_section)]
const #ts_append_content_id: &'static str = #ts_append_content;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = #props_str)]
pub type #props_id;
#[wasm_bindgen(typescript_type = #partial_props_str)]
pub type #partial_props_id;
#[wasm_bindgen(typescript_type = #arr_str)]
pub type #arr_id;
}
#[wasm_bindgen]
impl #id {
#[wasm_bindgen(constructor)]
pub fn new(props: Option<#partial_props_id>) -> Result<#id, JsValue> {
match props {
Some(partial) => Ok(#id::from(serde_wasm_bindgen::from_value::<#partial_id>(partial.into())?)),
_=> Ok(#id::default())
}
}
#[wasm_bindgen(js_name="fromJson")]
pub fn from_json(json: &str) -> Result<#id, JsValue> {
serde_json::from_str(json).map_err(|e| JsValue::from(&e.to_string()))
}
#[wasm_bindgen(js_name="fromCbor")]
pub fn from_cbor(cbor: &[u8]) -> Result<#id, JsValue> {
let mut dec = minicbor::Decoder::new(cbor);
dec.decode().map_err(|e| JsValue::from(&e.to_string()))
}
#[wasm_bindgen(js_name="fromCborArray")]
pub fn from_cbor_array(cbor: &[u8]) -> Result<#arr_id, JsValue> {
let mut dec = minicbor::Decoder::new(cbor);
let len = dec.probe()
.array()
.map_err(|e| JsValue::from_str(&e.to_string()))?
.ok_or_else(|| JsValue::from_str("invalid array"))?;
let mut vec: Vec<#id> = Vec::with_capacity(len as usize);
for decoded in dec.array_iter::<#id>().map_err(|e| JsValue::from_str(&e.to_string()))? {
vec.push(decoded.map_err(|e| JsValue::from_str(&e.to_string()))?);
}
Ok(vec
.into_iter()
.map(JsValue::from)
.collect::<js_sys::Array>()
.unchecked_into::<#arr_id>())
}
#[wasm_bindgen(js_name="toCborArray")]
pub fn to_cbor_array(arr: Vec<#props_id>) -> Result<Vec<u8>, JsValue> {
Self::cbor_array(arr)
}
#[wasm_bindgen(js_name="cborArray")]
pub fn cbor_array(arr: Vec<#props_id>) -> Result<Vec<u8>, JsValue> {
let vec: Vec<#id> = arr.into_iter()
.map(|item| serde_wasm_bindgen::from_value::<#id>(item.into()))
.collect::<Result<Vec<#id>, serde_wasm_bindgen::Error>>()
.map_err(JsValue::from)?;
let len = minicbor::CborLen::cbor_len(vec.as_slice(), &mut ());
let mut enc = minicbor::Encoder::new(seedle::infallible_encoder::InfallibleEncoder::new(len));
enc.encode(vec.as_slice()).expect("infallible!");
Ok(enc.into_writer().into_inner())
}
#[wasm_bindgen(js_name="toJson")]
pub fn to_json(&self) -> Result<JsValue, JsValue> {
self.as_json()
}
#[wasm_bindgen(js_name="json")]
pub fn as_json(&self) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(self).map_err(|e| e.into())
}
#[wasm_bindgen(js_name="toCbor")]
pub fn to_cbor(&self) -> Vec<u8> {
self.as_cbor()
}
#[wasm_bindgen(js_name="cbor")]
pub fn as_cbor(&self) -> Vec<u8> {
use minicbor::CborLen;
let mut enc = minicbor::Encoder::new(seedle::infallible_encoder::InfallibleEncoder::new(self.cbor_len(&mut ())));
enc.encode(self).expect("infallible!");
enc.into_writer().into_inner()
}
#[wasm_bindgen(js_name="len")]
pub fn len(&self) -> usize {
use minicbor::CborLen;
self.cbor_len(&mut ())
}
#(#setter_getters)*
}
}
.to_tokens(tokens);
}
}
macro_rules! wasm_copyable {
($key:expr, $ty:ty) => {{
let member = quote::format_ident!("{}", $key.to_snake_case());
WasmSetterGetter {
key: $key,
getter: quote! {self.#member},
setter: quote! {self.#member=val},
getter_ty: quote! {$ty},
setter_ty: quote! {$ty},
}
}};
}
macro_rules! wasm_array {
($key:expr, $ty:ty, $len:expr) => {{
let member = quote::format_ident!("{}", $key.to_snake_case());
let len = proc_macro2::Literal::usize_unsuffixed($len);
WasmSetterGetter {
key: $key,
getter: quote! {self.#member.to_vec()},
setter: quote! {
let min = core::cmp::min(val.len(), #len);
self.#member[0..min].copy_from_slice(&val[0..min]);
self.#member[min..].fill(0);
},
getter_ty: quote! {Vec<$ty>},
setter_ty: quote! {&[$ty]},
}
}};
}
macro_rules! wasm_struct {
($key:expr, $other:expr) => {{
let member = quote::format_ident!("{}", $key.to_snake_case());
let other = quote::format_ident!("{}", $other.to_upper_camel_case());
WasmSetterGetter {
key: $key,
getter: quote! {self.#member.clone()},
setter: quote! {self.#member=val},
getter_ty: quote! {#other},
setter_ty: quote! {#other},
}
}};
($key:expr) => {{
let member = quote::format_ident!("{}", $key.to_snake_case());
let other = quote::format_ident!("JsValue");
WasmSetterGetter {
key: $key,
getter: quote! {serde_wasm_bindgen::to_value(&self.#member).unwrap()},
setter: quote! {self.#member = serde_wasm_bindgen::from_value(val).unwrap()},
getter_ty: quote! {#other},
setter_ty: quote! {#other},
}
}};
}
macro_rules! wasm_str {
($key:expr, $len:expr) => {{
let member = quote::format_ident!("{}", $key.to_snake_case());
let len = proc_macro2::Literal::u64_unsuffixed(*$len as u64);
WasmSetterGetter {
key: $key,
getter: quote! {
let ascii = self.#member
.iter()
.position(|&x| x==0)
.map(|pos| self.#member.split_at(pos).0)
.unwrap_or(&self.#member);
std::str::from_utf8(ascii)
.expect("invalid utf8")
.to_string()
},
setter: quote! {
let min = core::cmp::min(val.len(), #len);
self.#member[0..min].copy_from_slice(&val.as_bytes()[0..min]);
self.#member[min..].fill(0);
},
getter_ty: quote! {String},
setter_ty: quote! {&str},
}
}};
($key:expr, $n:expr, $len:expr) => {{
let member = quote::format_ident!("{}", $key.to_snake_case());
let _len = proc_macro2::Literal::u64_unsuffixed(*$n as u64);
WasmSetterGetter {
key: $key,
getter: quote! {
self.#member.map(|member| {
let ascii = member
.iter()
.position(|&x| x==0)
.map(|pos| member.split_at(pos).0)
.unwrap_or(member.as_ref());
std::str::from_utf8(ascii)
.expect("invalid utf8")
.to_string()
}).to_vec()
},
setter: quote! {
assert!(val.len() == self.#member.len(), "invalid array length");
for i in 0..val.len() {
let min = core::cmp::min(val[i].len(), self.#member.len());
self.#member[i][0..min].copy_from_slice(&val[i].as_bytes()[0..min]);
self.#member[i][min..].fill(0);
}
},
getter_ty: quote! {Vec<String>},
setter_ty: quote! {Vec<String>},
}
}};
}
struct WasmSetterGetter<'a> {
key: &'a str,
getter: TokenStream,
getter_ty: TokenStream,
setter: TokenStream,
setter_ty: TokenStream,
}
impl<'a> WasmSetterGetter<'a> {
fn new(p: &'a Prefix, key: &'a str, node: &'a LinkedNode) -> Self {
match node {
LinkedNode::Primative(ConstrainedPrimative::U8) => wasm_copyable!(key, u8),
LinkedNode::Primative(ConstrainedPrimative::U16) => wasm_copyable!(key, u16),
LinkedNode::Primative(ConstrainedPrimative::U32) => wasm_copyable!(key, u32),
LinkedNode::Primative(ConstrainedPrimative::U64) => wasm_copyable!(key, u64),
LinkedNode::Primative(ConstrainedPrimative::I8) => wasm_copyable!(key, i8),
LinkedNode::Primative(ConstrainedPrimative::I16) => wasm_copyable!(key, i16),
LinkedNode::Primative(ConstrainedPrimative::I32) => wasm_copyable!(key, i32),
LinkedNode::Primative(ConstrainedPrimative::I64) => wasm_copyable!(key, i64),
LinkedNode::Primative(ConstrainedPrimative::Bool) => wasm_copyable!(key, bool),
LinkedNode::Primative(ConstrainedPrimative::Str(n)) => wasm_str!(key, n),
LinkedNode::ForeignStruct(s) => {
wasm_struct!(key, &format!("{p}{s}").to_upper_camel_case())
}
LinkedNode::Array(LinkedArray { ty, len }) => match &**ty {
LinkedNode::Primative(ConstrainedPrimative::U8) => wasm_array!(key, u8, *len),
LinkedNode::Primative(ConstrainedPrimative::U16) => wasm_array!(key, u16, *len),
LinkedNode::Primative(ConstrainedPrimative::U32) => wasm_array!(key, u32, *len),
LinkedNode::Primative(ConstrainedPrimative::U64) => wasm_array!(key, u64, *len),
LinkedNode::Primative(ConstrainedPrimative::I8) => wasm_array!(key, i8, *len),
LinkedNode::Primative(ConstrainedPrimative::I16) => wasm_array!(key, i16, *len),
LinkedNode::Primative(ConstrainedPrimative::I32) => wasm_array!(key, i32, *len),
LinkedNode::Primative(ConstrainedPrimative::I64) => wasm_array!(key, i64, *len),
LinkedNode::Primative(ConstrainedPrimative::Bool) => wasm_array!(key, bool, *len),
LinkedNode::Primative(ConstrainedPrimative::Str(n)) => wasm_str!(key, n, *len),
LinkedNode::ForeignStruct(_s) => wasm_struct!(key),
n => panic!("unexpected type {:?} for wasm array setter/getter impl", n),
},
n => panic!("unexpected type {:?} for wasm node setter/getter impl", n),
}
}
}
impl<'a> ToTokens for WasmSetterGetter<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let snaked = self.key.to_snake_case();
let js_name = quote::format_ident!("{}", self.key.to_lower_camel_case());
let fn_getter = quote::format_ident!("{}", snaked);
let fn_setter = quote::format_ident!("set_{}", snaked);
let getter = &self.getter;
let getter_ty = &self.getter_ty;
let setter = &self.setter;
let setter_ty = &self.setter_ty;
quote! {
#[wasm_bindgen(getter, js_name=#js_name)]
pub fn #fn_getter(&self) -> #getter_ty {
#getter
}
#[wasm_bindgen(setter, js_name=#js_name)]
pub fn #fn_setter(&mut self, val: #setter_ty) {
#setter
}
}
.to_tokens(tokens);
}
}