use super::super::Transpiler;
use crate::frontend::ast::{BinaryOp, Expr, ExprKind};
use anyhow::Result;
use proc_macro2::TokenStream;
use quote::quote;
impl Transpiler {
pub fn transpile_binary(&self, left: &Expr, op: BinaryOp, right: &Expr) -> Result<TokenStream> {
if op == BinaryOp::Add
&& (self.is_definitely_string(left) || self.is_definitely_string(right))
{
return self.transpile_string_concatenation(left, right);
}
if op == BinaryOp::Add && Self::is_vec_array_concat(left, right) {
return self.transpile_vec_concatenation(left, right);
}
if Self::is_comparison_op(op) {
let left_is_len = Self::is_len_call(left);
let right_is_len = Self::is_len_call(right);
if left_is_len && !right_is_len {
let left_tokens = self.transpile_expr_with_precedence(left, op, true)?;
let right_tokens = self.transpile_expr_with_precedence(right, op, false)?;
let casted_right = quote! { (#right_tokens as usize) };
return Ok(Self::transpile_binary_op(left_tokens, op, casted_right));
} else if right_is_len && !left_is_len {
let left_tokens = self.transpile_expr_with_precedence(left, op, true)?;
let right_tokens = self.transpile_expr_with_precedence(right, op, false)?;
let casted_left = quote! { (#left_tokens as usize) };
return Ok(Self::transpile_binary_op(casted_left, op, right_tokens));
}
}
if Self::is_arithmetic_op(op) {
let left_is_int = Self::is_integer_literal(left);
let left_is_float = Self::is_float_literal(left);
let right_is_int = Self::is_integer_literal(right);
let right_is_float = Self::is_float_literal(right);
if (left_is_int && right_is_float) || (left_is_float && right_is_int) {
let left_tokens = self.transpile_expr(left)?;
let right_tokens = self.transpile_expr(right)?;
let casted_left = quote! { (#left_tokens as f64) };
let casted_right = quote! { (#right_tokens as f64) };
return Ok(Self::transpile_binary_op(casted_left, op, casted_right));
}
}
let left_tokens = self.transpile_expr_with_precedence(left, op, true)?;
let right_tokens = self.transpile_expr_with_precedence(right, op, false)?;
Ok(Self::transpile_binary_op(left_tokens, op, right_tokens))
}
fn transpile_expr_with_precedence(
&self,
expr: &Expr,
parent_op: BinaryOp,
is_left_operand: bool,
) -> Result<TokenStream> {
let tokens = self.transpile_expr(expr)?;
if let ExprKind::Binary { op: child_op, .. } = &expr.kind {
let parent_prec = Self::get_operator_precedence(parent_op);
let child_prec = Self::get_operator_precedence(*child_op);
let needs_parens = child_prec < parent_prec
|| (!is_left_operand
&& child_prec == parent_prec
&& Self::is_right_associative(parent_op));
if needs_parens {
return Ok(quote! { (#tokens) });
}
}
Ok(tokens)
}
fn get_operator_precedence(op: BinaryOp) -> i32 {
match op {
BinaryOp::Or => 10,
BinaryOp::And => 20,
BinaryOp::Equal | BinaryOp::NotEqual => 30,
BinaryOp::Less
| BinaryOp::LessEqual
| BinaryOp::Greater
| BinaryOp::GreaterEqual
| BinaryOp::In => 40,
BinaryOp::Add | BinaryOp::Subtract => 50,
BinaryOp::Multiply | BinaryOp::Divide | BinaryOp::Modulo => 60,
BinaryOp::Power => 70,
BinaryOp::Send => 15, _ => 0, }
}
fn is_right_associative(op: BinaryOp) -> bool {
matches!(op, BinaryOp::Power) }
fn transpile_binary_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
use BinaryOp::{
Add, And, BitwiseAnd, BitwiseOr, BitwiseXor, Divide, Equal, Greater, GreaterEqual, In,
LeftShift, Less, LessEqual, Modulo, Multiply, NotEqual, NullCoalesce, Or, Power,
RightShift, Send, Subtract,
};
match op {
Add | Subtract | Multiply | Divide | Modulo | Power => {
Self::transpile_arithmetic_op(left, op, right)
}
Equal | NotEqual | Less | LessEqual | Greater | GreaterEqual | BinaryOp::Gt => {
Self::transpile_comparison_op(left, op, right)
}
In => quote! { #right.contains(&#left) },
And | Or | NullCoalesce => Self::transpile_logical_op(left, op, right),
BitwiseAnd | BitwiseOr | BitwiseXor => Self::transpile_bitwise_op(left, op, right),
LeftShift => Self::transpile_shift_ops(left, op, right),
RightShift => Self::transpile_shift_ops(left, op, right),
Send => quote! { #left.send(#right) },
}
}
fn transpile_arithmetic_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
use BinaryOp::{Add, Divide, Modulo, Multiply, Power, Subtract};
match op {
Add | Subtract | Multiply | Divide | Modulo => {
Self::transpile_basic_arithmetic(left, op, right)
}
Power => quote! { #left.powf(#right) },
_ => unreachable!(),
}
}
fn transpile_basic_arithmetic(
left: TokenStream,
op: BinaryOp,
right: TokenStream,
) -> TokenStream {
match op {
BinaryOp::Add => quote! { #left + #right },
BinaryOp::Subtract => quote! { #left - #right },
BinaryOp::Multiply => quote! { #left * #right },
_ => Self::transpile_division_mod(left, op, right),
}
}
fn transpile_division_mod(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
match op {
BinaryOp::Divide => quote! { #left / #right },
BinaryOp::Modulo => quote! { #left % #right },
_ => unreachable!(),
}
}
fn transpile_comparison_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
use BinaryOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual};
match op {
Equal | NotEqual => Self::transpile_equality(left, op, right),
Less | LessEqual | Greater | GreaterEqual | BinaryOp::Gt => {
Self::transpile_ordering(left, op, right)
}
_ => unreachable!(),
}
}
fn transpile_equality(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
match op {
BinaryOp::Equal => quote! { #left == #right },
BinaryOp::NotEqual => quote! { #left != #right },
_ => unreachable!(),
}
}
fn transpile_ordering(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
match op {
BinaryOp::Less => quote! { #left < #right },
BinaryOp::LessEqual => quote! { #left <= #right },
_ => Self::transpile_greater_ops(left, op, right),
}
}
fn transpile_greater_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
match op {
BinaryOp::Greater => quote! { #left > #right },
BinaryOp::GreaterEqual => quote! { #left >= #right },
BinaryOp::Gt => quote! { #left > #right }, _ => unreachable!(),
}
}
fn transpile_logical_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
match op {
BinaryOp::And => quote! { #left && #right },
BinaryOp::Or => quote! { #left || #right },
BinaryOp::NullCoalesce => quote! { #left.unwrap_or(#right) },
_ => unreachable!(),
}
}
fn transpile_bitwise_op(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
use BinaryOp::{BitwiseAnd, BitwiseOr, BitwiseXor};
match op {
BitwiseAnd => quote! { #left & #right },
BitwiseOr => quote! { #left | #right },
BitwiseXor => quote! { #left ^ #right },
_ => Self::transpile_shift_ops(left, op, right),
}
}
fn transpile_shift_ops(left: TokenStream, op: BinaryOp, right: TokenStream) -> TokenStream {
match op {
BinaryOp::LeftShift => quote! { #left << #right },
BinaryOp::RightShift => quote! { #left >> #right },
_ => unreachable!("Invalid shift operation: {:?}", op),
}
}
fn is_len_call(expr: &Expr) -> bool {
match &expr.kind {
ExprKind::MethodCall { method, .. } if method == "len" => true,
ExprKind::Call { func, args } if args.len() == 1 => {
matches!(&func.kind, ExprKind::Identifier(name) if name == "len")
}
_ => false,
}
}
fn is_comparison_op(op: BinaryOp) -> bool {
matches!(
op,
BinaryOp::Less
| BinaryOp::LessEqual
| BinaryOp::Greater
| BinaryOp::GreaterEqual
| BinaryOp::Equal
| BinaryOp::NotEqual
)
}
fn is_arithmetic_op(op: BinaryOp) -> bool {
matches!(
op,
BinaryOp::Add
| BinaryOp::Subtract
| BinaryOp::Multiply
| BinaryOp::Divide
| BinaryOp::Modulo
| BinaryOp::Power
)
}
fn is_integer_literal(expr: &Expr) -> bool {
use crate::frontend::ast::Literal;
matches!(&expr.kind, ExprKind::Literal(Literal::Integer(_, _)))
}
fn is_float_literal(expr: &Expr) -> bool {
use crate::frontend::ast::Literal;
matches!(&expr.kind, ExprKind::Literal(Literal::Float(_)))
}
fn is_vec_array_concat(_left: &Expr, right: &Expr) -> bool {
let right_is_array = matches!(&right.kind, ExprKind::List(_));
right_is_array
}
fn transpile_vec_concatenation(&self, left: &Expr, right: &Expr) -> Result<TokenStream> {
let left_tokens = self.transpile_expr(left)?;
let right_tokens = self.transpile_expr(right)?;
Ok(quote! { [#left_tokens.as_slice(), &#right_tokens].concat() })
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn test_get_operator_precedence_or() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Or), 10);
}
#[test]
fn test_get_operator_precedence_and() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::And), 20);
}
#[test]
fn test_get_operator_precedence_comparison() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Equal), 30);
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Less), 40);
}
#[test]
fn test_get_operator_precedence_add_subtract() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Add), 50);
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Subtract), 50);
}
#[test]
fn test_get_operator_precedence_multiply_divide() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Multiply), 60);
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Divide), 60);
}
#[test]
fn test_get_operator_precedence_power() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Power), 70);
}
#[test]
fn test_is_right_associative_power() {
assert!(Transpiler::is_right_associative(BinaryOp::Power));
}
#[test]
fn test_is_right_associative_add() {
assert!(!Transpiler::is_right_associative(BinaryOp::Add));
}
#[test]
fn test_transpile_binary_op_add() {
let left = quote! { a };
let right = quote! { b };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Add, right);
assert_eq!(result.to_string(), "a + b");
}
#[test]
fn test_transpile_binary_op_subtract() {
let left = quote! { x };
let right = quote! { y };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Subtract, right);
assert_eq!(result.to_string(), "x - y");
}
#[test]
fn test_transpile_binary_op_multiply() {
let left = quote! { m };
let right = quote! { n };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Multiply, right);
assert_eq!(result.to_string(), "m * n");
}
#[test]
fn test_transpile_binary_op_divide() {
let left = quote! { p };
let right = quote! { q };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Divide, right);
assert_eq!(result.to_string(), "p / q");
}
#[test]
fn test_transpile_binary_op_modulo() {
let left = quote! { r };
let right = quote! { s };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Modulo, right);
assert_eq!(result.to_string(), "r % s");
}
#[test]
fn test_transpile_binary_op_power() {
let left = quote! { base };
let right = quote! { exp };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Power, right);
assert_eq!(result.to_string(), "base . powf (exp)");
}
#[test]
fn test_transpile_binary_op_equal() {
let left = quote! { a };
let right = quote! { b };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Equal, right);
assert_eq!(result.to_string(), "a == b");
}
#[test]
fn test_transpile_binary_op_not_equal() {
let left = quote! { x };
let right = quote! { y };
let result = Transpiler::transpile_binary_op(left, BinaryOp::NotEqual, right);
assert_eq!(result.to_string(), "x != y");
}
#[test]
fn test_transpile_binary_op_less() {
let left = quote! { a };
let right = quote! { b };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Less, right);
assert_eq!(result.to_string(), "a < b");
}
#[test]
fn test_transpile_binary_op_greater() {
let left = quote! { x };
let right = quote! { y };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Greater, right);
assert_eq!(result.to_string(), "x > y");
}
#[test]
fn test_transpile_binary_op_and() {
let left = quote! { cond1 };
let right = quote! { cond2 };
let result = Transpiler::transpile_binary_op(left, BinaryOp::And, right);
assert_eq!(result.to_string(), "cond1 && cond2");
}
#[test]
fn test_transpile_binary_op_or() {
let left = quote! { a };
let right = quote! { b };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Or, right);
assert_eq!(result.to_string(), "a || b");
}
#[test]
fn test_transpile_binary_op_bitwise_and() {
let left = quote! { flags1 };
let right = quote! { flags2 };
let result = Transpiler::transpile_binary_op(left, BinaryOp::BitwiseAnd, right);
assert_eq!(result.to_string(), "flags1 & flags2");
}
#[test]
fn test_transpile_binary_op_bitwise_or() {
let left = quote! { mask1 };
let right = quote! { mask2 };
let result = Transpiler::transpile_binary_op(left, BinaryOp::BitwiseOr, right);
assert_eq!(result.to_string(), "mask1 | mask2");
}
#[test]
fn test_transpile_binary_op_left_shift() {
let left = quote! { value };
let right = quote! { bits };
let result = Transpiler::transpile_binary_op(left, BinaryOp::LeftShift, right);
assert_eq!(result.to_string(), "value << bits");
}
#[test]
fn test_transpile_binary_op_right_shift() {
let left = quote! { num };
let right = quote! { shift };
let result = Transpiler::transpile_binary_op(left, BinaryOp::RightShift, right);
assert_eq!(result.to_string(), "num >> shift");
}
#[test]
fn test_is_comparison_op_equal() {
assert!(Transpiler::is_comparison_op(BinaryOp::Equal));
}
#[test]
fn test_is_comparison_op_less() {
assert!(Transpiler::is_comparison_op(BinaryOp::Less));
}
#[test]
fn test_is_comparison_op_add() {
assert!(!Transpiler::is_comparison_op(BinaryOp::Add));
}
#[test]
fn test_transpile_binary_op_null_coalesce() {
let left = quote! { opt };
let right = quote! { default };
let result = Transpiler::transpile_binary_op(left, BinaryOp::NullCoalesce, right);
assert_eq!(result.to_string(), "opt . unwrap_or (default)");
}
#[test]
fn test_transpile_binary_op_send() {
let left = quote! { actor };
let right = quote! { message };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Send, right);
assert_eq!(result.to_string(), "actor . send (message)");
}
#[test]
fn test_transpile_binary_op_greater_equal() {
let left = quote! { a };
let right = quote! { b };
let result = Transpiler::transpile_binary_op(left, BinaryOp::GreaterEqual, right);
assert_eq!(result.to_string(), "a >= b");
}
#[test]
fn test_is_len_call_method() {
use crate::frontend::ast::{Expr, ExprKind, Span};
let receiver = Expr {
kind: ExprKind::Identifier("vec".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let expr = Expr {
kind: ExprKind::MethodCall {
receiver: Box::new(receiver),
method: "len".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_len_call(&expr));
}
#[test]
fn test_is_len_call_function() {
use crate::frontend::ast::{Expr, ExprKind, Span};
let func = Expr {
kind: ExprKind::Identifier("len".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let arg = Expr {
kind: ExprKind::Identifier("vec".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let expr = Expr {
kind: ExprKind::Call {
func: Box::new(func),
args: vec![arg],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_len_call(&expr));
}
#[test]
fn test_is_len_call_not_len() {
use crate::frontend::ast::{Expr, ExprKind, Span};
let receiver = Expr {
kind: ExprKind::Identifier("vec".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let expr = Expr {
kind: ExprKind::MethodCall {
receiver: Box::new(receiver),
method: "push".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(!Transpiler::is_len_call(&expr));
}
#[test]
fn test_is_comparison_op_greater_equal() {
assert!(Transpiler::is_comparison_op(BinaryOp::GreaterEqual));
}
#[test]
fn test_is_comparison_op_less_equal() {
assert!(Transpiler::is_comparison_op(BinaryOp::LessEqual));
}
#[test]
fn test_is_comparison_op_not_equal() {
assert!(Transpiler::is_comparison_op(BinaryOp::NotEqual));
}
#[test]
fn test_is_comparison_op_greater() {
assert!(Transpiler::is_comparison_op(BinaryOp::Greater));
}
#[test]
fn test_is_comparison_op_multiply() {
assert!(!Transpiler::is_comparison_op(BinaryOp::Multiply));
}
#[test]
fn test_is_vec_array_concat_with_list() {
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
let left = Expr {
kind: ExprKind::Identifier("vec".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let right = Expr {
kind: ExprKind::List(vec![Expr {
kind: ExprKind::Literal(Literal::Integer(1, None)),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}]),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_vec_array_concat(&left, &right));
}
#[test]
fn test_is_vec_array_concat_not_list() {
use crate::frontend::ast::{Expr, ExprKind, Span};
let left = Expr {
kind: ExprKind::Identifier("vec".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let right = Expr {
kind: ExprKind::Identifier("other".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(!Transpiler::is_vec_array_concat(&left, &right));
}
#[test]
fn test_get_operator_precedence_modulo() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Modulo), 60);
}
#[test]
fn test_get_operator_precedence_send() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::Send), 15);
}
#[test]
fn test_get_operator_precedence_in() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::In), 40);
}
#[test]
fn test_get_operator_precedence_not_equal() {
assert_eq!(Transpiler::get_operator_precedence(BinaryOp::NotEqual), 30);
}
#[test]
fn test_transpile_binary_op_in() {
let left = quote! { item };
let right = quote! { collection };
let result = Transpiler::transpile_binary_op(left, BinaryOp::In, right);
assert_eq!(result.to_string(), "collection . contains (& item)");
}
#[test]
fn test_transpile_binary_op_bitwise_xor() {
let left = quote! { a };
let right = quote! { b };
let result = Transpiler::transpile_binary_op(left, BinaryOp::BitwiseXor, right);
assert_eq!(result.to_string(), "a ^ b");
}
#[test]
fn test_transpile_binary_op_less_equal() {
let left = quote! { x };
let right = quote! { y };
let result = Transpiler::transpile_binary_op(left, BinaryOp::LessEqual, right);
assert_eq!(result.to_string(), "x <= y");
}
#[test]
fn test_is_right_associative_multiply() {
assert!(!Transpiler::is_right_associative(BinaryOp::Multiply));
}
#[test]
fn test_is_right_associative_or() {
assert!(!Transpiler::is_right_associative(BinaryOp::Or));
}
#[test]
fn test_transpile_binary_op_gt() {
let left = quote! { a };
let right = quote! { b };
let result = Transpiler::transpile_binary_op(left, BinaryOp::Gt, right);
assert_eq!(result.to_string(), "a > b");
}
}