alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__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 (261) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +258 -0
  3. alita_sdk/cli/agent_executor.py +15 -3
  4. alita_sdk/cli/agent_loader.py +56 -8
  5. alita_sdk/cli/agent_ui.py +93 -31
  6. alita_sdk/cli/agents.py +2274 -230
  7. alita_sdk/cli/callbacks.py +96 -25
  8. alita_sdk/cli/cli.py +10 -1
  9. alita_sdk/cli/config.py +162 -9
  10. alita_sdk/cli/context/__init__.py +30 -0
  11. alita_sdk/cli/context/cleanup.py +198 -0
  12. alita_sdk/cli/context/manager.py +731 -0
  13. alita_sdk/cli/context/message.py +285 -0
  14. alita_sdk/cli/context/strategies.py +289 -0
  15. alita_sdk/cli/context/token_estimation.py +127 -0
  16. alita_sdk/cli/input_handler.py +419 -0
  17. alita_sdk/cli/inventory.py +1073 -0
  18. alita_sdk/cli/testcases/__init__.py +94 -0
  19. alita_sdk/cli/testcases/data_generation.py +119 -0
  20. alita_sdk/cli/testcases/discovery.py +96 -0
  21. alita_sdk/cli/testcases/executor.py +84 -0
  22. alita_sdk/cli/testcases/logger.py +85 -0
  23. alita_sdk/cli/testcases/parser.py +172 -0
  24. alita_sdk/cli/testcases/prompts.py +91 -0
  25. alita_sdk/cli/testcases/reporting.py +125 -0
  26. alita_sdk/cli/testcases/setup.py +108 -0
  27. alita_sdk/cli/testcases/test_runner.py +282 -0
  28. alita_sdk/cli/testcases/utils.py +39 -0
  29. alita_sdk/cli/testcases/validation.py +90 -0
  30. alita_sdk/cli/testcases/workflow.py +196 -0
  31. alita_sdk/cli/toolkit.py +14 -17
  32. alita_sdk/cli/toolkit_loader.py +35 -5
  33. alita_sdk/cli/tools/__init__.py +36 -2
  34. alita_sdk/cli/tools/approval.py +224 -0
  35. alita_sdk/cli/tools/filesystem.py +910 -64
  36. alita_sdk/cli/tools/planning.py +389 -0
  37. alita_sdk/cli/tools/terminal.py +414 -0
  38. alita_sdk/community/__init__.py +72 -12
  39. alita_sdk/community/inventory/__init__.py +236 -0
  40. alita_sdk/community/inventory/config.py +257 -0
  41. alita_sdk/community/inventory/enrichment.py +2137 -0
  42. alita_sdk/community/inventory/extractors.py +1469 -0
  43. alita_sdk/community/inventory/ingestion.py +3172 -0
  44. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  45. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  46. alita_sdk/community/inventory/parsers/base.py +295 -0
  47. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  48. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  49. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  50. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  51. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  52. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  53. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  54. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  55. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  56. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  57. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  58. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  59. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  60. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  61. alita_sdk/community/inventory/patterns/loader.py +348 -0
  62. alita_sdk/community/inventory/patterns/registry.py +198 -0
  63. alita_sdk/community/inventory/presets.py +535 -0
  64. alita_sdk/community/inventory/retrieval.py +1403 -0
  65. alita_sdk/community/inventory/toolkit.py +173 -0
  66. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  67. alita_sdk/community/inventory/visualize.py +1370 -0
  68. alita_sdk/configurations/__init__.py +1 -1
  69. alita_sdk/configurations/ado.py +141 -20
  70. alita_sdk/configurations/bitbucket.py +0 -3
  71. alita_sdk/configurations/confluence.py +76 -42
  72. alita_sdk/configurations/figma.py +76 -0
  73. alita_sdk/configurations/gitlab.py +17 -5
  74. alita_sdk/configurations/openapi.py +329 -0
  75. alita_sdk/configurations/qtest.py +72 -1
  76. alita_sdk/configurations/report_portal.py +96 -0
  77. alita_sdk/configurations/sharepoint.py +148 -0
  78. alita_sdk/configurations/testio.py +83 -0
  79. alita_sdk/runtime/clients/artifact.py +3 -3
  80. alita_sdk/runtime/clients/client.py +353 -48
  81. alita_sdk/runtime/clients/sandbox_client.py +0 -21
  82. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  83. alita_sdk/runtime/langchain/assistant.py +123 -26
  84. alita_sdk/runtime/langchain/constants.py +642 -1
  85. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  86. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  87. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
  88. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  89. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  90. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  91. alita_sdk/runtime/langchain/langraph_agent.py +279 -73
  92. alita_sdk/runtime/langchain/utils.py +82 -15
  93. alita_sdk/runtime/llms/preloaded.py +2 -6
  94. alita_sdk/runtime/skills/__init__.py +91 -0
  95. alita_sdk/runtime/skills/callbacks.py +498 -0
  96. alita_sdk/runtime/skills/discovery.py +540 -0
  97. alita_sdk/runtime/skills/executor.py +610 -0
  98. alita_sdk/runtime/skills/input_builder.py +371 -0
  99. alita_sdk/runtime/skills/models.py +330 -0
  100. alita_sdk/runtime/skills/registry.py +355 -0
  101. alita_sdk/runtime/skills/skill_runner.py +330 -0
  102. alita_sdk/runtime/toolkits/__init__.py +7 -0
  103. alita_sdk/runtime/toolkits/application.py +21 -9
  104. alita_sdk/runtime/toolkits/artifact.py +15 -5
  105. alita_sdk/runtime/toolkits/datasource.py +13 -6
  106. alita_sdk/runtime/toolkits/mcp.py +139 -251
  107. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  108. alita_sdk/runtime/toolkits/planning.py +178 -0
  109. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  110. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  111. alita_sdk/runtime/toolkits/tools.py +238 -32
  112. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  113. alita_sdk/runtime/tools/__init__.py +3 -1
  114. alita_sdk/runtime/tools/application.py +20 -6
  115. alita_sdk/runtime/tools/artifact.py +511 -28
  116. alita_sdk/runtime/tools/data_analysis.py +183 -0
  117. alita_sdk/runtime/tools/function.py +43 -15
  118. alita_sdk/runtime/tools/image_generation.py +50 -44
  119. alita_sdk/runtime/tools/llm.py +852 -67
  120. alita_sdk/runtime/tools/loop.py +3 -1
  121. alita_sdk/runtime/tools/loop_output.py +3 -1
  122. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  123. alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
  124. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  125. alita_sdk/runtime/tools/planning/models.py +246 -0
  126. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  127. alita_sdk/runtime/tools/router.py +2 -4
  128. alita_sdk/runtime/tools/sandbox.py +9 -6
  129. alita_sdk/runtime/tools/skill_router.py +776 -0
  130. alita_sdk/runtime/tools/tool.py +3 -1
  131. alita_sdk/runtime/tools/vectorstore.py +7 -2
  132. alita_sdk/runtime/tools/vectorstore_base.py +51 -11
  133. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  134. alita_sdk/runtime/utils/constants.py +5 -1
  135. alita_sdk/runtime/utils/mcp_client.py +492 -0
  136. alita_sdk/runtime/utils/mcp_oauth.py +202 -5
  137. alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
  138. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  139. alita_sdk/runtime/utils/serialization.py +155 -0
  140. alita_sdk/runtime/utils/streamlit.py +6 -10
  141. alita_sdk/runtime/utils/toolkit_utils.py +16 -5
  142. alita_sdk/runtime/utils/utils.py +36 -0
  143. alita_sdk/tools/__init__.py +113 -29
  144. alita_sdk/tools/ado/repos/__init__.py +51 -33
  145. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  146. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  147. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  148. alita_sdk/tools/ado/utils.py +1 -18
  149. alita_sdk/tools/ado/wiki/__init__.py +25 -8
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  151. alita_sdk/tools/ado/work_item/__init__.py +26 -9
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  155. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  156. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  157. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  158. alita_sdk/tools/base/tool.py +5 -1
  159. alita_sdk/tools/base_indexer_toolkit.py +170 -45
  160. alita_sdk/tools/bitbucket/__init__.py +17 -12
  161. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  162. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  163. alita_sdk/tools/browser/__init__.py +5 -4
  164. alita_sdk/tools/carrier/__init__.py +5 -6
  165. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  166. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  167. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  168. alita_sdk/tools/chunkers/__init__.py +3 -1
  169. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  170. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  171. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  172. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  173. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  174. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  175. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  176. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  177. alita_sdk/tools/code/linter/__init__.py +10 -8
  178. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  179. alita_sdk/tools/code/sonar/__init__.py +10 -7
  180. alita_sdk/tools/code_indexer_toolkit.py +73 -23
  181. alita_sdk/tools/confluence/__init__.py +21 -15
  182. alita_sdk/tools/confluence/api_wrapper.py +78 -23
  183. alita_sdk/tools/confluence/loader.py +4 -2
  184. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  185. alita_sdk/tools/elastic/__init__.py +11 -8
  186. alita_sdk/tools/elitea_base.py +493 -30
  187. alita_sdk/tools/figma/__init__.py +58 -11
  188. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  189. alita_sdk/tools/figma/figma_client.py +73 -0
  190. alita_sdk/tools/figma/toon_tools.py +2748 -0
  191. alita_sdk/tools/github/__init__.py +13 -14
  192. alita_sdk/tools/github/github_client.py +224 -100
  193. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  194. alita_sdk/tools/github/schemas.py +14 -5
  195. alita_sdk/tools/github/tool.py +5 -1
  196. alita_sdk/tools/github/tool_prompts.py +9 -22
  197. alita_sdk/tools/gitlab/__init__.py +15 -11
  198. alita_sdk/tools/gitlab/api_wrapper.py +207 -41
  199. alita_sdk/tools/gitlab_org/__init__.py +10 -8
  200. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  201. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  202. alita_sdk/tools/google/bigquery/tool.py +5 -1
  203. alita_sdk/tools/google_places/__init__.py +10 -8
  204. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  205. alita_sdk/tools/jira/__init__.py +17 -11
  206. alita_sdk/tools/jira/api_wrapper.py +91 -40
  207. alita_sdk/tools/keycloak/__init__.py +11 -8
  208. alita_sdk/tools/localgit/__init__.py +9 -3
  209. alita_sdk/tools/localgit/local_git.py +62 -54
  210. alita_sdk/tools/localgit/tool.py +5 -1
  211. alita_sdk/tools/memory/__init__.py +11 -3
  212. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  213. alita_sdk/tools/ocr/__init__.py +11 -8
  214. alita_sdk/tools/openapi/__init__.py +490 -114
  215. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  216. alita_sdk/tools/openapi/tool.py +20 -0
  217. alita_sdk/tools/pandas/__init__.py +20 -12
  218. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  219. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  220. alita_sdk/tools/postman/__init__.py +11 -11
  221. alita_sdk/tools/pptx/__init__.py +10 -9
  222. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  223. alita_sdk/tools/qtest/__init__.py +30 -10
  224. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  225. alita_sdk/tools/rally/__init__.py +10 -8
  226. alita_sdk/tools/rally/api_wrapper.py +1 -1
  227. alita_sdk/tools/report_portal/__init__.py +12 -9
  228. alita_sdk/tools/salesforce/__init__.py +10 -9
  229. alita_sdk/tools/servicenow/__init__.py +17 -14
  230. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  231. alita_sdk/tools/sharepoint/__init__.py +10 -8
  232. alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
  233. alita_sdk/tools/slack/__init__.py +10 -8
  234. alita_sdk/tools/slack/api_wrapper.py +2 -2
  235. alita_sdk/tools/sql/__init__.py +11 -9
  236. alita_sdk/tools/testio/__init__.py +10 -8
  237. alita_sdk/tools/testrail/__init__.py +11 -8
  238. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  239. alita_sdk/tools/utils/__init__.py +9 -4
  240. alita_sdk/tools/utils/content_parser.py +77 -3
  241. alita_sdk/tools/utils/text_operations.py +410 -0
  242. alita_sdk/tools/utils/tool_prompts.py +79 -0
  243. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  244. alita_sdk/tools/xray/__init__.py +12 -9
  245. alita_sdk/tools/yagmail/__init__.py +9 -3
  246. alita_sdk/tools/zephyr/__init__.py +9 -7
  247. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
  248. alita_sdk/tools/zephyr_essential/__init__.py +10 -8
  249. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  250. alita_sdk/tools/zephyr_essential/client.py +2 -2
  251. alita_sdk/tools/zephyr_scale/__init__.py +11 -9
  252. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  253. alita_sdk/tools/zephyr_squad/__init__.py +10 -8
  254. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
  255. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  256. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  257. alita_sdk-0.3.462.dist-info/RECORD +0 -384
  258. alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
  259. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  260. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  261. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,858 @@
