elvwf

8 commits
Updated 2026-06-13 11:19:44
derive/src/struct
derive/src/struct/parser.rs
use crate::r#struct::TriggerSource;

use super::{FieldKind, LenSpec, ValueSpec, WiredField, classify::*};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{spanned::Spanned, *};

pub struct ParsedField<'a> {
    pub ident: &'a Ident,
    pub len: TokenStream,
    pub header: TokenStream,
    pub encode: TokenStream,
    pub decode: TokenStream,
}

impl<'a> ParsedField<'a> {
    pub fn new(field: &'a WiredField, kind: FieldKind, last: bool) -> Result<Self> {
        let ident = field
            .ident
            .as_ref()
            .ok_or_else(|| Error::new(field.ty.span(), "Wired requires named fields"))?;

        let id_o = format_ident!("{}_opt", ident);
        let id_l = format_ident!("{}_len", ident);

        let mut header = Vec::new();
        let mut extra = Vec::new();

        let shape = match &kind {
            FieldKind::Optional(s) => {
                let trigger = field.opt_no_condition()?;
                push_trigger(
                    &mut header,
                    &mut extra,
                    trigger,
                    &id_o,
                    quote!(self.#ident.is_some()),
                );
                s
            }
            FieldKind::Required(s) => match &field.opt {
                None => s,
                Some(_) => {
                    let (cond, trigger) = field.opt_condition()?;
                    push_trigger(
                        &mut header,
                        &mut extra,
                        trigger,
                        &id_o,
                        quote!(self.#ident != #cond),
                    );
                    s
                }
            },
        };

        match shape {
            FieldShape::Scalar(_) => field.no_len()?,
            FieldShape::Slice(_) | FieldShape::Msg(_) => field.no_value()?,
        }

        if matches!(field.len(), Ok(LenSpec::Remaining)) && !last {
            return Err(Error::new(
                field.ident.span(),
                "Attribute `len(remaining)` can only be put on the last field",
            ));
        }

        let ty = shape.ty();
        let r = shape.root();
        let sub = kind.sub(&field.opt);
        let self_val = quote!(self.#ident);
        let self_ref = if shape.by_ref() {
            quote!(&self.#ident)
        } else {
            quote!(self.#ident)
        };
        let opt = kind.opt(&field.opt, &id_o);
        let ev = kind.extra_val(&field.opt);
        let er = kind.extra_ref(&field.opt);

        let (len, encode, decode) = match shape {
            FieldShape::Scalar(_) => match field.value()? {
                ValueSpec::Format(fmt) => {
                    let fmt = quote!(elvwf::scalar::#fmt);

                    (
                        quote!(len += #r #sub ::len::<#ty, #fmt>(#self_val #ev);),
                        quote!(#r #sub ::encode::<#ty, #fmt>(buf, #self_val #ev)?;),
                        quote!(#(#extra)* let #ident = #r #sub ::decode::<#ty, #fmt>(buf #opt #ev)?;),
                    )
                }
                ValueSpec::Slot(slot) => {
                    let slot_id = format_ident!("SLOT_{}", slot);
                    header.push(quote!(
                        h = elvwf::header::put(
                            h,
                            #self_val,
                            Self::#slot_id,
                        )?;
                    ));

                    (
                        quote!(),
                        quote!(),
                        quote!(let #ident = elvwf::header::get(h, Self::#slot_id)?;),
                    )
                }
            },
            FieldShape::Slice(_) | FieldShape::Msg(_) => match field.len()? {
                LenSpec::Prefixed { format } => {
                    let fmt = quote!(elvwf::scalar::#format);

                    (
                        quote!(len += #r #sub ::prefixed_len::<#ty, #fmt>(#self_ref #er);),
                        quote!(#r #sub ::encode::<#ty, #fmt>(buf, #self_val #ev)?;),
                        quote!(#(#extra)* let #ident = #r #sub ::decode::<#ty, #fmt>(buf #opt #ev)?;),
                    )
                }
                LenSpec::Slot(slot) => {
                    let slot_id = format_ident!("SLOT_{}", slot);
                    header.push(quote!(
                        h = elvwf::header::put(
                            h,
                            #r #sub ::value_len::<#ty>(#self_ref #ev),
                            Self::#slot_id,
                        )?;
                    ));
                    extra.push(quote!(let #id_l = elvwf::header::get(h, Self::#slot_id)?;));

                    (
                        quote!(len += #r #sub ::value_len::<#ty>(#self_ref #er);),
                        quote!(#r #sub ::encode_value::<#ty>(buf, #self_val #ev)?;),
                        quote!(#(#extra)* let #ident = #r #sub ::decode_value::<#ty>(buf #opt, #id_l)?;),
                    )
                }
                LenSpec::Remaining => {
                    extra.push(quote!(let #id_l: usize = len - (_bs - buf.len());));

                    (
                        quote!(len += #r #sub ::value_len::<#ty>(#self_ref #er);),
                        quote!(#r #sub ::encode_value::<#ty>(buf, #self_val #ev)?;),
                        quote!(#(#extra)* let #ident = #r #sub ::decode_value::<#ty>(buf #opt #ev, #id_l)?;),
                    )
                }
                LenSpec::Embedded => (
                    quote!(len += #r #sub ::value_len::<#ty>(#self_ref #er);),
                    quote!(#r #sub ::encode_value::<#ty>(buf, #self_val #ev)?;),
                    quote!(#(#extra)* let #ident = #r #sub ::decode_value::<#ty>(buf #opt #ev, 8)?;),
                ),
            },
        };

        Ok(Self {
            ident,
            len,
            header: quote!(#(#header)*),
            encode,
            decode,
        })
    }
}

fn push_trigger(
    header: &mut Vec<TokenStream>,
    extra: &mut Vec<TokenStream>,
    trigger: &TriggerSource,
    ident_opt: &Ident,
    cond_expr: TokenStream,
) {
    match trigger {
        TriggerSource::Flag(flag) => {
            let flag = format_ident!("FLAG_{}", flag);
            header.push(quote!(h = elvwf::header::trigger(h, #cond_expr, Self::#flag);));
            extra.push(quote!(let #ident_opt = elvwf::header::has(h, Self::#flag);));
        }
    }
}