use crate::CodegenError;
use crate::expr::ast::{
BinaryOp, BinaryOpExpr, ConditionalExpr, Expr, FieldAccessExpr, LiteralExpr, MethodCallExpr,
SharedFieldAccessExpr, UnaryOp, UnaryOpExpr,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn generate_expr(expr: &Expr) -> TokenStream {
match expr {
Expr::FieldAccess(field_access) => generate_field_access(field_access),
Expr::SharedFieldAccess(shared_access) => generate_shared_field_access(shared_access),
Expr::MethodCall(method_call) => generate_method_call(method_call),
Expr::BinaryOp(binary_op) => generate_binary_op(binary_op),
Expr::UnaryOp(unary_op) => generate_unary_op(unary_op),
Expr::Conditional(conditional) => generate_conditional(conditional),
Expr::Literal(literal) => generate_literal(literal),
}
}
pub fn generate_bool_expr(expr: &Expr) -> TokenStream {
match expr {
Expr::FieldAccess(field_access) => generate_field_access_raw(field_access),
Expr::SharedFieldAccess(shared_access) => generate_shared_field_access_raw(shared_access),
Expr::MethodCall(method_call) => generate_method_call_raw(method_call),
Expr::BinaryOp(binary_op) => generate_binary_op_raw(binary_op),
Expr::UnaryOp(unary_op) => generate_unary_op_raw(unary_op),
Expr::Conditional(conditional) => generate_conditional_raw(conditional),
Expr::Literal(literal) => generate_literal_raw(literal),
}
}
pub fn validate_expression_inlinable(expr: &Expr) -> Result<(), CodegenError> {
match expr {
Expr::FieldAccess(_) => Ok(()),
Expr::SharedFieldAccess(_) => Ok(()), Expr::MethodCall(method_expr) => {
validate_expression_inlinable(&method_expr.receiver)?;
for arg in &method_expr.args {
validate_expression_inlinable(arg)?;
}
Ok(())
}
Expr::BinaryOp(binary_expr) => {
validate_expression_inlinable(&binary_expr.left)?;
validate_expression_inlinable(&binary_expr.right)?;
Ok(())
}
Expr::UnaryOp(unary_expr) => {
validate_expression_inlinable(&unary_expr.operand)?;
Ok(())
}
Expr::Conditional(cond_expr) => {
validate_expression_inlinable(&cond_expr.condition)?;
validate_expression_inlinable(&cond_expr.then_branch)?;
validate_expression_inlinable(&cond_expr.else_branch)?;
Ok(())
}
Expr::Literal(_) => Ok(()),
}
}
fn generate_field_access(expr: &FieldAccessExpr) -> TokenStream {
if expr.path.is_empty() {
return quote! { String::new() };
}
let field_access: Vec<_> = expr.path.iter().map(|s| format_ident!("{}", s)).collect();
quote! { model.#(#field_access).*.to_string() }
}
fn generate_shared_field_access(expr: &SharedFieldAccessExpr) -> TokenStream {
if expr.path.is_empty() {
return quote! { String::new() };
}
let field_access: Vec<_> = expr.path.iter().map(|s| format_ident!("{}", s)).collect();
quote! { shared.#(#field_access).*.to_string() }
}
fn generate_method_call(expr: &MethodCallExpr) -> TokenStream {
let receiver_tokens = generate_expr(&expr.receiver);
let method_ident = format_ident!("{}", expr.method);
if expr.args.is_empty() {
quote! { #receiver_tokens.#method_ident().to_string() }
} else {
let arg_tokens: Vec<TokenStream> = expr.args.iter().map(generate_expr).collect();
quote! { #receiver_tokens.#method_ident(#(#arg_tokens),*).to_string() }
}
}
fn generate_binary_op(expr: &BinaryOpExpr) -> TokenStream {
let left = generate_expr(&expr.left);
let right = generate_expr(&expr.right);
let op = match expr.op {
BinaryOp::Eq => quote! { == },
BinaryOp::Ne => quote! { != },
BinaryOp::Lt => quote! { < },
BinaryOp::Le => quote! { <= },
BinaryOp::Gt => quote! { > },
BinaryOp::Ge => quote! { >= },
BinaryOp::And => quote! { && },
BinaryOp::Or => quote! { || },
BinaryOp::Add => quote! { + },
BinaryOp::Sub => quote! { - },
BinaryOp::Mul => quote! { * },
BinaryOp::Div => quote! { / },
};
quote! { (#left #op #right).to_string() }
}
fn generate_unary_op(expr: &UnaryOpExpr) -> TokenStream {
let operand = generate_expr(&expr.operand);
let op = match expr.op {
UnaryOp::Not => quote! { ! },
UnaryOp::Neg => quote! { - },
};
quote! { (#op #operand).to_string() }
}
fn generate_conditional(expr: &ConditionalExpr) -> TokenStream {
let condition = generate_expr(&expr.condition);
let then_branch = generate_expr(&expr.then_branch);
let else_branch = generate_expr(&expr.else_branch);
quote! {
{
let __cond = #condition;
let __then = #then_branch;
let __else = #else_branch;
if __cond.trim() == "true" || __cond.parse::<bool>().unwrap_or(false) {
__then
} else {
__else
}
}
}
}
fn generate_literal(expr: &LiteralExpr) -> TokenStream {
match expr {
LiteralExpr::String(s) => {
let lit = proc_macro2::Literal::string(s);
quote! { #lit.to_string() }
}
LiteralExpr::Integer(n) => {
let lit = proc_macro2::Literal::i64_unsuffixed(*n);
quote! { #lit.to_string() }
}
LiteralExpr::Float(f) => {
let lit = proc_macro2::Literal::f64_unsuffixed(*f);
quote! { #lit.to_string() }
}
LiteralExpr::Bool(b) => {
let val = if *b { "true" } else { "false" };
let lit = proc_macro2::Literal::string(val);
quote! { #lit.to_string() }
}
}
}
pub fn generate_interpolated(parts: &[String]) -> TokenStream {
if parts.is_empty() {
return quote! { String::new() };
}
let mut format_args = Vec::new();
let mut arg_exprs = Vec::new();
for part in parts {
if part.starts_with('{') && part.ends_with('}') {
let binding_name = &part[1..part.len() - 1];
let field_parts: Vec<_> = binding_name
.split('.')
.map(|s| format_ident!("{}", s))
.collect();
format_args.push("{}");
arg_exprs.push(quote! { #(#field_parts).*.to_string() });
} else {
format_args.push(part);
}
}
let format_string = format_args.join("");
let lit = proc_macro2::Literal::string(&format_string);
quote! { format!(#lit, #(#arg_exprs),*) }
}
fn generate_field_access_raw(expr: &FieldAccessExpr) -> TokenStream {
generate_field_access_raw_with_locals(expr, &std::collections::HashSet::new())
}
fn generate_field_access_raw_with_locals(
expr: &FieldAccessExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
if expr.path.is_empty() {
return quote! { false };
}
if let Some(first) = expr.path.first()
&& local_vars.contains(first)
{
let field_access: Vec<_> = expr.path.iter().map(|s| format_ident!("{}", s)).collect();
return quote! { #(#field_access).* };
}
let field_access: Vec<_> = expr.path.iter().map(|s| format_ident!("{}", s)).collect();
quote! { model.#(#field_access).* }
}
pub fn generate_bool_expr_with_locals(
expr: &Expr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
match expr {
Expr::FieldAccess(field_access) => {
generate_field_access_raw_with_locals(field_access, local_vars)
}
Expr::SharedFieldAccess(shared_access) => generate_shared_field_access_raw(shared_access),
Expr::MethodCall(method_call) => {
generate_method_call_raw_with_locals(method_call, local_vars)
}
Expr::BinaryOp(binary_op) => generate_binary_op_raw_with_locals(binary_op, local_vars),
Expr::UnaryOp(unary_op) => generate_unary_op_raw_with_locals(unary_op, local_vars),
Expr::Conditional(conditional) => {
generate_conditional_raw_with_locals(conditional, local_vars)
}
Expr::Literal(literal) => generate_literal_raw(literal),
}
}
pub fn generate_expr_with_locals(
expr: &Expr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
match expr {
Expr::FieldAccess(field_access) => {
generate_field_access_with_locals(field_access, local_vars)
}
Expr::SharedFieldAccess(shared_access) => generate_shared_field_access(shared_access),
Expr::MethodCall(method_call) => generate_method_call_with_locals(method_call, local_vars),
Expr::BinaryOp(binary_op) => generate_binary_op_with_locals(binary_op, local_vars),
Expr::UnaryOp(unary_op) => generate_unary_op_with_locals(unary_op, local_vars),
Expr::Conditional(conditional) => generate_conditional_with_locals(conditional, local_vars),
Expr::Literal(literal) => generate_literal(literal),
}
}
fn generate_field_access_with_locals(
expr: &FieldAccessExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
if expr.path.is_empty() {
return quote! { String::new() };
}
if let Some(first) = expr.path.first()
&& local_vars.contains(first)
{
let field_access: Vec<_> = expr.path.iter().map(|s| format_ident!("{}", s)).collect();
return quote! { #(#field_access).*.to_string() };
}
let field_access: Vec<_> = expr.path.iter().map(|s| format_ident!("{}", s)).collect();
quote! { model.#(#field_access).*.to_string() }
}
fn generate_method_call_with_locals(
expr: &MethodCallExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
let receiver_tokens = generate_expr_with_locals(&expr.receiver, local_vars);
let method_ident = format_ident!("{}", expr.method);
let arg_tokens: Vec<_> = expr
.args
.iter()
.map(|a| generate_expr_with_locals(a, local_vars))
.collect();
if arg_tokens.is_empty() {
quote! { #receiver_tokens.#method_ident().to_string() }
} else {
quote! { #receiver_tokens.#method_ident(#(#arg_tokens),*).to_string() }
}
}
fn generate_binary_op_with_locals(
expr: &BinaryOpExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
let left = generate_expr_with_locals(&expr.left, local_vars);
let right = generate_expr_with_locals(&expr.right, local_vars);
let op = match expr.op {
BinaryOp::Eq => quote! { == },
BinaryOp::Ne => quote! { != },
BinaryOp::Lt => quote! { < },
BinaryOp::Le => quote! { <= },
BinaryOp::Gt => quote! { > },
BinaryOp::Ge => quote! { >= },
BinaryOp::And => quote! { && },
BinaryOp::Or => quote! { || },
BinaryOp::Add => quote! { + },
BinaryOp::Sub => quote! { - },
BinaryOp::Mul => quote! { * },
BinaryOp::Div => quote! { / },
};
quote! { (#left #op #right).to_string() }
}
fn generate_unary_op_with_locals(
expr: &UnaryOpExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
let operand = generate_expr_with_locals(&expr.operand, local_vars);
let op = match expr.op {
UnaryOp::Not => quote! { ! },
UnaryOp::Neg => quote! { - },
};
quote! { (#op #operand).to_string() }
}
fn generate_conditional_with_locals(
expr: &ConditionalExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
let condition = generate_expr_with_locals(&expr.condition, local_vars);
let then_branch = generate_expr_with_locals(&expr.then_branch, local_vars);
let else_branch = generate_expr_with_locals(&expr.else_branch, local_vars);
quote! {
{
let __cond = #condition;
let __then = #then_branch;
let __else = #else_branch;
if __cond.trim() == "true" || __cond.parse::<bool>().unwrap_or(false) {
__then
} else {
__else
}
}
}
}
fn generate_method_call_raw_with_locals(
expr: &MethodCallExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
let receiver_tokens = generate_bool_expr_with_locals(&expr.receiver, local_vars);
let method_ident = format_ident!("{}", &expr.method);
let arg_tokens: Vec<_> = expr
.args
.iter()
.map(|a| generate_bool_expr_with_locals(a, local_vars))
.collect();
quote! { #receiver_tokens.#method_ident(#(#arg_tokens),*) }
}
fn generate_binary_op_raw_with_locals(
expr: &BinaryOpExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
let left = generate_bool_expr_with_locals(&expr.left, local_vars);
let right = generate_bool_expr_with_locals(&expr.right, local_vars);
let op = match expr.op {
BinaryOp::Eq => quote! { == },
BinaryOp::Ne => quote! { != },
BinaryOp::Lt => quote! { < },
BinaryOp::Le => quote! { <= },
BinaryOp::Gt => quote! { > },
BinaryOp::Ge => quote! { >= },
BinaryOp::And => quote! { && },
BinaryOp::Or => quote! { || },
BinaryOp::Add => quote! { + },
BinaryOp::Sub => quote! { - },
BinaryOp::Mul => quote! { * },
BinaryOp::Div => quote! { / },
};
quote! { #left #op #right }
}
fn generate_unary_op_raw_with_locals(
expr: &UnaryOpExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
let operand = generate_bool_expr_with_locals(&expr.operand, local_vars);
let op = match expr.op {
UnaryOp::Not => quote! { ! },
UnaryOp::Neg => quote! { - },
};
quote! { #op #operand }
}
fn generate_conditional_raw_with_locals(
expr: &ConditionalExpr,
local_vars: &std::collections::HashSet<String>,
) -> TokenStream {
let condition = generate_bool_expr_with_locals(&expr.condition, local_vars);
let then_branch = generate_bool_expr_with_locals(&expr.then_branch, local_vars);
let else_branch = generate_bool_expr_with_locals(&expr.else_branch, local_vars);
quote! {
if #condition {
#then_branch
} else {
#else_branch
}
}
}
fn generate_shared_field_access_raw(expr: &SharedFieldAccessExpr) -> TokenStream {
if expr.path.is_empty() {
return quote! { false };
}
let field_access: Vec<_> = expr.path.iter().map(|s| format_ident!("{}", s)).collect();
quote! { shared.#(#field_access).* }
}
fn generate_method_call_raw(expr: &MethodCallExpr) -> TokenStream {
let receiver_tokens = generate_bool_expr(&expr.receiver);
let method_ident = format_ident!("{}", &expr.method);
let arg_tokens: Vec<_> = expr.args.iter().map(generate_bool_expr).collect();
quote! { #receiver_tokens.#method_ident(#(#arg_tokens),*) }
}
fn generate_binary_op_raw(expr: &BinaryOpExpr) -> TokenStream {
let left = generate_bool_expr(&expr.left);
let right = generate_bool_expr(&expr.right);
let op = match expr.op {
BinaryOp::Eq => quote! { == },
BinaryOp::Ne => quote! { != },
BinaryOp::Lt => quote! { < },
BinaryOp::Le => quote! { <= },
BinaryOp::Gt => quote! { > },
BinaryOp::Ge => quote! { >= },
BinaryOp::And => quote! { && },
BinaryOp::Or => quote! { || },
BinaryOp::Add => quote! { + },
BinaryOp::Sub => quote! { - },
BinaryOp::Mul => quote! { * },
BinaryOp::Div => quote! { / },
};
quote! { #left #op #right }
}
fn generate_unary_op_raw(expr: &UnaryOpExpr) -> TokenStream {
let operand = generate_bool_expr(&expr.operand);
let op = match expr.op {
UnaryOp::Not => quote! { ! },
UnaryOp::Neg => quote! { - },
};
quote! { #op #operand }
}
fn generate_conditional_raw(expr: &ConditionalExpr) -> TokenStream {
let condition = generate_bool_expr(&expr.condition);
let then_branch = generate_bool_expr(&expr.then_branch);
let else_branch = generate_bool_expr(&expr.else_branch);
quote! {
if #condition {
#then_branch
} else {
#else_branch
}
}
}
fn generate_literal_raw(expr: &LiteralExpr) -> TokenStream {
match expr {
LiteralExpr::String(s) => {
let lit = proc_macro2::Literal::string(s);
quote! { #lit }
}
LiteralExpr::Integer(n) => {
let lit = proc_macro2::Literal::i64_unsuffixed(*n);
quote! { #lit }
}
LiteralExpr::Float(f) => {
let lit = proc_macro2::Literal::f64_unsuffixed(*f);
quote! { #lit }
}
LiteralExpr::Bool(b) => {
quote! { #b }
}
}
}