use crate::cddl::Fields;
use crate::parse::Language;
use crate::print::prefix::Prefix;
use crate::print::utils::method_attrs;
use heck::*;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::{format_ident, quote};
use std::borrow::Cow;
use syn::{Ident, LitStr};
pub struct VTable<'i> {
pub structs: Vec<(&'i String, Cow<'i, Fields>)>,
pub ident: &'i Ident,
pub language: Language,
pub prefix: Option<&'i LitStr>,
}
impl<'i> ToTokens for VTable<'i> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let lang = self.language;
let span = Span::call_site();
let prefix = self
.prefix
.map(|v| Prefix::new(v.value()))
.unwrap_or_else(|| Prefix::new(""));
let n = proc_macro2::Literal::usize_unsuffixed(self.structs.len());
let (key, table) = match self.language {
Language::C => {
let key = Ident::new(&format!("{prefix}KEY").to_shouty_snake_case(), span);
let entries = self.structs.iter().map(|(key, _)| VTableEntry {
entry: Ident::new(&key.to_shouty_snake_case(), span),
language: lang,
prefix: &prefix,
});
let enumeration = self.structs.iter().enumerate().map(|(idx, (key, _node))| {
let name = Ident::new(&format!("{prefix}{key}").to_shouty_snake_case(), span);
let idx = proc_macro2::Literal::i8_unsuffixed(idx as i8);
quote! {#name = #idx}
});
let table = quote! {
const __SEEDLE_VTABLE: [seedle::ffi::SeedleVTableEntry; #n] = [ #(#entries),* ];
#[repr(C)]
pub enum #key {
#(#enumeration),*
}
};
(key, table)
}
Language::Rust => {
let key = format_ident!("SeedleKey");
let entries = self.structs.iter().map(|(key, _)| VTableEntry {
entry: Ident::new(&key.to_upper_camel_case(), span),
language: lang,
prefix: &prefix,
});
let enumeration = self.structs.iter().enumerate().map(|(idx, (key, _node))| {
let name = Ident::new(&key.to_upper_camel_case(), span);
let idx = proc_macro2::Literal::i8_unsuffixed(idx as i8);
quote! {#name = #idx}
});
let table = quote! {
const __SEEDLE_VTABLE: [seedle::ffi::SeedleVTableEntry; #n] = [ #(#entries),* ];
#[repr(C)]
pub enum #key {
#(#enumeration),*
}
};
(key, table)
}
Language::Typescript => {
let key = format_ident!("SeedleKey");
let entries = self.structs.iter().map(|(key, _)| VTableEntry {
entry: Ident::new(&key.to_upper_camel_case(), span),
language: lang,
prefix: &prefix,
});
let enumeration = self.structs.iter().enumerate().map(|(idx, (key, _node))| {
let name = Ident::new(&key.to_upper_camel_case(), span);
let idx = proc_macro2::Literal::i8_unsuffixed(idx as i8);
quote! {#name = #idx}
});
let table = quote! {
const __SEEDLE_VTABLE: [seedle::ffi::SeedleVTableEntry; #n] = [ #(#entries),* ];
#[wasm_bindgen]
#[repr(C)]
pub enum #key {
#(#enumeration),*
}
};
(key, table)
}
};
let (enc, enc_attrs) = method_attrs(lang, format_ident!("{prefix}encode"));
let (enc_arr, enc_arr_attrs) = method_attrs(lang, format_ident!("{prefix}encode_array"));
let (dec, dec_attrs) = method_attrs(lang, format_ident!("{prefix}decode"));
let (dec_arr, dec_arr_attrs) = method_attrs(lang, format_ident!("{prefix}decode_array"));
let (len, len_attrs) = method_attrs(lang, format_ident!("{prefix}len"));
let (len_arr, len_arr_attrs) = method_attrs(lang, format_ident!("{prefix}array_len"));
quote! {
#table
#enc_attrs
fn #enc(dst: *mut u8, dstlen: u32, key: #key, src: *const core::ffi::c_void) -> i32 {
unsafe {(__SEEDLE_VTABLE[key as u8 as usize].encode)(dst, dstlen, &*src)}
}
#enc_arr_attrs
fn #enc_arr(dst: *mut u8, dstlen: u32, key: #key, src: *const core::ffi::c_void, srclen: u32) -> i32 {
unsafe {(__SEEDLE_VTABLE[key as u8 as usize].encode_array)(dst, dstlen, &*src, srclen)}
}
#dec_attrs
fn #dec (dst: *mut core::ffi::c_void, key: #key, src: *const u8, srclen: u32) -> i32 {
unsafe {(__SEEDLE_VTABLE[key as u8 as usize].decode)(&mut *dst, src, srclen)}
}
#dec_arr_attrs
fn #dec_arr (dst: *mut core::ffi::c_void, dstlen: u32, key: #key, src: *const u8, srclen: u32) -> i32 {
unsafe {(__SEEDLE_VTABLE[key as u8 as usize].decode_array)(&mut *dst, dstlen, src, srclen)}
}
#len_attrs
fn #len(key: #key, src: *const core::ffi::c_void) -> u32 {
unsafe {(__SEEDLE_VTABLE[key as u8 as usize].len)(&*src)}
}
#len_arr_attrs
fn #len_arr(key: #key, src: *const core::ffi::c_void, srclen: u32) -> u32 {
unsafe {(__SEEDLE_VTABLE[key as u8 as usize].array_len)(&*src, srclen)}
}
}
.to_tokens(tokens);
}
}
struct VTableEntry<'l> {
entry: Ident,
language: Language,
prefix: &'l Prefix,
}
impl<'l> ToTokens for VTableEntry<'l> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = self.entry.to_string().to_snake_case();
let prefix = self.prefix;
let span = Span::call_site();
let struct_name = match self.language {
Language::C => Ident::new(&format!("{prefix}{name}").to_snake_case(), span),
_ => Ident::new(&format!("{prefix}{name}").to_upper_camel_case(), span),
};
let encoder = quote::format_ident!("{prefix}encode_{name}");
let decoder = quote::format_ident!("{prefix}decode_{name}");
let array_encoder = quote::format_ident!("{prefix}encode_{name}_array");
let array_decoder = quote::format_ident!("{prefix}decode_{name}_array");
let len = quote::format_ident!("{prefix}len_{name}");
let array_len = quote::format_ident!("{prefix}array_len_{name}");
quote! {
unsafe { seedle::ffi::SeedleVTableEntry {
encode: core::mem::transmute::<seedle::ffi::EncodeFn<#struct_name>, seedle::ffi::EncodeFnErased>(#encoder),
encode_array: core::mem::transmute::<seedle::ffi::EncodeArrayFn<#struct_name>, seedle::ffi::EncodeArrayFnErased>(#array_encoder),
decode: core::mem::transmute::<seedle::ffi::DecodeFn<#struct_name>, seedle::ffi::DecodeFnErased>(#decoder),
decode_array: core::mem::transmute::<seedle::ffi::DecodeArrayFn<#struct_name>, seedle::ffi::DecodeArrayFnErased>(#array_decoder),
len: core::mem::transmute::<seedle::ffi::LenFn<#struct_name>, seedle::ffi::LenFnErased>(#len),
array_len: core::mem::transmute::<seedle::ffi::ArrayLenFn<#struct_name>, seedle::ffi::ArrayLenFnErased>(#array_len),
}}
}
.to_tokens(tokens)
}
}