seedle-macros 1.8.2

cbor codegeneration
Documentation
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)
    }
}