use proc_macro2::TokenStream;
use quote::ToTokens;
use crate::SourceCodeSpan as _;
use crate::ast::expr::{
BinaryOp, Expr, ExprBinary, ExprBool, ExprIndex, ExprMethodCall, ExprNullptr, ExprParen,
ExprUnary, UnaryOp,
};
use crate::transpile::{Transpile, TranspileContext, Transpiler};
use super::error::TranspileError;
impl Transpile for BinaryOp {
fn transpile(
&self,
_transpiler: &Transpiler,
_ctx: &mut TranspileContext,
tokens: &mut TokenStream,
) -> Result<(), TranspileError> {
match self {
BinaryOp::Add => tokens.extend(quote::quote!(+)),
BinaryOp::Sub => tokens.extend(quote::quote!(-)),
BinaryOp::Mul => tokens.extend(quote::quote!(*)),
BinaryOp::Div => tokens.extend(quote::quote!(/)),
BinaryOp::Mod => tokens.extend(quote::quote!(%)),
BinaryOp::Less => tokens.extend(quote::quote!(<)),
BinaryOp::LessEqual => tokens.extend(quote::quote!(<=)),
BinaryOp::Greater => tokens.extend(quote::quote!(>)),
BinaryOp::GreaterEqual => tokens.extend(quote::quote!(>=)),
BinaryOp::ThreeWay => tokens.extend(quote::quote!(<=>)),
BinaryOp::Equal => tokens.extend(quote::quote!(==)),
BinaryOp::NotEqual => tokens.extend(quote::quote!(!=)),
BinaryOp::BitAnd => tokens.extend(quote::quote!(&)),
BinaryOp::BitXor => tokens.extend(quote::quote!(^)),
BinaryOp::BitOr => tokens.extend(quote::quote!(|)),
BinaryOp::LogicalAnd => tokens.extend(quote::quote!(&&)),
BinaryOp::LogicalOr => tokens.extend(quote::quote!(||)),
BinaryOp::Assign => tokens.extend(quote::quote!(=)),
BinaryOp::AddAssign => tokens.extend(quote::quote!(+=)),
BinaryOp::SubAssign => tokens.extend(quote::quote!(-=)),
BinaryOp::MulAssign => tokens.extend(quote::quote!(*=)),
BinaryOp::DivAssign => tokens.extend(quote::quote!(/=)),
BinaryOp::ModAssign => tokens.extend(quote::quote!(%=)),
BinaryOp::ShiftLeftAssign => tokens.extend(quote::quote!(<<=)),
BinaryOp::ShiftRightAssign => tokens.extend(quote::quote!(>>=)),
BinaryOp::BitAndAssign => tokens.extend(quote::quote!(&=)),
BinaryOp::BitXorAssign => tokens.extend(quote::quote!(^=)),
BinaryOp::BitOrAssign => tokens.extend(quote::quote!(|=)),
_ => {
return Err(TranspileError::UnsupportedExpr {
message: format!("binary operator `{self:?}` cannot be transpiled to Rust"),
src: String::new(),
err_span: miette::SourceSpan::new(0.into(), 0),
});
}
}
Ok(())
}
}
pub fn unsupported_from_expr(message: &str, expr: &Expr<'_>) -> TranspileError {
match expr.span() {
Some(span) => TranspileError::UnsupportedExpr {
message: message.to_owned(),
src: span.full_source().to_owned(),
err_span: span.into(),
},
None => TranspileError::UnsupportedExpr {
message: format!("{}: {:?}", message, expr),
src: String::new(),
err_span: miette::SourceSpan::new(0.into(), 0),
},
}
}
impl<'de> Transpile for Expr<'de> {
#[allow(clippy::only_used_in_recursion)]
fn transpile(
&self,
transpiler: &Transpiler,
ctx: &mut TranspileContext,
tokens: &mut TokenStream,
) -> Result<(), TranspileError> {
match self {
Expr::Lit(lit) => {
let rust_expr: syn::Expr = syn::parse_str(lit.span.src()).map_err(|e| {
unsupported_from_expr(&format!("cannot parse literal: {e}"), self)
})?;
tokens.extend(quote::quote!(#rust_expr));
}
Expr::Bool(ExprBool { value, .. }) => {
tokens.extend(quote::quote!(#value));
}
Expr::Nullptr(ExprNullptr { .. }) => {
tokens.extend(quote::quote!(std::ptr::null()));
}
Expr::Ident(i) => {
if ctx.needs_self_prefix(i.ident.sym) {
tokens.extend(quote::quote!(self.));
}
i.ident.to_tokens(tokens);
}
Expr::Path(p) => {
let rust_expr = syn::Expr::try_from(p.path.clone()).map_err(|e| {
unsupported_from_expr(&format!("cannot transpile path expression: {e}"), self)
})?;
tokens.extend(quote::quote!(#rust_expr));
}
Expr::Unary(ExprUnary {
op: UnaryOp::Negate,
operand,
}) => {
let mut inner_tokens = TokenStream::new();
operand.transpile(transpiler, ctx, &mut inner_tokens)?;
tokens.extend(quote::quote!(- #inner_tokens));
}
Expr::Binary(ExprBinary { lhs, op, rhs }) => {
lhs.transpile(transpiler, ctx, tokens)?;
op.transpile(transpiler, ctx, tokens)?;
rhs.transpile(transpiler, ctx, tokens)?;
}
Expr::Paren(ExprParen { expr }) => {
let mut inner_tokens = TokenStream::new();
expr.transpile(transpiler, ctx, &mut inner_tokens)?;
tokens.extend(quote::quote!((#inner_tokens)));
}
Expr::Index(index) => {
index.transpile(transpiler, ctx, tokens)?;
}
Expr::MethodCall(call) => {
call.transpile(transpiler, ctx, tokens)?;
}
other => {
return if let Some(handler) = &transpiler.fallback_expr_handler {
handler(other, transpiler, ctx, tokens)
} else {
Err(unsupported_from_expr(
"expression cannot be transpiled to Rust",
other,
))
};
}
}
Ok(())
}
}
impl<'de> Transpile for ExprBinary<'de> {
fn transpile(
&self,
transpiler: &Transpiler,
ctx: &mut TranspileContext,
tokens: &mut TokenStream,
) -> Result<(), TranspileError> {
self.lhs.transpile(transpiler, ctx, tokens)?;
self.op.transpile(transpiler, ctx, tokens)?;
self.rhs.transpile(transpiler, ctx, tokens)
}
}
impl<'de> Transpile for ExprIndex<'de> {
fn transpile(
&self,
transpiler: &Transpiler,
ctx: &mut TranspileContext,
tokens: &mut TokenStream,
) -> Result<(), TranspileError> {
self.object.transpile(transpiler, ctx, tokens)?;
let mut index_tokens = TokenStream::new();
self.index.transpile(transpiler, ctx, &mut index_tokens)?;
tokens.extend(quote::quote!([#index_tokens]));
Ok(())
}
}
impl<'de> Transpile for ExprMethodCall<'de> {
fn transpile(
&self,
transpiler: &Transpiler,
ctx: &mut TranspileContext,
tokens: &mut TokenStream,
) -> Result<(), TranspileError> {
self.receiver.transpile(transpiler, ctx, tokens)?;
let method = &self.method;
let mut arg_tokens = Vec::new();
for arg in self.args.iter() {
let mut t = TokenStream::new();
arg.transpile(transpiler, ctx, &mut t)?;
arg_tokens.push(t);
}
tokens.extend(quote::quote!(.#method(#(#arg_tokens),*)));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::stmt::{Stmt, StmtExpr};
use crate::ast::{self, parse_file};
fn parse_first_expr(src: &str) -> Expr<'_> {
let file = parse_file(src).unwrap();
match &file.items[0] {
ast::Item::Fn(ast::ItemFn {
block: Some(block), ..
}) => match &block.stmts[0] {
Stmt::Expr(StmtExpr { expr }) => expr.clone(),
other => panic!("expected Stmt::Expr, got {other:?}"),
},
item => panic!("expected ItemFn, got {item:?}"),
}
}
#[test]
fn method_call_no_args_transpiles() -> Result<(), TranspileError> {
let transpiler = Transpiler::default();
let src = "void f() { obj.method(); }";
let expr = parse_first_expr(src);
assert_eq!(
expr.transpile_token_stream(&transpiler, &mut TranspileContext::default())?
.to_string(),
"obj . method ()"
);
Ok(())
}
#[test]
fn method_call_with_args_transpiles() -> Result<(), TranspileError> {
let transpiler = Transpiler::default();
let src = "void f() { obj.method(1, 2); }";
let expr = parse_first_expr(src);
assert_eq!(
expr.transpile_token_stream(&transpiler, &mut TranspileContext::default())?
.to_string(),
"obj . method (1 , 2)"
);
Ok(())
}
#[test]
fn index_expr_transpiles() -> Result<(), TranspileError> {
let transpiler = Transpiler::default();
let src = "void f() { arr[0]; }";
let expr = parse_first_expr(src);
assert_eq!(
expr.transpile_token_stream(&transpiler, &mut TranspileContext::default())?
.to_string(),
"arr [0]"
);
Ok(())
}
#[test]
fn arrow_method_call_transpiles() -> Result<(), TranspileError> {
let transpiler = Transpiler::default();
let src = "void f() { ptr->method(x); }";
let expr = parse_first_expr(src);
assert_eq!(
expr.transpile_token_stream(&transpiler, &mut TranspileContext::default())?
.to_string(),
"ptr . method (x)"
);
Ok(())
}
#[test]
fn ident_with_member_context_gets_self_prefix() -> Result<(), TranspileError> {
let transpiler = Transpiler::default();
let src = "void f() { member_var; }";
let expr = parse_first_expr(src);
let mut ctx = TranspileContext::with_members(["member_var".to_owned()]);
assert_eq!(
expr.transpile_token_stream(&transpiler, &mut ctx)?
.to_string(),
"self . member_var"
);
Ok(())
}
#[test]
fn ident_without_member_context_stays_plain() -> Result<(), TranspileError> {
let transpiler = Transpiler::default();
let src = "void f() { local_var; }";
let expr = parse_first_expr(src);
let mut ctx = TranspileContext::with_members(["member_var".to_owned()]);
assert_eq!(
expr.transpile_token_stream(&transpiler, &mut ctx)?
.to_string(),
"local_var"
);
Ok(())
}
#[test]
fn ident_local_shadows_member() -> Result<(), TranspileError> {
let transpiler = Transpiler::default();
let src = "void f() { x; }";
let expr = parse_first_expr(src);
let mut ctx = TranspileContext::with_members(["x".to_owned()]);
ctx.push_scope();
ctx.declare_local("x".to_owned());
assert_eq!(
expr.transpile_token_stream(&transpiler, &mut ctx)?
.to_string(),
"x"
);
Ok(())
}
}