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}