mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-26 06:43:15 +08:00
The test-impact engine selects tests by walking the graph, so it can only be as right as the graph. Three gaps made it wrong: One identity for a definition's mutually exclusive variants - A definition written once per #if/#else branch, per #[cfg], per platform file (foo_linux.c / foo_win.c, platform directories) or per Go build tag is ONE node. The graph buffer keeps every variant's span in a `variants` node property (definition labels only), so a change in any variant maps to the definition. - Extraction now sees definitions inside conditional blocks where it skipped them: C++/C#/ObjC class members under #if (one cursor walk, conditional block types per language in lang_specs), Elixir defs under if/unless, Fortran .F90/.F95/.F03/.F08, Rust #[cfg] twins (no more `name#cfg(...)` QNs that lost every call), C platform files and directories (non-static file-scope functions). - Haskell: the vendored scanner reads only an #if group's first branch. The grammar stays upstream; every further branch is parsed as its own same-length view (cpp_branch_views.c) and adds what the raw parse missed. haskell/bytestring: no new partial parses, +27 functions, IMPORTS 322 -> 329, 131 definitions with variant spans. - Incremental closure repair re-parses every file of such a definition when one of them changes (variant partners through DEFINES), so a repair equals a full index. No cross-language name guesses - A name-only resolution (unique_name, suffix_match, qualified_suffix, field_type_hint, and same_module across files that only share a path stem) never binds a symbol another language defines. - Bash imports are the files a script sources: every top-level command was read as an import of its name (`set -e` imported "set", bound to a TSX method in 137 scripts here). - This repository: cross-language CALLS 5,916 -> 5,091; the remainder are C/C++ pairs, builtins and same-stem module collisions (recorded). SPAWNS edges (per language, like the HTTP client patterns) - A call that starts a program (subprocess.run, exec.Command, child_process.spawn, posix_spawn, system, Process.Start, Command::new, ... about 30 languages, through the file's own imports) gets a SPAWNS edge to a Process node named after the program (literal first word, or <dynamic>) instead of a CALLS guess. No target resolution. Both resolvers ask at the same point (parity test). The engine follows the graph - Seeds map changed lines through every variant span; a test written once per #if branch is one test; when a change reaches a non-test `main`, the tests that spawn processes are reached too (the process runs main, and main reaches everything). Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
184 lines
5.5 KiB
C
184 lines
5.5 KiB
C
/*
|
|
* cpp_branch_views.c — one parse view per #if/#else branch (see the header).
|
|
*/
|
|
#include "cpp_branch_views.h"
|
|
|
|
#include <string.h>
|
|
|
|
enum { CPP_LINE_CODE, CPP_LINE_START, CPP_LINE_ELSE, CPP_LINE_END };
|
|
|
|
enum { CPP_NEST_MAX = 64 };
|
|
|
|
bool cbm_lang_needs_cpp_branch_views(CBMLanguage lang) {
|
|
return lang == CBM_LANG_HASKELL;
|
|
}
|
|
|
|
static bool cpp_word_char(char c) {
|
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
|
|
}
|
|
|
|
// Classify the line at src[pos..end): a conditional directive is a '#' in
|
|
// column 0, optional blanks, then the directive word — the same lines the
|
|
// Haskell scanner treats as conditionals.
|
|
static int cpp_line_kind(const char *src, int pos, int end) {
|
|
if (pos >= end || src[pos] != '#') {
|
|
return CPP_LINE_CODE;
|
|
}
|
|
int p = pos + 1;
|
|
while (p < end && (src[p] == ' ' || src[p] == '\t')) {
|
|
p++;
|
|
}
|
|
int w = p;
|
|
while (w < end && cpp_word_char(src[w])) {
|
|
w++;
|
|
}
|
|
static const struct {
|
|
const char *word;
|
|
int kind;
|
|
} words[] = {
|
|
{"if", CPP_LINE_START}, {"ifdef", CPP_LINE_START}, {"ifndef", CPP_LINE_START},
|
|
{"else", CPP_LINE_ELSE}, {"elif", CPP_LINE_ELSE}, {"elifdef", CPP_LINE_ELSE},
|
|
{"elifndef", CPP_LINE_ELSE}, {"endif", CPP_LINE_END},
|
|
};
|
|
size_t n = (size_t)(w - p);
|
|
for (size_t i = 0; i < sizeof(words) / sizeof(words[0]); i++) {
|
|
if (strlen(words[i].word) == n && memcmp(src + p, words[i].word, n) == 0) {
|
|
return words[i].kind;
|
|
}
|
|
}
|
|
return CPP_LINE_CODE;
|
|
}
|
|
|
|
static int cpp_line_end(const char *src, int pos, int len) {
|
|
const char *nl = memchr(src + pos, '\n', (size_t)(len - pos));
|
|
return nl ? (int)(nl - src) : len;
|
|
}
|
|
|
|
typedef struct {
|
|
int group; // groups are numbered in order of their #if line
|
|
int branch; // branch the current line is in
|
|
bool outer; // the enclosing context is kept
|
|
} cpp_frame_t;
|
|
|
|
typedef struct {
|
|
cpp_frame_t frames[CPP_NEST_MAX];
|
|
int depth;
|
|
int overflow; // groups nested past CPP_NEST_MAX are not tracked
|
|
int next;
|
|
} cpp_nest_t;
|
|
|
|
// Track one line. Returns the innermost tracked group when the line is one of
|
|
// its #else/#elif lines, else NULL.
|
|
static cpp_frame_t *cpp_nest_step(cpp_nest_t *n, int kind, bool active) {
|
|
switch (kind) {
|
|
case CPP_LINE_START:
|
|
if (n->depth < CPP_NEST_MAX && n->overflow == 0) {
|
|
n->frames[n->depth++] = (cpp_frame_t){n->next, 0, active};
|
|
} else {
|
|
n->overflow++;
|
|
}
|
|
n->next++;
|
|
return NULL;
|
|
case CPP_LINE_ELSE:
|
|
if (n->overflow > 0 || n->depth == 0) {
|
|
return NULL;
|
|
}
|
|
n->frames[n->depth - 1].branch++;
|
|
return &n->frames[n->depth - 1];
|
|
case CPP_LINE_END:
|
|
if (n->overflow > 0) {
|
|
n->overflow--;
|
|
} else if (n->depth > 0) {
|
|
n->depth--;
|
|
}
|
|
return NULL;
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Branch count per group. Returns the group count, or -1 on allocation failure.
|
|
static int cpp_group_branches(CBMArena *a, const char *src, int len, int **out) {
|
|
int groups = 0;
|
|
for (int pos = 0; pos < len;) {
|
|
int end = cpp_line_end(src, pos, len);
|
|
groups += cpp_line_kind(src, pos, end) == CPP_LINE_START;
|
|
pos = end + 1;
|
|
}
|
|
*out = NULL;
|
|
if (groups == 0) {
|
|
return 0;
|
|
}
|
|
int *branches = cbm_arena_alloc(a, sizeof(int) * (size_t)groups);
|
|
if (!branches) {
|
|
return -1;
|
|
}
|
|
for (int g = 0; g < groups; g++) {
|
|
branches[g] = 1;
|
|
}
|
|
cpp_nest_t nest = {.depth = 0};
|
|
for (int pos = 0; pos < len;) {
|
|
int end = cpp_line_end(src, pos, len);
|
|
cpp_frame_t *frame = cpp_nest_step(&nest, cpp_line_kind(src, pos, end), true);
|
|
if (frame) {
|
|
branches[frame->group] = frame->branch + 1;
|
|
}
|
|
pos = end + 1;
|
|
}
|
|
*out = branches;
|
|
return groups;
|
|
}
|
|
|
|
int cbm_cpp_branch_view_count(CBMArena *a, const char *src, int len) {
|
|
int *branches = NULL;
|
|
int groups = cpp_group_branches(a, src, len, &branches);
|
|
int most = 1;
|
|
for (int g = 0; g < groups; g++) {
|
|
if (branches[g] > most) {
|
|
most = branches[g];
|
|
}
|
|
}
|
|
int views = most - 1;
|
|
return views > CBM_CPP_BRANCH_VIEWS_MAX ? CBM_CPP_BRANCH_VIEWS_MAX : views;
|
|
}
|
|
|
|
// The current line belongs to the view: every enclosing group sits in the
|
|
// branch the view keeps.
|
|
static bool cpp_view_keeps(const cpp_nest_t *n, const int *branches, int view) {
|
|
if (n->depth == 0) {
|
|
return true;
|
|
}
|
|
const cpp_frame_t *top = &n->frames[n->depth - 1];
|
|
int nb = branches[top->group];
|
|
int kept = 0;
|
|
if (nb >= 2) {
|
|
kept = view < nb - 1 ? view : nb - 1;
|
|
}
|
|
return top->outer && top->branch == kept;
|
|
}
|
|
|
|
char *cbm_cpp_branch_view(CBMArena *a, const char *src, int len, int view) {
|
|
int *branches = NULL;
|
|
int groups = cpp_group_branches(a, src, len, &branches);
|
|
char *buf = groups >= 0 ? cbm_arena_alloc(a, (size_t)len + 1) : NULL;
|
|
if (!buf) {
|
|
return NULL;
|
|
}
|
|
memcpy(buf, src, (size_t)len);
|
|
buf[len] = '\0';
|
|
cpp_nest_t nest = {.depth = 0};
|
|
for (int pos = 0; pos < len;) {
|
|
int end = cpp_line_end(src, pos, len);
|
|
bool kept = cpp_view_keeps(&nest, branches, view);
|
|
int kind = cpp_line_kind(src, pos, end);
|
|
cpp_nest_step(&nest, kind, kept);
|
|
if (kind != CPP_LINE_CODE || !kept) {
|
|
for (int i = pos; i < end; i++) {
|
|
buf[i] = buf[i] == '\r' ? '\r' : ' ';
|
|
}
|
|
}
|
|
pos = end + 1;
|
|
}
|
|
return buf;
|
|
}
|