sark-gen 0.28.3

Sark proc-macro generators
Documentation
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::LitByteStr;

use crate::{macros::tokens, runtime::paths::Paths};

pub(super) struct Plan<'a> {
    pub(super) mode: &'a mode::Mode,
    pub(super) fields: Vec<JsonField>,
}

pub(super) struct JsonField {
    pub(super) ident: syn::Ident,
    pub(super) bind: syn::Ident,
    pub(super) ty: syn::Type,
    pub(super) vis: syn::Visibility,
    pub(super) name: String,
    pub(super) mode: field::FieldMode,
}

pub(super) struct Emission {
    pub(super) lengths: Vec<proc_macro2::TokenStream>,
    pub(super) writes: Vec<proc_macro2::TokenStream>,
}

impl<'a> Plan<'a> {
    pub(super) fn locals(&self, paths: &Paths) -> syn::Result<Vec<proc_macro2::TokenStream>> {
        if matches!(self.mode.kind, mode::JsonKind::Ordered) {
            return Ok(Vec::new());
        }
        self.fields
            .iter()
            .map(|field| {
                let bind = &field.bind;
                let ty = field::proof_type(paths, &field.ty, field.mode)?;
                Ok(quote::quote!(let mut #bind: ::core::option::Option<#ty> = ::core::option::Option::None;))
            })
            .collect()
    }

    pub(super) fn match_arms(&self, paths: &Paths) -> syn::Result<Vec<proc_macro2::TokenStream>> {
        let ns_json = &paths.json;
        let [v_raw, v_idx, v_depth] = ["__raw", "__idx", "__depth"].map(tokens::local);
        let [v_name, v_hand] = ["__name", "__handled"].map(tokens::local);
        use crate::types::lengths::LengthArms;
        let entries = self
            .fields
            .iter()
            .map(|field| {
                let bind = &field.bind;
                let lit = LitByteStr::new(field.name.as_bytes(), Span::call_site());
                let decode = sources::expr(paths, &field.ty, field.mode)?;
                Ok((
                    field.name.len(),
                    quote::quote! {
                        if !#v_hand && #v_name.is_equal(#lit) {
                            if #bind.is_none() {
                                #bind = ::core::option::Option::Some(#decode);
                            } else {
                                #ns_json::input::Input::skip_depth(#v_raw, &mut #v_idx, #v_depth)?;
                            }
                            #v_hand = true;
                        }
                    },
                ))
            })
            .collect::<syn::Result<Vec<_>>>()?;
        Ok(LengthArms::collect(entries).emit())
    }

