mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
feat(languages): add Lua language support
Add Lua as a supported language with symbol extraction and scope-resolution
at parity with existing languages.
Vendored grammar:
- Vendor tree-sitter-lua (v2.1.3, MIT). Upstream ships nan-based node
bindings incompatible with the pinned tree-sitter@0.21.1 runtime
(setLanguage rejects with "Invalid language object"), so the grammar C
sources are rebuilt against a Napi binding adapted from tree-sitter-c.
Symbol extraction (legacy tree-sitter queries):
- Function, Method, and Class definitions; call sites.
Scope-resolution (CALLS + IMPORTS edges):
- emitScopeCaptures, interpretImport, and a Lua ScopeResolver registered
in the scope-resolution pipeline.
- Import resolution via a SuffixIndex so root-level modules (e.g.
require("middleclass")) resolve, not just dotted paths.
Class structure (EXTENDS + HAS_METHOD + METHOD_OVERRIDES):
- middleclass class("Name", Parent) and function Class:method() handled
via a heritage edge hook (emitHeritageEdges), since middleclass has no
syntactic class body for the standard heritage/ownership walk.
Registered in all dispatch tables (SupportedLanguages enum, language
detection, parser-loader, parse-worker languageMap, language
classification, scope-resolver registry, vendored-grammar manifest).
Verified: tsc --noEmit clean; analyze on a Lua sample produces Class,
Method, EXTENDS, HAS_METHOD, METHOD_OVERRIDES, IMPORTS, and CALLS.
Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
parent
c056d136ad
commit
6deba51db0
34 changed files with 13909 additions and 1 deletions
5
.github/vendored-grammars.json
vendored
5
.github/vendored-grammars.json
vendored
|
|
@ -22,6 +22,11 @@
|
|||
"proto": {
|
||||
"name": "tree-sitter-proto",
|
||||
"upstream": { "github": "coder3101/tree-sitter-proto" }
|
||||
},
|
||||
"lua": {
|
||||
"name": "tree-sitter-lua",
|
||||
"upstream": { "npm": "tree-sitter-lua" },
|
||||
"hold": "tree-sitter-lua@2.1.3 ships nan-based node bindings incompatible with the pinned tree-sitter@0.21.1 runtime (setLanguage rejects with 'Invalid language object'); vendored with a Napi binding.cc rebuilt from source. Needs the tree-sitter runtime upgrade (#858) plus an upstream migration to node-addon-api before the monitor can auto-bump"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ const EXTENSION_MAP: Record<SupportedLanguages, readonly string[]> = {
|
|||
[SupportedLanguages.Swift]: ['.swift'],
|
||||
[SupportedLanguages.Dart]: ['.dart'],
|
||||
[SupportedLanguages.Vue]: ['.vue'],
|
||||
[SupportedLanguages.Lua]: ['.lua'],
|
||||
[SupportedLanguages.Cobol]: ['.cbl', '.cob', '.cpy', '.cobol'],
|
||||
} satisfies Record<SupportedLanguages, readonly string[]>; // Ensure exhaustiveness
|
||||
|
||||
|
|
@ -120,6 +121,7 @@ const SYNTAX_MAP: Record<SupportedLanguages, string> = {
|
|||
[SupportedLanguages.Swift]: 'swift',
|
||||
[SupportedLanguages.Dart]: 'dart',
|
||||
[SupportedLanguages.Vue]: 'typescript',
|
||||
[SupportedLanguages.Lua]: 'lua',
|
||||
[SupportedLanguages.Cobol]: 'cobol',
|
||||
} satisfies Record<SupportedLanguages, string>; // Ensure exhaustiveness
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export enum SupportedLanguages {
|
|||
Swift = 'swift',
|
||||
Dart = 'dart',
|
||||
Vue = 'vue',
|
||||
Lua = 'lua',
|
||||
/** Standalone regex processor — no tree-sitter, no LanguageProvider. */
|
||||
Cobol = 'cobol',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
* - production: javascript, typescript, python, java, c, cpp, csharp, go,
|
||||
* ruby, rust, php, kotlin, swift, dart
|
||||
* - experimental: vue (embedded-language / SFC complexity),
|
||||
* cobol (regex-provider path)
|
||||
* cobol (regex-provider path),
|
||||
* lua (definition-only legacy DAG path; scope-resolution
|
||||
* hooks pending — Phase B)
|
||||
* - quarantined: (none)
|
||||
*/
|
||||
|
||||
|
|
@ -39,6 +41,7 @@ export const LanguageClassifications: Readonly<Record<SupportedLanguages, Langua
|
|||
[SupportedLanguages.Kotlin]: 'production',
|
||||
[SupportedLanguages.Swift]: 'production',
|
||||
[SupportedLanguages.Dart]: 'production',
|
||||
[SupportedLanguages.Lua]: 'experimental',
|
||||
[SupportedLanguages.Vue]: 'experimental',
|
||||
[SupportedLanguages.Cobol]: 'experimental',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ const GRAMMARS = {
|
|||
proto: { required: false, display: 'Proto', ext: '.proto' },
|
||||
swift: { required: false, display: 'Swift', ext: '.swift' },
|
||||
kotlin: { required: false, display: 'Kotlin', ext: '.kt/.kts' },
|
||||
lua: { required: false, display: 'Lua', ext: '.lua' },
|
||||
};
|
||||
|
||||
const skipOptional = process.env.GITNEXUS_SKIP_OPTIONAL_GRAMMARS === '1';
|
||||
|
|
|
|||
10
gitnexus/src/core/ingestion/call-extractors/configs/lua.ts
Normal file
10
gitnexus/src/core/ingestion/call-extractors/configs/lua.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { CallExtractionConfig } from '../../call-types.js';
|
||||
|
||||
// Lua call extraction is fully handled by the generic createCallExtractor
|
||||
// (derive calledName + callForm + receiver from @call/@call.name captures in
|
||||
// LUA_QUERIES). No language-specific call shapes (no `::` method references),
|
||||
// so the config carries only the language id.
|
||||
export const luaCallConfig: CallExtractionConfig = {
|
||||
language: SupportedLanguages.Lua,
|
||||
};
|
||||
|
|
@ -240,5 +240,10 @@ export const swiftExportChecker: ExportChecker = (node, _name) => {
|
|||
/** Ruby: all top-level definitions are public (no export syntax). */
|
||||
export const rubyExportChecker: ExportChecker = (_node, _name) => true;
|
||||
|
||||
/** Lua: `function foo()` is global (reachable from other files via require/_G);
|
||||
* `local function foo()` is module-private. */
|
||||
export const luaExportChecker: ExportChecker = (node, _name) =>
|
||||
node.type !== 'local_function_definition_statement';
|
||||
|
||||
/** Dart: public if no leading underscore (convention, same as Python). */
|
||||
export const dartExportChecker: ExportChecker = (_node, name) => !name.startsWith('_');
|
||||
|
|
|
|||
34
gitnexus/src/core/ingestion/import-resolvers/configs/lua.ts
Normal file
34
gitnexus/src/core/ingestion/import-resolvers/configs/lua.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Lua import resolution config.
|
||||
*
|
||||
* `require("a.b.c")` → module path `a/b/c`, resolved via suffixResolve
|
||||
* (tries .lua then /init.lua — see EXTENSIONS in utils.ts). Mirrors
|
||||
* rubyRequireStrategy but splits on `.` (Lua's module separator) and strips
|
||||
* surrounding quotes (the @import.source capture is a string literal).
|
||||
*
|
||||
* NOTE: inert in Phase A. The legacy parse-worker skips @import captures
|
||||
* (parse-worker.ts:1654) — IMPORTS edges are emitted by the scope-resolution
|
||||
* phase once lua/query.ts + emitScopeCaptures land (Phase B). This config is
|
||||
* provided because LanguageProvider.importResolver is a required field.
|
||||
*/
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js';
|
||||
import { suffixResolve } from '../utils.js';
|
||||
|
||||
export const luaRequireStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
const cleaned = rawImportPath.replace(/^["']|["']$/g, '');
|
||||
const pathParts = cleaned.split('.').filter(Boolean);
|
||||
if (pathParts.length === 0) return null;
|
||||
const resolved = suffixResolve(
|
||||
pathParts,
|
||||
ctx.normalizedFileList,
|
||||
ctx.allFileList,
|
||||
ctx.index,
|
||||
);
|
||||
return resolved ? { kind: 'files', files: [resolved] } : null;
|
||||
};
|
||||
|
||||
export const luaImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Lua,
|
||||
strategies: [luaRequireStrategy],
|
||||
};
|
||||
|
|
@ -22,6 +22,9 @@ export const EXTENSIONS = [
|
|||
// Python
|
||||
'.py',
|
||||
'/__init__.py',
|
||||
// Lua — require("a.b.c") resolves to a/b/c.lua or a/b/c/init.lua
|
||||
'.lua',
|
||||
'/init.lua',
|
||||
// Java
|
||||
'.java',
|
||||
// Kotlin
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { swiftProvider } from './swift.js';
|
|||
import { dartProvider } from './dart.js';
|
||||
import { vueProvider } from './vue.js';
|
||||
import { cobolProvider } from './cobol.js';
|
||||
import { luaProvider } from './lua.js';
|
||||
|
||||
export const providers = {
|
||||
[SupportedLanguages.JavaScript]: javascriptProvider,
|
||||
|
|
@ -43,6 +44,7 @@ export const providers = {
|
|||
[SupportedLanguages.Dart]: dartProvider,
|
||||
[SupportedLanguages.Vue]: vueProvider,
|
||||
[SupportedLanguages.Cobol]: cobolProvider,
|
||||
[SupportedLanguages.Lua]: luaProvider,
|
||||
} satisfies Record<SupportedLanguages, LanguageProvider>;
|
||||
|
||||
/** Get provider by language enum (always succeeds for SupportedLanguages). */
|
||||
|
|
|
|||
41
gitnexus/src/core/ingestion/languages/lua.ts
Normal file
41
gitnexus/src/core/ingestion/languages/lua.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Lua language provider.
|
||||
*
|
||||
* Phase A (legacy DAG): emits Function/Method DEFINITION nodes from
|
||||
* LUA_QUERIES — `function foo()`, `local function foo()`, `function Obj:m()`
|
||||
* / `function Obj.m()`.
|
||||
*
|
||||
* Phase B1 (scope resolution): `emitScopeCaptures` (lua/captures.ts) runs the
|
||||
* scope query (lua/query.ts) and the central ScopeExtractor builds the scope
|
||||
* tree + declarations + imports + reference sites. `interpretImport` turns
|
||||
* require()'s `@import.source` into a wildcard ParsedImport; the legacy
|
||||
* `importResolver` (luaRequireStrategy / suffixResolve) is bridged to resolve
|
||||
* `targetRaw` → file (no separate resolveImportTarget needed, mirroring
|
||||
* Ruby). This unlocks CALLS edges (from @reference.call.*) and IMPORTS edges.
|
||||
*
|
||||
* Still Phase B (pending): middleclass `class("Name", Parent)` → Class nodes
|
||||
* + EXTENDS + HAS_METHOD (call-router pattern); receiverBinding/arity polish
|
||||
* for higher-quality call-target linking.
|
||||
*/
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { defineLanguage } from '../language-provider.js';
|
||||
import { LUA_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { typeConfig as luaTypeConfig } from '../type-extractors/lua.js';
|
||||
import { luaExportChecker } from '../export-detection.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { luaImportConfig } from '../import-resolvers/configs/lua.js';
|
||||
import { createCallExtractor } from '../call-extractors/generic.js';
|
||||
import { luaCallConfig } from '../call-extractors/configs/lua.js';
|
||||
import { emitLuaScopeCaptures, interpretLuaImport } from './lua/index.js';
|
||||
|
||||
export const luaProvider = defineLanguage({
|
||||
id: SupportedLanguages.Lua,
|
||||
extensions: ['.lua'],
|
||||
treeSitterQueries: LUA_QUERIES,
|
||||
typeConfig: luaTypeConfig,
|
||||
exportChecker: luaExportChecker,
|
||||
importResolver: createImportResolver(luaImportConfig),
|
||||
callExtractor: createCallExtractor(luaCallConfig),
|
||||
emitScopeCaptures: emitLuaScopeCaptures,
|
||||
interpretImport: interpretLuaImport,
|
||||
});
|
||||
59
gitnexus/src/core/ingestion/languages/lua/captures.ts
Normal file
59
gitnexus/src/core/ingestion/languages/lua/captures.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Lua scope-capture emitter (RFC #909 Ring 3).
|
||||
*
|
||||
* Minimal grouping: parse (or reuse the worker's cached AST) → run the scope
|
||||
* query → group each match's captures into a CaptureMatch keyed by `@name`.
|
||||
* No Ruby-style decomposition (Lua's require is captured directly in the query
|
||||
* as @import.statement + @import.source), no YARD, no receiver synthesis —
|
||||
* those are deferred (receiver/arity polish, Phase B3).
|
||||
*
|
||||
* The central ScopeExtractor partitions the output by capture prefix
|
||||
* (@scope.* / @declaration.* / @import.* / @reference.*) and builds the scope
|
||||
* tree, declarations, imports, and reference sites that finalize turns into
|
||||
* CALLS + IMPORTS edges.
|
||||
*/
|
||||
import Parser from 'tree-sitter';
|
||||
import type { Capture, CaptureMatch } from 'gitnexus-shared';
|
||||
import { nodeToCapture } from '../../utils/ast-helpers.js';
|
||||
import { getLuaParser, getLuaScopeQuery } from './query.js';
|
||||
import { getTreeSitterBufferSize } from '../../constants.js';
|
||||
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
||||
|
||||
export function emitLuaScopeCaptures(
|
||||
sourceText: string,
|
||||
_filePath: string,
|
||||
cachedTree?: unknown,
|
||||
): readonly CaptureMatch[] {
|
||||
let tree: Parser.Tree;
|
||||
if (cachedTree !== undefined && cachedTree !== null) {
|
||||
tree = cachedTree as Parser.Tree;
|
||||
} else {
|
||||
tree = parseSourceSafe(getLuaParser(), sourceText, undefined, {
|
||||
bufferSize: getTreeSitterBufferSize(sourceText),
|
||||
});
|
||||
}
|
||||
|
||||
const out: CaptureMatch[] = [];
|
||||
for (const match of getLuaScopeQuery().matches(tree.rootNode)) {
|
||||
const grouped: Record<string, Capture> = {};
|
||||
for (const c of match.captures) {
|
||||
const tag = '@' + c.name;
|
||||
// Skip tree-sitter predicate captures (e.g. @_req used by #eq?).
|
||||
if (tag.startsWith('@_')) continue;
|
||||
if (grouped[tag] === undefined) grouped[tag] = nodeToCapture(tag, c.node);
|
||||
}
|
||||
if (Object.keys(grouped).length === 0) continue;
|
||||
// middleclass `class("Name", ...)`: strip surrounding quotes from the name
|
||||
// so the Class node is named `BattleSkill`, not `"BattleSkill"`. tree-sitter-lua's
|
||||
// string node has no content child, so .text carries the quotes.
|
||||
const nameCap = grouped['@declaration.name'];
|
||||
if (grouped['@declaration.class'] !== undefined && nameCap !== undefined) {
|
||||
const stripped = nameCap.text.replace(/^["']|["']$/g, '');
|
||||
if (stripped !== nameCap.text) {
|
||||
grouped['@declaration.name'] = { ...nameCap, text: stripped };
|
||||
}
|
||||
}
|
||||
out.push(grouped);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
182
gitnexus/src/core/ingestion/languages/lua/heritage.ts
Normal file
182
gitnexus/src/core/ingestion/languages/lua/heritage.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/**
|
||||
* Lua middleclass inheritance edges (EXTENDS).
|
||||
*
|
||||
* middleclass has no syntactic class body — `class("Name", Parent)` is a plain
|
||||
* call, and methods are file-top-level `function Name:method()`. So neither
|
||||
* lexical heritage nor lexical HAS_METHOD applies. This hook re-parses each
|
||||
* file for `class("Name", Parent)` calls and emits EXTENDS from the child Class
|
||||
* graph node to the parent Class graph node, resolving both via `nodeLookup`.
|
||||
*
|
||||
* Mirrors `emitRubyMixinEdges` (the only other `emitHeritageEdges` impl), but
|
||||
* reads the heritage pair straight from the AST rather than threading markers
|
||||
* through `parsedImports` — middleclass's single-arg form needs no marker
|
||||
* decomposition, and the parent is a bare identifier in the source.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
import Parser from 'tree-sitter';
|
||||
import { SupportedLanguages, type ParsedFile, type NodeLabel } from 'gitnexus-shared';
|
||||
import { getLanguageGrammar } from '../../../tree-sitter/parser-loader.js';
|
||||
import { isClassLike } from '../../scope-resolution/scope/walkers.js';
|
||||
import { resolveDefGraphId } from '../../scope-resolution/graph-bridge/ids.js';
|
||||
import { positionKey, type GraphNodeLookup } from '../../scope-resolution/graph-bridge/node-lookup.js';
|
||||
import type { KnowledgeGraph } from '../../../graph/types.js';
|
||||
import { generateId } from '../../../../lib/utils.js';
|
||||
import { getLuaParser } from './query.js';
|
||||
|
||||
// `local Foo = class("Foo", Parent)` — the 2nd arg is a bare (variable name:).
|
||||
// `class("Foo")` (no parent) does not match: this pattern requires the trailing
|
||||
// variable, so parentless classes correctly emit no EXTENDS edge.
|
||||
const HERITAGE_QUERY = `
|
||||
(call
|
||||
function: (variable name: (identifier) @_class)
|
||||
arguments: (argument_list
|
||||
(expression_list
|
||||
(string) @child.name
|
||||
(variable name: (identifier) @parent.name)))
|
||||
(#eq? @_class "class")) @heritage
|
||||
`;
|
||||
|
||||
// `function ClassName:method()` (colon) and `function ClassName.method()` (dot).
|
||||
// The receiver (table) is captured as @method.owner; resolves to a Class node
|
||||
// for HAS_METHOD. middleclass methods are file-top-level (not in a class body),
|
||||
// so lexical HAS_METHOD cannot produce these.
|
||||
const METHOD_OWNER_QUERY = `
|
||||
(function_definition_statement
|
||||
name: (variable
|
||||
table: (identifier) @method.owner
|
||||
method: (identifier) @method.name)) @method.def
|
||||
|
||||
(function_definition_statement
|
||||
name: (variable
|
||||
table: (identifier) @method.owner
|
||||
field: (identifier) @method.name)) @method.def
|
||||
`;
|
||||
|
||||
let _heritageQuery: Parser.Query | null = null;
|
||||
function getHeritageQuery(): Parser.Query {
|
||||
if (_heritageQuery === null) {
|
||||
_heritageQuery = new Parser.Query(
|
||||
getLanguageGrammar(SupportedLanguages.Lua) as Parameters<Parser['setLanguage']>[0],
|
||||
HERITAGE_QUERY,
|
||||
);
|
||||
}
|
||||
return _heritageQuery;
|
||||
}
|
||||
|
||||
let _methodOwnerQuery: Parser.Query | null = null;
|
||||
function getMethodOwnerQuery(): Parser.Query {
|
||||
if (_methodOwnerQuery === null) {
|
||||
_methodOwnerQuery = new Parser.Query(
|
||||
getLanguageGrammar(SupportedLanguages.Lua) as Parameters<Parser['setLanguage']>[0],
|
||||
METHOD_OWNER_QUERY,
|
||||
);
|
||||
}
|
||||
return _methodOwnerQuery;
|
||||
}
|
||||
|
||||
function stripQuotes(s: string): string {
|
||||
return s.replace(/^["']|["']$/g, '');
|
||||
}
|
||||
|
||||
export function emitLuaHeritageEdges(
|
||||
graph: KnowledgeGraph,
|
||||
parsedFiles: readonly ParsedFile[],
|
||||
nodeLookup: GraphNodeLookup,
|
||||
): void {
|
||||
// name → graphId (global, for parent resolution). First-wins; same-named
|
||||
// classes in different files are rare in middleclass codebases, and a
|
||||
// collision here only risks a wrong parent — acceptable for a 0.85-confidence
|
||||
// heuristic edge (better an imperfect EXTENDS than none).
|
||||
const graphIdByName = new Map<string, string>();
|
||||
// (filePath, name) → graphId (per-file, for child resolution).
|
||||
const graphIdByFileAndName = new Map<string, string>();
|
||||
for (const parsed of parsedFiles) {
|
||||
for (const def of parsed.localDefs) {
|
||||
if (!isClassLike(def.type)) continue;
|
||||
const gid = resolveDefGraphId(parsed.filePath, def, nodeLookup);
|
||||
if (gid === undefined) continue;
|
||||
const qn = def.qualifiedName ?? '';
|
||||
if (qn.length > 0) {
|
||||
graphIdByFileAndName.set(`${parsed.filePath}::${qn}`, gid);
|
||||
if (!graphIdByName.has(qn)) graphIdByName.set(qn, gid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const emittedExtends = new Set<string>();
|
||||
const emittedHasMethod = new Set<string>();
|
||||
const parser = getLuaParser();
|
||||
const heritageQuery = getHeritageQuery();
|
||||
const methodOwnerQuery = getMethodOwnerQuery();
|
||||
for (const parsed of parsedFiles) {
|
||||
let src: string;
|
||||
try {
|
||||
src = readFileSync(parsed.filePath, 'utf8');
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
let tree;
|
||||
try {
|
||||
tree = parser.parse(src);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── EXTENDS: class("Name", Parent) ──────────────────────────────────────
|
||||
const heritageMatches = heritageQuery.matches(tree.rootNode);
|
||||
for (const m of heritageMatches) {
|
||||
const caps: Record<string, Parser.SyntaxNode> = {};
|
||||
for (const c of m.captures) caps[c.name] = c.node;
|
||||
const childName = stripQuotes(caps['child.name']?.text ?? '');
|
||||
const parentName = caps['parent.name']?.text;
|
||||
if (childName.length === 0 || parentName === undefined) continue;
|
||||
const childGid =
|
||||
graphIdByFileAndName.get(`${parsed.filePath}::${childName}`) ??
|
||||
graphIdByName.get(childName);
|
||||
const parentGid = graphIdByName.get(parentName);
|
||||
if (childGid === undefined || parentGid === undefined) continue;
|
||||
const edgeKey = `${childGid}->${parentGid}`;
|
||||
if (emittedExtends.has(edgeKey)) continue;
|
||||
emittedExtends.add(edgeKey);
|
||||
graph.addRelationship({
|
||||
id: generateId('EXTENDS', edgeKey),
|
||||
sourceId: childGid,
|
||||
targetId: parentGid,
|
||||
type: 'EXTENDS',
|
||||
confidence: 0.85,
|
||||
reason: 'lua-scope: middleclass inherits',
|
||||
});
|
||||
}
|
||||
|
||||
// ── HAS_METHOD: function ClassName:method() / function ClassName.method() ─
|
||||
const methodMatches = methodOwnerQuery.matches(tree.rootNode);
|
||||
for (const m of methodMatches) {
|
||||
const caps: Record<string, Parser.SyntaxNode> = {};
|
||||
for (const c of m.captures) caps[c.name] = c.node;
|
||||
const ownerName = caps['method.owner']?.text;
|
||||
const methodName = caps['method.name']?.text;
|
||||
const defNode = caps['method.def'];
|
||||
if (ownerName === undefined || methodName === undefined || defNode === undefined) continue;
|
||||
const classGid =
|
||||
graphIdByFileAndName.get(`${parsed.filePath}::${ownerName}`) ??
|
||||
graphIdByName.get(ownerName);
|
||||
if (classGid === undefined) continue;
|
||||
// Resolve the Method graph node by position (0-based row + simple name).
|
||||
const methodGid = nodeLookup.get(
|
||||
positionKey(parsed.filePath, 'Method' as NodeLabel, defNode.startPosition.row, methodName),
|
||||
);
|
||||
if (methodGid === undefined) continue;
|
||||
const edgeKey = `${classGid}->${methodGid}`;
|
||||
if (emittedHasMethod.has(edgeKey)) continue;
|
||||
emittedHasMethod.add(edgeKey);
|
||||
graph.addRelationship({
|
||||
id: generateId('HAS_METHOD', edgeKey),
|
||||
sourceId: classGid,
|
||||
targetId: methodGid,
|
||||
type: 'HAS_METHOD',
|
||||
confidence: 0.85,
|
||||
reason: 'lua-scope: middleclass method owner',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
7
gitnexus/src/core/ingestion/languages/lua/index.ts
Normal file
7
gitnexus/src/core/ingestion/languages/lua/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Lua scope-resolution hooks (RFC #909 Ring 3). Barrel.
|
||||
*/
|
||||
export { emitLuaScopeCaptures } from './captures.js';
|
||||
export { interpretLuaImport } from './interpret.js';
|
||||
export { getLuaParser, getLuaScopeQuery } from './query.js';
|
||||
export { emitLuaHeritageEdges } from './heritage.js';
|
||||
23
gitnexus/src/core/ingestion/languages/lua/interpret.ts
Normal file
23
gitnexus/src/core/ingestion/languages/lua/interpret.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Lua import interpretation (RFC #909).
|
||||
*
|
||||
* `require("a.b.c")` loads the module and (conventionally) returns its table.
|
||||
* We emit a `wildcard` ParsedImport so the IMPORTS edge (this file → required
|
||||
* file) is materialized and the target's exported names become resolvable as
|
||||
* the module's local name. Call-target linking for `util.split` style member
|
||||
* calls is handled by the scope-resolution registry against the import
|
||||
* binding — refined in Phase B3 (receiver/arity polish).
|
||||
*/
|
||||
import type { CaptureMatch, ParsedImport } from 'gitnexus-shared';
|
||||
|
||||
function stripQuotes(s: string): string {
|
||||
return s.replace(/^["']|["']$/g, '');
|
||||
}
|
||||
|
||||
export function interpretLuaImport(captures: CaptureMatch): ParsedImport | null {
|
||||
const source = captures['@import.source']?.text;
|
||||
if (source === undefined) return null;
|
||||
const targetRaw = stripQuotes(source);
|
||||
if (!targetRaw) return null;
|
||||
return { kind: 'wildcard', targetRaw };
|
||||
}
|
||||
110
gitnexus/src/core/ingestion/languages/lua/query.ts
Normal file
110
gitnexus/src/core/ingestion/languages/lua/query.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* Lua scope-resolution query (RFC #909).
|
||||
*
|
||||
* Captures the structural skeleton the central ScopeExtractor consumes:
|
||||
* - scopes: (chunk) root + function bodies
|
||||
* - declarations: function / local function / method (function Obj:m())
|
||||
* - imports: require("a.b.c") — the call + its string arg
|
||||
* - references: free calls foo() + member calls obj:m() / obj.f()
|
||||
*
|
||||
* `tree-sitter-lua` is a vendored grammar (loaded lazily via parser-loader, NOT
|
||||
* statically imported) — mirroring languages/dart/query.ts: a top-level
|
||||
* `import Lua from 'tree-sitter-lua'` would throw ERR_MODULE_NOT_FOUND on the
|
||||
* main thread (where the vendored grammar is not in node_modules) and crash
|
||||
* analyze even for repos with no Lua files (#2091/#2093).
|
||||
*
|
||||
* Node names verified against the vendored tree-sitter-lua grammar via the
|
||||
* smoke test in ~/gitnexus-lua-work/abi-test/smoke2.cjs:
|
||||
* (chunk) (function_definition_statement name: (identifier|variable))
|
||||
* (local_function_definition_statement name: (identifier))
|
||||
* (call function: (variable name:|table:+method:|table:+field:) arguments: (argument_list (expression_list ...)))
|
||||
*/
|
||||
import Parser from 'tree-sitter';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { getLanguageGrammar } from '../../../tree-sitter/parser-loader.js';
|
||||
|
||||
const LUA_SCOPE_QUERY = `
|
||||
;; ── Scopes ───────────────────────────────────────────────────────────────────
|
||||
(chunk) @scope.module
|
||||
|
||||
(function_definition_statement) @scope.function
|
||||
(local_function_definition_statement) @scope.function
|
||||
|
||||
;; ── Declarations — functions ─────────────────────────────────────────────────
|
||||
(function_definition_statement
|
||||
name: (identifier) @declaration.name) @declaration.function
|
||||
|
||||
(local_function_definition_statement
|
||||
name: (identifier) @declaration.name) @declaration.function
|
||||
|
||||
;; ── Declarations — methods: function Obj:method() / function Obj.field() ────
|
||||
;; name is a (variable) with table + method (colon) or table + field (dot).
|
||||
(function_definition_statement
|
||||
name: (variable
|
||||
method: (identifier) @declaration.name)) @declaration.method
|
||||
|
||||
(function_definition_statement
|
||||
name: (variable
|
||||
field: (identifier) @declaration.name)) @declaration.method
|
||||
|
||||
;; ── Declarations — middleclass classes: class("Name"[, Parent]) ──────────────
|
||||
;; local Foo = class("Foo", Parent) — class() is a plain call; the first
|
||||
;; string arg is the class name (quotes stripped in captures.ts). The second
|
||||
;; arg (Parent) → EXTENDS is Phase B2b (heritage marker).
|
||||
(call
|
||||
function: (variable name: (identifier) @_class)
|
||||
arguments: (argument_list (expression_list (string) @declaration.name))
|
||||
(#eq? @_class "class")) @declaration.class
|
||||
|
||||
;; ── Imports — require("a.b.c") ──────────────────────────────────────────────
|
||||
;; require is a plain (call) whose function is a (variable name: (identifier)).
|
||||
;; The string arg is captured as @import.source; interpretLuaImport strips
|
||||
;; quotes and resolves it via the legacy luaRequireStrategy (suffixResolve).
|
||||
(call
|
||||
function: (variable name: (identifier) @_req)
|
||||
arguments: (argument_list (expression_list (string) @import.source))
|
||||
(#eq? @_req "require")) @import.statement
|
||||
|
||||
;; ── References — free calls: foo() ───────────────────────────────────────────
|
||||
;; Also matches require(...) — harmless: require has no def, so it stays an
|
||||
;; unresolved reference (no CALLS edge) while the @import.statement pattern
|
||||
;; above emits the IMPORTS edge.
|
||||
(call
|
||||
function: (variable
|
||||
name: (identifier) @reference.name)) @reference.call.free
|
||||
|
||||
;; ── References — member calls: obj:method() (table + method) ─────────────────
|
||||
(call
|
||||
function: (variable
|
||||
table: (_) @reference.receiver
|
||||
method: (identifier) @reference.name)) @reference.call.member
|
||||
|
||||
;; ── References — field calls: obj.field() (table + field) ────────────────────
|
||||
(call
|
||||
function: (variable
|
||||
table: (_) @reference.receiver
|
||||
field: (identifier) @reference.name)) @reference.call.member
|
||||
`;
|
||||
|
||||
let _parser: Parser | null = null;
|
||||
let _query: Parser.Query | null = null;
|
||||
|
||||
export function getLuaParser(): Parser {
|
||||
if (_parser === null) {
|
||||
_parser = new Parser();
|
||||
_parser.setLanguage(
|
||||
getLanguageGrammar(SupportedLanguages.Lua) as Parameters<Parser['setLanguage']>[0],
|
||||
);
|
||||
}
|
||||
return _parser;
|
||||
}
|
||||
|
||||
export function getLuaScopeQuery(): Parser.Query {
|
||||
if (_query === null) {
|
||||
_query = new Parser.Query(
|
||||
getLanguageGrammar(SupportedLanguages.Lua) as Parameters<Parser['setLanguage']>[0],
|
||||
LUA_SCOPE_QUERY,
|
||||
);
|
||||
}
|
||||
return _query;
|
||||
}
|
||||
74
gitnexus/src/core/ingestion/languages/lua/scope-resolver.ts
Normal file
74
gitnexus/src/core/ingestion/languages/lua/scope-resolver.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Lua `ScopeResolver` registered in `SCOPE_RESOLVERS` and consumed by the
|
||||
* generic `runScopeResolution` orchestrator.
|
||||
*
|
||||
* Minimal Phase B1 wiring: Lua's scope model (Module + Function/Method, no
|
||||
* static types) plugs into `runScopeResolution` with the least configuration
|
||||
* that unlocks CALLS + IMPORTS edges. middleclass class inheritance (EXTENDS,
|
||||
* HAS_METHOD, MRO) is Phase B2; receiver/arity polish is Phase B3.
|
||||
*
|
||||
* Reference: `languages/cobol/scope-resolver.ts` (minimal) + `languages/go/`.
|
||||
*/
|
||||
import type { ParsedFile } from 'gitnexus-shared';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { populateClassOwnedMembers } from '../../scope-resolution/scope/walkers.js';
|
||||
import { buildSuffixIndex, suffixResolve, type SuffixIndex } from '../../import-resolvers/utils.js';
|
||||
import type { ScopeResolver } from '../../scope-resolution/contract/scope-resolver.js';
|
||||
import { luaProvider } from '../lua.js';
|
||||
import { emitLuaHeritageEdges } from './heritage.js';
|
||||
|
||||
// Cache the suffix index across calls within one analyze run — `allFilePaths`
|
||||
// is the same ReadonlySet for every Lua import in the run, so keying on its
|
||||
// reference avoids rebuilding a 500-entry index per require(). Must use the
|
||||
// index path: the linear-scan fallback in suffixResolve prepends `/` to the
|
||||
// suffix, which fails to match root-level files like `middleclass.lua`.
|
||||
let _cachedSet: ReadonlySet<string> | null = null;
|
||||
let _cachedIndex: SuffixIndex | null = null;
|
||||
|
||||
const luaScopeResolver: ScopeResolver = {
|
||||
language: SupportedLanguages.Lua,
|
||||
languageProvider: luaProvider,
|
||||
importEdgeReason: 'lua-scope: require',
|
||||
|
||||
// require("a.b.c") → module path a/b/c (+ EXTENSIONS: .lua / /init.lua).
|
||||
// targetRaw arrives quote-stripped (interpretLuaImport); Lua's module
|
||||
// separator is `.`, so split on it before joining to a path.
|
||||
resolveImportTarget: (targetRaw, _fromFile, allFilePaths) => {
|
||||
const parts = targetRaw.replace(/^["']|["']$/g, '').split('.').filter(Boolean);
|
||||
if (parts.length === 0) return null;
|
||||
if (_cachedSet !== allFilePaths) {
|
||||
const list = [...allFilePaths];
|
||||
_cachedIndex = buildSuffixIndex(list, list);
|
||||
_cachedSet = allFilePaths;
|
||||
}
|
||||
const list = [...allFilePaths];
|
||||
return suffixResolve(parts, list, list, _cachedIndex ?? undefined);
|
||||
},
|
||||
|
||||
// Lua: default local-first-then-imports merge (no language-specific precedence).
|
||||
mergeBindings: (existing) => [...existing],
|
||||
|
||||
// Lua varargs (...) + optional params make static arity checks unreliable —
|
||||
// 'unknown' (no signal) is the safe minimal choice.
|
||||
arityCompatibility: () => 'unknown',
|
||||
|
||||
// middleclass single inheritance — MRO populated in Phase B2 (buildMro +
|
||||
// defaultLinearize). Empty for now (no Class scopes yet).
|
||||
buildMro: () => new Map(),
|
||||
|
||||
populateOwners: (parsed: ParsedFile) => populateClassOwnedMembers(parsed),
|
||||
|
||||
// Lua has no super-call construct (middleclass __base access is Phase B2).
|
||||
isSuperReceiver: () => false,
|
||||
|
||||
// middleclass `class("Name", Parent)` — emits EXTENDS. middleclass has no
|
||||
// syntactic class body, so lexical heritage cannot produce these; the hook
|
||||
// re-parses for the class() call's parent arg.
|
||||
emitHeritageEdges: emitLuaHeritageEdges,
|
||||
|
||||
// Lua has globals (`function foo()` is global) — let unresolved free calls
|
||||
// fall back to the global symbol table.
|
||||
allowGlobalFreeCallFallback: true,
|
||||
};
|
||||
|
||||
export { luaScopeResolver };
|
||||
|
|
@ -27,6 +27,7 @@ import { cobolScopeResolver } from '../../languages/cobol/scope-resolver.js';
|
|||
import { swiftScopeResolver } from '../../languages/swift/scope-resolver.js';
|
||||
import { dartScopeResolver } from '../../languages/dart/scope-resolver.js';
|
||||
import { vueScopeResolver } from '../../languages/vue/scope-resolver.js';
|
||||
import { luaScopeResolver } from '../../languages/lua/scope-resolver.js';
|
||||
|
||||
/** Map of `SupportedLanguages` → `ScopeResolver`. The scope-resolution phase
|
||||
* iterates this map directly — every registered resolver runs. This is the
|
||||
|
|
@ -51,4 +52,5 @@ export const SCOPE_RESOLVERS: ReadonlyMap<SupportedLanguages, ScopeResolver> = n
|
|||
[SupportedLanguages.Swift, swiftScopeResolver],
|
||||
[SupportedLanguages.Dart, dartScopeResolver],
|
||||
[SupportedLanguages.Vue, vueScopeResolver],
|
||||
[SupportedLanguages.Lua, luaScopeResolver],
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -2447,6 +2447,67 @@ export const DART_QUERIES = `
|
|||
right: (_)) @assignment
|
||||
`;
|
||||
|
||||
// Lua queries — works with vendored tree-sitter-lua (rebuilt against the
|
||||
// tree-sitter@0.21.1 runtime + Napi convention; see vendor/tree-sitter-lua).
|
||||
// AST node names verified by parsing a sample with the smoke test in
|
||||
// ~/gitnexus-lua-work/abi-test/smoke2.cjs.
|
||||
//
|
||||
// Phase A (legacy DAG): definition NODES + call SITES. NOTE: CALLS and
|
||||
// IMPORTS EDGES are NOT emitted by the legacy path — call/import resolution
|
||||
// was migrated to the scope-resolution pipeline (RFC #909, gated on
|
||||
// emitScopeCaptures; see call-processor.ts:4-7 + parse-worker.ts:1654). The
|
||||
// @call captures here produce call sites in parse-worker, but those sites
|
||||
// only become CALLS edges once lua's scope query (lua/query.ts) + the
|
||||
// emitScopeCaptures hooks land (Phase B). The definition captures DO produce
|
||||
// Function/Method nodes regardless.
|
||||
export const LUA_QUERIES = `
|
||||
; ── Functions: function foo() ... end (name is a bare identifier) ────────────
|
||||
(function_definition_statement
|
||||
name: (identifier) @name) @definition.function
|
||||
|
||||
; ── Methods: function Obj:method() (name is a variable: table + method) ──────
|
||||
(function_definition_statement
|
||||
name: (variable
|
||||
method: (identifier) @name)) @definition.method
|
||||
|
||||
; ── Dot-methods: function Obj.method() (name is a variable: table + field) ───
|
||||
(function_definition_statement
|
||||
name: (variable
|
||||
field: (identifier) @name)) @definition.method
|
||||
|
||||
; ── Local functions: local function foo() ... end ─────────────────────────────
|
||||
(local_function_definition_statement
|
||||
name: (identifier) @name) @definition.function
|
||||
|
||||
; ── middleclass classes: local Foo = class("Foo"[, Parent]) ─────────────────────
|
||||
; class() is a plain call; the Class node's name comes from the local var
|
||||
; (quote-free). EXTENDS (Parent arg) + HAS_METHOD (function Foo:method() → Foo)
|
||||
; need scope-resolution receiver modeling and the heritage marker; middleclass
|
||||
; methods are file-top-level (not class-body nested), so the class is not a
|
||||
; lexical scope — deferred (Phase B2b/B3).
|
||||
(local_variable_declaration
|
||||
(variable_list (variable name: (identifier) @name))
|
||||
(expression_list
|
||||
value: (call
|
||||
function: (variable name: (identifier) @_class)
|
||||
(#eq? @_class "class")))) @definition.class
|
||||
|
||||
; ── Calls: foo(), obj:bar(), obj.baz() ────────────────────────────────────────
|
||||
; call.function is a (variable); @call.name lands on the invoked identifier
|
||||
; so the generic call extractor can derive calledName + callForm + receiver.
|
||||
(call
|
||||
function: (variable
|
||||
name: (identifier) @call.name)) @call
|
||||
|
||||
(call
|
||||
function: (variable
|
||||
method: (identifier) @call.name)) @call
|
||||
|
||||
(call
|
||||
function: (variable
|
||||
field: (identifier) @call.name)) @call
|
||||
`;
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
|
||||
export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
||||
|
|
@ -2464,6 +2525,7 @@ export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
|||
[SupportedLanguages.Ruby]: RUBY_QUERIES,
|
||||
[SupportedLanguages.Swift]: SWIFT_QUERIES,
|
||||
[SupportedLanguages.Dart]: DART_QUERIES,
|
||||
[SupportedLanguages.Lua]: LUA_QUERIES,
|
||||
[SupportedLanguages.Vue]: TYPESCRIPT_QUERIES, // Vue <script> blocks are parsed as TypeScript
|
||||
[SupportedLanguages.Cobol]: '', // Standalone regex processor — no tree-sitter queries
|
||||
};
|
||||
|
|
|
|||
16
gitnexus/src/core/ingestion/type-extractors/lua.ts
Normal file
16
gitnexus/src/core/ingestion/type-extractors/lua.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Lua type extractor — minimal.
|
||||
*
|
||||
* Lua has no static type annotations, so no (varName → typeName) bindings are
|
||||
* extracted: declarationNodeTypes is empty and extractDeclaration/extractParameter
|
||||
* are no-ops. Constructor-inference and return-type inference are deferred
|
||||
* (would belong in Phase B scope-resolution). Satisfies the LanguageTypeConfig
|
||||
* required fields with the least-possible surface.
|
||||
*/
|
||||
import type { LanguageTypeConfig } from './types.js';
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: new Set<string>(),
|
||||
extractDeclaration: () => {},
|
||||
extractParameter: () => {},
|
||||
};
|
||||
|
|
@ -81,6 +81,11 @@ let C: TreeSitterLanguage | null = null;
|
|||
try {
|
||||
C = requireVendoredGrammar('tree-sitter-c') as TreeSitterLanguage;
|
||||
} catch {}
|
||||
|
||||
let Lua: TreeSitterLanguage | null = null;
|
||||
try {
|
||||
Lua = requireVendoredGrammar('tree-sitter-lua') as TreeSitterLanguage;
|
||||
} catch {}
|
||||
import { getLanguageFromFilename } from 'gitnexus-shared';
|
||||
import {
|
||||
buildDefinitionPreScan,
|
||||
|
|
@ -548,6 +553,7 @@ const languageMap: Record<string, TreeSitterLanguage> = {
|
|||
[SupportedLanguages.Vue]: TypeScript.typescript,
|
||||
...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
|
||||
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
|
||||
...(Lua ? { [SupportedLanguages.Lua]: Lua } : {}),
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -177,6 +177,17 @@ const SOURCES: Record<string, GrammarSource> = {
|
|||
'Likely cause: no prebuilt `.node` for this platform/architecture. ' +
|
||||
`See ${ISSUES_URL}/2107.`,
|
||||
},
|
||||
[SupportedLanguages.Lua]: {
|
||||
load: () => requireVendoredGrammar('tree-sitter-lua'),
|
||||
optional: true,
|
||||
userSkippable: true,
|
||||
unavailableNote:
|
||||
'Lua parsing disabled: vendored `tree-sitter-lua` (under ' +
|
||||
'`gitnexus/vendor/tree-sitter-lua`) failed to load. ' +
|
||||
'Likely cause: the native binding did not build at install (missing ' +
|
||||
'python3/make/clang) or no prebuilt `.node` for this platform/architecture. ' +
|
||||
`See ${ISSUES_URL}.`,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export const VENDORED_GRAMMAR_PACKAGES: ReadonlySet<string> = new Set([
|
|||
'tree-sitter-proto',
|
||||
'tree-sitter-swift',
|
||||
'tree-sitter-kotlin',
|
||||
'tree-sitter-lua',
|
||||
]);
|
||||
|
||||
/** Absolute directory of a vendored grammar package under `vendor/`. */
|
||||
|
|
|
|||
21
gitnexus/vendor/tree-sitter-lua/LICENSE
vendored
Normal file
21
gitnexus/vendor/tree-sitter-lua/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) tree-sitter-grammars/tree-sitter-lua contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
16
gitnexus/vendor/tree-sitter-lua/README.md
vendored
Normal file
16
gitnexus/vendor/tree-sitter-lua/README.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# tree-sitter-lua (vendored)
|
||||
|
||||
Vendored copy of [tree-sitter-lua](https://github.com/tree-sitter-grammars/tree-sitter-lua) (v2.1.3, MIT).
|
||||
|
||||
## Why vendored
|
||||
|
||||
The upstream npm package ships `nan`-based node bindings that are ABI-incompatible with the `tree-sitter@0.21.1` runtime GitNexus pins (`setLanguage` rejects with "Invalid language object"). The grammar C sources (`src/parser.c`, `src/scanner.c`) are vendored unmodified and rebuilt against a Napi `binding.cc` adapted from `tree-sitter-c`'s vendored binding, so the grammar loads under the pinned runtime.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/` — upstream grammar sources (unmodified)
|
||||
- `bindings/node/binding.cc` — Napi binding (adapted from `tree-sitter-c`)
|
||||
- `bindings/node/index.js`, `index.d.ts` — node entry + types (adapted from `tree-sitter-c`)
|
||||
- `binding.gyp`, `package.json` — build config
|
||||
|
||||
Built by `scripts/build-tree-sitter-grammars.cjs` at install time; `build/` artifacts are gitignored.
|
||||
30
gitnexus/vendor/tree-sitter-lua/binding.gyp
vendored
Normal file
30
gitnexus/vendor/tree-sitter-lua/binding.gyp
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "tree_sitter_lua_binding",
|
||||
"dependencies": [
|
||||
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
|
||||
],
|
||||
"include_dirs": [
|
||||
"src"
|
||||
],
|
||||
"sources": [
|
||||
"bindings/node/binding.cc",
|
||||
"src/parser.c",
|
||||
"src/scanner.c"
|
||||
],
|
||||
"conditions": [
|
||||
["OS!='win'", {
|
||||
"cflags_c": [
|
||||
"-std=c11"
|
||||
]
|
||||
}, {
|
||||
"cflags_c": [
|
||||
"/std:c11",
|
||||
"/utf-8"
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
22
gitnexus/vendor/tree-sitter-lua/bindings/node/binding.cc
vendored
Normal file
22
gitnexus/vendor/tree-sitter-lua/bindings/node/binding.cc
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include <napi.h>
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
extern "C" TSLanguage *tree_sitter_lua();
|
||||
|
||||
// "tree-sitter", "language" hashed with BLAKE2 — the type tag every tree-sitter
|
||||
// grammar's language external carries; the tree-sitter@0.21.1 runtime's
|
||||
// Parser.setLanguage checks it to validate the language object.
|
||||
const napi_type_tag LANGUAGE_TYPE_TAG = {
|
||||
0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
|
||||
};
|
||||
|
||||
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
||||
exports["name"] = Napi::String::New(env, "lua");
|
||||
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_lua());
|
||||
language.TypeTag(&LANGUAGE_TYPE_TAG);
|
||||
exports["language"] = language;
|
||||
return exports;
|
||||
}
|
||||
|
||||
NODE_API_MODULE(tree_sitter_lua_binding, Init)
|
||||
5
gitnexus/vendor/tree-sitter-lua/bindings/node/index.d.ts
vendored
Normal file
5
gitnexus/vendor/tree-sitter-lua/bindings/node/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Type stub mirroring gitnexus/vendor/tree-sitter-c/bindings/node/index.d.ts.
|
||||
// The runtime binding is a native .node loaded by node-gyp-build; this stub
|
||||
// gives TypeScript a Language-shaped module to import.
|
||||
declare const language: unknown;
|
||||
export = language;
|
||||
7
gitnexus/vendor/tree-sitter-lua/bindings/node/index.js
vendored
Normal file
7
gitnexus/vendor/tree-sitter-lua/bindings/node/index.js
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const root = require("path").join(__dirname, "..", "..");
|
||||
|
||||
module.exports = require("node-gyp-build")(root);
|
||||
|
||||
try {
|
||||
module.exports.nodeTypeInfo = require("../../src/node-types.json");
|
||||
} catch (_) {}
|
||||
18
gitnexus/vendor/tree-sitter-lua/package.json
vendored
Normal file
18
gitnexus/vendor/tree-sitter-lua/package.json
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "tree-sitter-lua",
|
||||
"version": "2.1.3",
|
||||
"description": "Lua grammar for tree-sitter (vendored by gitnexus)",
|
||||
"repository": "https://github.com/tree-sitter-grammars/tree-sitter-lua",
|
||||
"license": "MIT",
|
||||
"main": "bindings/node/index.js",
|
||||
"types": "bindings/node/index.d.ts",
|
||||
"_vendoredBy": "gitnexus - runtime package derived from tree-sitter-lua@2.1.3 (tree-sitter-grammars/tree-sitter-lua). HELD at 2.1.3 grammar sources but REBUILT against the bundled tree-sitter@0.21.1 runtime + node-addon-api Napi convention (the same convention as vendor/tree-sitter-c). Upstream's npm package ships a nan/node.h binding that the tree-sitter@0.21.1 runtime rejects with 'Invalid language object' (nan External lacks the LANGUAGE_TYPE_TAG the 0.21.1 setLanguage validates); vendoring with this Napi binding.cc fixes that. Do not bump without verifying ABI against the 0.21.1 runtime. Loaded from vendor/ by absolute path at runtime (vendored-grammars.ts) — NEVER copied to node_modules (#2111). No prebuilds committed yet; build-tree-sitter-grammars.cjs source-builds on a toolchain host (darwin/linux x64+arm64 via clang+python).",
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.21.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree-sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
1302
gitnexus/vendor/tree-sitter-lua/src/node-types.json
vendored
Normal file
1302
gitnexus/vendor/tree-sitter-lua/src/node-types.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
11288
gitnexus/vendor/tree-sitter-lua/src/parser.c
vendored
Normal file
11288
gitnexus/vendor/tree-sitter-lua/src/parser.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
315
gitnexus/vendor/tree-sitter-lua/src/scanner.c
vendored
Normal file
315
gitnexus/vendor/tree-sitter-lua/src/scanner.c
vendored
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
#include <tree_sitter/parser.h>
|
||||
#include <wctype.h>
|
||||
|
||||
enum TokenType
|
||||
{
|
||||
COMMENT_START,
|
||||
COMMENT_CONTENT,
|
||||
COMMENT_END,
|
||||
STRING_START,
|
||||
STRING_CONTENT,
|
||||
STRING_END,
|
||||
};
|
||||
|
||||
static void consume(TSLexer *lexer)
|
||||
{
|
||||
lexer->advance(lexer, false);
|
||||
}
|
||||
static void skip(TSLexer *lexer)
|
||||
{
|
||||
lexer->advance(lexer, true);
|
||||
}
|
||||
static bool consume_if(TSLexer *lexer, const int32_t character)
|
||||
{
|
||||
if (lexer->lookahead == character)
|
||||
{
|
||||
consume(lexer);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char SQ_STRING_DELIMITER = '\'';
|
||||
const char DQ_STRING_DELIMITER = '"';
|
||||
|
||||
enum StartedToken
|
||||
{
|
||||
SHORT_COMMENT = 1,
|
||||
SHORT_SQ_STRING,
|
||||
SHORT_DQ_STRING,
|
||||
LONG_COMMENT,
|
||||
LONG_STRING,
|
||||
};
|
||||
|
||||
struct ScannerState
|
||||
{
|
||||
enum StartedToken started;
|
||||
unsigned int depth;
|
||||
};
|
||||
|
||||
void *tree_sitter_lua_external_scanner_create()
|
||||
{
|
||||
return malloc(sizeof(struct ScannerState));
|
||||
}
|
||||
|
||||
void tree_sitter_lua_external_scanner_destroy(void *payload)
|
||||
{
|
||||
free(payload);
|
||||
}
|
||||
|
||||
unsigned int tree_sitter_lua_external_scanner_serialize(void *payload, char *buffer)
|
||||
{
|
||||
struct ScannerState *state = payload;
|
||||
buffer[0] = state->started;
|
||||
buffer[1] = state->depth;
|
||||
return 2;
|
||||
}
|
||||
|
||||
void tree_sitter_lua_external_scanner_deserialize(void *payload, const char *buffer, unsigned int length)
|
||||
{
|
||||
if (length == 2)
|
||||
{
|
||||
struct ScannerState *state = payload;
|
||||
state->started = buffer[0];
|
||||
state->depth = buffer[1];
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int get_depth(TSLexer *lexer)
|
||||
{
|
||||
unsigned int current_depth = 0;
|
||||
while (consume_if(lexer, '='))
|
||||
{
|
||||
current_depth += 1;
|
||||
}
|
||||
|
||||
return current_depth;
|
||||
}
|
||||
|
||||
static bool scan_depth(TSLexer *lexer, unsigned int remaining_depth)
|
||||
{
|
||||
while (remaining_depth > 0 && consume_if(lexer, '='))
|
||||
{
|
||||
remaining_depth -= 1;
|
||||
}
|
||||
|
||||
return remaining_depth == 0;
|
||||
}
|
||||
|
||||
bool tree_sitter_lua_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols)
|
||||
{
|
||||
struct ScannerState *state = payload;
|
||||
switch (state->started)
|
||||
{
|
||||
case SHORT_COMMENT:
|
||||
{
|
||||
// try to match the short comment's end (new line or eof)
|
||||
if (lexer->lookahead == '\n' || lexer->eof(lexer))
|
||||
{
|
||||
if (valid_symbols[COMMENT_END])
|
||||
{
|
||||
state->started = 0;
|
||||
|
||||
lexer->result_symbol = COMMENT_END;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (valid_symbols[COMMENT_CONTENT])
|
||||
{
|
||||
// consume all characters till a short comment's end
|
||||
do
|
||||
{
|
||||
consume(lexer);
|
||||
} while (lexer->lookahead != '\n' && !lexer->eof(lexer));
|
||||
|
||||
lexer->result_symbol = COMMENT_CONTENT;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SHORT_SQ_STRING:
|
||||
case SHORT_DQ_STRING:
|
||||
{
|
||||
// define the short string's delimiter
|
||||
const char delimiter = state->started == SHORT_SQ_STRING ? SQ_STRING_DELIMITER : DQ_STRING_DELIMITER;
|
||||
|
||||
// try to match the short string's end (" or ')
|
||||
if (consume_if(lexer, delimiter))
|
||||
{
|
||||
if (valid_symbols[STRING_END])
|
||||
{
|
||||
state->started = 0;
|
||||
|
||||
lexer->result_symbol = STRING_END;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (valid_symbols[STRING_CONTENT] && lexer->lookahead != '\n' && !lexer->eof(lexer))
|
||||
{
|
||||
// consume any character till a short string's end, new line or eof
|
||||
do
|
||||
{
|
||||
// consume any character after a backslash, unless it's a new line or eof
|
||||
if (consume_if(lexer, '\\') && (lexer->lookahead == '\n' || lexer->eof(lexer)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
consume(lexer);
|
||||
} while (lexer->lookahead != delimiter && lexer->lookahead != '\n' && !lexer->eof(lexer));
|
||||
|
||||
lexer->result_symbol = STRING_CONTENT;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case LONG_COMMENT:
|
||||
case LONG_STRING:
|
||||
{
|
||||
const bool is_inside_a_comment = state->started == LONG_COMMENT;
|
||||
|
||||
bool some_characters_were_consumed = false;
|
||||
if (is_inside_a_comment ? valid_symbols[COMMENT_END] : valid_symbols[STRING_END])
|
||||
{
|
||||
// try to match the long comment's/string's end (]=*])
|
||||
if (consume_if(lexer, ']'))
|
||||
{
|
||||
if (scan_depth(lexer, state->depth) && consume_if(lexer, ']'))
|
||||
{
|
||||
state->started = 0;
|
||||
state->depth = 0;
|
||||
|
||||
lexer->result_symbol = is_inside_a_comment ? COMMENT_END : STRING_END;
|
||||
return true;
|
||||
}
|
||||
|
||||
some_characters_were_consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_inside_a_comment ? valid_symbols[COMMENT_CONTENT] : valid_symbols[STRING_CONTENT])
|
||||
{
|
||||
if (!some_characters_were_consumed)
|
||||
{
|
||||
if (lexer->eof(lexer))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// consume the next character as it can't start a long comment's/string's end ([)
|
||||
consume(lexer);
|
||||
}
|
||||
|
||||
// consume any character till a long comment's/string's end or eof
|
||||
while (true)
|
||||
{
|
||||
lexer->mark_end(lexer);
|
||||
if (consume_if(lexer, ']'))
|
||||
{
|
||||
if (scan_depth(lexer, state->depth))
|
||||
{
|
||||
if (consume_if(lexer, ']'))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (lexer->eof(lexer))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
consume(lexer);
|
||||
}
|
||||
|
||||
lexer->result_symbol = is_inside_a_comment ? COMMENT_CONTENT : STRING_CONTENT;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// ignore all whitespace
|
||||
while (iswspace(lexer->lookahead))
|
||||
{
|
||||
skip(lexer);
|
||||
}
|
||||
|
||||
if (valid_symbols[COMMENT_START])
|
||||
{
|
||||
// try to match a short comment's start (--)
|
||||
if (consume_if(lexer, '-'))
|
||||
{
|
||||
if (consume_if(lexer, '-'))
|
||||
{
|
||||
state->started = SHORT_COMMENT;
|
||||
|
||||
// try to match a long comment's start (--[=*[)
|
||||
lexer->mark_end(lexer);
|
||||
if (consume_if(lexer, '['))
|
||||
{
|
||||
unsigned int possible_depth = get_depth(lexer);
|
||||
|
||||
if (consume_if(lexer, '['))
|
||||
{
|
||||
state->started = LONG_COMMENT;
|
||||
state->depth = possible_depth;
|
||||
|
||||
lexer->mark_end(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
lexer->result_symbol = COMMENT_START;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_symbols[STRING_START])
|
||||
{
|
||||
// try to match a short single-quoted string's start (")
|
||||
if (consume_if(lexer, SQ_STRING_DELIMITER))
|
||||
{
|
||||
state->started = SHORT_SQ_STRING;
|
||||
}
|
||||
// try to match a short double-quoted string's start (')
|
||||
else if (consume_if(lexer, DQ_STRING_DELIMITER))
|
||||
{
|
||||
state->started = SHORT_DQ_STRING;
|
||||
}
|
||||
// try to match a long string's start ([=*[)
|
||||
else if (consume_if(lexer, '['))
|
||||
{
|
||||
unsigned int possible_depth = get_depth(lexer);
|
||||
|
||||
if (consume_if(lexer, '['))
|
||||
{
|
||||
state->started = LONG_STRING;
|
||||
state->depth = possible_depth;
|
||||
}
|
||||
}
|
||||
|
||||
if (state->started)
|
||||
{
|
||||
lexer->result_symbol = STRING_START;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
224
gitnexus/vendor/tree-sitter-lua/src/tree_sitter/parser.h
vendored
Normal file
224
gitnexus/vendor/tree-sitter-lua/src/tree_sitter/parser.h
vendored
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
#ifndef TREE_SITTER_PARSER_H_
|
||||
#define TREE_SITTER_PARSER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ts_builtin_sym_error ((TSSymbol)-1)
|
||||
#define ts_builtin_sym_end 0
|
||||
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
|
||||
|
||||
typedef uint16_t TSStateId;
|
||||
|
||||
#ifndef TREE_SITTER_API_H_
|
||||
typedef uint16_t TSSymbol;
|
||||
typedef uint16_t TSFieldId;
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
TSFieldId field_id;
|
||||
uint8_t child_index;
|
||||
bool inherited;
|
||||
} TSFieldMapEntry;
|
||||
|
||||
typedef struct {
|
||||
uint16_t index;
|
||||
uint16_t length;
|
||||
} TSFieldMapSlice;
|
||||
|
||||
typedef struct {
|
||||
bool visible;
|
||||
bool named;
|
||||
bool supertype;
|
||||
} TSSymbolMetadata;
|
||||
|
||||
typedef struct TSLexer TSLexer;
|
||||
|
||||
struct TSLexer {
|
||||
int32_t lookahead;
|
||||
TSSymbol result_symbol;
|
||||
void (*advance)(TSLexer *, bool);
|
||||
void (*mark_end)(TSLexer *);
|
||||
uint32_t (*get_column)(TSLexer *);
|
||||
bool (*is_at_included_range_start)(const TSLexer *);
|
||||
bool (*eof)(const TSLexer *);
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TSParseActionTypeShift,
|
||||
TSParseActionTypeReduce,
|
||||
TSParseActionTypeAccept,
|
||||
TSParseActionTypeRecover,
|
||||
} TSParseActionType;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t type;
|
||||
TSStateId state;
|
||||
bool extra;
|
||||
bool repetition;
|
||||
} shift;
|
||||
struct {
|
||||
uint8_t type;
|
||||
uint8_t child_count;
|
||||
TSSymbol symbol;
|
||||
int16_t dynamic_precedence;
|
||||
uint16_t production_id;
|
||||
} reduce;
|
||||
uint8_t type;
|
||||
} TSParseAction;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
} TSLexMode;
|
||||
|
||||
typedef union {
|
||||
TSParseAction action;
|
||||
struct {
|
||||
uint8_t count;
|
||||
bool reusable;
|
||||
} entry;
|
||||
} TSParseActionEntry;
|
||||
|
||||
struct TSLanguage {
|
||||
uint32_t version;
|
||||
uint32_t symbol_count;
|
||||
uint32_t alias_count;
|
||||
uint32_t token_count;
|
||||
uint32_t external_token_count;
|
||||
uint32_t state_count;
|
||||
uint32_t large_state_count;
|
||||
uint32_t production_id_count;
|
||||
uint32_t field_count;
|
||||
uint16_t max_alias_sequence_length;
|
||||
const uint16_t *parse_table;
|
||||
const uint16_t *small_parse_table;
|
||||
const uint32_t *small_parse_table_map;
|
||||
const TSParseActionEntry *parse_actions;
|
||||
const char * const *symbol_names;
|
||||
const char * const *field_names;
|
||||
const TSFieldMapSlice *field_map_slices;
|
||||
const TSFieldMapEntry *field_map_entries;
|
||||
const TSSymbolMetadata *symbol_metadata;
|
||||
const TSSymbol *public_symbol_map;
|
||||
const uint16_t *alias_map;
|
||||
const TSSymbol *alias_sequences;
|
||||
const TSLexMode *lex_modes;
|
||||
bool (*lex_fn)(TSLexer *, TSStateId);
|
||||
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
|
||||
TSSymbol keyword_capture_token;
|
||||
struct {
|
||||
const bool *states;
|
||||
const TSSymbol *symbol_map;
|
||||
void *(*create)(void);
|
||||
void (*destroy)(void *);
|
||||
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
|
||||
unsigned (*serialize)(void *, char *);
|
||||
void (*deserialize)(void *, const char *, unsigned);
|
||||
} external_scanner;
|
||||
const TSStateId *primary_state_ids;
|
||||
};
|
||||
|
||||
/*
|
||||
* Lexer Macros
|
||||
*/
|
||||
|
||||
#define START_LEXER() \
|
||||
bool result = false; \
|
||||
bool skip = false; \
|
||||
bool eof = false; \
|
||||
int32_t lookahead; \
|
||||
goto start; \
|
||||
next_state: \
|
||||
lexer->advance(lexer, skip); \
|
||||
start: \
|
||||
skip = false; \
|
||||
lookahead = lexer->lookahead;
|
||||
|
||||
#define ADVANCE(state_value) \
|
||||
{ \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define SKIP(state_value) \
|
||||
{ \
|
||||
skip = true; \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ACCEPT_TOKEN(symbol_value) \
|
||||
result = true; \
|
||||
lexer->result_symbol = symbol_value; \
|
||||
lexer->mark_end(lexer);
|
||||
|
||||
#define END_STATE() return result;
|
||||
|
||||
/*
|
||||
* Parse Table Macros
|
||||
*/
|
||||
|
||||
#define SMALL_STATE(id) id - LARGE_STATE_COUNT
|
||||
|
||||
#define STATE(id) id
|
||||
|
||||
#define ACTIONS(id) id
|
||||
|
||||
#define SHIFT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = state_value \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_REPEAT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = state_value, \
|
||||
.repetition = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_EXTRA() \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.extra = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define REDUCE(symbol_val, child_count_val, ...) \
|
||||
{{ \
|
||||
.reduce = { \
|
||||
.type = TSParseActionTypeReduce, \
|
||||
.symbol = symbol_val, \
|
||||
.child_count = child_count_val, \
|
||||
__VA_ARGS__ \
|
||||
}, \
|
||||
}}
|
||||
|
||||
#define RECOVER() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeRecover \
|
||||
}}
|
||||
|
||||
#define ACCEPT_INPUT() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeAccept \
|
||||
}}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_PARSER_H_
|
||||
Loading…
Add table
Reference in a new issue