dampen_core/codegen/
bindings.rs

1//! Binding expression inlining for code generation
2//!
3//! This module provides functions to generate pure Rust code from binding expressions,
4//! eliminating runtime interpretation overhead in production builds.
5
6use crate::CodegenError;
7use crate::expr::ast::{
8    BinaryOp, BinaryOpExpr, ConditionalExpr, Expr, FieldAccessExpr, LiteralExpr, MethodCallExpr,
9    UnaryOp, UnaryOpExpr,
10};
11use proc_macro2::TokenStream;
12use quote::{format_ident, quote};
13
14/// Generate Rust code for a binding expression
15///
16/// This function converts binding expressions from the AST into TokenStream
17/// for direct field access without runtime evaluation.
18///
19/// # Arguments
20/// * `expr` - The expression to generate code for
21///
22/// # Returns
23/// Generated code as a TokenStream that produces a String when interpolated
24pub fn generate_expr(expr: &Expr) -> TokenStream {
25    match expr {
26        Expr::FieldAccess(field_access) => generate_field_access(field_access),
27        Expr::MethodCall(method_call) => generate_method_call(method_call),
28        Expr::BinaryOp(binary_op) => generate_binary_op(binary_op),
29        Expr::UnaryOp(unary_op) => generate_unary_op(unary_op),
30        Expr::Conditional(conditional) => generate_conditional(conditional),
31        Expr::Literal(literal) => generate_literal(literal),
32    }
33}
34
35/// Validate that an expression can be inlined
36///
37/// Returns Err if the expression contains unsupported constructs for codegen
38pub fn validate_expression_inlinable(expr: &Expr) -> Result<(), CodegenError> {
39    match expr {
40        Expr::FieldAccess(_) => Ok(()),
41        Expr::MethodCall(method_expr) => {
42            validate_expression_inlinable(&method_expr.receiver)?;
43            for arg in &method_expr.args {
44                validate_expression_inlinable(arg)?;
45            }
46            Ok(())
47        }
48        Expr::BinaryOp(binary_expr) => {
49            validate_expression_inlinable(&binary_expr.left)?;
50            validate_expression_inlinable(&binary_expr.right)?;
51            Ok(())
52        }
53        Expr::UnaryOp(unary_expr) => {
54            validate_expression_inlinable(&unary_expr.operand)?;
55            Ok(())
56        }
57        Expr::Conditional(cond_expr) => {
58            validate_expression_inlinable(&cond_expr.condition)?;
59            validate_expression_inlinable(&cond_expr.then_branch)?;
60            validate_expression_inlinable(&cond_expr.else_branch)?;
61            Ok(())
62        }
63        Expr::Literal(_) => Ok(()),
64    }
65}
66
67/// Generate code for a field access expression
68///
69/// # Arguments
70/// * `expr` - Field access with path components
71///
72/// # Returns
73/// TokenStream generating `field.to_string()` (without self prefix)
74fn generate_field_access(expr: &FieldAccessExpr) -> TokenStream {
75    if expr.path.is_empty() {
76        return quote! { String::new() };
77    }
78
79    let field_access: Vec<_> = expr.path.iter().map(|s| format_ident!("{}", s)).collect();
80
81    quote! { #(#field_access).*.to_string() }
82}
83
84/// Generate code for a method call expression
85///
86/// # Arguments
87/// * `expr` - Method call with receiver and arguments
88///
89/// # Returns
90/// TokenStream generating `self.receiver.method(args).to_string()`
91fn generate_method_call(expr: &MethodCallExpr) -> TokenStream {
92    let receiver_tokens = generate_expr(&expr.receiver);
93    let method_ident = format_ident!("{}", expr.method);
94
95    if expr.args.is_empty() {
96        quote! { #receiver_tokens.#method_ident().to_string() }
97    } else {
98        let arg_tokens: Vec<TokenStream> = expr.args.iter().map(generate_expr).collect();
99        quote! { #receiver_tokens.#method_ident(#(#arg_tokens),*).to_string() }
100    }
101}
102
103/// Generate code for a binary operation expression
104///
105/// # Arguments
106/// * `expr` - Binary operation with left, op, right
107///
108/// # Returns
109/// TokenStream generating `(left op right).to_string()`
110fn generate_binary_op(expr: &BinaryOpExpr) -> TokenStream {
111    let left = generate_expr(&expr.left);
112    let right = generate_expr(&expr.right);
113    let op = match expr.op {
114        BinaryOp::Eq => quote! { == },
115        BinaryOp::Ne => quote! { != },
116        BinaryOp::Lt => quote! { < },
117        BinaryOp::Le => quote! { <= },
118        BinaryOp::Gt => quote! { > },
119        BinaryOp::Ge => quote! { >= },
120        BinaryOp::And => quote! { && },
121        BinaryOp::Or => quote! { || },
122        BinaryOp::Add => quote! { + },
123        BinaryOp::Sub => quote! { - },
124        BinaryOp::Mul => quote! { * },
125        BinaryOp::Div => quote! { / },
126    };
127
128    quote! { (#left #op #right).to_string() }
129}
130
131/// Generate code for a unary operation expression
132///
133/// # Arguments
134/// * `expr` - Unary operation with operator and operand
135///
136/// # Returns
137/// TokenStream generating `(!operand).to_string()` or `(-operand).to_string()`
138fn generate_unary_op(expr: &UnaryOpExpr) -> TokenStream {
139    let operand = generate_expr(&expr.operand);
140    let op = match expr.op {
141        UnaryOp::Not => quote! { ! },
142        UnaryOp::Neg => quote! { - },
143    };
144
145    quote! { (#op #operand).to_string() }
146}
147
148/// Generate code for a conditional expression
149///
150/// # Arguments
151/// * `expr` - Conditional with condition, then_branch, else_branch
152///
153/// # Returns
154/// TokenStream generating `if condition { then } else { else }.to_string()`
155fn generate_conditional(expr: &ConditionalExpr) -> TokenStream {
156    let condition = generate_expr(&expr.condition);
157    let then_branch = generate_expr(&expr.then_branch);
158    let else_branch = generate_expr(&expr.else_branch);
159
160    quote! {
161        {
162            let __cond = #condition;
163            let __then = #then_branch;
164            let __else = #else_branch;
165            if __cond.trim() == "true" || __cond.parse::<bool>().unwrap_or(false) {
166                __then
167            } else {
168                __else
169            }
170        }
171    }
172}
173
174/// Generate code for a literal expression
175///
176/// # Arguments
177/// * `expr` - Literal value
178///
179/// # Returns
180/// TokenStream generating the literal value as a string
181fn generate_literal(expr: &LiteralExpr) -> TokenStream {
182    match expr {
183        LiteralExpr::String(s) => {
184            let lit = proc_macro2::Literal::string(s);
185            quote! { #lit.to_string() }
186        }
187        LiteralExpr::Integer(n) => {
188            let lit = proc_macro2::Literal::i64_unsuffixed(*n);
189            quote! { #lit.to_string() }
190        }
191        LiteralExpr::Float(f) => {
192            let lit = proc_macro2::Literal::f64_unsuffixed(*f);
193            quote! { #lit.to_string() }
194        }
195        LiteralExpr::Bool(b) => {
196            let val = if *b { "true" } else { "false" };
197            let lit = proc_macro2::Literal::string(val);
198            quote! { #lit.to_string() }
199        }
200    }
201}
202
203/// Generate Rust code for interpolated strings
204///
205/// Converts interpolated strings like "Count: {count}" into format! macro calls.
206///
207/// # Arguments
208/// * `parts` - Alternating literal strings and binding expressions
209///
210/// # Returns
211/// TokenStream generating format! macro invocation
212///
213/// # Examples
214/// ```ignore
215/// // "Count: {count}"
216/// generate_interpolated(...) -> quote! { format!("Count: {}", count) }
217/// ```
218pub fn generate_interpolated(parts: &[String]) -> TokenStream {
219    if parts.is_empty() {
220        return quote! { String::new() };
221    }
222
223    let mut format_args = Vec::new();
224    let mut arg_exprs = Vec::new();
225
226    for part in parts {
227        if part.starts_with('{') && part.ends_with('}') {
228            let binding_name = &part[1..part.len() - 1];
229            let field_parts: Vec<_> = binding_name
230                .split('.')
231                .map(|s| format_ident!("{}", s))
232                .collect();
233            format_args.push("{}");
234            arg_exprs.push(quote! { #(#field_parts).*.to_string() });
235        } else {
236            format_args.push(part);
237        }
238    }
239
240    let format_string = format_args.join("");
241    let lit = proc_macro2::Literal::string(&format_string);
242
243    quote! { format!(#lit, #(#arg_exprs),*) }
244}