espresso_logic_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Ident, Token};
4use syn::parse::{Parse, ParseStream, Result};
5
6/// AST for boolean expressions
7enum Expr {
8    Variable(Ident),
9    StringLiteral(syn::LitStr),
10    Constant(bool),
11    Not(Box<Expr>),
12    And(Box<Expr>, Box<Expr>),
13    Or(Box<Expr>, Box<Expr>),
14}
15
16impl Expr {
17    /// Generate code for this expression using references (no cloning in the macro)
18    /// 
19    /// The macro generates references and lets the monadic interface methods
20    /// (and, or, not) handle any necessary cloning internally. This follows
21    /// good Rust design - the macro doesn't assume ownership semantics.
22    fn to_tokens(&self) -> proc_macro2::TokenStream {
23        match self {
24            Expr::Variable(ident) => {
25                // Just use the variable by reference
26                // The monadic methods already take &self and clone internally
27                quote! {
28                    #ident
29                }
30            }
31            Expr::StringLiteral(lit) => {
32                // Create a variable from the string literal
33                quote! {
34                    BoolExpr::variable(#lit)
35                }
36            }
37            Expr::Constant(value) => {
38                // Create a constant from the boolean value
39                quote! {
40                    BoolExpr::constant(#value)
41                }
42            }
43            Expr::Not(inner) => {
44                let inner_tokens = inner.to_tokens();
45                quote! {
46                    (&(#inner_tokens)).not()
47                }
48            }
49            Expr::And(left, right) => {
50                let left_tokens = left.to_tokens();
51                let right_tokens = right.to_tokens();
52                quote! {
53                    (&(#left_tokens)).and(&(#right_tokens))
54                }
55            }
56            Expr::Or(left, right) => {
57                let left_tokens = left.to_tokens();
58                let right_tokens = right.to_tokens();
59                quote! {
60                    (&(#left_tokens)).or(&(#right_tokens))
61                }
62            }
63        }
64    }
65}
66
67/// Parser for boolean expressions with operator precedence
68struct BoolExprParser {
69    expr: Expr,
70}
71
72impl Parse for BoolExprParser {
73    fn parse(input: ParseStream) -> Result<Self> {
74        let expr = parse_or(input)?;
75        Ok(BoolExprParser { expr })
76    }
77}
78
79/// Parse OR expressions (lowest precedence)
80fn parse_or(input: ParseStream) -> Result<Expr> {
81    let mut left = parse_and(input)?;
82
83    while input.peek(Token![+]) || input.peek(Token![|]) {
84        if input.peek(Token![+]) {
85            input.parse::<Token![+]>()?;
86        } else {
87            input.parse::<Token![|]>()?;
88        }
89        let right = parse_and(input)?;
90        left = Expr::Or(Box::new(left), Box::new(right));
91    }
92
93    Ok(left)
94}
95
96/// Parse AND expressions (higher precedence)
97fn parse_and(input: ParseStream) -> Result<Expr> {
98    let mut left = parse_unary(input)?;
99
100    while input.peek(Token![*]) || input.peek(Token![&]) {
101        if input.peek(Token![*]) {
102            input.parse::<Token![*]>()?;
103        } else {
104            input.parse::<Token![&]>()?;
105        }
106        let right = parse_unary(input)?;
107        left = Expr::And(Box::new(left), Box::new(right));
108    }
109
110    Ok(left)
111}
112
113/// Parse unary expressions (NOT) and atoms (highest precedence)
114fn parse_unary(input: ParseStream) -> Result<Expr> {
115    if input.peek(Token![!]) {
116        input.parse::<Token![!]>()?;
117        let inner = parse_unary(input)?;
118        Ok(Expr::Not(Box::new(inner)))
119    } else if input.peek(Token![~]) {
120        input.parse::<Token![~]>()?;
121        let inner = parse_unary(input)?;
122        Ok(Expr::Not(Box::new(inner)))
123    } else {
124        parse_atom(input)
125    }
126}
127
128/// Parse atomic expressions (variables, string literals, numeric literals, and parenthesized expressions)
129fn parse_atom(input: ParseStream) -> Result<Expr> {
130    if input.peek(syn::token::Paren) {
131        let content;
132        syn::parenthesized!(content in input);
133        parse_or(&content)
134    } else if input.peek(syn::LitStr) {
135        let lit: syn::LitStr = input.parse()?;
136        Ok(Expr::StringLiteral(lit))
137    } else if input.peek(syn::LitInt) {
138        let lit: syn::LitInt = input.parse()?;
139        let value: u8 = lit.base10_parse()?;
140        match value {
141            0 => Ok(Expr::Constant(false)),
142            1 => Ok(Expr::Constant(true)),
143            _ => Err(syn::Error::new(
144                lit.span(),
145                "only 0 and 1 are supported as boolean constants"
146            )),
147        }
148    } else {
149        let ident: Ident = input.parse()?;
150        Ok(Expr::Variable(ident))
151    }
152}
153
154/// The `expr!` procedural macro for boolean expressions
155///
156/// Provides clean syntax for building boolean expressions from existing `BoolExpr` values
157/// with proper operator precedence.
158///
159/// # Supported Syntax
160///
161/// - `a` - Variable or any `BoolExpr` identifier in scope
162/// - `"a"` - String literal (creates `BoolExpr::variable("a")` automatically)
163/// - `0` - False constant (creates `BoolExpr::constant(false)`)
164/// - `1` - True constant (creates `BoolExpr::constant(true)`)
165/// - `!a` or `~a` - NOT operation (both syntaxes supported, like the parser)
166/// - `a * b` or `a & b` - AND operation (both `*` and `&` supported)
167/// - `a + b` or `a | b` - OR operation (both `+` and `|` supported)
168/// - `(a + b) * c` - Parentheses for grouping
169///
170/// # Operator Precedence
171///
172/// From highest to lowest:
173/// 1. `( )` (Parentheses - force evaluation order)
174/// 2. `!` / `~` (NOT)
175/// 3. `*` / `&` (AND)
176/// 4. `+` / `|` (OR)
177///
178/// # Examples
179///
180/// ```ignore
181/// use espresso_logic::{BoolExpr, expr};
182///
183/// // Option 1: Use string literals (variables created automatically)
184/// let xor = expr!("a" * !"b" + !"a" * "b");
185/// let complex = expr!(("a" + "b") * "c");
186///
187/// // Option 2: Use existing BoolExpr variables
188/// let a = BoolExpr::variable("a");
189/// let b = BoolExpr::variable("b");
190/// let c = BoolExpr::variable("c");
191/// let and_expr = expr!(a * b);
192/// let or_expr = expr!(a + b);
193/// let not_expr = expr!(!a);
194///
195/// // Option 3: Use constants
196/// let with_const = expr!("a" * 1 + "b" * 0);  // a AND true OR b AND false
197/// let always_true = expr!(1);
198/// let always_false = expr!(0);
199///
200/// // Option 4: Mix all styles
201/// let expr1 = expr!(a * b);
202/// let combined = expr!(expr1 + "c" * 1);
203///
204/// // Complex nested expressions
205/// let xor = expr!(a * !b + !a * b);
206/// let complex = expr!((a + b) * c);
207///
208/// // Can compose sub-expressions
209/// let sub_expr1 = expr!(a * b);
210/// let sub_expr2 = expr!(c + !a);
211/// let combined = expr!(sub_expr1 + sub_expr2);
212/// ```
213#[proc_macro]
214pub fn expr(input: TokenStream) -> TokenStream {
215    let parser = parse_macro_input!(input as BoolExprParser);
216    let tokens = parser.expr.to_tokens();
217    TokenStream::from(tokens)
218}