agent-codinglanguage-mapper 1.0.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.
Files changed (42) hide show
  1. agent_codinglanguage_mapper/__init__.py +20 -0
  2. agent_codinglanguage_mapper/__main__.py +4 -0
  3. agent_codinglanguage_mapper/_version.py +2 -0
  4. agent_codinglanguage_mapper/adapters/__init__.py +4 -0
  5. agent_codinglanguage_mapper/adapters/delphi.py +822 -0
  6. agent_codinglanguage_mapper/adapters/delphi_frontend/__init__.py +6 -0
  7. agent_codinglanguage_mapper/adapters/delphi_frontend/comment_builder.py +29 -0
  8. agent_codinglanguage_mapper/adapters/delphi_frontend/consts.py +345 -0
  9. agent_codinglanguage_mapper/adapters/delphi_frontend/grammar.py +460 -0
  10. agent_codinglanguage_mapper/adapters/delphi_frontend/lark_builder.py +2674 -0
  11. agent_codinglanguage_mapper/adapters/delphi_frontend/lark_tokens.py +237 -0
  12. agent_codinglanguage_mapper/adapters/delphi_frontend/lsp_server.py +1832 -0
  13. agent_codinglanguage_mapper/adapters/delphi_frontend/nodes.py +371 -0
  14. agent_codinglanguage_mapper/adapters/delphi_frontend/parser.py +193 -0
  15. agent_codinglanguage_mapper/adapters/delphi_frontend/preprocessor.py +997 -0
  16. agent_codinglanguage_mapper/adapters/delphi_frontend/project_discovery.py +518 -0
  17. agent_codinglanguage_mapper/adapters/delphi_frontend/project_indexer.py +319 -0
  18. agent_codinglanguage_mapper/adapters/delphi_frontend/semantic.py +375 -0
  19. agent_codinglanguage_mapper/adapters/delphi_frontend/semantic_builder.py +1384 -0
  20. agent_codinglanguage_mapper/adapters/delphi_frontend/source_reader.py +17 -0
  21. agent_codinglanguage_mapper/adapters/delphi_frontend/workspace.py +67 -0
  22. agent_codinglanguage_mapper/adapters/tree_sitter.py +1722 -0
  23. agent_codinglanguage_mapper/cli.py +138 -0
  24. agent_codinglanguage_mapper/discovery.py +1328 -0
  25. agent_codinglanguage_mapper/indexer.py +1102 -0
  26. agent_codinglanguage_mapper/integration.py +186 -0
  27. agent_codinglanguage_mapper/lsp_server.py +413 -0
  28. agent_codinglanguage_mapper/lsp_service.py +447 -0
  29. agent_codinglanguage_mapper/mapper.py +596 -0
  30. agent_codinglanguage_mapper/models.py +101 -0
  31. agent_codinglanguage_mapper/protocol.py +152 -0
  32. agent_codinglanguage_mapper/rendering.py +153 -0
  33. agent_codinglanguage_mapper/templates/opencode/plugins/codebase_map.ts +191 -0
  34. agent_codinglanguage_mapper/templates/skill/SKILL.md +52 -0
  35. agent_codinglanguage_mapper/worker.py +29 -0
  36. agent_codinglanguage_mapper/workspace.py +114 -0
  37. agent_codinglanguage_mapper-1.0.0.dist-info/METADATA +383 -0
  38. agent_codinglanguage_mapper-1.0.0.dist-info/RECORD +42 -0
  39. agent_codinglanguage_mapper-1.0.0.dist-info/WHEEL +5 -0
  40. agent_codinglanguage_mapper-1.0.0.dist-info/entry_points.txt +2 -0
  41. agent_codinglanguage_mapper-1.0.0.dist-info/licenses/LICENSE +373 -0
  42. agent_codinglanguage_mapper-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,518 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Iterable
