colbymchenry--codegraph
172 行
9.6 KiB
Markdown
172 行
9.6 KiB
Markdown
# tree-sitter-vbnet.wasm — provenance & rebuild
|
|
|
|
`src/extraction/wasm/tree-sitter-vbnet.wasm` is built from
|
|
[govindbanura/tree-sitter-vbnet](https://github.com/govindbanura/tree-sitter-vbnet)
|
|
(MIT) at commit `538b7087bf80e86004531b392fe1186379c0a2b5` with the patch in
|
|
`tree-sitter-vbnet.patch` applied. The patch carries two files: `grammar.js`
|
|
(edits) and `src/scanner.c` (a new external scanner; upstream has none). The
|
|
upstream repo checks in no generated `src/`, so everything else is produced by
|
|
`tree-sitter generate`.
|
|
|
|
Alternatives considered: `CodeAnt-AI/tree-sitter-vb-dotnet` (22★) has **no
|
|
license file** and its git history stopped in July 2025 — unusable for
|
|
vendoring; `gabriel-gubert/tree-sitter-vbnet` is a 470-line VBScript-flavored
|
|
toy. The Roslyn-based approach (PR #627) was withdrawn by its author in favor
|
|
of tree-sitter — a Roslyn sidecar would add a .NET runtime dependency to a
|
|
local-first npm tool.
|
|
|
|
## What the patch adds
|
|
|
|
Upstream parses textbook VB.NET but fails on the constructs that dominate real
|
|
codebases (measured: 3–18% of files parsed clean across PolicyPlus, CompactGUI,
|
|
and staxrip before patching). Each item below was found by parse-error census
|
|
on those repos plus SCrawler and PCL:
|
|
|
|
1. **Generic type arguments in dotted names** — `System.Collections.Generic.
|
|
Dictionary(Of K, V)`, `Implements IRepository(Of Invoice)`, and
|
|
method-level `Implements I(Of T).Member` (generic segments were only
|
|
accepted unqualified). Open generic types (`GetType(LoaderTask(Of ,))`)
|
|
parse too.
|
|
2. **Interpolated strings** `$"… {expr[,align][:fmt]} …"` with `""`/`{{`/`}}`
|
|
escapes — including multi-line bodies and content pieces that begin with an
|
|
apostrophe: the pieces carry lexical precedence 101 (above `comment`'s 100)
|
|
because the comment **extra** otherwise fires *inside* the string rule and
|
|
eats the rest of the line, closing quote included.
|
|
3. **Date/time literals** `#1/15/2020#` — previously lexed as a preprocessor
|
|
directive that swallowed to end-of-line. Directives are now constrained to
|
|
`#` + letter (`#If`, `#Region`, …), which real directives always satisfy.
|
|
4. **VB 14 multi-line string literals** (a `"…"` literal may span lines since
|
|
VS 2015) and single-token `string_literal`/`character_literal` (`"["c`) —
|
|
the old multi-token form let extras interleave mid-string.
|
|
5. **Numeric literal forms** — hex/octal/binary (`&HFF`, `&O777`, `&B1010`),
|
|
digit separators (`1_000`), type characters (`6.0!`, `50.0#`, `1.5@`,
|
|
`123&`, `7%`), and lowercase `f/r/d` suffixes. WinForms `.Designer.vb`
|
|
files are full of `6.0!`.
|
|
6. **Identifier type characters and Unicode identifiers** — `Dim i% = 0`,
|
|
`Dim r$ = …` (classic VB style, pervasive in SCrawler) and full Unicode
|
|
identifiers (`CrashReason.Java虚拟机参数有误` — PCL is written in Chinese).
|
|
The identifier token is now `[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Pc}]*
|
|
[%$&!#@]?` with the `u` regex flag. **The `u` flag requires
|
|
tree-sitter-cli ≥ 0.25** — 0.24.x silently drops the `\p{…}` classes.
|
|
7. **`As New T(args)` initializer clauses** — `as_clause` embeds a full
|
|
`object_creation_expression` for the `As New` form, so `Dim x As New
|
|
StringBuilder` / `Property P As New List(Of String)` produce instantiation
|
|
nodes. `Dim x? = expr` nullable declarators parse as well.
|
|
8. **Statement separators and single-line forms** — `:` as a statement
|
|
terminator and block opener (`Class X : Inherits Y`, `Case 1 : Return "X"`),
|
|
single-line `If … Then stmt Else stmt` (via terminator-less inline statement
|
|
variants, aliased to the normal statement node names), inline `RaiseEvent`,
|
|
and optional `Then` on block `If` and `ElseIf` (legal VB, used in staxrip).
|
|
9. **Multi-line lambdas** — `Sub(…) … End Sub` / `Function(…) … End Function`
|
|
bodies (upstream had a statement-block body with no `End` closer, so every
|
|
block lambda broke its surrounding argument list), `Async`/`Iterator`
|
|
lambda modifiers, `ByVal`/`ByRef` lambda parameters, and single-line
|
|
`Sub() If cond Then …` statement bodies.
|
|
10. **Member declarations** — `Declare [Auto|Ansi|Unicode] Sub/Function … Lib
|
|
"dll" [Alias "…"]` P/Invoke declarations, `Custom Event … AddHandler/
|
|
RemoveHandler/RaiseEvent … End Event`, stacked attribute lines above one
|
|
member, property `= initializer` before `Implements`, type-less
|
|
auto-properties, and **`MustOverride` body-less methods and properties**:
|
|
`MustOverride` lexes as a dedicated token (removed from the
|
|
`member_modifier` alternation) that only `abstract_method_declaration` /
|
|
`abstract_property_declaration` accept, making the body-less parse
|
|
deterministic. (A GLR body-less alternative on `method_declaration` was
|
|
tried first and measurably poisoned error recovery — 100%→60% clean on
|
|
PolicyPlus — before being replaced with the token split.)
|
|
11. **Expressions** — VB 15 tuple literals `(a, b)`, array literals
|
|
`{1, 2, 3}` (plus nested `{{k, v}, …}` dictionary groups, replacing the
|
|
ambiguous upstream `dictionary_initializer`), omitted argument slots
|
|
(`f(a,, b)` — Optional parameters passed positionally), `TypeOf x IsNot T`,
|
|
generic method calls without parens (`items.OfType(Of Panel)`),
|
|
null-conditional indexing `x?(0)`, and `Global.`-qualified type names.
|
|
12. **LINQ queries** — query expressions no longer require a trailing
|
|
`Select`/`Group` clause, `Aggregate`-led queries, and
|
|
`Distinct`/`Skip`/`Take` clauses.
|
|
|
|
### External scanner (`src/scanner.c`, new)
|
|
|
|
Two constructs are not LR(1)-parseable with tree-sitter's newline-as-extra
|
|
treatment; both get external tokens:
|
|
|
|
- **`QUERY_CLAUSE_CONTINUATION`** — multi-line LINQ (`From x In xs` ↵
|
|
`Where …`). At a clause boundary the newline alone cannot distinguish
|
|
"query continues on the next line" from "statement ends here". The scanner
|
|
looks past the newline run at the next word and emits the continuation
|
|
token only when it is a query-clause keyword (with a `Select Case`
|
|
guard), so the decision is made by the lexer instead of the LR table.
|
|
- **`XML_LITERAL`** — whole VB XML literals (`<Tags><Tag/></Tags>`) consumed
|
|
as one opaque token: element nesting, attributes, comments, CDATA,
|
|
processing instructions, and **nested** `<%= … %>` embedded expressions
|
|
(the staxrip `WriteTagfile` shape). Valid only where a literal can begin an
|
|
expression, so a relational `<` (which always *follows* an expression)
|
|
never collides. The scanner never skips a leading newline (it must remain
|
|
available as a statement terminator).
|
|
|
|
The scanner is stateless (serialize/deserialize are no-ops).
|
|
|
|
The `_eof` hack upstream (a literal-`$` token) cannot match a real
|
|
end-of-file, so files whose last line has no trailing newline would end with a
|
|
MISSING-newline error; the extractor's `preParse` appends a trailing newline
|
|
instead of patching that in the grammar.
|
|
|
|
## Measured parse health (at vendoring time)
|
|
|
|
| Corpus | Clean parses |
|
|
|---|---|
|
|
| Fleex255/PolicyPlus (94 `.vb`) | 94/94 (100%) — upstream: 3/94 |
|
|
| IridiumIO/CompactGUI (66) | 66/66 (100%) — upstream: 12/66 |
|
|
| staxrip/staxrip (145) | 138/145 (95.2%) — upstream: 22/145 |
|
|
| AAndyProgram/SCrawler (320) | 279/320 (87.2%) |
|
|
| Meloong-Git/PCL (112, Chinese identifiers) | 98/112 (87.5%) |
|
|
|
|
Known remaining gap (localized ERROR regions, deliberately unpatched):
|
|
|
|
- **Column-0 GoTo labels** (`Recheck:` at the start of a line inside indented
|
|
code — the classic VB label style, used heavily in PCL). The `word:`
|
|
keyword-extraction token interacts badly with a newline immediately followed
|
|
by a word at column 0, consuming the newline and dropping the previous
|
|
statement's terminator. Removing `word:` fixes labels but reintroduces
|
|
keyword-prefix identifier bugs corpus-wide (measured: staxrip 95%→28%), so
|
|
`word:` stays and column-0 labels keep a localized error; indented labels
|
|
parse fine. Worth an upstream tree-sitter investigation eventually.
|
|
|
|
## Rebuild
|
|
|
|
```bash
|
|
git clone https://github.com/govindbanura/tree-sitter-vbnet
|
|
cd tree-sitter-vbnet
|
|
git checkout 538b7087bf80e86004531b392fe1186379c0a2b5
|
|
git apply path/to/tree-sitter-vbnet.patch # patches grammar.js, adds src/scanner.c
|
|
# tree-sitter needs a tree-sitter.json (upstream ships none); grammar name is
|
|
# `vbnet` (C symbols tree_sitter_vbnet*):
|
|
cat > tree-sitter.json <<'JSON'
|
|
{
|
|
"grammars": [
|
|
{ "name": "vbnet", "camelcase": "Vbnet", "scope": "source.vbnet",
|
|
"path": ".", "file-types": ["vb"] }
|
|
],
|
|
"metadata": { "version": "0.1.0", "license": "MIT",
|
|
"description": "VB.NET grammar for tree-sitter",
|
|
"links": { "repository": "https://github.com/govindbanura/tree-sitter-vbnet" } }
|
|
}
|
|
JSON
|
|
npm install tree-sitter-cli@0.25.10 # ≥0.25 REQUIRED: the /u regex flag (Unicode
|
|
# identifiers) is dropped silently by 0.24.x
|
|
npx tree-sitter generate # src/scanner.c from the patch is picked up
|
|
npx tree-sitter build --wasm -o tree-sitter-vbnet.wasm # needs emscripten or Docker
|
|
```
|
|
|
|
Upstream's checked-in `test/corpus` expectations predate its own grammar.js
|
|
(every corpus test fails at the pinned commit, before any patching), so the
|
|
five-repo parse-health sweep above — plus 16 construct repros and the
|
|
`__tests__/extraction.test.ts` VB.NET block — is the regression baseline.
|
|
|
|
## Upstreaming
|
|
|
|
Not yet sent. The patch is one large, coherent "parse real-world VB.NET"
|
|
change; if upstream shows signs of life it can be offered as a PR the same way
|
|
the COBOL patch was ([tree-sitter-cobol#41](https://github.com/yutaro-sakamoto/tree-sitter-cobol/pull/41)),
|
|
with the corpus numbers above as the motivation. Until then,
|
|
`git apply tree-sitter-vbnet.patch` on upstream commit `538b708` reproduces
|
|
the vendored grammar exactly.
|