File size: 7,953 Bytes
59da845 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | 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)),
}
}
}
|