feat(languages): add MQL4/MQL5 extractor + mql-lsp-server registration - #823
Conversation
Publishes the grammar foundation for MQL4/MQL5 indexing: - Pin github.com/davalillo/tree-sitter-mql5 v0.1.0: a maintained fork of mskelton/tree-sitter-mql5 (tree-sitter-cpp + the MQL5 'input' storage class) with a Go binding added and corpus-driven grammar extensions (sinput, interface_specifier, color/datetime literals). The fork's external scanner is a C-only stub: MQL5 has no raw string literals, so the upstream C++ scanner is dead code there. - internal/parser/tsitter/mql: thin shim bridging the binding into tsitter.Language (dart.go pattern). One grammar serves .mq4/.mq5/ .mqh — modern MQL4 shares MQL5 syntax since build 600. - odd/tasks: ODD feature tracking with the corpus audit evidence (53-file LSP corpus: 47 clean; remaining errors are macro-expansion- only fixtures, left to ERROR tolerance by design). Full CGO build passes; tsitter package tests pass.
Registers 'mql' (.mq4/.mq5/.mqh) as a bespoke tree-sitter language over the davalillo/tree-sitter-mql5 fork pinned in the previous commit. The combined query walks class/struct/enum/interface definitions, in-body methods, free and template functions, #include directives, #define macros, and plain/member/template calls. MQL-specific surface: - input/sinput/extern top-level variables mint variable nodes with Meta["storage_class"] — an EA's user-facing configuration surface. - interface declarations index as KindInterface with Meta["methods"] (Java convention) for implementation matching. - The file node carries Meta["dialect"] (mql4/mql5; .mqh content- sniffed with the same MQL5 markers mql-language-server uses). - Calls attribute to the enclosing function/method via the shared funcRanges mechanism; member calls target unresolved::*.<method>. Known boundary (documented in docs/languages.md): macro-expansion-only declarations (Controls event map) stay ERROR nodes and mint nothing; out-of-line member definitions (Cls::method) emit as free functions. Covered by mql_test.go: happy path (method+member_of, interface methods, storage-class variables, include edge, call attribution), empty input, and dialect stamping across all three extensions. Full package suite aborts with a pre-existing environmental glibc heap corruption inside tree-sitter cgo — reproduced identically at the pre-MQL base commit (2b5480b) with a moving crash site; MQL tests pass in isolation and all meta-tests (extension collisions, coverage clusters) pass.
Language count 256 -> 257, bespoke tier ~31, core extraction matrix row, and an MQL specifics section covering dialect stamping, storage- class variables, interface method sets, includes, call attribution, and the macro-expansion / out-of-line-member coverage boundaries.
Adds the davalillo/mql-language-server spec to the LSP registry so enrichment upgrades MQL edges (definition/implementation -> lsp_resolved) and the on-demand action tools (get_diagnostics, get_code_actions, apply_code_action, fix_all_in_file) become available for .mq4/.mq5/.mqh. Routing notes: - Languages "mql" matches the extractor's language key. - .mq4/.mq5 short-circuit via languageId; .mqh deliberately maps to a non-recognized languageId so the server's LanguageDetection routes headers by content sniffing — its documented fallback, since a header's dialect is not knowable from its extension. - The server speaks plain LSP over stdio with no args (the --stdio flag its docs show is accepted and ignored). Install: self-contained binary from GitHub Releases (no .NET needed) or 'dotnet tool install -g mql-language-server' (.NET 10). Documented in docs/lsp.md server matrix and install section.
Consumes the ABI 15 parser released as github.com/davalillo/tree-sitter-mql5 v0.2.0 (tree-sitter-cpp v0.23.4 base, C-only scanner, zero real-corpus errors). Replaces the development-time local replace directive used for verification.
Registers the grammar fork dependency in the generated third-party list, following the same pattern as the other tree-sitter grammar modules.
|
Context: why MQL belongs in a code-intelligence engine MQL4/MQL5 is the proprietary language of MetaTrader 4/5, the retail trading platform family by MetaQuotes. Far from niche:
The grammar and extractor in this PR were validated against a real 21.4k-line production EA from that ecosystem (zero parse errors after the corpus-driven grammar extensions). |
…arations
emitMqlInputs only read initialized declarators, so 'input double
Rates[][6];', 'extern int A, B;' and 'extern CArrayObj *Ptr;' minted no
variable nodes. Collect one entry per declarator instead, resolving
array/pointer declarators down to their base identifier. Gating on
FieldNameForChild('declarator') is not usable: with comma-separated
declarators only the first carries the field name in the
tree-sitter-c grammar.
…cksum verification releases/latest silently tracks whatever shipped last. Install from the v2.4.2 tag, verify against the release CHECKSUMS.txt (sha256sum -c --ignore-missing), point at the other platform assets, and pin the dotnet tool path to --version 2.4.2. Upgrades become a deliberate tag bump instead of an untracked drift.
…d, scanner included davalillo/tree-sitter-mql5 is no longer a plain fork of mskelton: it has rebased onto tree-sitter-cpp v0.23.4 (ABI 15), grown corpus-driven extensions and highlight catalogs, and vendors its C scanner (renamed tree_sitter_mql5_*). Drop the 'fork' and 'scanner stub' wording.
The marker list is a shared contract between the extractor and mql-language-server (Mql5Tokens is that server's single source of truth). Anchor the copy in a comment — last verified identical against v2.4.2 — so a server-side change cannot silently diverge the .mqh dialect sniffing.
languages.md was bumped to ~31 when MQL4/MQL5 joined the bespoke tier; features.md still said ~30 from before this PR (its 257 total was also already ahead of languages.md's 256).
|
Hi @zzet, the macOS test job failed on the latest run, but the failure looks pre-existing and unrelated to this diff. Could you re-run the failed jobs when you have a minute? Details:
If it reproduces after the re-run I'll dig deeper, but right now it looks like a timing flake in the checkout control tests rather than something introduced here. |
zzet
left a comment
There was a problem hiding this comment.
Hey @davalillo,
I found one issue I'd like to highlight: Template member methods are silently omitted
walkMqlTypeBody handles direct function_definition nodes but does not handle template_declaration. At the same time, emitMqlTemplateFunction explicitly skips templates inside a type.
As a result, valid templated MQL methods produce no method or member_of node, and calls inside them cannot be attributed to their enclosing method.
Suggested fix:
- Handle
template_declarationinwalkMqlTypeBody. - Extract its wrapped
function_definitionand pass it through the method-emission path. - Add a regression test asserting:
- the method node exists;
- the
member_ofedge exists; - calls inside the template method are attributed correctly.
Relevant code:
…r functions Review finding (PR zzet#823, zzet): walkMqlTypeBody had no template_declaration case while the query's template dispatch skips in-body templates, so a templated member method was mis-emitted as a free function (plain declarator) or dropped entirely together with its body's calls (pointer declarator). - walkMqlTypeBody: unwrap a template_declaration's function_definition into the method path; the _method_L seen marker keeps the func.def dispatch from double-emitting it. A template wrapping a nested class keeps its class path. - qMqlAll: copy the pointer / double-pointer / reference declarator patterns from the C++ query — free functions like `CObject *New()` (the idiomatic MQL5 factory shape) were silently dropped before. - Regression tests: method node, member_of edge, call attribution, the pointer-return template form, and free pointer-return functions. - docs/languages.md: templated member methods listed as covered.
|
Thanks @zzet, spot on. I verified the finding empirically before fixing, and it was actually two shapes: a plain-declarator template member leaked out as a free function, while a pointer-return one ( Fixed in 93a256d:
Fixing it also surfaced a sibling gap worth flagging:
|
Summary
Adds MQL4/MQL5 (
.mq4/.mq5/.mqh) as a first-class language: bespoke tree-sitter extraction (following the #805 shape: extractor + tests + registration + docs) plus the LSP registration that upgrades MQL edges and unlocks the on-demand action tools.davalillo/tree-sitter-mql5, is independently maintained since 2026 — it began as a fork ofmskelton/tree-sitter-mql5(tree-sitter-cpp + the MQL5inputstorage class), but the upstream has been dormant since 2023, so that repo is now the canonical home: full git history preserved, per-contribution attribution inACKNOWLEDGMENTS.md. It has moved well past the original fork — rebased onto tree-sitter-cpp v0.23.4 (tree-sitter CLI 0.25, ABI 15) with a Go binding and corpus-driven extensions:sinput,interface_specifier, MQL-nativeC'…'/D'…'literals,input group, MQL primitive types, declarations as un-braced if/else consequences, and empty member declarations after inline constructor bodies. The external scanner is the C scanner of the cpp base (renamedtree_sitter_mql5_*; raw strings are not MQL5), keeping the build free of a C++ toolchain.funcRangesattribution) and adds the MQL surface:input/sinput/externvariables,Meta["methods"]interfaces, methods withEdgeMemberOf,#includeimports,Meta["dialect"]on file nodes (content-sniffed for.mqh).mql-lsp-server(my project,davalillo/mql-language-server: an OmniSharp-based stdio LSP) sodefinition/implementationenrichment produceslsp_resolvededges andget_diagnostics/get_code_actions/fix_all_in_filework on MQL..mqhdeliberately maps to a languageId the server does not short-circuit on, so itsLanguageDetectionroutes headers by content sniffing — a header's dialect isn't knowable from its extension. Plain stdio, no args (the--stdioflag its docs show is accepted and ignored). Install: self-contained binary from GitHub Releases (no .NET runtime) ordotnet tool install -g mql-language-server(.NET 10).github.com/davalillo/tree-sitter-mql5— I'm its maintainer and will keep evolving it alongsidemql-language-server. Happy to transfer togortexhqin the future if that ever becomes the better home.Corpus audit
The grammar extensions are data-driven, not speculative. Parsing a 53-file MQL corpus with the stock grammar leaves 46 files clean with 37 ERROR nodes; after the extensions, 47 files clean and 10 ERROR nodes — every remaining one is a macro-expansion-only declaration (Controls event map), the same boundary mql-language-server enforces with its macro layer.
On a real 21.4k-line / 720 KB production EA, the stock grammar produced 40 contained ERROR nodes; the two rules added for it (declaration-as-statement, stray semicolon after inline constructor bodies) bring it to zero ERROR nodes. The v0.2.0 grammar also ships a 111-file real-corpus regression harness with CI (fails if the per-file error count rises). 100/100 tree-sitter tests pass, including the full upstream C++ corpus (no C++ regressions).
Extraction performance on that EA: ~145 ms per file (parse + graph), 400 nodes (198 functions, 175 variables, 18 types) and 4,668 edges (4,263 calls).
Changes
go.mod,go.sumgithub.com/davalillo/tree-sitter-mql5 v0.2.0internal/parser/tsitter/mql/mql.gointernal/parser/languages/mql.gointernal/parser/languages/mql_test.gointernal/parser/languages/register.godocs/languages.mdinternal/parser/languages/mql.go(+test)input double Rates[][6];,extern int A, B;) — previously silently droppedinternal/semantic/lsp/registry.gomql-lsp-serverspec (priority 5)docs/lsp.mddocs/languages.mdTesting
go test -race ./internal/parser/languages/ -run TestMQL→ 7/7go test -race ./internal/semantic/lsp/→ 376/376gofmtclean;go build ./...passesgo test -race ./...in full — blocked on my machine by a pre-existing glibc heap corruption inside tree-sitter cgo (SIGABRT, moving crash site), reproduced identically at base2b5480bfwith no MQL code present. Happy to file a separate issue with the reproduction details.-raceon./internal/parser/languages(corrupted size vs. prev_size, moving crash site mid-Go-extractor tests) and at the PR base with zero MQL changes (~5/6 runs), so it is not-race-specific. All MQL tests, the LSP suite (376/376) and meta-tests pass; only the flaky abort interrupts the full-package run.internal/parser/forestdoes not compile (go-sitter-forestperlscanner.cvs system gcc/glibc) — no MQL code involved.Checklist
Meta["methods"]for interfacesEdgeMemberOfedges to their containing type