elvwf

8 commits
Updated 2026-06-13 11:19:44
derive/src/struct
derive/src/struct/header.rs
use std::str::FromStr;

use darling::FromMeta;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::*;

fn parse_field(field: &str) -> Result<(Option<String>, u8, Option<&str>)> {
    let (name, rest) = field
        .split_once(':')
        .map_or((field, None), |(n, r)| (n.trim(), Some(r.trim())));
    let (bits, value) = match rest {
        Some(r) => match r.split_once('=') {
            Some((s, v)) => (
                s.trim().parse().map_err(|_| {
                    Error::new(Span::call_site(), "Bit length should be an integer")
                })?,
                Some(v.trim()),
            ),
            None => (
                r.parse().map_err(|_| {
                    Error::new(Span::call_site(), "Bit length should be an integer")
                })?,
                None,
            ),
        },
        None => (1, None),
    };
    let name = (name != "_").then(|| name.to_uppercase());
    Ok((name, bits, value))
}

pub fn parse_header(input: &str) -> Result<(TokenStream, TokenStream)> {
    let fields = input
        .split('|')
        .map(str::trim)
        .filter(|s| !s.is_empty())
        .map(parse_field)
        .collect::<Result<Vec<(Option<String>, u8, Option<&str>)>>>()?;

    let total: usize = fields.iter().map(|(_, b, _)| *b as usize).sum();
    let width = total + 2;

    if !total.is_power_of_two() || total > 64 || total < 8 {
        return Err(Error::new(
            Span::call_site(),
            "Header len should be a power of 2, between 8 and 64",
        ));
    }

    let header = format_ident!("u{total}");

    let mut consts = Vec::<TokenStream>::with_capacity(fields.len());
    let mut base = Vec::<TokenStream>::with_capacity(fields.len());
    let mut count = 0u8;
    for (name, bits, value) in fields.iter().rev() {
        let mask = (((1u128 << bits) - 1) << count) as u64;
        let formatted = format!("{mask:#0width$b}");
        let formatted = TokenStream::from_str(&formatted)?;
        match (name, value) {
            (Some(name), Some(v)) => {
                let v = TokenStream::from_str(v)?;
                let name = Ident::from_string(name)?;
                let mname = format_ident!("MASK_{name}");

                base.push(quote!((Self::#name << Self::#mname.trailing_zeros())));
                consts.push(quote!(const #name: #header = #v;));
                consts.push(quote!(const #mname: #header = #formatted;));
            }
            (Some(name), None) => {
                let pname = if *bits == 1 {
                    format_ident!("FLAG_{name}")
                } else {
                    format_ident!("SLOT_{name}")
                };
                consts.push(quote!(const #pname: #header = #formatted;));
            }
            (None, Some(_)) => {
                return Err(Error::new(
                    Span::call_site(),
                    "Value should always be named",
                ));
            }
            _ => {}
        }
        count += bits;
    }

    Ok((
        quote!(#header),
        quote! {
            #(#consts)*
            const HBASE: #header = #(#base+)* 0;
        },
    ))
}