mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-15 13:56:34 +08:00
Merge pull request #2948 from DeusData/feat/vendor-vbnet-grammar
chore(vendor): vendor the VB.NET tree-sitter grammar
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
[](https://github.com/DeusData/codebase-memory-mcp/actions/workflows/release.yml)
|
||||
[](#tests)
|
||||
[](#mcp-tools)
|
||||
[](https://github.com/DeusData/codebase-memory-mcp)
|
||||
[](https://github.com/DeusData/codebase-memory-mcp)
|
||||
[](#hybrid-lsp)
|
||||
[](https://github.com/DeusData/codebase-memory-mcp)
|
||||
[](https://github.com/DeusData/codebase-memory-mcp)
|
||||
@@ -398,7 +398,7 @@ In this repository, test narrowing is wired to the Linux x86_64 test leg when `T
|
||||
- `SEMANTICALLY_RELATED` (vocabulary-mismatch, same-language, score ≥ 0.80)
|
||||
|
||||
### Indexing pipeline
|
||||
- **162 vendored tree-sitter grammars** compiled into the binary
|
||||
- **163 vendored tree-sitter grammars** compiled into the binary
|
||||
- **Generic package / module resolution**: bare specifiers like `@myorg/pkg`, `github.com/foo/bar`, `use my_crate::foo` resolved via manifest scanning (`package.json`, `go.mod`, `Cargo.toml`, `pyproject.toml`, `composer.json`, `pubspec.yaml`, `pom.xml`, `build.gradle`, `mix.exs`, `*.gemspec`)
|
||||
- **Infrastructure-as-code indexing**: Dockerfiles (`Dockerfile.*`, `*.Dockerfile`, `Containerfile`: bases, what the container runs, `ENV`), container images (`Image`: the normalised repository, tag and digest on the edges; a repository built from several Dockerfiles, one per component tag, gets an `Image` per component), docker compose services (`Resource`), Kubernetes manifests (`Resource`, with `group`, `version`, `kind`, `namespace`, `labels` as `k=v,k2=v2`), Kustomize overlays (`IMPORTS` to their entries and patch files, `OVERRIDE` from their patches), Helm charts (`Chart`, `DEPENDS_ON`), Terraform / OpenTofu (`.tf`, `.tofu`, `.tfvars`) as graph nodes. A Service walks to the code it runs: Service → workload → `Image` → Dockerfile → `main`
|
||||
- **[Hybrid LSP semantic type resolution](#hybrid-lsp)** for Python, TypeScript / JavaScript / JSX / TSX, PHP, C#, Go, C, C++, Java, Kotlin, Rust, and Perl; a lightweight C implementation of language type-resolution algorithms, structurally inspired by and compatible with major language servers including tsserver / typescript-go, pyright, gopls, Roslyn, Eclipse JDT, and rust-analyzer (parameter binding, return-type inference, generic substitution, JSX component dispatch, JSDoc inference for plain JS files, namespace + trait + late-static-binding resolution for PHP, file-scoped namespaces + records + LINQ method syntax for C#, class-hierarchy + overload + lambda resolution for Java, extension-function + scope-function resolution for Kotlin, trait-method + UFCS resolution for Rust)
|
||||
@@ -1124,14 +1124,14 @@ codebase-memory-mcp ships a **lightweight C implementation of language type-reso
|
||||
|
||||
**Two-layer architecture:**
|
||||
|
||||
1. **Tree-sitter pass**: fast, syntactic, runs for every one of the 162 languages. Extracts definitions, calls, imports.
|
||||
1. **Tree-sitter pass**: fast, syntactic, runs for every one of the 163 languages. Extracts definitions, calls, imports.
|
||||
2. **Hybrid LSP pass**: type-aware, runs above the tree-sitter pass per-language. Refines call edges using the import graph plus a per-file or pre-built cross-file definition registry. Languages without a Hybrid LSP pass yet fall back to textual resolution, with coverage varying by language and construct.
|
||||
|
||||
The result is a knowledge graph accurate enough to drive `trace_path` across packages, inheritance hierarchies, and stdlib calls; without paying for a language server process per project.
|
||||
|
||||
## Language Support
|
||||
|
||||
162 languages, counted by the vendored grammar directories in [the parser manifest](internal/cbm/vendored/grammars/MANIFEST.md). Benchmarked against 64 real open-source repositories (78 to 49K nodes):
|
||||
163 languages, counted by the vendored grammar directories in [the parser manifest](internal/cbm/vendored/grammars/MANIFEST.md). Benchmarked against 64 real open-source repositories (78 to 49K nodes):
|
||||
|
||||
| Tier | Score | Languages |
|
||||
|------|-------|-----------|
|
||||
@@ -1158,7 +1158,7 @@ src/
|
||||
ui/ Local HTTP server + embedded CBM Lens frontend
|
||||
foundation/ Platform abstractions (threads, filesystem, logging, memory)
|
||||
graph-ui/ CodeAtlasWeb: React, Monaco, and Three.js browser frontend
|
||||
internal/cbm/ Vendored tree-sitter grammars (162 languages) + AST extraction engine
|
||||
internal/cbm/ Vendored tree-sitter grammars (163 languages) + AST extraction engine
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
+26
-4
@@ -37,7 +37,7 @@ The core runtime headers in `internal/cbm/vendored/common/tree_sitter/`
|
||||
|
||||
## Tree-sitter Grammars
|
||||
|
||||
160 pre-generated parsers are vendored in `internal/cbm/vendored/grammars/<lang>/`
|
||||
163 pre-generated parsers are vendored in `internal/cbm/vendored/grammars/<lang>/`
|
||||
(generated `parser.c` plus `scanner.c` where applicable, compiled statically).
|
||||
Each grammar is the work of its upstream authors and each grammar directory
|
||||
contains the upstream `LICENSE` file.
|
||||
@@ -60,8 +60,8 @@ License summary:
|
||||
smart-coin language, written for this project because no usable public
|
||||
grammar exists; its source and corpus tests live in
|
||||
`tools/tree-sitter-chialisp/`.
|
||||
- Ten further grammars (`arkts`, `assembly`, `cfml`,
|
||||
`cfscript`, `dotenv`, `javascript`, `pascal`, `pine`, `qml`, `tsx`) are
|
||||
- Eleven further grammars (`arkts`, `assembly`, `cfml`,
|
||||
`cfscript`, `dotenv`, `javascript`, `pascal`, `pine`, `qml`, `tsx`, `vbnet`) are
|
||||
self-maintained forks that retain their original upstream authors' licenses —
|
||||
see the manifest for per-grammar provenance. `arkts` is a first-party derivative of
|
||||
[tree-sitter/tree-sitter-typescript](https://github.com/tree-sitter/tree-sitter-typescript)
|
||||
@@ -79,7 +79,29 @@ License summary:
|
||||
([Isopod/tree-sitter-pascal](https://github.com/Isopod/tree-sitter-pascal),
|
||||
MIT, (c) 2018 Benjamin Gray) is the upstream grammar with one (c) 2026
|
||||
DeusData patch that accepts non-ASCII letters in identifiers; its grammar
|
||||
source lives in `tools/tree-sitter-pascal/`.
|
||||
source lives in `tools/tree-sitter-pascal/`. `vbnet` is described in its own
|
||||
section below.
|
||||
|
||||
### tree-sitter-vbnet (with CodeGraph's real-world VB.NET patch)
|
||||
|
||||
- **Project:** [govindbanura/tree-sitter-vbnet](https://github.com/govindbanura/tree-sitter-vbnet)
|
||||
- **License:** MIT
|
||||
- **Copyright:** (c) 2025 Govind Banura
|
||||
- **Patch:** `docs/grammars/tree-sitter-vbnet.patch` from
|
||||
[colbymchenry/codegraph](https://github.com/colbymchenry/codegraph)
|
||||
(CodeGraph), MIT, (c) 2026 Colby Mchenry. The patch rewrites much of the
|
||||
grammar and adds the external scanner (`scanner.c`), so CodeGraph's license
|
||||
text ships next to the code as `LICENSE-codegraph`.
|
||||
- **Vendored at:** `internal/cbm/vendored/grammars/vbnet/`
|
||||
- **Pinned commits:** grammar `538b7087bf80` (v0.1-beta); patch `fe7542b6f6a4`
|
||||
- **Notes:** Visual Basic .NET grammar, maintained here as a self-maintained
|
||||
fork: the grammar source, the verbatim patch and the regeneration recipe live
|
||||
in `tools/tree-sitter-vbnet/`, and the parser is generated in this repository
|
||||
with tree-sitter-cli 0.25.10 (upstream checks in no generated sources). One
|
||||
local patch of ours, (c) 2026 DeusData, makes the scanner stop at text that
|
||||
cannot be XML (`tools/tree-sitter-vbnet/patches/xml-literal-tag-syntax.patch`).
|
||||
The grammar is vendored ahead of VB.NET extraction (#1179); provenance hashes
|
||||
are in `internal/cbm/vendored/grammars/MANIFEST.md`.
|
||||
|
||||
### tree-sitter-plsql
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Vendored tree-sitter grammar: vbnet
|
||||
// Each grammar compiled as separate unit (conflicting static symbols).
|
||||
#include "vendored/grammars/vbnet/parser.c"
|
||||
#include "vendored/grammars/vbnet/scanner.c"
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Govind Banura
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Colby Mchenry
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+976623
File diff suppressed because it is too large
Load Diff
+326
@@ -0,0 +1,326 @@
|
||||
#include "tree_sitter/parser.h"
|
||||
#include <string.h>
|
||||
#include <wctype.h>
|
||||
|
||||
// External tokens for the two constructs that are not LR(1)-parseable with
|
||||
// tree-sitter's newline-as-extra treatment:
|
||||
//
|
||||
// 1. QUERY_CLAUSE_CONTINUATION — a newline run that CONTINUES a LINQ query
|
||||
// (`From x In xs` ↵ `Where …`). At a clause boundary the parser cannot
|
||||
// know from the newline alone whether the query ends (newline = statement
|
||||
// terminator) or continues (newline = insignificant, next line starts with
|
||||
// a query keyword). The scanner looks past the newline at the next word
|
||||
// and only emits the token when it is a query-clause keyword — so the
|
||||
// terminator/continuation choice is made by the lexer, not the LR table.
|
||||
//
|
||||
// 2. XML_LITERAL — a whole VB XML literal (`<Tags><Tag/></Tags>`, including
|
||||
// `<%= expr %>` embedded expressions, comments, CDATA) consumed as one
|
||||
// opaque token. Only valid where a literal can begin an expression, so a
|
||||
// relational `<` (which always FOLLOWS an expression) can never collide.
|
||||
|
||||
enum TokenType {
|
||||
QUERY_CLAUSE_CONTINUATION,
|
||||
XML_LITERAL,
|
||||
};
|
||||
|
||||
void *tree_sitter_vbnet_external_scanner_create(void) { return NULL; }
|
||||
void tree_sitter_vbnet_external_scanner_destroy(void *payload) { (void)payload; }
|
||||
unsigned tree_sitter_vbnet_external_scanner_serialize(void *payload, char *buffer) {
|
||||
(void)payload; (void)buffer;
|
||||
return 0; // stateless
|
||||
}
|
||||
void tree_sitter_vbnet_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {
|
||||
(void)payload; (void)buffer; (void)length;
|
||||
}
|
||||
|
||||
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
|
||||
static inline void skip_ws(TSLexer *lexer) { lexer->advance(lexer, true); }
|
||||
|
||||
// Read one alphabetic word (advancing past it), lowercased into buf.
|
||||
// Returns its length (0 if the next char is not a letter).
|
||||
static int read_word(TSLexer *lexer, char *buf, int cap) {
|
||||
int n = 0;
|
||||
while (iswalpha((wint_t)lexer->lookahead)) {
|
||||
if (n < cap - 1) buf[n] = (char)towlower((wint_t)lexer->lookahead);
|
||||
n++;
|
||||
advance(lexer);
|
||||
}
|
||||
buf[n < cap - 1 ? n : cap - 1] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool is_query_keyword(const char *w) {
|
||||
static const char *kws[] = {
|
||||
"where", "select", "order", "group", "join", "let", "skip",
|
||||
"take", "distinct", "aggregate", "from", "into", "on", NULL,
|
||||
};
|
||||
for (int i = 0; kws[i]; i++)
|
||||
if (strcmp(w, kws[i]) == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// [ \t\r]* '\n' [ \t\r\n]* followed by a query-clause keyword.
|
||||
// The token covers only the whitespace/newline run (mark_end before the
|
||||
// keyword lookahead), so the clause keyword itself lexes normally after it.
|
||||
static bool scan_query_continuation(TSLexer *lexer) {
|
||||
bool saw_newline = false;
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == ' ' || c == '\t' || c == '\r') {
|
||||
advance(lexer);
|
||||
} else if (c == '\n') {
|
||||
saw_newline = true;
|
||||
advance(lexer);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!saw_newline) return false;
|
||||
lexer->mark_end(lexer);
|
||||
|
||||
char w[12];
|
||||
int n = read_word(lexer, w, sizeof w);
|
||||
if (n == 0 || n >= (int)(sizeof w)) return false;
|
||||
if (!is_query_keyword(w)) return false;
|
||||
|
||||
// `Select Case` opens a statement, never a select clause.
|
||||
if (strcmp(w, "select") == 0) {
|
||||
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') advance(lexer);
|
||||
char w2[8];
|
||||
int m = read_word(lexer, w2, sizeof w2);
|
||||
if (m > 0 && m < (int)(sizeof w2) && strcmp(w2, "case") == 0) return false;
|
||||
}
|
||||
|
||||
lexer->result_symbol = QUERY_CLAUSE_CONTINUATION;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consume a VB string inside an embedded `<%= … %>` region ("" escapes).
|
||||
static void consume_vb_string(TSLexer *lexer) {
|
||||
advance(lexer); // opening quote
|
||||
for (;;) {
|
||||
if (lexer->lookahead == 0) return;
|
||||
if (lexer->lookahead == '"') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead != '"') return; // closing (not an escaped "")
|
||||
// escaped quote: fall through, keep consuming
|
||||
}
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
// Consume `<%= … %>` starting at the '%' (the '<' is already consumed).
|
||||
// Embedded expressions NEST (`<%= From t In ts Select <Tag><%= t.Name %></Tag> %>`,
|
||||
// the staxrip WriteTagfile shape), so track <% / %> depth.
|
||||
static bool consume_embedded_expression(TSLexer *lexer) {
|
||||
advance(lexer); // '%'
|
||||
int depth = 1;
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == '"') { consume_vb_string(lexer); continue; }
|
||||
if (c == '<') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '%') { depth++; advance(lexer); }
|
||||
continue;
|
||||
}
|
||||
if (c == '%') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '>') {
|
||||
advance(lexer);
|
||||
if (--depth == 0) return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
// Consume `<!-- … -->` / `<![CDATA[ … ]]>` / `<!DOCTYPE …>` starting at '!'.
|
||||
static bool consume_bang_construct(TSLexer *lexer) {
|
||||
advance(lexer); // '!'
|
||||
if (lexer->lookahead == '-') {
|
||||
// comment: to -->
|
||||
int dashes = 0;
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == '-') { dashes++; advance(lexer); continue; }
|
||||
if (c == '>' && dashes >= 2) { advance(lexer); return true; }
|
||||
dashes = 0;
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
if (lexer->lookahead == '[') {
|
||||
// CDATA: to ]]>
|
||||
int brackets = 0;
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == ']') { brackets++; advance(lexer); continue; }
|
||||
if (c == '>' && brackets >= 2) { advance(lexer); return true; }
|
||||
brackets = 0;
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
// DOCTYPE-ish: to bare >
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == '>') { advance(lexer); return true; }
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
// Consume `<? … ?>` starting at the '?'.
|
||||
static bool consume_processing_instruction(TSLexer *lexer) {
|
||||
advance(lexer); // '?'
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == '?') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '>') { advance(lexer); return true; }
|
||||
continue;
|
||||
}
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_name_start(int32_t c) {
|
||||
return iswalpha((wint_t)c) || c == '_';
|
||||
}
|
||||
|
||||
// cbm: what may appear inside an element tag outside a quoted value: name
|
||||
// characters, blanks and '='. Anything else is not XML, so the scan stops
|
||||
// there. A '<' where a declaration or member starts opens a VB attribute
|
||||
// (`<Serializable()> _`); without this check the scan took `<Serializable`
|
||||
// for an element and read the rest of the file looking for its closing tag,
|
||||
// once per attribute.
|
||||
static bool is_tag_char(int32_t c) {
|
||||
return iswalnum((wint_t)c) || c == '_' || c == '-' || c == '.' || c == ':' ||
|
||||
c == '=' || c == ' ' || c == '\t' || c == '\r' || c == '\n' || c >= 0x80;
|
||||
}
|
||||
|
||||
static bool scan_xml_literal(TSLexer *lexer) {
|
||||
// Only same-line blanks are skipped: a leading newline must stay unconsumed
|
||||
// so it can serve as a statement/block terminator (a successful scan would
|
||||
// otherwise swallow it as trivia). XML on a continuation line still works:
|
||||
// the newline is consumed as an extra first, then the scanner re-runs.
|
||||
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') skip_ws(lexer);
|
||||
if (lexer->lookahead != '<') return false;
|
||||
advance(lexer);
|
||||
// Only element-start markup opens an XML literal here; comments/PIs as the
|
||||
// outermost construct (`Dim d = <?xml …`) are rare enough to leave alone,
|
||||
// and a bare `<` (comparison) never begins an expression.
|
||||
if (!is_name_start(lexer->lookahead)) return false;
|
||||
|
||||
int depth = 0; // open (unclosed) elements
|
||||
bool in_tag = true; // inside <...> of an element tag
|
||||
bool closing = false;
|
||||
int32_t quote = 0;
|
||||
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false; // unterminated: let the normal parser error
|
||||
|
||||
if (in_tag) {
|
||||
if (quote) {
|
||||
if (c == quote) quote = 0;
|
||||
advance(lexer);
|
||||
continue;
|
||||
}
|
||||
if (c == '"' || c == '\'') { quote = c; advance(lexer); continue; }
|
||||
if (c == '<') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '%') {
|
||||
// attribute value embedded expression: attr=<%= x %>
|
||||
if (!consume_embedded_expression(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
return false; // cbm: a stray < in a tag is not XML
|
||||
}
|
||||
if (c == '/') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '>') {
|
||||
// self-closing tag
|
||||
advance(lexer);
|
||||
in_tag = false;
|
||||
if (depth == 0) break; // single self-closed root: done
|
||||
continue;
|
||||
}
|
||||
return false; // cbm: '/' in a tag only closes it
|
||||
}
|
||||
if (c == '>') {
|
||||
advance(lexer);
|
||||
if (closing) {
|
||||
depth--;
|
||||
if (depth <= 0) break; // root element closed: done
|
||||
} else {
|
||||
depth++;
|
||||
}
|
||||
in_tag = false;
|
||||
continue;
|
||||
}
|
||||
if (!is_tag_char(c)) return false; // cbm: e.g. the '(' of `<Obsolete("x")>`
|
||||
advance(lexer);
|
||||
continue;
|
||||
}
|
||||
|
||||
// text content between tags
|
||||
if (c == '<') {
|
||||
advance(lexer);
|
||||
int32_t d = lexer->lookahead;
|
||||
if (d == '/') { advance(lexer); closing = true; in_tag = true; continue; }
|
||||
if (d == '%') {
|
||||
if (!consume_embedded_expression(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
if (d == '!') {
|
||||
if (!consume_bang_construct(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
if (d == '?') {
|
||||
if (!consume_processing_instruction(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
// cbm: otherwise only an element name (or `<<%= %>`) may follow; a
|
||||
// comparison such as `x < y` in code after an attribute is not XML.
|
||||
if (!is_name_start(d) && d != '<' && d < 0x80) return false;
|
||||
closing = false;
|
||||
in_tag = true; // opening tag
|
||||
continue;
|
||||
}
|
||||
if (c == '&') {
|
||||
// cbm: '&' in XML text starts a reference (`&`, `&`); VB's
|
||||
// concatenation `a & b` in code after an attribute is not XML.
|
||||
advance(lexer);
|
||||
int32_t d = lexer->lookahead;
|
||||
if (d != '#' && !is_name_start(d) && d < 0x80) return false;
|
||||
continue;
|
||||
}
|
||||
advance(lexer);
|
||||
}
|
||||
|
||||
lexer->mark_end(lexer);
|
||||
lexer->result_symbol = XML_LITERAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tree_sitter_vbnet_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
|
||||
(void)payload;
|
||||
int32_t c = lexer->lookahead;
|
||||
bool ws = (c == ' ' || c == '\t' || c == '\n' || c == '\r');
|
||||
|
||||
// A scan attempt advances the lexer, so exactly ONE attempt runs per
|
||||
// invocation — never chain a second attempt after a failed first (its
|
||||
// reads would start mid-stream). The leading character picks the branch;
|
||||
// a false return discards the attempt entirely.
|
||||
if (valid_symbols[QUERY_CLAUSE_CONTINUATION] && ws) {
|
||||
return scan_query_continuation(lexer);
|
||||
}
|
||||
if (valid_symbols[XML_LITERAL] && (c == '<' || ws)) {
|
||||
return scan_xml_literal(lexer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef TREE_SITTER_ALLOC_H_
|
||||
#define TREE_SITTER_ALLOC_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Allow clients to override allocation functions
|
||||
#ifdef TREE_SITTER_REUSE_ALLOCATOR
|
||||
|
||||
extern void *(*ts_current_malloc)(size_t size);
|
||||
extern void *(*ts_current_calloc)(size_t count, size_t size);
|
||||
extern void *(*ts_current_realloc)(void *ptr, size_t size);
|
||||
extern void (*ts_current_free)(void *ptr);
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc ts_current_malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc ts_current_calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc ts_current_realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free ts_current_free
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free free
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ALLOC_H_
|
||||
@@ -0,0 +1,291 @@
|
||||
#ifndef TREE_SITTER_ARRAY_H_
|
||||
#define TREE_SITTER_ARRAY_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "./alloc.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4101)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
|
||||
#define Array(T) \
|
||||
struct { \
|
||||
T *contents; \
|
||||
uint32_t size; \
|
||||
uint32_t capacity; \
|
||||
}
|
||||
|
||||
/// Initialize an array.
|
||||
#define array_init(self) \
|
||||
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
|
||||
|
||||
/// Create an empty array.
|
||||
#define array_new() \
|
||||
{ NULL, 0, 0 }
|
||||
|
||||
/// Get a pointer to the element at a given `index` in the array.
|
||||
#define array_get(self, _index) \
|
||||
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
|
||||
|
||||
/// Get a pointer to the first element in the array.
|
||||
#define array_front(self) array_get(self, 0)
|
||||
|
||||
/// Get a pointer to the last element in the array.
|
||||
#define array_back(self) array_get(self, (self)->size - 1)
|
||||
|
||||
/// Clear the array, setting its size to zero. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_clear(self) ((self)->size = 0)
|
||||
|
||||
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
|
||||
/// less than the array's current capacity, this function has no effect.
|
||||
#define array_reserve(self, new_capacity) \
|
||||
_array__reserve((Array *)(self), array_elem_size(self), new_capacity)
|
||||
|
||||
/// Free any memory allocated for this array. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_delete(self) _array__delete((Array *)(self))
|
||||
|
||||
/// Push a new `element` onto the end of the array.
|
||||
#define array_push(self, element) \
|
||||
(_array__grow((Array *)(self), 1, array_elem_size(self)), \
|
||||
(self)->contents[(self)->size++] = (element))
|
||||
|
||||
/// Increase the array's size by `count` elements.
|
||||
/// New elements are zero-initialized.
|
||||
#define array_grow_by(self, count) \
|
||||
do { \
|
||||
if ((count) == 0) break; \
|
||||
_array__grow((Array *)(self), count, array_elem_size(self)); \
|
||||
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
|
||||
(self)->size += (count); \
|
||||
} while (0)
|
||||
|
||||
/// Append all elements from one array to the end of another.
|
||||
#define array_push_all(self, other) \
|
||||
array_extend((self), (other)->size, (other)->contents)
|
||||
|
||||
/// Append `count` elements to the end of the array, reading their values from the
|
||||
/// `contents` pointer.
|
||||
#define array_extend(self, count, contents) \
|
||||
_array__splice( \
|
||||
(Array *)(self), array_elem_size(self), (self)->size, \
|
||||
0, count, contents \
|
||||
)
|
||||
|
||||
/// Remove `old_count` elements from the array starting at the given `index`. At
|
||||
/// the same index, insert `new_count` new elements, reading their values from the
|
||||
/// `new_contents` pointer.
|
||||
#define array_splice(self, _index, old_count, new_count, new_contents) \
|
||||
_array__splice( \
|
||||
(Array *)(self), array_elem_size(self), _index, \
|
||||
old_count, new_count, new_contents \
|
||||
)
|
||||
|
||||
/// Insert one `element` into the array at the given `index`.
|
||||
#define array_insert(self, _index, element) \
|
||||
_array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
|
||||
|
||||
/// Remove one element from the array at the given `index`.
|
||||
#define array_erase(self, _index) \
|
||||
_array__erase((Array *)(self), array_elem_size(self), _index)
|
||||
|
||||
/// Pop the last element off the array, returning the element by value.
|
||||
#define array_pop(self) ((self)->contents[--(self)->size])
|
||||
|
||||
/// Assign the contents of one array to another, reallocating if necessary.
|
||||
#define array_assign(self, other) \
|
||||
_array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
|
||||
|
||||
/// Swap one array with another
|
||||
#define array_swap(self, other) \
|
||||
_array__swap((Array *)(self), (Array *)(other))
|
||||
|
||||
/// Get the size of the array contents
|
||||
#define array_elem_size(self) (sizeof *(self)->contents)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
///
|
||||
/// If an existing element is found to be equal to `needle`, then the `index`
|
||||
/// out-parameter is set to the existing value's index, and the `exists`
|
||||
/// out-parameter is set to true. Otherwise, `index` is set to an index where
|
||||
/// `needle` should be inserted in order to preserve the sorting, and `exists`
|
||||
/// is set to false.
|
||||
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using integer comparisons
|
||||
/// of a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_with`.
|
||||
#define array_search_sorted_by(self, field, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
#define array_insert_sorted_with(self, compare, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using integer comparisons of
|
||||
/// a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_by`.
|
||||
#define array_insert_sorted_by(self, field, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
// Private
|
||||
|
||||
typedef Array(void) Array;
|
||||
|
||||
/// This is not what you're looking for, see `array_delete`.
|
||||
static inline void _array__delete(Array *self) {
|
||||
if (self->contents) {
|
||||
ts_free(self->contents);
|
||||
self->contents = NULL;
|
||||
self->size = 0;
|
||||
self->capacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_erase`.
|
||||
static inline void _array__erase(Array *self, size_t element_size,
|
||||
uint32_t index) {
|
||||
assert(index < self->size);
|
||||
char *contents = (char *)self->contents;
|
||||
memmove(contents + index * element_size, contents + (index + 1) * element_size,
|
||||
(self->size - index - 1) * element_size);
|
||||
self->size--;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_reserve`.
|
||||
static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
|
||||
if (new_capacity > self->capacity) {
|
||||
if (self->contents) {
|
||||
self->contents = ts_realloc(self->contents, new_capacity * element_size);
|
||||
} else {
|
||||
self->contents = ts_malloc(new_capacity * element_size);
|
||||
}
|
||||
self->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_assign`.
|
||||
static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
|
||||
_array__reserve(self, element_size, other->size);
|
||||
self->size = other->size;
|
||||
memcpy(self->contents, other->contents, self->size * element_size);
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_swap`.
|
||||
static inline void _array__swap(Array *self, Array *other) {
|
||||
Array swap = *other;
|
||||
*other = *self;
|
||||
*self = swap;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
|
||||
static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
|
||||
uint32_t new_size = self->size + count;
|
||||
if (new_size > self->capacity) {
|
||||
uint32_t new_capacity = self->capacity * 2;
|
||||
if (new_capacity < 8) new_capacity = 8;
|
||||
if (new_capacity < new_size) new_capacity = new_size;
|
||||
_array__reserve(self, element_size, new_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_splice`.
|
||||
static inline void _array__splice(Array *self, size_t element_size,
|
||||
uint32_t index, uint32_t old_count,
|
||||
uint32_t new_count, const void *elements) {
|
||||
uint32_t new_size = self->size + new_count - old_count;
|
||||
uint32_t old_end = index + old_count;
|
||||
uint32_t new_end = index + new_count;
|
||||
assert(old_end <= self->size);
|
||||
|
||||
_array__reserve(self, element_size, new_size);
|
||||
|
||||
char *contents = (char *)self->contents;
|
||||
if (self->size > old_end) {
|
||||
memmove(
|
||||
contents + new_end * element_size,
|
||||
contents + old_end * element_size,
|
||||
(self->size - old_end) * element_size
|
||||
);
|
||||
}
|
||||
if (new_count > 0) {
|
||||
if (elements) {
|
||||
memcpy(
|
||||
(contents + index * element_size),
|
||||
elements,
|
||||
new_count * element_size
|
||||
);
|
||||
} else {
|
||||
memset(
|
||||
(contents + index * element_size),
|
||||
0,
|
||||
new_count * element_size
|
||||
);
|
||||
}
|
||||
}
|
||||
self->size += new_count - old_count;
|
||||
}
|
||||
|
||||
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
|
||||
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
|
||||
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
|
||||
do { \
|
||||
*(_index) = start; \
|
||||
*(_exists) = false; \
|
||||
uint32_t size = (self)->size - *(_index); \
|
||||
if (size == 0) break; \
|
||||
int comparison; \
|
||||
while (size > 1) { \
|
||||
uint32_t half_size = size / 2; \
|
||||
uint32_t mid_index = *(_index) + half_size; \
|
||||
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
|
||||
if (comparison <= 0) *(_index) = mid_index; \
|
||||
size -= half_size; \
|
||||
} \
|
||||
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
|
||||
if (comparison == 0) *(_exists) = true; \
|
||||
else if (comparison < 0) *(_index) += 1; \
|
||||
} while (0)
|
||||
|
||||
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
|
||||
/// parameter by reference in order to work with the generic sorting function above.
|
||||
#define _compare_int(a, b) ((int)*(a) - (int)(b))
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ARRAY_H_
|
||||
@@ -0,0 +1,286 @@
|
||||
#ifndef TREE_SITTER_PARSER_H_
|
||||
#define TREE_SITTER_PARSER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ts_builtin_sym_error ((TSSymbol)-1)
|
||||
#define ts_builtin_sym_end 0
|
||||
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
|
||||
|
||||
#ifndef TREE_SITTER_API_H_
|
||||
typedef uint16_t TSStateId;
|
||||
typedef uint16_t TSSymbol;
|
||||
typedef uint16_t TSFieldId;
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
typedef struct TSLanguageMetadata {
|
||||
uint8_t major_version;
|
||||
uint8_t minor_version;
|
||||
uint8_t patch_version;
|
||||
} TSLanguageMetadata;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
TSFieldId field_id;
|
||||
uint8_t child_index;
|
||||
bool inherited;
|
||||
} TSFieldMapEntry;
|
||||
|
||||
// Used to index the field and supertype maps.
|
||||
typedef struct {
|
||||
uint16_t index;
|
||||
uint16_t length;
|
||||
} TSMapSlice;
|
||||
|
||||
typedef struct {
|
||||
bool visible;
|
||||
bool named;
|
||||
bool supertype;
|
||||
} TSSymbolMetadata;
|
||||
|
||||
typedef struct TSLexer TSLexer;
|
||||
|
||||
struct TSLexer {
|
||||
int32_t lookahead;
|
||||
TSSymbol result_symbol;
|
||||
void (*advance)(TSLexer *, bool);
|
||||
void (*mark_end)(TSLexer *);
|
||||
uint32_t (*get_column)(TSLexer *);
|
||||
bool (*is_at_included_range_start)(const TSLexer *);
|
||||
bool (*eof)(const TSLexer *);
|
||||
void (*log)(const TSLexer *, const char *, ...);
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TSParseActionTypeShift,
|
||||
TSParseActionTypeReduce,
|
||||
TSParseActionTypeAccept,
|
||||
TSParseActionTypeRecover,
|
||||
} TSParseActionType;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t type;
|
||||
TSStateId state;
|
||||
bool extra;
|
||||
bool repetition;
|
||||
} shift;
|
||||
struct {
|
||||
uint8_t type;
|
||||
uint8_t child_count;
|
||||
TSSymbol symbol;
|
||||
int16_t dynamic_precedence;
|
||||
uint16_t production_id;
|
||||
} reduce;
|
||||
uint8_t type;
|
||||
} TSParseAction;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
} TSLexMode;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
uint16_t reserved_word_set_id;
|
||||
} TSLexerMode;
|
||||
|
||||
typedef union {
|
||||
TSParseAction action;
|
||||
struct {
|
||||
uint8_t count;
|
||||
bool reusable;
|
||||
} entry;
|
||||
} TSParseActionEntry;
|
||||
|
||||
typedef struct {
|
||||
int32_t start;
|
||||
int32_t end;
|
||||
} TSCharacterRange;
|
||||
|
||||
struct TSLanguage {
|
||||
uint32_t abi_version;
|
||||
uint32_t symbol_count;
|
||||
uint32_t alias_count;
|
||||
uint32_t token_count;
|
||||
uint32_t external_token_count;
|
||||
uint32_t state_count;
|
||||
uint32_t large_state_count;
|
||||
uint32_t production_id_count;
|
||||
uint32_t field_count;
|
||||
uint16_t max_alias_sequence_length;
|
||||
const uint16_t *parse_table;
|
||||
const uint16_t *small_parse_table;
|
||||
const uint32_t *small_parse_table_map;
|
||||
const TSParseActionEntry *parse_actions;
|
||||
const char * const *symbol_names;
|
||||
const char * const *field_names;
|
||||
const TSMapSlice *field_map_slices;
|
||||
const TSFieldMapEntry *field_map_entries;
|
||||
const TSSymbolMetadata *symbol_metadata;
|
||||
const TSSymbol *public_symbol_map;
|
||||
const uint16_t *alias_map;
|
||||
const TSSymbol *alias_sequences;
|
||||
const TSLexerMode *lex_modes;
|
||||
bool (*lex_fn)(TSLexer *, TSStateId);
|
||||
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
|
||||
TSSymbol keyword_capture_token;
|
||||
struct {
|
||||
const bool *states;
|
||||
const TSSymbol *symbol_map;
|
||||
void *(*create)(void);
|
||||
void (*destroy)(void *);
|
||||
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
|
||||
unsigned (*serialize)(void *, char *);
|
||||
void (*deserialize)(void *, const char *, unsigned);
|
||||
} external_scanner;
|
||||
const TSStateId *primary_state_ids;
|
||||
const char *name;
|
||||
const TSSymbol *reserved_words;
|
||||
uint16_t max_reserved_word_set_size;
|
||||
uint32_t supertype_count;
|
||||
const TSSymbol *supertype_symbols;
|
||||
const TSMapSlice *supertype_map_slices;
|
||||
const TSSymbol *supertype_map_entries;
|
||||
TSLanguageMetadata metadata;
|
||||
};
|
||||
|
||||
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
|
||||
uint32_t index = 0;
|
||||
uint32_t size = len - index;
|
||||
while (size > 1) {
|
||||
uint32_t half_size = size / 2;
|
||||
uint32_t mid_index = index + half_size;
|
||||
const TSCharacterRange *range = &ranges[mid_index];
|
||||
if (lookahead >= range->start && lookahead <= range->end) {
|
||||
return true;
|
||||
} else if (lookahead > range->end) {
|
||||
index = mid_index;
|
||||
}
|
||||
size -= half_size;
|
||||
}
|
||||
const TSCharacterRange *range = &ranges[index];
|
||||
return (lookahead >= range->start && lookahead <= range->end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lexer Macros
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define UNUSED __pragma(warning(suppress : 4101))
|
||||
#else
|
||||
#define UNUSED __attribute__((unused))
|
||||
#endif
|
||||
|
||||
#define START_LEXER() \
|
||||
bool result = false; \
|
||||
bool skip = false; \
|
||||
UNUSED \
|
||||
bool eof = false; \
|
||||
int32_t lookahead; \
|
||||
goto start; \
|
||||
next_state: \
|
||||
lexer->advance(lexer, skip); \
|
||||
start: \
|
||||
skip = false; \
|
||||
lookahead = lexer->lookahead;
|
||||
|
||||
#define ADVANCE(state_value) \
|
||||
{ \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ADVANCE_MAP(...) \
|
||||
{ \
|
||||
static const uint16_t map[] = { __VA_ARGS__ }; \
|
||||
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
|
||||
if (map[i] == lookahead) { \
|
||||
state = map[i + 1]; \
|
||||
goto next_state; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SKIP(state_value) \
|
||||
{ \
|
||||
skip = true; \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ACCEPT_TOKEN(symbol_value) \
|
||||
result = true; \
|
||||
lexer->result_symbol = symbol_value; \
|
||||
lexer->mark_end(lexer);
|
||||
|
||||
#define END_STATE() return result;
|
||||
|
||||
/*
|
||||
* Parse Table Macros
|
||||
*/
|
||||
|
||||
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
|
||||
|
||||
#define STATE(id) id
|
||||
|
||||
#define ACTIONS(id) id
|
||||
|
||||
#define SHIFT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value) \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_REPEAT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value), \
|
||||
.repetition = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_EXTRA() \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.extra = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define REDUCE(symbol_name, children, precedence, prod_id) \
|
||||
{{ \
|
||||
.reduce = { \
|
||||
.type = TSParseActionTypeReduce, \
|
||||
.symbol = symbol_name, \
|
||||
.child_count = children, \
|
||||
.dynamic_precedence = precedence, \
|
||||
.production_id = prod_id \
|
||||
}, \
|
||||
}}
|
||||
|
||||
#define RECOVER() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeRecover \
|
||||
}}
|
||||
|
||||
#define ACCEPT_INPUT() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeAccept \
|
||||
}}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_PARSER_H_
|
||||
@@ -18,7 +18,7 @@ release-owned runtime assets. Chocolatey retains the complete extracted runtime
|
||||
set and exposes its executable through a PATH shim.
|
||||
|
||||
Full indexes an average repository in milliseconds, the Linux kernel (28M LOC) in
|
||||
3 minutes. Answers structural queries in under 1ms. Supports 162 languages via
|
||||
3 minutes. Answers structural queries in under 1ms. Supports 163 languages via
|
||||
vendored tree-sitter grammars. No hosted service or API key. Plug and play across
|
||||
11 coding agents including Claude Code, Codex CLI, Gemini CLI, and Zed.
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ FORKS = { # self-maintained forks: vendored LICENSE must match the original ups
|
||||
"pascal": "Isopod/tree-sitter-pascal",
|
||||
"qml": "yuja/tree-sitter-qmljs",
|
||||
"tsx": "tree-sitter/tree-sitter-typescript",
|
||||
# vbnet: tree-sitter-vbnet + CodeGraph's real-world VB.NET patch; same
|
||||
# routing rule as arkts -- `LICENSE` is upstream's MIT verbatim and is the
|
||||
# file byte-verified here (local_license() takes it before the sibling
|
||||
# LICENSE-codegraph, whose provenance MANIFEST.md records by SHA-256).
|
||||
"vbnet": "govindbanura/tree-sitter-vbnet",
|
||||
}
|
||||
SPECIAL_NOTICE = {
|
||||
"assembly": "RETAINED-MIT (upstream RubixDev/tree-sitter-assembly deleted from GitHub)",
|
||||
|
||||
@@ -5,7 +5,7 @@ c5cfb43042b6b72045f4ba997834d0a7786d2793d91680868b5815b39f14fc78 internal/cbm/v
|
||||
b29c1c9fb7cc82f58c84b376df1297d6e2737a1d655fd356db0859e3c29c2fea internal/cbm/vendored/common/tree_sitter/alloc.h
|
||||
31e60a1bff6f715afacce03b5b70efe42b58371b4f9595dd4af52a577ff9608c internal/cbm/vendored/common/tree_sitter/array.h
|
||||
180b893c8734778fd32f372dfbc27bd6ad1cd2221f26150b31256ff6716320d2 internal/cbm/vendored/common/tree_sitter/parser.h
|
||||
38172147795b4729640a3d9ef61ac94719e68ab5e3f9aaca792aa1c8c36d2940 internal/cbm/vendored/grammars/MANIFEST.md
|
||||
e47220380048a65e4ae290c5368f1eaafe024c98d83ffd2fd16b98a8ce3833d5 internal/cbm/vendored/grammars/MANIFEST.md
|
||||
ad8425038de519f8c4e3e9339feebf99dfad8a6002dcf79227d91402780a32cc internal/cbm/vendored/grammars/ada/LICENSE
|
||||
02805ec13939b749c891567be36bf024b09034e04c80683a1ae667272458d549 internal/cbm/vendored/grammars/ada/parser.c
|
||||
115a75d000bef9c70c4de7dfbb7f2a80b90cbb14ba265056dc7abb1ef3b9b6db internal/cbm/vendored/grammars/ada/tree_sitter/alloc.h
|
||||
@@ -862,6 +862,13 @@ eeb1ce8085d748ea8ec44b337b179a676dfab4549593b834e9c510362e795b64 internal/cbm/v
|
||||
02d139d44303b0b0b85a899839dbc1346c478e7318d1f2971fad2b38fd57cb73 internal/cbm/vendored/grammars/typst/tree_sitter/array.h
|
||||
2db740ea1be6b71014d2be1385491f01bf2d15b607e61c5b30b4128535efe68a internal/cbm/vendored/grammars/typst/tree_sitter/parser.h
|
||||
6d1a6e017b78433535460ceb3e386d92d8c5dca6e85e6919d5810f1f9aa665a1 internal/cbm/vendored/grammars/typst/unicode.h
|
||||
fa7ab39bcf92c216d429e8aa8be8a5a40db85bfbe4ff14a7bb0ecbbcca937454 internal/cbm/vendored/grammars/vbnet/LICENSE
|
||||
e6d98f98c666bebe065ac2492a0a19232cc318d4d67bac3ca42ffb77bacc8809 internal/cbm/vendored/grammars/vbnet/LICENSE-codegraph
|
||||
d0c29e9a77416fb24e60c8e7f832d60b0cbbf7f40e375f179b119790b0ff0d78 internal/cbm/vendored/grammars/vbnet/parser.c
|
||||
fde8463315219bb0b931fb470b67f8a2d94abe5116db23387e9ef78261a8dbb7 internal/cbm/vendored/grammars/vbnet/scanner.c
|
||||
b29c1c9fb7cc82f58c84b376df1297d6e2737a1d655fd356db0859e3c29c2fea internal/cbm/vendored/grammars/vbnet/tree_sitter/alloc.h
|
||||
5bdf6ed1a78e3409fd443e085ca967a64c188a5d082aaf7f819bccd53a471c94 internal/cbm/vendored/grammars/vbnet/tree_sitter/array.h
|
||||
180b893c8734778fd32f372dfbc27bd6ad1cd2221f26150b31256ff6716320d2 internal/cbm/vendored/grammars/vbnet/tree_sitter/parser.h
|
||||
8d2ff8d1e308828099436c43dc8bfbb8b6f368ad2a012ab9802a80ca22b03b53 internal/cbm/vendored/grammars/verilog/LICENSE
|
||||
bb0f5fe2423ac18a4ab15d8201a2cf155c874b52f3a3022a16e2aa58bb92a4d9 internal/cbm/vendored/grammars/verilog/parser.c
|
||||
b29c1c9fb7cc82f58c84b376df1297d6e2737a1d655fd356db0859e3c29c2fea internal/cbm/vendored/grammars/verilog/tree_sitter/alloc.h
|
||||
|
||||
@@ -1375,6 +1375,72 @@ TEST(complexity_rescript_error_recovery_lexing_is_linear) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Attribute lines in the VB.NET XML-literal scanner ───────────────────
|
||||
* The vendored vbnet grammar (not yet routed to any file) lexes a whole VB
|
||||
* XML literal as one external token, and its scanner is offered a '<' where a
|
||||
* declaration or member starts. In valid VB that '<' opens an attribute block
|
||||
* (`<Serializable()> _`), never an XML literal, but the scanner read
|
||||
* `<Serializable` as an element start and searched the rest of the file for a
|
||||
* closing tag before giving up: O(rest of file) per attribute, O(n^2) per
|
||||
* file, and generated proxies put an attribute on every member. A
|
||||
* well-formed XML tag cannot contain '(' outside a quoted value, so the scan
|
||||
* now fails there. Work is bytes pulled through the chunked TSInput above. */
|
||||
extern const TSLanguage *tree_sitter_vbnet(void);
|
||||
|
||||
enum { CX_VB_ATTR_COPIES = 200 };
|
||||
|
||||
static const char CX_VB_ATTR_CLASS[] = "<Serializable()> _\n"
|
||||
"Public Class Item\n"
|
||||
" <System.Runtime.Serialization.DataMember()> _\n"
|
||||
" Public Property Id As Integer\n"
|
||||
" <Obsolete(\"use Id\")> _\n"
|
||||
" Public Function Key(x As Integer) As Integer\n"
|
||||
" Return x\n"
|
||||
" End Function\n"
|
||||
"End Class\n"
|
||||
"\n";
|
||||
|
||||
/* Bytes pulled while parsing `copies` copies of CX_VB_ATTR_CLASS; 0 on failure
|
||||
* or when the tree has an ERROR node (the work must be a clean parse's). */
|
||||
static uint64_t cx_vbnet_attr_lex_work(uint32_t copies) {
|
||||
size_t unit = sizeof(CX_VB_ATTR_CLASS) - 1;
|
||||
uint32_t len = (uint32_t)(unit * copies);
|
||||
char *src = malloc(len);
|
||||
TSParser *parser = ts_parser_new();
|
||||
uint64_t work = 0;
|
||||
if (src && parser && ts_parser_set_language(parser, tree_sitter_vbnet())) {
|
||||
for (uint32_t i = 0; i < copies; i++) {
|
||||
memcpy(src + (size_t)i * unit, CX_VB_ATTR_CLASS, unit);
|
||||
}
|
||||
CxLexInput in = {src, len, 0};
|
||||
TSInput input = {&in, cx_lex_read, TSInputEncodingUTF8, NULL};
|
||||
TSTree *tree = ts_parser_parse(parser, NULL, input);
|
||||
if (tree) {
|
||||
if (!ts_node_has_error(ts_tree_root_node(tree))) {
|
||||
work = in.bytes_pulled;
|
||||
}
|
||||
ts_tree_delete(tree);
|
||||
}
|
||||
}
|
||||
if (parser) {
|
||||
ts_parser_delete(parser);
|
||||
}
|
||||
free(src);
|
||||
return work;
|
||||
}
|
||||
|
||||
TEST(complexity_vbnet_attribute_lexing_is_linear) {
|
||||
uint64_t base = cx_vbnet_attr_lex_work(CX_VB_ATTR_COPIES);
|
||||
uint64_t doubled = cx_vbnet_attr_lex_work(2 * CX_VB_ATTR_COPIES);
|
||||
double r = cx_ratio((double)doubled, (double)base);
|
||||
printf(" vbnet attributes: lexer bytes %llu -> %llu ratio %.2f (linear ~2, quadratic ~4)\n",
|
||||
(unsigned long long)base, (unsigned long long)doubled, r);
|
||||
/* Non-vacuous: a clean parse that read the input at least once. */
|
||||
ASSERT_GTE(base, (uint64_t)(sizeof(CX_VB_ATTR_CLASS) - 1) * CX_VB_ATTR_COPIES);
|
||||
ASSERT_TRUE(r >= CX_RATIO_LO && r <= CX_RATIO_HI);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(complexity_py_module_ranges_preserve_calls_with_bounded_work) {
|
||||
static const char *const keys[] = {"CBM_WORKERS", "CBM_INDEX_SINGLE_THREAD"};
|
||||
char *saved[2] = {NULL, NULL};
|
||||
@@ -1438,6 +1504,7 @@ TEST(complexity_py_module_ranges_preserve_calls_with_bounded_work) {
|
||||
SUITE(complexity) {
|
||||
RUN_TEST(complexity_py_module_ranges_preserve_calls_with_bounded_work);
|
||||
RUN_TEST(complexity_rescript_error_recovery_lexing_is_linear);
|
||||
RUN_TEST(complexity_vbnet_attribute_lexing_is_linear);
|
||||
RUN_TEST(complexity_replicated_modules_scale_linearly);
|
||||
RUN_TEST(complexity_perfile_registry_work_is_linear);
|
||||
RUN_TEST(complexity_infra_resolution_is_linear);
|
||||
|
||||
@@ -2037,6 +2037,147 @@ TEST(pascal_broken_source_still_flagged_issue2211) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── VB.NET: the vendored grammar parses real VB.NET (#1178, #1179) ────────
|
||||
* tools/tree-sitter-vbnet (tree-sitter-vbnet + CodeGraph's real-world patch)
|
||||
* is vendored ahead of VB.NET extraction: no CBMLanguage routes to it yet, so
|
||||
* these cases parse through tree_sitter_vbnet() directly. The sample is the
|
||||
* shape extraction must index -- Imports, a WinForms Designer partial class,
|
||||
* Handles, Namespace, Inherits/Implements, MustOverride, #Region, Sub New,
|
||||
* Property, Function -- plus names that begin with a keyword. It is RED on the
|
||||
* alternatives: unpatched upstream leaves ERROR nodes at the Designer's Single
|
||||
* literals (8.25!, 6.0!) and at MustOverride, and the patch revision before
|
||||
* 2026-10-06 turns `Sub NewItem()` into an ERROR and `Public SharedCache` into
|
||||
* a Shared field named Cache. */
|
||||
extern const TSLanguage *tree_sitter_vbnet(void);
|
||||
|
||||
static const char VB_REAL_SOURCE[] =
|
||||
"Imports System\n"
|
||||
"Imports System.Collections.Generic\n"
|
||||
"Imports System.Windows.Forms\n"
|
||||
"\n"
|
||||
"<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _\n"
|
||||
"Partial Class MainForm\n"
|
||||
" Inherits System.Windows.Forms.Form\n"
|
||||
"\n"
|
||||
" <System.Diagnostics.DebuggerStepThrough()> _\n"
|
||||
" Private Sub InitializeComponent()\n"
|
||||
" Me.btnSave = New System.Windows.Forms.Button()\n"
|
||||
" Me.SuspendLayout()\n"
|
||||
" Me.btnSave.Font = New System.Drawing.Font(\"Segoe UI\", 8.25!)\n"
|
||||
" Me.btnSave.Text = \"Save\"\n"
|
||||
" Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)\n"
|
||||
" Me.Controls.Add(Me.btnSave)\n"
|
||||
" Me.ResumeLayout(False)\n"
|
||||
" End Sub\n"
|
||||
"\n"
|
||||
" Friend WithEvents btnSave As System.Windows.Forms.Button\n"
|
||||
"End Class\n"
|
||||
"\n"
|
||||
"Partial Class MainForm\n"
|
||||
" Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click\n"
|
||||
" MessageBox.Show(\"Saved\")\n"
|
||||
" End Sub\n"
|
||||
"End Class\n"
|
||||
"\n"
|
||||
"Namespace Contoso.Billing\n"
|
||||
" Public MustInherit Class EntityBase\n"
|
||||
" Public MustOverride Function Describe() As String\n"
|
||||
" End Class\n"
|
||||
"\n"
|
||||
" Public Class Customer\n"
|
||||
" Inherits EntityBase\n"
|
||||
" Implements IComparable(Of Customer), IDisposable\n"
|
||||
"\n"
|
||||
"#Region \"Fields\"\n"
|
||||
" Private _name As String\n"
|
||||
" Public SharedCache As Integer\n"
|
||||
" Public Dimension As Integer\n"
|
||||
"#End Region\n"
|
||||
"\n"
|
||||
" Public Sub New(name As String)\n"
|
||||
" _name = name\n"
|
||||
" End Sub\n"
|
||||
"\n"
|
||||
" Public Sub NewItem()\n"
|
||||
" Dimension = 0\n"
|
||||
" End Sub\n"
|
||||
"\n"
|
||||
" Public Property Name As String\n"
|
||||
" Get\n"
|
||||
" Return _name\n"
|
||||
" End Get\n"
|
||||
" Set(value As String)\n"
|
||||
" _name = value\n"
|
||||
" End Set\n"
|
||||
" End Property\n"
|
||||
"\n"
|
||||
" Public Overrides Function Describe() As String\n"
|
||||
" Return _name\n"
|
||||
" End Function\n"
|
||||
"\n"
|
||||
" Public Sub Dispose() Implements IDisposable.Dispose\n"
|
||||
" _name = Nothing\n"
|
||||
" End Sub\n"
|
||||
"\n"
|
||||
" Public Function CompareTo(o As Customer) As Integer _\n"
|
||||
" Implements IComparable(Of Customer).CompareTo\n"
|
||||
" Return String.Compare(_name, o.Name)\n"
|
||||
" End Function\n"
|
||||
" End Class\n"
|
||||
"End Namespace\n";
|
||||
|
||||
/* Counts the `identifier` nodes under `node` whose text is exactly `text`. */
|
||||
static int vb_identifier_count(TSNode node, const char *src, const char *text) {
|
||||
int hits = 0;
|
||||
if (strcmp(ts_node_type(node), "identifier") == 0) {
|
||||
uint32_t start = ts_node_start_byte(node);
|
||||
uint32_t len = ts_node_end_byte(node) - start;
|
||||
if (strlen(text) == len && memcmp(src + start, text, len) == 0) {
|
||||
hits++;
|
||||
}
|
||||
}
|
||||
uint32_t count = ts_node_child_count(node);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
hits += vb_identifier_count(ts_node_child(node, i), src, text);
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
TEST(vbnet_vendored_grammar_parses_real_vb_clean) {
|
||||
TSParser *parser = ts_parser_new();
|
||||
ASSERT_NOT_NULL(parser);
|
||||
/* The vendored runtime accepts the generated ABI (15). */
|
||||
ASSERT_TRUE(ts_parser_set_language(parser, tree_sitter_vbnet()));
|
||||
TSTree *tree =
|
||||
ts_parser_parse_string(parser, NULL, VB_REAL_SOURCE, (uint32_t)strlen(VB_REAL_SOURCE));
|
||||
ASSERT_NOT_NULL(tree);
|
||||
TSNode root = ts_tree_root_node(tree);
|
||||
ASSERT_STR_EQ(ts_node_type(root), "source_file");
|
||||
ASSERT_FALSE(ts_node_has_error(root));
|
||||
/* Names that begin with a keyword stay whole identifiers. */
|
||||
ASSERT_GTE(vb_identifier_count(root, VB_REAL_SOURCE, "SharedCache"), 1);
|
||||
ASSERT_GTE(vb_identifier_count(root, VB_REAL_SOURCE, "Dimension"), 1);
|
||||
ASSERT_GTE(vb_identifier_count(root, VB_REAL_SOURCE, "NewItem"), 1);
|
||||
ASSERT_EQ(vb_identifier_count(root, VB_REAL_SOURCE, "Cache"), 0);
|
||||
ASSERT_EQ(vb_identifier_count(root, VB_REAL_SOURCE, "ension"), 0);
|
||||
ts_tree_delete(tree);
|
||||
ts_parser_delete(parser);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(vbnet_vendored_grammar_still_flags_broken_source) {
|
||||
static const char broken[] = "Public Class Broken\n Public Sub (\nEnd Class\n";
|
||||
TSParser *parser = ts_parser_new();
|
||||
ASSERT_NOT_NULL(parser);
|
||||
ASSERT_TRUE(ts_parser_set_language(parser, tree_sitter_vbnet()));
|
||||
TSTree *tree = ts_parser_parse_string(parser, NULL, broken, (uint32_t)strlen(broken));
|
||||
ASSERT_NOT_NULL(tree);
|
||||
ASSERT_TRUE(ts_node_has_error(ts_tree_root_node(tree)));
|
||||
ts_tree_delete(tree);
|
||||
ts_parser_delete(parser);
|
||||
PASS();
|
||||
}
|
||||
|
||||
SUITE(parse_coverage) {
|
||||
RUN_TEST(source_line_count_one_convention);
|
||||
RUN_TEST(c_ifdef_split_brace_sets_parse_incomplete);
|
||||
@@ -2100,4 +2241,6 @@ SUITE(parse_coverage) {
|
||||
RUN_TEST(pascal_cyrillic_procedure_name_extracted_issue2211);
|
||||
RUN_TEST(pascal_ascii_control_still_clean_issue2211);
|
||||
RUN_TEST(pascal_broken_source_still_flagged_issue2211);
|
||||
RUN_TEST(vbnet_vendored_grammar_parses_real_vb_clean);
|
||||
RUN_TEST(vbnet_vendored_grammar_still_flags_broken_source);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "tree-sitter-vbnet-cbm",
|
||||
"version": "0.1.0-cbm.1",
|
||||
"description": "cbm fork of tree-sitter-vbnet @ 538b708 with CodeGraph's real-world VB.NET patch",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"tree-sitter-cli": "0.25.10"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
diff --git a/src/scanner.c b/src/scanner.c
|
||||
index 8334fdb..b3473d1 100644
|
||||
--- a/src/scanner.c
|
||||
+++ b/src/scanner.c
|
||||
@@ -191,6 +191,17 @@ static bool is_name_start(int32_t c) {
|
||||
return iswalpha((wint_t)c) || c == '_';
|
||||
}
|
||||
|
||||
+// cbm: what may appear inside an element tag outside a quoted value: name
|
||||
+// characters, blanks and '='. Anything else is not XML, so the scan stops
|
||||
+// there. A '<' where a declaration or member starts opens a VB attribute
|
||||
+// (`<Serializable()> _`); without this check the scan took `<Serializable`
|
||||
+// for an element and read the rest of the file looking for its closing tag,
|
||||
+// once per attribute.
|
||||
+static bool is_tag_char(int32_t c) {
|
||||
+ return iswalnum((wint_t)c) || c == '_' || c == '-' || c == '.' || c == ':' ||
|
||||
+ c == '=' || c == ' ' || c == '\t' || c == '\r' || c == '\n' || c >= 0x80;
|
||||
+}
|
||||
+
|
||||
static bool scan_xml_literal(TSLexer *lexer) {
|
||||
// Only same-line blanks are skipped: a leading newline must stay unconsumed
|
||||
// so it can serve as a statement/block terminator (a successful scan would
|
||||
@@ -227,7 +238,7 @@ static bool scan_xml_literal(TSLexer *lexer) {
|
||||
if (!consume_embedded_expression(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
- continue; // stray < in a tag: malformed, keep consuming
|
||||
+ return false; // cbm: a stray < in a tag is not XML
|
||||
}
|
||||
if (c == '/') {
|
||||
advance(lexer);
|
||||
@@ -238,7 +249,7 @@ static bool scan_xml_literal(TSLexer *lexer) {
|
||||
if (depth == 0) break; // single self-closed root: done
|
||||
continue;
|
||||
}
|
||||
- continue;
|
||||
+ return false; // cbm: '/' in a tag only closes it
|
||||
}
|
||||
if (c == '>') {
|
||||
advance(lexer);
|
||||
@@ -251,6 +262,7 @@ static bool scan_xml_literal(TSLexer *lexer) {
|
||||
in_tag = false;
|
||||
continue;
|
||||
}
|
||||
+ if (!is_tag_char(c)) return false; // cbm: e.g. the '(' of `<Obsolete("x")>`
|
||||
advance(lexer);
|
||||
continue;
|
||||
}
|
||||
@@ -272,10 +284,21 @@ static bool scan_xml_literal(TSLexer *lexer) {
|
||||
if (!consume_processing_instruction(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
+ // cbm: otherwise only an element name (or `<<%= %>`) may follow; a
|
||||
+ // comparison such as `x < y` in code after an attribute is not XML.
|
||||
+ if (!is_name_start(d) && d != '<' && d < 0x80) return false;
|
||||
closing = false;
|
||||
in_tag = true; // opening tag
|
||||
continue;
|
||||
}
|
||||
+ if (c == '&') {
|
||||
+ // cbm: '&' in XML text starts a reference (`&`, `&`); VB's
|
||||
+ // concatenation `a & b` in code after an attribute is not XML.
|
||||
+ advance(lexer);
|
||||
+ int32_t d = lexer->lookahead;
|
||||
+ if (d != '#' && !is_name_start(d) && d < 0x80) return false;
|
||||
+ continue;
|
||||
+ }
|
||||
advance(lexer);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,326 @@
|
||||
#include "tree_sitter/parser.h"
|
||||
#include <string.h>
|
||||
#include <wctype.h>
|
||||
|
||||
// External tokens for the two constructs that are not LR(1)-parseable with
|
||||
// tree-sitter's newline-as-extra treatment:
|
||||
//
|
||||
// 1. QUERY_CLAUSE_CONTINUATION — a newline run that CONTINUES a LINQ query
|
||||
// (`From x In xs` ↵ `Where …`). At a clause boundary the parser cannot
|
||||
// know from the newline alone whether the query ends (newline = statement
|
||||
// terminator) or continues (newline = insignificant, next line starts with
|
||||
// a query keyword). The scanner looks past the newline at the next word
|
||||
// and only emits the token when it is a query-clause keyword — so the
|
||||
// terminator/continuation choice is made by the lexer, not the LR table.
|
||||
//
|
||||
// 2. XML_LITERAL — a whole VB XML literal (`<Tags><Tag/></Tags>`, including
|
||||
// `<%= expr %>` embedded expressions, comments, CDATA) consumed as one
|
||||
// opaque token. Only valid where a literal can begin an expression, so a
|
||||
// relational `<` (which always FOLLOWS an expression) can never collide.
|
||||
|
||||
enum TokenType {
|
||||
QUERY_CLAUSE_CONTINUATION,
|
||||
XML_LITERAL,
|
||||
};
|
||||
|
||||
void *tree_sitter_vbnet_external_scanner_create(void) { return NULL; }
|
||||
void tree_sitter_vbnet_external_scanner_destroy(void *payload) { (void)payload; }
|
||||
unsigned tree_sitter_vbnet_external_scanner_serialize(void *payload, char *buffer) {
|
||||
(void)payload; (void)buffer;
|
||||
return 0; // stateless
|
||||
}
|
||||
void tree_sitter_vbnet_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {
|
||||
(void)payload; (void)buffer; (void)length;
|
||||
}
|
||||
|
||||
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
|
||||
static inline void skip_ws(TSLexer *lexer) { lexer->advance(lexer, true); }
|
||||
|
||||
// Read one alphabetic word (advancing past it), lowercased into buf.
|
||||
// Returns its length (0 if the next char is not a letter).
|
||||
static int read_word(TSLexer *lexer, char *buf, int cap) {
|
||||
int n = 0;
|
||||
while (iswalpha((wint_t)lexer->lookahead)) {
|
||||
if (n < cap - 1) buf[n] = (char)towlower((wint_t)lexer->lookahead);
|
||||
n++;
|
||||
advance(lexer);
|
||||
}
|
||||
buf[n < cap - 1 ? n : cap - 1] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool is_query_keyword(const char *w) {
|
||||
static const char *kws[] = {
|
||||
"where", "select", "order", "group", "join", "let", "skip",
|
||||
"take", "distinct", "aggregate", "from", "into", "on", NULL,
|
||||
};
|
||||
for (int i = 0; kws[i]; i++)
|
||||
if (strcmp(w, kws[i]) == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// [ \t\r]* '\n' [ \t\r\n]* followed by a query-clause keyword.
|
||||
// The token covers only the whitespace/newline run (mark_end before the
|
||||
// keyword lookahead), so the clause keyword itself lexes normally after it.
|
||||
static bool scan_query_continuation(TSLexer *lexer) {
|
||||
bool saw_newline = false;
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == ' ' || c == '\t' || c == '\r') {
|
||||
advance(lexer);
|
||||
} else if (c == '\n') {
|
||||
saw_newline = true;
|
||||
advance(lexer);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!saw_newline) return false;
|
||||
lexer->mark_end(lexer);
|
||||
|
||||
char w[12];
|
||||
int n = read_word(lexer, w, sizeof w);
|
||||
if (n == 0 || n >= (int)(sizeof w)) return false;
|
||||
if (!is_query_keyword(w)) return false;
|
||||
|
||||
// `Select Case` opens a statement, never a select clause.
|
||||
if (strcmp(w, "select") == 0) {
|
||||
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') advance(lexer);
|
||||
char w2[8];
|
||||
int m = read_word(lexer, w2, sizeof w2);
|
||||
if (m > 0 && m < (int)(sizeof w2) && strcmp(w2, "case") == 0) return false;
|
||||
}
|
||||
|
||||
lexer->result_symbol = QUERY_CLAUSE_CONTINUATION;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consume a VB string inside an embedded `<%= … %>` region ("" escapes).
|
||||
static void consume_vb_string(TSLexer *lexer) {
|
||||
advance(lexer); // opening quote
|
||||
for (;;) {
|
||||
if (lexer->lookahead == 0) return;
|
||||
if (lexer->lookahead == '"') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead != '"') return; // closing (not an escaped "")
|
||||
// escaped quote: fall through, keep consuming
|
||||
}
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
// Consume `<%= … %>` starting at the '%' (the '<' is already consumed).
|
||||
// Embedded expressions NEST (`<%= From t In ts Select <Tag><%= t.Name %></Tag> %>`,
|
||||
// the staxrip WriteTagfile shape), so track <% / %> depth.
|
||||
static bool consume_embedded_expression(TSLexer *lexer) {
|
||||
advance(lexer); // '%'
|
||||
int depth = 1;
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == '"') { consume_vb_string(lexer); continue; }
|
||||
if (c == '<') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '%') { depth++; advance(lexer); }
|
||||
continue;
|
||||
}
|
||||
if (c == '%') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '>') {
|
||||
advance(lexer);
|
||||
if (--depth == 0) return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
// Consume `<!-- … -->` / `<![CDATA[ … ]]>` / `<!DOCTYPE …>` starting at '!'.
|
||||
static bool consume_bang_construct(TSLexer *lexer) {
|
||||
advance(lexer); // '!'
|
||||
if (lexer->lookahead == '-') {
|
||||
// comment: to -->
|
||||
int dashes = 0;
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == '-') { dashes++; advance(lexer); continue; }
|
||||
if (c == '>' && dashes >= 2) { advance(lexer); return true; }
|
||||
dashes = 0;
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
if (lexer->lookahead == '[') {
|
||||
// CDATA: to ]]>
|
||||
int brackets = 0;
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == ']') { brackets++; advance(lexer); continue; }
|
||||
if (c == '>' && brackets >= 2) { advance(lexer); return true; }
|
||||
brackets = 0;
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
// DOCTYPE-ish: to bare >
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == '>') { advance(lexer); return true; }
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
// Consume `<? … ?>` starting at the '?'.
|
||||
static bool consume_processing_instruction(TSLexer *lexer) {
|
||||
advance(lexer); // '?'
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false;
|
||||
if (c == '?') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '>') { advance(lexer); return true; }
|
||||
continue;
|
||||
}
|
||||
advance(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_name_start(int32_t c) {
|
||||
return iswalpha((wint_t)c) || c == '_';
|
||||
}
|
||||
|
||||
// cbm: what may appear inside an element tag outside a quoted value: name
|
||||
// characters, blanks and '='. Anything else is not XML, so the scan stops
|
||||
// there. A '<' where a declaration or member starts opens a VB attribute
|
||||
// (`<Serializable()> _`); without this check the scan took `<Serializable`
|
||||
// for an element and read the rest of the file looking for its closing tag,
|
||||
// once per attribute.
|
||||
static bool is_tag_char(int32_t c) {
|
||||
return iswalnum((wint_t)c) || c == '_' || c == '-' || c == '.' || c == ':' ||
|
||||
c == '=' || c == ' ' || c == '\t' || c == '\r' || c == '\n' || c >= 0x80;
|
||||
}
|
||||
|
||||
static bool scan_xml_literal(TSLexer *lexer) {
|
||||
// Only same-line blanks are skipped: a leading newline must stay unconsumed
|
||||
// so it can serve as a statement/block terminator (a successful scan would
|
||||
// otherwise swallow it as trivia). XML on a continuation line still works:
|
||||
// the newline is consumed as an extra first, then the scanner re-runs.
|
||||
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') skip_ws(lexer);
|
||||
if (lexer->lookahead != '<') return false;
|
||||
advance(lexer);
|
||||
// Only element-start markup opens an XML literal here; comments/PIs as the
|
||||
// outermost construct (`Dim d = <?xml …`) are rare enough to leave alone,
|
||||
// and a bare `<` (comparison) never begins an expression.
|
||||
if (!is_name_start(lexer->lookahead)) return false;
|
||||
|
||||
int depth = 0; // open (unclosed) elements
|
||||
bool in_tag = true; // inside <...> of an element tag
|
||||
bool closing = false;
|
||||
int32_t quote = 0;
|
||||
|
||||
for (;;) {
|
||||
int32_t c = lexer->lookahead;
|
||||
if (c == 0) return false; // unterminated: let the normal parser error
|
||||
|
||||
if (in_tag) {
|
||||
if (quote) {
|
||||
if (c == quote) quote = 0;
|
||||
advance(lexer);
|
||||
continue;
|
||||
}
|
||||
if (c == '"' || c == '\'') { quote = c; advance(lexer); continue; }
|
||||
if (c == '<') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '%') {
|
||||
// attribute value embedded expression: attr=<%= x %>
|
||||
if (!consume_embedded_expression(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
return false; // cbm: a stray < in a tag is not XML
|
||||
}
|
||||
if (c == '/') {
|
||||
advance(lexer);
|
||||
if (lexer->lookahead == '>') {
|
||||
// self-closing tag
|
||||
advance(lexer);
|
||||
in_tag = false;
|
||||
if (depth == 0) break; // single self-closed root: done
|
||||
continue;
|
||||
}
|
||||
return false; // cbm: '/' in a tag only closes it
|
||||
}
|
||||
if (c == '>') {
|
||||
advance(lexer);
|
||||
if (closing) {
|
||||
depth--;
|
||||
if (depth <= 0) break; // root element closed: done
|
||||
} else {
|
||||
depth++;
|
||||
}
|
||||
in_tag = false;
|
||||
continue;
|
||||
}
|
||||
if (!is_tag_char(c)) return false; // cbm: e.g. the '(' of `<Obsolete("x")>`
|
||||
advance(lexer);
|
||||
continue;
|
||||
}
|
||||
|
||||
// text content between tags
|
||||
if (c == '<') {
|
||||
advance(lexer);
|
||||
int32_t d = lexer->lookahead;
|
||||
if (d == '/') { advance(lexer); closing = true; in_tag = true; continue; }
|
||||
if (d == '%') {
|
||||
if (!consume_embedded_expression(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
if (d == '!') {
|
||||
if (!consume_bang_construct(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
if (d == '?') {
|
||||
if (!consume_processing_instruction(lexer)) return false;
|
||||
continue;
|
||||
}
|
||||
// cbm: otherwise only an element name (or `<<%= %>`) may follow; a
|
||||
// comparison such as `x < y` in code after an attribute is not XML.
|
||||
if (!is_name_start(d) && d != '<' && d < 0x80) return false;
|
||||
closing = false;
|
||||
in_tag = true; // opening tag
|
||||
continue;
|
||||
}
|
||||
if (c == '&') {
|
||||
// cbm: '&' in XML text starts a reference (`&`, `&`); VB's
|
||||
// concatenation `a & b` in code after an attribute is not XML.
|
||||
advance(lexer);
|
||||
int32_t d = lexer->lookahead;
|
||||
if (d != '#' && !is_name_start(d) && d < 0x80) return false;
|
||||
continue;
|
||||
}
|
||||
advance(lexer);
|
||||
}
|
||||
|
||||
lexer->mark_end(lexer);
|
||||
lexer->result_symbol = XML_LITERAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tree_sitter_vbnet_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
|
||||
(void)payload;
|
||||
int32_t c = lexer->lookahead;
|
||||
bool ws = (c == ' ' || c == '\t' || c == '\n' || c == '\r');
|
||||
|
||||
// A scan attempt advances the lexer, so exactly ONE attempt runs per
|
||||
// invocation — never chain a second attempt after a failed first (its
|
||||
// reads would start mid-stream). The leading character picks the branch;
|
||||
// a false return discards the attempt entirely.
|
||||
if (valid_symbols[QUERY_CLAUSE_CONTINUATION] && ws) {
|
||||
return scan_query_continuation(lexer);
|
||||
}
|
||||
if (valid_symbols[XML_LITERAL] && (c == '<' || ws)) {
|
||||
return scan_xml_literal(lexer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef TREE_SITTER_ALLOC_H_
|
||||
#define TREE_SITTER_ALLOC_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Allow clients to override allocation functions
|
||||
#ifdef TREE_SITTER_REUSE_ALLOCATOR
|
||||
|
||||
extern void *(*ts_current_malloc)(size_t size);
|
||||
extern void *(*ts_current_calloc)(size_t count, size_t size);
|
||||
extern void *(*ts_current_realloc)(void *ptr, size_t size);
|
||||
extern void (*ts_current_free)(void *ptr);
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc ts_current_malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc ts_current_calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc ts_current_realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free ts_current_free
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free free
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ALLOC_H_
|
||||
@@ -0,0 +1,291 @@
|
||||
#ifndef TREE_SITTER_ARRAY_H_
|
||||
#define TREE_SITTER_ARRAY_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "./alloc.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4101)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
|
||||
#define Array(T) \
|
||||
struct { \
|
||||
T *contents; \
|
||||
uint32_t size; \
|
||||
uint32_t capacity; \
|
||||
}
|
||||
|
||||
/// Initialize an array.
|
||||
#define array_init(self) \
|
||||
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
|
||||
|
||||
/// Create an empty array.
|
||||
#define array_new() \
|
||||
{ NULL, 0, 0 }
|
||||
|
||||
/// Get a pointer to the element at a given `index` in the array.
|
||||
#define array_get(self, _index) \
|
||||
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
|
||||
|
||||
/// Get a pointer to the first element in the array.
|
||||
#define array_front(self) array_get(self, 0)
|
||||
|
||||
/// Get a pointer to the last element in the array.
|
||||
#define array_back(self) array_get(self, (self)->size - 1)
|
||||
|
||||
/// Clear the array, setting its size to zero. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_clear(self) ((self)->size = 0)
|
||||
|
||||
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
|
||||
/// less than the array's current capacity, this function has no effect.
|
||||
#define array_reserve(self, new_capacity) \
|
||||
_array__reserve((Array *)(self), array_elem_size(self), new_capacity)
|
||||
|
||||
/// Free any memory allocated for this array. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_delete(self) _array__delete((Array *)(self))
|
||||
|
||||
/// Push a new `element` onto the end of the array.
|
||||
#define array_push(self, element) \
|
||||
(_array__grow((Array *)(self), 1, array_elem_size(self)), \
|
||||
(self)->contents[(self)->size++] = (element))
|
||||
|
||||
/// Increase the array's size by `count` elements.
|
||||
/// New elements are zero-initialized.
|
||||
#define array_grow_by(self, count) \
|
||||
do { \
|
||||
if ((count) == 0) break; \
|
||||
_array__grow((Array *)(self), count, array_elem_size(self)); \
|
||||
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
|
||||
(self)->size += (count); \
|
||||
} while (0)
|
||||
|
||||
/// Append all elements from one array to the end of another.
|
||||
#define array_push_all(self, other) \
|
||||
array_extend((self), (other)->size, (other)->contents)
|
||||
|
||||
/// Append `count` elements to the end of the array, reading their values from the
|
||||
/// `contents` pointer.
|
||||
#define array_extend(self, count, contents) \
|
||||
_array__splice( \
|
||||
(Array *)(self), array_elem_size(self), (self)->size, \
|
||||
0, count, contents \
|
||||
)
|
||||
|
||||
/// Remove `old_count` elements from the array starting at the given `index`. At
|
||||
/// the same index, insert `new_count` new elements, reading their values from the
|
||||
/// `new_contents` pointer.
|
||||
#define array_splice(self, _index, old_count, new_count, new_contents) \
|
||||
_array__splice( \
|
||||
(Array *)(self), array_elem_size(self), _index, \
|
||||
old_count, new_count, new_contents \
|
||||
)
|
||||
|
||||
/// Insert one `element` into the array at the given `index`.
|
||||
#define array_insert(self, _index, element) \
|
||||
_array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
|
||||
|
||||
/// Remove one element from the array at the given `index`.
|
||||
#define array_erase(self, _index) \
|
||||
_array__erase((Array *)(self), array_elem_size(self), _index)
|
||||
|
||||
/// Pop the last element off the array, returning the element by value.
|
||||
#define array_pop(self) ((self)->contents[--(self)->size])
|
||||
|
||||
/// Assign the contents of one array to another, reallocating if necessary.
|
||||
#define array_assign(self, other) \
|
||||
_array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
|
||||
|
||||
/// Swap one array with another
|
||||
#define array_swap(self, other) \
|
||||
_array__swap((Array *)(self), (Array *)(other))
|
||||
|
||||
/// Get the size of the array contents
|
||||
#define array_elem_size(self) (sizeof *(self)->contents)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
///
|
||||
/// If an existing element is found to be equal to `needle`, then the `index`
|
||||
/// out-parameter is set to the existing value's index, and the `exists`
|
||||
/// out-parameter is set to true. Otherwise, `index` is set to an index where
|
||||
/// `needle` should be inserted in order to preserve the sorting, and `exists`
|
||||
/// is set to false.
|
||||
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using integer comparisons
|
||||
/// of a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_with`.
|
||||
#define array_search_sorted_by(self, field, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
#define array_insert_sorted_with(self, compare, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using integer comparisons of
|
||||
/// a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_by`.
|
||||
#define array_insert_sorted_by(self, field, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
// Private
|
||||
|
||||
typedef Array(void) Array;
|
||||
|
||||
/// This is not what you're looking for, see `array_delete`.
|
||||
static inline void _array__delete(Array *self) {
|
||||
if (self->contents) {
|
||||
ts_free(self->contents);
|
||||
self->contents = NULL;
|
||||
self->size = 0;
|
||||
self->capacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_erase`.
|
||||
static inline void _array__erase(Array *self, size_t element_size,
|
||||
uint32_t index) {
|
||||
assert(index < self->size);
|
||||
char *contents = (char *)self->contents;
|
||||
memmove(contents + index * element_size, contents + (index + 1) * element_size,
|
||||
(self->size - index - 1) * element_size);
|
||||
self->size--;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_reserve`.
|
||||
static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
|
||||
if (new_capacity > self->capacity) {
|
||||
if (self->contents) {
|
||||
self->contents = ts_realloc(self->contents, new_capacity * element_size);
|
||||
} else {
|
||||
self->contents = ts_malloc(new_capacity * element_size);
|
||||
}
|
||||
self->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_assign`.
|
||||
static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
|
||||
_array__reserve(self, element_size, other->size);
|
||||
self->size = other->size;
|
||||
memcpy(self->contents, other->contents, self->size * element_size);
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_swap`.
|
||||
static inline void _array__swap(Array *self, Array *other) {
|
||||
Array swap = *other;
|
||||
*other = *self;
|
||||
*self = swap;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
|
||||
static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
|
||||
uint32_t new_size = self->size + count;
|
||||
if (new_size > self->capacity) {
|
||||
uint32_t new_capacity = self->capacity * 2;
|
||||
if (new_capacity < 8) new_capacity = 8;
|
||||
if (new_capacity < new_size) new_capacity = new_size;
|
||||
_array__reserve(self, element_size, new_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_splice`.
|
||||
static inline void _array__splice(Array *self, size_t element_size,
|
||||
uint32_t index, uint32_t old_count,
|
||||
uint32_t new_count, const void *elements) {
|
||||
uint32_t new_size = self->size + new_count - old_count;
|
||||
uint32_t old_end = index + old_count;
|
||||
uint32_t new_end = index + new_count;
|
||||
assert(old_end <= self->size);
|
||||
|
||||
_array__reserve(self, element_size, new_size);
|
||||
|
||||
char *contents = (char *)self->contents;
|
||||
if (self->size > old_end) {
|
||||
memmove(
|
||||
contents + new_end * element_size,
|
||||
contents + old_end * element_size,
|
||||
(self->size - old_end) * element_size
|
||||
);
|
||||
}
|
||||
if (new_count > 0) {
|
||||
if (elements) {
|
||||
memcpy(
|
||||
(contents + index * element_size),
|
||||
elements,
|
||||
new_count * element_size
|
||||
);
|
||||
} else {
|
||||
memset(
|
||||
(contents + index * element_size),
|
||||
0,
|
||||
new_count * element_size
|
||||
);
|
||||
}
|
||||
}
|
||||
self->size += new_count - old_count;
|
||||
}
|
||||
|
||||
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
|
||||
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
|
||||
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
|
||||
do { \
|
||||
*(_index) = start; \
|
||||
*(_exists) = false; \
|
||||
uint32_t size = (self)->size - *(_index); \
|
||||
if (size == 0) break; \
|
||||
int comparison; \
|
||||
while (size > 1) { \
|
||||
uint32_t half_size = size / 2; \
|
||||
uint32_t mid_index = *(_index) + half_size; \
|
||||
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
|
||||
if (comparison <= 0) *(_index) = mid_index; \
|
||||
size -= half_size; \
|
||||
} \
|
||||
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
|
||||
if (comparison == 0) *(_exists) = true; \
|
||||
else if (comparison < 0) *(_index) += 1; \
|
||||
} while (0)
|
||||
|
||||
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
|
||||
/// parameter by reference in order to work with the generic sorting function above.
|
||||
#define _compare_int(a, b) ((int)*(a) - (int)(b))
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ARRAY_H_
|
||||
@@ -0,0 +1,286 @@
|
||||
#ifndef TREE_SITTER_PARSER_H_
|
||||
#define TREE_SITTER_PARSER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ts_builtin_sym_error ((TSSymbol)-1)
|
||||
#define ts_builtin_sym_end 0
|
||||
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
|
||||
|
||||
#ifndef TREE_SITTER_API_H_
|
||||
typedef uint16_t TSStateId;
|
||||
typedef uint16_t TSSymbol;
|
||||
typedef uint16_t TSFieldId;
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
typedef struct TSLanguageMetadata {
|
||||
uint8_t major_version;
|
||||
uint8_t minor_version;
|
||||
uint8_t patch_version;
|
||||
} TSLanguageMetadata;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
TSFieldId field_id;
|
||||
uint8_t child_index;
|
||||
bool inherited;
|
||||
} TSFieldMapEntry;
|
||||
|
||||
// Used to index the field and supertype maps.
|
||||
typedef struct {
|
||||
uint16_t index;
|
||||
uint16_t length;
|
||||
} TSMapSlice;
|
||||
|
||||
typedef struct {
|
||||
bool visible;
|
||||
bool named;
|
||||
bool supertype;
|
||||
} TSSymbolMetadata;
|
||||
|
||||
typedef struct TSLexer TSLexer;
|
||||
|
||||
struct TSLexer {
|
||||
int32_t lookahead;
|
||||
TSSymbol result_symbol;
|
||||
void (*advance)(TSLexer *, bool);
|
||||
void (*mark_end)(TSLexer *);
|
||||
uint32_t (*get_column)(TSLexer *);
|
||||
bool (*is_at_included_range_start)(const TSLexer *);
|
||||
bool (*eof)(const TSLexer *);
|
||||
void (*log)(const TSLexer *, const char *, ...);
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TSParseActionTypeShift,
|
||||
TSParseActionTypeReduce,
|
||||
TSParseActionTypeAccept,
|
||||
TSParseActionTypeRecover,
|
||||
} TSParseActionType;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t type;
|
||||
TSStateId state;
|
||||
bool extra;
|
||||
bool repetition;
|
||||
} shift;
|
||||
struct {
|
||||
uint8_t type;
|
||||
uint8_t child_count;
|
||||
TSSymbol symbol;
|
||||
int16_t dynamic_precedence;
|
||||
uint16_t production_id;
|
||||
} reduce;
|
||||
uint8_t type;
|
||||
} TSParseAction;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
} TSLexMode;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
uint16_t reserved_word_set_id;
|
||||
} TSLexerMode;
|
||||
|
||||
typedef union {
|
||||
TSParseAction action;
|
||||
struct {
|
||||
uint8_t count;
|
||||
bool reusable;
|
||||
} entry;
|
||||
} TSParseActionEntry;
|
||||
|
||||
typedef struct {
|
||||
int32_t start;
|
||||
int32_t end;
|
||||
} TSCharacterRange;
|
||||
|
||||
struct TSLanguage {
|
||||
uint32_t abi_version;
|
||||
uint32_t symbol_count;
|
||||
uint32_t alias_count;
|
||||
uint32_t token_count;
|
||||
uint32_t external_token_count;
|
||||
uint32_t state_count;
|
||||
uint32_t large_state_count;
|
||||
uint32_t production_id_count;
|
||||
uint32_t field_count;
|
||||
uint16_t max_alias_sequence_length;
|
||||
const uint16_t *parse_table;
|
||||
const uint16_t *small_parse_table;
|
||||
const uint32_t *small_parse_table_map;
|
||||
const TSParseActionEntry *parse_actions;
|
||||
const char * const *symbol_names;
|
||||
const char * const *field_names;
|
||||
const TSMapSlice *field_map_slices;
|
||||
const TSFieldMapEntry *field_map_entries;
|
||||
const TSSymbolMetadata *symbol_metadata;
|
||||
const TSSymbol *public_symbol_map;
|
||||
const uint16_t *alias_map;
|
||||
const TSSymbol *alias_sequences;
|
||||
const TSLexerMode *lex_modes;
|
||||
bool (*lex_fn)(TSLexer *, TSStateId);
|
||||
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
|
||||
TSSymbol keyword_capture_token;
|
||||
struct {
|
||||
const bool *states;
|
||||
const TSSymbol *symbol_map;
|
||||
void *(*create)(void);
|
||||
void (*destroy)(void *);
|
||||
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
|
||||
unsigned (*serialize)(void *, char *);
|
||||
void (*deserialize)(void *, const char *, unsigned);
|
||||
} external_scanner;
|
||||
const TSStateId *primary_state_ids;
|
||||
const char *name;
|
||||
const TSSymbol *reserved_words;
|
||||
uint16_t max_reserved_word_set_size;
|
||||
uint32_t supertype_count;
|
||||
const TSSymbol *supertype_symbols;
|
||||
const TSMapSlice *supertype_map_slices;
|
||||
const TSSymbol *supertype_map_entries;
|
||||
TSLanguageMetadata metadata;
|
||||
};
|
||||
|
||||
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
|
||||
uint32_t index = 0;
|
||||
uint32_t size = len - index;
|
||||
while (size > 1) {
|
||||
uint32_t half_size = size / 2;
|
||||
uint32_t mid_index = index + half_size;
|
||||
const TSCharacterRange *range = &ranges[mid_index];
|
||||
if (lookahead >= range->start && lookahead <= range->end) {
|
||||
return true;
|
||||
} else if (lookahead > range->end) {
|
||||
index = mid_index;
|
||||
}
|
||||
size -= half_size;
|
||||
}
|
||||
const TSCharacterRange *range = &ranges[index];
|
||||
return (lookahead >= range->start && lookahead <= range->end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lexer Macros
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define UNUSED __pragma(warning(suppress : 4101))
|
||||
#else
|
||||
#define UNUSED __attribute__((unused))
|
||||
#endif
|
||||
|
||||
#define START_LEXER() \
|
||||
bool result = false; \
|
||||
bool skip = false; \
|
||||
UNUSED \
|
||||
bool eof = false; \
|
||||
int32_t lookahead; \
|
||||
goto start; \
|
||||
next_state: \
|
||||
lexer->advance(lexer, skip); \
|
||||
start: \
|
||||
skip = false; \
|
||||
lookahead = lexer->lookahead;
|
||||
|
||||
#define ADVANCE(state_value) \
|
||||
{ \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ADVANCE_MAP(...) \
|
||||
{ \
|
||||
static const uint16_t map[] = { __VA_ARGS__ }; \
|
||||
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
|
||||
if (map[i] == lookahead) { \
|
||||
state = map[i + 1]; \
|
||||
goto next_state; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SKIP(state_value) \
|
||||
{ \
|
||||
skip = true; \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ACCEPT_TOKEN(symbol_value) \
|
||||
result = true; \
|
||||
lexer->result_symbol = symbol_value; \
|
||||
lexer->mark_end(lexer);
|
||||
|
||||
#define END_STATE() return result;
|
||||
|
||||
/*
|
||||
* Parse Table Macros
|
||||
*/
|
||||
|
||||
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
|
||||
|
||||
#define STATE(id) id
|
||||
|
||||
#define ACTIONS(id) id
|
||||
|
||||
#define SHIFT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value) \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_REPEAT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value), \
|
||||
.repetition = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_EXTRA() \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.extra = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define REDUCE(symbol_name, children, precedence, prod_id) \
|
||||
{{ \
|
||||
.reduce = { \
|
||||
.type = TSParseActionTypeReduce, \
|
||||
.symbol = symbol_name, \
|
||||
.child_count = children, \
|
||||
.dynamic_precedence = precedence, \
|
||||
.production_id = prod_id \
|
||||
}, \
|
||||
}}
|
||||
|
||||
#define RECOVER() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeRecover \
|
||||
}}
|
||||
|
||||
#define ACCEPT_INPUT() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeAccept \
|
||||
}}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_PARSER_H_
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"grammars": [
|
||||
{
|
||||
"name": "vbnet",
|
||||
"camelcase": "Vbnet",
|
||||
"scope": "source.vbnet",
|
||||
"path": ".",
|
||||
"file-types": ["vb"]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"version": "0.1.0-cbm.1",
|
||||
"license": "MIT",
|
||||
"description": "cbm fork of tree-sitter-vbnet @ 538b708 with CodeGraph's real-world VB.NET patch"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user