chore: import upstream snapshot with attribution
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:28:05 +08:00
commit 41cb1c0170
1830 changed files with 38276124 additions and 0 deletions
+428
View File
@@ -0,0 +1,428 @@
// ac.c — Aho-Corasick multi-pattern matcher with fused LZ4 decompression.
//
// Custom implementation (~300 lines) because no permissive-licensed C AC
// libraries exist (ACISM and MultiFast are LGPL).
//
// Key design: pre-computed goto table (ACISM matrix approach) — for each
// (state, byte) pair the next state is a direct array lookup. Zero branches
// during scanning. Bitmask output for ≤64 patterns.
#include <stddef.h> // NULL
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "ac.h"
#include "foundation/compat.h"
#include "lz4_store.h"
// ─── Data structures ───────────────────────────────────────────────────────
// Maximum pattern count for bitmask mode.
#define CBM_AC_MAX_BITMASK 64
#define CBM_AC_BYTE_RANGE 256
#define CBM_AC_NO_STATE (-1)
#define CBM_AC_ROOT_STATES 1
#define CBM_AC_ALLOC_ONE 1
#define CBM_AC_PATTERN_BIT(p) (1ULL << (p))
#define CBM_AC_CLEAR_LOW_BIT(b) ((b) & ((b) - 1ULL))
// Decompression buffer alignment mask (round up to 64KB chunks).
#define DECOMP_BUF_ALIGN_MASK 0xFFFF
struct CBMAutomaton {
int num_states;
int num_patterns;
int alpha_size; // 256 for raw byte, or smaller for mapped alphabet
uint8_t alpha_map[CBM_AC_BYTE_RANGE]; // byte → mapped index (identity if alpha_size==256)
int *go_table; // [num_states * alpha_size] — pre-computed transitions
uint64_t *output; // [num_states] — bitmask of matching pattern IDs
int *output_list; // [num_states] — linked list: pattern ID or -1
int *output_next; // [num_states] — next pointer for output_list chain
};
// ─── Build ─────────────────────────────────────────────────────────────────
// Queue for BFS during failure function computation.
typedef struct {
int *data;
int head, tail, cap;
} Queue;
static void queue_init(Queue *q, int cap) {
q->data = (int *)malloc(cap * sizeof(int));
q->head = q->tail = 0;
q->cap = cap;
}
static void queue_push(Queue *q, int v) {
q->data[q->tail++] = v;
}
static int queue_pop(Queue *q) {
return q->data[q->head++];
}
static int queue_empty(Queue *q) {
return q->head >= q->tail;
}
static void queue_free(Queue *q) {
free(q->data);
}
// Phase 1: Build trie (goto function) from patterns. Returns state count.
static int ac_build_trie(CBMAutomaton *ac, const char **patterns, const int *lengths, int count) {
int alpha_size = ac->alpha_size;
int num_states = CBM_AC_ROOT_STATES; // state 0 = root
for (int p = 0; p < count; p++) {
int state = 0;
for (int j = 0; j < lengths[p]; j++) {
int c = ac->alpha_map[(unsigned char)patterns[p][j]];
int idx = (state * alpha_size) + c;
if (ac->go_table[idx] == CBM_AC_NO_STATE) {
ac->go_table[idx] = num_states++;
}
state = ac->go_table[idx];
}
if (p < CBM_AC_MAX_BITMASK) {
ac->output[state] |= CBM_AC_PATTERN_BIT(p);
}
ac->output_list[state] = p;
}
// Root self-loops for unmatched bytes.
for (int c = 0; c < alpha_size; c++) {
if (ac->go_table[c] == CBM_AC_NO_STATE) {
ac->go_table[c] = 0;
}
}
return num_states;
}
// Phase 2: Build failure function via BFS + compute full goto table.
static void ac_build_failure(CBMAutomaton *ac, int num_states) {
int alpha_size = ac->alpha_size;
int *fail = (int *)calloc(num_states, sizeof(int));
Queue q;
queue_init(&q, num_states);
for (int c = 0; c < alpha_size; c++) {
int s = ac->go_table[c];
if (s != 0) {
fail[s] = 0;
queue_push(&q, s);
}
}
while (!queue_empty(&q)) {
int r = queue_pop(&q);
for (int c = 0; c < alpha_size; c++) {
int idx = (r * alpha_size) + c;
int s = ac->go_table[idx];
if (s != CBM_AC_NO_STATE) {
fail[s] = ac->go_table[(fail[r] * alpha_size) + c];
ac->output[s] |= ac->output[fail[s]];
if (ac->output_next[s] == CBM_AC_NO_STATE &&
ac->output_list[fail[s]] != CBM_AC_NO_STATE) {
ac->output_next[s] = fail[s];
}
queue_push(&q, s);
} else {
ac->go_table[idx] = ac->go_table[(fail[r] * alpha_size) + c];
}
}
}
free(fail);
queue_free(&q);
}
// Shrink allocations to exact state count.
static void ac_shrink_tables(CBMAutomaton *ac, int num_states, int max_states) {
if (num_states >= max_states) {
return;
}
int alpha_size = ac->alpha_size;
void *tmp;
tmp = realloc(ac->go_table, (size_t)num_states * alpha_size * sizeof(int));
if (tmp) {
ac->go_table = (int *)tmp;
}
tmp = realloc(ac->output, (size_t)num_states * sizeof(uint64_t));
if (tmp) {
ac->output = (uint64_t *)tmp;
}
tmp = realloc(ac->output_list, (size_t)num_states * sizeof(int));
if (tmp) {
ac->output_list = (int *)tmp;
}
tmp = realloc(ac->output_next, (size_t)num_states * sizeof(int));
if (tmp) {
ac->output_next = (int *)tmp;
}
}
// cbm_ac_build constructs an Aho-Corasick automaton from a set of patterns.
//
// Parameters:
// patterns — array of pattern pointers (not necessarily NUL-terminated)
// lengths — length of each pattern
// count — number of patterns (max 64 for bitmask mode)
// alpha_map — byte→index mapping (NULL = identity/256). For compact alphabets,
// map relevant chars to 1..N and everything else to 0.
// alpha_size — alphabet size (256 if alpha_map is NULL)
//
// Returns a heap-allocated automaton. Caller must call cbm_ac_free().
CBMAutomaton *cbm_ac_build(const char **patterns, const int *lengths, int count,
const uint8_t *alpha_map, int alpha_size) {
if (count <= 0) {
return NULL;
}
if (alpha_size <= 0) {
alpha_size = CBM_AC_BYTE_RANGE;
}
int max_states = CBM_AC_ROOT_STATES;
for (int i = 0; i < count; i++) {
max_states += lengths[i];
}
CBMAutomaton *ac = (CBMAutomaton *)calloc(CBM_AC_ALLOC_ONE, sizeof(CBMAutomaton));
ac->alpha_size = alpha_size;
ac->num_patterns = count;
if (alpha_map) {
memcpy(ac->alpha_map, alpha_map, CBM_AC_BYTE_RANGE);
} else {
for (int i = 0; i < CBM_AC_BYTE_RANGE; i++) {
ac->alpha_map[i] = (uint8_t)i;
}
}
ac->go_table = (int *)malloc((size_t)max_states * alpha_size * sizeof(int));
memset(ac->go_table, CBM_AC_NO_STATE, (size_t)max_states * alpha_size * sizeof(int));
ac->output = (uint64_t *)calloc(max_states, sizeof(uint64_t));
ac->output_list = (int *)malloc(max_states * sizeof(int));
ac->output_next = (int *)malloc(max_states * sizeof(int));
for (int i = 0; i < max_states; i++) {
ac->output_list[i] = CBM_AC_NO_STATE;
ac->output_next[i] = CBM_AC_NO_STATE;
}
int num_states = ac_build_trie(ac, patterns, lengths, count);
ac_build_failure(ac, num_states);
ac->num_states = num_states;
ac_shrink_tables(ac, num_states, max_states);
return ac;
}
// cbm_ac_free releases all memory for an automaton.
void cbm_ac_free(CBMAutomaton *ac) {
if (!ac) {
return;
}
free(ac->go_table);
free(ac->output);
free(ac->output_list);
free(ac->output_next);
free(ac);
}
// ─── Scan functions ────────────────────────────────────────────────────────
// cbm_ac_scan_bitmask scans text through the automaton and returns a bitmask
// of all matched pattern IDs (patterns 0..63).
uint64_t cbm_ac_scan_bitmask(const CBMAutomaton *ac, const char *text, int text_len) {
uint64_t result = 0;
int state = 0;
const int alpha_size = ac->alpha_size;
const int *go_table = ac->go_table;
const uint64_t *output = ac->output;
for (int i = 0; i < text_len; i++) {
int c = ac->alpha_map[(unsigned char)text[i]];
state = go_table[(state * alpha_size) + c];
result |= output[state];
}
return result;
}
// ─── Fused LZ4 + AC scan ──────────────────────────────────────────────────
// Thread-local reusable decompression buffer to avoid repeated malloc/free.
// Each goroutine gets its own OS thread (via CGo), so CBM_TLS is safe.
static CBM_TLS char *tls_decomp_buf = NULL;
static CBM_TLS int tls_decomp_cap = 0;
static char *get_decomp_buf(int needed) {
if (needed > tls_decomp_cap) {
free(tls_decomp_buf);
// Round up to 64KB chunks for reuse.
int cap = (needed + DECOMP_BUF_ALIGN_MASK) & ~DECOMP_BUF_ALIGN_MASK;
tls_decomp_buf = (cap > 0) ? (char *)malloc((size_t)cap) : NULL;
tls_decomp_cap = cap;
}
return tls_decomp_buf;
}
// cbm_ac_scan_lz4_bitmask decompresses LZ4 data into a thread-local buffer
// and scans it through the AC automaton. Returns bitmask of matched patterns.
// Zero Go heap allocation — the decompression buffer lives in C.
uint64_t cbm_ac_scan_lz4_bitmask(const CBMAutomaton *ac, const char *compressed, int compressed_len,
int original_len) {
if (!ac || !compressed || compressed_len <= 0 || original_len <= 0) {
return 0;
}
char *buf = get_decomp_buf(original_len);
if (!buf) {
return 0;
}
int decompressed = cbm_lz4_decompress(compressed, compressed_len, buf, original_len);
if (decompressed < 0) {
return 0;
}
return cbm_ac_scan_bitmask(ac, buf, decompressed);
}
// ─── Batch LZ4 + AC scan ───────────────────────────────────────────────────
// CBMLz4Entry and CBMLz4Match defined in ac.h.
// cbm_ac_scan_lz4_batch decompresses and scans multiple files in one call.
// Returns the number of matching files written to out_matches.
// Uses a single reusable decompression buffer across all files.
int cbm_ac_scan_lz4_batch(const CBMAutomaton *ac, const CBMLz4Entry *entries, int num_entries,
CBMLz4Match *out_matches, int max_matches) {
if (!ac || !entries || num_entries <= 0) {
return 0;
}
// Allocate decompression buffer sized to the largest file.
int max_orig = 0;
for (int i = 0; i < num_entries; i++) {
if (entries[i].original_len > max_orig) {
max_orig = entries[i].original_len;
}
}
char *buf = get_decomp_buf(max_orig);
if (!buf) {
return 0;
}
const int alpha_size = ac->alpha_size;
const int *go_table = ac->go_table;
const uint64_t *output = ac->output;
int total = 0;
for (int i = 0; i < num_entries && total < max_matches; i++) {
if (!entries[i].data || entries[i].compressed_len <= 0 || entries[i].original_len <= 0) {
continue;
}
int decompressed = cbm_lz4_decompress(entries[i].data, entries[i].compressed_len, buf,
entries[i].original_len);
if (decompressed <= 0) {
continue;
}
// Inline AC scan for speed (avoid function call overhead per file).
uint64_t result = 0;
int state = 0;
for (int j = 0; j < decompressed; j++) {
int c = ac->alpha_map[(unsigned char)buf[j]];
state = go_table[(state * alpha_size) + c];
result |= output[state];
}
if (result != 0) {
out_matches[total].file_index = i;
out_matches[total].bitmask = result;
total++;
}
}
return total;
}
// ─── Batch scan for configlinker ───────────────────────────────────────────
// CBMMatchResult defined in ac.h.
// cbm_ac_scan_batch scans multiple NUL-separated names through the automaton.
// For each name, reports all unique matched pattern IDs.
// Returns total number of matches written to out_matches.
//
// Parameters:
// ac — automaton
// names_buf — concatenated names separated by NUL bytes
// name_offsets — start offset of each name in names_buf
// name_lengths — length of each name
// num_names — number of names
// out_matches — output buffer for (name_index, pattern_id) pairs
// max_matches — capacity of out_matches
int cbm_ac_scan_batch(const CBMAutomaton *ac, const char *names_buf, const int *name_offsets,
const int *name_lengths, int num_names, CBMMatchResult *out_matches,
int max_matches) {
int total = 0;
const int alpha_size = ac->alpha_size;
const int *go_table = ac->go_table;
for (int n = 0; n < num_names && total < max_matches; n++) {
const char *text = names_buf + name_offsets[n];
int text_len = name_lengths[n];
int state = 0;
// Track which patterns matched for this name (deduplicate).
uint64_t seen = 0;
for (int i = 0; i < text_len; i++) {
int c = ac->alpha_map[(unsigned char)text[i]];
state = go_table[(state * alpha_size) + c];
// Walk output chain for >64 patterns.
int s = state;
while (s > 0 && total < max_matches) {
// Bitmask fast path for first 64 patterns.
uint64_t bits = ac->output[s] & ~seen;
while (bits && total < max_matches) {
int pid = __builtin_ctzll(bits);
out_matches[total].name_index = n;
out_matches[total].pattern_id = pid;
total++;
seen |= CBM_AC_PATTERN_BIT(pid);
bits = CBM_AC_CLEAR_LOW_BIT(bits);
}
// Follow output_next for patterns beyond bitmask range.
int next_state = ac->output_next[s];
if (next_state == CBM_AC_NO_STATE || next_state == s) {
break;
}
s = next_state;
}
}
}
return total;
}
// ─── Info ──────────────────────────────────────────────────────────────────
int cbm_ac_num_states(const CBMAutomaton *ac) {
return ac ? ac->num_states : 0;
}
int cbm_ac_num_patterns(const CBMAutomaton *ac) {
return ac ? ac->num_patterns : 0;
}
// cbm_ac_table_bytes returns the approximate memory used by the goto table.
int cbm_ac_table_bytes(const CBMAutomaton *ac) {
if (!ac) {
return 0;
}
return ac->num_states * ac->alpha_size * (int)sizeof(int);
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef CBM_AC_H
#define CBM_AC_H
#include <stdint.h>
// Forward declaration — full struct in ac.c
typedef struct CBMAutomaton CBMAutomaton;
// Input for batch LZ4 scanning.
typedef struct {
const char *data;
int compressed_len;
int original_len;
} CBMLz4Entry;
// Output for batch LZ4 scanning.
typedef struct {
int file_index;
uint64_t bitmask;
} CBMLz4Match;
// Output for batch name scanning.
typedef struct {
int name_index;
int pattern_id;
} CBMMatchResult;
// Build an Aho-Corasick automaton from patterns.
CBMAutomaton *cbm_ac_build(const char **patterns, const int *lengths, int count,
const uint8_t *alpha_map, int alpha_size);
void cbm_ac_free(CBMAutomaton *ac);
// Single-text scanning (returns bitmask of matched pattern IDs).
uint64_t cbm_ac_scan_bitmask(const CBMAutomaton *ac, const char *text, int text_len);
// LZ4-compressed scanning.
uint64_t cbm_ac_scan_lz4_bitmask(const CBMAutomaton *ac, const char *compressed, int compressed_len,
int original_len);
int cbm_ac_scan_lz4_batch(const CBMAutomaton *ac, const CBMLz4Entry *entries, int num_entries,
CBMLz4Match *out_matches, int max_matches);
// Batch name scanning.
int cbm_ac_scan_batch(const CBMAutomaton *ac, const char *names_buf, const int *name_offsets,
const int *name_lengths, int num_names, CBMMatchResult *out_matches,
int max_matches);
// Introspection.
int cbm_ac_num_states(const CBMAutomaton *ac);
int cbm_ac_num_patterns(const CBMAutomaton *ac);
int cbm_ac_table_bytes(const CBMAutomaton *ac);
#endif // CBM_AC_H
+107
View File
@@ -0,0 +1,107 @@
#include "arena.h"
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
void cbm_arena_init(CBMArena *a) {
memset(a, 0, sizeof(*a));
a->block_size = CBM_ARENA_DEFAULT_BLOCK_SIZE;
a->blocks[0] = (char *)malloc(a->block_size);
if (a->blocks[0]) {
a->nblocks = SKIP_ONE;
}
}
static int arena_grow(CBMArena *a, size_t min_size) {
if (a->nblocks >= CBM_ARENA_MAX_BLOCKS) {
return 0;
}
size_t new_size = a->block_size * PAIR_LEN;
if (new_size < min_size) {
new_size = min_size;
}
char *block = (char *)malloc(new_size);
if (!block) {
return 0;
}
a->blocks[a->nblocks] = block;
a->nblocks++;
a->block_size = new_size;
a->used = 0;
return 1;
}
void *cbm_arena_alloc(CBMArena *a, size_t n) {
if (!a || n == 0) {
return NULL;
}
// 8-byte alignment
n = (n + 7) & ~(size_t)7;
if (a->nblocks == 0) {
return NULL;
}
if (a->used + n > a->block_size) {
if (!arena_grow(a, n)) {
return NULL;
}
}
char *ptr = a->blocks[a->nblocks - SKIP_ONE] + a->used;
a->used += n;
return ptr;
}
char *cbm_arena_strdup(CBMArena *a, const char *s) {
if (!s)
return NULL;
size_t len = strlen(s);
char *dst = (char *)cbm_arena_alloc(a, len + SKIP_ONE);
if (dst) {
memcpy(dst, s, len + SKIP_ONE);
}
return dst;
}
char *cbm_arena_strndup(CBMArena *a, const char *s, size_t len) {
if (!s)
return NULL;
char *dst = (char *)cbm_arena_alloc(a, len + SKIP_ONE);
if (dst) {
memcpy(dst, s, len);
dst[len] = '\0';
}
return dst;
}
char *cbm_arena_sprintf(CBMArena *a, const char *fmt, ...) {
// First pass: compute length
va_list args;
va_start(args, fmt);
int needed = vsnprintf(NULL, 0, fmt, args);
va_end(args);
if (needed < 0) {
return NULL;
}
char *dst = (char *)cbm_arena_alloc(a, (size_t)needed + SKIP_ONE);
if (!dst) {
return NULL;
}
va_start(args, fmt);
vsnprintf(dst, (size_t)needed + SKIP_ONE, fmt, args);
va_end(args);
return dst;
}
void cbm_arena_destroy(CBMArena *a) {
for (int i = 0; i < a->nblocks; i++) {
free(a->blocks[i]);
}
memset(a, 0, sizeof(*a));
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef CBM_ARENA_H
#define CBM_ARENA_H
#include <stddef.h>
// CBMArena is a simple bump allocator that allocates from fixed-size blocks.
// All memory is freed at once via cbm_arena_destroy(). Individual frees are not
// supported — this is by design for per-file extraction where all data has the
// same lifetime.
#define CBM_ARENA_MAX_BLOCKS 256
#define CBM_ARENA_DEFAULT_BLOCK_SIZE (64 * 1024) // 64KB initial
typedef struct {
char *blocks[CBM_ARENA_MAX_BLOCKS];
size_t block_sizes[CBM_ARENA_MAX_BLOCKS]; // per-block sizes (for stats)
int nblocks;
size_t block_size;
size_t used; // bytes used in current block
size_t total_alloc; // cumulative bytes allocated (for stats)
} CBMArena;
// Initialize an arena with the default block size.
void cbm_arena_init(CBMArena *a);
// Allocate n bytes from the arena. Returns NULL on OOM or block exhaustion.
// All returned pointers are 8-byte aligned.
void *cbm_arena_alloc(CBMArena *a, size_t n);
// Duplicate a string into arena memory. Returns arena-owned copy.
char *cbm_arena_strdup(CBMArena *a, const char *s);
// Duplicate a string of known length into arena memory. NUL-terminates.
char *cbm_arena_strndup(CBMArena *a, const char *s, size_t len);
// sprintf into arena memory. Returns arena-owned string.
char *cbm_arena_sprintf(CBMArena *a, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
// Free all blocks. Arena is invalid after this call.
void cbm_arena_destroy(CBMArena *a);
#endif // CBM_ARENA_H
+1347
View File
File diff suppressed because it is too large Load Diff
+650
View File
@@ -0,0 +1,650 @@
#ifndef CBM_H
#define CBM_H
#include <stdint.h>
#include <stdbool.h>
#include "arena.h"
#include "tree_sitter/api.h"
// Language enum mirrors lang.Language in Go.
// Order must match lang_specs.c tables.
typedef enum {
CBM_LANG_GO = 0,
CBM_LANG_PYTHON,
CBM_LANG_JAVASCRIPT,
CBM_LANG_TYPESCRIPT,
CBM_LANG_TSX,
CBM_LANG_RUST,
CBM_LANG_JAVA,
CBM_LANG_CPP,
CBM_LANG_CSHARP,
CBM_LANG_PHP,
CBM_LANG_LUA,
CBM_LANG_SCALA,
CBM_LANG_KOTLIN,
CBM_LANG_RUBY,
CBM_LANG_C,
CBM_LANG_BASH,
CBM_LANG_ZIG,
CBM_LANG_ELIXIR,
CBM_LANG_HASKELL,
CBM_LANG_OCAML,
CBM_LANG_OBJC,
CBM_LANG_SWIFT,
CBM_LANG_DART,
CBM_LANG_PERL,
CBM_LANG_GROOVY,
CBM_LANG_ERLANG,
CBM_LANG_R,
CBM_LANG_HTML,
CBM_LANG_CSS,
CBM_LANG_SCSS,
CBM_LANG_YAML,
CBM_LANG_TOML,
CBM_LANG_HCL,
CBM_LANG_SQL,
CBM_LANG_DOCKERFILE,
// New languages (v0.5 expansion)
CBM_LANG_CLOJURE,
CBM_LANG_FSHARP,
CBM_LANG_JULIA,
CBM_LANG_VIMSCRIPT,
CBM_LANG_NIX,
CBM_LANG_COMMONLISP,
CBM_LANG_ELM,
CBM_LANG_FORTRAN,
CBM_LANG_CUDA,
CBM_LANG_COBOL,
CBM_LANG_VERILOG,
CBM_LANG_EMACSLISP,
CBM_LANG_JSON,
CBM_LANG_XML,
CBM_LANG_MARKDOWN,
CBM_LANG_MAKEFILE,
CBM_LANG_CMAKE,
CBM_LANG_PROTOBUF,
CBM_LANG_GRAPHQL,
CBM_LANG_VUE,
CBM_LANG_SVELTE,
CBM_LANG_MESON,
CBM_LANG_GLSL,
CBM_LANG_INI,
// Scientific/math languages
CBM_LANG_MATLAB,
CBM_LANG_LEAN,
CBM_LANG_FORM,
CBM_LANG_MAGMA,
CBM_LANG_WOLFRAM,
CBM_LANG_SOLIDITY,
CBM_LANG_TYPST,
CBM_LANG_GDSCRIPT,
CBM_LANG_GLEAM,
CBM_LANG_POWERSHELL,
CBM_LANG_PASCAL,
CBM_LANG_DLANG,
CBM_LANG_NIM,
CBM_LANG_SCHEME,
CBM_LANG_FENNEL,
CBM_LANG_FISH,
CBM_LANG_AWK,
CBM_LANG_ZSH,
CBM_LANG_TCL,
CBM_LANG_ADA,
CBM_LANG_AGDA,
CBM_LANG_RACKET,
CBM_LANG_ODIN,
CBM_LANG_RESCRIPT,
CBM_LANG_PURESCRIPT,
CBM_LANG_NICKEL,
CBM_LANG_CRYSTAL,
CBM_LANG_TEAL,
CBM_LANG_HARE,
CBM_LANG_PONY,
CBM_LANG_LUAU,
CBM_LANG_JANET,
CBM_LANG_SWAY,
CBM_LANG_NASM,
CBM_LANG_ASSEMBLY,
CBM_LANG_ASTRO,
CBM_LANG_BLADE,
CBM_LANG_JUST,
CBM_LANG_GOTEMPLATE,
CBM_LANG_TEMPL,
CBM_LANG_LIQUID,
CBM_LANG_JINJA2,
CBM_LANG_PRISMA,
CBM_LANG_HYPRLANG,
CBM_LANG_DOTENV,
CBM_LANG_DIFF,
CBM_LANG_WGSL,
CBM_LANG_KDL,
CBM_LANG_JSON5,
CBM_LANG_JSONNET,
CBM_LANG_RON,
CBM_LANG_THRIFT,
CBM_LANG_CAPNP,
CBM_LANG_PROPERTIES,
CBM_LANG_SSHCONFIG,
CBM_LANG_BIBTEX,
CBM_LANG_STARLARK,
CBM_LANG_BICEP,
CBM_LANG_CSV,
CBM_LANG_REQUIREMENTS,
CBM_LANG_HLSL,
CBM_LANG_VHDL,
CBM_LANG_SYSTEMVERILOG,
CBM_LANG_DEVICETREE,
CBM_LANG_LINKERSCRIPT,
CBM_LANG_GN,
CBM_LANG_KCONFIG,
CBM_LANG_BITBAKE,
CBM_LANG_SMALI,
CBM_LANG_TABLEGEN,
CBM_LANG_ISPC,
CBM_LANG_CAIRO,
CBM_LANG_MOVE,
CBM_LANG_SQUIRREL,
CBM_LANG_FUNC,
CBM_LANG_REGEX,
CBM_LANG_JSDOC,
CBM_LANG_RST,
CBM_LANG_BEANCOUNT,
CBM_LANG_MERMAID,
CBM_LANG_PUPPET,
CBM_LANG_PO,
CBM_LANG_GITATTRIBUTES,
CBM_LANG_GITIGNORE,
CBM_LANG_SLANG,
CBM_LANG_LLVM_IR,
CBM_LANG_SMITHY,
CBM_LANG_WIT,
CBM_LANG_TLAPLUS,
CBM_LANG_PKL,
CBM_LANG_GOMOD,
CBM_LANG_APEX,
CBM_LANG_SOQL,
CBM_LANG_SOSL,
CBM_LANG_KUSTOMIZE, // kustomization.yaml — Kubernetes overlay tool
CBM_LANG_K8S, // Generic Kubernetes manifest (apiVersion: detected)
CBM_LANG_PINE, // Pine Script (TradingView indicator / strategy language)
CBM_LANG_QML, // Qt QML (Qt Modeling Language — declarative UI + embedded JS)
CBM_LANG_CFSCRIPT, // CFML script dialect (.cfc components — Lucee/ColdFusion)
CBM_LANG_CFML, // CFML tag dialect (.cfm templates — Lucee/ColdFusion)
CBM_LANG_MOJO, // Mojo
CBM_LANG_COUNT
} CBMLanguage;
// --- Extraction result structs ---
typedef struct {
const char *name; // short name
const char *qualified_name; // project.path.name
const char *label; // "Function", "Method", "Class", "Variable", "Module"
const char *file_path; // relative path
uint32_t start_line;
uint32_t end_line;
const char *signature; // parameter text (NULL if none)
const char *return_type; // return type text (NULL if none)
const char *receiver; // Go method receiver (NULL if none)
const char *docstring; // leading doc comment (NULL if none)
const char *parent_class; // enclosing class QN for methods (NULL if none)
const char **decorators; // NULL-terminated array (NULL if none)
const char **base_classes; // NULL-terminated array (NULL if none)
const char **param_names; // NULL-terminated array (NULL if none)
const char **param_types; // NULL-terminated array (NULL if none)
const char **return_types; // NULL-terminated array (NULL if none)
const char *route_path; // HTTP route path from decorator (e.g., "/api/users") or NULL
const char *route_method; // HTTP method from decorator (e.g., "POST") or NULL
int complexity; // cyclomatic complexity
int cognitive; // cognitive complexity (nesting-weighted)
int loop_count; // number of loop constructs in the body
int loop_depth; // max nested-loop depth (bottleneck proxy)
bool is_recursive; // body contains a direct self-call (seed for "recursive")
int param_count; // number of parameters (large = complexity smell)
int max_access_depth; // deepest chained member/subscript access (a.b.c.d)
int linear_scan_in_loop; // count of linear-scan calls (find/contains/indexOf) inside loops
int alloc_in_loop; // count of allocation/append calls inside loops
bool recursion_in_loop; // a self-call occurs inside a loop body
bool unguarded_recursion; // recursive with no self-call guarded by a conditional
int lines; // body line count
uint32_t *fingerprint; // MinHash fingerprint (arena-allocated, K values) or NULL
int fingerprint_k; // number of hash values (CBM_MINHASH_K or 0)
bool is_exported;
bool is_abstract;
bool is_test;
bool is_entry_point;
const char *structural_profile; // AST structural profile (arena-allocated) or NULL
const char *body_tokens; // space-separated raw identifier tokens from body (arena) or NULL
} CBMDefinition;
/* Argument captured from a call expression */
typedef struct {
const char *expr; // raw expression text ("payload.info", "MY_URL", "'hello'")
const char *value; // resolved string value or NULL (constant propagation)
const char *keyword; // keyword name if keyword arg ("url", "topic_id"), NULL if positional
int index; // positional index (0-based)
} CBMCallArg;
#define CBM_MAX_CALL_ARGS 8
typedef struct {
const char *callee_name; // raw callee text ("pkg.Func", "foo")
const char *enclosing_func_qn; // QN of enclosing function (or module QN)
const char *first_string_arg; // first string literal argument (URL, topic, key) or NULL
const char *second_arg_name; // second argument identifier (handler ref) or NULL
CBMCallArg args[CBM_MAX_CALL_ARGS]; // first N arguments with expressions
int arg_count; // number of captured arguments
int loop_depth; // enclosing loop nesting at the call site
int branch_depth; // enclosing branch nesting at the call site
int start_line; // 1-based source line of the call (for def range-match)
bool is_method; // method/member call with a non-self receiver. Perl:
// arrow/method call ($obj->m). TS/JS/TSX: member call
// x.foo() whose receiver is not this/super. Default false.
} CBMCall;
typedef struct {
const char *local_name; // local alias or name
const char *module_path; // resolved module path / QN
} CBMImport;
typedef struct {
const char *ref_name; // referenced identifier
const char *enclosing_func_qn; // QN of enclosing function (or module QN)
} CBMUsage;
typedef struct {
const char *exception_name; // exception class/type name
const char *enclosing_func_qn; // QN of enclosing function
} CBMThrow;
typedef struct {
const char *var_name; // variable name
const char *enclosing_func_qn; // QN of enclosing function
bool is_write; // true = write, false = read
} CBMReadWrite;
typedef struct {
const char *type_name; // referenced type/class name
const char *enclosing_func_qn; // QN of enclosing function
} CBMTypeRef;
typedef struct {
const char *env_key; // environment variable key
const char *enclosing_func_qn; // QN of enclosing function
} CBMEnvAccess;
typedef struct {
const char *var_name; // variable being assigned
const char *type_name; // class/type name of RHS constructor
const char *enclosing_func_qn; // QN of enclosing function
} CBMTypeAssign;
// String reference: URL, config key, or async target found in source.
// Extracted from string literals during AST walk.
typedef enum {
CBM_STRREF_URL = 0, // REST path or full URL
CBM_STRREF_CONFIG = 1, // config file path or env var key
} CBMStringRefKind;
typedef struct {
const char *value; // the string literal content
const char *enclosing_func_qn; // QN of enclosing function
const char *key_path; // dotted key path from YAML/JSON nesting (NULL if flat)
CBMStringRefKind kind; // URL, CONFIG
} CBMStringRef;
/* Infrastructure binding: topic/queue → endpoint URL.
* Extracted from YAML/HCL/JSON subscription/scheduler configs.
* Used by pass_route_nodes to connect async Route nodes to handler services. */
typedef struct {
const char *source_name; // topic, queue, or schedule name
const char *target_url; // push_endpoint, uri, or http_target URL
const char *broker; // "pubsub", "cloud_tasks", "cloud_scheduler", "sqs", "kafka"
} CBMInfraBinding;
/* Pub/sub channel participation. One record per emit() or on()/addListener()
* call detected in source — the receiver (e.g. Socket.IO client, EventEmitter
* instance) is intentionally NOT identified; matching is by channel_name
* across files, which captures the common pattern of one logical bus per
* service. Transport disambiguates Socket.IO vs EventEmitter vs future
* detectors (Kafka, Cloud Pub/Sub, etc.). */
typedef enum {
CBM_CHANNEL_EMIT = 0,
CBM_CHANNEL_LISTEN = 1,
} CBMChannelDirection;
typedef struct {
const char *channel_name; // literal channel name (e.g. "user.created")
const char *transport; // "socketio", "event_emitter", ...
const char *enclosing_func_qn; // QN of the function containing the emit/on call
CBMChannelDirection direction;
} CBMChannel;
// Rust: impl Trait for Struct
typedef struct {
const char *trait_name; // trait name (raw text)
const char *struct_name; // struct/type name (raw text)
} CBMImplTrait;
// LSP-resolved call: high-confidence type-aware call resolution
typedef struct {
const char *caller_qn; // enclosing function QN
const char *callee_qn; // resolved target QN (fully qualified)
const char *strategy; // "lsp_type_dispatch", "lsp_direct", etc.
float confidence; // 0.90-0.95
const char *reason; // diagnostic label for unresolved calls (NULL if resolved)
} CBMResolvedCall;
typedef struct {
CBMResolvedCall *items;
int count;
int cap;
} CBMResolvedCallArray;
// Growable arrays used during extraction.
typedef struct {
CBMDefinition *items;
int count;
int cap;
} CBMDefArray;
typedef struct {
CBMCall *items;
int count;
int cap;
} CBMCallArray;
typedef struct {
CBMImport *items;
int count;
int cap;
} CBMImportArray;
typedef struct {
CBMUsage *items;
int count;
int cap;
} CBMUsageArray;
typedef struct {
CBMThrow *items;
int count;
int cap;
} CBMThrowArray;
typedef struct {
CBMReadWrite *items;
int count;
int cap;
} CBMRWArray;
typedef struct {
CBMTypeRef *items;
int count;
int cap;
} CBMTypeRefArray;
typedef struct {
CBMEnvAccess *items;
int count;
int cap;
} CBMEnvAccessArray;
typedef struct {
CBMTypeAssign *items;
int count;
int cap;
} CBMTypeAssignArray;
typedef struct {
CBMStringRef *items;
int count;
int cap;
} CBMStringRefArray;
typedef struct {
CBMInfraBinding *items;
int count;
int cap;
} CBMInfraBindingArray;
typedef struct {
CBMImplTrait *items;
int count;
int cap;
} CBMImplTraitArray;
typedef struct {
CBMChannel *items;
int count;
int cap;
} CBMChannelArray;
// Full extraction result for one file.
typedef struct {
CBMArena arena; // owns all string memory
CBMDefArray defs;
CBMCallArray calls;
CBMImportArray imports;
CBMUsageArray usages;
CBMThrowArray throws;
CBMRWArray rw;
CBMTypeRefArray type_refs;
CBMEnvAccessArray env_accesses;
CBMTypeAssignArray type_assigns;
CBMImplTraitArray impl_traits; // Rust: impl Trait for Struct pairs
CBMResolvedCallArray resolved_calls; // LSP-resolved calls (high confidence)
CBMStringRefArray string_refs; // URL/config string literals from AST
CBMInfraBindingArray infra_bindings; // topic→URL pairs from IaC configs
CBMChannelArray channels; // Socket.IO / EventEmitter pub/sub participation
const char *module_qn; // module qualified name
const char *namespace_name; // declared namespace/package (Java/Kotlin/C#/PHP), NULL if none
const char **exports; // NULL-terminated (NULL if none)
const char **constants; // NULL-terminated (NULL if none)
const char **global_vars; // NULL-terminated (NULL if none)
const char **macros; // NULL-terminated, C/C++ only (NULL if none)
bool has_error;
const char *error_msg;
/* Best-effort parse-coverage signal (experimental). parse_incomplete is true
* when the parse tree contains tree-sitter ERROR/MISSING nodes — constructs
* in those regions are silently absent from the graph. error_ranges is a
* compact "start-end,start-end" list of 1-based line ranges (arena-owned) or
* NULL. This only marks what we can DETECT: the absence of a flag is NOT a
* completeness guarantee. Callers should treat a flagged file as "prefer
* grep here", never treat an unflagged file as provably complete. */
bool parse_incomplete;
const char *error_ranges;
int error_region_count;
bool is_test_file;
int imports_count;
TSTree *cached_tree; // retained parse tree (caller frees via cbm_free_tree)
CBMLanguage cached_lang; // language of cached tree (for parser selection)
// Retained source bytes — copied into `arena` by the parallel
// extract pass so the fused cross-file LSP step in resolve_worker
// can run without re-reading the file from disk. NULL when the
// file exceeded the per-file (100 MB) or total (2 GB) retention
// cap; in that case the cross-file LSP step is skipped for this
// file (defs/calls already extracted are unaffected).
const char *source;
int source_len;
} CBMFileResult;
// --- Enclosing function cache ---
// Avoids repeated parent-chain walks for nodes within the same function body.
// Each entry records a function's byte range and its precomputed QN.
#define EFC_SIZE 64 // power of 2 for fast modulo
typedef struct {
uint32_t start_byte;
uint32_t end_byte;
const char *qn;
} EFCEntry;
typedef struct {
EFCEntry entries[EFC_SIZE];
int count;
} EFCache;
// --- Extraction context passed to sub-extractors ---
// Module-level string constant map (for constant propagation)
#define CBM_MAX_STRING_CONSTANTS 256
typedef struct {
const char *names[CBM_MAX_STRING_CONSTANTS];
const char *values[CBM_MAX_STRING_CONSTANTS];
int count;
} CBMStringConstantMap;
typedef struct {
CBMArena *arena;
CBMFileResult *result;
const char *source;
int source_len;
CBMLanguage language;
const char *project;
const char *rel_path;
const char *module_qn;
TSNode root;
EFCache ef_cache; // enclosing function cache
const char *enclosing_class_qn; // for nested class QN computation
CBMStringConstantMap string_constants; // module-level NAME = "value" pairs
} CBMExtractCtx;
// --- Public API ---
// Bind third-party allocators (tree-sitter, sqlite3) to mimalloc as
// defense-in-depth, so they never depend on the fragile MI_OVERRIDE symbol
// override (#424). MUST be called as the very first statement of main(), before
// any sqlite3_open*/sqlite3_initialize (SQLITE_CONFIG_MALLOC returns
// SQLITE_MISUSE once sqlite has initialized).
// Idempotent (static guard); intended for single-threaded startup. cbm_init()
// also calls it so non-main entry points (pipeline passes) still get the binds.
// In the test build (no CBM_BIND_TS_ALLOCATOR) this is a no-op.
void cbm_alloc_init(void);
// Initialize the library. Call once at startup. Returns 0 on success.
int cbm_init(void);
// True when rel_path is in the crash-quarantine set — the newline-delimited list
// of files (CBM_INDEX_QUARANTINE_FILE) the crash supervisor pinned as crashers
// during its single-threaded recovery re-run. Loaded once, lazily; read-only
// after load. cbm_extract_file short-circuits such files to an empty result so no
// pass can crash on them; the pipeline extract loops call this to also REPORT the
// skip as phase="crash". Always false (cheap no-op) when the env var is unset.
bool cbm_index_is_quarantined(const char *rel_path);
// Phase a quarantined file was pinned under: "crash" (a fault signal) or "hang"
// (killed for making no progress). Returns NULL when rel_path is not quarantined.
// Drives the same lazy once-load as cbm_index_is_quarantined. Used by the pipeline
// extract loops to report the skip's phase in skipped[] (falls back to "crash").
const char *cbm_index_quarantine_phase(const char *rel_path);
// Crash-supervisor marker journal (parallel-safe): appends "S <rel_path>" /
// "D <rel_path>" to CBM_INDEX_MARKER_FILE. Files with an S but no D form the
// parent's crash/hang suspect set. No-ops when the env var is unset.
// cbm_extract_file journals its own start/done; long-running per-file phases
// (cross-LSP resolve) call these around their per-file work so a hang there
// is attributed to the RIGHT file instead of a stale extraction marker.
void cbm_index_mark_start(const char *rel_path);
void cbm_index_mark_done(const char *rel_path);
// Extract all data from one file. Caller must call cbm_free_result().
// source must remain valid for the duration of the call.
// timeout_micros: per-file parse timeout in microseconds (0 = no timeout).
CBMFileResult *cbm_extract_file(const char *source, int source_len, CBMLanguage language,
const char *project, const char *rel_path, int64_t timeout_micros,
const char **extra_defines, // NULL-terminated, or NULL
const char **include_paths // NULL-terminated, or NULL
);
// Free all memory associated with a result.
void cbm_free_result(CBMFileResult *result);
// Free only the cached tree from a result (caller retained it for reuse).
void cbm_free_tree(CBMFileResult *result);
// Free a standalone TSTree pointer (for Go layer cleanup).
void cbm_free_tree_ptr(TSTree *tree);
// Reset the thread-local parser's internal state, releasing slab-allocated
// subtrees. Must be called BEFORE cbm_slab_reset_thread() so the slab rebuild
// doesn't corrupt live parser state.
void cbm_reset_thread_parser(void);
// Destroy the thread-local parser. Call on worker thread exit.
void cbm_destroy_thread_parser(void);
// Shutdown the library. Call once at exit.
void cbm_shutdown(void);
// Profiling: get accumulated parse/extraction times and file count.
typedef struct {
uint64_t *parse_ns;
uint64_t *extract_ns;
uint64_t *files;
} cbm_profile_out_t;
void cbm_get_profile(cbm_profile_out_t out);
uint64_t cbm_get_lsp_ns(void);
uint64_t cbm_get_preprocess_ns(void);
uint64_t cbm_get_files_preprocessed(void);
void cbm_reset_profile(void);
// Toggle C/C++ preprocessor Macro-node extraction (#375). The pipeline enables
// it only for full/advanced index modes (it dominates extraction on macro-dense
// codebases). Default ON. Set before extraction; read-only during.
void cbm_set_macro_extraction(int enabled);
int cbm_macro_extraction_enabled(void);
// --- Internal helpers used by extractors ---
// Growable array push functions (arena-allocated, no individual free needed).
void cbm_defs_push(CBMDefArray *arr, CBMArena *a, CBMDefinition def);
void cbm_calls_push(CBMCallArray *arr, CBMArena *a, CBMCall call);
void cbm_imports_push(CBMImportArray *arr, CBMArena *a, CBMImport imp);
void cbm_usages_push(CBMUsageArray *arr, CBMArena *a, CBMUsage usage);
void cbm_throws_push(CBMThrowArray *arr, CBMArena *a, CBMThrow thr);
void cbm_rw_push(CBMRWArray *arr, CBMArena *a, CBMReadWrite rw);
void cbm_typerefs_push(CBMTypeRefArray *arr, CBMArena *a, CBMTypeRef tr);
void cbm_envaccess_push(CBMEnvAccessArray *arr, CBMArena *a, CBMEnvAccess ea);
void cbm_typeassign_push(CBMTypeAssignArray *arr, CBMArena *a, CBMTypeAssign ta);
void cbm_stringref_push(CBMStringRefArray *arr, CBMArena *a, CBMStringRef sr);
void cbm_infrabinding_push(CBMInfraBindingArray *arr, CBMArena *a, CBMInfraBinding ib);
void cbm_impltrait_push(CBMImplTraitArray *arr, CBMArena *a, CBMImplTrait it);
void cbm_resolvedcall_push(CBMResolvedCallArray *arr, CBMArena *a, CBMResolvedCall rc);
void cbm_channels_push(CBMChannelArray *arr, CBMArena *a, CBMChannel ch);
// --- Sub-extractor entry points ---
void cbm_extract_definitions(CBMExtractCtx *ctx);
void cbm_extract_imports(CBMExtractCtx *ctx);
void cbm_extract_usages(CBMExtractCtx *ctx);
void cbm_extract_semantic(CBMExtractCtx *ctx);
void cbm_extract_type_refs(CBMExtractCtx *ctx);
void cbm_extract_env_accesses(CBMExtractCtx *ctx);
void cbm_extract_type_assigns(CBMExtractCtx *ctx);
void cbm_extract_channels(CBMExtractCtx *ctx);
// Single-pass unified extraction (replaces the 7 calls above except defs+imports).
void cbm_extract_unified(CBMExtractCtx *ctx);
// K8s / Kustomize semantic extractor (called when language is CBM_LANG_K8S or CBM_LANG_KUSTOMIZE).
void cbm_extract_k8s(CBMExtractCtx *ctx);
// --- Label predicates ---
// True when `label` names a TYPE-LIKE container definition — a node that can own
// methods/fields, be a base/embedded type, satisfy/declare an interface, and be a
// target of name→type resolution. The canonical set is:
// Class, Struct, Interface, Enum, Type, Trait.
// Single source of truth for every type-resolution / registry-seeding /
// INHERITS·IMPLEMENTS / LSP-type-registrar consumer, so adding a new type-like
// label (e.g. "Struct" for Rust/Go/Swift/D structs) updates them all at once
// instead of scattering `|| strcmp(label,"Struct")==0` across the tree.
// `label` may be NULL (returns false). Defined in helpers.c.
bool cbm_label_is_type_like(const char *label);
#endif // CBM_H
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
+211
View File
@@ -0,0 +1,211 @@
#include "cbm.h"
#include "arena.h" // CBMArena, cbm_arena_strndup
#include "helpers.h"
#include "lang_specs.h"
#include "extract_unified.h"
#include "foundation/constants.h"
#include "extract_node_stack.h"
#include "tree_sitter/api.h" // TSNode, ts_node_*
#include <stdint.h> // uint32_t
#include <string.h>
#include <ctype.h>
/* Minimum length for an env var name (e.g., "DB"). */
enum { MIN_ENV_NAME_LEN = 2 };
// Unquote a string literal: "foo" -> foo, 'foo' -> foo
static const char *unquote(CBMArena *a, const char *s) {
if (!s || !s[0]) {
return NULL;
}
// Trim whitespace
while (*s == ' ' || *s == '\t') {
s++;
}
size_t len = strlen(s);
if (len >= CBM_QUOTE_PAIR) {
char first = s[0];
char last = s[len - CBM_QUOTE_OFFSET];
if ((first == '"' && last == '"') || (first == '\'' && last == '\'') ||
(first == '`' && last == '`')) {
return cbm_arena_strndup(a, s + CBM_QUOTE_OFFSET, len - CBM_QUOTE_PAIR);
}
}
return s;
}
// Extract env key from a function call like os.Getenv("KEY").
static const char *extract_env_key_from_call(CBMExtractCtx *ctx, TSNode node,
const CBMLangSpec *spec) {
if (!spec->env_access_functions || !spec->env_access_functions[0]) {
return NULL;
}
TSNode func_node = ts_node_child_by_field_name(node, TS_FIELD("function"));
if (ts_node_is_null(func_node)) {
return NULL;
}
char *callee = cbm_node_text(ctx->arena, func_node, ctx->source);
if (!callee) {
return NULL;
}
// Check if callee matches any env access function
bool match = false;
for (const char **ef = spec->env_access_functions; *ef; ef++) {
if (strcmp(callee, *ef) == 0) {
match = true;
break;
}
}
if (!match) {
return NULL;
}
// Get first argument (the env key)
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
if (ts_node_is_null(args)) {
return NULL;
}
// Find first named child (skip parentheses)
for (uint32_t i = 0; i < ts_node_child_count(args); i++) {
TSNode child = ts_node_child(args, i);
const char *ck = ts_node_type(child);
if (strcmp(ck, "(") == 0 || strcmp(ck, ")") == 0 || strcmp(ck, ",") == 0) {
continue;
}
char *arg_text = cbm_node_text(ctx->arena, child, ctx->source);
return unquote(ctx->arena, arg_text);
}
return NULL;
}
// Extract env key from member access like process.env.KEY or os.environ["KEY"].
static const char *extract_env_key_from_member(CBMExtractCtx *ctx, TSNode node,
const CBMLangSpec *spec) {
if (!spec->env_access_member_patterns || !spec->env_access_member_patterns[0]) {
return NULL;
}
char *text = cbm_node_text(ctx->arena, node, ctx->source);
if (!text || !text[0]) {
return NULL;
}
for (const char **pat = spec->env_access_member_patterns; *pat; pat++) {
size_t plen = strlen(*pat);
// Dot access: pattern.KEY
if (strncmp(text, *pat, plen) == 0 && text[plen] == '.') {
const char *key = text + plen + CBM_QUOTE_OFFSET;
// Validate: no further dots/brackets
if (key[0] && !strchr(key, '.') && !strchr(key, '[')) {
return key;
}
}
// Subscript: pattern["KEY"]
if (strncmp(text, *pat, plen) == 0 && text[plen] == '[') {
const char *inner = text + plen + CBM_QUOTE_OFFSET;
size_t ilen = strlen(inner);
if (ilen > 0 && inner[ilen - CBM_QUOTE_OFFSET] == ']') {
char *bracket_content =
cbm_arena_strndup(ctx->arena, inner, ilen - CBM_QUOTE_OFFSET);
return unquote(ctx->arena, bracket_content);
}
}
}
return NULL;
}
// Check if an env key name looks like an environment variable (uppercase + underscores).
static bool is_env_var_name(const char *s) {
if (!s || strlen(s) < MIN_ENV_NAME_LEN) {
return false;
}
bool has_upper = false;
for (const char *p = s; *p; p++) {
if (*p >= 'A' && *p <= 'Z') {
{
has_upper = true;
}
} else if (*p == '_' || (*p >= '0' && *p <= '9')) { /* ok */
} else {
{ return false; }
}
}
return has_upper;
}
// Iterative env access walker — explicit stack
static void walk_env_accesses(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
TSNodeStack stack;
ts_nstack_init(&stack, ctx->arena, 4096);
ts_nstack_push(&stack, ctx->arena, root);
while (stack.count > 0) {
TSNode node = ts_nstack_pop(&stack);
const char *kind = ts_node_type(node);
const char *env_key = NULL;
if (cbm_kind_in_set(node, spec->call_node_types)) {
env_key = extract_env_key_from_call(ctx, node, spec);
} else if (strcmp(kind, "member_expression") == 0 || strcmp(kind, "subscript") == 0 ||
strcmp(kind, "attribute") == 0) {
env_key = extract_env_key_from_member(ctx, node, spec);
}
if (env_key && env_key[0] && is_env_var_name(env_key)) {
CBMEnvAccess ea;
ea.env_key = env_key;
ea.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, node);
cbm_envaccess_push(&ctx->result->env_accesses, ctx->arena, ea);
continue; // don't push children (avoid double-counting)
}
ts_nstack_push_children(&stack, ctx->arena, node);
}
}
void cbm_extract_env_accesses(CBMExtractCtx *ctx) {
const CBMLangSpec *spec = cbm_lang_spec(ctx->language);
if (!spec) {
return;
}
bool has_funcs = spec->env_access_functions && spec->env_access_functions[0];
bool has_members = spec->env_access_member_patterns && spec->env_access_member_patterns[0];
if (!has_funcs && !has_members) {
return;
}
walk_env_accesses(ctx, ctx->root, spec);
}
// --- Unified handler ---
void handle_env_accesses(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
WalkState *state) {
bool has_funcs = spec->env_access_functions && spec->env_access_functions[0];
bool has_members = spec->env_access_member_patterns && spec->env_access_member_patterns[0];
if (!has_funcs && !has_members) {
return;
}
const char *kind = ts_node_type(node);
const char *env_key = NULL;
if (has_funcs && spec->call_node_types && cbm_kind_in_set(node, spec->call_node_types)) {
env_key = extract_env_key_from_call(ctx, node, spec);
} else if (has_members && (strcmp(kind, "member_expression") == 0 ||
strcmp(kind, "subscript") == 0 || strcmp(kind, "attribute") == 0)) {
env_key = extract_env_key_from_member(ctx, node, spec);
}
if (env_key && env_key[0] && is_env_var_name(env_key)) {
CBMEnvAccess ea;
ea.env_key = env_key;
ea.enclosing_func_qn = state->enclosing_func_qn;
cbm_envaccess_push(&ctx->result->env_accesses, ctx->arena, ea);
}
}
File diff suppressed because it is too large Load Diff
+360
View File
@@ -0,0 +1,360 @@
// extract_k8s.c — K8s manifest and Kustomize file extractor.
//
// For CBM_LANG_KUSTOMIZE: walks top-level block_mapping_pair nodes whose key
// matches "resources", "bases", "patches", "components", or
// "patchesStrategicMerge", then emits one CBMImport per block_sequence item.
//
// For CBM_LANG_K8S: finds apiVersion, kind, and metadata.name scalars in the
// first document's block_mapping and emits one CBMDefinition with label
// "Resource" and name "Kind/metadata-name".
#include "cbm.h"
#include "arena.h"
#include "helpers.h"
#include "tree_sitter/api.h"
#include "foundation/constants.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* Local constants. */
enum {
K8S_BUF_SIZE = 256,
RESULT_BUF_SIZE = 512,
};
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
// Return the raw source text for a scalar node (plain, single-quoted, or
// double-quoted). Surrounding quote characters are stripped for quoted forms.
// Handles flow_node wrappers transparently by descending into the first named
// child (the tree-sitter YAML grammar often wraps scalars in flow_node).
// Returns NULL for non-scalar node types.
static const char *get_scalar_text(CBMArena *a, TSNode node, const char *source) {
enum { MAX_UNWRAP = 4 };
for (int depth = 0; depth < MAX_UNWRAP; depth++) {
const char *type = ts_node_type(node);
if (strcmp(type, "flow_node") == 0) {
TSNode inner = ts_node_named_child(node, 0);
if (ts_node_is_null(inner)) {
return NULL;
}
node = inner;
continue;
}
if (strcmp(type, "plain_scalar") == 0) {
return cbm_node_text(a, node, source);
}
if (strcmp(type, "double_quote_scalar") == 0 || strcmp(type, "single_quote_scalar") == 0) {
const char *raw = cbm_node_text(a, node, source);
if (!raw) {
return NULL;
}
size_t len = strlen(raw);
if (len >= PAIR_LEN) {
return cbm_arena_strndup(a, raw + SKIP_ONE, len - PAIR_LEN);
}
return raw;
}
break;
}
return NULL;
}
// Return true if the key text of a block_mapping_pair matches one of the
// Kustomize resource-list field names.
static int is_kustomize_list_key(const char *key) {
return (strcmp(key, "resources") == 0 || strcmp(key, "bases") == 0 ||
strcmp(key, "patches") == 0 || strcmp(key, "components") == 0 ||
strcmp(key, "patchesStrategicMerge") == 0 || strcmp(key, "crds") == 0);
}
// ---------------------------------------------------------------------------
// Kustomize extraction
// ---------------------------------------------------------------------------
// Walk a block_sequence node and emit one CBMImport per block_sequence_item
// scalar child, using key_name as the local_name.
static void emit_kustomize_sequence(CBMExtractCtx *ctx, TSNode seq_node, const char *key_name) {
CBMArena *a = ctx->arena;
uint32_t n = ts_node_child_count(seq_node);
for (uint32_t i = 0; i < n; i++) {
TSNode item = ts_node_child(seq_node, i);
if (strcmp(ts_node_type(item), "block_sequence_item") != 0) {
continue;
}
// block_sequence_item has one named child: the value
uint32_t ic = ts_node_child_count(item);
for (uint32_t j = 0; j < ic; j++) {
TSNode val = ts_node_child(item, j);
const char *scalar = get_scalar_text(a, val, ctx->source);
if (!scalar) {
continue;
}
CBMImport imp = {
.local_name = cbm_arena_strdup(a, key_name),
.module_path = cbm_arena_strdup(a, scalar),
};
cbm_imports_push(&ctx->result->imports, a, imp);
}
}
}
// Unwrap a YAML node through optional block_node wrapper to get block_mapping.
// Returns null node if not a block_mapping.
static TSNode unwrap_block_mapping(TSNode doc_child) {
TSNode mapping = ts_node_named_child(doc_child, 0);
if (ts_node_is_null(mapping)) {
return mapping;
}
if (strcmp(ts_node_type(mapping), "block_node") == 0) {
mapping = ts_node_named_child(mapping, 0);
}
if (ts_node_is_null(mapping) || strcmp(ts_node_type(mapping), "block_mapping") != 0) {
TSNode null_node = {0};
return null_node;
}
return mapping;
}
// Process a single block_mapping_pair for kustomize list keys.
static void process_kustomize_pair(CBMExtractCtx *ctx, TSNode pair) {
if (strcmp(ts_node_type(pair), "block_mapping_pair") != 0) {
return;
}
TSNode key_node = ts_node_named_child(pair, 0);
if (ts_node_is_null(key_node)) {
return;
}
const char *key_text = get_scalar_text(ctx->arena, key_node, ctx->source);
if (!key_text || !is_kustomize_list_key(key_text)) {
return;
}
TSNode val_node = ts_node_named_child(pair, SKIP_ONE);
if (ts_node_is_null(val_node)) {
return;
}
if (strcmp(ts_node_type(val_node), "block_node") == 0) {
val_node = ts_node_named_child(val_node, 0);
}
if (ts_node_is_null(val_node) || strcmp(ts_node_type(val_node), "block_sequence") != 0) {
return;
}
emit_kustomize_sequence(ctx, val_node, key_text);
}
// Forward declaration: defined with the K8s-manifest helpers below.
static TSNode unwrap_pair_value(TSNode pair);
// Emit a "Class" def named after the document's `kind` scalar. A kustomization
// file has no metadata.name, so the def name is the bare kind ("Kustomization").
// Mirrors the K8s manifest kind-def so Kustomize resources are also discoverable.
static void emit_kustomize_kind_def(CBMExtractCtx *ctx, TSNode mapping) {
CBMArena *a = ctx->arena;
uint32_t pair_n = ts_node_child_count(mapping);
for (uint32_t pi = 0; pi < pair_n; pi++) {
TSNode pair = ts_node_child(mapping, pi);
if (strcmp(ts_node_type(pair), "block_mapping_pair") != 0) {
continue;
}
TSNode key_node = ts_node_named_child(pair, 0);
if (ts_node_is_null(key_node)) {
continue;
}
const char *key = get_scalar_text(a, key_node, ctx->source);
if (!key || strcmp(key, "kind") != 0) {
continue;
}
TSNode val_node = unwrap_pair_value(pair);
if (ts_node_is_null(val_node)) {
continue;
}
const char *kind = get_scalar_text(a, val_node, ctx->source);
if (!kind || !kind[0]) {
continue;
}
CBMDefinition def = {0};
def.name = cbm_arena_strdup(a, kind);
def.qualified_name = cbm_arena_sprintf(a, "%s.%s", ctx->module_qn, kind);
def.label = cbm_arena_strdup(a, "Resource");
def.file_path = ctx->rel_path;
def.start_line = ts_node_start_point(mapping).row + TS_LINE_OFFSET;
def.end_line = ts_node_end_point(mapping).row + TS_LINE_OFFSET;
cbm_defs_push(&ctx->result->defs, a, def);
return;
}
}
static void extract_kustomize(CBMExtractCtx *ctx) {
TSNode root = ctx->root;
uint32_t root_n = ts_node_child_count(root);
for (uint32_t si = 0; si < root_n; si++) {
TSNode stream_child = ts_node_child(root, si);
if (strcmp(ts_node_type(stream_child), "document") != 0) {
continue;
}
TSNode mapping = unwrap_block_mapping(stream_child);
if (ts_node_is_null(mapping)) {
continue;
}
emit_kustomize_kind_def(ctx, mapping);
uint32_t pair_n = ts_node_child_count(mapping);
for (uint32_t pi = 0; pi < pair_n; pi++) {
process_kustomize_pair(ctx, ts_node_child(mapping, pi));
}
}
}
// ---------------------------------------------------------------------------
// K8s manifest extraction
// ---------------------------------------------------------------------------
// Extract the "name" scalar from a metadata block_mapping.
static void extract_metadata_name(CBMArena *a, TSNode meta_mapping, const char *source,
char *meta_name_buf, size_t meta_sz) {
if (ts_node_is_null(meta_mapping) || strcmp(ts_node_type(meta_mapping), "block_mapping") != 0) {
return;
}
uint32_t mn = ts_node_child_count(meta_mapping);
for (uint32_t mi = 0; mi < mn; mi++) {
TSNode mpair = ts_node_child(meta_mapping, mi);
if (strcmp(ts_node_type(mpair), "block_mapping_pair") != 0) {
continue;
}
TSNode mkey = ts_node_named_child(mpair, 0);
if (ts_node_is_null(mkey)) {
continue;
}
const char *mkey_text = get_scalar_text(a, mkey, source);
if (!mkey_text || strcmp(mkey_text, "name") != 0) {
continue;
}
TSNode mval = ts_node_named_child(mpair, SKIP_ONE);
if (ts_node_is_null(mval)) {
continue;
}
const char *meta_name = get_scalar_text(a, mval, source);
if (meta_name) {
snprintf(meta_name_buf, meta_sz, "%s", meta_name);
}
}
}
// Unwrap a block_mapping_pair value through optional block_node.
static TSNode unwrap_pair_value(TSNode pair) {
TSNode val_node = ts_node_named_child(pair, SKIP_ONE);
if (ts_node_is_null(val_node)) {
return val_node;
}
if (strcmp(ts_node_type(val_node), "block_node") == 0) {
val_node = ts_node_named_child(val_node, 0);
}
return val_node;
}
// Descend into the first block_mapping of a document and extract
// kind and metadata.name. Returns void; fills kind_buf and meta_name_buf.
static void extract_k8s_scalars(CBMExtractCtx *ctx, TSNode mapping, char *kind_buf, size_t kind_sz,
char *meta_name_buf, size_t meta_sz) {
CBMArena *a = ctx->arena;
kind_buf[0] = '\0';
meta_name_buf[0] = '\0';
uint32_t n = ts_node_child_count(mapping);
for (uint32_t i = 0; i < n; i++) {
TSNode pair = ts_node_child(mapping, i);
if (strcmp(ts_node_type(pair), "block_mapping_pair") != 0) {
continue;
}
TSNode key_node = ts_node_named_child(pair, 0);
if (ts_node_is_null(key_node)) {
continue;
}
const char *key = get_scalar_text(a, key_node, ctx->source);
if (!key) {
continue;
}
TSNode val_node = unwrap_pair_value(pair);
if (ts_node_is_null(val_node)) {
continue;
}
if (strcmp(key, "kind") == 0) {
const char *v = get_scalar_text(a, val_node, ctx->source);
if (v) {
snprintf(kind_buf, kind_sz, "%s", v);
}
} else if (strcmp(key, "metadata") == 0) {
extract_metadata_name(a, val_node, ctx->source, meta_name_buf, meta_sz);
}
}
}
static void extract_k8s_manifest(CBMExtractCtx *ctx) {
CBMArena *a = ctx->arena;
TSNode root = ctx->root;
uint32_t root_n = ts_node_child_count(root);
for (uint32_t si = 0; si < root_n; si++) {
TSNode stream_child = ts_node_child(root, si);
if (strcmp(ts_node_type(stream_child), "document") != 0) {
continue;
}
TSNode mapping = ts_node_named_child(stream_child, 0);
if (ts_node_is_null(mapping)) {
continue;
}
if (strcmp(ts_node_type(mapping), "block_node") == 0) {
mapping = ts_node_named_child(mapping, 0);
}
if (ts_node_is_null(mapping) || strcmp(ts_node_type(mapping), "block_mapping") != 0) {
continue;
}
char kind_buf[K8S_BUF_SIZE] = {0};
char meta_name_buf[K8S_BUF_SIZE] = {0};
extract_k8s_scalars(ctx, mapping, kind_buf, sizeof(kind_buf), meta_name_buf,
sizeof(meta_name_buf));
// Skip malformed manifests (no kind or no metadata.name)
if (kind_buf[0] == '\0' || meta_name_buf[0] == '\0') {
continue;
}
char def_name[RESULT_BUF_SIZE];
snprintf(def_name, sizeof(def_name), "%s/%s", kind_buf, meta_name_buf);
CBMDefinition def = {0};
def.name = cbm_arena_strdup(a, def_name);
def.qualified_name = cbm_arena_sprintf(a, "%s.%s", ctx->module_qn, def_name);
// "Resource" is the canonical def label for a K8s resource kind. It is a
// valid graph label and is what the K8s pipeline pass (pass_k8s.c) filters
// on to upsert Resource nodes and emit INFRA_MAPS edges.
def.label = cbm_arena_strdup(a, "Resource");
def.file_path = ctx->rel_path;
def.start_line = ts_node_start_point(mapping).row + TS_LINE_OFFSET;
def.end_line = ts_node_end_point(mapping).row + TS_LINE_OFFSET;
cbm_defs_push(&ctx->result->defs, a, def);
break; // Only the first document per file
}
}
// ---------------------------------------------------------------------------
// Public entry point
// ---------------------------------------------------------------------------
void cbm_extract_k8s(CBMExtractCtx *ctx) {
if (ctx->language == CBM_LANG_KUSTOMIZE) {
extract_kustomize(ctx);
} else if (ctx->language == CBM_LANG_K8S) {
extract_k8s_manifest(ctx);
}
}
+86
View File
@@ -0,0 +1,86 @@
/*
* extract_node_stack.h — Growable TSNode stack for AST traversal.
*
* Replaces fixed-size TSNode stack[] arrays that silently drop AST subtrees
* when the stack overflows (GitHub issue #199).
*
* Uses the arena allocator for zero-fragmentation growth: old blocks are
* abandoned (freed when the arena is destroyed at end of file extraction).
* Initial capacity matches the previous fixed caps so small files allocate
* no extra memory.
*/
#ifndef CBM_EXTRACT_NODE_STACK_H
#define CBM_EXTRACT_NODE_STACK_H
#include "arena.h"
#include "tree_sitter/api.h"
#include <string.h> /* memcpy */
typedef struct {
TSNode *items;
int count;
int cap;
} TSNodeStack;
/* Initialize a stack with the given initial capacity, arena-allocated. */
static inline void ts_nstack_init(TSNodeStack *s, CBMArena *arena, int initial_cap) {
s->items = (TSNode *)cbm_arena_alloc(arena, (size_t)initial_cap * sizeof(TSNode));
s->count = 0;
s->cap = s->items ? initial_cap : 0;
}
/* Push a node onto the stack, growing 2x if needed. */
static inline void ts_nstack_push(TSNodeStack *s, CBMArena *arena, TSNode node) {
if (s->count >= s->cap) {
int new_cap = s->cap ? s->cap * 2 : 512;
TSNode *new_items = (TSNode *)cbm_arena_alloc(arena, (size_t)new_cap * sizeof(TSNode));
if (!new_items)
return; /* OOM: best-effort, stop growing */
if (s->items && s->count > 0) {
memcpy(new_items, s->items, (size_t)s->count * sizeof(TSNode));
}
/* Old s->items is abandoned in the arena — freed on arena_destroy. */
s->items = new_items;
s->cap = new_cap;
}
s->items[s->count++] = node;
}
/* Pop a node from the stack. Caller must check s->count > 0. */
static inline TSNode ts_nstack_pop(TSNodeStack *s) {
return s->items[--s->count];
}
/*
* Push all children of `node` so they POP in forward (source) order — a drop-in
* replacement for the common idiom:
* for (int i = (int)count - 1; i >= 0; i--) ts_nstack_push(s, a, ts_node_child(node, i));
*
* That idiom calls ts_node_child(node, i) once per index, and ts_node_child is
* O(i) in tree-sitter (it walks the child iterator from the first child each
* time). Over a node with N children that is O(N^2) — catastrophic on a program
* root holding hundreds of thousands of top-level nodes (e.g. fixture/generated
* files). This helper enumerates children in a single O(N) cursor pass, then
* reverses the just-pushed segment so pop order is identical to the old idiom.
*/
static inline void ts_nstack_push_children(TSNodeStack *s, CBMArena *arena, TSNode node) {
int base = s->count;
TSTreeCursor cursor = ts_tree_cursor_new(node);
if (ts_tree_cursor_goto_first_child(&cursor)) {
do {
ts_nstack_push(s, arena, ts_tree_cursor_current_node(&cursor));
} while (ts_tree_cursor_goto_next_sibling(&cursor));
}
ts_tree_cursor_delete(&cursor);
/* Reverse [base, count) so the first child pops first (forward order). */
int lo = base, hi = s->count - 1;
while (lo < hi) {
TSNode tmp = s->items[lo];
s->items[lo] = s->items[hi];
s->items[hi] = tmp;
lo++;
hi--;
}
}
#endif /* CBM_EXTRACT_NODE_STACK_H */
+342
View File
@@ -0,0 +1,342 @@
#include "cbm.h"
#include "arena.h"
#include "helpers.h"
#include "lang_specs.h"
#include "extract_unified.h"
#include "tree_sitter/api.h" // TSNode, ts_node_*
#include "foundation/constants.h"
#include "extract_node_stack.h"
enum { MAX_EXCEPTION_NAME_LEN = 100, LAST_IDX = 1 };
#include <stdint.h> // uint32_t
#include <string.h>
#include <ctype.h>
// Field name length for ts_node_child_by_field_name() calls.
#define FIELD_LEN_CONSTRUCTOR 11 // strlen("constructor")
// --- Throw/Raise extraction ---
// Detect whether a node is a throw node for the given spec.
//
// Kotlin special case: this grammar models `throw X(...)` as a `jump_expression`
// (the same node also covers return/break/continue), NOT a `throw_expression`,
// so the spec's throw_node_types ("throw_expression") never matches. We treat a
// Kotlin `jump_expression` as a throw only when its first child is the `throw`
// keyword, which excludes return/break/continue without false positives.
static bool is_throw_node(TSNode node, const CBMLangSpec *spec) {
if (cbm_kind_in_set(node, spec->throw_node_types)) {
return true;
}
if (spec->language == CBM_LANG_KOTLIN && strcmp(ts_node_type(node), "jump_expression") == 0) {
uint32_t nc = ts_node_child_count(node);
if (nc > 0 && strcmp(ts_node_type(ts_node_child(node, 0)), "throw") == 0) {
return true;
}
}
return false;
}
// Resolve exception name from the first meaningful child of a throw/raise node.
static char *resolve_exception_name(CBMArena *a, TSNode throw_node, const char *source) {
uint32_t nc = ts_node_child_count(throw_node);
for (uint32_t i = 0; i < nc; i++) {
TSNode child = ts_node_child(throw_node, i);
const char *ck = ts_node_type(child);
if (strcmp(ck, "raise") == 0 || strcmp(ck, "throw") == 0) {
continue;
}
if (ck[0] == ';' || ck[0] == '(' || ck[0] == ')') {
continue;
}
if (strcmp(ck, "call") == 0 || strcmp(ck, "call_expression") == 0 ||
strcmp(ck, "new_expression") == 0 || strcmp(ck, "object_creation_expression") == 0 ||
strcmp(ck, "instance_expression") == 0) {
TSNode fn = ts_node_child_by_field_name(child, TS_FIELD("function"));
if (ts_node_is_null(fn)) {
fn = ts_node_child_by_field_name(child, "constructor", FIELD_LEN_CONSTRUCTOR);
}
if (ts_node_is_null(fn)) {
fn = ts_node_child_by_field_name(child, TS_FIELD("type"));
}
if (ts_node_is_null(fn) && ts_node_named_child_count(child) > 0) {
fn = ts_node_named_child(child, 0);
}
if (!ts_node_is_null(fn)) {
return cbm_node_text(a, fn, source);
}
} else {
return cbm_node_text(a, child, source);
}
break;
}
return NULL;
}
// Extract exception types from a Java-style throws clause.
static void extract_throws_clause(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
const char *func_qn) {
if (!spec->throws_clause_field || !spec->throws_clause_field[0]) {
return;
}
const char *kind = ts_node_type(node);
if (strcmp(kind, "method_declaration") != 0 && strcmp(kind, "constructor_declaration") != 0) {
return;
}
TSNode throws_clause = ts_node_child_by_field_name(node, spec->throws_clause_field,
(uint32_t)strlen(spec->throws_clause_field));
if (ts_node_is_null(throws_clause)) {
return;
}
uint32_t nc = ts_node_child_count(throws_clause);
for (uint32_t i = 0; i < nc; i++) {
TSNode child = ts_node_child(throws_clause, i);
const char *ck = ts_node_type(child);
if (strcmp(ck, "type_identifier") == 0 || strcmp(ck, "identifier") == 0 ||
strcmp(ck, "scoped_type_identifier") == 0) {
char *exc = cbm_node_text(ctx->arena, child, ctx->source);
if (exc && exc[0]) {
CBMThrow thr = {.exception_name = exc, .enclosing_func_qn = func_qn};
cbm_throws_push(&ctx->result->throws, ctx->arena, thr);
}
}
}
}
// Process a single node for throw extraction (called from iterative walker).
static void process_throw_node(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec) {
if (is_throw_node(node, spec)) {
char *exc_name = resolve_exception_name(ctx->arena, node, ctx->source);
if (exc_name && exc_name[0]) {
if (strlen(exc_name) > MAX_EXCEPTION_NAME_LEN) {
exc_name[MAX_EXCEPTION_NAME_LEN] = '\0';
}
CBMThrow thr;
thr.exception_name = exc_name;
thr.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, node);
cbm_throws_push(&ctx->result->throws, ctx->arena, thr);
}
}
extract_throws_clause(ctx, node, spec, cbm_enclosing_func_qn_cached(ctx, node));
}
// Iterative throw walker
static void walk_throws(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
TSNodeStack stack;
ts_nstack_init(&stack, ctx->arena, CBM_SZ_512);
ts_nstack_push(&stack, ctx->arena, root);
while (stack.count > 0) {
TSNode node = ts_nstack_pop(&stack);
process_throw_node(ctx, node, spec);
uint32_t count = ts_node_child_count(node);
for (int i = (int)count - LAST_IDX; i >= 0; i--) {
ts_nstack_push(&stack, ctx->arena, ts_node_child(node, (uint32_t)i));
}
}
}
// --- Read/Write detection (iterative) ---
// Resolve an assignment LHS node to the bare name being written. Handles
// common wrappers so WRITES resolve for more languages than a raw identifier:
// - expression_list (Go `x = ...` desugars left to expression_list[x])
// - index/subscript (`cache[k] = v` → write the base var `cache`)
// - field/member/selector access (`self.total = ...`, `obj.Field = ...` →
// write the trailing field name `total`/`Field`)
// Returns NULL if no simple write target can be determined.
static char *resolve_lhs_write_name(CBMExtractCtx *ctx, TSNode left) {
// Unwrap a single-element expression_list (Go).
if (strcmp(ts_node_type(left), "expression_list") == 0) {
if (ts_node_named_child_count(left) != 1) {
return NULL; // multi-assign: ambiguous, skip
}
left = ts_node_named_child(left, 0);
}
const char *lk = ts_node_type(left);
if (strcmp(lk, "identifier") == 0 || strcmp(lk, "simple_identifier") == 0) {
return cbm_node_text(ctx->arena, left, ctx->source);
}
// Indexed write: write the base operand's identifier (`cache[k]` → cache).
if (strcmp(lk, "index_expression") == 0 || strcmp(lk, "subscript_expression") == 0) {
TSNode base = ts_node_child_by_field_name(left, TS_FIELD("operand"));
if (ts_node_is_null(base)) {
base = ts_node_child_by_field_name(left, TS_FIELD("object"));
}
if (ts_node_is_null(base) && ts_node_named_child_count(left) > 0) {
base = ts_node_named_child(left, 0);
}
if (!ts_node_is_null(base)) {
const char *bk = ts_node_type(base);
if (strcmp(bk, "identifier") == 0 || strcmp(bk, "simple_identifier") == 0) {
return cbm_node_text(ctx->arena, base, ctx->source);
}
}
return NULL;
}
// Field/member write: write the trailing field name (`self.total` → total,
// `obj.Field` → Field). Covers Rust field_expression, C#/Java member access.
if (strcmp(lk, "field_expression") == 0 || strcmp(lk, "member_access_expression") == 0 ||
strcmp(lk, "field_access") == 0 || strcmp(lk, "selector_expression") == 0) {
TSNode fld = ts_node_child_by_field_name(left, TS_FIELD("field"));
if (ts_node_is_null(fld)) {
fld = ts_node_child_by_field_name(left, TS_FIELD("name"));
}
if (!ts_node_is_null(fld)) {
return cbm_node_text(ctx->arena, fld, ctx->source);
}
return NULL;
}
return NULL;
}
// Resolve the write target of a node in an assignment_node_types set. For a
// plain assignment the target is the "left" field (or first child). For an
// increment/decrement unary expression (`x++`, `++x`, C#
// postfix_/prefix_unary_expression) there is no "left" field and the operand
// may sit on either side of the operator token, so scan named children for the
// first identifier / member-style operand. Returns a null node when no simple
// target is found.
static TSNode resolve_write_lhs_node(TSNode node) {
TSNode left = ts_node_child_by_field_name(node, TS_FIELD("left"));
if (!ts_node_is_null(left)) {
return left;
}
const char *nk = ts_node_type(node);
if (strcmp(nk, "postfix_unary_expression") == 0 || strcmp(nk, "prefix_unary_expression") == 0 ||
strcmp(nk, "update_expression") == 0) {
// Only ++/-- mutate their operand. Other unary postfix/prefix forms
// (C# null-forgiving `x!`, address-of `&x`, deref `*x`, logical `!x`)
// READ the operand — never treat them as writes.
bool is_incdec = false;
uint32_t total = ts_node_child_count(node);
for (uint32_t i = 0; i < total; i++) {
TSNode c = ts_node_child(node, i);
if (ts_node_is_named(c)) {
continue; // operator is an anonymous token
}
const char *op = ts_node_type(c);
if (strcmp(op, "++") == 0 || strcmp(op, "--") == 0) {
is_incdec = true;
break;
}
}
if (!is_incdec) {
return (TSNode){0};
}
uint32_t cnc = ts_node_named_child_count(node);
for (uint32_t i = 0; i < cnc; i++) {
TSNode c = ts_node_named_child(node, i);
const char *ck = ts_node_type(c);
if (strcmp(ck, "identifier") == 0 || strcmp(ck, "simple_identifier") == 0 ||
strcmp(ck, "member_access_expression") == 0 ||
strcmp(ck, "field_expression") == 0 || strcmp(ck, "field_access") == 0 ||
strcmp(ck, "selector_expression") == 0 || strcmp(ck, "subscript_expression") == 0 ||
strcmp(ck, "index_expression") == 0) {
return c;
}
}
return (TSNode){0};
}
if (ts_node_child_count(node) > 0) {
return ts_node_child(node, 0);
}
return (TSNode){0};
}
// Try to emit a write for an assignment node.
static void try_emit_assignment_write(CBMExtractCtx *ctx, TSNode node, const char *func_qn) {
TSNode left = resolve_write_lhs_node(node);
if (ts_node_is_null(left)) {
return;
}
char *name = resolve_lhs_write_name(ctx, left);
if (name && name[0] && !cbm_is_keyword(name, ctx->language)) {
CBMReadWrite rw;
rw.var_name = name;
rw.is_write = true;
rw.enclosing_func_qn = func_qn;
cbm_rw_push(&ctx->result->rw, ctx->arena, rw);
}
}
static void walk_readwrites(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
TSNodeStack stack;
ts_nstack_init(&stack, ctx->arena, CBM_SZ_512);
ts_nstack_push(&stack, ctx->arena, root);
while (stack.count > 0) {
TSNode node = ts_nstack_pop(&stack);
if (cbm_kind_in_set(node, spec->assignment_node_types)) {
try_emit_assignment_write(ctx, node, cbm_enclosing_func_qn_cached(ctx, node));
}
uint32_t count = ts_node_child_count(node);
for (int i = (int)count - LAST_IDX; i >= 0; i--) {
ts_nstack_push(&stack, ctx->arena, ts_node_child(node, (uint32_t)i));
}
}
}
void cbm_extract_semantic(CBMExtractCtx *ctx) {
const CBMLangSpec *spec = cbm_lang_spec(ctx->language);
if (!spec) {
return;
}
// Throws
if ((spec->throw_node_types && spec->throw_node_types[0]) ||
(spec->throws_clause_field && spec->throws_clause_field[0])) {
walk_throws(ctx, ctx->root, spec);
}
// Reads/Writes
if (spec->assignment_node_types && spec->assignment_node_types[0]) {
walk_readwrites(ctx, ctx->root, spec);
}
}
// --- Unified handlers ---
void handle_throws(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state) {
bool has_throws = spec->throw_node_types && spec->throw_node_types[0];
bool has_clause = spec->throws_clause_field && spec->throws_clause_field[0];
if (!has_throws && !has_clause) {
return;
}
if (has_throws && is_throw_node(node, spec)) {
char *exc_name = resolve_exception_name(ctx->arena, node, ctx->source);
if (exc_name && exc_name[0]) {
if (strlen(exc_name) > MAX_EXCEPTION_NAME_LEN) {
exc_name[MAX_EXCEPTION_NAME_LEN] = '\0';
}
CBMThrow thr;
thr.exception_name = exc_name;
thr.enclosing_func_qn = state->enclosing_func_qn;
cbm_throws_push(&ctx->result->throws, ctx->arena, thr);
}
}
extract_throws_clause(ctx, node, spec, state->enclosing_func_qn);
}
void handle_readwrites(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state) {
if (!spec->assignment_node_types || !spec->assignment_node_types[0]) {
return;
}
if (cbm_kind_in_set(node, spec->assignment_node_types)) {
TSNode left = resolve_write_lhs_node(node);
if (!ts_node_is_null(left)) {
char *name = resolve_lhs_write_name(ctx, left);
if (name && name[0] && !cbm_is_keyword(name, ctx->language)) {
CBMReadWrite rw;
rw.var_name = name;
rw.is_write = true;
rw.enclosing_func_qn = state->enclosing_func_qn;
cbm_rw_push(&ctx->result->rw, ctx->arena, rw);
}
}
}
}
+212
View File
@@ -0,0 +1,212 @@
#include "cbm.h"
#include "arena.h" // CBMArena
#include "helpers.h"
#include "lang_specs.h"
#include "extract_unified.h"
#include "tree_sitter/api.h" // TSNode, ts_node_*
#include "foundation/constants.h"
#include "extract_node_stack.h"
#include <stdint.h> // uint32_t
#include <string.h>
#include <ctype.h>
// Extract type from new_expression / object_creation_expression.
static const char *extract_new_expr_type(CBMArena *a, TSNode rhs, const char *source) {
TSNode type_node = ts_node_child_by_field_name(rhs, TS_FIELD("type"));
if (!ts_node_is_null(type_node)) {
const char *tk = ts_node_type(type_node);
if (strcmp(tk, "type_identifier") == 0 || strcmp(tk, "identifier") == 0 ||
strcmp(tk, "simple_identifier") == 0) {
return cbm_node_text(a, type_node, source);
}
if (strcmp(tk, "generic_type") == 0 && ts_node_child_count(type_node) > 0) {
return cbm_node_text(a, ts_node_child(type_node, 0), source);
}
return cbm_node_text(a, type_node, source);
}
// Fallback: first identifier child
for (uint32_t i = 0; i < ts_node_child_count(rhs); i++) {
TSNode child = ts_node_child(rhs, i);
const char *ck = ts_node_type(child);
if (strcmp(ck, "identifier") == 0 || strcmp(ck, "type_identifier") == 0 ||
strcmp(ck, "simple_identifier") == 0) {
return cbm_node_text(a, child, source);
}
}
return NULL;
}
// Extract class/type name from a constructor expression.
// e.g., new Foo() -> "Foo", Foo() -> "Foo" (if uppercase), Foo{} -> "Foo"
static const char *extract_constructor_type(CBMArena *a, TSNode rhs, const char *source,
CBMLanguage lang) {
const char *kind = ts_node_type(rhs);
if (strcmp(kind, "new_expression") == 0 || strcmp(kind, "object_creation_expression") == 0) {
return extract_new_expr_type(a, rhs, source);
}
if (strcmp(kind, "call") == 0 || strcmp(kind, "call_expression") == 0) {
TSNode func = ts_node_child_by_field_name(rhs, TS_FIELD("function"));
if (ts_node_is_null(func) && ts_node_child_count(rhs) > 0) {
func = ts_node_child(rhs, 0);
}
if (!ts_node_is_null(func)) {
char *fname = cbm_node_text(a, func, source);
if (fname && fname[0] >= 'A' && fname[0] <= 'Z') {
return fname;
}
/* Lower-cased package prefix: Go-style `pb.NewFooClient(...)` and
* Java-style `fooGrpc.newBlockingStub(...)`. Accept the qualified
* name when the last segment matches a typed-stub factory pattern.
* Downstream passes can use this to infer the constructed type. */
if (fname && fname[0]) {
const char *last = strrchr(fname, '.');
last = last ? last + 1 : fname;
bool is_factory = false;
if ((strncmp(last, "New", 3) == 0 || strncmp(last, "new", 3) == 0) && last[3]) {
size_t llen = strlen(last);
if ((llen > 6 && strcmp(last + llen - 6, "Client") == 0) ||
(llen > 4 && strcmp(last + llen - 4, "Stub") == 0)) {
is_factory = true;
}
}
if (is_factory) {
return fname;
}
}
}
}
if (strcmp(kind, "composite_literal") == 0) {
TSNode type_node = ts_node_child_by_field_name(rhs, TS_FIELD("type"));
if (!ts_node_is_null(type_node)) {
return cbm_node_text(a, type_node, source);
}
}
if (lang == CBM_LANG_RUST && strcmp(kind, "struct_expression") == 0) {
TSNode name = ts_node_child_by_field_name(rhs, TS_FIELD("name"));
if (!ts_node_is_null(name)) {
return cbm_node_text(a, name, source);
}
}
return NULL;
}
// Emit a type assignment if var_name and constructor type are valid.
static void try_emit_type_assign(CBMExtractCtx *ctx, TSNode var_node, TSNode rhs_node,
const char *func_qn) {
char *var_name = cbm_node_text(ctx->arena, var_node, ctx->source);
const char *type_name =
extract_constructor_type(ctx->arena, rhs_node, ctx->source, ctx->language);
if (var_name && var_name[0] && type_name && type_name[0]) {
CBMTypeAssign ta;
ta.var_name = var_name;
ta.type_name = type_name;
ta.enclosing_func_qn = func_qn;
cbm_typeassign_push(&ctx->result->type_assigns, ctx->arena, ta);
}
}
// Process assignment-type nodes (left/right fields with identifier check).
static void process_assignment_type_assign(CBMExtractCtx *ctx, TSNode node, const char *func_qn) {
TSNode left = ts_node_child_by_field_name(node, TS_FIELD("left"));
TSNode right = ts_node_child_by_field_name(node, TS_FIELD("right"));
if (ts_node_is_null(right)) {
right = ts_node_child_by_field_name(node, TS_FIELD("value"));
}
if (!ts_node_is_null(left) && !ts_node_is_null(right)) {
const char *lk = ts_node_type(left);
if (strcmp(lk, "identifier") == 0 || strcmp(lk, "simple_identifier") == 0) {
try_emit_type_assign(ctx, left, right, func_qn);
}
}
}
// Process Go short_var_declaration/var_spec nodes.
static void process_go_var_type_assign(CBMExtractCtx *ctx, TSNode node, const char *func_qn) {
TSNode left = ts_node_child_by_field_name(node, TS_FIELD("name"));
if (ts_node_is_null(left)) {
left = ts_node_child_by_field_name(node, TS_FIELD("left"));
}
TSNode right = ts_node_child_by_field_name(node, TS_FIELD("value"));
if (ts_node_is_null(right)) {
right = ts_node_child_by_field_name(node, TS_FIELD("right"));
}
if (!ts_node_is_null(left) && !ts_node_is_null(right)) {
try_emit_type_assign(ctx, left, right, func_qn);
}
}
// Process JS/TS variable_declarator nodes (name + value with identifier check).
static void process_declarator_type_assign(CBMExtractCtx *ctx, TSNode node, const char *func_qn) {
TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("name"));
TSNode value_node = ts_node_child_by_field_name(node, TS_FIELD("value"));
if (!ts_node_is_null(name_node) && !ts_node_is_null(value_node)) {
const char *nk = ts_node_type(name_node);
if (strcmp(nk, "identifier") == 0 || strcmp(nk, "simple_identifier") == 0) {
try_emit_type_assign(ctx, name_node, value_node, func_qn);
}
}
}
// Process Rust let_declaration nodes (pattern + value).
static void process_rust_let_type_assign(CBMExtractCtx *ctx, TSNode node, const char *func_qn) {
TSNode pat = ts_node_child_by_field_name(node, TS_FIELD("pattern"));
TSNode val = ts_node_child_by_field_name(node, TS_FIELD("value"));
if (!ts_node_is_null(pat) && !ts_node_is_null(val)) {
if (strcmp(ts_node_type(pat), "identifier") == 0) {
try_emit_type_assign(ctx, pat, val, func_qn);
}
}
}
// Process assignment nodes (assignment, short_var_declaration, variable_declarator,
// let_declaration).
static void process_type_assign_node(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
const char *func_qn) {
const char *kind = ts_node_type(node);
if (cbm_kind_in_set(node, spec->assignment_node_types)) {
process_assignment_type_assign(ctx, node, func_qn);
}
if (strcmp(kind, "short_var_declaration") == 0 || strcmp(kind, "var_spec") == 0) {
process_go_var_type_assign(ctx, node, func_qn);
}
if (strcmp(kind, "variable_declarator") == 0) {
process_declarator_type_assign(ctx, node, func_qn);
}
if (strcmp(kind, "let_declaration") == 0 && ctx->language == CBM_LANG_RUST) {
process_rust_let_type_assign(ctx, node, func_qn);
}
}
// Walk AST for assignment patterns where RHS is a constructor call.
static void walk_type_assigns(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
TSNodeStack stack;
ts_nstack_init(&stack, ctx->arena, 4096);
ts_nstack_push(&stack, ctx->arena, root);
while (stack.count > 0) {
TSNode node = ts_nstack_pop(&stack);
process_type_assign_node(ctx, node, spec, cbm_enclosing_func_qn_cached(ctx, node));
ts_nstack_push_children(&stack, ctx->arena, node);
}
}
void cbm_extract_type_assigns(CBMExtractCtx *ctx) {
const CBMLangSpec *spec = cbm_lang_spec(ctx->language);
if (!spec) {
return;
}
walk_type_assigns(ctx, ctx->root, spec);
}
// --- Unified handler ---
void handle_type_assigns(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
WalkState *state) {
process_type_assign_node(ctx, node, spec, state->enclosing_func_qn);
}
+360
View File
@@ -0,0 +1,360 @@
#include "cbm.h"
#include "arena.h" // CBMArena, cbm_arena_sprintf/strndup
#include "helpers.h"
#include "lang_specs.h"
#include "extract_unified.h"
#include "tree_sitter/api.h" // TSNode, ts_node_*
#include "foundation/constants.h"
#include "extract_node_stack.h"
enum { MIN_TYPE_LEN = 1 };
#include <stdint.h> // uint32_t
#include <string.h>
#include <ctype.h>
// Builtin types that should not generate USES_TYPE edges.
static bool is_builtin_type(const char *name) {
if (!name || !name[0]) {
return true;
}
if (strlen(name) <= MIN_TYPE_LEN) {
return true;
}
// Common primitives
if (strcmp(name, "int") == 0 || strcmp(name, "string") == 0 || strcmp(name, "bool") == 0 ||
strcmp(name, "float") == 0 || strcmp(name, "float32") == 0 ||
strcmp(name, "float64") == 0 || strcmp(name, "int8") == 0 || strcmp(name, "int16") == 0 ||
strcmp(name, "int32") == 0 || strcmp(name, "int64") == 0 || strcmp(name, "uint") == 0 ||
strcmp(name, "uint8") == 0 || strcmp(name, "uint16") == 0 || strcmp(name, "uint32") == 0 ||
strcmp(name, "uint64") == 0 || strcmp(name, "uintptr") == 0 || strcmp(name, "byte") == 0 ||
strcmp(name, "rune") == 0 || strcmp(name, "void") == 0 || strcmp(name, "char") == 0 ||
strcmp(name, "double") == 0 || strcmp(name, "long") == 0 || strcmp(name, "short") == 0 ||
strcmp(name, "unsigned") == 0 || strcmp(name, "error") == 0 || strcmp(name, "any") == 0 ||
strcmp(name, "interface") == 0 || strcmp(name, "object") == 0 ||
strcmp(name, "Object") == 0 || strcmp(name, "None") == 0 || strcmp(name, "nil") == 0 ||
strcmp(name, "null") == 0 || strcmp(name, "undefined") == 0 ||
strcmp(name, "number") == 0 || strcmp(name, "boolean") == 0 || strcmp(name, "str") == 0 ||
strcmp(name, "dict") == 0 || strcmp(name, "list") == 0 || strcmp(name, "tuple") == 0 ||
strcmp(name, "set") == 0 || strcmp(name, "complex128") == 0 ||
strcmp(name, "complex64") == 0) {
return true;
}
return false;
}
// Strip pointer/reference/slice/optional markers from a type name.
static const char *clean_type_name(CBMArena *a, const char *name) {
if (!name || !name[0]) {
return name;
}
// Skip leading *, &, [], ?
while (*name == '*' || *name == '&' || *name == '?' || *name == '[' || *name == ']') {
name++;
}
if (!*name) {
return NULL;
}
// Take only the base type before any < or [
size_t len = strlen(name);
for (size_t i = 0; i < len; i++) {
if (name[i] == '<' || name[i] == '[') {
return cbm_arena_strndup(a, name, i);
}
}
// Strip trailing ? (optionals)
if (len > 0 && name[len - SKIP_ONE] == '?') {
return cbm_arena_strndup(a, name, len - SKIP_ONE);
}
return name;
}
// Extract type name from a type annotation node.
static const char *extract_type_text(CBMArena *a, TSNode node, const char *source) {
enum { MAX_UNWRAP = 8 };
for (int depth = 0; depth < MAX_UNWRAP; depth++) {
const char *kind = ts_node_type(node);
if (strcmp(kind, "type_identifier") == 0 || strcmp(kind, "identifier") == 0 ||
strcmp(kind, "simple_identifier") == 0 || strcmp(kind, "name") == 0) {
return cbm_node_text(a, node, source);
}
if (strcmp(kind, "generic_type") == 0 || strcmp(kind, "parameterized_type") == 0) {
if (ts_node_child_count(node) > 0) {
return cbm_node_text(a, ts_node_child(node, 0), source);
}
}
if (strcmp(kind, "pointer_type") == 0 || strcmp(kind, "reference_type") == 0 ||
strcmp(kind, "slice_type") == 0 || strcmp(kind, "array_type") == 0) {
TSNode elem = ts_node_child_by_field_name(node, TS_FIELD("element"));
if (!ts_node_is_null(elem)) {
node = elem;
continue;
}
TSNode type_node = ts_node_child_by_field_name(node, TS_FIELD("type"));
if (!ts_node_is_null(type_node)) {
node = type_node;
continue;
}
}
break;
}
return clean_type_name(a, cbm_node_text(a, node, source));
}
// Add a type reference for a function.
static void add_type_ref(CBMExtractCtx *ctx, const char *type_name, const char *func_qn) {
if (!type_name || !type_name[0]) {
return;
}
type_name = clean_type_name(ctx->arena, type_name);
if (!type_name || !type_name[0]) {
return;
}
if (is_builtin_type(type_name)) {
return;
}
CBMTypeRef tr;
tr.type_name = type_name;
tr.enclosing_func_qn = func_qn;
cbm_typerefs_push(&ctx->result->type_refs, ctx->arena, tr);
}
// Extract parameter types from a parameters/formal_parameters node.
static void extract_param_type_refs(CBMExtractCtx *ctx, TSNode params, const char *func_qn) {
uint32_t count = ts_node_child_count(params);
for (uint32_t i = 0; i < count; i++) {
TSNode child = ts_node_child(params, i);
TSNode type_node = ts_node_child_by_field_name(child, TS_FIELD("type"));
if (!ts_node_is_null(type_node)) {
const char *tname = extract_type_text(ctx->arena, type_node, ctx->source);
add_type_ref(ctx, tname, func_qn);
}
}
}
// Extract return type references.
static void extract_return_type_refs(CBMExtractCtx *ctx, TSNode func_node, const char *func_qn) {
const char *fields[] = {"result", "return_type", "type", NULL};
for (const char **f = fields; *f; f++) {
TSNode rt = ts_node_child_by_field_name(func_node, *f, (uint32_t)strlen(*f));
if (!ts_node_is_null(rt)) {
const char *tname = extract_type_text(ctx->arena, rt, ctx->source);
add_type_ref(ctx, tname, func_qn);
break;
}
}
}
// Extract type ref from a node whose "type" field contains the type.
static void extract_type_field_ref(CBMExtractCtx *ctx, TSNode node, const char *func_qn) {
TSNode type_node = ts_node_child_by_field_name(node, TS_FIELD("type"));
if (!ts_node_is_null(type_node)) {
add_type_ref(ctx, extract_type_text(ctx->arena, type_node, ctx->source), func_qn);
}
}
// Extract type refs from TS/JS type_arguments or type_annotation children.
static void extract_ts_body_type_refs(CBMExtractCtx *ctx, TSNode node, const char *kind,
const char *func_qn) {
if (strcmp(kind, "as_expression") == 0 || strcmp(kind, "satisfies_expression") == 0) {
extract_type_field_ref(ctx, node, func_qn);
} else if (strcmp(kind, "type_arguments") == 0) {
uint32_t nc = ts_node_child_count(node);
for (uint32_t i = 0; i < nc; i++) {
TSNode child = ts_node_child(node, i);
if (strcmp(ts_node_type(child), "type_identifier") == 0 ||
strcmp(ts_node_type(child), "identifier") == 0) {
add_type_ref(ctx, cbm_node_text(ctx->arena, child, ctx->source), func_qn);
}
}
} else if (strcmp(kind, "variable_declarator") == 0) {
uint32_t nc = ts_node_child_count(node);
for (uint32_t i = 0; i < nc; i++) {
TSNode child = ts_node_child(node, i);
if (strcmp(ts_node_type(child), "type_annotation") == 0) {
if (ts_node_child_count(child) > 0) {
TSNode inner = ts_node_child(child, ts_node_child_count(child) - SKIP_ONE);
add_type_ref(ctx, extract_type_text(ctx->arena, inner, ctx->source), func_qn);
}
}
}
}
}
// Extract type refs from Java generic_type children.
static void extract_java_body_type_refs(CBMExtractCtx *ctx, TSNode node, const char *kind,
const char *func_qn) {
if (strcmp(kind, "local_variable_declaration") == 0 || strcmp(kind, "cast_expression") == 0) {
extract_type_field_ref(ctx, node, func_qn);
} else if (strcmp(kind, "generic_type") == 0) {
uint32_t nc = ts_node_child_count(node);
for (uint32_t i = 0; i < nc; i++) {
TSNode child = ts_node_child(node, i);
if (strcmp(ts_node_type(child), "type_arguments") == 0) {
uint32_t nta = ts_node_child_count(child);
for (uint32_t j = 0; j < nta; j++) {
TSNode ta = ts_node_child(child, j);
if (strcmp(ts_node_type(ta), "type_identifier") == 0) {
add_type_ref(ctx, cbm_node_text(ctx->arena, ta, ctx->source), func_qn);
}
}
}
}
}
}
// Process a single node for body-level type references.
static void process_body_type_ref(CBMExtractCtx *ctx, TSNode node, const char *func_qn) {
const char *kind = ts_node_type(node);
switch (ctx->language) {
case CBM_LANG_GO:
if (strcmp(kind, "var_spec") == 0 || strcmp(kind, "type_assertion") == 0 ||
strcmp(kind, "type_conversion_expression") == 0 ||
strcmp(kind, "composite_literal") == 0) {
extract_type_field_ref(ctx, node, func_qn);
}
break;
case CBM_LANG_TYPESCRIPT:
case CBM_LANG_TSX:
extract_ts_body_type_refs(ctx, node, kind, func_qn);
break;
case CBM_LANG_JAVA:
extract_java_body_type_refs(ctx, node, kind, func_qn);
break;
case CBM_LANG_PYTHON:
if (strcmp(kind, "assignment") == 0) {
TSNode type_node = ts_node_child_by_field_name(node, TS_FIELD("type"));
if (!ts_node_is_null(type_node)) {
add_type_ref(ctx, cbm_node_text(ctx->arena, type_node, ctx->source), func_qn);
}
}
break;
case CBM_LANG_RUST:
if (strcmp(kind, "let_declaration") == 0 || strcmp(kind, "type_cast_expression") == 0) {
extract_type_field_ref(ctx, node, func_qn);
}
break;
default:
break;
}
}
// Walk function body for type references (casts, type assertions, local var types, generics).
static void walk_body_type_refs(CBMExtractCtx *ctx, TSNode root, const char *func_qn) {
TSNodeStack stack;
ts_nstack_init(&stack, ctx->arena, 4096);
ts_nstack_push(&stack, ctx->arena, root);
while (stack.count > 0) {
TSNode node = ts_nstack_pop(&stack);
process_body_type_ref(ctx, node, func_qn);
uint32_t count = ts_node_child_count(node);
for (int i = (int)count - SKIP_ONE; i >= 0; i--) {
ts_nstack_push(&stack, ctx->arena, ts_node_child(node, (uint32_t)i));
}
}
}
// Walk AST for function nodes, extract type references from signatures and bodies.
/* Process a single function node: extract type refs from signature + body. */
static void process_func_type_refs(CBMExtractCtx *ctx, TSNode node) {
TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("name"));
if (ts_node_is_null(name_node)) {
return;
}
char *func_name = cbm_node_text(ctx->arena, name_node, ctx->source);
if (!func_name || !func_name[0]) {
return;
}
const char *func_qn = cbm_fqn_compute(ctx->arena, ctx->project, ctx->rel_path, func_name);
TSNode params = ts_node_child_by_field_name(node, TS_FIELD("parameters"));
if (!ts_node_is_null(params)) {
extract_param_type_refs(ctx, params, func_qn);
}
extract_return_type_refs(ctx, node, func_qn);
TSNode body = ts_node_child_by_field_name(node, TS_FIELD("body"));
if (ts_node_is_null(body)) {
body = ts_node_child_by_field_name(node, TS_FIELD("block"));
}
if (!ts_node_is_null(body)) {
walk_body_type_refs(ctx, body, func_qn);
}
}
static void walk_type_refs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
if (!spec->function_node_types || !spec->function_node_types[0]) {
return;
}
TSNodeStack stack;
ts_nstack_init(&stack, ctx->arena, 4096);
ts_nstack_push(&stack, ctx->arena, root);
while (stack.count > 0) {
TSNode node = ts_nstack_pop(&stack);
if (cbm_kind_in_set(node, spec->function_node_types)) {
process_func_type_refs(ctx, node);
continue;
}
uint32_t count = ts_node_child_count(node);
for (int i = (int)count - SKIP_ONE; i >= 0; i--) {
ts_nstack_push(&stack, ctx->arena, ts_node_child(node, (uint32_t)i));
}
}
}
void cbm_extract_type_refs(CBMExtractCtx *ctx) {
const CBMLangSpec *spec = cbm_lang_spec(ctx->language);
if (!spec) {
return;
}
walk_type_refs(ctx, ctx->root, spec);
}
// --- Unified handler ---
// For function nodes: extract signature type refs (params + return).
// For body-level type-bearing nodes: extract body type refs.
// The cursor visits both, so this single handler replaces the old
// walk_type_refs + walk_body_type_refs split.
// Extract signature type refs from a function node.
static void extract_signature_type_refs(CBMExtractCtx *ctx, TSNode node, WalkState *state) {
TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("name"));
if (ts_node_is_null(name_node)) {
return;
}
char *func_name = cbm_node_text(ctx->arena, name_node, ctx->source);
if (!func_name || !func_name[0]) {
return;
}
const char *func_qn;
if (state->enclosing_class_qn) {
func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", state->enclosing_class_qn, func_name);
} else {
func_qn = cbm_fqn_compute(ctx->arena, ctx->project, ctx->rel_path, func_name);
}
TSNode params = ts_node_child_by_field_name(node, TS_FIELD("parameters"));
if (!ts_node_is_null(params)) {
extract_param_type_refs(ctx, params, func_qn);
}
extract_return_type_refs(ctx, node, func_qn);
}
void handle_type_refs(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state) {
if (!spec->function_node_types || !spec->function_node_types[0]) {
return;
}
if (cbm_kind_in_set(node, spec->function_node_types)) {
extract_signature_type_refs(ctx, node, state);
return;
}
process_body_type_ref(ctx, node, state->enclosing_func_qn);
}
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
#ifndef CBM_EXTRACT_UNIFIED_H
#define CBM_EXTRACT_UNIFIED_H
#include "cbm.h"
#include "lang_specs.h"
// Scope kinds for the walk state stack.
#define SCOPE_FUNC 1
#define SCOPE_CLASS 2
#define SCOPE_CALL 3
#define SCOPE_IMPORT 4
#define SCOPE_LOOP 5
#define SCOPE_BRANCH 6
#define MAX_SCOPES 64
// WalkState tracks scope context during the unified cursor walk.
// Replaces parent-chain walks for enclosing_func_qn, inside_call, etc.
typedef struct {
const char *enclosing_func_qn; // current function QN (module_qn at top level)
const char *enclosing_class_qn; // current class QN (NULL outside class)
bool inside_call; // within a call_node_types subtree
bool inside_import; // within an import_node_types subtree
int loop_depth; // count of enclosing loop scopes (for bottleneck metrics)
int branch_depth; // count of enclosing branch scopes
struct {
const char *qn;
uint32_t depth;
uint8_t kind;
} scopes[MAX_SCOPES];
int scope_top;
} WalkState;
// Per-node handler prototypes. Each is called once per node during the
// unified cursor walk, replacing the old recursive walk_* functions.
void handle_calls(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state);
void handle_usages(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state);
void handle_throws(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state);
void handle_readwrites(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state);
void handle_type_refs(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state);
void handle_env_accesses(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
WalkState *state);
void handle_type_assigns(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
WalkState *state);
// Single-pass extraction using TSTreeCursor. Visits every node once,
// dispatching to all handlers per node. Replaces the 7 separate walk_*
// functions for calls/usages/throws/readwrites/type_refs/env_accesses/type_assigns.
// Definitions and imports stay as separate passes (different recursion patterns).
void cbm_extract_unified(CBMExtractCtx *ctx);
#endif // CBM_EXTRACT_UNIFIED_H
+170
View File
@@ -0,0 +1,170 @@
#include "cbm.h"
#include "helpers.h"
#include "lang_specs.h"
#include "extract_unified.h"
#include "tree_sitter/api.h" // TSNode, ts_node_*
#include "foundation/constants.h"
#include "extract_node_stack.h"
enum { MAX_PARENT_DEPTH = 10, LAST_IDX = 1 };
#include <stdint.h> // uint32_t
#include <string.h>
#include <ctype.h>
// Forward declaration
static void walk_usages(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec);
// Check if a node is inside a call expression (to avoid double-counting as usage)
static bool is_inside_call(TSNode node, const CBMLangSpec *spec) {
TSNode cur = ts_node_parent(node);
int depth = 0;
while (!ts_node_is_null(cur) && depth < MAX_PARENT_DEPTH) {
if (cbm_kind_in_set(cur, spec->call_node_types)) {
return true;
}
cur = ts_node_parent(cur);
depth++;
}
return false;
}
// Check if a node is inside an import statement
static bool is_inside_import(TSNode node, const CBMLangSpec *spec) {
if (!spec->import_node_types || !spec->import_node_types[0]) {
return false;
}
TSNode cur = ts_node_parent(node);
int depth = 0;
while (!ts_node_is_null(cur) && depth < MAX_PARENT_DEPTH) {
if (cbm_kind_in_set(cur, spec->import_node_types)) {
return true;
}
cur = ts_node_parent(cur);
depth++;
}
return false;
}
// Is this an identifier-like node that represents a reference?
static bool is_reference_node(TSNode node, CBMLanguage lang) {
const char *kind = ts_node_type(node);
// Common identifier types across languages
if (strcmp(kind, "identifier") == 0 || strcmp(kind, "simple_identifier") == 0 ||
strcmp(kind, "type_identifier") == 0) {
return true;
}
// Language-specific reference types
switch (lang) {
case CBM_LANG_GO:
return strcmp(kind, "field_identifier") == 0 || strcmp(kind, "package_identifier") == 0;
case CBM_LANG_PYTHON:
return strcmp(kind, "attribute") == 0;
case CBM_LANG_RUST:
return strcmp(kind, "field_identifier") == 0 || strcmp(kind, "scoped_identifier") == 0;
case CBM_LANG_HASKELL:
return strcmp(kind, "variable") == 0 || strcmp(kind, "constructor") == 0;
case CBM_LANG_OCAML:
return strcmp(kind, "value_path") == 0 || strcmp(kind, "constructor_path") == 0;
case CBM_LANG_ERLANG:
return strcmp(kind, "atom") == 0 || strcmp(kind, "var") == 0;
default:
return false;
}
}
// Check if a reference node is a definition name (the "name" field of its parent).
static bool is_definition_name(TSNode node) {
TSNode parent = ts_node_parent(node);
if (ts_node_is_null(parent)) {
return false;
}
TSNode name_field = ts_node_child_by_field_name(parent, TS_FIELD("name"));
return !ts_node_is_null(name_field) &&
ts_node_start_byte(name_field) == ts_node_start_byte(node) &&
ts_node_end_byte(name_field) == ts_node_end_byte(node);
}
// Try to emit a usage for a reference node. Returns early if the node should be skipped.
static void try_emit_usage(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec) {
if (!is_reference_node(node, ctx->language)) {
return;
}
if (is_inside_call(node, spec) || is_inside_import(node, spec)) {
return;
}
if (is_definition_name(node)) {
return;
}
char *name = cbm_node_text(ctx->arena, node, ctx->source);
if (name && name[0] && !cbm_is_keyword(name, ctx->language)) {
CBMUsage usage;
usage.ref_name = name;
usage.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, node);
cbm_usages_push(&ctx->result->usages, ctx->arena, usage);
}
}
// Iterative usage walker — explicit stack
static void walk_usages(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
TSNodeStack stack;
ts_nstack_init(&stack, ctx->arena, 4096);
ts_nstack_push(&stack, ctx->arena, root);
while (stack.count > 0) {
TSNode node = ts_nstack_pop(&stack);
try_emit_usage(ctx, node, spec);
uint32_t count = ts_node_child_count(node);
for (int i = (int)count - LAST_IDX; i >= 0; i--) {
ts_nstack_push(&stack, ctx->arena, ts_node_child(node, (uint32_t)i));
}
}
}
void cbm_extract_usages(CBMExtractCtx *ctx) {
const CBMLangSpec *spec = cbm_lang_spec(ctx->language);
if (!spec) {
return;
}
walk_usages(ctx, ctx->root, spec);
}
// --- Unified handler: called once per node by the cursor walk ---
// Uses WalkState flags instead of parent-chain walks for O(1) context checks.
void handle_usages(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state) {
(void)spec;
if (!is_reference_node(node, ctx->language)) {
return;
}
// Skip if inside a call (already counted as CALLS edge) — O(1) via state
if (state->inside_call) {
return;
}
// Skip if inside an import
if (state->inside_import) {
return;
}
// Skip if it's a definition name (left side of assignment, function name)
TSNode parent = ts_node_parent(node);
if (!ts_node_is_null(parent)) {
TSNode name_field = ts_node_child_by_field_name(parent, TS_FIELD("name"));
if (!ts_node_is_null(name_field) &&
ts_node_start_byte(name_field) == ts_node_start_byte(node) &&
ts_node_end_byte(name_field) == ts_node_end_byte(node)) {
return;
}
}
char *name = cbm_node_text(ctx->arena, node, ctx->source);
if (name && name[0] && !cbm_is_keyword(name, ctx->language)) {
CBMUsage usage;
usage.ref_name = name;
usage.enclosing_func_qn = state->enclosing_func_qn;
cbm_usages_push(&ctx->result->usages, ctx->arena, usage);
}
}
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: ada
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/ada/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: agda
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/agda/parser.c"
#include "vendored/grammars/agda/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: apex
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/apex/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: assembly
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/assembly/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: astro
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/astro/parser.c"
#include "vendored/grammars/astro/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: awk
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/awk/parser.c"
#include "vendored/grammars/awk/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: bash
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/bash/parser.c"
#include "vendored/grammars/bash/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: beancount
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/beancount/parser.c"
#include "vendored/grammars/beancount/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: bibtex
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/bibtex/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: bicep
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/bicep/parser.c"
#include "vendored/grammars/bicep/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: bitbake
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/bitbake/parser.c"
#include "vendored/grammars/bitbake/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: blade
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/blade/parser.c"
#include "vendored/grammars/blade/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: c
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/c/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: c_sharp
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/c_sharp/parser.c"
#include "vendored/grammars/c_sharp/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: cairo
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/cairo/parser.c"
#include "vendored/grammars/cairo/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: capnp
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/capnp/parser.c"
+6
View File
@@ -0,0 +1,6 @@
// Vendored tree-sitter grammar: cfml (CFML tag dialect — .cfm templates)
// Each grammar compiled as separate unit (conflicting static symbols).
// scanner.c pulls in the shared ../../common/scanner.h (and tag.h), which is
// all-static; tag.c is therefore NOT included here (it would redefine it).
#include "vendored/grammars/cfml/parser.c"
#include "vendored/grammars/cfml/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: cfscript (CFML script dialect — .cfc components)
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/cfscript/parser.c"
#include "vendored/grammars/cfscript/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: clojure
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/clojure/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: cmake
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/cmake/parser.c"
#include "vendored/grammars/cmake/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: cobol
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/cobol/parser.c"
#include "vendored/grammars/cobol/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: commonlisp
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/commonlisp/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: cpp
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/cpp/parser.c"
#include "vendored/grammars/cpp/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: crystal
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/crystal/parser.c"
#include "vendored/grammars/crystal/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: css
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/css/parser.c"
#include "vendored/grammars/css/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: csv
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/csv/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: cuda
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/cuda/parser.c"
#include "vendored/grammars/cuda/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: d
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/d/parser.c"
#include "vendored/grammars/d/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: dart
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/dart/parser.c"
#include "vendored/grammars/dart/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: devicetree
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/devicetree/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: diff
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/diff/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: dockerfile
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/dockerfile/parser.c"
#include "vendored/grammars/dockerfile/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: dotenv
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/dotenv/parser.c"
#include "vendored/grammars/dotenv/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: elisp (Emacs Lisp)
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/elisp/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: elixir
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/elixir/parser.c"
#include "vendored/grammars/elixir/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: elm
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/elm/parser.c"
#include "vendored/grammars/elm/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: erlang
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/erlang/parser.c"
#include "vendored/grammars/erlang/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: fennel
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/fennel/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: fish
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/fish/parser.c"
#include "vendored/grammars/fish/scanner.c"
+2
View File
@@ -0,0 +1,2 @@
// Vendored tree-sitter grammar: form
#include "vendored/grammars/form/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: fortran
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/fortran/parser.c"
#include "vendored/grammars/fortran/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: fsharp
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/fsharp/parser.c"
#include "vendored/grammars/fsharp/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: func
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/func/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: gdscript
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/gdscript/parser.c"
#include "vendored/grammars/gdscript/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: gitattributes
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/gitattributes/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: gitignore
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/gitignore/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: gleam
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/gleam/parser.c"
#include "vendored/grammars/gleam/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: glsl
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/glsl/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: gn
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/gn/parser.c"
#include "vendored/grammars/gn/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: go
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/go/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: gomod
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/gomod/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: gotemplate
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/gotemplate/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: graphql
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/graphql/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: groovy
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/groovy/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: hare
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/hare/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: haskell
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/haskell/parser.c"
#include "vendored/grammars/haskell/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: hcl
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/hcl/parser.c"
#include "vendored/grammars/hcl/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: hlsl
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/hlsl/parser.c"
#include "vendored/grammars/hlsl/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: html
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/html/parser.c"
#include "vendored/grammars/html/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: hyprlang
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/hyprlang/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: ini
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/ini/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: ispc
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/ispc/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: janet
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/janet/parser.c"
#include "vendored/grammars/janet/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: java
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/java/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: javascript
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/javascript/parser.c"
#include "vendored/grammars/javascript/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: jinja2
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/jinja2/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: jsdoc
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/jsdoc/parser.c"
#include "vendored/grammars/jsdoc/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: json
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/json/parser.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: json5
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/json5/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: jsonnet
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/jsonnet/parser.c"
#include "vendored/grammars/jsonnet/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: julia
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/julia/parser.c"
#include "vendored/grammars/julia/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: just
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/just/parser.c"
#include "vendored/grammars/just/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: kconfig
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/kconfig/parser.c"
#include "vendored/grammars/kconfig/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: kdl
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/kdl/parser.c"
#include "vendored/grammars/kdl/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: kotlin
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/kotlin/parser.c"
#include "vendored/grammars/kotlin/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: lean
#include "vendored/grammars/lean/parser.c"
#include "vendored/grammars/lean/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: linkerscript
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/linkerscript/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: liquid
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/liquid/parser.c"
#include "vendored/grammars/liquid/scanner.c"
+3
View File
@@ -0,0 +1,3 @@
// Vendored tree-sitter grammar: llvm
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/llvm/parser.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: lua
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/lua/parser.c"
#include "vendored/grammars/lua/scanner.c"
+4
View File
@@ -0,0 +1,4 @@
// Vendored tree-sitter grammar: luau
// Each grammar compiled as separate unit (conflicting static symbols).
#include "vendored/grammars/luau/parser.c"
#include "vendored/grammars/luau/scanner.c"

Some files were not shown because too many files have changed in this diff Show More