From 9eda1558c25da0fa5997ea9634560e38767d032b Mon Sep 17 00:00:00 2001 From: Safi Date: Mon, 6 Apr 2026 21:58:09 +0100 Subject: [PATCH] feat: Lua language support --- README.md | 2 +- graphify/detect.py | 2 +- graphify/extract.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ graphify/skill.md | 2 +- pyproject.toml | 1 + 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 896ea21e..1423070f 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Works with any mix of file types: | Type | Extensions | Extraction | |------|-----------|------------| -| Code | `.py .ts .js .go .rs .java .c .cpp .rb .cs .kt .scala .php` | AST via tree-sitter + call-graph + docstring/comment rationale | +| Code | `.py .ts .js .go .rs .java .c .cpp .rb .cs .kt .scala .php .swift .lua` | AST via tree-sitter + call-graph + docstring/comment rationale | | Docs | `.md .txt .rst` | Concepts + relationships + design rationale via Claude | | Papers | `.pdf` | Citation mining + concept extraction | | Images | `.png .jpg .webp .gif` | Claude vision - screenshots, diagrams, any language | diff --git a/graphify/detect.py b/graphify/detect.py index 3c740688..040ec2e4 100644 --- a/graphify/detect.py +++ b/graphify/detect.py @@ -16,7 +16,7 @@ class FileType(str, Enum): _MANIFEST_PATH = "graphify-out/manifest.json" -CODE_EXTENSIONS = {'.py', '.ts', '.js', '.tsx', '.go', '.rs', '.java', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php'} +CODE_EXTENSIONS = {'.py', '.ts', '.js', '.tsx', '.go', '.rs', '.java', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.toc'} DOC_EXTENSIONS = {'.md', '.txt', '.rst'} PAPER_EXTENSIONS = {'.pdf'} IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'} diff --git a/graphify/extract.py b/graphify/extract.py index e3181f5c..ccd976e6 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -545,6 +545,43 @@ _PHP_CONFIG = LanguageConfig( ) +def _import_lua(node, source: bytes, file_nid: str, stem: str, edges: list, str_path: str) -> None: + """Extract require('module') from Lua variable_declaration nodes.""" + text = _read_text(node, source) + import re + m = re.search(r"""require\s*[\('"]\s*['"]?([^'")\s]+)""", text) + if m: + module_name = m.group(1).split(".")[-1] + if module_name: + edges.append({ + "source": file_nid, + "target": module_name, + "relation": "imports", + "confidence": "EXTRACTED", + "confidence_score": 1.0, + "source_file": str_path, + "source_location": str(node.start_point[0] + 1), + "weight": 1.0, + }) + + +_LUA_CONFIG = LanguageConfig( + ts_module="tree_sitter_lua", + ts_language_fn="language", + class_types=frozenset(), + function_types=frozenset({"function_declaration"}), + import_types=frozenset({"variable_declaration"}), + call_types=frozenset({"function_call"}), + call_function_field="name", + call_accessor_node_types=frozenset({"method_index_expression"}), + call_accessor_field="name", + name_fallback_child_types=("identifier", "method_index_expression"), + body_fallback_child_types=("block",), + function_boundary_types=frozenset({"function_declaration"}), + import_handler=_import_lua, +) + + def _import_swift(node, source: bytes, file_nid: str, stem: str, edges: list, str_path: str) -> None: for child in node.children: if child.type == "identifier": @@ -1078,6 +1115,11 @@ def extract_php(path: Path) -> dict: return _extract_generic(path, _PHP_CONFIG) +def extract_lua(path: Path) -> dict: + """Extract functions, methods, require() imports, and calls from a .lua file.""" + return _extract_generic(path, _LUA_CONFIG) + + def extract_swift(path: Path) -> dict: """Extract classes, structs, protocols, functions, imports, and calls from a .swift file.""" return _extract_generic(path, _SWIFT_CONFIG) @@ -1623,6 +1665,8 @@ def extract(paths: list[Path]) -> dict: ".scala": extract_scala, ".php": extract_php, ".swift": extract_swift, + ".lua": extract_lua, + ".toc": extract_lua, } for path in paths: @@ -1665,6 +1709,7 @@ def collect_files(target: Path) -> list[Path]: "*.py", "*.js", "*.ts", "*.tsx", "*.go", "*.rs", "*.java", "*.c", "*.h", "*.cpp", "*.cc", "*.cxx", "*.hpp", "*.rb", "*.cs", "*.kt", "*.kts", "*.scala", "*.php", "*.swift", + "*.lua", "*.toc", ) results: list[Path] = [] for pattern in _EXTENSIONS: diff --git a/graphify/skill.md b/graphify/skill.md index 5229a510..f4d2145f 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -702,7 +702,7 @@ import json from pathlib import Path result = json.loads(open('.graphify_incremental.json').read()) if Path('.graphify_incremental.json').exists() else {} -code_exts = {'.py','.ts','.js','.go','.rs','.java','.cpp','.c','.rb','.swift','.kt','.cs','.scala','.php','.cc','.cxx','.hpp','.h','.kts'} +code_exts = {'.py','.ts','.js','.go','.rs','.java','.cpp','.c','.rb','.swift','.kt','.cs','.scala','.php','.cc','.cxx','.hpp','.h','.kts','.lua','.toc'} new_files = result.get('new_files', {}) all_changed = [f for files in new_files.values() for f in files] code_only = all(Path(f).suffix.lower() in code_exts for f in all_changed) diff --git a/pyproject.toml b/pyproject.toml index c4feb1f5..0c71a52a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "tree-sitter-scala", "tree-sitter-php", "tree-sitter-swift", + "tree-sitter-lua", ] [project.urls]