mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Resolve IMPORTS edges for 12 grammar-only languages
extract_imports: recursive lisp walker (Clojure ns/:require, Common Lisp defpackage/:use, Fennel nested require) + parsers for elm/move/smali(descriptor-demangle)/tlaplus(EXTENDS)/vhdl(use work)/wit/smithy(#->.)/hyprlang(source). lang_specs: astro frontmatter JS re-parse for component imports. pass_pkgmap: basename sibling-file candidate for prefixed include paths. All sound resolution via namespace-map / sibling / symbol-fallback / relative-import.
This commit is contained in:
1 parent
bd6d617bcf
commit
0d87a86e54
3 files changed
+454
-29
No files matched your search
+440
-28
@@ -64,6 +64,14 @@ static void parse_tablegen_imports(CBMExtractCtx *ctx);
|
||||
static void parse_crystal_imports(CBMExtractCtx *ctx);
|
||||
static void parse_fsharp_imports(CBMExtractCtx *ctx);
|
||||
static void parse_ada_imports(CBMExtractCtx *ctx);
|
||||
static void parse_elm_imports(CBMExtractCtx *ctx);
|
||||
static void parse_move_imports(CBMExtractCtx *ctx);
|
||||
static void parse_smali_imports(CBMExtractCtx *ctx);
|
||||
static void parse_tlaplus_imports(CBMExtractCtx *ctx);
|
||||
static void parse_vhdl_imports(CBMExtractCtx *ctx);
|
||||
static void parse_wit_imports(CBMExtractCtx *ctx);
|
||||
static void parse_smithy_imports(CBMExtractCtx *ctx);
|
||||
static void parse_hyprlang_imports(CBMExtractCtx *ctx);
|
||||
|
||||
// Helper: strip quotes from a string literal
|
||||
static char *strip_quotes(CBMArena *a, const char *s) {
|
||||
@@ -1657,37 +1665,134 @@ static void lisp_push_module(CBMExtractCtx *ctx, TSNode mod_node) {
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_lisp_imports(CBMExtractCtx *ctx) {
|
||||
/* Strip a leading ':' from a lisp keyword/symbol token (Clojure/Fennel/CL
|
||||
* package keywords carry it: `:util` → `util`). Returns an arena copy. */
|
||||
static char *lisp_strip_kw(CBMArena *a, const char *s) {
|
||||
if (!s) {
|
||||
return NULL;
|
||||
}
|
||||
if (s[0] == ':') {
|
||||
s++;
|
||||
}
|
||||
return cbm_arena_strdup(a, s);
|
||||
}
|
||||
|
||||
/* Record the file's declared namespace/package so the pipeline namespace map
|
||||
* can resolve `(:require ns)` / `(:use :pkg)` to the declaring file. First
|
||||
* declaration wins (mirrors capture_namespace_decl). */
|
||||
static void lisp_set_namespace(CBMExtractCtx *ctx, TSNode name_node) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSTreeCursor cursor = ts_tree_cursor_new(ctx->root);
|
||||
if (!ts_tree_cursor_goto_first_child(&cursor)) {
|
||||
ts_tree_cursor_delete(&cursor);
|
||||
char *ns = lisp_strip_kw(a, cbm_node_text(a, name_node, ctx->source));
|
||||
if (ns && ns[0] && !ctx->result->namespace_name) {
|
||||
ctx->result->namespace_name = ns;
|
||||
}
|
||||
}
|
||||
|
||||
/* Clojure `(ns app.core (:require [app.util :as u]) (:use app.io))` and Common
|
||||
* Lisp `(defpackage :main (:use :cl :util))` carry their dependency list inside
|
||||
* nested keyword clauses (`:require`/`:use`/`:import`). Walk such a clause and
|
||||
* push each referenced module/package as an import. `clause` is the
|
||||
* `(:require ...)` / `(:use ...)` list. */
|
||||
static void lisp_push_clause_modules(CBMExtractCtx *ctx, TSNode clause) {
|
||||
uint32_t cc = ts_node_named_child_count(clause);
|
||||
for (uint32_t j = 1; j < cc; j++) { /* skip the leading keyword head */
|
||||
TSNode item = ts_node_named_child(clause, j);
|
||||
const char *ik = ts_node_type(item);
|
||||
/* `[app.util :as u]` — the module is the vector's first symbol. */
|
||||
if (strcmp(ik, "vec_lit") == 0 || strcmp(ik, "list_lit") == 0 ||
|
||||
strcmp(ik, "list") == 0) {
|
||||
uint32_t vc = ts_node_named_child_count(item);
|
||||
if (vc > 0) {
|
||||
lisp_push_module(ctx, ts_node_named_child(item, 0));
|
||||
}
|
||||
} else {
|
||||
/* bare symbol/keyword: `:util`, `app.io`. */
|
||||
lisp_push_module(ctx, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The s-expression head symbol text, or NULL if the list has no symbol head. */
|
||||
static char *lisp_head_text(CBMExtractCtx *ctx, TSNode list, TSNode *out_head) {
|
||||
if (ts_node_named_child_count(list) < 1) {
|
||||
return NULL;
|
||||
}
|
||||
TSNode head = ts_node_named_child(list, 0);
|
||||
const char *ht = ts_node_type(head);
|
||||
if (strcmp(ht, "symbol") != 0 && strcmp(ht, "sym_lit") != 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (out_head) {
|
||||
*out_head = head;
|
||||
}
|
||||
return cbm_node_text(ctx->arena, head, ctx->source);
|
||||
}
|
||||
|
||||
/* Process one s-expression list as a potential import/namespace form. */
|
||||
static void lisp_process_list(CBMExtractCtx *ctx, TSNode node) {
|
||||
CBMArena *a = ctx->arena;
|
||||
(void)a;
|
||||
TSNode head = node;
|
||||
char *hn = lisp_head_text(ctx, node, &head);
|
||||
if (!hn) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
TSNode node = ts_tree_cursor_current_node(&cursor);
|
||||
const char *nt = ts_node_type(node);
|
||||
/* Different lisp grammars name the s-expression list node differently:
|
||||
* Scheme/Racket/CL/elisp use "list"; Clojure uses "list_lit". */
|
||||
if (strcmp(nt, "list") != 0 && strcmp(nt, "list_lit") != 0) {
|
||||
continue;
|
||||
uint32_t nc = ts_node_named_child_count(node);
|
||||
|
||||
/* Clojure namespace form: `(ns app.core (:require ...) (:use ...))`.
|
||||
* 2nd child is the namespace symbol; later keyword-headed lists are
|
||||
* dependency clauses. */
|
||||
if (strcmp(hn, "ns") == 0 && nc >= 2) {
|
||||
lisp_set_namespace(ctx, ts_node_named_child(node, 1));
|
||||
for (uint32_t j = 2; j < nc; j++) {
|
||||
TSNode clause = ts_node_named_child(node, j);
|
||||
const char *ck = ts_node_type(clause);
|
||||
if (strcmp(ck, "list_lit") == 0 || strcmp(ck, "list") == 0) {
|
||||
lisp_push_clause_modules(ctx, clause);
|
||||
}
|
||||
}
|
||||
uint32_t nc = ts_node_named_child_count(node);
|
||||
if (nc < 1) {
|
||||
continue;
|
||||
}
|
||||
TSNode head = ts_node_named_child(node, 0);
|
||||
const char *ht = ts_node_type(head);
|
||||
/* Clojure tokenizes the head symbol as "sym_lit"; others as "symbol". */
|
||||
if (strcmp(ht, "symbol") != 0 && strcmp(ht, "sym_lit") != 0) {
|
||||
continue;
|
||||
}
|
||||
char *hn = cbm_node_text(a, head, ctx->source);
|
||||
if (!hn || (strcmp(hn, "import") != 0 && strcmp(hn, "require") != 0 &&
|
||||
strcmp(hn, "load") != 0 && strcmp(hn, "use") != 0 &&
|
||||
strcmp(hn, "include") != 0)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Common Lisp package form: `(defpackage :main (:use :cl :util) ...)`.
|
||||
* 2nd child is the package name keyword; nested `(:use ...)` lists name the
|
||||
* imported packages. */
|
||||
if (strcmp(hn, "defpackage") == 0 && nc >= 2) {
|
||||
lisp_set_namespace(ctx, ts_node_named_child(node, 1));
|
||||
for (uint32_t j = 2; j < nc; j++) {
|
||||
TSNode clause = ts_node_named_child(node, j);
|
||||
const char *ck = ts_node_type(clause);
|
||||
if (strcmp(ck, "list_lit") != 0 && strcmp(ck, "list") != 0) {
|
||||
continue;
|
||||
}
|
||||
/* Only `:use` / `:import-from` clauses denote dependencies. */
|
||||
char *chn = lisp_head_text(ctx, clause, NULL);
|
||||
if (chn && (strcmp(chn, ":use") == 0 || strstr(chn, "import-from"))) {
|
||||
lisp_push_clause_modules(ctx, clause);
|
||||
} else if (ts_node_named_child_count(clause) > 0) {
|
||||
TSNode ch0 = ts_node_named_child(clause, 0);
|
||||
const char *c0k = ts_node_type(ch0);
|
||||
/* CL `(:use ...)` head is a `kwd_lit`, not a symbol. */
|
||||
if (strcmp(c0k, "kwd_lit") == 0) {
|
||||
char *kw = cbm_node_text(ctx->arena, ch0, ctx->source);
|
||||
if (kw && (strstr(kw, "use") || strstr(kw, "import-from"))) {
|
||||
lisp_push_clause_modules(ctx, clause);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Common Lisp `(in-package :util)` also declares the file's package. */
|
||||
if (strcmp(hn, "in-package") == 0 && nc >= 2) {
|
||||
lisp_set_namespace(ctx, ts_node_named_child(node, 1));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Plain import head: `(require :util)`, `(import ...)`, `(use ...)`. */
|
||||
if (strcmp(hn, "import") == 0 || strcmp(hn, "require") == 0 || strcmp(hn, "load") == 0 ||
|
||||
strcmp(hn, "use") == 0 || strcmp(hn, "include") == 0) {
|
||||
for (uint32_t j = 1; j < nc; j++) {
|
||||
TSNode mod_node = ts_node_named_child(node, j);
|
||||
const char *mk = ts_node_type(mod_node);
|
||||
@@ -1699,8 +1804,25 @@ static void parse_lisp_imports(CBMExtractCtx *ctx) {
|
||||
lisp_push_module(ctx, mod_node);
|
||||
}
|
||||
}
|
||||
} while (ts_tree_cursor_goto_next_sibling(&cursor));
|
||||
ts_tree_cursor_delete(&cursor);
|
||||
}
|
||||
}
|
||||
|
||||
/* Recursively walk lisp lists. Fennel binds requires inside other forms
|
||||
* (`(local util (require :util))`), so we must descend into every list, not
|
||||
* just root children. */
|
||||
static void lisp_walk(CBMExtractCtx *ctx, TSNode node) { // NOLINT(misc-no-recursion)
|
||||
const char *nt = ts_node_type(node);
|
||||
if (strcmp(nt, "list") == 0 || strcmp(nt, "list_lit") == 0) {
|
||||
lisp_process_list(ctx, node);
|
||||
}
|
||||
uint32_t cc = ts_node_named_child_count(node);
|
||||
for (uint32_t i = 0; i < cc; i++) {
|
||||
lisp_walk(ctx, ts_node_named_child(node, i));
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_lisp_imports(CBMExtractCtx *ctx) {
|
||||
lisp_walk(ctx, ctx->root);
|
||||
}
|
||||
|
||||
// --- Starlark imports ---
|
||||
@@ -2346,6 +2468,271 @@ static void parse_ada_imports(CBMExtractCtx *ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Elm imports: `import Module.Path [as X] [exposing (..)]` ---
|
||||
// AST: file -> import_clause(moduleName: upper_case_qid). The module path maps
|
||||
// to a sibling file (`import Utils` -> Utils.elm), resolved by the pipeline's
|
||||
// sibling-file resolver.
|
||||
static void parse_elm_imports(CBMExtractCtx *ctx) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
if (strcmp(ts_node_type(node), "import_clause") == 0) {
|
||||
TSNode name = node;
|
||||
if (find_first_descendant_of(node, "upper_case_qid", &name)) {
|
||||
char *mod = cbm_node_text(a, name, ctx->source);
|
||||
if (mod && mod[0]) {
|
||||
/* Elm module dots map to directory separators on disk
|
||||
* (Math.Util -> Math/Util.elm); '.'-> '/' so the sibling
|
||||
* resolver builds the right path. */
|
||||
for (char *p = mod; *p; p++) {
|
||||
if (*p == '.') {
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
CBMImport imp = {.local_name = path_last(a, mod), .module_path = mod};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Move imports: `use 0xADDR::module [::member];` ---
|
||||
// AST: use_declaration with `argument: identifier` = the module name. The
|
||||
// leading hex address parses as an error node but the module identifier is
|
||||
// captured cleanly via the `argument` field. Resolves to a sibling file.
|
||||
static void parse_move_imports(CBMExtractCtx *ctx) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
if (strcmp(ts_node_type(node), "use_declaration") == 0) {
|
||||
TSNode arg = ts_node_child_by_field_name(node, TS_FIELD("argument"));
|
||||
char *mod = NULL;
|
||||
if (!ts_node_is_null(arg)) {
|
||||
mod = cbm_node_text(a, arg, ctx->source);
|
||||
} else if (find_first_descendant_of(node, "identifier", &arg)) {
|
||||
mod = cbm_node_text(a, arg, ctx->source);
|
||||
}
|
||||
if (mod && mod[0]) {
|
||||
CBMImport imp = {.local_name = path_last(a, mod), .module_path = mod};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Smali imports: `.super Lpkg/Name;` / `.implements Lpkg/Iface;` ---
|
||||
// AST: super_directive / implements_directive -> class_identifier holding a JVM
|
||||
// type descriptor `Lpkg/Name;`. Demangle the descriptor (drop leading 'L',
|
||||
// trailing ';', '/'->'.') so the bare class name resolves to its sibling file.
|
||||
static char *smali_demangle_descriptor(CBMArena *a, const char *desc) {
|
||||
if (!desc || !desc[0]) {
|
||||
return NULL;
|
||||
}
|
||||
const char *start = desc;
|
||||
if (start[0] == 'L') {
|
||||
start++;
|
||||
}
|
||||
size_t len = strlen(start);
|
||||
if (len > 0 && start[len - 1] == ';') {
|
||||
len--;
|
||||
}
|
||||
char *out = cbm_arena_strndup(a, start, len);
|
||||
if (out) {
|
||||
for (char *p = out; *p; p++) {
|
||||
if (*p == '/') {
|
||||
*p = '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void parse_smali_imports(CBMExtractCtx *ctx) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
const char *k = ts_node_type(node);
|
||||
if (strcmp(k, "super_directive") == 0 || strcmp(k, "implements_directive") == 0) {
|
||||
TSNode cid = node;
|
||||
if (find_first_descendant_of(node, "class_identifier", &cid)) {
|
||||
char *mod = smali_demangle_descriptor(a, cbm_node_text(a, cid, ctx->source));
|
||||
if (mod && mod[0]) {
|
||||
CBMImport imp = {.local_name = path_last(a, mod), .module_path = mod};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- TLA+ imports: `EXTENDS Foo, Bar` / `INSTANCE Foo` ---
|
||||
// AST: extends -> identifier_ref* (one per extended module). Each module maps
|
||||
// to a sibling `Foo.tla` file.
|
||||
static void parse_tlaplus_imports(CBMExtractCtx *ctx) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
const char *k = ts_node_type(node);
|
||||
if (strcmp(k, "extends") == 0 || strcmp(k, "instance") == 0) {
|
||||
uint32_t nc = ts_node_named_child_count(node);
|
||||
for (uint32_t j = 0; j < nc; j++) {
|
||||
TSNode c = ts_node_named_child(node, j);
|
||||
const char *ck = ts_node_type(c);
|
||||
if (strcmp(ck, "identifier_ref") == 0 || strcmp(ck, "identifier") == 0) {
|
||||
char *mod = cbm_node_text(a, c, ctx->source);
|
||||
if (mod && mod[0]) {
|
||||
CBMImport imp = {.local_name = path_last(a, mod), .module_path = mod};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- VHDL imports: `use work.pkg.all;` ---
|
||||
// AST: use_clause -> selected_name(library:, package:, ...). The `package`
|
||||
// field names the imported package, which maps to a sibling file (math_pkg ->
|
||||
// math_pkg.vhd) declaring `package math_pkg`. `library_clause` (`library work;`)
|
||||
// names no file and is skipped.
|
||||
static void parse_vhdl_imports(CBMExtractCtx *ctx) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
if (strcmp(ts_node_type(node), "use_clause") == 0) {
|
||||
TSNode sel = node;
|
||||
if (find_first_descendant_of(node, "selected_name", &sel)) {
|
||||
TSNode pkg = ts_node_child_by_field_name(sel, TS_FIELD("package"));
|
||||
char *mod = NULL;
|
||||
if (!ts_node_is_null(pkg)) {
|
||||
mod = cbm_node_text(a, pkg, ctx->source);
|
||||
}
|
||||
if (mod && mod[0]) {
|
||||
CBMImport imp = {.local_name = path_last(a, mod), .module_path = mod};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- WIT imports: `use types.{point};` / `use pkg/iface;` ---
|
||||
// AST: use_item -> use_path(id...). The leading path segment names the imported
|
||||
// interface/package, mapping to a sibling `types.wit` file.
|
||||
static void parse_wit_imports(CBMExtractCtx *ctx) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
if (strcmp(ts_node_type(node), "use_item") == 0) {
|
||||
TSNode path = node;
|
||||
if (find_first_descendant_of(node, "use_path", &path)) {
|
||||
/* First `id` child of the path is the interface/package name. */
|
||||
TSNode idn = path;
|
||||
if (find_first_descendant_of(path, "id", &idn)) {
|
||||
char *mod = cbm_node_text(a, idn, ctx->source);
|
||||
if (mod && mod[0]) {
|
||||
CBMImport imp = {.local_name = path_last(a, mod), .module_path = mod};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Smithy imports: `use com.example.common#Shape` ---
|
||||
// AST: use_statement -> absolute_root_shape_id(namespace, identifier). Emit a
|
||||
// dotted path `<namespace>.<Shape>` so the pipeline's symbol-name fallback links
|
||||
// it to the declaring shape (a Class/Struct node) in the namespace's file.
|
||||
static void parse_smithy_imports(CBMExtractCtx *ctx) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
if (strcmp(ts_node_type(node), "use_statement") == 0) {
|
||||
TSNode sid = node;
|
||||
if (find_first_descendant_of(node, "absolute_root_shape_id", &sid) ||
|
||||
find_first_descendant_of(node, "shape_id", &sid)) {
|
||||
char *mod = cbm_node_text(a, sid, ctx->source);
|
||||
if (mod && mod[0]) {
|
||||
/* `com.example.common#Timestamp` -> dotted path so the last
|
||||
* segment (Timestamp) resolves to the declaring shape. */
|
||||
for (char *p = mod; *p; p++) {
|
||||
if (*p == '#') {
|
||||
*p = '.';
|
||||
}
|
||||
}
|
||||
CBMImport imp = {.local_name = path_last(a, mod), .module_path = mod};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Hyprlang imports: `source = ~/path/to/file.conf` ---
|
||||
// AST: source -> string. Emit the referenced path; the pipeline's sibling-file
|
||||
// resolver matches it (by full path, then by basename) to the included file.
|
||||
static void parse_hyprlang_imports(CBMExtractCtx *ctx) {
|
||||
CBMArena *a = ctx->arena;
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, a, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, a, ctx->root);
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
if (strcmp(ts_node_type(node), "source") == 0) {
|
||||
TSNode str = node;
|
||||
char *path = NULL;
|
||||
if (find_first_descendant_of(node, "string", &str)) {
|
||||
path = strip_quotes(a, cbm_node_text(a, str, ctx->source));
|
||||
} else {
|
||||
path = strip_quotes(a, cbm_node_text(a, node, ctx->source));
|
||||
}
|
||||
if (path && path[0]) {
|
||||
CBMImport imp = {.local_name = path_last(a, path), .module_path = path};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ts_nstack_push_children(&stack, a, node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Main dispatch ---
|
||||
|
||||
void cbm_extract_imports(CBMExtractCtx *ctx) {
|
||||
@@ -2467,6 +2854,7 @@ void cbm_extract_imports(CBMExtractCtx *ctx) {
|
||||
case CBM_LANG_EMACSLISP:
|
||||
case CBM_LANG_FENNEL:
|
||||
case CBM_LANG_COMMONLISP:
|
||||
case CBM_LANG_CLOJURE:
|
||||
parse_lisp_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_STARLARK:
|
||||
@@ -2532,6 +2920,30 @@ void cbm_extract_imports(CBMExtractCtx *ctx) {
|
||||
case CBM_LANG_ADA:
|
||||
parse_ada_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_ELM:
|
||||
parse_elm_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_MOVE:
|
||||
parse_move_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_SMALI:
|
||||
parse_smali_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_TLAPLUS:
|
||||
parse_tlaplus_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_VHDL:
|
||||
parse_vhdl_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_WIT:
|
||||
parse_wit_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_SMITHY:
|
||||
parse_smithy_imports(ctx);
|
||||
break;
|
||||
case CBM_LANG_HYPRLANG:
|
||||
parse_hyprlang_imports(ctx);
|
||||
break;
|
||||
/* Host languages whose tree-sitter grammar leaves <script> bodies as raw
|
||||
* text — re-parse the embedded slice via the embedded-language spec. */
|
||||
case CBM_LANG_HTML:
|
||||
|
||||
@@ -865,6 +865,10 @@ static const CBMEmbeddedLangSpec html_embedded_imports[] = {
|
||||
{NULL, NULL, 0},
|
||||
};
|
||||
static const CBMEmbeddedLangSpec astro_embedded_imports[] = {
|
||||
/* Astro component scripts live in the `---` frontmatter fence, which the
|
||||
* grammar keeps as an unparsed frontmatter_js_block. Re-parse that slice
|
||||
* with the JS grammar so `import X from './X.astro'` becomes a real edge. */
|
||||
{"frontmatter", "frontmatter_js_block", CBM_LANG_JAVASCRIPT},
|
||||
{"script_element", "raw_text", CBM_LANG_JAVASCRIPT},
|
||||
{NULL, NULL, 0},
|
||||
};
|
||||
|
||||
@@ -1287,7 +1287,7 @@ static const cbm_gbuf_node_t *resolve_sibling_file(const cbm_pipeline_ctx_t *ctx
|
||||
}
|
||||
|
||||
/* Candidate relative paths, in priority order. */
|
||||
char cands[4][PKGMAP_PATH_BUF];
|
||||
char cands[5][PKGMAP_PATH_BUF];
|
||||
int ncand = 0;
|
||||
const char *base = module_path;
|
||||
/* Skip a leading "./". */
|
||||
@@ -1309,6 +1309,15 @@ static const cbm_gbuf_node_t *resolve_sibling_file(const cbm_pipeline_ctx_t *ctx
|
||||
}
|
||||
/* 3. Meson subdir: dir/<module_path>/meson.build. */
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s/meson.build", dir, dir[0] ? "/" : "", base);
|
||||
/* 4. Basename sibling: dir/<basename(module_path)>. Covers include paths
|
||||
* that carry a non-relative prefix (Hyprlang `source = ~/.config/.../x.conf`,
|
||||
* absolute include paths) but reference a file sitting beside the importer. */
|
||||
{
|
||||
const char *slash = strrchr(base, '/');
|
||||
if (slash && slash[1]) {
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s", dir, dir[0] ? "/" : "", slash + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const cbm_gbuf_node_t *found = NULL;
|
||||
for (int i = 0; i < ncand; i++) {
|
||||
|
||||
Reference in new issue
Block a user