graphitect 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graphify/__init__.py +30 -0
- graphify/__main__.py +757 -0
- graphify/_minhash.py +107 -0
- graphify/affected.py +318 -0
- graphify/always_on/agents-md.md +12 -0
- graphify/always_on/antigravity-rules.md +14 -0
- graphify/always_on/claude-md.md +9 -0
- graphify/always_on/gemini-md.md +9 -0
- graphify/always_on/kiro-steering.md +5 -0
- graphify/always_on/vscode-instructions.md +17 -0
- graphify/analyze.py +769 -0
- graphify/benchmark.py +152 -0
- graphify/build.py +2300 -0
- graphify/cache.py +1746 -0
- graphify/callflow_html.py +2051 -0
- graphify/cargo_introspect.py +109 -0
- graphify/cli.py +4745 -0
- graphify/cluster.py +409 -0
- graphify/command-kilo.md +15 -0
- graphify/cross_repo_calls.py +216 -0
- graphify/cross_repo_types.py +75 -0
- graphify/csharp_dispatch.py +154 -0
- graphify/dedup.py +1213 -0
- graphify/detect.py +2566 -0
- graphify/diagnostics.py +406 -0
- graphify/export.py +1349 -0
- graphify/exporters/__init__.py +1 -0
- graphify/exporters/base.py +14 -0
- graphify/exporters/graphdb.py +173 -0
- graphify/exporters/html.py +637 -0
- graphify/extract.py +7856 -0
- graphify/extractors/MIGRATION.md +107 -0
- graphify/extractors/__init__.py +66 -0
- graphify/extractors/apex.py +215 -0
- graphify/extractors/base.py +85 -0
- graphify/extractors/bash.py +579 -0
- graphify/extractors/blade.py +53 -0
- graphify/extractors/commonlisp.py +540 -0
- graphify/extractors/csharp.py +448 -0
- graphify/extractors/dart.py +564 -0
- graphify/extractors/dm.py +494 -0
- graphify/extractors/elixir.py +241 -0
- graphify/extractors/engine.py +6509 -0
- graphify/extractors/fortran.py +311 -0
- graphify/extractors/go.py +527 -0
- graphify/extractors/json_config.py +240 -0
- graphify/extractors/julia.py +289 -0
- graphify/extractors/markdown.py +408 -0
- graphify/extractors/models.py +131 -0
- graphify/extractors/objc.py +566 -0
- graphify/extractors/ocaml.py +289 -0
- graphify/extractors/pascal.py +688 -0
- graphify/extractors/pascal_forms.py +196 -0
- graphify/extractors/powershell.py +522 -0
- graphify/extractors/razor.py +192 -0
- graphify/extractors/resolution.py +3584 -0
- graphify/extractors/robot.py +296 -0
- graphify/extractors/rust.py +470 -0
- graphify/extractors/sln.py +92 -0
- graphify/extractors/sql.py +720 -0
- graphify/extractors/terraform.py +181 -0
- graphify/extractors/verilog.py +329 -0
- graphify/extractors/zig.py +181 -0
- graphify/file_slice.py +246 -0
- graphify/global_graph.py +194 -0
- graphify/google_workspace.py +237 -0
- graphify/hooks.py +933 -0
- graphify/ids.py +93 -0
- graphify/ingest.py +358 -0
- graphify/install.py +2366 -0
- graphify/llm.py +3544 -0
- graphify/manifest.py +4 -0
- graphify/manifest_ingest.py +311 -0
- graphify/mcp_ingest.py +386 -0
- graphify/multigraph_compat.py +212 -0
- graphify/pascal_resolution.py +129 -0
- graphify/paths.py +436 -0
- graphify/pg_introspect.py +165 -0
- graphify/prs.py +770 -0
- graphify/querylog.py +80 -0
- graphify/reflect.py +882 -0
- graphify/report.py +346 -0
- graphify/resolver_registry.py +85 -0
- graphify/ruby_resolution.py +242 -0
- graphify/scip_ingest.py +363 -0
- graphify/security.py +460 -0
- graphify/semantic_cleanup.py +336 -0
- graphify/serve.py +2608 -0
- graphify/skill-agents.md +710 -0
- graphify/skill-aider.md +1283 -0
- graphify/skill-amp.md +710 -0
- graphify/skill-claw.md +713 -0
- graphify/skill-codex.md +710 -0
- graphify/skill-copilot.md +713 -0
- graphify/skill-devin.md +1410 -0
- graphify/skill-droid.md +710 -0
- graphify/skill-kilo.md +722 -0
- graphify/skill-kiro.md +713 -0
- graphify/skill-opencode.md +705 -0
- graphify/skill-pi.md +713 -0
- graphify/skill-trae.md +711 -0
- graphify/skill-vscode.md +709 -0
- graphify/skill-windows.md +755 -0
- graphify/skill.md +713 -0
- graphify/skills/agents/references/add-watch.md +56 -0
- graphify/skills/agents/references/exports.md +87 -0
- graphify/skills/agents/references/extraction-spec.md +70 -0
- graphify/skills/agents/references/github-and-merge.md +46 -0
- graphify/skills/agents/references/hooks.md +33 -0
- graphify/skills/agents/references/query.md +311 -0
- graphify/skills/agents/references/transcribe.md +52 -0
- graphify/skills/agents/references/update.md +210 -0
- graphify/skills/amp/references/add-watch.md +56 -0
- graphify/skills/amp/references/exports.md +87 -0
- graphify/skills/amp/references/extraction-spec.md +70 -0
- graphify/skills/amp/references/github-and-merge.md +46 -0
- graphify/skills/amp/references/hooks.md +33 -0
- graphify/skills/amp/references/query.md +311 -0
- graphify/skills/amp/references/transcribe.md +52 -0
- graphify/skills/amp/references/update.md +210 -0
- graphify/skills/claude/references/add-watch.md +56 -0
- graphify/skills/claude/references/exports.md +87 -0
- graphify/skills/claude/references/extraction-spec.md +70 -0
- graphify/skills/claude/references/github-and-merge.md +46 -0
- graphify/skills/claude/references/hooks.md +33 -0
- graphify/skills/claude/references/query.md +311 -0
- graphify/skills/claude/references/transcribe.md +52 -0
- graphify/skills/claude/references/update.md +210 -0
- graphify/skills/claw/references/add-watch.md +56 -0
- graphify/skills/claw/references/exports.md +87 -0
- graphify/skills/claw/references/extraction-spec.md +31 -0
- graphify/skills/claw/references/github-and-merge.md +46 -0
- graphify/skills/claw/references/hooks.md +33 -0
- graphify/skills/claw/references/query.md +311 -0
- graphify/skills/claw/references/transcribe.md +52 -0
- graphify/skills/claw/references/update.md +210 -0
- graphify/skills/codex/references/add-watch.md +56 -0
- graphify/skills/codex/references/exports.md +87 -0
- graphify/skills/codex/references/extraction-spec.md +31 -0
- graphify/skills/codex/references/github-and-merge.md +46 -0
- graphify/skills/codex/references/hooks.md +33 -0
- graphify/skills/codex/references/query.md +311 -0
- graphify/skills/codex/references/transcribe.md +52 -0
- graphify/skills/codex/references/update.md +210 -0
- graphify/skills/copilot/references/add-watch.md +56 -0
- graphify/skills/copilot/references/exports.md +87 -0
- graphify/skills/copilot/references/extraction-spec.md +70 -0
- graphify/skills/copilot/references/github-and-merge.md +46 -0
- graphify/skills/copilot/references/hooks.md +33 -0
- graphify/skills/copilot/references/query.md +311 -0
- graphify/skills/copilot/references/transcribe.md +52 -0
- graphify/skills/copilot/references/update.md +210 -0
- graphify/skills/droid/references/add-watch.md +56 -0
- graphify/skills/droid/references/exports.md +87 -0
- graphify/skills/droid/references/extraction-spec.md +70 -0
- graphify/skills/droid/references/github-and-merge.md +46 -0
- graphify/skills/droid/references/hooks.md +33 -0
- graphify/skills/droid/references/query.md +311 -0
- graphify/skills/droid/references/transcribe.md +52 -0
- graphify/skills/droid/references/update.md +210 -0
- graphify/skills/kilo/references/add-watch.md +56 -0
- graphify/skills/kilo/references/exports.md +87 -0
- graphify/skills/kilo/references/extraction-spec.md +70 -0
- graphify/skills/kilo/references/github-and-merge.md +46 -0
- graphify/skills/kilo/references/hooks.md +33 -0
- graphify/skills/kilo/references/query.md +311 -0
- graphify/skills/kilo/references/transcribe.md +52 -0
- graphify/skills/kilo/references/update.md +210 -0
- graphify/skills/kiro/references/add-watch.md +56 -0
- graphify/skills/kiro/references/exports.md +87 -0
- graphify/skills/kiro/references/extraction-spec.md +31 -0
- graphify/skills/kiro/references/github-and-merge.md +46 -0
- graphify/skills/kiro/references/hooks.md +33 -0
- graphify/skills/kiro/references/query.md +311 -0
- graphify/skills/kiro/references/transcribe.md +52 -0
- graphify/skills/kiro/references/update.md +210 -0
- graphify/skills/opencode/references/add-watch.md +56 -0
- graphify/skills/opencode/references/exports.md +87 -0
- graphify/skills/opencode/references/extraction-spec.md +70 -0
- graphify/skills/opencode/references/github-and-merge.md +46 -0
- graphify/skills/opencode/references/hooks.md +33 -0
- graphify/skills/opencode/references/query.md +311 -0
- graphify/skills/opencode/references/transcribe.md +52 -0
- graphify/skills/opencode/references/update.md +210 -0
- graphify/skills/pi/references/add-watch.md +56 -0
- graphify/skills/pi/references/exports.md +87 -0
- graphify/skills/pi/references/extraction-spec.md +31 -0
- graphify/skills/pi/references/github-and-merge.md +46 -0
- graphify/skills/pi/references/hooks.md +33 -0
- graphify/skills/pi/references/query.md +311 -0
- graphify/skills/pi/references/transcribe.md +52 -0
- graphify/skills/pi/references/update.md +210 -0
- graphify/skills/trae/references/add-watch.md +56 -0
- graphify/skills/trae/references/exports.md +87 -0
- graphify/skills/trae/references/extraction-spec.md +70 -0
- graphify/skills/trae/references/github-and-merge.md +46 -0
- graphify/skills/trae/references/hooks.md +35 -0
- graphify/skills/trae/references/query.md +311 -0
- graphify/skills/trae/references/transcribe.md +52 -0
- graphify/skills/trae/references/update.md +210 -0
- graphify/skills/vscode/references/add-watch.md +56 -0
- graphify/skills/vscode/references/exports.md +87 -0
- graphify/skills/vscode/references/extraction-spec.md +70 -0
- graphify/skills/vscode/references/github-and-merge.md +46 -0
- graphify/skills/vscode/references/hooks.md +33 -0
- graphify/skills/vscode/references/query.md +311 -0
- graphify/skills/vscode/references/transcribe.md +52 -0
- graphify/skills/vscode/references/update.md +210 -0
- graphify/skills/windows/references/add-watch.md +56 -0
- graphify/skills/windows/references/exports.md +87 -0
- graphify/skills/windows/references/extraction-spec.md +70 -0
- graphify/skills/windows/references/github-and-merge.md +46 -0
- graphify/skills/windows/references/hooks.md +33 -0
- graphify/skills/windows/references/query.md +311 -0
- graphify/skills/windows/references/transcribe.md +52 -0
- graphify/skills/windows/references/update.md +210 -0
- graphify/symbol_resolution.py +556 -0
- graphify/transcribe.py +186 -0
- graphify/tree_html.py +603 -0
- graphify/validate.py +95 -0
- graphify/watch.py +2280 -0
- graphify/wiki.py +405 -0
- graphitect/__init__.py +28 -0
- graphitect/__main__.py +4 -0
- graphitect/_vendor/__init__.py +2 -0
- graphitect/_vendor/archify/LICENSE +22 -0
- graphitect/_vendor/archify/SKILL.md +137 -0
- graphitect/_vendor/archify/THIRD_PARTY_NOTICES.md +69 -0
- graphitect/_vendor/archify/assets/JetBrainsMono-OFL.txt +93 -0
- graphitect/_vendor/archify/assets/template.html +14935 -0
- graphitect/_vendor/archify/bin/archify.mjs +2091 -0
- graphitect/_vendor/archify/bin/open-artifact.mjs +86 -0
- graphitect/_vendor/archify/bin/preview.mjs +653 -0
- graphitect/_vendor/archify/bin/visual-check.mjs +829 -0
- graphitect/_vendor/archify/brand-marks/README.md +31 -0
- graphitect/_vendor/archify/brand-marks/catalog.json +131 -0
- graphitect/_vendor/archify/delta/architecture-delta.mjs +1221 -0
- graphitect/_vendor/archify/examples/agent-run.lifecycle.json +60 -0
- graphitect/_vendor/archify/examples/agent-tool-call.workflow.json +94 -0
- graphitect/_vendor/archify/examples/async-job-roundtrip.sequence.json +61 -0
- graphitect/_vendor/archify/examples/brand-aware-delivery.architecture.json +47 -0
- graphitect/_vendor/archify/examples/cache-miss-request.sequence.json +82 -0
- graphitect/_vendor/archify/examples/checkout-platform.base.architecture.json +31 -0
- graphitect/_vendor/archify/examples/checkout-platform.head.architecture.json +31 -0
- graphitect/_vendor/archify/examples/dataflow-product-analytics.html +15045 -0
- graphitect/_vendor/archify/examples/deployment-release.lifecycle.json +49 -0
- graphitect/_vendor/archify/examples/event-stream.dataflow.json +57 -0
- graphitect/_vendor/archify/examples/incident-response.workflow.json +64 -0
- graphitect/_vendor/archify/examples/lifecycle-agent-run.html +14980 -0
- graphitect/_vendor/archify/examples/product-analytics.dataflow.json +76 -0
- graphitect/_vendor/archify/examples/production-deployment.architecture.json +71 -0
- graphitect/_vendor/archify/examples/release-delivery.workflow.json +62 -0
- graphitect/_vendor/archify/examples/sequence-cache-miss-request.html +15060 -0
- graphitect/_vendor/archify/examples/web-app-rendered.html +15009 -0
- graphitect/_vendor/archify/examples/web-app.architecture.json +46 -0
- graphitect/_vendor/archify/examples/workflow-agent-tool-call-rendered.html +15051 -0
- graphitect/_vendor/archify/migrations/workflow-v2.mjs +279 -0
- graphitect/_vendor/archify/package-lock.json +149 -0
- graphitect/_vendor/archify/package.json +39 -0
- graphitect/_vendor/archify/recipes/scenarios.mjs +391 -0
- graphitect/_vendor/archify/references/authoring-contract.md +243 -0
- graphitect/_vendor/archify/references/brand-marks.md +65 -0
- graphitect/_vendor/archify/references/delivery-contract.md +120 -0
- graphitect/_vendor/archify/references/viewer-runtime.md +45 -0
- graphitect/_vendor/archify/renderers/architecture/grid.mjs +62 -0
- graphitect/_vendor/archify/renderers/architecture/render-architecture.mjs +1078 -0
- graphitect/_vendor/archify/renderers/dataflow/README.md +104 -0
- graphitect/_vendor/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- graphitect/_vendor/archify/renderers/lifecycle/README.md +115 -0
- graphitect/_vendor/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- graphitect/_vendor/archify/renderers/sequence/README.md +114 -0
- graphitect/_vendor/archify/renderers/sequence/render-sequence.mjs +464 -0
- graphitect/_vendor/archify/renderers/shared/brand-marks.mjs +563 -0
- graphitect/_vendor/archify/renderers/shared/cli.mjs +218 -0
- graphitect/_vendor/archify/renderers/shared/desktop-readability.mjs +26 -0
- graphitect/_vendor/archify/renderers/shared/diagnostics.mjs +127 -0
- graphitect/_vendor/archify/renderers/shared/engineering-profiles.mjs +157 -0
- graphitect/_vendor/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- graphitect/_vendor/archify/renderers/shared/generated-validators.mjs +13 -0
- graphitect/_vendor/archify/renderers/shared/geometry.mjs +1423 -0
- graphitect/_vendor/archify/renderers/shared/i18n.mjs +595 -0
- graphitect/_vendor/archify/renderers/shared/layout-report.mjs +40 -0
- graphitect/_vendor/archify/renderers/shared/legend.mjs +217 -0
- graphitect/_vendor/archify/renderers/shared/output-path.mjs +340 -0
- graphitect/_vendor/archify/renderers/shared/repository-evidence.mjs +238 -0
- graphitect/_vendor/archify/renderers/shared/repository-location.mjs +58 -0
- graphitect/_vendor/archify/renderers/shared/text-fit.mjs +49 -0
- graphitect/_vendor/archify/renderers/shared/utils.mjs +232 -0
- graphitect/_vendor/archify/renderers/shared/validator.mjs +86 -0
- graphitect/_vendor/archify/renderers/workflow/README.md +223 -0
- graphitect/_vendor/archify/renderers/workflow/render-workflow.mjs +35 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- graphitect/_vendor/archify/schemas/README.md +211 -0
- graphitect/_vendor/archify/schemas/architecture.schema.json +178 -0
- graphitect/_vendor/archify/schemas/common.schema.json +115 -0
- graphitect/_vendor/archify/schemas/dataflow.schema.json +243 -0
- graphitect/_vendor/archify/schemas/lifecycle.schema.json +266 -0
- graphitect/_vendor/archify/schemas/sequence.schema.json +223 -0
- graphitect/_vendor/archify/schemas/workflow.schema.json +428 -0
- graphitect/_vendor/archify/scripts/check-render-output.mjs +836 -0
- graphitect/_vendor/archify/scripts/check-update.mjs +1667 -0
- graphitect/_vendor/archify/scripts/generate-brand-marks.mjs +141 -0
- graphitect/_vendor/archify/scripts/generate-validators.mjs +66 -0
- graphitect/_vendor/archify/scripts/render-examples.mjs +26 -0
- graphitect/_vendor/archify/scripts/update-contract.mjs +182 -0
- graphitect/_vendor/archify/skill-release.json +10 -0
- graphitect/cli.py +981 -0
- graphitect/deliver/__init__.py +5 -0
- graphitect/deliver/archify_adapter.py +1877 -0
- graphitect/deliver/archify_ir.py +160 -0
- graphitect/deliver/archify_repair.py +135 -0
- graphitect/deliver/doc_compiler.py +916 -0
- graphitect/ground/__init__.py +5 -0
- graphitect/ground/describe_source.py +27 -0
- graphitect/ground/fullread_source.py +56 -0
- graphitect/ground/graphify_source.py +107 -0
- graphitect/models.py +118 -0
- graphitect/skill/SKILL.md +80 -0
- graphitect/skill/agents/openai.yaml +4 -0
- graphitect/synthesize/__init__.py +5 -0
- graphitect/synthesize/engine.py +281 -0
- graphitect/synthesize/llm_backend.py +331 -0
- graphitect/synthesize/questions.py +139 -0
- graphitect/synthesize/rubric.py +104 -0
- graphitect-0.2.0.dist-info/METADATA +284 -0
- graphitect-0.2.0.dist-info/RECORD +336 -0
- graphitect-0.2.0.dist-info/WHEEL +5 -0
- graphitect-0.2.0.dist-info/entry_points.txt +2 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE +21 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-ARCHIFY-MIT +22 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-APACHE-2.0 +202 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-MIT +21 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-ARCHIFY-THIRD-PARTY.md +69 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-GRAPHIFY +8 -0
- graphitect-0.2.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""Common Lisp extractor for .lisp/.cl/.lsp/.asd, backed by tree-sitter-commonlisp."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import warnings
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from graphify.extractors.base import _file_stem, _make_id
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Standard CL definer forms that introduce data/type/variable bindings
|
|
11
|
+
# (not callable, no body to walk for calls)
|
|
12
|
+
_CL_DATA_DEFINERS = frozenset({
|
|
13
|
+
"defstruct", "deftype", "define-condition",
|
|
14
|
+
"defvar", "defparameter", "defconstant",
|
|
15
|
+
"define-symbol-macro",
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
# Subset of data definers that take a VALUE as their second argument, which
|
|
19
|
+
# may itself be a string literal. For these, we must not mistake the value
|
|
20
|
+
# for a docstring.
|
|
21
|
+
_CL_VALUE_DEFINERS = frozenset({"defvar", "defparameter", "defconstant"})
|
|
22
|
+
|
|
23
|
+
# Standard CL macro-style definers
|
|
24
|
+
_CL_MACRO_DEFINERS = frozenset({
|
|
25
|
+
"define-modify-macro", "define-compiler-macro",
|
|
26
|
+
"define-setf-expander", "defsetf",
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
# Symbols that start with "def" but are NOT definitions (denylist for the
|
|
30
|
+
# def-prefix heuristic that catches custom definers like definline-maybe)
|
|
31
|
+
_CL_NOT_DEFINERS = frozenset({
|
|
32
|
+
"default", "default-value", "defaults", "define",
|
|
33
|
+
"defer", "deferred", "deflate", "deftest",
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
# CL special forms / macros that should not count as "calls" in the graph
|
|
37
|
+
_CL_SPECIAL_FORMS = frozenset({
|
|
38
|
+
"let", "let*", "flet", "labels", "macrolet",
|
|
39
|
+
"if", "when", "unless", "cond", "case", "ecase", "typecase", "etypecase",
|
|
40
|
+
"progn", "prog1", "prog2", "block", "return-from", "tagbody", "go",
|
|
41
|
+
"lambda", "function", "funcall", "apply",
|
|
42
|
+
"setf", "setq", "psetf", "psetq", "incf", "decf", "push", "pop",
|
|
43
|
+
"loop", "do", "do*", "dolist", "dotimes", "map", "mapcar", "mapc",
|
|
44
|
+
"format", "print", "princ", "prin1", "write", "terpri",
|
|
45
|
+
"and", "or", "not", "null",
|
|
46
|
+
"values", "multiple-value-bind", "multiple-value-setq",
|
|
47
|
+
"declare", "the", "locally", "eval-when",
|
|
48
|
+
"quote", "backquote",
|
|
49
|
+
"error", "signal", "warn", "assert", "check-type",
|
|
50
|
+
"handler-case", "handler-bind", "restart-case", "ignore-errors",
|
|
51
|
+
"unwind-protect", "catch", "throw",
|
|
52
|
+
"with-open-file", "with-output-to-string", "with-input-from-string",
|
|
53
|
+
"with-slots", "with-accessors",
|
|
54
|
+
"defvar", "defparameter", "defconstant",
|
|
55
|
+
"in-package", "require", "provide", "use-package",
|
|
56
|
+
"t", "nil",
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def extract_commonlisp(path: Path) -> dict:
|
|
61
|
+
"""Extract packages, classes, functions, methods, macros, and calls from a Common Lisp file."""
|
|
62
|
+
try:
|
|
63
|
+
import tree_sitter_commonlisp as tscl
|
|
64
|
+
from tree_sitter import Language, Parser
|
|
65
|
+
except ImportError:
|
|
66
|
+
return {"nodes": [], "edges": [], "error": "tree-sitter-commonlisp not installed"}
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# tree-sitter-commonlisp 0.4.1 (latest) ships the old binding that returns
|
|
70
|
+
# an int pointer from language(); tree_sitter.Language only accepts that
|
|
71
|
+
# int via a deprecated path. Silence that one warning at the call site
|
|
72
|
+
# until the grammar ships a PyCapsule binding.
|
|
73
|
+
with warnings.catch_warnings():
|
|
74
|
+
warnings.filterwarnings(
|
|
75
|
+
"ignore",
|
|
76
|
+
message="int argument support is deprecated",
|
|
77
|
+
category=DeprecationWarning,
|
|
78
|
+
)
|
|
79
|
+
language = Language(tscl.language())
|
|
80
|
+
parser = Parser(language)
|
|
81
|
+
source = path.read_bytes()
|
|
82
|
+
tree = parser.parse(source)
|
|
83
|
+
root = tree.root_node
|
|
84
|
+
except Exception as e:
|
|
85
|
+
return {"nodes": [], "edges": [], "error": str(e)}
|
|
86
|
+
|
|
87
|
+
# Path-qualified, not the bare `path.stem`: same-named .lisp files in
|
|
88
|
+
# different directories must not collide (#1504). Pre-collapsed through
|
|
89
|
+
# `_make_id` because `_cl_id` would otherwise map the `/` separators to
|
|
90
|
+
# `_slash` via _CL_CHAR_MAP.
|
|
91
|
+
stem = _make_id(_file_stem(path))
|
|
92
|
+
str_path = str(path)
|
|
93
|
+
nodes: list[dict] = []
|
|
94
|
+
edges: list[dict] = []
|
|
95
|
+
seen_ids: set[str] = set()
|
|
96
|
+
function_bodies: list[tuple[str, object]] = [] # (nid, body_nodes)
|
|
97
|
+
current_package: str | None = None
|
|
98
|
+
|
|
99
|
+
def _text(node) -> str:
|
|
100
|
+
return source[node.start_byte:node.end_byte].decode("utf-8", errors="replace")
|
|
101
|
+
|
|
102
|
+
# CL symbols can contain operator chars like = < > ? ! + * / that the
|
|
103
|
+
# generic _make_id strips. Map them to readable suffixes so upi=, upi<,
|
|
104
|
+
# upi> become distinct ids.
|
|
105
|
+
_CL_CHAR_MAP = {
|
|
106
|
+
'=': '_eq', '<': '_lt', '>': '_gt', '?': '_p', '!': '_bang',
|
|
107
|
+
'+': '_plus', '*': '_star', '/': '_slash', '%': '_pct', '&': '_amp',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
def _cl_id(*parts: str) -> str:
|
|
111
|
+
normalized = []
|
|
112
|
+
for p in parts:
|
|
113
|
+
normalized.append(''.join(_CL_CHAR_MAP.get(c, c) for c in p))
|
|
114
|
+
return _make_id(*normalized)
|
|
115
|
+
|
|
116
|
+
def add_node(nid: str, label: str, line: int, file_type: str = "code") -> None:
|
|
117
|
+
if nid not in seen_ids:
|
|
118
|
+
seen_ids.add(nid)
|
|
119
|
+
nodes.append({
|
|
120
|
+
"id": nid,
|
|
121
|
+
"label": label,
|
|
122
|
+
"file_type": file_type,
|
|
123
|
+
"source_file": str_path,
|
|
124
|
+
"source_location": f"L{line}",
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
def add_edge(src: str, tgt: str, relation: str, line: int,
|
|
128
|
+
confidence: str = "EXTRACTED", weight: float = 1.0) -> None:
|
|
129
|
+
edges.append({
|
|
130
|
+
"source": src,
|
|
131
|
+
"target": tgt,
|
|
132
|
+
"relation": relation,
|
|
133
|
+
"confidence": confidence,
|
|
134
|
+
"source_file": str_path,
|
|
135
|
+
"source_location": f"L{line}",
|
|
136
|
+
"weight": weight,
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
file_nid = _cl_id(stem)
|
|
140
|
+
add_node(file_nid, path.name, 1)
|
|
141
|
+
|
|
142
|
+
def add_import_stub(mod_name: str) -> str:
|
|
143
|
+
"""Mint a SOURCELESS stub for an imported package so its `imports` edge
|
|
144
|
+
has a real target instead of dangling. The corpus rewire collapses it
|
|
145
|
+
onto a unique in-corpus `defpackage` of the same name; an external one
|
|
146
|
+
(cl, alexandria) stays a sourceless leaf. `origin_file` is stripped
|
|
147
|
+
before persist (no #1899 leak); a sourced bare stub would salt the id."""
|
|
148
|
+
nid = _cl_id(mod_name)
|
|
149
|
+
if nid not in seen_ids:
|
|
150
|
+
seen_ids.add(nid)
|
|
151
|
+
nodes.append({
|
|
152
|
+
"id": nid,
|
|
153
|
+
"label": mod_name,
|
|
154
|
+
"file_type": "code",
|
|
155
|
+
"source_file": "",
|
|
156
|
+
"source_location": "",
|
|
157
|
+
"origin_file": str_path,
|
|
158
|
+
})
|
|
159
|
+
return nid
|
|
160
|
+
|
|
161
|
+
def ensure_class_ref(name: str, line: int) -> str:
|
|
162
|
+
"""Resolve a superclass name to a node id.
|
|
163
|
+
|
|
164
|
+
If the class is defined in THIS file, bind to its local node. Otherwise
|
|
165
|
+
mint a SOURCELESS stub (like `add_import_stub`) so the corpus-level
|
|
166
|
+
rewire can collapse it onto the real `defclass` in another file. Without
|
|
167
|
+
this, a cross-file superclass had no matching node and its `inherits`
|
|
168
|
+
edge was pruned by the dangling-edge filter — silently erasing every
|
|
169
|
+
inheritance edge whose parent lives in a different file."""
|
|
170
|
+
local = _cl_id(stem, name)
|
|
171
|
+
if local in seen_ids:
|
|
172
|
+
return local
|
|
173
|
+
stub = _cl_id(name)
|
|
174
|
+
if stub not in seen_ids:
|
|
175
|
+
seen_ids.add(stub)
|
|
176
|
+
nodes.append({
|
|
177
|
+
"id": stub,
|
|
178
|
+
"label": name,
|
|
179
|
+
"file_type": "code",
|
|
180
|
+
"source_file": "",
|
|
181
|
+
"source_location": "",
|
|
182
|
+
"origin_file": str_path,
|
|
183
|
+
})
|
|
184
|
+
return stub
|
|
185
|
+
|
|
186
|
+
def _first_sym(node) -> str | None:
|
|
187
|
+
"""Get the first sym_lit text from a list_lit's children."""
|
|
188
|
+
for child in node.children:
|
|
189
|
+
if child.type == "sym_lit":
|
|
190
|
+
return _text(child)
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def _kwd_text(node) -> str:
|
|
194
|
+
"""Extract the symbol name from a kwd_lit, stripping the leading colon."""
|
|
195
|
+
t = _text(node)
|
|
196
|
+
return t.lstrip(":").lstrip("#:")
|
|
197
|
+
|
|
198
|
+
def _handle_defpackage(node) -> None:
|
|
199
|
+
nonlocal current_package
|
|
200
|
+
children = node.children
|
|
201
|
+
pkg_name = None
|
|
202
|
+
for child in children:
|
|
203
|
+
if child.type == "kwd_lit" and pkg_name is None:
|
|
204
|
+
pkg_name = _kwd_text(child)
|
|
205
|
+
break
|
|
206
|
+
if child.type == "sym_lit" and _text(child) != "defpackage":
|
|
207
|
+
pkg_name = _text(child)
|
|
208
|
+
break
|
|
209
|
+
if not pkg_name:
|
|
210
|
+
return
|
|
211
|
+
current_package = pkg_name
|
|
212
|
+
pkg_nid = _cl_id(stem, pkg_name)
|
|
213
|
+
line = node.start_point[0] + 1
|
|
214
|
+
add_node(pkg_nid, pkg_name, line)
|
|
215
|
+
add_edge(file_nid, pkg_nid, "contains", line)
|
|
216
|
+
|
|
217
|
+
# Extract :use clauses as imports
|
|
218
|
+
for child in children:
|
|
219
|
+
if child.type == "list_lit":
|
|
220
|
+
first = _first_sym(child)
|
|
221
|
+
if first is None:
|
|
222
|
+
# Could be (:use ...) with kwd_lit
|
|
223
|
+
for gc in child.children:
|
|
224
|
+
if gc.type == "kwd_lit" and _kwd_text(gc) == "use":
|
|
225
|
+
for uc in child.children:
|
|
226
|
+
if uc.type == "kwd_lit" and uc != gc:
|
|
227
|
+
mod_name = _kwd_text(uc)
|
|
228
|
+
if mod_name != "use":
|
|
229
|
+
tgt_nid = add_import_stub(mod_name)
|
|
230
|
+
add_edge(pkg_nid, tgt_nid, "imports",
|
|
231
|
+
child.start_point[0] + 1)
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
def _handle_defclass(node) -> None:
|
|
235
|
+
children = node.children
|
|
236
|
+
# Find class name: second sym_lit after "defclass"
|
|
237
|
+
sym_lits = [c for c in children if c.type == "sym_lit"]
|
|
238
|
+
if len(sym_lits) < 2:
|
|
239
|
+
return
|
|
240
|
+
class_name = _text(sym_lits[1])
|
|
241
|
+
line = node.start_point[0] + 1
|
|
242
|
+
class_nid = _cl_id(stem, class_name)
|
|
243
|
+
add_node(class_nid, class_name, line)
|
|
244
|
+
|
|
245
|
+
parent_nid = file_nid
|
|
246
|
+
if current_package:
|
|
247
|
+
pkg_nid = _cl_id(stem, current_package)
|
|
248
|
+
if pkg_nid in seen_ids:
|
|
249
|
+
parent_nid = pkg_nid
|
|
250
|
+
add_edge(parent_nid, class_nid, "contains", line)
|
|
251
|
+
|
|
252
|
+
# Superclasses (third element, a list)
|
|
253
|
+
list_lits = [c for c in children if c.type == "list_lit"]
|
|
254
|
+
if list_lits:
|
|
255
|
+
superclass_list = list_lits[0]
|
|
256
|
+
for sc in superclass_list.children:
|
|
257
|
+
if sc.type == "sym_lit":
|
|
258
|
+
sc_name = _text(sc)
|
|
259
|
+
sc_nid = ensure_class_ref(sc_name, superclass_list.start_point[0] + 1)
|
|
260
|
+
add_edge(class_nid, sc_nid, "inherits",
|
|
261
|
+
superclass_list.start_point[0] + 1)
|
|
262
|
+
|
|
263
|
+
def _handle_defun_node(node) -> None:
|
|
264
|
+
"""Handle a defun AST node (covers defun, defmethod, defgeneric, defmacro)."""
|
|
265
|
+
header = None
|
|
266
|
+
for child in node.children:
|
|
267
|
+
if child.type == "defun_header":
|
|
268
|
+
header = child
|
|
269
|
+
break
|
|
270
|
+
if not header:
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
# Determine keyword type
|
|
274
|
+
keyword_type = "defun"
|
|
275
|
+
func_name = None
|
|
276
|
+
for child in header.children:
|
|
277
|
+
if child.type == "defun_keyword":
|
|
278
|
+
for kc in child.children:
|
|
279
|
+
if kc.type in ("defun", "defmethod", "defgeneric", "defmacro"):
|
|
280
|
+
keyword_type = kc.type
|
|
281
|
+
break
|
|
282
|
+
if child.type == "sym_lit" and func_name is None:
|
|
283
|
+
func_name = _text(child)
|
|
284
|
+
|
|
285
|
+
if not func_name:
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
line = node.start_point[0] + 1
|
|
289
|
+
func_nid = _cl_id(stem, func_name)
|
|
290
|
+
|
|
291
|
+
if keyword_type == "defmethod":
|
|
292
|
+
label = f".{func_name}()"
|
|
293
|
+
elif keyword_type == "defmacro":
|
|
294
|
+
label = f"{func_name} (macro)"
|
|
295
|
+
elif keyword_type == "defgeneric":
|
|
296
|
+
label = f"{func_name} (generic)"
|
|
297
|
+
else:
|
|
298
|
+
label = f"{func_name}()"
|
|
299
|
+
|
|
300
|
+
add_node(func_nid, label, line)
|
|
301
|
+
|
|
302
|
+
parent_nid = file_nid
|
|
303
|
+
if current_package:
|
|
304
|
+
pkg_nid = _cl_id(stem, current_package)
|
|
305
|
+
if pkg_nid in seen_ids:
|
|
306
|
+
parent_nid = pkg_nid
|
|
307
|
+
if keyword_type == "defmethod":
|
|
308
|
+
add_edge(parent_nid, func_nid, "method", line)
|
|
309
|
+
else:
|
|
310
|
+
add_edge(parent_nid, func_nid, "contains", line)
|
|
311
|
+
|
|
312
|
+
# Method specializers: (defmethod name ((param class) ...))
|
|
313
|
+
if keyword_type == "defmethod":
|
|
314
|
+
for child in header.children:
|
|
315
|
+
if child.type == "list_lit":
|
|
316
|
+
# This is the parameter list
|
|
317
|
+
for param in child.children:
|
|
318
|
+
if param.type == "list_lit":
|
|
319
|
+
syms = [c for c in param.children if c.type == "sym_lit"]
|
|
320
|
+
if len(syms) >= 2:
|
|
321
|
+
specializer_name = _text(syms[1])
|
|
322
|
+
spec_nid = _cl_id(stem, specializer_name)
|
|
323
|
+
add_edge(func_nid, spec_nid, "specializes",
|
|
324
|
+
param.start_point[0] + 1)
|
|
325
|
+
break
|
|
326
|
+
|
|
327
|
+
# Docstring
|
|
328
|
+
for child in node.children:
|
|
329
|
+
if child.type == "str_lit":
|
|
330
|
+
doc_text = _text(child).strip('"')
|
|
331
|
+
if doc_text:
|
|
332
|
+
doc_nid = _cl_id(func_nid, "rationale")
|
|
333
|
+
add_node(doc_nid, doc_text[:120], child.start_point[0] + 1, file_type="rationale")
|
|
334
|
+
add_edge(doc_nid, func_nid, "rationale_for",
|
|
335
|
+
child.start_point[0] + 1)
|
|
336
|
+
break
|
|
337
|
+
|
|
338
|
+
# Collect body for call extraction
|
|
339
|
+
body_nodes = [c for c in node.children
|
|
340
|
+
if c.type not in ("(", ")", "defun_header", "str_lit")]
|
|
341
|
+
if body_nodes:
|
|
342
|
+
function_bodies.append((func_nid, body_nodes))
|
|
343
|
+
|
|
344
|
+
def _extract_def_name(node, def_keyword: str) -> str | None:
|
|
345
|
+
"""Get the name being defined. May be a sym_lit or a list_lit whose
|
|
346
|
+
first sym_lit is the name (e.g. (defstruct (foo :conc-name "BAR-") ...))."""
|
|
347
|
+
seen_keyword = False
|
|
348
|
+
for child in node.children:
|
|
349
|
+
if not seen_keyword:
|
|
350
|
+
if child.type == "sym_lit" and _text(child).lower() == def_keyword:
|
|
351
|
+
seen_keyword = True
|
|
352
|
+
continue
|
|
353
|
+
if child.type == "sym_lit":
|
|
354
|
+
return _text(child)
|
|
355
|
+
if child.type == "list_lit":
|
|
356
|
+
for gc in child.children:
|
|
357
|
+
if gc.type == "sym_lit":
|
|
358
|
+
return _text(gc)
|
|
359
|
+
return None
|
|
360
|
+
return None
|
|
361
|
+
|
|
362
|
+
def _collect_def_body(node, def_keyword: str) -> list:
|
|
363
|
+
"""Collect body forms from (DEFKEYWORD name (params) [doc] body...).
|
|
364
|
+
Skips the def keyword, the name, the params list (if present), and
|
|
365
|
+
a leading docstring str_lit."""
|
|
366
|
+
children = [c for c in node.children if c.type not in ("(", ")")]
|
|
367
|
+
idx = 0
|
|
368
|
+
# Skip def keyword
|
|
369
|
+
if idx < len(children) and children[idx].type == "sym_lit" \
|
|
370
|
+
and _text(children[idx]).lower() == def_keyword:
|
|
371
|
+
idx += 1
|
|
372
|
+
# Skip name (sym_lit or list_lit)
|
|
373
|
+
if idx < len(children) and children[idx].type in ("sym_lit", "list_lit"):
|
|
374
|
+
idx += 1
|
|
375
|
+
# Skip params list (the next list_lit, if any)
|
|
376
|
+
if idx < len(children) and children[idx].type == "list_lit":
|
|
377
|
+
idx += 1
|
|
378
|
+
# Skip leading docstring
|
|
379
|
+
if idx < len(children) and children[idx].type == "str_lit":
|
|
380
|
+
idx += 1
|
|
381
|
+
return children[idx:]
|
|
382
|
+
|
|
383
|
+
def _handle_def_form(node, def_keyword: str) -> None:
|
|
384
|
+
"""Handle a generic (DEFKEYWORD name ...) form. Covers standard CL
|
|
385
|
+
definers (defstruct, deftype, defvar, define-condition, etc.) and
|
|
386
|
+
custom def-prefixed macros (definline, definline-maybe, etc.)."""
|
|
387
|
+
name = _extract_def_name(node, def_keyword)
|
|
388
|
+
if not name:
|
|
389
|
+
return
|
|
390
|
+
line = node.start_point[0] + 1
|
|
391
|
+
nid = _cl_id(stem, name)
|
|
392
|
+
|
|
393
|
+
kw = def_keyword.lower()
|
|
394
|
+
if kw in _CL_DATA_DEFINERS:
|
|
395
|
+
label = name
|
|
396
|
+
is_callable = False
|
|
397
|
+
elif kw in _CL_MACRO_DEFINERS or "macro" in kw:
|
|
398
|
+
label = f"{name} (macro)"
|
|
399
|
+
is_callable = True
|
|
400
|
+
else:
|
|
401
|
+
# Custom def-prefixed: assume function-like
|
|
402
|
+
label = f"{name}()"
|
|
403
|
+
is_callable = True
|
|
404
|
+
|
|
405
|
+
add_node(nid, label, line)
|
|
406
|
+
|
|
407
|
+
parent_nid = file_nid
|
|
408
|
+
if current_package:
|
|
409
|
+
pkg_nid = _cl_id(stem, current_package)
|
|
410
|
+
if pkg_nid in seen_ids:
|
|
411
|
+
parent_nid = pkg_nid
|
|
412
|
+
add_edge(parent_nid, nid, "contains", line)
|
|
413
|
+
|
|
414
|
+
# Docstring. Skip for defvar/defparameter/defconstant — their second
|
|
415
|
+
# argument is a value (possibly a string literal) which would be
|
|
416
|
+
# wrongly captured as a docstring.
|
|
417
|
+
if kw not in _CL_VALUE_DEFINERS:
|
|
418
|
+
for child in node.children:
|
|
419
|
+
if child.type == "str_lit":
|
|
420
|
+
doc_text = _text(child).strip('"')
|
|
421
|
+
if doc_text:
|
|
422
|
+
doc_nid = _cl_id(nid, "rationale")
|
|
423
|
+
add_node(doc_nid, doc_text[:120], child.start_point[0] + 1, file_type="rationale")
|
|
424
|
+
add_edge(doc_nid, nid, "rationale_for",
|
|
425
|
+
child.start_point[0] + 1)
|
|
426
|
+
break
|
|
427
|
+
|
|
428
|
+
if is_callable:
|
|
429
|
+
body_nodes = _collect_def_body(node, def_keyword)
|
|
430
|
+
if body_nodes:
|
|
431
|
+
function_bodies.append((nid, body_nodes))
|
|
432
|
+
|
|
433
|
+
def _is_def_prefixed(sym: str) -> bool:
|
|
434
|
+
"""Heuristic: does this symbol look like a definition macro?"""
|
|
435
|
+
if sym in _CL_NOT_DEFINERS:
|
|
436
|
+
return False
|
|
437
|
+
return sym.startswith("def") or sym.startswith("define-")
|
|
438
|
+
|
|
439
|
+
def _process_form(top) -> bool:
|
|
440
|
+
"""Dispatch on a single list_lit form. Returns True if it was
|
|
441
|
+
recognized as a definition or package directive (caller must NOT
|
|
442
|
+
recurse into it). Returns False if unrecognized, so the caller can
|
|
443
|
+
recurse to find defs nested inside wrapper macros like
|
|
444
|
+
(optimizing ...), (eval-when ...), (progn ...), etc."""
|
|
445
|
+
nonlocal current_package
|
|
446
|
+
|
|
447
|
+
# Check for defun node type inside list_lit
|
|
448
|
+
for child in top.children:
|
|
449
|
+
if child.type == "defun":
|
|
450
|
+
_handle_defun_node(child)
|
|
451
|
+
return True
|
|
452
|
+
|
|
453
|
+
first = _first_sym(top)
|
|
454
|
+
if not first:
|
|
455
|
+
return False
|
|
456
|
+
first_lower = first.lower()
|
|
457
|
+
|
|
458
|
+
if first_lower == "defpackage":
|
|
459
|
+
_handle_defpackage(top)
|
|
460
|
+
return True
|
|
461
|
+
if first_lower == "in-package":
|
|
462
|
+
for child in top.children:
|
|
463
|
+
if child.type == "kwd_lit":
|
|
464
|
+
current_package = _kwd_text(child)
|
|
465
|
+
break
|
|
466
|
+
if child.type == "sym_lit" and _text(child) != "in-package":
|
|
467
|
+
current_package = _text(child)
|
|
468
|
+
break
|
|
469
|
+
return True
|
|
470
|
+
if first_lower == "defclass":
|
|
471
|
+
_handle_defclass(top)
|
|
472
|
+
return True
|
|
473
|
+
if first_lower in ("require", "ql:quickload"):
|
|
474
|
+
for child in top.children:
|
|
475
|
+
if child.type in ("kwd_lit", "str_lit"):
|
|
476
|
+
mod_name = _kwd_text(child) if child.type == "kwd_lit" else _text(child).strip('"')
|
|
477
|
+
if mod_name:
|
|
478
|
+
tgt_nid = add_import_stub(mod_name)
|
|
479
|
+
add_edge(file_nid, tgt_nid, "imports",
|
|
480
|
+
top.start_point[0] + 1)
|
|
481
|
+
break
|
|
482
|
+
return True
|
|
483
|
+
if first_lower in _CL_DATA_DEFINERS or first_lower in _CL_MACRO_DEFINERS:
|
|
484
|
+
_handle_def_form(top, first_lower)
|
|
485
|
+
return True
|
|
486
|
+
if _is_def_prefixed(first_lower):
|
|
487
|
+
# Custom definer (definline, definline-maybe, defcomponent, etc.)
|
|
488
|
+
_handle_def_form(top, first_lower)
|
|
489
|
+
return True
|
|
490
|
+
|
|
491
|
+
return False
|
|
492
|
+
|
|
493
|
+
def _walk_forms(parent) -> None:
|
|
494
|
+
"""Walk list_lit children of `parent`, processing each. If a form
|
|
495
|
+
isn't recognized, recurse into it — many CL codebases wrap
|
|
496
|
+
definitions in macros like (optimizing ...), (eval-when ...), or
|
|
497
|
+
(progn ...) that aren't themselves definers but contain defs.
|
|
498
|
+
Also descends into reader conditionals (#+feature / #-feature),
|
|
499
|
+
which wrap their guarded form in an include_reader_macro node."""
|
|
500
|
+
for top in parent.children:
|
|
501
|
+
if top.type == "list_lit":
|
|
502
|
+
if not _process_form(top):
|
|
503
|
+
_walk_forms(top)
|
|
504
|
+
elif top.type == "include_reader_macro":
|
|
505
|
+
_walk_forms(top)
|
|
506
|
+
|
|
507
|
+
_walk_forms(root)
|
|
508
|
+
|
|
509
|
+
# Call extraction pass
|
|
510
|
+
label_to_nid: dict[str, str] = {}
|
|
511
|
+
for n in nodes:
|
|
512
|
+
raw = n["label"]
|
|
513
|
+
normalised = raw.replace(" (macro)", "").replace(" (generic)", "").strip("()").lstrip(".")
|
|
514
|
+
label_to_nid[normalised.lower()] = n["id"]
|
|
515
|
+
|
|
516
|
+
seen_call_pairs: set[tuple[str, str]] = set()
|
|
517
|
+
|
|
518
|
+
def walk_calls(node, caller_nid: str) -> None:
|
|
519
|
+
if node.type == "defun":
|
|
520
|
+
return
|
|
521
|
+
if node.type == "list_lit":
|
|
522
|
+
callee = _first_sym(node)
|
|
523
|
+
if callee and callee.lower() not in _CL_SPECIAL_FORMS:
|
|
524
|
+
tgt_nid = label_to_nid.get(callee.lower())
|
|
525
|
+
if tgt_nid and tgt_nid != caller_nid:
|
|
526
|
+
pair = (caller_nid, tgt_nid)
|
|
527
|
+
if pair not in seen_call_pairs:
|
|
528
|
+
seen_call_pairs.add(pair)
|
|
529
|
+
add_edge(caller_nid, tgt_nid, "calls",
|
|
530
|
+
node.start_point[0] + 1, confidence="EXTRACTED", weight=1.0)
|
|
531
|
+
for child in node.children:
|
|
532
|
+
walk_calls(child, caller_nid)
|
|
533
|
+
|
|
534
|
+
for caller_nid, body_nodes in function_bodies:
|
|
535
|
+
for body_node in body_nodes:
|
|
536
|
+
walk_calls(body_node, caller_nid)
|
|
537
|
+
|
|
538
|
+
clean_edges = [e for e in edges if e["source"] in seen_ids and
|
|
539
|
+
(e["target"] in seen_ids or e["relation"] in ("imports", "imports_from"))]
|
|
540
|
+
return {"nodes": nodes, "edges": clean_edges}
|