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);));
}
}
}