mql5-compiler / mql-rust /src /compiler /transpiler.rs
algorembrant's picture
Upload 25 files
59da845 verified
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 } => {
// 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)),
}
}
}