Merge pull request #2948 from DeusData/feat/vendor-vbnet-grammar

chore(vendor): vendor the VB.NET tree-sitter grammar
This commit is contained in:
Martin Vogel
2026-11-11 00:26:43 +01:00
committed by GitHub
28 changed files with 1977206 additions and 13 deletions
+5 -5
View File
@@ -5,7 +5,7 @@
[![CI](https://img.shields.io/github/actions/workflow/status/DeusData/codebase-memory-mcp/release.yml?branch=main&label=main%20CI)](https://github.com/DeusData/codebase-memory-mcp/actions/workflows/release.yml)
[![Native and frontend tests](https://img.shields.io/badge/tests-native_%2B_frontend-blue)](#tests)
[![MCP tools](https://img.shields.io/badge/MCP_tools-17-blue)](#mcp-tools)
[![Languages](https://img.shields.io/badge/languages-162-orange)](https://github.com/DeusData/codebase-memory-mcp)
[![Languages](https://img.shields.io/badge/languages-163-orange)](https://github.com/DeusData/codebase-memory-mcp)
[![Hybrid LSP](https://img.shields.io/badge/Hybrid_LSP-type_resolution-blue)](#hybrid-lsp)
[![Agents](https://img.shields.io/badge/agent_surfaces-46-purple)](https://github.com/DeusData/codebase-memory-mcp)
[![Pure C](https://img.shields.io/badge/pure_C-no_language_runtime-blue)](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
View File
@@ -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
+4
View File
@@ -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.
File diff suppressed because it is too large Load Diff
+326
View File
@@ -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 (`&amp;`, `&#38;`); 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_
+1 -1
View File
@@ -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.
+5
View File
@@ -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)",
+8 -1
View File
@@ -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
+67
View File
@@ -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);
+143
View File
@@ -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
+9
View File
@@ -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 (`&amp;`, `&#38;`); 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
+326
View File
@@ -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 (`&amp;`, `&#38;`); 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_
+16
View File
@@ -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"
}
}