sark-gen 0.12.0

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

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

pub(super) struct ValuePlan {
    access: TokenStream,
    value: Value,
    optional: bool,
}

pub(super) struct Emission {
    pub(super) len: TokenStream,
    pub(super) write: TokenStream,
}

enum Value {
    U64,
    I64,
    F64,
    Bool,
    Bytes {
        view: ByteView,
        encoding: ByteEncoding,
        length: ByteLength,
    },
    Nested {
        borrow: bool,
    },
    Sequence(Box<Self>),
}

#[derive(Clone, Copy)]
enum ByteView {
    Bytes,
    Slice,
}

#[derive(Clone, Copy)]
enum ByteEncoding {
    Raw,
    Plain,
    Escaped,
}

#[derive(Clone, Copy)]
enum ByteLength {
    Direct,
    View,
}

impl ValuePlan {
    pub(super) fn new(ty: &Type, mode: FieldMode, access: TokenStream) -> Result<Self> {
        let (value, optional) = if mode.seq {
            let elem = ty
                .vec_inner()
                .ok_or_else(|| Error::new_spanned(ty, "#[field(seq)] requires a Vec<T> field"))?;
            let value = if mode.nested {
                Value::Nested { borrow: false }
            } else {
                let class = Classified::of(elem)?;
                if class.optional {
                    return Err(Error::new_spanned(
                        elem,
                        "sequence field elements cannot be Option<T>",
                    ));
                }
                Value::scalar(class.scalar, mode, true, elem)?
            };
            (Value::Sequence(Box::new(value)), false)
        } else if mode.nested {
            (Value::Nested { borrow: true }, false)
        } else {
            let class = Classified::of(ty)?;
            (
                Value::scalar(class.scalar, mode, false, ty)?,
                class.optional,
            )
        };
        Ok(Self {
            access,
            value,
            optional,
        })
    }

    pub(super) fn emit(&self) -> Emission {
        if self.optional {
            self.value
                .emit(self.value.optional_access())
                .optional(&self.access)
        } else {
            self.value.emit(self.access.clone())
        }
    }
}

impl Value {
    fn scalar(scalar: Scalar, mode: FieldMode, sequence: bool, ty: &Type) -> Result<Self> {
        let encoding = ByteEncoding::from(mode);
        Ok(match scalar {
            Scalar::U64 if !sequence => Self::U64,
            Scalar::I64 if !sequence => Self::I64,
            Scalar::F64 if !sequence => Self::F64,
            Scalar::Bool if !sequence => Self::Bool,
            Scalar::String => Self::Bytes {
                view: ByteView::Bytes,
                encoding: if sequence {
                    encoding
                } else {
                    ByteEncoding::Escaped
                },
                length: ByteLength::from_sequence(sequence),
            },
            Scalar::Shared | Scalar::Borrowed | Scalar::Retained | Scalar::JsonBytes => {
                Self::Bytes {
                    view: ByteView::Slice,
                    encoding,
                    length: ByteLength::from_sequence(sequence),
                }
            }
            Scalar::InlineToken => Self::Bytes {
                view: ByteView::Bytes,
                encoding,
                length: ByteLength::from_sequence(sequence),
            },
            Scalar::U64 | Scalar::I64 | Scalar::F64 | Scalar::Bool => {
                return Err(Error::new_spanned(
                    ty,
                    "sequence field element must be a byte string",
                ));
            }
        })
    }

