Extract units, classes, procedures, uses-imports, and calls from Pascal/Delphi files. Produces nodes for: - The file itself - unit / program / library declarations - class and interface type declarations - procedure / function implementations (including qualified TClass.Method n
(path: Path)
| 13346 | |
| 13347 | |
| 13348 | def extract_pascal(path: Path) -> dict: |
| 13349 | """Extract units, classes, procedures, uses-imports, and calls from Pascal/Delphi files. |
| 13350 | |
| 13351 | Produces nodes for: |
| 13352 | - The file itself |
| 13353 | - unit / program / library declarations |
| 13354 | - class and interface type declarations |
| 13355 | - procedure / function implementations (including qualified TClass.Method names) |
| 13356 | |
| 13357 | Produces edges for: |
| 13358 | - file --contains--> module |
| 13359 | - module --imports--> other file node (via uses clause, resolved to path-based IDs) |
| 13360 | - class --inherits--> base class |
| 13361 | - class/module --contains--> method forward declaration |
| 13362 | - class/module --contains--> procedure/function implementation |
| 13363 | - procedure --calls--> other procedure (within the same file) |
| 13364 | |
| 13365 | Uses tree-sitter-pascal when available; falls back to a regex-based extractor |
| 13366 | (_extract_pascal_regex) when it isn't installed or fails to parse, so Pascal |
| 13367 | extraction works out of the box without an extra pip install. |
| 13368 | """ |
| 13369 | try: |
| 13370 | import tree_sitter_pascal as tspascal |
| 13371 | from tree_sitter import Language, Parser |
| 13372 | except ImportError: |
| 13373 | return _extract_pascal_regex(path) |
| 13374 | |
| 13375 | try: |
| 13376 | language = Language(tspascal.language()) |
| 13377 | parser = Parser(language) |
| 13378 | source = path.read_bytes() |
| 13379 | tree = parser.parse(source) |
| 13380 | root = tree.root_node |
| 13381 | except Exception: |
| 13382 | return _extract_pascal_regex(path) |
| 13383 | |
| 13384 | stem = _file_stem(path) |
| 13385 | str_path = str(path) |
| 13386 | nodes: list[dict] = [] |
| 13387 | edges: list[dict] = [] |
| 13388 | seen_ids: set[str] = set() |
| 13389 | proc_bodies: list[tuple[str, Any]] = [] |
| 13390 | |
| 13391 | def _read(node) -> str: # type: ignore[no-untyped-def] |
| 13392 | return source[node.start_byte:node.end_byte].decode("utf-8", errors="replace") |
| 13393 | |
| 13394 | def add_node(nid: str, label: str, line: int) -> None: |
| 13395 | if nid not in seen_ids: |
| 13396 | seen_ids.add(nid) |
| 13397 | nodes.append({ |
| 13398 | "id": nid, "label": label, "file_type": "code", |
| 13399 | "source_file": str_path, "source_location": f"L{line}", |
| 13400 | }) |
| 13401 | |
| 13402 | def add_edge( |
| 13403 | src: str, tgt: str, relation: str, line: int, |
| 13404 | confidence: str = "EXTRACTED", weight: float = 1.0, |
| 13405 | context: str | None = None, |