sark-gen 0.11.0

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

use super::field::FieldMode;
use super::scalar::{Classified, Scalar};
use crate::util::TypeExt;

pub(super) struct Encoder<'a> {
    ty: &'a Type,
    mode: FieldMode,
    access: TokenStream,
    class: Option<Classified>,
}

impl<'a> Encoder<'a> {
    pub(super) fn new(ty: &'a Type, mode: FieldMode, access: TokenStream) -> Result<Self> {
        let class = if mode.nested {
            if mode.seq && ty.vec_inner().is_none() {
                return Err(syn::Error::new_spanned(
                    ty,
                    "#[field(seq)] requires a Vec<T> field",
                ));
            }
            None
        } else if mode.seq {
            let elem = ty.vec_inner().ok_or_else(|| {
                syn::Error::new_spanned(ty, "#[field(seq)] requires a Vec<T> field")
            })?;
            let class = Classified::of(elem)?;
            if class.optional {
                return Err(syn::Error::new_spanned(
                    elem,
                    "sequence field elements cannot be Option<T>",
                ));
            }
            Some(class)
        } else {
            Some(Classified::of(ty)?)
        };
        Ok(Self {
            ty,
            mode,
            access,
            class,
        })
    }

    pub(super) fn len_expr(&self) -> Result<TokenStream> {
        let fmode = self.mode;
        let access = &self.access;
        if fmode.seq {
            let elem = if fmode.nested {
                quote!(sark::json::JsonEncode::json_len(__e))
            } else {
                let Some(class) = self.class else {
                    return Err(syn::Error::new_spanned(
                        self.ty,
                        "missing sequence element classification",
                    ));
                };
                let bytes = match class.scalar {
                    Scalar::String | Scalar::InlineToken => quote!(__e.as_bytes()),
                    Scalar::Shared | Scalar::Retained => quote!(__e.as_slice()),
                    _ => {
                        return Err(syn::Error::new_spanned(
                            self.ty,
                            "sequence field element must be a byte string",
                        ));
                    }
                };
                if fmode.raw {
                    quote!(#bytes.len())
                } else if fmode.plain {
                    quote!(2usize + #bytes.len())
                } else {
                    quote!(sark::json::Encode::str_len(#bytes))
                }
            };
            return Ok(quote! {{
                let mut __n = 2usize;
                let mut __first = true;
                for __e in (#access).iter() {
                    if !__first {
                        __n += 1;
                    }
                    __first = false;
                    __n += #elem;
                }
                __n
            }});
        }
        if fmode.nested {
            return Ok(quote!(sark::json::JsonEncode::json_len(&#access)));
        }
        let Some(class) = self.class else {
            return Err(syn::Error::new_spanned(
                self.ty,
                "missing scalar classification",
            ));
        };
        let len = match class.scalar {
            Scalar::U64 => quote!(sark::json::Encode::u64_len(#access)),
            Scalar::I64 => quote!(sark::json::Encode::i64_len(#access)),
            Scalar::F64 => quote!(sark::json::Encode::f64_len(#access)),
            Scalar::Bool => quote!(if #access { 4usize } else { 5usize }),
            Scalar::String => quote!(sark::json::Encode::str_len(#access.as_bytes())),
            Scalar::Shared => {
                if fmode.raw {
                    quote!(#access.len())
                } else if fmode.plain {
                    quote!(2usize + #access.len())
                } else {
                    quote!(sark::json::Encode::str_len(#access.as_slice()))
                }
            }
            Scalar::Retained => {
                if fmode.raw {
                    quote!(#access.len())
                } else if fmode.plain {
                    quote!(2usize + #access.len())
                } else {
                    quote!(sark::json::Encode::str_len(#access.as_slice()))
                }
            }
            Scalar::InlineToken => {
                if fmode.raw {
                    quote!(#access.len())
                } else if fmode.plain {
                    quote!(2usize + #access.len())
                } else {
                    quote!(sark::json::Encode::str_len(#access.as_bytes()))
                }
            }
        };
        Ok(if class.optional {
            quote! {
                match #access {
                    Some(value) => #len,
                    None => 4usize,
                }
            }
        } else {
            len
        })
    }

    pub(super) fn write_expr(&self) -> Result<TokenStream> {
        let fmode = self.mode;
        let access = &self.access;
        if fmode.seq {
            let elem = if fmode.nested {
                quote!(sark::json::JsonEncode::write_into(__e, __w);)
            } else {
                let Some(class) = self.class else {
                    return Err(syn::Error::new_spanned(
                        self.ty,
                        "missing sequence element classification",
                    ));
                };
                let bytes = match class.scalar {
                    Scalar::String | Scalar::InlineToken => quote!(__e.as_bytes()),
                    Scalar::Shared | Scalar::Retained => quote!(__e.as_slice()),
                    _ => {
                        return Err(syn::Error::new_spanned(
                            self.ty,
                            "sequence field element must be a byte string",
                        ));
                    }
                };
                if fmode.raw {
                    quote!(__w.put(#bytes);)
                } else if fmode.plain {
                    quote!(__w.put_str_plain(#bytes);)
                } else {
                    quote!(__w.put_str(#bytes);)
                }
            };
            return Ok(quote! {{
                __w.put(b"[");
                let mut __first = true;
                for __e in (#access).iter() {
                    if !__first {
                        __w.put(b",");
                    }
                    __first = false;
                    #elem
                }
                __w.put(b"]");
            }});
        }
        if fmode.nested {
            return Ok(quote!(sark::json::JsonEncode::write_into(&#access, __w);));
        }
        let Some(class) = self.class else {
            return Err(syn::Error::new_spanned(
                self.ty,
                "missing scalar classification",
            ));
        };
        let write = match class.scalar {
            Scalar::U64 => quote!(__w.put_u64(#access);),
            Scalar::I64 => quote!(__w.put_i64(#access);),
            Scalar::F64 => quote!(__w.put_f64(#access);),
            Scalar::Bool => quote! {
                if #access {
                    __w.put(b"true");
                } else {
                    __w.put(b"false");
                }
            },
            Scalar::String => quote!(__w.put_str(#access.as_bytes());),
            Scalar::Shared => {
                if fmode.raw {
                    quote!(__w.put(#access.as_slice());)
                } else if fmode.plain {
                    quote!(__w.put_str_plain(#access.as_slice());)
                } else {
                    quote!(__w.put_str(#access.as_slice());)
                }
            }
            Scalar::Retained => {
                if fmode.raw {
                    quote!(__w.put(#access.as_slice());)
                } else if fmode.plain {
                    quote!(__w.put_str_plain(#access.as_slice());)
                } else {
                    quote!(__w.put_str(#access.as_slice());)
                }
            }
            Scalar::InlineToken => {
                if fmode.raw {
                    quote!(__w.put(#access.as_bytes());)
                } else if fmode.plain {
                    quote!(__w.put_str_plain(#access.as_bytes());)
                } else {
                    quote!(__w.put_str(#access.as_bytes());)
                }
            }
        };
        Ok(if class.optional {
            quote! {
                match #access {
                    Some(value) => { #write }
                    None => { __w.put(b"null"); }
                }
            }
        } else {
            write
        })
    }
}