    fn emit(&self, access: TokenStream) -> Emission {
        match self {
            Self::U64 => Emission::new(
                quote!(sark::json::Encode::u64_len(#access)),
                quote!(__w.put_u64(#access)?;),
            ),
            Self::I64 => Emission::new(
                quote!(sark::json::Encode::i64_len(#access)),
                quote!(__w.put_i64(#access)?;),
            ),
            Self::F64 => Emission::new(
                quote!(sark::json::Encode::f64_len(#access)),
                quote!(__w.put_f64(#access)?;),
            ),
            Self::Bool => Emission::new(
                quote!(if #access { 4usize } else { 5usize }),
                quote! {
                    if #access {
                        __w.put(b"true")?;
                    } else {
                        __w.put(b"false")?;
                    }
                },
            ),
            Self::Bytes {
                view,
                encoding,
                length,
            } => Emission::bytes(&access, *view, *encoding, *length),
            Self::Nested { borrow } => {
                let access = if *borrow { quote!(&#access) } else { access };
                Emission::new(
                    quote!(sark::json::JsonEncode::json_len(#access)),
                    quote!(sark::json::JsonEncode::write_into(#access, __w)?;),
                )
            }
            Self::Sequence(element) => element.emit(quote!(__e)).sequence(&access),
        }
    }

    fn optional_access(&self) -> TokenStream {
        match self {
            Self::U64 | Self::I64 | Self::F64 | Self::Bool => quote!(*value),
            Self::Bytes { .. } | Self::Nested { .. } | Self::Sequence(_) => quote!(value),
        }
    }
}

impl ByteView {
    fn apply(self, access: &TokenStream) -> TokenStream {
        match self {
            Self::Bytes => quote!(#access.as_bytes()),
            Self::Slice => quote!(#access.as_slice()),
        }
    }
}

impl From<FieldMode> for ByteEncoding {
    fn from(mode: FieldMode) -> Self {
        if mode.raw {
            Self::Raw
        } else if mode.plain {
            Self::Plain
        } else {
            Self::Escaped
        }
    }
}

impl ByteLength {
    fn from_sequence(sequence: bool) -> Self {
        if sequence { Self::View } else { Self::Direct }
    }

    fn apply(self, access: &TokenStream, view: ByteView) -> TokenStream {
        match self {
            Self::Direct => access.clone(),
            Self::View => view.apply(access),
        }
    }
}

impl Emission {
    fn new(len: TokenStream, write: TokenStream) -> Self {
        Self { len, write }
    }

    fn bytes(
        access: &TokenStream,
        view: ByteView,
        encoding: ByteEncoding,
        length: ByteLength,
    ) -> Self {
        match encoding {
            ByteEncoding::Raw => {
                let len_bytes = length.apply(access, view);
                let write_bytes = view.apply(access);
                Self::new(quote!(#len_bytes.len()), quote!(__w.put(#write_bytes)?;))
            }
            ByteEncoding::Plain => {
                let len_bytes = length.apply(access, view);
                let write_bytes = view.apply(access);
                Self::new(
                    quote!(2usize + #len_bytes.len()),
                    quote!(__w.put_str_plain(#write_bytes)?;),
                )
            }
            ByteEncoding::Escaped => {
                let bytes = view.apply(access);
                Self::new(
                    quote!(sark::json::Encode::str_len(#bytes)),
                    quote!(__w.put_str(#bytes)?;),
                )
            }
        }
    }

    fn sequence(self, access: &TokenStream) -> Self {
        let element_len = self.len;
        let element_write = self.write;
        Self::new(
            quote! {{
                let mut __n = 2usize;
                let mut __first = true;
                for __e in (#access).iter() {
                    if !__first {
                        __n += 1;
                    }
                    __first = false;
                    __n += #element_len;
                }
                __n
            }},
            quote! {{
                __w.put(b"[")?;
                let mut __first = true;
                for __e in (#access).iter() {
                    if !__first {
                        __w.put(b",")?;
                    }
                    __first = false;
                    #element_write
                }
                __w.put(b"]")?;
            }},
        )
    }

    fn optional(self, access: &TokenStream) -> Self {
        let present_len = self.len;
        let present_write = self.write;
        Self::new(
            quote! {
                match &#access {
                    Some(value) => #present_len,
                    None => 4usize,
                }
            },
            quote! {
                match &#access {
                    Some(value) => { #present_write }
                    None => { __w.put(b"null")?; }
                }
            },
        )
    }
}