use super::prefix::Prefix;
use crate::parse::Language;
use heck::*;
use indoc::formatdoc;
use proc_macro2::{Literal, Span, TokenStream};
use quote::{quote, ToTokens};
use std::borrow::Cow;
use std::fmt::Display;
use syn::{Ident, LitStr};
pub struct GroupedLiterals<'a> {
pub language: Language,
pub prefix: Option<&'a LitStr>,
pub group: String,
pub literals: Vec<(String, Cow<'a, crate::cddl::Literal>)>,
}
impl<'a> ToTokens for GroupedLiterals<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let group = &self.group;
let span = Span::call_site();
let prefix = self
.prefix
.map(|v| Prefix::new(v.value()))
.unwrap_or_else(|| Prefix::new(""));
let name = Ident::new(
&format!("{prefix}{group}_CONSTANTS").to_shouty_snake_case(),
span,
);
let iface = Ident::new(
&format!("{prefix}{group}Constants").to_upper_camel_case(),
span,
);
let ts_append_content_name = quote::format_ident!("TS_APPEND_CONTENT_{name}");
let assignments: Vec<(Ident, proc_macro2::Literal)> = self
.literals
.iter()
.map(|(key, literal)| {
use crate::cddl::Literal;
let lit = match literal.as_ref() {
Literal::Bool(true) => proc_macro2::Literal::usize_unsuffixed(1),
Literal::Bool(false) => proc_macro2::Literal::usize_unsuffixed(0),
Literal::Int(i) => proc_macro2::Literal::isize_unsuffixed(*i as isize),
Literal::UInt(u) => proc_macro2::Literal::usize_unsuffixed(*u as usize),
Literal::Char(c) => proc_macro2::Literal::u8_unsuffixed(*c as u8),
Literal::Str(s) => s
.chars()
.next()
.map(|c| proc_macro2::Literal::u8_unsuffixed(c as u8))
.unwrap_or_else(|| proc_macro2::Literal::u8_unsuffixed(0)),
_ => proc_macro2::Literal::usize_unsuffixed(0),
};
(quote::format_ident!("{}", key.to_shouty_snake_case()), lit)
})
.collect();
let rs_fields = assignments
.iter()
.map(|(field, lit)| quote! {#field = #lit});
let ts_fields = assignments
.iter()
.map(|(field, lit)| format!("{field}: {lit};"))
.collect::<String>();
let ts_content = proc_macro2::Literal::string(&formatdoc! {r#"
export interface {iface} {{
{ts_fields}
}}
export declare const {name}: {iface};
"#});
quote! {
#[allow(non_camel_case_types)]
#[repr(u32)]
#[wasm_bindgen(skip_typescript)]
pub enum #name {
#(#rs_fields),*
}
#[wasm_bindgen(typescript_custom_section)]
const #ts_append_content_name: &'static str = #ts_content;
}
.to_tokens(tokens)
}
}
pub struct LitToks<'a> {
pub name: &'a str,
pub prefix: Option<&'a LitStr>,
pub lit: &'a crate::cddl::Literal,
pub language: Language,
}
impl<'a> ToTokens for LitToks<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = self
.prefix
.map(|v| Prefix::new(v.value()))
.unwrap_or_else(|| Prefix::new(""));
match self.lit {
crate::cddl::Literal::Bool(lit) => CopyToks {
lit,
prefix: &prefix,
name: self.name,
language: self.language,
ty: "bool",
}
.to_tokens(tokens),
crate::cddl::Literal::Int(lit) => CopyToks {
lit,
prefix: &prefix,
name: self.name,
language: self.language,
ty: "i64",
}
.to_tokens(tokens),
crate::cddl::Literal::UInt(lit) => CopyToks {
lit,
prefix: &prefix,
name: self.name,
language: self.language,
ty: "u64",
}
.to_tokens(tokens),
crate::cddl::Literal::Char(lit) => CharToks {
lit: *lit,
prefix: &prefix,
name: self.name,
language: self.language,
}
.to_tokens(tokens),
crate::cddl::Literal::Str(lit) => StrToks {
lit,
prefix: &prefix,
name: self.name,
language: self.language,
}
.to_tokens(tokens),
_ => unimplemented!(),
};
}
}
struct CopyToks<'a, T: ToTokens + Copy + Display> {
name: &'a str,
prefix: &'a Prefix,
ty: &'a str,
lit: T,
language: Language,
}
impl<'a, T: ToTokens + Copy + Display> ToTokens for CopyToks<'a, T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let span = Span::call_site();
let prefix = self.prefix;
let name = self.name;
let name = Ident::new(&format!("{prefix}{name}").to_shouty_snake_case(), span);
let ts_name = Ident::new(&format!("TS_{prefix}{name}").to_shouty_snake_case(), span);
let ty = quote::format_ident!("{}", self.ty);
let val = self.lit;
match self.language {
Language::Typescript => {
let s = Literal::string(&format!("export type {name} = {}", self.lit));
quote! {
pub const #name: #ty = #val;
#[wasm_bindgen(typescript_custom_section)]
const #ts_name: &'static str = #s ;
}
}
_ => quote! {pub const #name: #ty = #val;},
}
.to_tokens(tokens)
}
}
struct CharToks<'a> {
name: &'a str,
prefix: &'a Prefix,
lit: char,
language: Language,
}
impl<'a> ToTokens for CharToks<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let span = Span::call_site();
let prefix = self.prefix;
let name = self.name;
let name = Ident::new(&format!("{prefix}{name}").to_shouty_snake_case(), span);
let ts_name = Ident::new(&format!("TS_{prefix}{name}").to_shouty_snake_case(), span);
let val = Literal::character(self.lit);
match self.language {
Language::Typescript => {
let s = Literal::string(&format!("export type {name} = '{}'", self.lit));
quote! {
pub const #name: char = #val;
#[wasm_bindgen(typescript_custom_section)]
const #ts_name: &'static str = #s ;
}
}
_ => quote! {pub const #name: char = #val;},
}
.to_tokens(tokens)
}
}
struct StrToks<'a> {
name: &'a str,
prefix: &'a Prefix,
lit: &'a str,
language: Language,
}
impl<'a> ToTokens for StrToks<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let span = Span::call_site();
let prefix = self.prefix;
let name = self.name;
let name = Ident::new(&format!("{prefix}{name}",).to_shouty_snake_case(), span);
let ts_name = Ident::new(&format!("TS_{prefix}{name}",).to_shouty_snake_case(), span);
let val = quote::format_ident!("\"{}\"'", self.lit);
match self.language {
Language::Typescript => {
let s = Literal::string(&format!("export type {name} = \"{}\"", self.lit));
quote! {
pub const #name: &'static str = #val;
#[wasm_bindgen(typescript_custom_section)]
const #ts_name: &'static str = #s ;
}
}
_ => quote! {pub const #name: &'static str = #val;},
}
.to_tokens(tokens)
}
}