#!/usr/bin/env python3 """Translate the MQL5 sources of this project into C++ so that the exact same code can be compiled and tested without MetaTrader. Only the constrained MQL5 subset used in this repository is supported: * dynamic arrays T name[]; -> MqlArray name; * fixed arrays T name[N]; -> MqlFixed name; * array parameters [const] T &name[] -> [const] MqlArray &name * #property lines are dropped, #include is mapped to the translated file. * datetime literals D'YYYY.MM.DD [HH:MM[:SS]]' become integer seconds (UTC). Everything else must already be valid C++ (see tests/mql5_shim.h). usage: mql2cpp.py input.mqh output.h """ import calendar import re import sys KEYWORDS = {'return', 'delete', 'new', 'else', 'case', 'goto', 'throw', 'typedef', 'using'} RE_PARAM = re.compile(r'(\bconst\s+)?\b([A-Za-z_]\w*)\s*&\s*([A-Za-z_]\w*)\s*\[\s*\]') RE_DYN = re.compile(r'^(\s*)(static\s+)?([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*\[\s*\]\s*;') RE_FIX = re.compile(r'^(\s*)(static\s+)?([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*\[\s*([^\]]+?)\s*\]\s*;') RE_INCLUDE = re.compile(r'^\s*#include\s*[<"]([^>"]+)\.mqh[>"]') RE_DATE = re.compile(r"D'(\d{4})\.(\d{2})\.(\d{2})(?:\s+(\d{2}):(\d{2})(?::(\d{2}))?)?'") def date_literal(m): y, mo, d = int(m.group(1)), int(m.group(2)), int(m.group(3)) hh = int(m.group(4) or 0) mm = int(m.group(5) or 0) ss = int(m.group(6) or 0) return '((datetime)%dL)' % calendar.timegm((y, mo, d, hh, mm, ss, 0, 0, 0)) def translate(src): out = [] for line in src.splitlines(): if line.lstrip().startswith('#property'): out.append('// ' + line) continue m = RE_INCLUDE.match(line) if m: name = m.group(1).split('/')[-1] out.append('#include "%s.h"' % name) continue line = RE_DATE.sub(date_literal, line) line = RE_PARAM.sub(lambda mm: '%sMqlArray<%s> &%s' % (mm.group(1) or '', mm.group(2), mm.group(3)), line) m = RE_DYN.match(line) if m and m.group(3) not in KEYWORDS: line = '%s%sMqlArray<%s> %s;%s' % (m.group(1), m.group(2) or '', m.group(3), m.group(4), line[m.end():]) else: m = RE_FIX.match(line) if m and m.group(3) not in KEYWORDS: line = '%s%sMqlFixed<%s, (%s)> %s;%s' % (m.group(1), m.group(2) or '', m.group(3), m.group(5), m.group(4), line[m.end():]) out.append(line) return '\n'.join(out) + '\n' def main(): src = open(sys.argv[1], encoding='utf-8').read() res = '// Generated by tests/mql2cpp.py from %s - do not edit.\n' % sys.argv[1].split('/')[-1] res += translate(src) open(sys.argv[2], 'w', encoding='utf-8').write(res) if __name__ == '__main__': main()