| 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<String, String> { |
| 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 } => { |
| |
| let type_str = self.mql_to_rust_type(var_type)?; |
| if *is_input { |
| |
| 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)?; |
| |
| |
| if name == "OnInit" || name == "OnDeinit" || name == "OnTick" || name == "OnTimer" { |
| |
| out.push_str(&format!("pub fn mql_{}(bt_ctx: &mut Backtester", name)); |
| } else { |
| out.push_str(&format!("fn {}(", name)); |
| } |
|
|
| |
| if !params.is_empty() { |
| if name.starts_with("On") { out.push_str(", "); } |
| 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 name == "OrderSend" { |
| 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 } => { |
| |
| 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)), |
| } |
| } |
| } |
|
|