1
+ """
2
+ Rust Parser - Regex-based parser for Rust source files.
3
+
4
+ Extracts symbols and relationships from .rs files using comprehensive
5
+ regex patterns. Supports Rust-specific features like traits, lifetimes,
6
+ macros, async/await, and pattern matching.
7
+ """
8
+
9
+ import re
10
+ from typing import Dict, List, Optional, Set, Tuple
11
+ from pathlib import Path
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ import logging
14
+
15
+ from .base import (
16
+ BaseParser,
17
+ ParseResult,
18
+ Symbol,
19
+ Relationship,
20
+ RelationshipType,
21
+ SymbolType,
22
+ Scope,
23
+ Position,
24
+ Range,
25
+ parser_registry,
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ from typing import Any
31
+
32
+
33
+ # Comprehensive Rust regex patterns
34
+ PATTERNS = {
35
+ # Module declaration
36
+ 'mod_declaration': re.compile(
37
+ r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?mod\s+(\w+)\s*[{;]',
38
+ re.MULTILINE
39
+ ),
40
+
41
+ # Use statements
42
+ 'use': re.compile(
43
+ r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?use\s+([\w:]+)(?:::\{([^}]+)\})?(?:\s+as\s+(\w+))?\s*;',
44
+ re.MULTILINE
45
+ ),
46
+ 'use_glob': re.compile(
47
+ r'^\s*(?:pub(?:\([^)]+\))?\s+)?use\s+([\w:]+)::\*\s*;',
48
+ re.MULTILINE
49
+ ),
50
+
51
+ # Struct declarations
52
+ 'struct': re.compile(
53
+ r'^\s*(?:#\[[^\]]+\]\s*)*' # Attributes
54
+ r'(?:(pub(?:\([^)]+\))?)\s+)?'
55
+ r'struct\s+(\w+)'
56
+ r'(?:<[^>]+>)?' # Generic parameters
57
+ r'(?:\s*\([^)]*\))?' # Tuple struct
58
+ r'(?:\s+where\s+[^{;]+)?', # Where clause
59
+ re.MULTILINE
60
+ ),
61
+
62
+ # Enum declarations
63
+ 'enum': re.compile(
64
+ r'^\s*(?:#\[[^\]]+\]\s*)*'
65
+ r'(?:(pub(?:\([^)]+\))?)\s+)?'
66
+ r'enum\s+(\w+)'
67
+ r'(?:<[^>]+>)?' # Generic parameters
68
+ r'(?:\s+where\s+[^{]+)?', # Where clause
69
+ re.MULTILINE
70
+ ),
71
+
72
+ # Trait declarations
73
+ 'trait': re.compile(
74
+ r'^\s*(?:#\[[^\]]+\]\s*)*'
75
+ r'(?:(pub(?:\([^)]+\))?)\s+)?'
76
+ r'(?:(unsafe)\s+)?'
77
+ r'trait\s+(\w+)'
78
+ r'(?:<[^>]+>)?' # Generic parameters
79
+ r'(?:\s*:\s*([^{]+))?' # Supertraits
80
+ r'(?:\s+where\s+[^{]+)?', # Where clause
81
+ re.MULTILINE
82
+ ),
83
+
84
+ # Impl blocks
85
+ 'impl': re.compile(
86
+ r'^\s*(?:(unsafe)\s+)?'
87
+ r'impl\s*'
88
+ r'(?:<[^>]+>\s*)?' # Generic parameters
89
+ r'(?:(\w+)\s+for\s+)?' # Trait impl
90
+ r'(\w+)' # Type
91
+ r'(?:<[^>]+>)?' # Type parameters
92
+ r'(?:\s+where\s+[^{]+)?', # Where clause
93
+ re.MULTILINE
94
+ ),
95
+
96
+ # Function declarations
97
+ 'function': re.compile(
98
+ r'^\s*(?:#\[[^\]]+\]\s*)*'
99
+ r'(?:(pub(?:\([^)]+\))?)\s+)?'
100
+ r'(?:(const|async|unsafe|extern(?:\s+"[^"]*")?)\s+)*'
101
+ r'fn\s+(\w+)'
102
+ r'(?:<[^>]+>)?' # Generic parameters
103
+ r'\s*\([^)]*\)' # Parameters
104
+ r'(?:\s*->\s*([^\n{;]+))?', # Return type
105
+ re.MULTILINE
106
+ ),
107
+
108
+ # Type alias
109
+ 'type_alias': re.compile(
110
+ r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?'
111
+ r'type\s+(\w+)'
112
+ r'(?:<[^>]+>)?' # Generic parameters
113
+ r'\s*=\s*([^;]+);',
114
+ re.MULTILINE
115
+ ),
116
+
117
+ # Const declarations
118
+ 'const': re.compile(
119
+ r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?'
120
+ r'const\s+(\w+)\s*:\s*([^=]+)\s*=',
121
+ re.MULTILINE
122
+ ),
123
+
124
+ # Static declarations
125
+ 'static': re.compile(
126
+ r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?'
127
+ r'static\s+(?:mut\s+)?(\w+)\s*:\s*([^=]+)\s*=',
128
+ re.MULTILINE
129
+ ),
130
+
131
+ # Macro definitions
132
+ 'macro_rules': re.compile(
133
+ r'^\s*(?:#\[[^\]]+\]\s*)*'
134
+ r'macro_rules!\s+(\w+)',
135
+ re.MULTILINE
136
+ ),
137
+
138
+ # Declarative macro (macro 2.0)
139
+ 'macro_decl': re.compile(
140
+ r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?'
141
+ r'macro\s+(\w+)',
142
+ re.MULTILINE
143
+ ),
144
+
145
+ # Attribute macros and derive
146
+ 'attribute': re.compile(
147
+ r'#\[(\w+)(?:\([^\]]*\))?\]',
148
+ re.MULTILINE
149
+ ),
150
+ 'derive': re.compile(
151
+ r'#\[derive\(([^\]]+)\)\]',
152
+ re.MULTILINE
153
+ ),
154
+
155
+ # Method calls
156
+ 'method_call': re.compile(
157
+ r'\.(\w+)\s*(?::<[^>]+>)?\s*\(',
158
+ re.MULTILINE
159
+ ),
160
+
161
+ # Function calls
162
+ 'function_call': re.compile(
163
+ r'(?:^|[^\w.])(\w+)\s*(?::<[^>]+>)?\s*\(',
164
+ re.MULTILINE
165
+ ),
166
+
167
+ # Macro invocations
168
+ 'macro_call': re.compile(
169
+ r'(\w+)!\s*[\(\[\{]',
170
+ re.MULTILINE
171
+ ),
172
+
173
+ # Type references (in type position)
174
+ 'type_ref': re.compile(
175
+ r':\s*(?:&(?:\'[\w]+\s+)?(?:mut\s+)?)?([A-Z]\w*)',
176
+ re.MULTILINE
177
+ ),
178
+
179
+ # Path expressions (e.g., std::collections::HashMap)
180
+ 'path_expr': re.compile(
181
+ r'(\w+(?:::\w+)+)',
182
+ re.MULTILINE
183
+ ),
184
+
185
+ # Struct instantiation
186
+ 'struct_init': re.compile(
187
+ r'([A-Z]\w*)\s*(?:::<[^>]+>)?\s*\{',
188
+ re.MULTILINE
189
+ ),
190
+
191
+ # Trait bounds
192
+ 'trait_bound': re.compile(
193
+ r'(?:impl|dyn)\s+([A-Z]\w*)',
194
+ re.MULTILINE
195
+ ),
196
+
197
+ # Doc comments
198
+ 'doc_comment': re.compile(
199
+ r'///\s*(.+)',
200
+ re.MULTILINE
201
+ ),
202
+ 'doc_comment_block': re.compile(
203
+ r'/\*\*\s*([\s\S]*?)\s*\*/',
204
+ re.MULTILINE
205
+ ),
206
+
207
+ # Lifetime annotations (for metadata)
208
+ 'lifetime': re.compile(
209
+ r"'(\w+)",
210
+ re.MULTILINE
211
+ ),
212
+ }
213
+
214
+
215
+ class RustParser(BaseParser):
216
+ """Rust source code parser using regex patterns."""
217
+
218
+ # Global symbol registry for cross-file resolution
219
+ _global_symbols: Dict[str, Set[str]] = {}
220
+ _symbol_to_file: Dict[str, str] = {}
221
+
222
+ def __init__(self):
223
+ super().__init__("rust")
224
+
225
+ def _get_supported_extensions(self) -> Set[str]:
226
+ return {'.rs'}
227
+
228
+ def _make_range(self, content: str, start_offset: int, end_line: int) -> Range:
229
+ """Create a Range object from content and offset."""
230
+ start_line = content[:start_offset].count('\n') + 1
231
+ last_newline = content.rfind('\n', 0, start_offset)
232
+ start_col = start_offset - last_newline - 1 if last_newline >= 0 else start_offset
233
+ return Range(
234
+ start=Position(line=start_line, column=start_col),
235
+ end=Position(line=end_line, column=0)
236
+ )
237
+
238
+ def _make_symbol(
239
+ self,
240
+ name: str,
241
+ symbol_type: SymbolType,
242
+ content: str,
243
+ file_path: str,
244
+ start_offset: int,
245
+ end_line: int,
246
+ scope: Scope = Scope.GLOBAL,
247
+ parent: Optional[str] = None,
248
+ full_name: Optional[str] = None,
249
+ docstring: Optional[str] = None,
250
+ visibility: Optional[str] = None,
251
+ is_static: bool = False,
252
+ is_async: bool = False,
253
+ return_type: Optional[str] = None,
254
+ metadata: Optional[Dict[str, Any]] = None
255
+ ) -> Symbol:
256
+ """Create a Symbol with correct fields."""
257
+ return Symbol(
258
+ name=name,
259
+ symbol_type=symbol_type,
260
+ scope=scope,
261
+ range=self._make_range(content, start_offset, end_line),
262
+ file_path=file_path,
263
+ parent_symbol=parent,
264
+ full_name=full_name,
265
+ visibility=visibility,
266
+ is_static=is_static,
267
+ is_async=is_async,
268
+ docstring=docstring,
269
+ return_type=return_type,
270
+ metadata=metadata or {}
271
+ )
272
+
273
+ def _make_relationship(
274
+ self,
275
+ source: str,
276
+ target: str,
277
+ rel_type: RelationshipType,
278
+ file_path: str,
279
+ content: str,
280
+ offset: int,
281
+ metadata: Optional[Dict[str, Any]] = None
282
+ ) -> Relationship:
283
+ """Create a Relationship with correct fields."""
284
+ return Relationship(
285
+ source_symbol=source,
286
+ target_symbol=target,
287
+ relationship_type=rel_type,
288
+ source_file=file_path,
289
+ source_range=self._make_range(content, offset, content[:offset].count('\n') + 1),
290
+ metadata=metadata or {}
291
+ )
292
+
293
+ def parse_file(self, file_path: str, content: Optional[str] = None) -> ParseResult:
294
+ """Parse Rust source code and extract symbols and relationships."""
295
+ if content is None:
296
+ try:
297
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
298
+ content = f.read()
299
+ except Exception as e:
300
+ return ParseResult(
301
+ file_path=file_path,
302
+ language=self.language,
303
+ symbols=[],
304
+ relationships=[],
305
+ errors=[str(e)]
306
+ )
307
+
308
+ symbols = self._extract_symbols(content, file_path)
309
+ relationships = self._extract_relationships(content, file_path, symbols)
310
+
311
+ return ParseResult(
312
+ symbols=symbols,
313
+ relationships=relationships,
314
+ file_path=file_path,
315
+ language=self.language
316
+ )
317
+
318
+ def _extract_symbols(self, content: str, file_path: str) -> List[Symbol]:
319
+ """Extract all symbols from Rust source code."""
320
+ symbols = []
321
+ module_name = self._extract_module_path(file_path)
322
+
323
+ # Extract module declarations
324
+ for match in PATTERNS['mod_declaration'].finditer(content):
325
+ visibility, name = match.groups()
326
+
327
+ symbols.append(self._make_symbol(
328
+ name=name,
329
+ symbol_type=SymbolType.MODULE,
330
+ content=content,
331
+ file_path=file_path,
332
+ start_offset=match.start(),
333
+ end_line=self._find_block_end(content, match.end()) if '{' in match.group() else content[:match.start()].count('\n') + 1,
334
+ full_name=f"{module_name}::{name}" if module_name else name,
335
+ visibility=visibility or 'private',
336
+ metadata={'visibility': visibility or 'private'}
337
+ ))
338
+
339
+ # Extract structs
340
+ for match in PATTERNS['struct'].finditer(content):
341
+ visibility, name = match.groups()
342
+
343
+ docstring = self._find_preceding_doc(content, match.start())
344
+ derives = self._find_derives_before(content, match.start())
345
+
346
+ metadata = {'visibility': visibility or 'private'}
347
+ if derives:
348
+ metadata['derives'] = derives
349
+
350
+ symbols.append(self._make_symbol(
351
+ name=name,
352
+ symbol_type=SymbolType.CLASS,
353
+ content=content,
354
+ file_path=file_path,
355
+ start_offset=match.start(),
356
+ end_line=self._find_block_end(content, match.end()),
357
+ full_name=f"{module_name}::{name}" if module_name else name,
358
+ docstring=docstring,
359
+ visibility=visibility or 'private',
360
+ metadata=metadata
361
+ ))
362
+
363
+ # Extract enums
364
+ for match in PATTERNS['enum'].finditer(content):
365
+ visibility, name = match.groups()
366
+
367
+ docstring = self._find_preceding_doc(content, match.start())
368
+ derives = self._find_derives_before(content, match.start())
369
+
370
+ metadata = {'visibility': visibility or 'private'}
371
+ if derives:
372
+ metadata['derives'] = derives
373
+
374
+ symbols.append(self._make_symbol(
375
+ name=name,
376
+ symbol_type=SymbolType.ENUM,
377
+ content=content,
378
+ file_path=file_path,
379
+ start_offset=match.start(),
380
+ end_line=self._find_block_end(content, match.end()),
381
+ full_name=f"{module_name}::{name}" if module_name else name,
382
+ docstring=docstring,
383
+ visibility=visibility or 'private',
384
+ metadata=metadata
385
+ ))
386
+
387
+ # Extract traits
388
+ for match in PATTERNS['trait'].finditer(content):
389
+ visibility, unsafe_kw, name, supertraits = match.groups()
390
+
391
+ docstring = self._find_preceding_doc(content, match.start())
392
+
393
+ metadata = {'visibility': visibility or 'private'}
394
+ if unsafe_kw:
395
+ metadata['unsafe'] = True
396
+ if supertraits:
397
+ metadata['supertraits'] = [s.strip() for s in supertraits.split('+')]
398
+
399
+ symbols.append(self._make_symbol(
400
+ name=name,
401
+ symbol_type=SymbolType.INTERFACE,
402
+ content=content,
403
+ file_path=file_path,
404
+ start_offset=match.start(),
405
+ end_line=self._find_block_end(content, match.end()),
406
+ full_name=f"{module_name}::{name}" if module_name else name,
407
+ docstring=docstring,
408
+ visibility=visibility or 'private',
409
+ metadata=metadata
410
+ ))
411
+
412
+ # Extract functions
413
+ for match in PATTERNS['function'].finditer(content):
414
+ visibility, modifiers, name, return_type = match.groups()
415
+
416
+ docstring = self._find_preceding_doc(content, match.start())
417
+
418
+ metadata = {'visibility': visibility or 'private'}
419
+ is_async = False
420
+ if modifiers:
421
+ modifier_list = modifiers.strip().split()
422
+ metadata['modifiers'] = modifier_list
423
+ if 'async' in modifiers:
424
+ is_async = True
425
+ metadata['is_async'] = True
426
+ if 'unsafe' in modifiers:
427
+ metadata['is_unsafe'] = True
428
+ if 'const' in modifiers:
429
+ metadata['is_const'] = True
430
+ if return_type:
431
+ metadata['return_type'] = return_type.strip()
432
+
433
+ symbols.append(self._make_symbol(
434
+ name=name,
435
+ symbol_type=SymbolType.FUNCTION,
436
+ content=content,
437
+ file_path=file_path,
438
+ start_offset=match.start(),
439
+ end_line=self._find_block_end(content, match.end()),
440
+ full_name=f"{module_name}::{name}" if module_name else name,
441
+ docstring=docstring,
442
+ visibility=visibility or 'private',
443
+ is_async=is_async,
444
+ return_type=return_type.strip() if return_type else None,
445
+ metadata=metadata
446
+ ))
447
+
448
+ # Extract type aliases
449
+ for match in PATTERNS['type_alias'].finditer(content):
450
+ visibility, name, aliased_type = match.groups()
451
+
452
+ symbols.append(self._make_symbol(
453
+ name=name,
454
+ symbol_type=SymbolType.TYPE_ALIAS,
455
+ content=content,
456
+ file_path=file_path,
457
+ start_offset=match.start(),
458
+ end_line=content[:match.start()].count('\n') + 1,
459
+ full_name=f"{module_name}::{name}" if module_name else name,
460
+ visibility=visibility or 'private',
461
+ metadata={
462
+ 'visibility': visibility or 'private',
463
+ 'aliased_type': aliased_type.strip()
464
+ }
465
+ ))
466
+
467
+ # Extract constants
468
+ for match in PATTERNS['const'].finditer(content):
469
+ visibility, name, const_type = match.groups()
470
+
471
+ symbols.append(self._make_symbol(
472
+ name=name,
473
+ symbol_type=SymbolType.CONSTANT,
474
+ content=content,
475
+ file_path=file_path,
476
+ start_offset=match.start(),
477
+ end_line=content[:match.start()].count('\n') + 1,
478
+ full_name=f"{module_name}::{name}" if module_name else name,
479
+ visibility=visibility or 'private',
480
+ metadata={
481
+ 'visibility': visibility or 'private',
482
+ 'type': const_type.strip()
483
+ }
484
+ ))
485
+
486
+ # Extract statics
487
+ for match in PATTERNS['static'].finditer(content):
488
+ visibility, name, static_type = match.groups()
489
+
490
+ symbols.append(self._make_symbol(
491
+ name=name,
492
+ symbol_type=SymbolType.VARIABLE,
493
+ content=content,
494
+ file_path=file_path,
495
+ start_offset=match.start(),
496
+ end_line=content[:match.start()].count('\n') + 1,
497
+ full_name=f"{module_name}::{name}" if module_name else name,
498
+ visibility=visibility or 'private',
499
+ is_static=True,
500
+ metadata={
501
+ 'visibility': visibility or 'private',
502
+ 'type': static_type.strip(),
503
+ 'is_static': True
504
+ }
505
+ ))
506
+
507
+ # Extract macro_rules definitions
508
+ for match in PATTERNS['macro_rules'].finditer(content):
509
+ name = match.group(1)
510
+
511
+ docstring = self._find_preceding_doc(content, match.start())
512
+
513
+ symbols.append(self._make_symbol(
514
+ name=name,
515
+ symbol_type=SymbolType.FUNCTION,
516
+ content=content,
517
+ file_path=file_path,
518
+ start_offset=match.start(),
519
+ end_line=self._find_block_end(content, match.end()),
520
+ full_name=f"{module_name}::{name}" if module_name else name,
521
+ docstring=docstring,
522
+ metadata={'is_macro': True}
523
+ ))
524
+
525
+ return symbols
526
+
527
+ def _extract_relationships(
528
+ self,
529
+ content: str,
530
+ file_path: str,
531
+ symbols: List[Symbol]
532
+ ) -> List[Relationship]:
533
+ """Extract relationships from Rust source code."""
534
+ relationships = []
535
+ current_scope = Path(file_path).stem
536
+
537
+ # Extract use statements
538
+ for match in PATTERNS['use'].finditer(content):
539
+ visibility, path, items, alias = match.groups()
540
+
541
+ if items:
542
+ # Multiple imports: use foo::{bar, baz}
543
+ for item in items.split(','):
544
+ item = item.strip()
545
+ if item:
546
+ relationships.append(self._make_relationship(
547
+ source=current_scope,
548
+ target=f"{path}::{item}",
549
+ rel_type=RelationshipType.IMPORTS,
550
+ file_path=file_path,
551
+ content=content,
552
+ offset=match.start()
553
+ ))
554
+ else:
555
+ # Single import
556
+ relationships.append(self._make_relationship(
557
+ source=current_scope,
558
+ target=path,
559
+ rel_type=RelationshipType.IMPORTS,
560
+ file_path=file_path,
561
+ content=content,
562
+ offset=match.start(),
563
+ metadata={'alias': alias} if alias else None
564
+ ))
565
+
566
+ # Extract glob imports
567
+ for match in PATTERNS['use_glob'].finditer(content):
568
+ path = match.group(1)
569
+
570
+ relationships.append(self._make_relationship(
571
+ source=current_scope,
572
+ target=path,
573
+ rel_type=RelationshipType.IMPORTS,
574
+ file_path=file_path,
575
+ content=content,
576
+ offset=match.start(),
577
+ metadata={'glob': True}
578
+ ))
579
+
580
+ # Extract impl blocks (trait implementations)
581
+ for match in PATTERNS['impl'].finditer(content):
582
+ unsafe_kw, trait_name, type_name = match.groups()
583
+
584
+ if trait_name:
585
+ # Trait implementation
586
+ relationships.append(self._make_relationship(
587
+ source=type_name,
588
+ target=trait_name,
589
+ rel_type=RelationshipType.IMPLEMENTATION,
590
+ file_path=file_path,
591
+ content=content,
592
+ offset=match.start(),
593
+ metadata={'unsafe': True} if unsafe_kw else None
594
+ ))
595
+
596
+ # Extract trait supertraits
597
+ for match in PATTERNS['trait'].finditer(content):
598
+ _, _, trait_name, supertraits = match.groups()
599
+ if supertraits:
600
+ for supertrait in supertraits.split('+'):
601
+ supertrait = supertrait.strip()
602
+ if supertrait and not supertrait.startswith("'"): # Skip lifetimes
603
+ relationships.append(self._make_relationship(
604
+ source=trait_name,
605
+ target=supertrait,
606
+ rel_type=RelationshipType.INHERITANCE,
607
+ file_path=file_path,
608
+ content=content,
609
+ offset=match.start()
610
+ ))
611
+
612
+ # Extract derive macro usages
613
+ for match in PATTERNS['derive'].finditer(content):
614
+ derives = match.group(1)
615
+
616
+ for derive in derives.split(','):
617
+ derive = derive.strip()
618
+ if derive:
619
+ relationships.append(self._make_relationship(
620
+ source=current_scope,
621
+ target=derive,
622
+ rel_type=RelationshipType.DECORATES,
623
+ file_path=file_path,
624
+ content=content,
625
+ offset=match.start(),
626
+ metadata={'derive': True}
627
+ ))
628
+
629
+ # Extract attribute macro usages
630
+ symbol_names = {s.name for s in symbols}
631
+ for match in PATTERNS['attribute'].finditer(content):
632
+ attr = match.group(1)
633
+
634
+ # Skip built-in attributes
635
+ if attr not in {
636
+ 'derive', 'cfg', 'test', 'bench', 'allow', 'warn', 'deny', 'forbid',
637
+ 'deprecated', 'must_use', 'doc', 'inline', 'cold', 'link', 'link_name',
638
+ 'no_mangle', 'repr', 'path', 'macro_use', 'macro_export', 'global_allocator',
639
+ 'feature', 'non_exhaustive', 'target_feature', 'track_caller'
640
+ }:
641
+ relationships.append(self._make_relationship(
642
+ source=current_scope,
643
+ target=attr,
644
+ rel_type=RelationshipType.DECORATES,
645
+ file_path=file_path,
646
+ content=content,
647
+ offset=match.start()
648
+ ))
649
+
650
+ # Extract function calls
651
+ for match in PATTERNS['function_call'].finditer(content):
652
+ func_name = match.group(1)
653
+
654
+ # Skip keywords and common functions
655
+ if func_name not in symbol_names and func_name not in {
656
+ 'if', 'match', 'while', 'for', 'loop', 'return', 'break', 'continue',
657
+ 'Some', 'None', 'Ok', 'Err', 'Box', 'Vec', 'String', 'Option', 'Result',
658
+ 'println', 'print', 'eprintln', 'eprint', 'format', 'panic', 'assert',
659
+ 'assert_eq', 'assert_ne', 'debug_assert', 'unreachable', 'unimplemented',
660
+ 'todo', 'cfg', 'env', 'concat', 'stringify', 'include', 'include_str',
661
+ 'include_bytes', 'file', 'line', 'column', 'module_path'
662
+ }:
663
+ relationships.append(self._make_relationship(
664
+ source=current_scope,
665
+ target=func_name,
666
+ rel_type=RelationshipType.CALLS,
667
+ file_path=file_path,
668
+ content=content,
669
+ offset=match.start()
670
+ ))
671
+
672
+ # Extract macro calls
673
+ for match in PATTERNS['macro_call'].finditer(content):
674
+ macro_name = match.group(1)
675
+
676
+ # Skip common std macros
677
+ if macro_name not in {
678
+ 'println', 'print', 'eprintln', 'eprint', 'format', 'panic', 'assert',
679
+ 'assert_eq', 'assert_ne', 'debug_assert', 'debug_assert_eq', 'debug_assert_ne',
680
+ 'unreachable', 'unimplemented', 'todo', 'cfg', 'env', 'concat', 'stringify',
681
+ 'include', 'include_str', 'include_bytes', 'file', 'line', 'column',
682
+ 'module_path', 'vec', 'format_args', 'write', 'writeln', 'try', 'matches'
683
+ }:
684
+ relationships.append(self._make_relationship(
685
+ source=current_scope,
686
+ target=macro_name,
687
+ rel_type=RelationshipType.CALLS,
688
+ file_path=file_path,
689
+ content=content,
690
+ offset=match.start(),
691
+ metadata={'macro': True}
692
+ ))
693
+
694
+ # Extract struct instantiations
695
+ for match in PATTERNS['struct_init'].finditer(content):
696
+ struct_name = match.group(1)
697
+
698
+ if struct_name not in symbol_names:
699
+ relationships.append(self._make_relationship(
700
+ source=current_scope,
701
+ target=struct_name,
702
+ rel_type=RelationshipType.USES,
703
+ file_path=file_path,
704
+ content=content,
705
+ offset=match.start()
706
+ ))
707
+
708
+ # Extract trait bound references
709
+ for match in PATTERNS['trait_bound'].finditer(content):
710
+ trait_name = match.group(1)
711
+
712
+ relationships.append(self._make_relationship(
713
+ source=current_scope,
714
+ target=trait_name,
715
+ rel_type=RelationshipType.REFERENCES,
716
+ file_path=file_path,
717
+ content=content,
718
+ offset=match.start(),
719
+ metadata={'trait_bound': True}
720
+ ))
721
+
722
+ # Extract path expressions (crate paths)
723
+ for match in PATTERNS['path_expr'].finditer(content):
724
+ path = match.group(1)
725
+
726
+ # Get the first component
727
+ first_component = path.split('::')[0]
728
+ if first_component not in {'self', 'super', 'crate', 'std', 'core', 'alloc'}:
729
+ relationships.append(self._make_relationship(
730
+ source=current_scope,
731
+ target=path,
732
+ rel_type=RelationshipType.REFERENCES,
733
+ file_path=file_path,
734
+ content=content,
735
+ offset=match.start()
736
+ ))
737
+
738
+ return relationships
739
+
740
+ def _extract_module_path(self, file_path: str) -> Optional[str]:
741
+ """Extract module path from file path."""
742
+ path = Path(file_path)
743
+
744
+ # Get relative path components
745
+ parts = []
746
+ current = path
747
+ while current.name not in {'src', 'lib', 'bin', ''}:
748
+ if current.stem not in {'mod', 'lib', 'main'}:
749
+ parts.insert(0, current.stem)
750
+ current = current.parent
751
+
752
+ return '::'.join(parts) if parts else None
753
+
754
+ def _find_preceding_doc(self, content: str, position: int) -> Optional[str]:
755
+ """Find doc comment preceding a position."""
756
+ before = content[:position]
757
+ lines = before.split('\n')
758
+
759
+ # Look for /// comments in preceding lines
760
+ doc_lines = []
761
+ for line in reversed(lines[:-1]): # Skip current line
762
+ stripped = line.strip()
763
+ if stripped.startswith('///'):
764
+ doc_lines.insert(0, stripped[3:].strip())
765
+ elif stripped.startswith('#['):
766
+ # Skip attributes
767
+ continue
768
+ elif stripped == '':
769
+ continue
770
+ else:
771
+ break
772
+
773
+ return '\n'.join(doc_lines) if doc_lines else None
774
+
775
+ def _find_derives_before(self, content: str, position: int) -> List[str]:
776
+ """Find derive attributes before a position."""
777
+ before = content[:position]
778
+
779
+ # Look for #[derive(...)] in the preceding content
780
+ derives = []
781
+ for match in PATTERNS['derive'].finditer(before[-500:]): # Look in last 500 chars
782
+ for derive in match.group(1).split(','):
783
+ derive = derive.strip()
784
+ if derive:
785
+ derives.append(derive)
786
+
787
+ return derives
788
+
789
+ def _find_block_end(self, content: str, start: int) -> int:
790
+ """Find the end line of a code block."""
791
+ brace_count = 0
792
+ in_block = False
793
+
794
+ for i, char in enumerate(content[start:], start):
795
+ if char == '{':
796
+ brace_count += 1
797
+ in_block = True
798
+ elif char == '}':
799
+ brace_count -= 1
800
+ if in_block and brace_count == 0:
801
+ return content[:i].count('\n') + 1
802
+
803
+ return content[:start].count('\n') + 1
804
+
805
+ def parse_multiple_files(
806
+ self,
807
+ files: List[Tuple[str, Optional[str]]],
808
+ resolve_cross_file: bool = True,
809
+ max_workers: int = 4
810
+ ) -> Dict[str, ParseResult]:
811
+ """
812
+ Parse multiple Rust files with optional cross-file resolution.
813
+
814
+ Args:
815
+ files: List of (file_path, content) tuples
816
+ resolve_cross_file: Whether to resolve cross-file references
817
+ max_workers: Maximum number of parallel workers
818
+
819
+ Returns:
820
+ Dict mapping file paths to ParseResult objects
821
+ """
822
+ results = {}
823
+
824
+ def parse_single(file_info: Tuple[str, Optional[str]]) -> Tuple[str, Optional[ParseResult]]:
825
+ file_path, content = file_info
826
+ if content is None:
827
+ try:
828
+ with open(file_path, 'r', encoding='utf-8') as f:
829
+ content = f.read()
830
+ except Exception as e:
831
+ logger.warning(f"Failed to read {file_path}: {e}")
832
+ return file_path, None
833
+
834
+ try:
835
+ result = self.parse(content, file_path)
836
+ return file_path, result
837
+ except Exception as e:
838
+ logger.warning(f"Failed to parse {file_path}: {e}")
839
+ return file_path, None
840
+
841
+ # Parse files in parallel
842
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
843
+ for file_path, result in executor.map(parse_single, files):
844
+ if result:
845
+ results[file_path] = result
846
+
847
+ # Register symbols for cross-file resolution
848
+ if resolve_cross_file:
849
+ for symbol in result.symbols:
850
+ self._symbol_to_file[symbol.name] = file_path
851
+ if symbol.qualified_name:
852
+ self._symbol_to_file[symbol.qualified_name] = file_path
853
+
854
+ return results
855
+
856
+
857
+ # Register the parser
858
+ parser_registry.register_parser(RustParser())