use crate::compiler::ast::AstNode; use crate::compiler::lexer::Token; pub struct Transpiler; impl Transpiler { pub fn new() -> Self { Self } pub fn transpile(&self, ast: &AstNode) -> Result { let mut output = String::new(); output.push_str("// Auto-generated by MQL-Rust Transpiler\n"); output.push_str("use crate::engine::backtester::{Backtester, OrderType};\n\n"); self.visit_node(ast, &mut output)?; Ok(output) } fn visit_node(&self, node: &AstNode, out: &mut String) -> Result<(), String> { match node { AstNode::Program(nodes) => { for n in nodes { self.visit_node(n, out)?; out.push('\n'); } } AstNode::VarDecl { var_type, is_input, name, init_value } => { // Convert input variables to public struct fields (for UI configuration) or global consts let type_str = self.mql_to_rust_type(var_type)?; if *is_input { // For now, hardcode inputs as consts to simplify the transpiler proof-of-concept out.push_str(&format!("pub const {}: {} = ", name.to_uppercase(), type_str)); } else { out.push_str(&format!("let mut {}: {} = ", name, type_str)); } if let Some(val) = init_value { self.visit_node(val, out)?; } else { out.push_str("Default::default()"); } out.push_str(";\n"); } AstNode::FunctionDecl { return_type, name, params, body } => { let ret_str = self.mql_to_rust_type(return_type)?; // Special handling for MQL5 entry points if name == "OnInit" || name == "OnDeinit" || name == "OnTick" || name == "OnTimer" { // Passing the backtester context out.push_str(&format!("pub fn mql_{}(bt_ctx: &mut Backtester", name)); } else { out.push_str(&format!("fn {}(", name)); } // Add params explicitly handling if !params.is_empty() { if name.starts_with("On") { out.push_str(", "); } // Add comma if context was added for (i, p) in params.iter().enumerate() { if let AstNode::VarDecl { var_type, name: p_name, .. } = p { let p_type = self.mql_to_rust_type(var_type)?; out.push_str(&format!("{}: &{}", p_name, p_type)); if i < params.len() - 1 { out.push_str(", "); } } } } out.push_str(")"); if *return_type != Token::Void { out.push_str(&format!(" -> {}", ret_str)); } out.push_str(" "); self.visit_node(body, out)?; } AstNode::Block(nodes) => { out.push_str("{\n"); for n in nodes { self.visit_node(n, out)?; out.push('\n'); } out.push_str("}\n"); } AstNode::ExpressionStatement(expr) => { self.visit_node(expr, out)?; out.push_str(";"); } AstNode::ReturnStatement(opt_expr) => { out.push_str("return "); if let Some(expr) = opt_expr { self.visit_node(expr, out)?; } out.push_str(";"); } AstNode::IfStatement { condition, then_branch, else_branch } => { out.push_str("if "); self.visit_node(condition, out)?; out.push_str(" "); self.visit_node(then_branch, out)?; if let Some(e) = else_branch { out.push_str(" else "); self.visit_node(e, out)?; } } AstNode::BinaryOp { left, op, right } => { self.visit_node(left, out)?; let op_str = match op { Token::Plus => " + ", Token::Minus => " - ", Token::Star => " * ", Token::Slash => " / ", Token::Assign => " = ", Token::Equals => " == ", Token::NotEquals => " != ", Token::GreaterEq => " >= ", Token::LessEq => " <= ", Token::Greater => " > ", Token::Less => " < ", Token::And => " && ", Token::Or => " || ", _ => return Err(format!("Unsupported binary operator {:?}", op)), }; out.push_str(op_str); self.visit_node(right, out)?; } AstNode::UnaryOp { op, expr } => { let op_str = match op { Token::Not => "!", Token::Minus => "-", _ => return Err(format!("Unsupported unary operator {:?}", op)), }; out.push_str(op_str); self.visit_node(expr, out)?; } AstNode::FunctionCall { name, args } => { // If it's a built in order function, route to the context if name == "OrderSend" { // Simulating simpler logic out.push_str("bt_ctx.market_order("); } else { out.push_str(&format!("{}(", name)); } for (i, arg) in args.iter().enumerate() { self.visit_node(arg, out)?; if i < args.len() - 1 { out.push_str(", "); } } out.push_str(")"); } AstNode::OrderSendCall { symbol, cmd, volume, price, sl, tp } => { // Specialized mapping for trading commands let cmd_str = if *cmd == Token::Identifier("ORDER_TYPE_BUY".to_string()) { "OrderType::Buy" } else { "OrderType::Sell" }; out.push_str(&format!( "bt_ctx.market_order(\"{}\", {}, {}, Some({}), Some({}), \"Time_Sim\".to_string())", symbol, cmd_str, volume, sl, tp )); } AstNode::Identifier(id) => { out.push_str(id); } AstNode::Literal(tok) => { match tok { Token::IntLiteral(i) => out.push_str(&i.to_string()), Token::DoubleLiteral(d) => out.push_str(&format!("{:.5}", d)), Token::BoolLiteral(b) => out.push_str(if *b { "true" } else { "false" }), Token::StringLiteral(s) => out.push_str(&format!("\"{}\"", s)), _ => return Err(format!("Unexpected literal token {:?}", tok)), } } _ => return Err("Unsupported AST Node".to_string()), } Ok(()) } fn mql_to_rust_type(&self, mql_type: &Token) -> Result<&'static str, String> { match mql_type { Token::Int => Ok("i64"), Token::Double => Ok("f64"), Token::Bool => Ok("bool"), Token::String => Ok("String"), Token::Void => Ok("()"), Token::MqlTick => Ok("crate::data::parser::Tick"), Token::MqlRates => Ok("crate::data::parser::Ohlcv"), _ => Err(format!("Unknown or unmapped MQL5 type: {:?}", mql_type)), } } }