blob: 7375895aa22279c0efb8b7b4b4a324ddac8e6c57 [file] [edit]
//! Attribute parsing for tables, fields, variants, and enums.
use std::str::FromStr;
use font_types::Tag;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use regex::Captures;
use syn::{
parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Attribute, Token,
};
use super::logged_syn_error;
mod kw {
syn::custom_keyword!(skip);
}
#[derive(Debug, Clone)]
pub(crate) struct Attr<T> {
pub(crate) name: syn::Ident,
pub(crate) attr: T,
}
impl<T> Attr<T> {
fn new(name: syn::Ident, attr: T) -> Self {
Attr { name, attr }
}
pub(crate) fn span(&self) -> Span {
self.name.span()
}
}
impl<T> std::ops::Deref for Attr<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.attr
}
}
impl<T: ToTokens> ToTokens for Attr<T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.attr.to_tokens(tokens)
}
}
#[derive(Debug, Default, Clone)]
pub(crate) struct TableAttrs {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) skip_font_write: Option<syn::Path>,
pub(crate) skip_from_obj: Option<syn::Path>,
pub(crate) skip_constructor: Option<syn::Path>,
pub(crate) read_args: Option<Attr<TableReadArgs>>,
pub(crate) generic_offset: Option<Attr<syn::Ident>>,
pub(crate) tag: Option<Attr<syn::LitStr>>,
pub(crate) write_only: Option<syn::Path>,
/// Custom validation behaviour, must be a fn(&self, &mut ValidationCtx) for the type
pub(crate) validate: Option<Attr<syn::Ident>>,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct FieldAttrs {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) nullable: Option<syn::Path>,
pub(crate) conditional: Option<Attr<Condition>>,
pub(crate) skip_getter: Option<syn::Path>,
/// specify that an offset getter has a custom impl
pub(crate) offset_getter: Option<Attr<syn::Ident>>,
/// optionally a method on the parent type used to generate the offset data
/// source for this item.
pub(crate) offset_data: Option<Attr<syn::Ident>>,
/// If present, argument is an expression that evaluates to a u32, and is
/// used to adjust the write position of offsets.
//TODO: this could maybe be combined with offset_data?
pub(crate) offset_adjustment: Option<Attr<InlineExpr>>,
pub(crate) version: Option<syn::Path>,
pub(crate) format: Option<Attr<syn::LitInt>>,
pub(crate) count: Option<Attr<Count>>,
pub(crate) compile: Option<Attr<CustomCompile>>,
pub(crate) compile_with: Option<Attr<syn::Ident>>,
pub(crate) default: Option<Attr<syn::Expr>>,
pub(crate) compile_type: Option<Attr<syn::Type>>,
pub(crate) read_with_args: Option<Attr<FieldReadArgs>>,
pub(crate) read_offset_args: Option<Attr<FieldReadArgs>>,
/// If present, a custom method that returns a FieldType for this field,
/// during traversal.
pub(crate) traverse_with: Option<Attr<syn::Ident>>,
pub(crate) to_owned: Option<Attr<InlineExpr>>,
/// Custom validation behaviour
pub(crate) validate: Option<Attr<FieldValidation>>,
/// Marks this field as the discriminant for a generic offset type.
pub(crate) discriminant: Option<syn::Path>,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct VariantAttrs {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) match_stmt: Option<Attr<InlineExpr>>,
pub(crate) write_only: Option<syn::Path>,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct EnumVariantAttrs {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) default: Option<syn::Path>,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct TableReadArgs {
pub(crate) args: Vec<TableReadArg>,
}
#[derive(Debug, Clone)]
pub(crate) struct TableReadArg {
pub(crate) ident: syn::Ident,
pub(crate) typ: syn::Ident,
}
#[derive(Debug, Clone)]
pub(crate) struct FieldReadArgs {
pub(crate) inputs: Vec<syn::Ident>,
}
#[derive(Clone, Debug)]
pub(crate) enum Condition {
SinceVersion(VersionSpec),
IfFlag { field: syn::Ident, flag: syn::Path },
}
#[derive(Clone, Debug)]
pub(crate) struct VersionSpec {
pub(crate) major: u16,
pub(crate) minor: Option<u16>,
}
/// Annotations for how to calculate the count of an array.
///
/// ```no_compile
/// #[count(1)] #[count(..)] #[count($hi)] // simple
/// #[count(subtract($field, 1))] // complex
/// ```
#[derive(Clone, Debug)]
pub(crate) enum Count {
// the field isn't used, but it's nice to hold onto if we want to print errors
// in the future
#[allow(dead_code)]
All(syn::token::DotDot),
SingleArg(CountArg),
Complicated {
args: Vec<CountArg>,
xform: CountTransform,
},
}
#[derive(Clone, Debug)]
pub(crate) enum CountArg {
Field(syn::Ident),
Literal(syn::LitInt),
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum CountTransform {
/// requires exactly two args, defined as $arg1 - $arg2
Sub,
/// requires exactly two args, defined as $arg1 + $arg2
Add,
/// requires exactly three args, defined as ($arg1 + $arg2) * $arg3
AddMul,
/// requires exactly three args, defined as ($arg1 * $arg2) + $arg3
MulAdd,
/// requires exactly one arg. defined as $arg1 / 2
Half,
DeltaValueCount,
DeltaSetIndexData,
/// three args: the axis count, the tuple index, and a constant on that index
TupleLen,
/// only ItemVariationStore: requires item_count, word_delta_count, region_index_count
ItemVariationDataLen,
/// Number of bytes to hold a bitmap of N items
BitmapLen,
/// requires exactly two args, defined as $arg1 - $arg2 + 2
SubAddTwo,
/// requires exactly one arg. Get the count from the $arg1.`try_into::<usize>`().
TryInto,
}
/// Attributes for specifying how to compile a field
#[derive(Debug, Clone)]
pub(crate) enum CustomCompile {
/// this field is ignored
Skip,
/// an inline is provided for calculating this field's value
Expr(InlineExpr),
}
/// Attributes for specifying how to validate a field
#[derive(Debug, Clone)]
pub(crate) enum FieldValidation {
/// this field is not validated
Skip,
/// the field is validated with a custom method.
///
/// This must be a method with a &self param and a &mut ValidationCtx param.
Custom(syn::Ident),
}
/// an inline expression used in an attribute
///
/// this has one fancy quality: you can reference fields of the current
/// object by prepending a '$' to the field name, e.g.
///
/// `#[count( $num_items - 1 )]`
#[derive(Debug, Clone)]
pub(crate) struct InlineExpr {
pub(crate) expr: Box<syn::Expr>,
// the expression used in a compilation context. This resolves any referenced
// fields against `self`.
compile_expr: Option<Box<syn::Expr>>,
pub(crate) referenced_fields: Vec<syn::Ident>,
}
impl InlineExpr {
pub(crate) fn compile_expr(&self) -> &syn::Expr {
self.compile_expr.as_ref().unwrap_or(&self.expr)
}
}
// ── FieldAttrs helpers ──────────────────────────────────────────────
impl FieldAttrs {
// returns an error if multiple condition attributes are present, which I hope
// to not need to support
fn checked_set_condition(
&mut self,
ident: &syn::Ident,
condition: Condition,
) -> syn::Result<()> {
if let Some(existing) = &self.conditional {
return Err(syn::Error::new(
ident.span(),
format!(
"condition conflicts with existing condition {}",
existing.name
),
));
}
self.conditional = Some(Attr::new(ident.clone(), condition));
Ok(())
}
}
// ── Attribute name constants ────────────────────────────────────────
static DOC: &str = "doc";
static NULLABLE: &str = "nullable";
static SKIP_GETTER: &str = "skip_getter";
static COUNT: &str = "count";
static SINCE_VERSION: &str = "since_version";
static IF_FLAG: &str = "if_flag";
static FORMAT: &str = "format";
static VERSION: &str = "version";
static OFFSET_GETTER: &str = "offset_getter";
static OFFSET_DATA: &str = "offset_data_method";
static OFFSET_ADJUSTMENT: &str = "offset_adjustment";
static COMPILE: &str = "compile";
static COMPILE_WITH: &str = "compile_with";
static COMPILE_TYPE: &str = "compile_type";
static DEFAULT: &str = "default";
static READ_WITH: &str = "read_with";
static READ_OFFSET_WITH: &str = "read_offset_with";
static TRAVERSE_WITH: &str = "traverse_with";
static TO_OWNED: &str = "to_owned";
static VALIDATE: &str = "validate";
static DISCRIMINANT: &str = "discriminant";
static MATCH_IF: &str = "match_if";
static WRITE_FONTS_ONLY: &str = "write_fonts_only";
static SKIP_FROM_OBJ: &str = "skip_from_obj";
static SKIP_FONT_WRITE: &str = "skip_font_write";
static SKIP_CONSTRUCTOR: &str = "skip_constructor";
static READ_ARGS: &str = "read_args";
static GENERIC_OFFSET: &str = "generic_offset";
static TAG: &str = "tag";
// ── Parse impls for attr containers ─────────────────────────────────
impl Parse for FieldAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut this = FieldAttrs::default();
let attrs = Attribute::parse_outer(input)
.map_err(|e| syn::Error::new(e.span(), format!("hmm: '{e}'")))?;
for attr in attrs {
let ident = attr.path().get_ident().ok_or_else(|| {
syn::Error::new(
attr.path().span(),
"attr paths should be a single identifier",
)
})?;
if ident == DOC {
this.docs.push(attr);
} else if ident == NULLABLE {
this.nullable = Some(attr.path().clone());
} else if ident == SKIP_GETTER {
this.skip_getter = Some(attr.path().clone());
} else if ident == OFFSET_GETTER {
this.offset_getter = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == OFFSET_DATA {
this.offset_data = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == OFFSET_ADJUSTMENT {
this.offset_adjustment = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == VERSION {
this.version = Some(attr.path().clone());
} else if ident == COUNT {
this.count = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == COMPILE {
this.compile = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == COMPILE_WITH {
this.compile_with = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == COMPILE_TYPE {
this.compile_type = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == DEFAULT {
this.default = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == VALIDATE {
this.validate = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == TO_OWNED {
this.to_owned = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == SINCE_VERSION {
let spec = attr.parse_args()?;
this.checked_set_condition(ident, Condition::SinceVersion(spec))?;
} else if ident == IF_FLAG {
let condition = parse_if_flag(&attr)?;
this.checked_set_condition(ident, condition)?;
} else if ident == READ_WITH {
this.read_with_args = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == READ_OFFSET_WITH {
this.read_offset_args = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == TRAVERSE_WITH {
this.traverse_with = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == FORMAT {
this.format = Some(Attr::new(ident.clone(), parse_attr_eq_value(&attr)?))
} else if ident == DISCRIMINANT {
this.discriminant = Some(attr.path().clone());
} else {
return Err(logged_syn_error(
ident.span(),
format!("unknown field attribute {ident}"),
));
}
}
Ok(this)
}
}
impl Parse for TableAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut this = TableAttrs::default();
let attrs = Attribute::parse_outer(input)
.map_err(|e| syn::Error::new(e.span(), format!("hmm: '{e}'")))?;
for attr in attrs {
let ident = attr.path().get_ident().ok_or_else(|| {
syn::Error::new(
attr.path().span(),
"attr paths should be a single identifier",
)
})?;
if ident == DOC {
this.docs.push(attr);
} else if ident == SKIP_FROM_OBJ {
this.skip_from_obj = Some(attr.path().clone());
} else if ident == SKIP_FONT_WRITE {
this.skip_font_write = Some(attr.path().clone());
} else if ident == SKIP_CONSTRUCTOR {
this.skip_constructor = Some(attr.path().clone());
} else if ident == WRITE_FONTS_ONLY {
this.write_only = Some(attr.path().clone());
} else if ident == READ_ARGS {
this.read_args = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == GENERIC_OFFSET {
this.generic_offset = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == TAG {
let tag: syn::LitStr = parse_attr_eq_value(&attr)?;
if let Err(e) = Tag::new_checked(tag.value().as_bytes()) {
return Err(logged_syn_error(tag.span(), format!("invalid tag: '{e}'")));
}
this.tag = Some(Attr::new(ident.clone(), tag))
} else if ident == VALIDATE {
this.validate = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else {
return Err(logged_syn_error(
ident.span(),
format!("unknown table attribute {ident}"),
));
}
}
Ok(this)
}
}
impl Parse for VariantAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut this = VariantAttrs::default();
let attrs = Attribute::parse_outer(input)
.map_err(|e| syn::Error::new(e.span(), format!("hmm: '{e}'")))?;
for attr in attrs {
let ident = attr.path().get_ident().ok_or_else(|| {
syn::Error::new(
attr.path().span(),
"attr paths should be a single identifier",
)
})?;
if ident == DOC {
this.docs.push(attr);
} else if ident == MATCH_IF {
this.match_stmt = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == WRITE_FONTS_ONLY {
this.write_only = Some(attr.path().clone());
} else {
return Err(logged_syn_error(
ident.span(),
format!("unknown variant attribute {ident}"),
));
}
}
Ok(this)
}
}
impl Parse for EnumVariantAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut this = EnumVariantAttrs::default();
let attrs = Attribute::parse_outer(input)
.map_err(|e| syn::Error::new(e.span(), format!("hmm: '{e}'")))?;
for attr in attrs {
let ident = attr.path().get_ident().ok_or_else(|| {
syn::Error::new(
attr.path().span(),
"attr paths should be a single identifier",
)
})?;
if ident == DOC {
this.docs.push(attr);
} else if ident == DEFAULT {
this.default = Some(attr.path().clone());
} else {
return Err(logged_syn_error(
ident.span(),
format!("unknown field attribute {ident}"),
));
}
}
Ok(this)
}
}
// ── Parse impls for attr value types ────────────────────────────────
impl Parse for TableReadArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let args = Punctuated::<TableReadArg, Token![,]>::parse_separated_nonempty(input)?
.into_iter()
.collect();
Ok(TableReadArgs { args })
}
}
impl Parse for TableReadArg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
input.parse::<Token![:]>()?;
let typ = input.parse()?;
Ok(TableReadArg { ident, typ })
}
}
impl Parse for FieldReadArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut inputs = Vec::new();
while !input.is_empty() {
input.parse::<Token![$]>()?;
inputs.push(input.parse::<syn::Ident>()?);
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(FieldReadArgs { inputs })
}
}
impl Parse for VersionSpec {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fork = input.fork();
if fork.parse::<syn::LitInt>().is_ok() && fork.is_empty() {
let major = input.parse::<syn::LitInt>()?;
let major: u16 = major.base10_parse()?;
return Ok(VersionSpec { major, minor: None });
}
let version = input.parse::<syn::LitFloat>()?;
let Some((major, minor)) = version.base10_digits().split_once('.') else {
return Err(syn::Error::new(version.span(), "version should be single integer major or major.minor (e.g. '1', '4', '1.1', '2.5')"));
};
let major = major.parse::<u16>();
let minor = minor.parse::<u16>();
major
.and_then(|major| {
minor.map(|minor| VersionSpec {
major,
minor: Some(minor),
})
})
.map_err(|_| syn::Error::new(version.span(), "could not parse major/minor version"))
}
}
impl ToTokens for VersionSpec {
fn to_tokens(&self, tokens: &mut TokenStream) {
let major = &self.major;
if let Some(minor) = &self.minor {
tokens.extend(quote!( (#major, #minor) ));
} else {
major.to_tokens(tokens);
}
}
}
impl Parse for CountArg {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![$]) {
input.parse::<Token![$]>()?;
input.parse().map(Self::Field)
} else {
let int = input.parse::<syn::LitInt>()?;
let digits = int.base10_digits();
if digits.starts_with('-') {
return Err(syn::Error::new(
input.span(),
"negative count is not supported",
));
}
//HACK: we ensure these literals always have an explicit type.
//Is this necessary? no clue.
if int.suffix() != "usize" {
let reparse = format!("{digits}_usize");
syn::parse_str(&reparse).map(Self::Literal)
} else {
Ok(Self::Literal(int))
}
}
}
}
impl Parse for Count {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![..]) {
input.parse().map(Count::All)
} else if input.peek(syn::Ident) {
// leading ident must be a function
let xform = input.parse()?;
let content;
let _ = parenthesized!(content in input);
let args = Punctuated::<CountArg, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
Count::try_from_fancy_stuff(input.span(), xform, args)
} else {
input.parse().map(Self::SingleArg)
}
}
}
impl Parse for CountTransform {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
CountTransform::from_str(&ident.to_string())
.map_err(|err| syn::Error::new(ident.span(), err))
}
}
static TRANSFORM_IDENTS: &[(CountTransform, &str)] = &[
(CountTransform::Sub, "subtract"),
(CountTransform::Add, "add"),
(CountTransform::AddMul, "add_multiply"),
(CountTransform::MulAdd, "multiply_add"),
(CountTransform::Half, "half"),
(CountTransform::DeltaValueCount, "delta_value_count"),
(CountTransform::DeltaSetIndexData, "delta_set_index_data"),
(CountTransform::TupleLen, "tuple_len"),
(
CountTransform::ItemVariationDataLen,
"item_variation_data_len",
),
(CountTransform::BitmapLen, "bitmap_len"),
(CountTransform::SubAddTwo, "subtract_add_two"),
(CountTransform::TryInto, "try_into"),
];
impl FromStr for CountTransform {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
TRANSFORM_IDENTS
.iter()
.find_map(|(var, ident)| (*ident == s).then_some(*var))
.ok_or_else(|| {
format!(
"invalid transform, expected one of {}",
TRANSFORM_IDENTS
.iter()
.map(|(_, ident)| format!("'{ident}'"))
.collect::<Vec<_>>()
.join(", ")
)
})
}
}
impl CountTransform {
fn arg_count(self) -> usize {
match self {
CountTransform::Sub => 2,
CountTransform::Add => 2,
CountTransform::AddMul => 3,
CountTransform::MulAdd => 3,
CountTransform::Half => 1,
CountTransform::DeltaValueCount => 3,
CountTransform::DeltaSetIndexData => 2,
CountTransform::TupleLen => 3,
CountTransform::ItemVariationDataLen => 3,
CountTransform::BitmapLen => 1,
CountTransform::SubAddTwo => 2,
CountTransform::TryInto => 1,
}
}
}
impl ToTokens for CountArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
CountArg::Field(fld) => fld.to_tokens(tokens),
CountArg::Literal(lit) => lit.to_tokens(tokens),
}
}
}
impl Count {
fn try_from_fancy_stuff(
err_span: Span,
xform: CountTransform,
args: Vec<CountArg>,
) -> Result<Self, syn::Error> {
let expected_arg_count = xform.arg_count();
if args.len() != expected_arg_count {
return Err(syn::Error::new(
err_span,
format!("expected {expected_arg_count} arguments"),
));
}
Ok(Count::Complicated { args, xform })
}
pub(crate) fn single_field(&self) -> Option<&syn::Ident> {
if let Count::SingleArg(CountArg::Field(ident)) = self {
Some(ident)
} else {
None
}
}
pub(crate) fn lit_int(&self) -> Option<&syn::LitInt> {
if let Count::SingleArg(CountArg::Literal(int)) = self {
Some(int)
} else {
None
}
}
pub(crate) fn iter_referenced_fields(&self) -> impl Iterator<Item = &syn::Ident> {
let (one, two) = match self {
Self::SingleArg(CountArg::Field(ident)) => (Some(ident), None),
Self::Complicated { args, .. } => (
None,
Some(args.iter().filter_map(|arg| match arg {
CountArg::Field(ident) => Some(ident),
_ => None,
})),
),
_ => (None, None),
};
// a trick so we return the exact sample iterator type from both match arms
one.into_iter().chain(two.into_iter().flatten())
}
pub(crate) fn count_expr(&self) -> TokenStream {
match self {
Count::All(_) => unreachable!("'all' count handled separately"),
Count::SingleArg(CountArg::Field(arg)) => quote!(transforms::to_usize(#arg)),
Count::SingleArg(CountArg::Literal(arg)) => quote!(#arg),
Count::Complicated { args, xform } => match (xform, args.as_slice()) {
(CountTransform::Sub, [a, b]) => {
quote!(transforms::subtract(#a, #b))
}
(CountTransform::Add, [a, b]) => {
quote!(transforms::add(#a, #b))
}
(CountTransform::AddMul, [a, b, c]) => {
quote!(transforms::add_multiply(#a, #b, #c))
}
(CountTransform::MulAdd, [a, b, c]) => {
quote!(transforms::multiply_add(#a, #b, #c))
}
(CountTransform::Half, [a]) => {
quote!(transforms::half(#a))
}
(CountTransform::DeltaSetIndexData, [a, b]) => {
quote!(EntryFormat::map_size(#a, #b))
}
(CountTransform::DeltaValueCount, [a, b, c]) => {
quote!(DeltaFormat::value_count(#a, #b, #c))
}
(CountTransform::TupleLen, [a, b, c]) => {
quote!(TupleIndex::tuple_len(#a, #b, #c))
}
(CountTransform::ItemVariationDataLen, [a, b, c]) => {
quote!(ItemVariationData::delta_sets_len(#a, #b, #c))
}
(CountTransform::BitmapLen, [a]) => {
quote!(transforms::bitmap_len(#a))
}
(CountTransform::SubAddTwo, [a, b]) => {
quote!(transforms::subtract_add_two(#a, #b))
}
(CountTransform::TryInto, [a]) => {
quote!(transforms::to_usize(#a))
}
_ => unreachable!("validated before now"),
},
}
}
}
impl Parse for CustomCompile {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fork = input.fork();
if fork.parse::<kw::skip>().is_ok() && fork.is_empty() {
input.parse::<kw::skip>()?;
return Ok(Self::Skip);
}
input.parse().map(Self::Expr)
}
}
impl Parse for FieldValidation {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fork = input.fork();
if fork.parse::<kw::skip>().is_ok() && fork.is_empty() {
input.parse::<kw::skip>()?;
return Ok(Self::Skip);
}
input.parse().map(Self::Custom)
}
}
impl Parse for InlineExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
fn parse_inline_expr(tokens: TokenStream) -> syn::Result<InlineExpr> {
let span = tokens.span();
let s = tokens.to_string();
let mut idents = Vec::new();
let find_dollar_idents = regex::Regex::new(r"(\$) (\w+)").unwrap();
for ident in find_dollar_idents.captures_iter(&s) {
let text = ident.get(2).unwrap().as_str();
let ident = syn::parse_str::<syn::Ident>(text).map_err(|_| {
syn::Error::new(tokens.span(), format!("invalid ident '{text}'"))
})?;
idents.push(ident);
}
let expr: syn::Expr = if idents.is_empty() {
syn::parse2(tokens)
} else {
let new_source = find_dollar_idents.replace_all(&s, "$2");
syn::parse_str(&new_source)
}
.map_err(|_| syn::Error::new(span, "failed to parse expression"))?;
let compile_expr = (!idents.is_empty())
.then(|| {
let new_source =
find_dollar_idents.replace_all(&s, replace_field_with_compile_field);
syn::parse_str::<syn::Expr>(&new_source)
})
.transpose()?
.map(Box::new);
idents.sort_unstable();
idents.dedup();
Ok(InlineExpr {
expr: expr.into(),
compile_expr,
referenced_fields: idents,
})
}
let tokens: TokenStream = input.parse()?;
parse_inline_expr(tokens)
}
}
fn replace_field_with_compile_field(captures: &Captures) -> String {
let ident = captures.get(2).unwrap().as_str();
let ident = crate::fields::remove_offset_from_field_name(ident);
format!("&self.{ident}")
}
// ── Helper functions ────────────────────────────────────────────────
fn parse_attr_eq_value<T: Parse>(attr: &syn::Attribute) -> syn::Result<T> {
let tokens = attr.meta.require_name_value()?.value.to_token_stream();
syn::parse2::<T>(tokens).map_err(|err| syn::Error::new(attr.meta.span(), err.to_string()))
}
fn parse_if_flag(attr: &syn::Attribute) -> syn::Result<Condition> {
struct IfFlag(syn::Ident, syn::Path);
impl Parse for IfFlag {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<Token![$]>()?;
let ident = input.parse::<syn::Ident>()?;
input.parse::<Token![,]>()?;
let path = input.parse::<syn::Path>()?;
Ok(IfFlag(ident, path))
}
}
attr.parse_args::<IfFlag>()
.map(|IfFlag(field, flag)| Condition::IfFlag { field, flag })
.map_err(|e| {
syn::Error::new(
e.span(),
format!("expected #[if_flag($field_name, FlagType::SOME_FLAG)]: '{e}'"),
)
})
}