feat(extract): MQL5 (.mq5/.mqh) extractor - #2481
Conversation
MQL5 is C++ plus a few trading-specific constructs, so this does not add a bespoke extractor. It masks the four MQL5-only forms tree-sitter-cpp cannot parse and runs the existing _CPP_CONFIG pipeline over the result via _extract_generic's source_override: - input / sinput / extern storage classes - input group "..." inspector headings (no type, no terminator) - C'255,128,0' color literals - D'2024.01.31 22:00' datetime literals Masking blanks bytes in place rather than deleting them, so every byte offset -- and therefore every source_location -- still matches the original file. On a 9,000-line corpus of live expert advisors this takes tree-sitter-cpp from 196 parse errors to 0. Masking deliberately reduces `input double Lots = 0.1;` to an ordinary global, so input/extern declarations are recovered separately and re-attached as nodes, with references edges from the functions that read them: in an expert advisor the inputs are the strategy's tunable surface, and "which function reads magic_number" is the question people actually ask of one. Attribution walks brace depth rather than parsing a second tree, which is exact here because MQL5 functions are file scope and never nested; string bodies are blanked first so a brace inside a literal cannot desynchronise the counter. #property version/copyright/link land as metadata on the file node, and .mq5/.mqh join the C++ member-call resolver so CTrade-style object calls resolve.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR adds MQL5 (.mq5/.mqh) file support to the extraction pipeline. It introduces a new graphify/extractors/mql5.py module and an extract_mql5 function that masks MQL5-only syntax the C++ grammar can't parse, then runs the existing C++ extraction pipeline over the result and separately recovers input/extern declarations plus #property metadata. Supporting changes register the new extensions across detection, analysis, build edge-family maps, the C++ member-call resolver, the dispatch table, README, and CHANGELOG, and a new test file (tests_test_mql5*) is included. The bulk of the remaining changed symbols appear to be renumbered rationale/internal helper identifiers, so the reviewer may want to confirm whether those are substantive changes or incidental renumbering.
Worth a look
- decode uses errors='replace' for input fact scanning while masking uses raw bytes, causing offset drift —
graphify/extract.py:1719· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2462 functions depend on the 624 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract_mql5()— 4 callers, 3 callees
Verification — 2462 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2204 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below.
| return _extract_generic(path, _CPP_CONFIG) | ||
|
|
||
|
|
||
| def extract_mql5(path: Path) -> dict: |
There was a problem hiding this comment.
extract_mql5()
high coupling complexity (Ca·Ce = 12).
Grounded coupling-delta finding (deterministic), not an LLM guess.
What
Adds MQL5 (
.mq5/.mqh) extraction — MetaTrader 5 expert advisors, indicators and include headers.The interesting part is what this doesn't do: MQL5 is C++ plus a handful of trading-specific constructs, so rather than a bespoke extractor it masks the four MQL5-only forms
tree-sitter-cppcannot parse and runs the existing_CPP_CONFIGpipeline over the result, using_extract_generic'ssource_override. That gets real classes, structs, functions, calls and includes for ~200 lines of new code instead of a second grammar.The masked constructs:
input/sinput/externstorage classesinput double Lots = 0.1;input group "..."inspector headinginput group "=== GENERAL ==="C'255,128,0'D'2024.01.31 22:00'Masking blanks bytes in place rather than deleting them, so every byte offset — and therefore every
source_location— still matches the original file. Shifting them would silently point every node at the wrong line, which is the kind of bug that is invisible until someone clicks a link.Inputs
Masking deliberately reduces
input double Lots = 0.1;to an ordinary global, soinput/externdeclarations are recovered separately from the original source and re-attached as nodes, withreferencesedges from the functions that read them.This is the same call made for Pine in #2480, for the same reason: in an expert advisor the inputs are the strategy's tunable surface, and "which function reads
magic_number" is the question people actually ask of one. The declared type is kept asmql5_input_type.Attribution walks brace depth rather than parsing a second tree — exact here because MQL5 functions are file scope and never nested. String bodies are blanked before counting so a brace inside a literal cannot desynchronise it, and MetaEditor's house style (opening brace on the line after the signature) is handled explicitly, as is a single-line body.
Also:
#property version/copyright/linkland as metadata on the file node, and.mq5/.mqhjoin the C++ member-call resolver soCTrade-style object calls resolve.Validation
Measured on a private corpus of live expert advisors, ~9,000 lines across 6
.mq5files and 3.mqhheaders:Parse errors: 196 → 0. Per file, before → after masking: 74→0, 61→0, 53→0, 4→0, 4→0, 0→0. The
.mqhheaders already parsed clean.Extraction on the largest EA (3,051 lines): 133 nodes, 267 edges, 64 inputs recovered. Input reads were verified by hand against the source — every
referencesedge formagic_numberlands on a function that genuinely reads it (lines 261, 346, 621, 736, 783, 822, 869, 889, 1649), and no intra-file edge dangles.Tests
tests/fixtures/sample.mq5+tests/test_mql5.py, 13 cases covering masking (byte-offset preservation, each masked construct), extraction (functions, structs, inputs with their declared types, includes), attribution (enclosing function, single-line body, string-brace desync), the#propertyheader, and a no-dangling-edges guard. One test asserts the fixture parses with zero tree-sitter errors after masking, so a future grammar bump that breaks this shows up immediately.Full suite on this branch: 3904 passed. The 56 failures on my machine (
test_skillgen,test_watch,test_uninstall_scope) are pre-existing on Windows — suite run with-p no:randomlybefore and after, failure set byte-identical.Notes
tree-sitter-cppis already a core dep.v8and the two touch different lines. Either can merge first.