    pub(super) fn finals(&self, paths: &Paths) -> Vec<proc_macro2::TokenStream> {
        let ns_json = &paths.json;
        self.fields
            .iter()
            .map(|field| {
                let ident = &field.ident;
                let bind = &field.bind;
                if matches!(self.mode.kind, mode::JsonKind::Ordered) {
                    quote::quote!(#ident: #bind)
                } else if types::View::new(&field.ty).is_optional() {
                    quote::quote!(#ident: #bind.flatten())
                } else {
                    let name = &field.name;
                    quote::quote! {
                        #ident: #bind
                            .ok_or_else(|| {
                                #ns_json::parsing::Error::Missing(#name)
                            })?
                    }
                }
            })
            .collect()
    }

    pub(super) fn ordered(&self, paths: &Paths) -> syn::Result<Vec<proc_macro2::TokenStream>> {
        let ns_json = &paths.json;
        let [v_raw, v_idx] = ["__raw", "__idx"].map(tokens::local);
        self.fields
            .iter()
            .enumerate()
            .map(|(idx, field)| {
                let bind = &field.bind;
                let name = LitByteStr::new(field.name.as_bytes(), Span::call_site());
                let first = idx == 0;
                let ty = field::proof_type(paths, &field.ty, field.mode)?;
                let decode = sources::expr(paths, &field.ty, field.mode)?;
                Ok(quote::quote! {
                    #ns_json::input::Input::expect_prop(#v_raw, &mut #v_idx, #first, #name)?;
                    let #bind: #ty = #decode;
                })
            })
            .collect()
    }

    pub(super) fn borrowed(&self, paths: &Paths) -> proc_macro2::TokenStream {
        let ns_json = &paths.json;
        let [v_raw, v_view] = ["__raw", "__view"].map(tokens::local);
        if self.fields.iter().any(|field| {
            field.mode.nested
                || field.mode.seq
                || types::View::new(types::View::new(&field.ty).value()).is_named("Shared")
        }) {
            return proc_macro2::TokenStream::new();
        }
        let fields = self.fields.iter().map(|field| {
            let ident = &field.ident;
            quote::quote!(#ident: #v_view.#ident)
        });
        quote::quote! {
            fn decode_ref<'__a>(#v_raw: &'__a [::core::primitive::u8]) ->
                ::core::result::Result<Self, #ns_json::parsing::Error<'__a>> {
                let #v_view = <Self as #ns_json::parsing::Request>::decode(#v_raw)?;
                ::core::result::Result::Ok(Self { #(#fields,)* })
            }
        }
    }

    pub(super) fn decode_body(&self, paths: &Paths) -> syn::Result<proc_macro2::TokenStream> {
        let ns_json = &paths.json;
        let [v_raw, v_idx, v_depth] = ["__raw", "__idx", "__depth"].map(tokens::local);
        let [v_name, v_hand] = ["__name", "__handled"].map(tokens::local);
        let body = match self.mode.kind {
            mode::JsonKind::Ordered => {
                let steps = self.ordered(paths)?;
                quote::quote! {
                    #(
                        #steps
                    )*
                    #ns_json::input::Input::ws(#v_raw, &mut #v_idx);
                    #ns_json::input::Input::expect_byte(#v_raw, &mut #v_idx, b'}')?;
                }
            }
            mode::JsonKind::Unordered => {
                let arms = self.match_arms(paths)?;
                quote::quote! {
                    #ns_json::input::Input::ws(#v_raw, &mut #v_idx);
                    if #ns_json::input::Input::take_byte(#v_raw, &mut #v_idx, b'}')
                        == #ns_json::input::Taken::No
                    {
                        loop {
                            let #v_name = #ns_json::input::Input::name(#v_raw, &mut #v_idx)?;
                            #ns_json::input::Input::ws(#v_raw, &mut #v_idx);
                            #ns_json::input::Input::expect_byte(#v_raw, &mut #v_idx, b':')?;
                            #ns_json::input::Input::ws(#v_raw, &mut #v_idx);
                            let mut #v_hand = false;
                            match #v_name.len() {
                                #( #arms )*
                                _ => {}
                            }
                            if !#v_hand {
                                #ns_json::input::Input::skip_depth(#v_raw, &mut #v_idx, #v_depth)?;
                            }
                            #ns_json::input::Input::ws(#v_raw, &mut #v_idx);
                            if #ns_json::input::Input::take_byte(#v_raw, &mut #v_idx, b',')
                                == #ns_json::input::Taken::Yes
                            {
                                #ns_json::input::Input::ws(#v_raw, &mut #v_idx);
                                continue;
                            }
                            #ns_json::input::Input::expect_byte(#v_raw, &mut #v_idx, b'}')?;
                            break;
                        }
                    }
                }
            }
        };
        Ok(body)
    }
}

use crate::{
    json::{field, mode, sources},
    types,
};

pub(super) mod encoding;

#[derive(Clone, Copy)]
pub(in crate::json) enum Scope<'a> {
    Object,
    Members(&'a syn::Ident),
}

