2026-11-06 16:07:23 -08:00 | | | #!/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<T> name;
|
| | | * fixed arrays T name[N]; -> MqlFixed<T, (N)> name;
|
| | | * array parameters [const] T &name[] -> [const] MqlArray<T> &name
|
2026-11-06 20:26:15 -08:00 | | | * #property lines are dropped.
|
| | | * #include "path/File.mqh" is mapped to the translated header "File.h". The
|
| | | path must be quoted and relative to the including file, which is how
|
| | | MetaEditor resolves it; <...> searches MQL5/Include only, and the project
|
| | | is not there when it is cloned into MQL5/Shared Projects. A path that does
|
| | | not point to an existing file is an error.
|
2026-11-06 17:06:47 -08:00 | | | * datetime literals D'YYYY.MM.DD [HH:MM[:SS]]' become integer seconds (UTC).
|
2026-11-06 16:07:23 -08:00 | | | Everything else must already be valid C++ (see tests/mql5_shim.h).
|
| | |
|
| | | usage: mql2cpp.py input.mqh output.h
|
| | | """
|
2026-11-06 17:06:47 -08:00 | | | import calendar
|
2026-11-06 20:26:15 -08:00 | | | import os
|
2026-11-06 16:07:23 -08:00 | | | 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*;')
|
2026-11-06 20:26:15 -08:00 | | | RE_INCLUDE = re.compile(r'^\s*#include\s*(<[^>]*>|"[^"]*")')
|
2026-11-06 17:06:47 -08:00 | | | 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))
|
2026-11-06 16:07:23 -08:00 | | |
|
| | |
|
2026-11-06 20:26:15 -08:00 | | | def include(spec, src_path):
|
| | | """Return the C++ #include for the MQL5 include spec ("..." or <...>)."""
|
| | | if not spec.startswith('"'):
|
| | | sys.exit('%s: #include %s: use a quoted path relative to this file' % (src_path, spec))
|
| | | path = spec[1:-1].replace('\\', '/')
|
| | | if not os.path.isfile(os.path.join(os.path.dirname(src_path), path)):
|
| | | sys.exit('%s: #include %s: no such file relative to this file' % (src_path, spec))
|
| | | return '#include "%s.h"' % os.path.splitext(os.path.basename(path))[0]
|
| | |
|
| | |
|
| | | def translate(src, src_path):
|
2026-11-06 16:07:23 -08:00 | | | out = []
|
| | | for line in src.splitlines():
|
| | | if line.lstrip().startswith('#property'):
|
| | | out.append('// ' + line)
|
| | | continue
|
| | | m = RE_INCLUDE.match(line)
|
| | | if m:
|
2026-11-06 20:26:15 -08:00 | | | out.append(include(m.group(1), src_path))
|
2026-11-06 16:07:23 -08:00 | | | continue
|
2026-11-06 17:06:47 -08:00 | | | line = RE_DATE.sub(date_literal, line)
|
2026-11-06 16:07:23 -08:00 | | | 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]
|
2026-11-06 20:26:15 -08:00 | | | res += translate(src, sys.argv[1])
|
2026-11-06 16:07:23 -08:00 | | | open(sys.argv[2], 'w', encoding='utf-8').write(res)
|
| | |
|
| | |
|
| | | if __name__ == '__main__':
|
| | | main()
|