csv-codegen 0.2.3

A Rust procedural macro that transforms CSV data into safe, zero-cost code. Generate match arms, loops, and nested queries directly from CSV files, ensuring type safety and deterministic code generation.
Documentation
use proc_macro2::Ident;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Lit, Token, parenthesized, token::Paren};

#[derive(Debug, PartialEq, Clone)]
pub struct FilterExpression {
    pub _parens: Paren,
    pub expr: FilterExpressionInner,
}

#[derive(Debug, PartialEq, Clone)]
pub enum FilterExpressionInner {
    All(Punctuated<FilterExpressionInner, Token![&&]>),
    Any(Punctuated<FilterExpressionInner, Token![||]>),
    Comparison(PredicateComparison),
}

impl Default for FilterExpression {
    fn default() -> Self {
        Self {
            _parens: Default::default(),
            expr: FilterExpressionInner::All(Punctuated::new()),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct PredicateComparison {
    pub(crate) field: Ident,
    operator: WhereOp,
    value: Lit,
}

#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum WhereOp {
    Equal(Token![==]),
    NotEqual(Token![!=]),
}

impl Parse for FilterExpression {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;
        let _parens = parenthesized!(content in input);
        let expr = content.parse()?;
        Ok(Self { _parens, expr })
    }
}

impl Parse for FilterExpressionInner {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let first = FilterExpressionInner::Comparison(input.parse()?);
        if input.peek(Token![&&]) {
            let mut all = syn::punctuated::Punctuated::new();
            all.push(first);
            all.push_punct(input.parse()?);
            all.extend(syn::punctuated::Punctuated::parse_terminated(input)?.into_pairs());
            Ok(FilterExpressionInner::All(all))
        } else if input.peek(Token![||]) {
            let mut any = syn::punctuated::Punctuated::new();
            any.push(first);
            any.push_punct(input.parse()?);
            any.extend(syn::punctuated::Punctuated::parse_terminated(input)?.into_pairs());
            Ok(FilterExpressionInner::Any(any))
        } else {
            Ok(first)
        }
    }
}

impl Parse for PredicateComparison {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let field;
        let operator;
        let value;
        if input.peek(syn::Ident) {
            field = input.parse()?;
            operator = input.parse()?;
            value = input.parse()?;
        } else if input.peek(syn::Lit) {
            value = input.parse()?;
            operator = input.parse()?;
            field = input.parse()?;
        } else {
            return Err(input.error("Expected ident or literal"));
        }
        let comparison = PredicateComparison {
            field,
            operator,
            value,
        };
        Ok(comparison)
    }
}

impl Parse for WhereOp {
    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
        if input.peek(Token![==]) {
            input.parse().map(Self::Equal)
        } else if input.peek(Token![!=]) {
            input.parse().map(Self::NotEqual)
        } else {
            Err(input.error("Expected `==` or `!=`, other comparators are not yet supported."))
        }
    }
}

impl PredicateComparison {
    pub fn evaluate(&self, record_value: Lit) -> bool {
        match self.operator {
            WhereOp::Equal(_) => record_value == self.value,
            WhereOp::NotEqual(_) => record_value != self.value,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use insta::assert_debug_snapshot;
    use syn::parse_quote;

    #[test]
    fn parse_condition() {
        let parsed1: FilterExpressionInner = parse_quote!(field == "field");
        let parsed2: FilterExpressionInner = parse_quote!("field" == field);
        assert_eq!(parsed1, parsed2);
        assert_debug_snapshot!(parsed1, @r###"
        Comparison(
            PredicateComparison {
                field: Ident(
                    field,
                ),
                operator: Equal(
                    EqEq,
                ),
                value: Lit::Str {
                    token: "field",
                },
            },
        )
        "###);

        let parsed1: FilterExpressionInner = parse_quote!(field != "field");
        let parsed2: FilterExpressionInner = parse_quote!("field" != field);
        assert_eq!(parsed1, parsed2);
        assert_debug_snapshot!(parsed1, @r###"
        Comparison(
            PredicateComparison {
                field: Ident(
                    field,
                ),
                operator: NotEqual(
                    Ne,
                ),
                value: Lit::Str {
                    token: "field",
                },
            },
        )
        "###);

        let parsed1: FilterExpressionInner =
            parse_quote!(field == "field" && field2 == "field2" && field3 == "field3");
        assert_debug_snapshot!(parsed1, @r###"
        All(
            [
                Comparison(
                    PredicateComparison {
                        field: Ident(
                            field,
                        ),
                        operator: Equal(
                            EqEq,
                        ),
                        value: Lit::Str {
                            token: "field",
                        },
                    },
                ),
                AndAnd,
                All(
                    [
                        Comparison(
                            PredicateComparison {
                                field: Ident(
                                    field2,
                                ),
                                operator: Equal(
                                    EqEq,
                                ),
                                value: Lit::Str {
                                    token: "field2",
                                },
                            },
                        ),
                        AndAnd,
                        Comparison(
                            PredicateComparison {
                                field: Ident(
                                    field3,
                                ),
                                operator: Equal(
                                    EqEq,
                                ),
                                value: Lit::Str {
                                    token: "field3",
                                },
                            },
                        ),
                    ],
                ),
            ],
        )
        "###);

        let parsed1: FilterExpressionInner = parse_quote!(field == "field" || field2 == "field2");
        assert_debug_snapshot!(parsed1, @r###"
        Any(
            [
                Comparison(
                    PredicateComparison {
                        field: Ident(
                            field,
                        ),
                        operator: Equal(
                            EqEq,
                        ),
                        value: Lit::Str {
                            token: "field",
                        },
                    },
                ),
                OrOr,
                Comparison(
                    PredicateComparison {
                        field: Ident(
                            field2,
                        ),
                        operator: Equal(
                            EqEq,
                        ),
                        value: Lit::Str {
                            token: "field2",
                        },
                    },
                ),
            ],
        )
        "###);
    }
}