impl Plan<'_> {
    pub(in crate::json) fn members(
        &self,
        paths: &Paths,
        st: &syn::ItemStruct,
    ) -> syn::Result<TokenStream> {
        let ns_json = &paths.json;
        let ns_http = &paths.http;
        if self.fields.is_empty() {
            return Ok(TokenStream::new());
        }
        let names = types::Generics::new(st.generics.clone());
        let first = names.ident("__FIRST");
        let names = encoding::Names::new(&st.generics);
        let lifetime = &names.input;
        let sink = &names.sink;
        let writer = &names.writer;
        let size = &names.size;
        let Emission { lengths, writes } = self.encoding(paths, Scope::Members(&first), &names)?;
        let name = &st.ident;
        let (imp, ty, bounds) = st.generics.split_for_impl();
        Ok(quote! {
            impl #imp #ns_json::writing::Members for #name #ty #bounds {
                fn try_fields(&self) -> ::core::result::Result<::core::primitive::usize, #ns_http::payload::response::exact::Overflow> {
                    let mut #size = 0usize;
                    #( #size = #ns_http::payload::response::exact::Overflow::try_add(#size, #lengths)?; )*
                    ::core::result::Result::Ok(#size)
                }

                fn try_members<#lifetime, #sink: #ns_json::o3::buffer::write::Sink, const #first: ::core::primitive::bool>(
                    &self,
                    mut #writer: #ns_json::writing::Writer<#lifetime, #sink>,
                ) -> ::core::ops::ControlFlow<
                    #ns_json::writing::Failure<#lifetime, #sink>,
                    #ns_json::writing::Writer<#lifetime, #sink>,
                > {
                    #( #writes )*
                    ::core::ops::ControlFlow::Continue(#writer)
                }
            }
        })
    }

    pub(in crate::json) fn encoding(
        &self,
        paths: &Paths,
        scope: Scope<'_>,
        names: &encoding::Names,
    ) -> syn::Result<Emission> {
        let mut lengths = Vec::with_capacity(self.fields.len());
        let mut writes = Vec::with_capacity(self.fields.len());
        for (index, field) in self.fields.iter().enumerate() {
            let emission = if field.mode.flatten {
                field.members(paths, index, scope, names)
            } else {
                field.encoding(paths, index, scope, names)?
            };
            lengths.push(emission.len);
            writes.push(emission.write);
        }
        Ok(Emission { lengths, writes })
    }
}

impl JsonField {
    fn head(&self, prefix: &[u8]) -> LitByteStr {
        let mut bytes = Vec::with_capacity(self.name.len() + 4);
        bytes.extend_from_slice(prefix);
        bytes.push(b'"');
        for byte in self.name.bytes() {
            match byte {
                b'"' | b'\\' => bytes.extend_from_slice(&[b'\\', byte]),
                0..=0x1f => {
                    const HEX: &[u8; 16] = b"0123456789abcdef";
                    bytes.extend_from_slice(b"\\u00");
                    bytes.push(HEX[usize::from(byte >> 4)]);
                    bytes.push(HEX[usize::from(byte & 0xf)]);
                }
                _ => bytes.push(byte),
            }
        }
        bytes.extend_from_slice(b"\":");
        LitByteStr::new(&bytes, Span::call_site())
    }

    fn encoding(
        &self,
        paths: &Paths,
        index: usize,
        scope: Scope<'_>,
        names: &encoding::Names,
    ) -> syn::Result<encoding::Emission> {
        let ns_http = &paths.http;
        let prefix = match (index, scope) {
            (0, Scope::Object) => b"{".as_slice(),
            (0, Scope::Members(_)) => b"".as_slice(),
            (_, Scope::Object | Scope::Members(_)) => b",".as_slice(),
        };
        let head = self.head(prefix);
        let head_len = head.value().len();
        let put = encoding::call(names, quote!(put), quote!(#head));
        let put = if let (0, Scope::Members(first)) = (index, scope) {
            let comma = self.head(b",");
            let comma = encoding::call(names, quote!(put), quote!(#comma));
            quote! { if #first { #put } else { #comma } }
        } else {
            put
        };
        let ident = &self.ident;
        let value =
            encoding::Emission::try_value(paths, &self.ty, self.mode, quote!(self.#ident), names)?;
        let len = value.len;
        let write = value.write;
        Ok(encoding::Emission {
            len: quote!(#ns_http::payload::response::exact::Overflow::try_add(#head_len, #len)?),
            write: quote! { #put #write },
        })
    }

    fn members(
        &self,
        paths: &Paths,
        index: usize,
        scope: Scope<'_>,
        names: &encoding::Names,
    ) -> encoding::Emission {
        let ns_json = &paths.json;
        let ns_http = &paths.http;
        let ident = &self.ident;
        let writer = &names.writer;
        let (prefix, first, extra) = match (index, scope) {
            (0, Scope::Object) => (
                encoding::call(names, quote!(put), quote!(b"{")),
                quote!(true),
                1usize,
            ),
            (0, Scope::Members(first)) => (TokenStream::new(), quote!(#first), 0),
            (_, Scope::Object | Scope::Members(_)) => (TokenStream::new(), quote!(false), 1),
        };
        encoding::Emission {
            len: quote!(#ns_http::payload::response::exact::Overflow::try_add(
                #extra,
                #ns_json::writing::Members::try_fields(&self.#ident)?,
            )?),
            write: quote! {
                #prefix
                #writer = #ns_json::writing::Members::try_members::<_, #first>(&self.#ident, #writer)?;
            },
        }
    }
}