mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-23 00:41:36 +00:00
feat: add C scope resolution files for language migration (RFC #909)
Add 11 C language scope resolution files following the Go pattern: - query.ts: tree-sitter-c query and parser for C constructs - captures.ts: emit scope captures with arity enrichment - import-decomposer.ts: decompose #include into structured captures - arity-metadata.ts: C function declaration/call arity computation - interpret.ts: interpret C imports and type bindings - import-target.ts: resolve #include paths via suffix matching - arity.ts: C arity compatibility (variadic detection) - merge-bindings.ts: first-wins binding merge by tier - simple-hooks.ts: null hooks (no receivers/methods in C) - index.ts: barrel re-exports - scope-resolver.ts: ScopeResolver implementation for C Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
This commit is contained in:
parent
54679ff308
commit
4a60e98d0f
11 changed files with 620 additions and 0 deletions
94
gitnexus/src/core/ingestion/languages/c/arity-metadata.ts
Normal file
94
gitnexus/src/core/ingestion/languages/c/arity-metadata.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
|
||||
export interface CArityInfo {
|
||||
parameterCount?: number;
|
||||
requiredParameterCount?: number;
|
||||
parameterTypes?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute declaration arity from a C function definition or declaration node.
|
||||
*/
|
||||
export function computeCDeclarationArity(node: SyntaxNode): CArityInfo {
|
||||
// Find the function_declarator child (may be wrapped in pointer_declarator)
|
||||
let funcDecl = findFuncDeclarator(node);
|
||||
if (funcDecl === null) return {};
|
||||
|
||||
const paramList = funcDecl.childForFieldName?.('parameters');
|
||||
if (paramList === null || paramList === undefined) return {};
|
||||
|
||||
const params: SyntaxNode[] = [];
|
||||
for (let i = 0; i < paramList.childCount; i++) {
|
||||
const child = paramList.child(i);
|
||||
if (child === null) continue;
|
||||
if (child.type === 'parameter_declaration' || child.type === 'variadic_parameter') {
|
||||
params.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
// (void) means zero parameters
|
||||
if (params.length === 1 && params[0].type === 'parameter_declaration') {
|
||||
const typeNode = params[0].childForFieldName?.('type');
|
||||
if (typeNode !== null && typeNode !== undefined && typeNode.text === 'void' && params[0].childForFieldName?.('declarator') === null) {
|
||||
return { parameterCount: 0, requiredParameterCount: 0, parameterTypes: [] };
|
||||
}
|
||||
}
|
||||
|
||||
const isVariadic = params.some((p) => p.type === 'variadic_parameter');
|
||||
const nonVariadicCount = params.filter((p) => p.type !== 'variadic_parameter').length;
|
||||
|
||||
const types: string[] = [];
|
||||
for (const p of params) {
|
||||
if (p.type === 'variadic_parameter') {
|
||||
types.push('...');
|
||||
} else {
|
||||
const typeNode = p.childForFieldName?.('type');
|
||||
types.push(typeNode?.text ?? 'unknown');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
parameterCount: isVariadic ? undefined : nonVariadicCount,
|
||||
requiredParameterCount: nonVariadicCount,
|
||||
parameterTypes: types,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute call-site arity from a call_expression node.
|
||||
*/
|
||||
export function computeCCallArity(node: SyntaxNode): number {
|
||||
const argList = node.childForFieldName?.('arguments');
|
||||
if (argList === null || argList === undefined) return 0;
|
||||
|
||||
let count = 0;
|
||||
for (let i = 0; i < argList.childCount; i++) {
|
||||
const child = argList.child(i);
|
||||
if (child === null) continue;
|
||||
// Skip punctuation (commas, parens)
|
||||
if (child.type !== ',' && child.type !== '(' && child.type !== ')') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function findFuncDeclarator(node: SyntaxNode): SyntaxNode | null {
|
||||
// Direct child
|
||||
let decl = node.childForFieldName?.('declarator');
|
||||
if (decl === null || decl === undefined) {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const c = node.child(i);
|
||||
if (c?.type === 'function_declarator') return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Unwrap pointer_declarator
|
||||
while (decl !== null && decl.type === 'pointer_declarator') {
|
||||
const next = decl.childForFieldName?.('declarator');
|
||||
if (next === null || next === undefined) break;
|
||||
decl = next;
|
||||
}
|
||||
if (decl?.type === 'function_declarator') return decl;
|
||||
return null;
|
||||
}
|
||||
20
gitnexus/src/core/ingestion/languages/c/arity.ts
Normal file
20
gitnexus/src/core/ingestion/languages/c/arity.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { Callsite, SymbolDefinition } from 'gitnexus-shared';
|
||||
|
||||
/**
|
||||
* C arity compatibility: no overloading. Variadic functions detected
|
||||
* via '...' in parameterTypes. Otherwise exact match or unknown.
|
||||
*/
|
||||
export function cArityCompatibility(
|
||||
def: SymbolDefinition,
|
||||
callsite: Callsite,
|
||||
): 'compatible' | 'unknown' | 'incompatible' {
|
||||
const max = def.parameterCount;
|
||||
const min = def.requiredParameterCount;
|
||||
if (max === undefined && min === undefined) return 'unknown';
|
||||
if (!Number.isFinite(callsite.arity) || callsite.arity < 0) return 'unknown';
|
||||
|
||||
const variadic = def.parameterTypes?.some((t) => t === '...') ?? false;
|
||||
if (min !== undefined && callsite.arity < min) return 'incompatible';
|
||||
if (max !== undefined && callsite.arity > max && !variadic) return 'incompatible';
|
||||
return 'compatible';
|
||||
}
|
||||
105
gitnexus/src/core/ingestion/languages/c/captures.ts
Normal file
105
gitnexus/src/core/ingestion/languages/c/captures.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import type { Capture, CaptureMatch } from 'gitnexus-shared';
|
||||
import { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
||||
import { getCParser, getCScopeQuery } from './query.js';
|
||||
import { getTreeSitterBufferSize } from '../../constants.js';
|
||||
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
||||
import { splitCInclude } from './import-decomposer.js';
|
||||
import { computeCDeclarationArity, computeCCallArity } from './arity-metadata.js';
|
||||
|
||||
export function emitCScopeCaptures(
|
||||
sourceText: string,
|
||||
_filePath: string,
|
||||
cachedTree?: unknown,
|
||||
): readonly CaptureMatch[] {
|
||||
let tree = cachedTree as ReturnType<ReturnType<typeof getCParser>['parse']> | undefined;
|
||||
if (tree === undefined) {
|
||||
tree = parseSourceSafe(getCParser(), sourceText, undefined, {
|
||||
bufferSize: getTreeSitterBufferSize(sourceText),
|
||||
});
|
||||
}
|
||||
|
||||
const rawMatches = getCScopeQuery().matches(tree.rootNode);
|
||||
const out: CaptureMatch[] = [];
|
||||
|
||||
for (const m of rawMatches) {
|
||||
const grouped: Record<string, Capture> = {};
|
||||
for (const c of m.captures) {
|
||||
const tag = '@' + c.name;
|
||||
if (tag.startsWith('@_')) continue;
|
||||
grouped[tag] = nodeToCapture(tag, c.node);
|
||||
}
|
||||
if (Object.keys(grouped).length === 0) continue;
|
||||
|
||||
// Handle #include statements
|
||||
if (grouped['@import.statement'] !== undefined) {
|
||||
const anchor = grouped['@import.statement']!;
|
||||
const includeNode = findNodeAtRange(tree.rootNode, anchor.range, 'preproc_include');
|
||||
if (includeNode !== null) {
|
||||
const split = splitCInclude(includeNode);
|
||||
if (split !== null) {
|
||||
out.push(split);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enrich function declarations with arity metadata
|
||||
const declAnchor = grouped['@declaration.function'];
|
||||
if (declAnchor !== undefined) {
|
||||
const fnNode =
|
||||
findNodeAtRange(tree.rootNode, declAnchor.range, 'function_definition') ??
|
||||
findNodeAtRange(tree.rootNode, declAnchor.range, 'declaration');
|
||||
if (fnNode !== null) {
|
||||
const arity = computeCDeclarationArity(fnNode);
|
||||
if (arity.parameterCount !== undefined) {
|
||||
grouped['@declaration.parameter-count'] = syntheticCapture(
|
||||
'@declaration.parameter-count',
|
||||
fnNode,
|
||||
String(arity.parameterCount),
|
||||
);
|
||||
}
|
||||
if (arity.requiredParameterCount !== undefined) {
|
||||
grouped['@declaration.required-parameter-count'] = syntheticCapture(
|
||||
'@declaration.required-parameter-count',
|
||||
fnNode,
|
||||
String(arity.requiredParameterCount),
|
||||
);
|
||||
}
|
||||
if (arity.parameterTypes !== undefined) {
|
||||
grouped['@declaration.parameter-types'] = syntheticCapture(
|
||||
'@declaration.parameter-types',
|
||||
fnNode,
|
||||
JSON.stringify(arity.parameterTypes),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enrich call references with arity
|
||||
const callAnchor =
|
||||
grouped['@reference.call.free'] ?? grouped['@reference.call.member'];
|
||||
if (callAnchor !== undefined && grouped['@reference.arity'] === undefined) {
|
||||
const callNode = findNodeAtRange(tree.rootNode, callAnchor.range, 'call_expression');
|
||||
if (callNode !== null) {
|
||||
grouped['@reference.arity'] = syntheticCapture(
|
||||
'@reference.arity',
|
||||
callNode,
|
||||
String(computeCCallArity(callNode)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
out.push(grouped);
|
||||
}
|
||||
|
||||
// Synthesize typeBindings for struct fields (for compound receiver resolution)
|
||||
for (const match of out) {
|
||||
if (match['@declaration.field'] === undefined) continue;
|
||||
const nameCap = match['@declaration.name'];
|
||||
if (nameCap === undefined) continue;
|
||||
// For C, we don't have rich type info on fields from the query
|
||||
// but we keep the slot for future enhancement
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
42
gitnexus/src/core/ingestion/languages/c/import-decomposer.ts
Normal file
42
gitnexus/src/core/ingestion/languages/c/import-decomposer.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import type { Capture, CaptureMatch } from 'gitnexus-shared';
|
||||
import { nodeToCapture, syntheticCapture, type SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
|
||||
/**
|
||||
* Decompose a `preproc_include` node into a CaptureMatch with structured
|
||||
* import captures. C #include maps to a wildcard import (all symbols
|
||||
* from the header are visible).
|
||||
*/
|
||||
export function splitCInclude(node: SyntaxNode): CaptureMatch | null {
|
||||
// node.type === 'preproc_include'
|
||||
// children: '#include' + (string_literal | system_lib_string)
|
||||
let pathNode: SyntaxNode | null = null;
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child === null) continue;
|
||||
if (child.type === 'string_literal' || child.type === 'system_lib_string') {
|
||||
pathNode = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pathNode === null) return null;
|
||||
|
||||
// Strip quotes: "foo.h" → foo.h or <stdio.h> → stdio.h
|
||||
let raw = pathNode.text;
|
||||
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith('<') && raw.endsWith('>'))) {
|
||||
raw = raw.slice(1, -1);
|
||||
}
|
||||
|
||||
const isSystem = pathNode.type === 'system_lib_string';
|
||||
|
||||
const result: CaptureMatch = {
|
||||
'@import.statement': nodeToCapture('@import.statement', node),
|
||||
'@import.kind': syntheticCapture('@import.kind', node, 'wildcard'),
|
||||
'@import.source': syntheticCapture('@import.source', node, raw),
|
||||
};
|
||||
|
||||
if (isSystem) {
|
||||
result['@import.system'] = syntheticCapture('@import.system', node, 'true');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
39
gitnexus/src/core/ingestion/languages/c/import-target.ts
Normal file
39
gitnexus/src/core/ingestion/languages/c/import-target.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Resolve a C #include path to a file in the workspace.
|
||||
*
|
||||
* Strategy: match the include path suffix against all file paths in
|
||||
* the workspace. "foo.h" matches "src/foo.h", "include/foo.h", etc.
|
||||
* For paths with directory components ("dir/foo.h"), match the full
|
||||
* relative suffix.
|
||||
*/
|
||||
export function resolveCImportTarget(
|
||||
targetRaw: string,
|
||||
_fromFile: string,
|
||||
allFilePaths: ReadonlySet<string>,
|
||||
): string | null {
|
||||
if (!targetRaw) return null;
|
||||
|
||||
const normalizedTarget = targetRaw.replace(/\\/g, '/');
|
||||
|
||||
// Exact match first
|
||||
if (allFilePaths.has(normalizedTarget)) return normalizedTarget;
|
||||
|
||||
// Suffix match: find files ending with /targetRaw or equal to targetRaw
|
||||
const suffix = '/' + normalizedTarget;
|
||||
let bestMatch: string | null = null;
|
||||
let bestDepth = Infinity;
|
||||
|
||||
for (const filePath of allFilePaths) {
|
||||
const normalized = filePath.replace(/\\/g, '/');
|
||||
if (normalized === normalizedTarget || normalized.endsWith(suffix)) {
|
||||
// Prefer shortest path (closest match)
|
||||
const depth = normalized.split('/').length;
|
||||
if (depth < bestDepth) {
|
||||
bestDepth = depth;
|
||||
bestMatch = filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
10
gitnexus/src/core/ingestion/languages/c/index.ts
Normal file
10
gitnexus/src/core/ingestion/languages/c/index.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* C scope-resolution hooks (RFC #909 Ring 3).
|
||||
*/
|
||||
export { emitCScopeCaptures } from './captures.js';
|
||||
export { interpretCImport, interpretCTypeBinding, normalizeCTypeName } from './interpret.js';
|
||||
export { splitCInclude } from './import-decomposer.js';
|
||||
export { cArityCompatibility } from './arity.js';
|
||||
export { cMergeBindings } from './merge-bindings.js';
|
||||
export { cBindingScopeFor, cImportOwningScope, cReceiverBinding } from './simple-hooks.js';
|
||||
export { resolveCImportTarget } from './import-target.js';
|
||||
51
gitnexus/src/core/ingestion/languages/c/interpret.ts
Normal file
51
gitnexus/src/core/ingestion/languages/c/interpret.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import type { CaptureMatch, ParsedImport, ParsedTypeBinding, TypeRef } from 'gitnexus-shared';
|
||||
|
||||
/**
|
||||
* Interpret a C #include capture into a ParsedImport.
|
||||
* C includes are always wildcard imports (all symbols from the header).
|
||||
*/
|
||||
export function interpretCImport(captures: CaptureMatch): ParsedImport | null {
|
||||
const source = captures['@import.source']?.text;
|
||||
if (source === undefined) return null;
|
||||
|
||||
// System headers (e.g. <stdio.h>) are not resolved to local files
|
||||
if (captures['@import.system'] !== undefined) return null;
|
||||
|
||||
return { kind: 'wildcard', targetRaw: source };
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret a C type-binding capture into a ParsedTypeBinding.
|
||||
*/
|
||||
export function interpretCTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null {
|
||||
const name = captures['@type-binding.name']?.text;
|
||||
const type = captures['@type-binding.type']?.text;
|
||||
if (name === undefined || type === undefined) return null;
|
||||
|
||||
let source: TypeRef['source'] = 'annotation';
|
||||
|
||||
if (captures['@type-binding.parameter'] !== undefined) {
|
||||
source = 'parameter-annotation';
|
||||
} else if (captures['@type-binding.assignment'] !== undefined) {
|
||||
source = 'assignment-inferred';
|
||||
}
|
||||
|
||||
return { boundName: name, rawTypeName: normalizeCTypeName(type), source };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a C type name: strip pointer/array syntax, qualifiers.
|
||||
*/
|
||||
export function normalizeCTypeName(text: string): string {
|
||||
let t = text.trim();
|
||||
// Strip const, volatile, restrict qualifiers
|
||||
t = t.replace(/\b(const|volatile|restrict|static|extern|inline)\b/g, '').trim();
|
||||
// Strip pointer stars
|
||||
while (t.endsWith('*')) t = t.slice(0, -1).trim();
|
||||
while (t.startsWith('*')) t = t.slice(1).trim();
|
||||
// Strip array brackets
|
||||
t = t.replace(/\[.*?\]/g, '').trim();
|
||||
// Strip struct/union/enum prefixes
|
||||
t = t.replace(/^(struct|union|enum)\s+/, '');
|
||||
return t;
|
||||
}
|
||||
32
gitnexus/src/core/ingestion/languages/c/merge-bindings.ts
Normal file
32
gitnexus/src/core/ingestion/languages/c/merge-bindings.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import type { BindingRef } from 'gitnexus-shared';
|
||||
|
||||
const TIER: Record<BindingRef['origin'], number> = {
|
||||
local: 0,
|
||||
namespace: 1,
|
||||
import: 2,
|
||||
reexport: 3,
|
||||
wildcard: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* C merge bindings: simple first-wins by tier (local > import > wildcard).
|
||||
* C has no namespaces or reexports, but the tiers are defined for
|
||||
* compatibility with the shared infrastructure.
|
||||
*/
|
||||
export function cMergeBindings(
|
||||
existing: readonly BindingRef[],
|
||||
incoming: readonly BindingRef[],
|
||||
_scopeId: string,
|
||||
): BindingRef[] {
|
||||
const seen = new Set<string>();
|
||||
return [...existing, ...incoming]
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(TIER[a.origin] ?? 99) - (TIER[b.origin] ?? 99) || a.def.nodeId.localeCompare(b.def.nodeId),
|
||||
)
|
||||
.filter((binding) => {
|
||||
if (seen.has(binding.def.nodeId)) return false;
|
||||
seen.add(binding.def.nodeId);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
144
gitnexus/src/core/ingestion/languages/c/query.ts
Normal file
144
gitnexus/src/core/ingestion/languages/c/query.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import Parser from 'tree-sitter';
|
||||
import C from 'tree-sitter-c';
|
||||
|
||||
const C_SCOPE_QUERY = `
|
||||
;; Scopes
|
||||
(translation_unit) @scope.module
|
||||
(struct_specifier) @scope.class
|
||||
(union_specifier) @scope.class
|
||||
(function_definition) @scope.function
|
||||
(compound_statement) @scope.block
|
||||
(if_statement) @scope.block
|
||||
(for_statement) @scope.block
|
||||
(while_statement) @scope.block
|
||||
(do_statement) @scope.block
|
||||
(switch_statement) @scope.block
|
||||
(case_statement) @scope.block
|
||||
|
||||
;; Declarations — struct
|
||||
(struct_specifier
|
||||
name: (type_identifier) @declaration.name
|
||||
body: (field_declaration_list)) @declaration.struct
|
||||
|
||||
;; Declarations — union
|
||||
(union_specifier
|
||||
name: (type_identifier) @declaration.name
|
||||
body: (field_declaration_list)) @declaration.union
|
||||
|
||||
;; Declarations — enum
|
||||
(enum_specifier
|
||||
name: (type_identifier) @declaration.name) @declaration.enum
|
||||
|
||||
;; Declarations — function definition
|
||||
(function_definition
|
||||
declarator: (function_declarator
|
||||
declarator: (identifier) @declaration.name)) @declaration.function
|
||||
|
||||
;; Declarations — function definition with pointer return
|
||||
(function_definition
|
||||
declarator: (pointer_declarator
|
||||
declarator: (function_declarator
|
||||
declarator: (identifier) @declaration.name))) @declaration.function
|
||||
|
||||
;; Declarations — function declaration (prototype)
|
||||
(declaration
|
||||
declarator: (function_declarator
|
||||
declarator: (identifier) @declaration.name)) @declaration.function
|
||||
|
||||
;; Declarations — function declaration with pointer return (prototype)
|
||||
(declaration
|
||||
declarator: (pointer_declarator
|
||||
declarator: (function_declarator
|
||||
declarator: (identifier) @declaration.name))) @declaration.function
|
||||
|
||||
;; Declarations — typedef
|
||||
(type_definition
|
||||
declarator: (type_identifier) @declaration.name) @declaration.typedef
|
||||
|
||||
;; Declarations — struct fields
|
||||
(field_declaration
|
||||
declarator: (field_identifier) @declaration.name) @declaration.field
|
||||
|
||||
;; Declarations — struct fields (pointer)
|
||||
(field_declaration
|
||||
declarator: (pointer_declarator
|
||||
declarator: (field_identifier) @declaration.name)) @declaration.field
|
||||
|
||||
;; Declarations — variables
|
||||
(declaration
|
||||
declarator: (init_declarator
|
||||
declarator: (identifier) @declaration.name)) @declaration.variable
|
||||
|
||||
;; Declarations — plain variable (no initializer)
|
||||
(declaration
|
||||
declarator: (identifier) @declaration.name
|
||||
!declarator) @declaration.variable
|
||||
|
||||
;; Declarations — macro definitions
|
||||
(preproc_def
|
||||
name: (identifier) @declaration.name) @declaration.macro
|
||||
|
||||
(preproc_function_def
|
||||
name: (identifier) @declaration.name) @declaration.macro
|
||||
|
||||
;; Declarations — enum constants
|
||||
(enumerator
|
||||
name: (identifier) @declaration.name) @declaration.const
|
||||
|
||||
;; Imports
|
||||
(preproc_include) @import.statement
|
||||
|
||||
;; Type bindings — parameter annotations
|
||||
(function_definition
|
||||
declarator: (function_declarator
|
||||
declarator: (identifier) @_fn_name
|
||||
parameters: (parameter_list
|
||||
(parameter_declaration
|
||||
declarator: (identifier) @type-binding.name
|
||||
type: (_) @type-binding.type)))) @type-binding.parameter
|
||||
|
||||
;; Type bindings — variable with type (init_declarator)
|
||||
(declaration
|
||||
type: (_) @type-binding.type
|
||||
declarator: (init_declarator
|
||||
declarator: (identifier) @type-binding.name)) @type-binding.assignment
|
||||
|
||||
;; References — free calls
|
||||
(call_expression
|
||||
function: (identifier) @reference.name) @reference.call.free
|
||||
|
||||
;; References — member calls via pointer (ptr->func())
|
||||
(call_expression
|
||||
function: (field_expression
|
||||
argument: (_) @reference.receiver
|
||||
field: (field_identifier) @reference.name)) @reference.call.member
|
||||
|
||||
;; References — field reads
|
||||
(field_expression
|
||||
argument: (_) @reference.receiver
|
||||
field: (field_identifier) @reference.name) @reference.read
|
||||
|
||||
;; References — field writes (assignment)
|
||||
(assignment_expression
|
||||
left: (field_expression
|
||||
argument: (_) @reference.receiver
|
||||
field: (field_identifier) @reference.name)) @reference.write
|
||||
`;
|
||||
|
||||
let _parser: Parser | null = null;
|
||||
let _query: Parser.Query | null = null;
|
||||
|
||||
export function getCParser(): Parser {
|
||||
if (_parser === null) {
|
||||
_parser = new Parser();
|
||||
_parser.setLanguage(C as Parameters<Parser['setLanguage']>[0]);
|
||||
}
|
||||
return _parser;
|
||||
}
|
||||
|
||||
export function getCScopeQuery(): Parser.Query {
|
||||
if (_query === null) {
|
||||
_query = new Parser.Query(C as Parameters<Parser['setLanguage']>[0], C_SCOPE_QUERY);
|
||||
}
|
||||
return _query;
|
||||
}
|
||||
45
gitnexus/src/core/ingestion/languages/c/scope-resolver.ts
Normal file
45
gitnexus/src/core/ingestion/languages/c/scope-resolver.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import type { ParsedFile } from 'gitnexus-shared';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { buildMro, defaultLinearize } from '../../scope-resolution/passes/mro.js';
|
||||
import { populateClassOwnedMembers } from '../../scope-resolution/scope/walkers.js';
|
||||
import type { ScopeResolver } from '../../scope-resolution/contract/scope-resolver.js';
|
||||
import { cProvider } from '../c-cpp.js';
|
||||
import { cArityCompatibility, cMergeBindings, resolveCImportTarget } from './index.js';
|
||||
|
||||
/**
|
||||
* C `ScopeResolver` registered in `SCOPE_RESOLVERS` and consumed by
|
||||
* the generic `runScopeResolution` orchestrator (RFC #909 Ring 3).
|
||||
*
|
||||
* C is a structurally simple language for scope resolution:
|
||||
* - No classes (structs are value types, no method dispatch)
|
||||
* - No inheritance (no MRO needed beyond the shared first-wins default)
|
||||
* - No overloading (arity check is simple: variadic detection only)
|
||||
* - `#include` is wildcard import (all symbols from header are visible)
|
||||
* - `static` functions are file-local (not exported)
|
||||
*/
|
||||
export const cScopeResolver: ScopeResolver = {
|
||||
language: SupportedLanguages.C,
|
||||
languageProvider: cProvider,
|
||||
importEdgeReason: 'c-scope: include',
|
||||
|
||||
resolveImportTarget: (targetRaw, fromFile, allFilePaths) =>
|
||||
resolveCImportTarget(targetRaw, fromFile, allFilePaths),
|
||||
|
||||
mergeBindings: (existing, incoming, scopeId) => cMergeBindings(existing, incoming, scopeId),
|
||||
|
||||
arityCompatibility: (callsite, def) => cArityCompatibility(def, callsite),
|
||||
|
||||
buildMro: (graph, parsedFiles, nodeLookup) =>
|
||||
buildMro(graph, parsedFiles, nodeLookup, defaultLinearize),
|
||||
|
||||
populateOwners: (parsed: ParsedFile) => populateClassOwnedMembers(parsed),
|
||||
|
||||
isSuperReceiver: () => false,
|
||||
|
||||
// C is statically typed — disable field fallback heuristic
|
||||
fieldFallbackOnMethodLookup: false,
|
||||
// C has no method return types to propagate
|
||||
propagatesReturnTypesAcrossImports: false,
|
||||
// C #include brings in all symbols — enable global free call fallback
|
||||
allowGlobalFreeCallFallback: true,
|
||||
};
|
||||
38
gitnexus/src/core/ingestion/languages/c/simple-hooks.ts
Normal file
38
gitnexus/src/core/ingestion/languages/c/simple-hooks.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import type {
|
||||
CaptureMatch,
|
||||
ParsedImport,
|
||||
Scope,
|
||||
ScopeId,
|
||||
ScopeTree,
|
||||
TypeRef,
|
||||
} from 'gitnexus-shared';
|
||||
|
||||
/**
|
||||
* C binding scope: always use default auto-hoist (null).
|
||||
* C has no self/receiver bindings that need special scoping.
|
||||
*/
|
||||
export function cBindingScopeFor(
|
||||
_decl: CaptureMatch,
|
||||
_innermost: Scope,
|
||||
_tree: ScopeTree,
|
||||
): ScopeId | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* C import owning scope: always use default (null).
|
||||
*/
|
||||
export function cImportOwningScope(
|
||||
_imp: ParsedImport,
|
||||
_innermost: Scope,
|
||||
_tree: ScopeTree,
|
||||
): ScopeId | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* C receiver binding: always null. C has no methods or receivers.
|
||||
*/
|
||||
export function cReceiverBinding(_functionScope: Scope): TypeRef | null {
|
||||
return null;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue