fix(extraction): blank C designated-initializer macro args before parsing (#1755)
tree-sitter-c has no rule for `.field = value` as a call argument. A
statement-level `MACRO(a, b, .x = …, .y = { … },);` recovers by extending
the enclosing function_definition to EOF — later functions vanish or nest
as outer::inner (#1729). blankCDesignatedMacroArgs empties such argument
lists to spaces (newlines kept) at the head of preParseCSource, before the
kernel route point, so both wasm and kernel C arms see the same bytes.
Tests cover the issue fixture (trailing-comma designated args) and a
120-field scale guard. Refs #1729.
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
co-authored by
Colby McHenry
parent
9a32487491
commit
8df9ecac9d
@@ -1515,8 +1515,56 @@ export function blankCNamedVariadicDefineDots(source: string): string {
|
||||
* C-detected headers in CUDA projects (llm.c keeps `__device__` helpers and
|
||||
* kernel prototypes in plain `.h`) — the same content-gated CUDA blank as
|
||||
* C++. Offset-preserving. */
|
||||
/**
|
||||
* Blank the argument list of a statement-level `MACRO( … );` call whose
|
||||
* arguments are designated initializers — betaflight's
|
||||
*
|
||||
* RESET_CONFIG(pidProfile_t, pidProfile,
|
||||
* .pid = { [PID_ROLL] = PID_ROLL_DEFAULT, … },
|
||||
* .pidSumLimit = PIDSUM_LIMIT,
|
||||
* …
|
||||
* );
|
||||
*
|
||||
* tree-sitter-c has no rule for `.field = value` as a call argument. Even a
|
||||
* small statement-level `M(a, b, .x = 1, .y = { 1, 2 },);` with a trailing
|
||||
* comma recovers by extending the enclosing `function_definition` to EOF —
|
||||
* the next function vanishes and later ones nest under the first (#1729 —
|
||||
* 310 functions in 73 files on a betaflight tree, which name matching then
|
||||
* treated as unreachable closures). Emptying the argument list to spaces,
|
||||
* newlines kept, leaves `RESET_CONFIG(\n\n…\n);` — a call the grammar parses
|
||||
* cleanly — at the cost of the references inside the initializer, which the
|
||||
* broken parse was not yielding either. Statement-level only (`);` follows),
|
||||
* macro-cased name only, offsets preserved. Runs before the kernel route
|
||||
* point, so both the wasm and kernel C arms see the same bytes.
|
||||
*/
|
||||
export function blankCDesignatedMacroArgs(source: string): string {
|
||||
if (source.indexOf('=') === -1) return source;
|
||||
const out = source.split('');
|
||||
const re = /^[ \t]*([A-Z_][A-Z0-9_]*)\s*\(/gm;
|
||||
let m: RegExpExecArray | null;
|
||||
while ((m = re.exec(source))) {
|
||||
const open = m.index + m[0].length - 1;
|
||||
let depth = 1;
|
||||
let i = open + 1;
|
||||
for (; i < source.length && depth > 0; i++) {
|
||||
const c = source[i];
|
||||
if (c === '(') depth++;
|
||||
else if (c === ')') depth--;
|
||||
}
|
||||
if (depth !== 0) continue;
|
||||
const close = i - 1;
|
||||
const args = source.slice(open + 1, close);
|
||||
// A designator at argument depth: `.name =` or `[index] =`.
|
||||
if (!/(^|[,{(\s])(\.[A-Za-z_]\w*|\[[^\]]+\])\s*=[^=]/.test(args)) continue;
|
||||
if (!/^\s*;/.test(source.slice(close + 1))) continue;
|
||||
for (let k = open + 1; k < close; k++) if (out[k] !== '\n') out[k] = ' ';
|
||||
re.lastIndex = close;
|
||||
}
|
||||
return out.join('');
|
||||
}
|
||||
|
||||
function preParseCSource(source: string): string {
|
||||
const inner = blankCKernelAnnotations(blankCCplusplusGuardBodies(source));
|
||||
const inner = blankCDesignatedMacroArgs(blankCKernelAnnotations(blankCCplusplusGuardBodies(source)));
|
||||
let blanked = blankCLeadingAttrMacros(
|
||||
blankLoneMacroLines(
|
||||
blankCStatementMacroCalls(
|
||||
|
||||
Reference in New Issue
Block a user