6
+ import os
7
+ import re
8
+ import xml.etree.ElementTree as ET
9
+
10
+
11
+ SOURCE_EXTENSIONS = (".pas", ".dpr", ".dpk", ".inc")
12
+ PROJECT_EXTENSIONS = (".dpr", ".dpk")
13
+ SKIP_DIRS = {
14
+ ".git",
15
+ ".hg",
16
+ ".svn",
17
+ ".venv",
18
+ "venv",
19
+ "env",
20
+ "__pycache__",
21
+ ".pytest_cache",
22
+ ".ruff_cache",
23
+ ".mypy_cache",
24
+ "build",
25
+ "dist",
26
+ ".worktrees",
27
+ "node_modules",
28
+ }
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class DiscoveryProblem:
33
+ kind: str
34
+ message: str
35
+ origin: str
36
+
37
+
38
+ @dataclass
39
+ class DelphiProjectDiscovery:
40
+ root: str
41
+ project_files: list[str] = field(default_factory=list)
42
+ config_files: list[str] = field(default_factory=list)
43
+ search_paths: list[str] = field(default_factory=list)
44
+ include_paths: list[str] = field(default_factory=list)
45
+ defines: list[str] = field(default_factory=list)
46
+ source_files: list[str] = field(default_factory=list)
47
+ unit_paths: dict[str, list[str]] = field(default_factory=dict)
48
+ problems: list[DiscoveryProblem] = field(default_factory=list)
49
+ search_path_origins: dict[str, list[str]] = field(default_factory=dict)
50
+ include_path_origins: dict[str, list[str]] = field(default_factory=dict)
51
+ define_origins: dict[str, list[str]] = field(default_factory=dict)
52
+
53
+
54
+ _DPR_UNIT_RE = re.compile(
55
+ r"\b(?P<name>[A-Za-z_][A-Za-z0-9_.]*)\b\s*(?:in\s*['\"](?P<path>[^'\"]+)['\"])?",
56
+ re.IGNORECASE,
57
+ )
58
+ _DPR_CLAUSE_RE = re.compile(r"\b(?:uses|contains)\b(?P<body>.*?);", re.IGNORECASE | re.DOTALL)
59
+ _CFG_TOKEN_RE = re.compile(r"(?P<option>-[UID])(?P<value>.+)", re.IGNORECASE)
60
+ _MACRO_RE = re.compile(r"\$\(([A-Za-z_][A-Za-z0-9_]*)\)")
61
+ _WINDOWS_ABSOLUTE_RE = re.compile(r"^[A-Za-z]:[\\/]")
62
+
63
+
64
+ def discover_delphi_project(
65
+ root: str | os.PathLike[str],
66
+ *,
67
+ project_file: str | os.PathLike[str] | None = None,
68
+ include_paths: Iterable[str | os.PathLike[str]] = (),
69
+ search_paths: Iterable[str | os.PathLike[str]] = (),
70
+ defines: Iterable[str] = (),
71
+ scan_workspace_sources: bool = True,
72
+ ) -> DelphiProjectDiscovery:
73
+ root_path = Path(root).expanduser().resolve()
74
+ project_path = Path(project_file).expanduser().resolve() if project_file is not None else None
75
+ discovery = DelphiProjectDiscovery(root=str(root_path))
76
+
77
+ seen_search: set[str] = set()
78
+ seen_include: set[str] = set()
79
+ seen_defines: set[str] = set()
80
+ seen_projects: set[str] = set()
81
+ seen_configs: set[str] = set()
82
+
83
+ def add_path(
84
+ target: list[str],
85
+ seen: set[str],
86
+ origins: dict[str, list[str]],
87
+ path: Path | str,
88
+ *,
89
+ base: Path,
90
+ origin: str,
91
+ ) -> None:
92
+ _add_resolved_path(
93
+ target,
94
+ seen,
95
+ origins,
96
+ str(path),
97
+ base=base,
98
+ origin=origin,
99
+ discovery=discovery,
100
+ )
101
+
102
+ def add_define(raw: str, *, origin: str = "manual define") -> None:
103
+ for item in _split_list(raw):
104
+ define = item.strip()
105
+ if not define:
106
+ continue
107
+ if _MACRO_RE.search(define):
108
+ discovery.problems.append(
109
+ DiscoveryProblem("unresolved_macro", f"Could not resolve {define} in define list", origin)
110
+ )
111
+ continue
112
+ key = define.casefold()
113
+ if key not in seen_defines:
114
+ seen_defines.add(key)
115
+ discovery.defines.append(define)
116
+ exposed_define = next(item for item in discovery.defines if item.casefold() == key)
117
+ _record_origin(discovery.define_origins, exposed_define, origin)
118
+
119
+ for value in search_paths:
120
+ add_path(
121
+ discovery.search_paths,
122
+ seen_search,
123
+ discovery.search_path_origins,
124
+ Path(value),
125
+ base=root_path,
126
+ origin="manual search path",
127
+ )
128
+ for value in include_paths:
129
+ add_path(
130
+ discovery.include_paths,
131
+ seen_include,
132
+ discovery.include_path_origins,
133
+ Path(value),
134
+ base=root_path,
135
+ origin="manual include path",
136
+ )
137
+ for value in defines:
138
+ add_define(value)
139
+
140
+ candidates = _project_candidates(root_path, project_path)
141
+ for project in candidates:
142
+ key = str(project).casefold()
143
+ if key not in seen_projects:
144
+ seen_projects.add(key)
145
+ discovery.project_files.append(str(project))
146
+ if project.suffix.casefold() in PROJECT_EXTENSIONS:
147
+ _read_dpr_paths(
148
+ project,
149
+ discovery,
150
+ discovery.search_paths,
151
+ seen_search,
152
+ discovery.search_path_origins,
153
+ )
154
+ dproj = project.with_suffix(".dproj")
155
+ if dproj.exists():
156
+ _read_dproj(
157
+ dproj,
158
+ discovery,
159
+ discovery.search_paths,
160
+ seen_search,
161
+ discovery.search_path_origins,
162
+ discovery.include_paths,
163
+ seen_include,
164
+ discovery.include_path_origins,
165
+ add_define,
166
+ )
167
+ seen_configs.add(str(dproj).casefold())
168
+ discovery.config_files.append(str(dproj))
169
+ for cfg in (project.with_suffix(".cfg"), project.with_suffix(".dof")):
170
+ if cfg.exists():
171
+ _read_cfg(
172
+ cfg,
173
+ discovery,
174
+ discovery.search_paths,
175
+ seen_search,
176
+ discovery.search_path_origins,
177
+ discovery.include_paths,
178
+ seen_include,
179
+ discovery.include_path_origins,
180
+ add_define,
181
+ )
182
+ key = str(cfg).casefold()
183
+ if key not in seen_configs:
184
+ seen_configs.add(key)
185
+ discovery.config_files.append(str(cfg))
186
+
187
+ if scan_workspace_sources:
188
+ populate_workspace_sources(discovery)
189
+
190
+ return discovery
191
+
192
+
193
+ def populate_workspace_sources(discovery: DelphiProjectDiscovery) -> DelphiProjectDiscovery:
194
+ root_path = Path(discovery.root).expanduser().resolve()
195
+ seen_sources = {source.casefold() for source in discovery.source_files}
196
+ seen_search = {path.casefold() for path in discovery.search_paths}
197
+ seen_include = {path.casefold() for path in discovery.include_paths}
198
+
199
+ _scan_sources(root_path, discovery, seen_sources)
200
+ for source in discovery.source_files:
201
+ path = Path(source)
202
+ unit_key = path.stem.casefold()
203
+ unit_paths = discovery.unit_paths.setdefault(unit_key, [])
204
+ if source not in unit_paths:
205
+ unit_paths.append(source)
206
+ if path.suffix.casefold() in {".pas", ".dpr", ".dpk"}:
207
+ _add_resolved_path(
208
+ discovery.search_paths,
209
+ seen_search,
210
+ discovery.search_path_origins,
211
+ str(path.parent),
212
+ base=root_path,
213
+ origin="workspace source scan",
214
+ discovery=discovery,
215
+ )
216
+ elif path.suffix.casefold() == ".inc":
217
+ _add_resolved_path(
218
+ discovery.include_paths,
219
+ seen_include,
220
+ discovery.include_path_origins,
221
+ str(path.parent),
222
+ base=root_path,
223
+ origin="workspace include scan",
224
+ discovery=discovery,
225
+ )
226
+
227
+ return discovery
228
+
229
+
230
+ def discover_workspace_sources(root: str | os.PathLike[str]) -> DelphiProjectDiscovery:
231
+ root_path = Path(root).expanduser().resolve()
232
+ discovery = DelphiProjectDiscovery(root=str(root_path))
233
+ return populate_workspace_sources(discovery)
234
+
235
+
236
+ def _project_candidates(root: Path, explicit: Path | None) -> list[Path]:
237
+ if explicit is not None:
238
+ return [explicit]
239
+ candidates: list[Path] = []
240
+ for ext in PROJECT_EXTENSIONS:
241
+ candidates.extend(_walk_sources(root, f"*{ext}"))
242
+ dproj_mains: list[Path] = []
243
+ for dproj in _walk_sources(root, "*.dproj"):
244
+ main = _main_source_from_dproj(dproj)
245
+ if main is not None:
246
+ dproj_mains.append((dproj.parent / main).resolve())
247
+ for candidate in [*dproj_mains, *candidates]:
248
+ if candidate.exists() and candidate.is_file() and candidate not in candidates:
249
+ candidates.append(candidate)
250
+ return sorted(candidates, key=lambda path: str(path).casefold())
251
+
252
+
253
+ def _read_dpr_paths(
254
+ project: Path,
255
+ discovery: DelphiProjectDiscovery,
256
+ search_paths: list[str],
257
+ seen_search: set[str],
258
+ search_path_origins: dict[str, list[str]],
259
+ ) -> None:
260
+ try:
261
+ text = project.read_text(encoding="utf-8")
262
+ except UnicodeDecodeError:
263
+ text = project.read_text(encoding="latin-1")
264
+ except OSError as exc:
265
+ discovery.problems.append(DiscoveryProblem("cant_read_project", str(exc), str(project)))
266
+ return
267
+ for clause in _DPR_CLAUSE_RE.finditer(text):
268
+ body = clause.group("body")
269
+ for match in _DPR_UNIT_RE.finditer(body):
270
+ unit_path = match.group("path")
271
+ if not unit_path:
272
+ continue
273
+ resolved = _resolve_project_path(unit_path, base=project.parent, origin=str(project), discovery=discovery)
274
+ if resolved is None:
275
+ continue
276
+ _add_resolved_path(
277
+ search_paths,
278
+ seen_search,
279
+ search_path_origins,
280
+ str(resolved.parent),
281
+ base=project.parent,
282
+ origin=str(project),
283
+ discovery=discovery,
284
+ )
285
+
286
+
287
+ def _read_dproj(
288
+ path: Path,
289
+ discovery: DelphiProjectDiscovery,
290
+ search_paths: list[str],
291
+ seen_search: set[str],
292
+ search_path_origins: dict[str, list[str]],
293
+ include_paths: list[str],
294
+ seen_include: set[str],
295
+ include_path_origins: dict[str, list[str]],
296
+ add_define,
297
+ ) -> None:
298
+ try:
299
+ root = ET.parse(path).getroot()
300
+ except (ET.ParseError, OSError) as exc:
301
+ discovery.problems.append(DiscoveryProblem("cant_read_dproj", str(exc), str(path)))
302
+ return
303
+
304
+ for element in root.iter():
305
+ name = _xml_local_name(element.tag)
306
+ text = (element.text or "").strip()
307
+ if name in {"DCC_UnitSearchPath", "UnitSearchPath"} and text:
308
+ for item in _split_list(text):
309
+ _add_resolved_path(
310
+ search_paths,
311
+ seen_search,
312
+ search_path_origins,
313
+ item,
314
+ base=path.parent,
315
+ origin=str(path),
316
+ discovery=discovery,
317
+ )
318
+ elif name in {"DCC_IncludePath", "IncludePath"} and text:
319
+ for item in _split_list(text):
320
+ _add_resolved_path(
321
+ include_paths,
322
+ seen_include,
323
+ include_path_origins,
324
+ item,
325
+ base=path.parent,
326
+ origin=str(path),
327
+ discovery=discovery,
328
+ )
329
+ elif name in {"DCC_Define", "DefineConstants"} and text:
330
+ add_define(text, origin=str(path))
331
+
332
+ if name == "DCCReference":
333
+ include = element.attrib.get("Include") or element.attrib.get("include")
334
+ if include:
335
+ resolved = _resolve_project_path(include, base=path.parent, origin=str(path), discovery=discovery)
336
+ if resolved is not None:
337
+ _add_resolved_path(
338
+ search_paths,
339
+ seen_search,
340
+ search_path_origins,
341
+ str(resolved.parent),
342
+ base=path.parent,
343
+ origin=str(path),
344
+ discovery=discovery,
345
+ )
346
+
347
+
348
+ def _read_cfg(
349
+ path: Path,
350
+ discovery: DelphiProjectDiscovery,
351
+ search_paths: list[str],
352
+ seen_search: set[str],
353
+ search_path_origins: dict[str, list[str]],
354
+ include_paths: list[str],
355
+ seen_include: set[str],
356
+ include_path_origins: dict[str, list[str]],
357
+ add_define,
358
+ ) -> None:
359
+ try:
360
+ lines = path.read_text(encoding="utf-8").splitlines()
361
+ except UnicodeDecodeError:
362
+ lines = path.read_text(encoding="latin-1").splitlines()
363
+ except OSError as exc:
364
+ discovery.problems.append(DiscoveryProblem("cant_read_config", str(exc), str(path)))
365
+ return
366
+ for line in lines:
367
+ stripped = line.strip()
368
+ if not stripped or stripped.startswith("#"):
369
+ continue
370
+ match = _CFG_TOKEN_RE.match(stripped)
371
+ if match is None:
372
+ continue
373
+ option = match.group("option").casefold()
374
+ value = match.group("value")
375
+ if option == "-u":
376
+ for item in _split_list(value):
377
+ _add_resolved_path(
378
+ search_paths,
379
+ seen_search,
380
+ search_path_origins,
381
+ item,
382
+ base=path.parent,
383
+ origin=str(path),
384
+ discovery=discovery,
385
+ )
386
+ elif option == "-i":
387
+ for item in _split_list(value):
388
+ _add_resolved_path(
389
+ include_paths,
390
+ seen_include,
391
+ include_path_origins,
392
+ item,
393
+ base=path.parent,
394
+ origin=str(path),
395
+ discovery=discovery,
396
+ )
397
+ elif option == "-d":
398
+ add_define(value, origin=str(path))
399
+
400
+
401
+ def _main_source_from_dproj(path: Path) -> str | None:
402
+ try:
403
+ root = ET.parse(path).getroot()
404
+ except (ET.ParseError, OSError):
405
+ return None
406
+ for element in root.iter():
407
+ if _xml_local_name(element.tag) == "MainSource" and element.text:
408
+ return element.text.strip()
409
+ return None
410
+
411
+
412
+ def _scan_sources(root: Path, discovery: DelphiProjectDiscovery, seen_sources: set[str]) -> None:
413
+ for path in root.rglob("*"):
414
+ if path.suffix.casefold() not in SOURCE_EXTENSIONS:
415
+ continue
416
+ if any(part in SKIP_DIRS for part in path.parts):
417
+ continue
418
+ if not path.is_file():
419
+ continue
420
+ resolved = path.resolve()
421
+ key = str(resolved).casefold()
422
+ if key in seen_sources:
423
+ continue
424
+ seen_sources.add(key)
425
+ discovery.source_files.append(str(resolved))
426
+ discovery.source_files.sort(key=lambda item: (item.casefold(), item))
427
+
428
+
429
+ def _walk_sources(root: Path, pattern: str) -> list[Path]:
430
+ results: list[Path] = []
431
+ for path in root.rglob(pattern):
432
+ if any(part in SKIP_DIRS for part in path.parts):
433
+ continue
434
+ if path.is_file():
435
+ results.append(path.resolve())
436
+ return results
437
+
438
+
439
+ def _add_resolved_path(
440
+ target: list[str],
441
+ seen: set[str],
442
+ origins: dict[str, list[str]],
443
+ value: str,
444
+ *,
445
+ base: Path,
446
+ origin: str,
447
+ discovery: DelphiProjectDiscovery,
448
+ ) -> None:
449
+ resolved = _resolve_project_path(value, base=base, origin=origin, discovery=discovery)
450
+ if resolved is None:
451
+ return
452
+ key = str(resolved).casefold()
453
+ if key not in seen:
454
+ seen.add(key)
455
+ target.append(str(resolved))
456
+ exposed_path = next(item for item in target if item.casefold() == key)
457
+ _record_origin(origins, exposed_path, origin)
458
+
459
+
460
+ def _record_origin(origins: dict[str, list[str]], key: str, origin: str) -> None:
461
+ entries = origins.setdefault(key, [])
462
+ if origin not in entries:
463
+ entries.append(origin)
464
+
465
+
466
+ def _resolve_project_path(
467
+ raw: str,
468
+ *,
469
+ base: Path,
470
+ origin: str,
471
+ discovery: DelphiProjectDiscovery,
472
+ ) -> Path | None:
473
+ cleaned = raw.strip().strip('"').strip("'")
474
+ if not cleaned:
475
+ return None
476
+ if os.name != "nt" and _WINDOWS_ABSOLUTE_RE.match(cleaned):
477
+ discovery.problems.append(
478
+ DiscoveryProblem("external_path", f"Skipping non-local Windows absolute path: {raw}", origin)
479
+ )
480
+ return None
481
+ normalized = cleaned.replace("\\", os.sep)
482
+ macros = _MACRO_RE.findall(normalized)
483
+ replacements = {
484
+ "PROJECTDIR": str(base),
485
+ "PROJECT_DIR": str(base),
486
+ "MSBUILDPROJECTDIRECTORY": str(base),
487
+ "MSBUILDTHISFILEDIRECTORY": str(base),
488
+ }
489
+ for macro in macros:
490
+ value = replacements.get(macro.upper())
491
+ if value is None:
492
+ discovery.problems.append(
493
+ DiscoveryProblem("unresolved_macro", f"Could not resolve $({macro}) in {raw}", origin)
494
+ )
495
+ return None
496
+ normalized = normalized.replace(f"$({macro})", value)
497
+ normalized = os.path.expandvars(normalized)
498
+ path = Path(normalized)
499
+ if not path.is_absolute():
500
+ path = base / path
501
+ return path.resolve()
502
+
503
+
504
+ def _split_list(raw: str) -> list[str]:
505
+ return [item.strip() for item in raw.replace("\n", ";").split(";") if item.strip()]
506
+
507
+
508
+ def _xml_local_name(tag: str) -> str:
509
+ return tag.rsplit("}", 1)[-1]
510
+
511
+
512
+ __all__ = [
513
+ "DelphiProjectDiscovery",
514
+ "DiscoveryProblem",
515
+ "discover_delphi_project",
516
+ "discover_workspace_sources",
517
+ "populate_workspace_sources",
518
+ ]