Skip to content

Commit 10ce652

Browse files
committed
feat(extract): extract C/C++ preprocessor macros as Macro nodes
#define directives were not represented in the graph. Object-like macros (preproc_def) and function-like macros (preproc_function_def) are now emitted as Macro nodes with a DEFINES edge from the file. A function-like macro keeps its parameter list as the signature; the macro body is not descended into. Applies to all C-preprocessor languages (C, C++, CUDA, GLSL, Objective-C, ISPC). Closes #375
1 parent 955b87d commit 10ce652

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

internal/cbm/extract_defs.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3577,6 +3577,47 @@ static void push_class_body_children(TSNode node, const CBMLangSpec *spec, walk_
35773577
}
35783578
}
35793579

3580+
// Languages that use the C preprocessor and therefore have #define macros.
3581+
static bool is_c_preprocessor_lang(CBMLanguage lang) {
3582+
return lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA ||
3583+
lang == CBM_LANG_GLSL || lang == CBM_LANG_OBJC || lang == CBM_LANG_ISPC;
3584+
}
3585+
3586+
// C/C++ preprocessor macros become Macro nodes (#375):
3587+
// #define SIMPLE 1 -> preproc_def
3588+
// #define FN(x) (2 * (x)) -> preproc_function_def
3589+
// The name is the `name` field; a function-like macro's parameter list is kept
3590+
// as the signature. The macro body (a preproc_arg) is not descended into.
3591+
static void extract_c_macro_def(CBMExtractCtx *ctx, TSNode node) {
3592+
CBMArena *a = ctx->arena;
3593+
TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("name"));
3594+
if (ts_node_is_null(name_node)) {
3595+
return;
3596+
}
3597+
char *name = cbm_node_text(a, name_node, ctx->source);
3598+
if (!name || !name[0]) {
3599+
return;
3600+
}
3601+
3602+
CBMDefinition def;
3603+
memset(&def, 0, sizeof(def));
3604+
def.name = name;
3605+
def.qualified_name = cbm_fqn_compute(a, ctx->project, ctx->rel_path, name);
3606+
def.label = "Macro";
3607+
def.file_path = ctx->rel_path;
3608+
def.start_line = ts_node_start_point(node).row + TS_LINE_OFFSET;
3609+
def.end_line = ts_node_end_point(node).row + TS_LINE_OFFSET;
3610+
def.lines = (int)(def.end_line - def.start_line + TS_LINE_OFFSET);
3611+
def.is_exported = true; // macros have no translation-unit scoping — globally visible
3612+
3613+
TSNode params = ts_node_child_by_field_name(node, TS_FIELD("parameters"));
3614+
if (!ts_node_is_null(params)) {
3615+
def.signature = cbm_node_text(a, params, ctx->source);
3616+
}
3617+
3618+
cbm_defs_push(&ctx->result->defs, a, def);
3619+
}
3620+
35803621
static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec, int depth_unused) {
35813622
(void)depth_unused;
35823623
walk_defs_frame_t stack[CBM_WALK_DEFS_STACK_CAP];
@@ -3594,6 +3635,12 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
35943635
continue;
35953636
}
35963637

3638+
if (is_c_preprocessor_lang(ctx->language) &&
3639+
(strcmp(kind, "preproc_def") == 0 || strcmp(kind, "preproc_function_def") == 0)) {
3640+
extract_c_macro_def(ctx, node);
3641+
continue; // the macro body is a preproc_arg — nothing more to extract
3642+
}
3643+
35973644
if (cbm_kind_in_set(node, spec->function_node_types)) {
35983645
if (!is_template_class_node(node, ctx->language)) {
35993646
extract_func_def(ctx, node, spec);

tests/test_extraction.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,38 @@ TEST(extract_ts_factory_object_methods_issue341) {
120120
PASS();
121121
}
122122

123+
/* --- C/C++ preprocessor macros become Macro nodes (#375) --- */
124+
TEST(extract_c_macros_issue375) {
125+
CBMFileResult *r = extract("#define SIMPLE_MACRO 1\n"
126+
"#define FN_MACRO(x) (2 * (x))\n"
127+
"#define EMPTY_MACRO\n"
128+
"int main(void) { return FN_MACRO(SIMPLE_MACRO); }\n",
129+
CBM_LANG_C, "p", "macros.c");
130+
ASSERT_NOT_NULL(r);
131+
ASSERT_FALSE(r->has_error);
132+
ASSERT(has_def(r, "Macro", "SIMPLE_MACRO"));
133+
ASSERT(has_def(r, "Macro", "FN_MACRO"));
134+
ASSERT(has_def(r, "Macro", "EMPTY_MACRO"));
135+
ASSERT(has_def(r, "Function", "main")); /* macros don't displace function defs */
136+
cbm_free_result(r);
137+
PASS();
138+
}
139+
140+
TEST(extract_cpp_macros_issue375) {
141+
CBMFileResult *r = extract("#define MAX(a, b) ((a) > (b) ? (a) : (b))\n"
142+
"#define PI 3.14159\n"
143+
"namespace n {\n"
144+
"int f() { return MAX(1, 2); }\n"
145+
"}\n",
146+
CBM_LANG_CPP, "p", "macros.cpp");
147+
ASSERT_NOT_NULL(r);
148+
ASSERT_FALSE(r->has_error);
149+
ASSERT(has_def(r, "Macro", "MAX"));
150+
ASSERT(has_def(r, "Macro", "PI"));
151+
cbm_free_result(r);
152+
PASS();
153+
}
154+
123155
/* --- Java --- */
124156
TEST(java_class) {
125157
CBMFileResult *r = extract(
@@ -2385,6 +2417,8 @@ SUITE(extraction) {
23852417
RUN_TEST(extract_r_box_use_imports_issue218);
23862418
RUN_TEST(extract_r_dollar_call_issue219);
23872419
RUN_TEST(extract_ts_factory_object_methods_issue341);
2420+
RUN_TEST(extract_c_macros_issue375);
2421+
RUN_TEST(extract_cpp_macros_issue375);
23882422

23892423
/* OOP */
23902424
RUN_TEST(java_class);

0 commit comments

Comments
 (0)