fieldwork-macro 0.1.0

Proc-macro implementation for fieldwork crate
Documentation
//! Parser for the `with_!` macro syntax.
//!
//! This module handles parsing of the macro input into structured AST types:
//! - [`WithInput`]: The complete macro input
//! - [`MethodDef`]: Method definitions in the `methods { }` block
//! - [`FieldDef`]: Field declarations in the `fields { }` block
//! - [`FieldParam`]: Field parameters in method signatures
//! - [`ExtraParam`]: Non-field parameters (e.g., `arg: Type`)

use syn::{
    braced, parenthesized,
    parse::{Parse, ParseStream},
    Block, Expr, Ident, Result, Token, Type,
};

/// Field access mode.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FieldMode {
    /// Shared reference: `&self.field`
    Ref,
    /// Mutable reference: `&mut self.field`
    Mut,
    /// Copy by value: `self.field` (requires Copy)
    Copy,
}

/// A field parameter in method definition: `&db`, `mut slices`, `copy id`
#[derive(Clone, Debug)]
pub struct FieldParam {
    pub mode: FieldMode,
    pub name: Ident,
}

impl Parse for FieldParam {
    fn parse(input: ParseStream) -> Result<Self> {
        // Check for leading `&`
        if input.peek(Token![&]) {
            input.parse::<Token![&]>()?;
        }

        // Check for `mut` or `copy`
        let mode = if input.peek(Token![mut]) {
            input.parse::<Token![mut]>()?;
            FieldMode::Mut
        } else if input.peek(Ident) {
            let fork = input.fork();
            let ident: Ident = fork.parse()?;
            if ident == "copy" {
                input.parse::<Ident>()?;
                FieldMode::Copy
            } else {
                FieldMode::Ref
            }
        } else {
            FieldMode::Ref
        };

        let name = input.parse()?;
        Ok(FieldParam { mode, name })
    }
}

/// An extra (non-field) parameter in method signature: `val: u64`
///
/// The type is parsed but not stored, as method bodies are inlined
/// and the actual argument expressions are substituted directly.
#[derive(Clone)]
pub struct ExtraParam {
    pub name: Ident,
}

impl Parse for ExtraParam {
    fn parse(input: ParseStream) -> Result<Self> {
        let name = input.parse()?;
        input.parse::<Token![:]>()?;
        // Parse and discard the type - we only need the name for substitution
        let _ty: Type = input.parse()?;
        Ok(ExtraParam { name })
    }
}

/// Method definition within the `methods { }` block.
///
/// The return type (`-> Type`) is parsed for syntax completeness but not stored,
/// as method bodies are inlined directly at call sites.
pub struct MethodDef {
    pub name: Ident,
    pub field_params: Vec<FieldParam>,
    pub extra_params: Vec<ExtraParam>,
    pub body: Block,
}

impl Parse for MethodDef {
    fn parse(input: ParseStream) -> Result<Self> {
        let name: Ident = input.parse()?;

        let content;
        parenthesized!(content in input);

        let mut field_params = Vec::new();
        let mut extra_params = Vec::new();

        while !content.is_empty() {
            if is_extra_param(&content) {
                extra_params.push(content.parse()?);
            } else {
                field_params.push(content.parse()?);
            }

            if content.peek(Token![,]) {
                content.parse::<Token![,]>()?;
            }
        }

        // Parse and discard return type - we inline the body directly
        if input.peek(Token![->]) {
            input.parse::<Token![->]>()?;
            let _return_type: Type = input.parse()?;
        }

        let body = input.parse()?;

        Ok(MethodDef {
            name,
            field_params,
            extra_params,
            body,
        })
    }
}

fn is_extra_param(input: ParseStream) -> bool {
    let fork = input.fork();

    if fork.peek(Token![&]) {
        let _ = fork.parse::<Token![&]>();
    }

    if fork.peek(Token![mut]) {
        let _ = fork.parse::<Token![mut]>();
    }

    if fork.parse::<Ident>().is_err() {
        return false;
    }

    fork.peek(Token![:])
}

/// Field definition within the `fields { }` block.
#[derive(Debug)]
pub struct FieldDef {
    pub mode: FieldMode,
    pub name: Ident,
}

impl Parse for FieldDef {
    fn parse(input: ParseStream) -> Result<Self> {
        let mode = if input.peek(Token![mut]) {
            input.parse::<Token![mut]>()?;
            FieldMode::Mut
        } else if input.peek(Ident) {
            let fork = input.fork();
            let ident: Ident = fork.parse()?;
            if ident == "copy" {
                input.parse::<Ident>()?;
                FieldMode::Copy
            } else {
                FieldMode::Ref
            }
        } else {
            FieldMode::Ref
        };

        let name = input.parse()?;
        Ok(FieldDef { mode, name })
    }
}

/// Complete input to the with_! macro.
pub struct WithInput {
    pub receiver: Expr,
    pub methods: Vec<MethodDef>,
    pub fields: Vec<FieldDef>,
    pub body: Block,
}

impl Parse for WithInput {
    fn parse(input: ParseStream) -> Result<Self> {
        let receiver = input.parse()?;
        input.parse::<Token![,]>()?;

        let main_content;
        braced!(main_content in input);

        let mut methods = Vec::new();
        let mut fields = Vec::new();
        let mut body = None;

        while !main_content.is_empty() {
            let keyword: Ident = main_content.parse()?;
            let keyword_str = keyword.to_string();

            match keyword_str.as_str() {
                "methods" => {
                    let methods_content;
                    braced!(methods_content in main_content);
                    while !methods_content.is_empty() {
                        methods.push(methods_content.parse()?);
                    }
                }
                "fields" => {
                    let fields_content;
                    braced!(fields_content in main_content);
                    while !fields_content.is_empty() {
                        fields.push(fields_content.parse()?);
                    }
                }
                "body" => {
                    body = Some(main_content.parse()?);
                }
                _ => {
                    return Err(syn::Error::new(
                        keyword.span(),
                        format!("expected 'methods', 'fields', or 'body', found '{}'", keyword_str),
                    ));
                }
            }
        }

        let body = body.ok_or_else(|| {
            syn::Error::new(input.span(), "missing 'body { }' block")
        })?;

        Ok(WithInput {
            receiver,
            methods,
            fields,
            body,
        })
    }
}