jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.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 (115) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +289 -87
  3. jarvis/jarvis_agent/agent_manager.py +17 -8
  4. jarvis/jarvis_agent/edit_file_handler.py +374 -86
  5. jarvis/jarvis_agent/event_bus.py +1 -1
  6. jarvis/jarvis_agent/file_context_handler.py +79 -0
  7. jarvis/jarvis_agent/jarvis.py +601 -43
  8. jarvis/jarvis_agent/main.py +32 -2
  9. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  10. jarvis/jarvis_agent/run_loop.py +38 -5
  11. jarvis/jarvis_agent/share_manager.py +8 -1
  12. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  13. jarvis/jarvis_agent/task_analyzer.py +5 -2
  14. jarvis/jarvis_agent/task_planner.py +496 -0
  15. jarvis/jarvis_agent/utils.py +5 -1
  16. jarvis/jarvis_agent/web_bridge.py +189 -0
  17. jarvis/jarvis_agent/web_output_sink.py +53 -0
  18. jarvis/jarvis_agent/web_server.py +751 -0
  19. jarvis/jarvis_c2rust/__init__.py +26 -0
  20. jarvis/jarvis_c2rust/cli.py +613 -0
  21. jarvis/jarvis_c2rust/collector.py +258 -0
  22. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  23. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  24. jarvis/jarvis_c2rust/optimizer.py +960 -0
  25. jarvis/jarvis_c2rust/scanner.py +1681 -0
  26. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  27. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  28. jarvis/jarvis_code_agent/code_agent.py +1171 -94
  29. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  30. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  31. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  32. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  33. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  34. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  35. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  36. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  37. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  38. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  39. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  40. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  41. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  42. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  43. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  44. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  45. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  46. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  47. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  48. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  49. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  50. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  51. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  52. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  53. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  54. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  55. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  56. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  57. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  58. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  59. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  60. jarvis/jarvis_code_agent/lint.py +270 -8
  61. jarvis/jarvis_code_agent/utils.py +142 -0
  62. jarvis/jarvis_code_analysis/code_review.py +483 -569
  63. jarvis/jarvis_data/config_schema.json +97 -8
  64. jarvis/jarvis_git_utils/git_commiter.py +38 -26
  65. jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
  66. jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
  67. jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
  68. jarvis/jarvis_multi_agent/__init__.py +239 -25
  69. jarvis/jarvis_multi_agent/main.py +37 -1
  70. jarvis/jarvis_platform/base.py +103 -51
  71. jarvis/jarvis_platform/openai.py +26 -1
  72. jarvis/jarvis_platform/yuanbao.py +1 -1
  73. jarvis/jarvis_platform_manager/service.py +2 -2
  74. jarvis/jarvis_rag/cli.py +4 -4
  75. jarvis/jarvis_sec/__init__.py +3605 -0
  76. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  77. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  78. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  79. jarvis/jarvis_sec/cli.py +116 -0
  80. jarvis/jarvis_sec/report.py +257 -0
  81. jarvis/jarvis_sec/status.py +264 -0
  82. jarvis/jarvis_sec/types.py +20 -0
  83. jarvis/jarvis_sec/workflow.py +219 -0
  84. jarvis/jarvis_stats/cli.py +1 -1
  85. jarvis/jarvis_stats/stats.py +1 -1
  86. jarvis/jarvis_stats/visualizer.py +1 -1
  87. jarvis/jarvis_tools/cli/main.py +1 -0
  88. jarvis/jarvis_tools/execute_script.py +46 -9
  89. jarvis/jarvis_tools/generate_new_tool.py +3 -1
  90. jarvis/jarvis_tools/read_code.py +275 -12
  91. jarvis/jarvis_tools/read_symbols.py +141 -0
  92. jarvis/jarvis_tools/read_webpage.py +5 -3
  93. jarvis/jarvis_tools/registry.py +73 -35
  94. jarvis/jarvis_tools/search_web.py +15 -11
  95. jarvis/jarvis_tools/sub_agent.py +24 -42
  96. jarvis/jarvis_tools/sub_code_agent.py +14 -13
  97. jarvis/jarvis_tools/virtual_tty.py +1 -1
  98. jarvis/jarvis_utils/config.py +187 -35
  99. jarvis/jarvis_utils/embedding.py +3 -0
  100. jarvis/jarvis_utils/git_utils.py +181 -6
  101. jarvis/jarvis_utils/globals.py +3 -3
  102. jarvis/jarvis_utils/http.py +1 -1
  103. jarvis/jarvis_utils/input.py +78 -2
  104. jarvis/jarvis_utils/methodology.py +25 -19
  105. jarvis/jarvis_utils/utils.py +644 -359
  106. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
  107. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  108. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
  109. jarvis/jarvis_agent/config.py +0 -92
  110. jarvis/jarvis_tools/edit_file.py +0 -179
  111. jarvis/jarvis_tools/rewrite_file.py +0 -191
  112. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  113. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  114. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  115. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2325 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ C2Rust 转译器模块
4
+
5
+ 目标:
6
+ - 基于 scanner 生成的 translation_order.jsonl 顺序,逐个函数进行转译
7
+ - 为每个函数:
8
+ 1) 准备上下文:C 源码片段+位置信息、被调用符号(若已转译则提供Rust模块与符号,否则提供原C位置信息)、crate目录结构
9
+ 2) 创建“模块选择与签名Agent”:让其选择合适的Rust模块路径,并在summary输出函数签名
10
+ 3) 记录当前进度到 progress.json
11
+ 4) 基于上述信息与落盘位置,创建 CodeAgent 生成转译后的Rust函数
12
+ 5) 尝试 cargo build,如失败则携带错误上下文创建 CodeAgent 修复,直到构建通过或达到上限
13
+ 6) 创建代码审查Agent;若 summary 指出问题,则 CodeAgent 优化,直到 summary 表示无问题
14
+ 7) 标记函数已转译,并记录 C 符号 -> Rust 符号/模块映射到 symbol_map.jsonl(JSONL,每行一条映射,支持重复与重载)
15
+
16
+ 说明:
17
+ - 本模块提供 run_transpile(...) 作为对外入口,后续在 cli.py 中挂载为子命令
18
+ - 尽量复用现有 Agent/CodeAgent 能力,保持最小侵入与稳定性
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+ import re
25
+ import subprocess
26
+ import time
27
+ from dataclasses import dataclass
28
+ from pathlib import Path
29
+ from typing import Any, Dict, List, Optional, Tuple, Union, Set
30
+
31
+ import typer
32
+
33
+ from jarvis.jarvis_c2rust.scanner import compute_translation_order_jsonl
34
+ from jarvis.jarvis_agent import Agent
35
+ from jarvis.jarvis_code_agent.code_agent import CodeAgent
36
+
37
+
38
+ # 数据文件常量
39
+ C2RUST_DIRNAME = ".jarvis/c2rust"
40
+
41
+ SYMBOLS_JSONL = "symbols.jsonl"
42
+ ORDER_JSONL = "translation_order.jsonl"
43
+ PROGRESS_JSON = "progress.json"
44
+ SYMBOL_MAP_JSONL = "symbol_map.jsonl"
45
+ # 兼容旧版:若存在 symbol_map.json 也尝试加载(只读)
46
+ LEGACY_SYMBOL_MAP_JSON = "symbol_map.json"
47
+
48
+ # 配置常量
49
+ ERROR_SUMMARY_MAX_LENGTH = 2000 # 错误信息摘要最大长度
50
+ DEFAULT_PLAN_MAX_RETRIES = 0 # 规划阶段默认最大重试次数(0表示无限重试)
51
+ DEFAULT_REVIEW_MAX_ITERATIONS = 0 # 审查阶段最大迭代次数(0表示无限重试)
52
+ DEFAULT_CHECK_MAX_RETRIES = 0 # cargo check 阶段默认最大重试次数(0表示无限重试)
53
+ DEFAULT_TEST_MAX_RETRIES = 0 # cargo test 阶段默认最大重试次数(0表示无限重试)
54
+
55
+
56
+ @dataclass
57
+ class FnRecord:
58
+ id: int
59
+ name: str
60
+ qname: str
61
+ file: str
62
+ start_line: int
63
+ start_col: int
64
+ end_line: int
65
+ end_col: int
66
+ refs: List[str]
67
+ # 额外元信息(来自 symbols/items):函数签名、返回类型与参数(可选)
68
+ signature: str = ""
69
+ return_type: str = ""
70
+ params: Optional[List[Dict[str, str]]] = None
71
+ # 来自库替代阶段的上下文元数据(若存在)
72
+ lib_replacement: Optional[Dict[str, Any]] = None
73
+
74
+
75
+ class _DbLoader:
76
+ """读取 symbols.jsonl 并提供按 id/name 查询与源码片段读取"""
77
+
78
+ def __init__(self, project_root: Union[str, Path]) -> None:
79
+ self.project_root = Path(project_root).resolve()
80
+ self.data_dir = self.project_root / C2RUST_DIRNAME
81
+
82
+ self.symbols_path = self.data_dir / SYMBOLS_JSONL
83
+ # 统一流程:仅使用 symbols.jsonl,不再兼容 functions.jsonl
84
+ if not self.symbols_path.exists():
85
+ raise FileNotFoundError(
86
+ f"在目录下未找到 symbols.jsonl: {self.data_dir}"
87
+ )
88
+
89
+ self.fn_by_id: Dict[int, FnRecord] = {}
90
+ self.name_to_id: Dict[str, int] = {}
91
+ self._load()
92
+
93
+ def _load(self) -> None:
94
+ """
95
+ 读取统一的 symbols.jsonl。
96
+ 不区分函数与类型定义,均加载为通用记录(位置与引用信息)。
97
+ """
98
+ def _iter_records_from_file(path: Path):
99
+ try:
100
+ with path.open("r", encoding="utf-8") as f:
101
+ idx = 0
102
+ for line in f:
103
+ line = line.strip()
104
+ if not line:
105
+ continue
106
+ try:
107
+ obj = json.loads(line)
108
+ except Exception:
109
+ continue
110
+ idx += 1
111
+ yield idx, obj
112
+ except FileNotFoundError:
113
+ return
114
+
115
+ # 加载所有符号记录(函数、类型等)
116
+ for idx, obj in _iter_records_from_file(self.symbols_path):
117
+ fid = int(obj.get("id") or idx)
118
+ nm = obj.get("name") or ""
119
+ qn = obj.get("qualified_name") or ""
120
+ fp = obj.get("file") or ""
121
+ refs = obj.get("ref")
122
+ # 统一使用列表类型的引用字段
123
+ if not isinstance(refs, list):
124
+ refs = []
125
+ refs = [c for c in refs if isinstance(c, str) and c]
126
+ sr = int(obj.get("start_line") or 0)
127
+ sc = int(obj.get("start_col") or 0)
128
+ er = int(obj.get("end_line") or 0)
129
+ ec = int(obj.get("end_col") or 0)
130
+ lr = obj.get("lib_replacement") if isinstance(obj.get("lib_replacement"), dict) else None
131
+ rec = FnRecord(
132
+ id=fid,
133
+ name=nm,
134
+ qname=qn,
135
+ file=fp,
136
+ start_line=sr,
137
+ start_col=sc,
138
+ end_line=er,
139
+ end_col=ec,
140
+ refs=refs,
141
+ lib_replacement=lr,
142
+ )
143
+ self.fn_by_id[fid] = rec
144
+ if nm:
145
+ self.name_to_id.setdefault(nm, fid)
146
+ if qn:
147
+ self.name_to_id.setdefault(qn, fid)
148
+
149
+ def get(self, fid: int) -> Optional[FnRecord]:
150
+ return self.fn_by_id.get(fid)
151
+
152
+ def get_id_by_name(self, name_or_qname: str) -> Optional[int]:
153
+ return self.name_to_id.get(name_or_qname)
154
+
155
+ def read_source_span(self, rec: FnRecord) -> str:
156
+ """按起止行读取源码片段(忽略列边界,尽量完整)"""
157
+ try:
158
+ p = Path(rec.file)
159
+ # 若记录为相对路径,基于 project_root 解析
160
+ if not p.is_absolute():
161
+ p = (self.project_root / p).resolve()
162
+ if not p.exists():
163
+ return ""
164
+ lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
165
+ s = max(1, rec.start_line)
166
+ e = min(len(lines), max(rec.end_line, s))
167
+ # Python 索引从0开始,包含终止行
168
+ chunk = "\n".join(lines[s - 1 : e])
169
+ return chunk
170
+ except Exception:
171
+ return ""
172
+
173
+
174
+ class _SymbolMapJsonl:
175
+ """
176
+ JSONL 形式的符号映射管理:
177
+ - 每行一条记录,支持同名函数的多条映射(用于处理重载/同名符号)
178
+ - 记录字段:
179
+ {
180
+ "c_name": "<简单名>",
181
+ "c_qname": "<限定名,可为空字符串>",
182
+ "c_file": "<源文件路径>",
183
+ "start_line": <int>,
184
+ "end_line": <int>,
185
+ "module": "src/xxx.rs 或 src/xxx/mod.rs",
186
+ "rust_symbol": "<Rust函数名>",
187
+ "updated_at": "YYYY-MM-DDTHH:MM:SS"
188
+ }
189
+ - 提供按名称(c_name/c_qname)查询、按源位置判断是否已记录等能力
190
+ """
191
+
192
+ def __init__(self, jsonl_path: Path, legacy_json_path: Optional[Path] = None) -> None:
193
+ self.jsonl_path = jsonl_path
194
+ self.legacy_json_path = legacy_json_path
195
+ self.records: List[Dict[str, Any]] = []
196
+ # 索引:名称 -> 记录列表索引
197
+ self.by_key: Dict[str, List[int]] = {}
198
+ # 唯一定位(避免同名冲突):(c_file, start_line, end_line, c_qname or c_name) -> 记录索引列表
199
+ self.by_pos: Dict[Tuple[str, int, int, str], List[int]] = {}
200
+ self._load()
201
+
202
+ def _load(self) -> None:
203
+ self.records = []
204
+ self.by_key = {}
205
+ self.by_pos = {}
206
+ # 读取 JSONL
207
+ if self.jsonl_path.exists():
208
+ try:
209
+ with self.jsonl_path.open("r", encoding="utf-8") as f:
210
+ for line in f:
211
+ line = line.strip()
212
+ if not line:
213
+ continue
214
+ try:
215
+ obj = json.loads(line)
216
+ except Exception:
217
+ continue
218
+ self._add_record_in_memory(obj)
219
+ except Exception:
220
+ pass
221
+ # 兼容旧版 symbol_map.json(若存在则读入为“最后一条”)
222
+ elif self.legacy_json_path and self.legacy_json_path.exists():
223
+ try:
224
+ legacy = json.loads(self.legacy_json_path.read_text(encoding="utf-8"))
225
+ if isinstance(legacy, dict):
226
+ for k, v in legacy.items():
227
+ rec = {
228
+ "c_name": k,
229
+ "c_qname": k,
230
+ "c_file": "",
231
+ "start_line": 0,
232
+ "end_line": 0,
233
+ "module": v.get("module"),
234
+ "rust_symbol": v.get("rust_symbol"),
235
+ "updated_at": v.get("updated_at"),
236
+ }
237
+ self._add_record_in_memory(rec)
238
+ except Exception:
239
+ pass
240
+
241
+ def _add_record_in_memory(self, rec: Dict[str, Any]) -> None:
242
+ idx = len(self.records)
243
+ self.records.append(rec)
244
+ for key in [rec.get("c_name") or "", rec.get("c_qname") or ""]:
245
+ k = str(key or "").strip()
246
+ if not k:
247
+ continue
248
+ self.by_key.setdefault(k, []).append(idx)
249
+ pos_key = (str(rec.get("c_file") or ""), int(rec.get("start_line") or 0), int(rec.get("end_line") or 0), str(rec.get("c_qname") or rec.get("c_name") or ""))
250
+ self.by_pos.setdefault(pos_key, []).append(idx)
251
+
252
+ def has_symbol(self, sym: str) -> bool:
253
+ return bool(self.by_key.get(sym))
254
+
255
+ def get(self, sym: str) -> List[Dict[str, Any]]:
256
+ idxs = self.by_key.get(sym) or []
257
+ return [self.records[i] for i in idxs]
258
+
259
+ def get_any(self, sym: str) -> Optional[Dict[str, Any]]:
260
+ recs = self.get(sym)
261
+ return recs[-1] if recs else None
262
+
263
+ def has_rec(self, rec: FnRecord) -> bool:
264
+ key = (str(rec.file or ""), int(rec.start_line or 0), int(rec.end_line or 0), str(rec.qname or rec.name or ""))
265
+ return bool(self.by_pos.get(key))
266
+
267
+ def add(self, rec: FnRecord, module: str, rust_symbol: str) -> None:
268
+ obj = {
269
+ "c_name": rec.name or "",
270
+ "c_qname": rec.qname or "",
271
+ "c_file": rec.file or "",
272
+ "start_line": int(rec.start_line or 0),
273
+ "end_line": int(rec.end_line or 0),
274
+ "module": str(module or ""),
275
+ "rust_symbol": str(rust_symbol or (rec.name or f"fn_{rec.id}")),
276
+ "updated_at": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime()),
277
+ }
278
+ # 先写盘,再更新内存索引
279
+ try:
280
+ self.jsonl_path.parent.mkdir(parents=True, exist_ok=True)
281
+ with self.jsonl_path.open("a", encoding="utf-8") as f:
282
+ f.write(json.dumps(obj, ensure_ascii=False) + "\n")
283
+ except Exception:
284
+ pass
285
+ self._add_record_in_memory(obj)
286
+
287
+
288
+ def _ensure_order_file(project_root: Path) -> Path:
289
+ """确保 translation_order.jsonl 存在且包含有效步骤;仅基于 symbols.jsonl 生成,不使用任何回退。"""
290
+ data_dir = project_root / C2RUST_DIRNAME
291
+ order_path = data_dir / ORDER_JSONL
292
+ typer.secho(f"[c2rust-transpiler][order] 目标顺序文件: {order_path}", fg=typer.colors.BLUE)
293
+
294
+ def _has_steps(p: Path) -> bool:
295
+ try:
296
+ steps = _iter_order_steps(p)
297
+ return bool(steps)
298
+ except Exception:
299
+ return False
300
+
301
+ # 已存在则校验是否有步骤
302
+ typer.secho(f"[c2rust-transpiler][order] 检查现有顺序文件有效性: {order_path}", fg=typer.colors.BLUE)
303
+ if order_path.exists():
304
+ if _has_steps(order_path):
305
+ typer.secho(f"[c2rust-transpiler][order] 现有顺序文件有效,将使用 {order_path}", fg=typer.colors.GREEN)
306
+ return order_path
307
+ # 为空或不可读:基于标准路径重新计算(仅 symbols.jsonl)
308
+ typer.secho("[c2rust-transpiler][order] 现有顺序文件为空/无效,正基于 symbols.jsonl 重新计算", fg=typer.colors.YELLOW)
309
+ try:
310
+ compute_translation_order_jsonl(data_dir, out_path=order_path)
311
+ except Exception as e:
312
+ raise RuntimeError(f"重新计算翻译顺序失败: {e}")
313
+ return order_path
314
+
315
+ # 不存在:按标准路径生成到固定文件名(仅 symbols.jsonl)
316
+ try:
317
+ compute_translation_order_jsonl(data_dir, out_path=order_path)
318
+ except Exception as e:
319
+ raise RuntimeError(f"计算翻译顺序失败: {e}")
320
+
321
+ typer.secho(f"[c2rust-transpiler][order] 已生成顺序文件: {order_path} (exists={order_path.exists()})", fg=typer.colors.BLUE)
322
+ if not order_path.exists():
323
+ raise FileNotFoundError(f"计算后未找到 translation_order.jsonl: {order_path}")
324
+
325
+ # 最终校验:若仍无有效步骤,直接报错并提示先执行 scan 或检查 symbols.jsonl
326
+ if not _has_steps(order_path):
327
+ raise RuntimeError("translation_order.jsonl 无有效步骤。请先执行 'jarvis-c2rust scan' 生成 symbols.jsonl 并重试。")
328
+
329
+ return order_path
330
+
331
+ def _iter_order_steps(order_jsonl: Path) -> List[List[int]]:
332
+ """
333
+ 读取翻译顺序(兼容新旧格式),返回按步骤的函数id序列列表。
334
+ 新格式:每行包含 "ids": [int, ...] 以及 "items": [完整符号对象,...]。
335
+ 不再兼容旧格式(不支持 "records"/"symbols" 键)。
336
+ """
337
+ # 旧格式已移除:不再需要基于 symbols.jsonl 的 name->id 映射
338
+
339
+ steps: List[List[int]] = []
340
+ with order_jsonl.open("r", encoding="utf-8") as f:
341
+ for ln in f:
342
+ ln = ln.strip()
343
+ if not ln:
344
+ continue
345
+ try:
346
+ obj = json.loads(ln)
347
+ except Exception:
348
+ continue
349
+
350
+ ids = obj.get("ids")
351
+ if isinstance(ids, list) and ids:
352
+ # 新格式:仅支持 ids
353
+ try:
354
+ ids_int = [int(x) for x in ids if isinstance(x, (int, str)) and str(x).strip()]
355
+ except Exception:
356
+ ids_int = []
357
+ if ids_int:
358
+ steps.append(ids_int)
359
+ continue
360
+ # 不支持旧格式(无 ids 则跳过该行)
361
+ return steps
362
+
363
+
364
+ def _dir_tree(root: Path) -> str:
365
+ """格式化 crate 目录结构(过滤部分常见目录)"""
366
+ lines: List[str] = []
367
+ exclude = {".git", "target", ".jarvis"}
368
+ if not root.exists():
369
+ return ""
370
+ for p in sorted(root.rglob("*")):
371
+ if any(part in exclude for part in p.parts):
372
+ continue
373
+ rel = p.relative_to(root)
374
+ depth = len(rel.parts) - 1
375
+ indent = " " * depth
376
+ name = rel.name + ("/" if p.is_dir() else "")
377
+ lines.append(f"{indent}- {name}")
378
+ return "\n".join(lines)
379
+
380
+
381
+ def _default_crate_dir(project_root: Path) -> Path:
382
+ """遵循 llm_module_agent 的默认crate目录选择:<parent>/<cwd.name>_rs(与当前目录同级)当传入为 '.' 时"""
383
+ try:
384
+ cwd = Path(".").resolve()
385
+ if project_root.resolve() == cwd:
386
+ return cwd.parent / f"{cwd.name}_rs"
387
+ else:
388
+ return project_root
389
+ except Exception:
390
+ return project_root
391
+
392
+
393
+ def _read_json(path: Path, default: Any) -> Any:
394
+ try:
395
+ if path.exists():
396
+ return json.loads(path.read_text(encoding="utf-8"))
397
+ except Exception:
398
+ pass
399
+ return default
400
+
401
+
402
+ def _write_json(path: Path, obj: Any) -> None:
403
+ """原子性写入JSON文件:先写入临时文件,再重命名"""
404
+ try:
405
+ path.parent.mkdir(parents=True, exist_ok=True)
406
+ # 使用临时文件确保原子性
407
+ temp_path = path.with_suffix(path.suffix + ".tmp")
408
+ temp_path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
409
+ # 原子性重命名
410
+ temp_path.replace(path)
411
+ except Exception:
412
+ # 如果原子写入失败,回退到直接写入
413
+ try:
414
+ path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
415
+ except Exception:
416
+ pass
417
+
418
+
419
+ def _extract_json_from_summary(text: str) -> Tuple[Dict[str, Any], Optional[str]]:
420
+ """
421
+ 从 Agent summary 中提取结构化数据(仅支持 YAML):
422
+ - 仅在 <SUMMARY>...</SUMMARY> 块内查找;
423
+ - 只接受 <yaml>...</yaml> 标签包裹的 YAML 对象;
424
+ 返回(解析结果, 错误信息)
425
+ 如果解析成功,返回(data, None)
426
+ 如果解析失败,返回({}, 错误信息)
427
+ """
428
+ if not isinstance(text, str) or not text.strip():
429
+ return {}, "摘要文本为空"
430
+
431
+ # 提取 <SUMMARY> 块
432
+ m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", text, flags=re.IGNORECASE)
433
+ block = (m.group(1) if m else text).strip()
434
+
435
+ # 仅解析 <yaml>...</yaml>
436
+ mm = re.search(r"<yaml>([\s\S]*?)</yaml>", block, flags=re.IGNORECASE)
437
+ raw_yaml = mm.group(1).strip() if mm else None
438
+ if not raw_yaml:
439
+ return {}, "未找到 <yaml> 或 </yaml> 标签,或标签内容为空"
440
+
441
+ try:
442
+ import yaml # type: ignore
443
+ try:
444
+ obj = yaml.safe_load(raw_yaml)
445
+ except Exception as yaml_err:
446
+ error_msg = f"YAML 解析失败: {str(yaml_err)}"
447
+ return {}, error_msg
448
+ if isinstance(obj, dict):
449
+ return obj, None
450
+ return {}, f"YAML 解析结果不是字典,而是 {type(obj).__name__}"
451
+ except Exception as e:
452
+ return {}, f"解析过程发生异常: {str(e)}"
453
+
454
+
455
+ class Transpiler:
456
+ def __init__(
457
+ self,
458
+ project_root: Union[str, Path] = ".",
459
+ crate_dir: Optional[Union[str, Path]] = None,
460
+ llm_group: Optional[str] = None,
461
+ plan_max_retries: int = DEFAULT_PLAN_MAX_RETRIES, # 规划阶段最大重试次数(0表示无限重试)
462
+ max_retries: int = 0, # 兼容旧接口,如未设置则使用 check_max_retries 和 test_max_retries
463
+ check_max_retries: Optional[int] = None, # cargo check 阶段最大重试次数(0表示无限重试)
464
+ test_max_retries: Optional[int] = None, # cargo test 阶段最大重试次数(0表示无限重试)
465
+ review_max_iterations: int = DEFAULT_REVIEW_MAX_ITERATIONS, # 审查阶段最大迭代次数(0表示无限重试)
466
+ resume: bool = True,
467
+ only: Optional[List[str]] = None, # 仅转译指定函数名(简单名或限定名)
468
+ non_interactive: bool = True,
469
+ ) -> None:
470
+ self.project_root = Path(project_root).resolve()
471
+ self.data_dir = self.project_root / C2RUST_DIRNAME
472
+ self.progress_path = self.data_dir / PROGRESS_JSON
473
+ # JSONL 路径
474
+ self.symbol_map_path = self.data_dir / SYMBOL_MAP_JSONL
475
+ # 兼容旧版 JSON 字典格式
476
+ self.legacy_symbol_map_path = self.data_dir / LEGACY_SYMBOL_MAP_JSON
477
+ self.llm_group = llm_group
478
+ self.plan_max_retries = plan_max_retries
479
+ # 兼容旧接口:如果只设置了 max_retries,则同时用于 check 和 test
480
+ if max_retries > 0 and check_max_retries is None and test_max_retries is None:
481
+ self.check_max_retries = max_retries
482
+ self.test_max_retries = max_retries
483
+ else:
484
+ self.check_max_retries = check_max_retries if check_max_retries is not None else DEFAULT_CHECK_MAX_RETRIES
485
+ self.test_max_retries = test_max_retries if test_max_retries is not None else DEFAULT_TEST_MAX_RETRIES
486
+ self.max_retries = max(self.check_max_retries, self.test_max_retries) # 保持兼容性
487
+ self.review_max_iterations = review_max_iterations
488
+ self.resume = resume
489
+ self.only = set(only or [])
490
+ self.non_interactive = non_interactive
491
+ typer.secho(f"[c2rust-transpiler][init] 初始化参数: project_root={self.project_root} crate_dir={Path(crate_dir) if crate_dir else _default_crate_dir(self.project_root)} llm_group={self.llm_group} plan_max_retries={self.plan_max_retries} check_max_retries={self.check_max_retries} test_max_retries={self.test_max_retries} review_max_iterations={self.review_max_iterations} resume={self.resume} only={sorted(list(self.only)) if self.only else []} non_interactive={self.non_interactive}", fg=typer.colors.BLUE)
492
+
493
+ self.crate_dir = Path(crate_dir) if crate_dir else _default_crate_dir(self.project_root)
494
+ # 使用自包含的 order.jsonl 记录构建索引,避免依赖 symbols.jsonl
495
+ self.fn_index_by_id: Dict[int, FnRecord] = {}
496
+ self.fn_name_to_id: Dict[str, int] = {}
497
+
498
+ self.progress: Dict[str, Any] = _read_json(self.progress_path, {"current": None, "converted": []})
499
+ # 使用 JSONL 存储的符号映射
500
+ self.symbol_map = _SymbolMapJsonl(self.symbol_map_path, legacy_json_path=self.legacy_symbol_map_path)
501
+
502
+ # 当前函数上下文与Agent复用缓存(按单个函数生命周期)
503
+ self._current_agents: Dict[str, Any] = {}
504
+ # 全量与精简上下文头部
505
+ self._current_context_full_header: str = ""
506
+ self._current_context_compact_header: str = ""
507
+ # 是否已发送过全量头部(每函数仅一次)
508
+ self._current_context_full_sent: bool = False
509
+ # 兼容旧字段(不再使用)
510
+ self._current_context_header: str = ""
511
+ self._current_function_id: Optional[int] = None
512
+
513
+ def _save_progress(self) -> None:
514
+ """保存进度,使用原子性写入"""
515
+ _write_json(self.progress_path, self.progress)
516
+
517
+ # JSONL 模式下不再整体写回 symbol_map;此方法保留占位(兼容旧调用),无操作
518
+ def _save_symbol_map(self) -> None:
519
+ return
520
+
521
+ def _read_source_span(self, rec: FnRecord) -> str:
522
+ """按起止行读取源码片段(忽略列边界,尽量完整)"""
523
+ try:
524
+ p = Path(rec.file)
525
+ if not p.is_absolute():
526
+ p = (self.project_root / p).resolve()
527
+ if not p.exists():
528
+ return ""
529
+ lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
530
+ s = max(1, int(rec.start_line or 1))
531
+ e = min(len(lines), max(int(rec.end_line or s), s))
532
+ chunk = "\n".join(lines[s - 1 : e])
533
+ return chunk
534
+ except Exception:
535
+ return ""
536
+
537
+ def _load_order_index(self, order_jsonl: Path) -> None:
538
+ """
539
+ 从自包含的 order.jsonl 中加载所有 records,建立:
540
+ - fn_index_by_id: id -> FnRecord
541
+ - fn_name_to_id: name/qname -> id
542
+ 若同一 id 多次出现,首次记录为准。
543
+ """
544
+ self.fn_index_by_id.clear()
545
+ self.fn_name_to_id.clear()
546
+ typer.secho(f"[c2rust-transpiler][index] 正在加载翻译顺序索引: {order_jsonl}", fg=typer.colors.BLUE)
547
+ try:
548
+ with order_jsonl.open("r", encoding="utf-8") as f:
549
+ for ln in f:
550
+ ln = ln.strip()
551
+ if not ln:
552
+ continue
553
+ try:
554
+ obj = json.loads(ln)
555
+ except Exception:
556
+ continue
557
+ # 仅支持新格式:items
558
+ recs = obj.get("items")
559
+ if not isinstance(recs, list):
560
+ continue
561
+ for r in recs:
562
+ if not isinstance(r, dict):
563
+ continue
564
+ # 构建 FnRecord
565
+ try:
566
+ fid = int(r.get("id"))
567
+ except Exception:
568
+ continue
569
+ if fid in self.fn_index_by_id:
570
+ # 已收录
571
+ continue
572
+ nm = r.get("name") or ""
573
+ qn = r.get("qualified_name") or ""
574
+ fp = r.get("file") or ""
575
+ refs = r.get("ref")
576
+ if not isinstance(refs, list):
577
+ refs = []
578
+ refs = [c for c in refs if isinstance(c, str) and c]
579
+ sr = int(r.get("start_line") or 0)
580
+ sc = int(r.get("start_col") or 0)
581
+ er = int(r.get("end_line") or 0)
582
+ ec = int(r.get("end_col") or 0)
583
+ sg = r.get("signature") or ""
584
+ rt = r.get("return_type") or ""
585
+ pr = r.get("params") if isinstance(r.get("params"), list) else None
586
+ lr = r.get("lib_replacement") if isinstance(r.get("lib_replacement"), dict) else None
587
+ rec = FnRecord(
588
+ id=fid,
589
+ name=nm,
590
+ qname=qn,
591
+ file=fp,
592
+ start_line=sr,
593
+ start_col=sc,
594
+ end_line=er,
595
+ end_col=ec,
596
+ refs=refs,
597
+ signature=str(sg or ""),
598
+ return_type=str(rt or ""),
599
+ params=pr,
600
+ lib_replacement=lr,
601
+ )
602
+ self.fn_index_by_id[fid] = rec
603
+ if nm:
604
+ self.fn_name_to_id.setdefault(nm, fid)
605
+ if qn:
606
+ self.fn_name_to_id.setdefault(qn, fid)
607
+ except Exception:
608
+ # 若索引构建失败,保持为空,后续流程将跳过
609
+ pass
610
+ typer.secho(f"[c2rust-transpiler][index] 索引构建完成: ids={len(self.fn_index_by_id)} names={len(self.fn_name_to_id)}", fg=typer.colors.BLUE)
611
+
612
+ def _should_skip(self, rec: FnRecord) -> bool:
613
+ # 如果 only 列表非空,则仅处理匹配者
614
+ if self.only:
615
+ if rec.name in self.only or rec.qname in self.only:
616
+ pass
617
+ else:
618
+ return True
619
+ # 已转译的跳过(按源位置与名称唯一性判断,避免同名不同位置的误判)
620
+ if self.symbol_map.has_rec(rec):
621
+ return True
622
+ return False
623
+
624
+ def _collect_callees_context(self, rec: FnRecord) -> List[Dict[str, Any]]:
625
+ """
626
+ 生成被引用符号上下文列表(不区分函数与类型):
627
+ - 若已转译:提供 {name, qname, translated: true, rust_module, rust_symbol, ambiguous?}
628
+ - 若未转译但存在扫描记录:提供 {name, qname, translated: false, file, start_line, end_line}
629
+ - 若仅名称:提供 {name, qname, translated: false}
630
+ 注:若存在同名映射多条记录(重载/同名符号),此处标记 ambiguous=true,并选择最近一条作为提示。
631
+ """
632
+ ctx: List[Dict[str, Any]] = []
633
+ for callee in rec.refs or []:
634
+ entry: Dict[str, Any] = {"name": callee, "qname": callee}
635
+ # 已转译映射
636
+ if self.symbol_map.has_symbol(callee):
637
+ recs = self.symbol_map.get(callee)
638
+ m = recs[-1] if recs else None
639
+ entry.update({
640
+ "translated": True,
641
+ "rust_module": (m or {}).get("module"),
642
+ "rust_symbol": (m or {}).get("rust_symbol"),
643
+ })
644
+ if len(recs) > 1:
645
+ entry["ambiguous"] = True
646
+ ctx.append(entry)
647
+ continue
648
+ # 使用 order 索引按名称解析ID(函数或类型)
649
+ cid = self.fn_name_to_id.get(callee)
650
+ if cid:
651
+ crec = self.fn_index_by_id.get(cid)
652
+ if crec:
653
+ entry.update({
654
+ "translated": False,
655
+ "file": crec.file,
656
+ "start_line": crec.start_line,
657
+ "end_line": crec.end_line,
658
+ })
659
+ else:
660
+ entry.update({"translated": False})
661
+ ctx.append(entry)
662
+ return ctx
663
+
664
+ def _untranslated_callee_symbols(self, rec: FnRecord) -> List[str]:
665
+ """
666
+ 返回尚未转换的被调函数符号(使用扫描记录中的名称/限定名作为键)
667
+ """
668
+ syms: List[str] = []
669
+ for callee in rec.refs or []:
670
+ if not self.symbol_map.has_symbol(callee):
671
+ syms.append(callee)
672
+ # 去重
673
+ try:
674
+ syms = list(dict.fromkeys(syms))
675
+ except Exception:
676
+ syms = sorted(list(set(syms)))
677
+ return syms
678
+
679
+ def _build_module_selection_prompts(
680
+ self,
681
+ rec: FnRecord,
682
+ c_code: str,
683
+ callees_ctx: List[Dict[str, Any]],
684
+ crate_tree: str,
685
+ ) -> Tuple[str, str, str]:
686
+ """
687
+ 返回 (system_prompt, user_prompt, summary_prompt)
688
+ 要求 summary 输出 YAML:
689
+ {
690
+ "module": "src/<path>.rs or module path (e.g., src/foo/mod.rs or src/foo/bar.rs)",
691
+ "rust_signature": "pub fn ...",
692
+ "notes": "optional"
693
+ }
694
+ """
695
+ system_prompt = (
696
+ "你是资深Rust工程师,擅长为C/C++函数选择合适的Rust模块位置并产出对应的Rust函数签名。\n"
697
+ "目标:根据提供的C源码、调用者上下文与crate目录结构,为该函数选择合适的Rust模块文件并给出Rust函数签名(不实现)。\n"
698
+ "原则:\n"
699
+ "- 按功能内聚与依赖方向选择模块,避免循环依赖;\n"
700
+ "- 模块路径必须落在 crate 的 src/ 下,优先放置到已存在的模块中;必要时可建议创建新的子模块文件;\n"
701
+ "- 函数接口设计应遵循 Rust 最佳实践,不需要兼容 C 的数据类型;优先使用 Rust 原生类型(如 i32/u32/usize、&[T]/&mut [T]、String、Result<T, E> 等),而不是 C 风格类型(如 core::ffi::c_*、libc::c_*);\n"
702
+ "- 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI;\n"
703
+ "- 参数个数与顺序可以保持与 C 一致,但类型设计应优先考虑 Rust 的惯用法和安全性;\n"
704
+ "- 仅输出必要信息,避免冗余解释。"
705
+ )
706
+ user_prompt = "\n".join([
707
+ "请阅读以下上下文并准备总结:",
708
+ f"- 函数标识: id={rec.id}, name={rec.name}, qualified={rec.qname}",
709
+ f"- 源文件位置: {rec.file}:{rec.start_line}-{rec.end_line}",
710
+ f"- crate 根目录路径: {self.crate_dir.resolve()}",
711
+ "",
712
+ "C函数源码片段:",
713
+ "<C_SOURCE>",
714
+ c_code,
715
+ "</C_SOURCE>",
716
+ "",
717
+ "符号表签名与参数(只读参考):",
718
+ json.dumps({"signature": getattr(rec, "signature", ""), "params": getattr(rec, "params", None)}, ensure_ascii=False, indent=2),
719
+ "",
720
+ "被引用符号上下文(如已转译则包含Rust模块信息):",
721
+ json.dumps(callees_ctx, ensure_ascii=False, indent=2),
722
+ "",
723
+ "库替代上下文(若存在):",
724
+ json.dumps(getattr(rec, "lib_replacement", None), ensure_ascii=False, indent=2),
725
+ "",
726
+ "当前crate目录结构(部分):",
727
+ "<CRATE_TREE>",
728
+ crate_tree,
729
+ "</CRATE_TREE>",
730
+ "",
731
+ "为避免完整读取体积较大的符号表,你也可以使用工具 read_symbols 按需获取指定符号记录:",
732
+ "- 工具: read_symbols",
733
+ "- 参数示例(YAML):",
734
+ f" symbols_file: \"{(self.data_dir / 'symbols.jsonl').resolve()}\"",
735
+ " symbols:",
736
+ " - 要读取的符号列表",
737
+ "",
738
+ "如果理解完毕,请进入总结阶段。",
739
+ ])
740
+ summary_prompt = (
741
+ "请仅输出一个 <SUMMARY> 块,块内必须且只包含一个 <yaml>...</yaml>,不得包含其它内容。\n"
742
+ "允许字段(YAML 对象):\n"
743
+ '- module: "<绝对路径>/src/xxx.rs 或 <绝对路径>/src/xxx/mod.rs;或相对路径 src/xxx.rs / src/xxx/mod.rs"\n'
744
+ '- rust_signature: "pub fn xxx(...)->..."\n'
745
+ '- notes: "可选说明(若有上下文缺失或风险点,请在此列出)"\n'
746
+ "注意:\n"
747
+ "- module 必须位于 crate 的 src/ 目录下,接受绝对路径或以 src/ 开头的相对路径;尽量选择已有文件;如需新建文件,给出合理路径;\n"
748
+ "- rust_signature 应遵循 Rust 最佳实践,不需要兼容 C 的数据类型;优先使用 Rust 原生类型和惯用法,而不是 C 风格类型。\n"
749
+ "- 类型设计原则:\n"
750
+ " * 基本类型:优先使用 i32/u32/i64/u64/isize/usize/f32/f64 等原生 Rust 类型,而不是 core::ffi::c_* 或 libc::c_*;\n"
751
+ " * 指针/引用:优先使用引用 &T/&mut T 或切片 &[T]/&mut [T],而非原始指针 *const T/*mut T;仅在必要时使用原始指针;\n"
752
+ " * 字符串:优先使用 String、&str 而非 *const c_char/*mut c_char;\n"
753
+ " * 错误处理:考虑使用 Result<T, E> 而非 C 风格的错误码;\n"
754
+ " * 参数个数与顺序可以保持与 C 一致,但类型应优先考虑 Rust 的惯用法、安全性和可读性;\n"
755
+ "- 函数签名应包含可见性修饰(pub)与函数名;类型应为 Rust 最佳实践的选择,而非简单映射 C 类型。\n"
756
+ "- 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI。\n"
757
+ "请严格按以下格式输出:\n"
758
+ "<SUMMARY><yaml>\nmodule: \"...\"\nrust_signature: \"...\"\nnotes: \"...\"\n</yaml></SUMMARY>"
759
+ )
760
+ return system_prompt, user_prompt, summary_prompt
761
+
762
+ def _plan_module_and_signature(self, rec: FnRecord, c_code: str) -> Tuple[str, str]:
763
+ """调用 Agent 选择模块与签名,返回 (module_path, rust_signature),若格式不满足将自动重试直到满足"""
764
+ crate_tree = _dir_tree(self.crate_dir)
765
+ callees_ctx = self._collect_callees_context(rec)
766
+ sys_p, usr_p, base_sum_p = self._build_module_selection_prompts(rec, c_code, callees_ctx, crate_tree)
767
+
768
+ def _validate(meta: Any) -> Tuple[bool, str]:
769
+ """基本格式检查,仅验证字段存在性,不做硬编码规则校验"""
770
+ if not isinstance(meta, dict) or not meta:
771
+ return False, "未解析到有效的 <SUMMARY><yaml> 对象"
772
+ module = meta.get("module")
773
+ rust_sig = meta.get("rust_signature")
774
+ if not isinstance(module, str) or not module.strip():
775
+ return False, "缺少必填字段 module"
776
+ if not isinstance(rust_sig, str) or not rust_sig.strip():
777
+ return False, "缺少必填字段 rust_signature"
778
+ # 路径归一化:容忍相对/简略路径,最终归一为 crate_dir 下的绝对路径(不做硬编码校验)
779
+ try:
780
+ raw = str(module).strip().replace("\\", "/")
781
+ crate_root = self.crate_dir.resolve()
782
+ mp: Path
783
+ p = Path(raw)
784
+ if p.is_absolute():
785
+ mp = p.resolve()
786
+ else:
787
+ # 规范化相对路径:若不以 src/ 开头,自动补全为 src/<raw>
788
+ if raw.startswith("./"):
789
+ raw = raw[2:]
790
+ if not raw.startswith("src/"):
791
+ raw = f"src/{raw}"
792
+ mp = (crate_root / raw).resolve()
793
+ # 将归一化后的绝对路径回写到 meta,避免后续流程二次解析歧义
794
+ meta["module"] = str(mp)
795
+ except Exception:
796
+ # 路径归一化失败不影响,保留原始值
797
+ pass
798
+ return True, ""
799
+
800
+ def _retry_sum_prompt(reason: str) -> str:
801
+ return (
802
+ base_sum_p
803
+ + "\n\n[格式检查失败,必须重试]\n"
804
+ + f"- 失败原因:{reason}\n"
805
+ + "- 仅输出一个 <SUMMARY> 块;块内仅包含单个 <yaml> 对象;\n"
806
+ + '- YAML 对象必须包含字段:module、rust_signature。\n'
807
+ )
808
+
809
+ attempt = 0
810
+ last_reason = "未知错误"
811
+ plan_max_retries_val = getattr(self, "plan_max_retries", 0)
812
+ # 如果 plan_max_retries 为 0,表示无限重试
813
+ use_direct_model = False # 标记是否使用直接模型调用
814
+ agent = None # 在循环外声明,以便重试时复用
815
+
816
+ while plan_max_retries_val == 0 or attempt < plan_max_retries_val:
817
+ attempt += 1
818
+ sum_p = base_sum_p if attempt == 1 else _retry_sum_prompt(last_reason)
819
+
820
+ # 第一次创建 Agent,后续重试时复用(如果使用直接模型调用)
821
+ if agent is None or not use_direct_model:
822
+ agent = Agent(
823
+ system_prompt=sys_p,
824
+ name="C2Rust-Function-Planner",
825
+ model_group=self.llm_group,
826
+ summary_prompt=sum_p,
827
+ need_summary=True,
828
+ auto_complete=True,
829
+ use_tools=["execute_script", "read_code", "retrieve_memory", "save_memory", "read_symbols"],
830
+ plan=False,
831
+ non_interactive=self.non_interactive,
832
+ use_methodology=False,
833
+ use_analysis=False,
834
+ disable_file_edit=True,
835
+ )
836
+
837
+ prev_cwd = os.getcwd()
838
+ try:
839
+ os.chdir(str(self.crate_dir))
840
+
841
+ if use_direct_model:
842
+ # 格式校验失败后,直接调用模型接口
843
+ # 构造包含摘要提示词和具体错误信息的完整提示
844
+ error_guidance = ""
845
+ if last_reason and last_reason != "未知错误":
846
+ if "YAML解析失败" in last_reason:
847
+ error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出的YAML格式正确,包括正确的缩进、引号、冒号等。YAML 对象必须包含字段:module(字符串)、rust_signature(字符串)。"
848
+ else:
849
+ error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- {last_reason}\n\n请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内仅包含单个 <yaml> 对象;YAML 对象必须包含字段:module(字符串)、rust_signature(字符串)。"
850
+
851
+ full_prompt = f"{usr_p}{error_guidance}\n\n{sum_p}"
852
+ try:
853
+ response = agent.model.chat_until_success(full_prompt) # type: ignore
854
+ summary = response
855
+ except Exception as e:
856
+ typer.secho(f"[c2rust-transpiler][plan] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
857
+ summary = agent.run(usr_p)
858
+ else:
859
+ # 第一次使用 run(),让 Agent 完整运行(可能使用工具)
860
+ summary = agent.run(usr_p)
861
+ finally:
862
+ os.chdir(prev_cwd)
863
+
864
+ meta, parse_error = _extract_json_from_summary(str(summary or ""))
865
+ if parse_error:
866
+ # YAML解析失败,将错误信息反馈给模型
867
+ typer.secho(f"[c2rust-transpiler][plan] YAML解析失败: {parse_error}", fg=typer.colors.YELLOW)
868
+ last_reason = f"YAML解析失败: {parse_error}"
869
+ use_direct_model = True
870
+ # 解析失败,继续重试
871
+ continue
872
+ else:
873
+ ok, reason = _validate(meta)
874
+ if ok:
875
+ module = str(meta.get("module") or "").strip()
876
+ rust_sig = str(meta.get("rust_signature") or "").strip()
877
+ typer.secho(f"[c2rust-transpiler][plan] 第 {attempt} 次尝试成功: 模块={module}, 签名={rust_sig}", fg=typer.colors.GREEN)
878
+ return module, rust_sig
879
+ else:
880
+ typer.secho(f"[c2rust-transpiler][plan] 第 {attempt} 次尝试失败: {reason}", fg=typer.colors.YELLOW)
881
+ last_reason = reason
882
+ # 格式校验失败,后续重试使用直接模型调用
883
+ use_direct_model = True
884
+ # 规划超出重试上限:回退到兜底方案(默认模块 src/ffi.rs + 简单占位签名)
885
+ # 注意:如果 plan_max_retries_val == 0(无限重试),理论上不应该到达这里
886
+ try:
887
+ crate_root = self.crate_dir.resolve()
888
+ fallback_module = str((crate_root / "src" / "ffi.rs").resolve())
889
+ except Exception:
890
+ fallback_module = "src/ffi.rs"
891
+ fallback_sig = f"pub fn {rec.name or ('fn_' + str(rec.id))}()"
892
+ typer.secho(f"[c2rust-transpiler][plan] 超出规划重试上限({plan_max_retries_val if plan_max_retries_val > 0 else '无限'}),回退到兜底: module={fallback_module}, signature={fallback_sig}", fg=typer.colors.YELLOW)
893
+ return fallback_module, fallback_sig
894
+
895
+ def _update_progress_current(self, rec: FnRecord, module: str, rust_sig: str) -> None:
896
+ self.progress["current"] = {
897
+ "time": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime()),
898
+ "id": rec.id,
899
+ "name": rec.name,
900
+ "qualified_name": rec.qname,
901
+ "file": rec.file,
902
+ "start_line": rec.start_line,
903
+ "end_line": rec.end_line,
904
+ "module": module,
905
+ "rust_signature": rust_sig,
906
+ }
907
+ self._save_progress()
908
+
909
+
910
+
911
+ # ========= Agent 复用与上下文拼接辅助 =========
912
+
913
+ def _compose_prompt_with_context(self, prompt: str) -> str:
914
+ """
915
+ 在复用Agent时,将此前构建的函数上下文头部拼接到当前提示词前,确保连续性。
916
+ 策略:
917
+ - 每个函数生命周期内,首次调用拼接“全量头部”;
918
+ - 后续调用仅拼接“精简头部”;
919
+ - 如头部缺失则直接返回原提示。
920
+ """
921
+ # 首次发送全量上下文
922
+ if (not getattr(self, "_current_context_full_sent", False)) and getattr(self, "_current_context_full_header", ""):
923
+ self._current_context_full_sent = True
924
+ return self._current_context_full_header + "\n\n" + prompt
925
+ # 后续拼接精简上下文
926
+ compact = getattr(self, "_current_context_compact_header", "")
927
+ if compact:
928
+ return compact + "\n\n" + prompt
929
+ return prompt
930
+
931
+ def _reset_function_context(self, rec: FnRecord, module: str, rust_sig: str, c_code: str) -> None:
932
+ """
933
+ 初始化当前函数的上下文与复用Agent缓存。
934
+ 在单个函数实现开始时调用一次,之后复用代码编写与修复Agent/Review等Agent。
935
+ """
936
+ self._current_agents = {}
937
+ self._current_function_id = rec.id
938
+
939
+ # 汇总上下文头部,供后续复用时拼接
940
+ callees_ctx = self._collect_callees_context(rec)
941
+ crate_tree = _dir_tree(self.crate_dir)
942
+ librep_ctx = rec.lib_replacement if isinstance(rec.lib_replacement, dict) else None
943
+
944
+ header_lines = [
945
+ "【当前函数上下文(复用Agent专用)】",
946
+ f"- 函数: {rec.qname or rec.name} (id={rec.id})",
947
+ f"- 源位置: {rec.file}:{rec.start_line}-{rec.end_line}",
948
+ f"- 原 C 工程目录: {self.project_root.resolve()}",
949
+ f"- 目标模块: {module}",
950
+ f"- 建议/当前签名: {rust_sig}",
951
+ f"- crate 根目录: {self.crate_dir.resolve()}",
952
+ "",
953
+ "原始C函数源码片段(只读参考):",
954
+ "<C_SOURCE>",
955
+ c_code or "",
956
+ "</C_SOURCE>",
957
+ "",
958
+ "被引用符号上下文:",
959
+ json.dumps(callees_ctx, ensure_ascii=False, indent=2),
960
+ "",
961
+ "库替代上下文(若有):",
962
+ json.dumps(librep_ctx, ensure_ascii=False, indent=2),
963
+ "",
964
+ "crate 目录结构(部分):",
965
+ "<CRATE_TREE>",
966
+ crate_tree,
967
+ "</CRATE_TREE>",
968
+ ]
969
+ # 精简头部(后续复用)
970
+ compact_lines = [
971
+ "【函数上下文简要(复用)】",
972
+ f"- 函数: {rec.qname or rec.name} (id={rec.id})",
973
+ f"- 原 C 工程目录: {self.project_root.resolve()}",
974
+ f"- 模块: {module}",
975
+ f"- 签名: {rust_sig}",
976
+ f"- crate: {self.crate_dir.resolve()}",
977
+ ]
978
+ self._current_context_full_header = "\n".join(header_lines)
979
+ self._current_context_compact_header = "\n".join(compact_lines)
980
+ self._current_context_full_sent = False
981
+
982
+ # 初始化一次代码生成Agent(CodeAgent),单个函数生命周期内复用
983
+ # 该Agent用于代码生成和修复,启用方法论、分析和强制记忆功能
984
+ self._current_agents[f"code_agent::{rec.id}"] = CodeAgent(
985
+ need_summary=False,
986
+ non_interactive=self.non_interactive,
987
+ plan=False,
988
+ model_group=self.llm_group,
989
+ use_methodology=True,
990
+ use_analysis=True,
991
+ force_save_memory=True, # 强制使用记忆功能
992
+ )
993
+
994
+ def _get_repair_agent(self) -> CodeAgent:
995
+ """
996
+ 获取复用的代码生成Agent(CodeAgent)。若未初始化,则按当前函数id创建。
997
+ 该Agent用于代码生成和修复,启用方法论、分析和强制记忆功能。
998
+ """
999
+ fid = self._current_function_id
1000
+ key = f"code_agent::{fid}" if fid is not None else "code_agent::default"
1001
+ agent = self._current_agents.get(key)
1002
+ if agent is None:
1003
+ # 复用的代码生成Agent启用方法论、分析和强制记忆功能
1004
+ agent = CodeAgent(
1005
+ need_summary=False,
1006
+ non_interactive=self.non_interactive,
1007
+ plan=False,
1008
+ model_group=self.llm_group,
1009
+ use_methodology=True,
1010
+ use_analysis=True,
1011
+ force_save_memory=True, # 强制使用记忆功能
1012
+ )
1013
+ self._current_agents[key] = agent
1014
+ return agent
1015
+
1016
+ def _refresh_compact_context(self, rec: FnRecord, module: str, rust_sig: str) -> None:
1017
+ """
1018
+ 刷新精简上下文头部(在 sig-fix/ensure-impl 后调用,保证后续提示一致)。
1019
+ 仅更新精简头部,不影响已发送的全量头部。
1020
+ """
1021
+ try:
1022
+ compact_lines = [
1023
+ "【函数上下文简要(复用)】",
1024
+ f"- 函数: {rec.qname or rec.name} (id={rec.id})",
1025
+ f"- 模块: {module}",
1026
+ f"- 签名: {rust_sig}",
1027
+ f"- crate: {self.crate_dir.resolve()}",
1028
+ ]
1029
+ self._current_context_compact_header = "\n".join(compact_lines)
1030
+ except Exception:
1031
+ pass
1032
+
1033
+ # ========= 代码生成与修复 =========
1034
+
1035
+ def _codeagent_generate_impl(self, rec: FnRecord, c_code: str, module: str, rust_sig: str, unresolved: List[str]) -> None:
1036
+ """
1037
+ 使用 CodeAgent 生成/更新目标模块中的函数实现。
1038
+ 约束:最小变更,生成可编译的占位实现,尽可能保留后续细化空间。
1039
+ """
1040
+ symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
1041
+ requirement_lines = [
1042
+ f"目标:在 crate 目录 {self.crate_dir.resolve()} 的 {module} 中,为 C 函数 {rec.qname or rec.name} 生成对应的 Rust 实现。",
1043
+ "要求:",
1044
+ f"- 函数签名(建议):{rust_sig}",
1045
+ f"- 原 C 工程目录位置:{self.project_root.resolve()}",
1046
+ "- 若 module 文件不存在则新建;为所在模块添加必要的 mod 声明(若需要);",
1047
+ "- 若已有函数占位/实现,尽量最小修改,不要破坏现有代码;",
1048
+ "- 你可以参考原 C 函数的关联实现(如同文件/同模块的相关辅助函数、内联实现、宏与注释等),在保持语义一致的前提下以符合 Rust 风格的方式实现;避免机械复制粘贴;",
1049
+ f"- 如需参考原 C 工程中的其他文件,可在原 C 工程目录 {self.project_root.resolve()} 中查找;",
1050
+ "- 禁止在函数实现中使用 todo!/unimplemented! 作为占位;对于尚未实现的被调符号,请阅读其原 C 实现并在本次一并补齐等价的 Rust 实现,避免遗留占位;",
1051
+ "- 为保证行为等价,禁止使用占位返回或随意默认值;必须实现与 C 语义等价的返回逻辑,不得使用 panic!/todo!/unimplemented!;",
1052
+ "- 不要删除或移动其他无关文件。",
1053
+ "",
1054
+ "编码原则与规范:",
1055
+ "- 函数接口设计应遵循 Rust 最佳实践,不需要兼容 C 的数据类型;优先使用 Rust 原生类型和惯用法:",
1056
+ " * 基本类型:使用 i32/u32/i64/u64/isize/usize/f32/f64 等原生 Rust 类型,而非 core::ffi::c_* 或 libc::c_*;",
1057
+ " * 指针/引用:优先使用引用 &T/&mut T 或切片 &[T]/&mut [T],而非原始指针 *const T/*mut T;仅在必要时使用原始指针;",
1058
+ " * 字符串:优先使用 String、&str 而非 *const c_char/*mut c_char;",
1059
+ " * 错误处理:如适用,考虑使用 Result<T, E> 而非 C 风格的错误码;",
1060
+ " * 禁止使用 extern \"C\";函数应使用标准的 Rust 调用约定,不需要 C ABI;",
1061
+ "- 保持最小变更,避免无关重构与格式化;禁止批量重排/重命名/移动文件;",
1062
+ "- 命名遵循Rust惯例(函数/模块蛇形命名),公共API使用pub;",
1063
+ "- 优先使用安全Rust;如需unsafe,将范围最小化并添加注释说明原因与SAFETY保证;",
1064
+ "- 错误处理:遵循 C 语义保持等价行为(逻辑一致性),但在类型设计上优先使用 Rust 惯用法;避免 panic!/unwrap();",
1065
+ "- 实现中禁止使用 todo!/unimplemented! 占位;对于尚未实现的被调符号,应基于其 C 源码补齐等价 Rust 实现;",
1066
+ "- 返回值必须与 C 语义等价,不得使用占位返回或随意默认值;避免 panic!/todo!/unimplemented!;",
1067
+ "- 若依赖未实现符号,请通过 read_symbols/read_code 获取其 C 源码并生成等价的 Rust 实现(可放置在同一模块或合理模块),而不是使用 todo!;",
1068
+ "- 文档:为新增函数添加简要文档注释,注明来源C函数与意图;",
1069
+ "- 导入:禁止使用 use ...::* 通配;仅允许精确导入所需符号",
1070
+ "- 依赖管理:如引入新的外部 crate 或需要启用 feature,请同步更新 Cargo.toml 的 [dependencies]/[dev-dependencies]/[features],避免未声明依赖导致构建失败;版本号可使用兼容范围(如 ^x.y)或默认值;",
1071
+ "- 测试生成:若函数可测试(无需外部环境/IO/网络/全局状态),在同文件添加 #[cfg(test)] mod tests 并编写至少一个可编译的单元测试(建议命名为 test_<函数名>_basic);测试仅调用函数,避免使用 panic!/todo!/unimplemented!;unsafe 函数以 unsafe 调用;必要时使用占位参数(如 0、core::ptr::null()/null_mut()、默认值)以保证通过。",
1072
+ "- 测试设计文档:在测试函数顶部使用文档注释(///)简要说明测试用例设计,包括:输入构造、预置条件、期望行为(或成功执行)、边界/异常不作为否决项;注释内容仅用于说明,不影响实现。",
1073
+ "- 不可测试时:如函数依赖外部环境或调用未实现符号,暂不生成测试,但请在函数文档注释中注明不可测试原因(例如外部依赖/未实现符号)。",
1074
+ "- 指针+长度参数:如 C 存在 <ptr, len> 组合,请优先保持成对出现;若暂不清晰,至少保留长度参数占位",
1075
+ "- 风格:遵循 rustfmt 默认风格,避免引入与本次改动无关的大范围格式变化;",
1076
+ "- 输出限制:仅以补丁形式修改目标文件,不要输出解释或多余文本。",
1077
+ "",
1078
+ "C 源码片段(供参考,不要原样粘贴):",
1079
+ "<C_SOURCE>",
1080
+ c_code,
1081
+ "</C_SOURCE>",
1082
+ "",
1083
+ "符号表签名与参数(只读参考):",
1084
+ json.dumps({"signature": getattr(rec, "signature", ""), "params": getattr(rec, "params", None)}, ensure_ascii=False, indent=2),
1085
+ "",
1086
+ "注意:所有修改均以补丁方式进行。",
1087
+ "",
1088
+ "如对实现细节不确定:可以使用工具 read_symbols 按需获取指定符号记录:",
1089
+ "- 工具: read_symbols",
1090
+ "- 参数示例(YAML):",
1091
+ f" symbols_file: \"{symbols_path}\"",
1092
+ " symbols:",
1093
+ " - 要读取的符号列表",
1094
+ "",
1095
+ "尚未转换的被调符号如下(请阅读这些符号的 C 源码并生成等价的 Rust 实现;必要时新增模块或签名):",
1096
+ *[f"- {s}" for s in (unresolved or [])],
1097
+ "",
1098
+ "【重要:依赖检查与实现要求】",
1099
+ "在实现函数之前,请务必检查以下内容:",
1100
+ "1. 检查当前函数是否已实现:",
1101
+ f" - 在目标模块 {module} 中查找函数 {rec.qname or rec.name} 的实现",
1102
+ " - 如果已存在实现,检查其是否完整且正确",
1103
+ "2. 检查所有依赖函数是否已实现:",
1104
+ " - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
1105
+ " - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
1106
+ " - 可以使用 read_code 工具读取相关模块文件进行检查",
1107
+ " - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
1108
+ "3. 对于未实现的依赖函数:",
1109
+ " - 使用 read_symbols 工具获取其 C 源码和符号信息",
1110
+ " - 使用 read_code 工具读取其 C 源码实现",
1111
+ " - 在本次实现中一并补齐这些依赖函数的 Rust 实现",
1112
+ " - 根据依赖关系选择合适的模块位置(可在同一模块或合理的新模块中)",
1113
+ " - 确保所有依赖函数都有完整实现,禁止使用 todo!/unimplemented! 占位",
1114
+ "4. 实现顺序:",
1115
+ " - 优先实现最底层的依赖函数(不依赖其他未实现函数的函数)",
1116
+ " - 然后实现依赖这些底层函数的函数",
1117
+ " - 最后实现当前目标函数",
1118
+ "5. 验证:",
1119
+ " - 确保当前函数及其所有依赖函数都已完整实现",
1120
+ " - 确保没有遗留的 todo!/unimplemented! 占位",
1121
+ " - 确保所有函数调用都能正确解析",
1122
+ ]
1123
+ # 若存在库替代上下文,则附加到实现提示中,便于生成器参考(多库组合、参考API、备注等)
1124
+ librep_ctx = None
1125
+ try:
1126
+ librep_ctx = getattr(rec, "lib_replacement", None)
1127
+ except Exception:
1128
+ librep_ctx = None
1129
+ if isinstance(librep_ctx, dict) and librep_ctx:
1130
+ requirement_lines.extend([
1131
+ "",
1132
+ "库替代上下文(若存在):",
1133
+ json.dumps(librep_ctx, ensure_ascii=False, indent=2),
1134
+ "",
1135
+ ])
1136
+ requirement_lines.extend([
1137
+ "",
1138
+ "【重要:记忆保存要求】",
1139
+ "在完成函数实现之后,请务必使用 save_memory 工具记录以下关键信息,以便后续检索和复用:",
1140
+ f"- 函数名称:{rec.qname or rec.name} (id={rec.id})",
1141
+ f"- 源文件位置:{rec.file}:{rec.start_line}-{rec.end_line}",
1142
+ f"- 目标模块:{module}",
1143
+ f"- Rust 函数签名:{rust_sig}",
1144
+ "- C 函数的核心功能与语义",
1145
+ "- 关键实现细节与设计决策",
1146
+ "- 依赖关系与调用链",
1147
+ "- 类型转换与边界处理要点",
1148
+ "- 错误处理策略",
1149
+ "- 实际实现的 Rust 代码要点与关键逻辑",
1150
+ "记忆标签建议:使用 'c2rust', 'function_impl', 函数名等作为标签,便于后续检索。",
1151
+ "请在完成代码实现之后保存记忆,记录本次实现的完整信息。",
1152
+ ])
1153
+ prompt = "\n".join(requirement_lines)
1154
+ # 确保目标模块文件存在(提高补丁应用与实现落盘的确定性)
1155
+ try:
1156
+ mp = Path(module)
1157
+ if not mp.is_absolute():
1158
+ mp = (self.crate_dir / module).resolve()
1159
+ mp.parent.mkdir(parents=True, exist_ok=True)
1160
+ if not mp.exists():
1161
+ try:
1162
+ mp.write_text("// Auto-created by c2rust transpiler\n", encoding="utf-8")
1163
+ typer.secho(f"[c2rust-transpiler][gen] auto-created module file: {mp}", fg=typer.colors.GREEN)
1164
+ except Exception:
1165
+ pass
1166
+ except Exception:
1167
+ pass
1168
+ # 切换到 crate 目录运行复用的代码编写与修复Agent,运行完毕后恢复
1169
+ prev_cwd = os.getcwd()
1170
+ try:
1171
+ os.chdir(str(self.crate_dir))
1172
+ agent = self._get_repair_agent() # 使用复用的Agent,同时支持代码编写和修复
1173
+ agent.run(self._compose_prompt_with_context(prompt), prefix="[c2rust-transpiler][gen]", suffix="")
1174
+ finally:
1175
+ os.chdir(prev_cwd)
1176
+
1177
+ def _extract_rust_fn_name_from_sig(self, rust_sig: str) -> str:
1178
+ """
1179
+ 从 rust 签名中提取函数名,支持生命周期参数和泛型参数。
1180
+ 例如: 'pub fn foo(a: i32) -> i32 { ... }' -> 'foo'
1181
+ 例如: 'pub fn foo<'a>(bzf: &'a mut BzFile) -> Result<&'a [u8], BzError>' -> 'foo'
1182
+ """
1183
+ # 支持生命周期参数和泛型参数:fn name<'a, T>(...)
1184
+ m = re.search(r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(", rust_sig or "")
1185
+ return m.group(1) if m else ""
1186
+
1187
+ def _ensure_top_level_pub_mod(self, mod_name: str) -> None:
1188
+ """
1189
+ 在 src/lib.rs 中确保存在 `pub mod <mod_name>;`
1190
+ - 如已存在 `pub mod`,不做改动
1191
+ - 如存在 `mod <mod_name>;`,升级为 `pub mod <mod_name>;`
1192
+ - 如都不存在,则在文件末尾追加一行 `pub mod <mod_name>;`
1193
+ - 最小改动,不覆盖其他内容
1194
+ """
1195
+ try:
1196
+ if not mod_name or mod_name in ("lib", "main"):
1197
+ return
1198
+ lib_rs = (self.crate_dir / "src" / "lib.rs").resolve()
1199
+ lib_rs.parent.mkdir(parents=True, exist_ok=True)
1200
+ if not lib_rs.exists():
1201
+ try:
1202
+ lib_rs.write_text("// Auto-generated by c2rust transpiler\n", encoding="utf-8")
1203
+ typer.secho(f"[c2rust-transpiler][mod] 已创建 src/lib.rs: {lib_rs}", fg=typer.colors.GREEN)
1204
+ except Exception:
1205
+ return
1206
+ txt = lib_rs.read_text(encoding="utf-8", errors="replace")
1207
+ pub_pat = re.compile(rf'(?m)^\s*pub\s+mod\s+{re.escape(mod_name)}\s*;\s*$')
1208
+ mod_pat = re.compile(rf'(?m)^\s*mod\s+{re.escape(mod_name)}\s*;\s*$')
1209
+ if pub_pat.search(txt):
1210
+ return
1211
+ if mod_pat.search(txt):
1212
+ # 升级为 pub mod(保留原缩进)
1213
+ def _repl(m):
1214
+ line = m.group(0)
1215
+ ws = re.match(r'^(\s*)', line).group(1) if re.match(r'^(\s*)', line) else ""
1216
+ return f"{ws}pub mod {mod_name};"
1217
+ new_txt = mod_pat.sub(_repl, txt, count=1)
1218
+ else:
1219
+ new_txt = (txt.rstrip() + f"\npub mod {mod_name};\n")
1220
+ lib_rs.write_text(new_txt, encoding="utf-8")
1221
+ typer.secho(f"[c2rust-transpiler][mod] updated src/lib.rs: ensured pub mod {mod_name}", fg=typer.colors.GREEN)
1222
+ except Exception:
1223
+ # 保持稳健,失败不阻塞主流程
1224
+ pass
1225
+
1226
+ def _ensure_mod_rs_decl(self, dir_path: Path, child_mod: str) -> None:
1227
+ """
1228
+ 在 dir_path/mod.rs 中确保存在 `pub mod <child_mod>;`
1229
+ - 如存在 `mod <child_mod>;` 则升级为 `pub mod <child_mod>;`
1230
+ - 如均不存在则在文件末尾追加 `pub mod <child_mod>;`
1231
+ - 最小改动,不覆盖其他内容
1232
+ """
1233
+ try:
1234
+ if not child_mod or child_mod in ("lib", "main"):
1235
+ return
1236
+ mod_rs = (dir_path / "mod.rs").resolve()
1237
+ mod_rs.parent.mkdir(parents=True, exist_ok=True)
1238
+ if not mod_rs.exists():
1239
+ try:
1240
+ mod_rs.write_text("// Auto-generated by c2rust transpiler\n", encoding="utf-8")
1241
+ typer.secho(f"[c2rust-transpiler][mod] 已创建 {mod_rs}", fg=typer.colors.GREEN)
1242
+ except Exception:
1243
+ return
1244
+ txt = mod_rs.read_text(encoding="utf-8", errors="replace")
1245
+ pub_pat = re.compile(rf'(?m)^\s*pub\s+mod\s+{re.escape(child_mod)}\s*;\s*$')
1246
+ mod_pat = re.compile(rf'(?m)^\s*mod\s+{re.escape(child_mod)}\s*;\s*$')
1247
+ if pub_pat.search(txt):
1248
+ return
1249
+ if mod_pat.search(txt):
1250
+ # 升级为 pub mod(保留原缩进)
1251
+ def _repl(m):
1252
+ line = m.group(0)
1253
+ ws = re.match(r'^(\s*)', line).group(1) if re.match(r'^(\s*)', line) else ""
1254
+ return f"{ws}pub mod {child_mod};"
1255
+ new_txt = mod_pat.sub(_repl, txt, count=1)
1256
+ else:
1257
+ new_txt = (txt.rstrip() + f"\npub mod {child_mod};\n")
1258
+ mod_rs.write_text(new_txt, encoding="utf-8")
1259
+ typer.secho(f"[c2rust-transpiler][mod] updated {mod_rs}: ensured pub mod {child_mod}", fg=typer.colors.GREEN)
1260
+ except Exception:
1261
+ pass
1262
+
1263
+ def _ensure_mod_chain_for_module(self, module: str) -> None:
1264
+ """
1265
+ 根据目标模块文件,补齐从该文件所在目录向上的 mod.rs 声明链:
1266
+ - 对于 src/foo/bar.rs:在 src/foo/mod.rs 确保 `pub mod bar;`
1267
+ 并在上层 src/mod.rs(不修改)改为在 src/lib.rs 确保 `pub mod foo;`(已由顶层函数处理)
1268
+ - 对于 src/foo/bar/mod.rs:在 src/foo/mod.rs 确保 `pub mod bar;`
1269
+ - 对多级目录,逐级在上层 mod.rs 确保对子目录的 `pub mod <child>;`
1270
+ """
1271
+ try:
1272
+ mp = Path(module)
1273
+ base = mp
1274
+ if not mp.is_absolute():
1275
+ base = (self.crate_dir / module).resolve()
1276
+ crate_root = self.crate_dir.resolve()
1277
+ # 必须在 crate/src 下
1278
+ rel = base.relative_to(crate_root)
1279
+ rel_s = str(rel).replace("\\", "/")
1280
+ if not rel_s.startswith("src/"):
1281
+ return
1282
+ # 计算起始目录与首个子模块名
1283
+ inside = rel_s[len("src/"):].strip("/")
1284
+ if not inside:
1285
+ return
1286
+ parts = [p for p in inside.split("/") if p] # 过滤空字符串
1287
+ if parts[-1].endswith(".rs"):
1288
+ if parts[-1] in ("lib.rs", "main.rs"):
1289
+ return
1290
+ child = parts[-1][:-3] # 去掉 .rs
1291
+ if len(parts) > 1:
1292
+ start_dir = crate_root / "src" / "/".join(parts[:-1])
1293
+ else:
1294
+ start_dir = crate_root / "src"
1295
+ # 确保 start_dir 在 crate/src 下
1296
+ try:
1297
+ start_dir_rel = start_dir.relative_to(crate_root)
1298
+ if not str(start_dir_rel).replace("\\", "/").startswith("src/"):
1299
+ return
1300
+ except ValueError:
1301
+ return
1302
+ # 在当前目录的 mod.rs 确保 pub mod <child>
1303
+ if start_dir.name != "src":
1304
+ self._ensure_mod_rs_decl(start_dir, child)
1305
+ # 向上逐级确保父目录对当前目录的 pub mod 声明
1306
+ cur_dir = start_dir
1307
+ else:
1308
+ # 末尾为目录(mod.rs 情况):确保父目录对该目录 pub mod
1309
+ if parts:
1310
+ cur_dir = crate_root / "src" / "/".join(parts)
1311
+ # 确保 cur_dir 在 crate/src 下
1312
+ try:
1313
+ cur_dir_rel = cur_dir.relative_to(crate_root)
1314
+ if not str(cur_dir_rel).replace("\\", "/").startswith("src/"):
1315
+ return
1316
+ except ValueError:
1317
+ return
1318
+ else:
1319
+ return
1320
+ # 逐级向上到 src 根(不修改 src/mod.rs,顶层由 lib.rs 公开)
1321
+ while True:
1322
+ parent = cur_dir.parent
1323
+ if not parent.exists():
1324
+ break
1325
+ # 确保不超过 crate 根目录
1326
+ try:
1327
+ parent.relative_to(crate_root)
1328
+ except ValueError:
1329
+ # parent 不在 crate_root 下,停止向上遍历
1330
+ break
1331
+ if parent.name == "src":
1332
+ # 顶层由 _ensure_top_level_pub_mod 负责
1333
+ break
1334
+ # 在 parent/mod.rs 确保 pub mod <cur_dir.name>
1335
+ # 确保 parent 在 crate/src 下
1336
+ try:
1337
+ parent_rel = parent.relative_to(crate_root)
1338
+ if str(parent_rel).replace("\\", "/").startswith("src/"):
1339
+ self._ensure_mod_rs_decl(parent, cur_dir.name)
1340
+ except (ValueError, Exception):
1341
+ # parent 不在 crate/src 下,跳过
1342
+ break
1343
+ cur_dir = parent
1344
+ except Exception:
1345
+ pass
1346
+
1347
+ def _module_file_to_crate_path(self, module: str) -> str:
1348
+ """
1349
+ 将模块文件路径转换为 crate 路径前缀:
1350
+ - src/lib.rs -> crate
1351
+ - src/foo/mod.rs -> crate::foo
1352
+ - src/foo/bar.rs -> crate::foo::bar
1353
+ 支持绝对路径:若 module 为绝对路径且位于 crate 根目录下,会自动转换为相对路径再解析;
1354
+ 其它(无法解析为 crate/src 下的路径)统一返回 'crate'
1355
+ """
1356
+ mod = str(module).strip()
1357
+ # 若传入绝对路径且在 crate_dir 下,转换为相对路径以便后续按 src/ 前缀解析
1358
+ try:
1359
+ mp = Path(mod)
1360
+ if mp.is_absolute():
1361
+ try:
1362
+ rel = mp.resolve().relative_to(self.crate_dir.resolve())
1363
+ mod = str(rel).replace("\\", "/")
1364
+ except Exception:
1365
+ # 绝对路径不在 crate_dir 下,保持原样
1366
+ pass
1367
+ except Exception:
1368
+ pass
1369
+ # 规范化 ./ 前缀
1370
+ if mod.startswith("./"):
1371
+ mod = mod[2:]
1372
+ # 仅处理位于 src/ 下的模块文件
1373
+ if not mod.startswith("src/"):
1374
+ return "crate"
1375
+ p = mod[len("src/"):]
1376
+ if p.endswith("mod.rs"):
1377
+ p = p[: -len("mod.rs")]
1378
+ elif p.endswith(".rs"):
1379
+ p = p[: -len(".rs")]
1380
+ p = p.strip("/")
1381
+ return "crate" if not p else "crate::" + p.replace("/", "::")
1382
+
1383
+ def _resolve_pending_todos_for_symbol(self, symbol: str, callee_module: str, callee_rust_fn: str, callee_rust_sig: str) -> None:
1384
+ """
1385
+ 当某个 C 符号对应的函数已转换为 Rust 后:
1386
+ - 扫描整个 crate(优先 src/ 目录)中所有 .rs 文件,查找占位:todo!("符号名") 或 unimplemented!("符号名")
1387
+ - 对每个命中的文件,创建 CodeAgent 将占位替换为对已转换函数的真实调用(可使用 crate::... 完全限定路径或 use 引入)
1388
+ - 最小化修改,避免无关重构
1389
+
1390
+ 说明:不再使用 todos.json,本方法直接搜索源码中的 todo!("xxxx") / unimplemented!("xxxx")。
1391
+ """
1392
+ if not symbol:
1393
+ return
1394
+
1395
+ # 计算被调函数的crate路径前缀,便于在提示中提供调用路径建议
1396
+ callee_path = self._module_file_to_crate_path(callee_module)
1397
+
1398
+ # 扫描 src 下的 .rs 文件,查找 todo!("symbol") 或 unimplemented!("symbol") 占位
1399
+ matches: List[str] = []
1400
+ src_root = (self.crate_dir / "src").resolve()
1401
+ if src_root.exists():
1402
+ for p in sorted(src_root.rglob("*.rs")):
1403
+ try:
1404
+ text = p.read_text(encoding="utf-8", errors="replace")
1405
+ except Exception:
1406
+ continue
1407
+ pat_todo = re.compile(r'todo\s*!\s*\(\s*["\']' + re.escape(symbol) + r'["\']\s*\)')
1408
+ pat_unimpl = re.compile(r'unimplemented\s*!\s*\(\s*["\']' + re.escape(symbol) + r'["\']\s*\)')
1409
+ if pat_todo.search(text) or pat_unimpl.search(text):
1410
+ try:
1411
+ # 记录绝对路径,避免依赖当前工作目录
1412
+ abs_path = str(p.resolve())
1413
+ except Exception:
1414
+ abs_path = str(p)
1415
+ matches.append(abs_path)
1416
+
1417
+ if not matches:
1418
+ typer.secho(f"[c2rust-transpiler][todo] 未在 src/ 中找到 todo!(\"{symbol}\") 或 unimplemented!(\"{symbol}\") 的出现", fg=typer.colors.BLUE)
1419
+ return
1420
+
1421
+ # 在当前工作目录运行 CodeAgent,不进入 crate 目录
1422
+ typer.secho(f"[c2rust-transpiler][todo] 发现 {len(matches)} 个包含 todo!(\"{symbol}\") 或 unimplemented!(\"{symbol}\") 的文件", fg=typer.colors.YELLOW)
1423
+ for target_file in matches:
1424
+ prompt = "\n".join([
1425
+ f"请在文件 {target_file} 中,定位所有以下占位并替换为对已转换函数的真实调用:",
1426
+ f"- todo!(\"{symbol}\")",
1427
+ f"- unimplemented!(\"{symbol}\")",
1428
+ "要求:",
1429
+ f"- 已转换的目标函数名:{callee_rust_fn}",
1430
+ f"- 其所在模块(crate路径提示):{callee_path}",
1431
+ f"- 函数签名提示:{callee_rust_sig}",
1432
+ f"- 当前 crate 根目录路径:{self.crate_dir.resolve()}",
1433
+ "- 优先使用完全限定路径(如 crate::...::函数(...));如需在文件顶部添加 use,仅允许精确导入,不允许通配(例如 use ...::*);",
1434
+ "- 保持最小改动,不要进行与本次修复无关的重构或格式化;",
1435
+ "- 如果参数列表暂不明确,可使用合理占位变量,确保编译通过。",
1436
+ "",
1437
+ f"仅修改 {target_file} 中与上述占位相关的代码,其他位置不要改动。",
1438
+ "请仅输出补丁,不要输出解释或多余文本。",
1439
+ ])
1440
+ prev_cwd = os.getcwd()
1441
+ try:
1442
+ os.chdir(str(self.crate_dir))
1443
+ agent = self._get_repair_agent()
1444
+ agent.run(self._compose_prompt_with_context(prompt), prefix=f"[c2rust-transpiler][todo-fix:{symbol}]", suffix="")
1445
+ finally:
1446
+ os.chdir(prev_cwd)
1447
+
1448
+ def _classify_rust_error(self, text: str) -> List[str]:
1449
+ """
1450
+ 朴素错误分类,用于提示 CodeAgent 聚焦修复:
1451
+ - missing_import: unresolved import / not found in this scope / cannot find ...
1452
+ - type_mismatch: mismatched types / expected ... found ...
1453
+ - visibility: private module/field/function
1454
+ - borrow_checker: does not live long enough / borrowed data escapes / cannot borrow as mutable
1455
+ - dependency_missing: failed to select a version / could not find crate
1456
+ - module_not_found: file not found for module / unresolved module
1457
+ """
1458
+ tags: List[str] = []
1459
+ t = (text or "").lower()
1460
+ def has(s: str) -> bool:
1461
+ return s in t
1462
+ if ("unresolved import" in t) or ("not found in this scope" in t) or ("cannot find" in t) or ("use of undeclared crate or module" in t):
1463
+ tags.append("missing_import")
1464
+ if ("mismatched types" in t) or ("expected" in t and "found" in t):
1465
+ tags.append("type_mismatch")
1466
+ if ("private" in t and "module" in t) or ("private" in t and "field" in t) or ("private" in t and "function" in t):
1467
+ tags.append("visibility")
1468
+ if ("does not live long enough" in t) or ("borrowed data escapes" in t) or ("cannot borrow" in t):
1469
+ tags.append("borrow_checker")
1470
+ if ("failed to select a version" in t) or ("could not find crate" in t) or ("no matching package named" in t):
1471
+ tags.append("dependency_missing")
1472
+ if ("file not found for module" in t) or ("unresolved module" in t):
1473
+ tags.append("module_not_found")
1474
+ # 去重
1475
+ try:
1476
+ tags = list(dict.fromkeys(tags))
1477
+ except Exception:
1478
+ tags = list(set(tags))
1479
+ return tags
1480
+
1481
+ def _get_current_function_context(self) -> Tuple[Dict[str, Any], str, str, str]:
1482
+ """
1483
+ 获取当前函数上下文信息。
1484
+ 返回: (curr, sym_name, src_loc, c_code)
1485
+ """
1486
+ try:
1487
+ curr = self.progress.get("current") or {}
1488
+ except Exception:
1489
+ curr = {}
1490
+ sym_name = str(curr.get("qualified_name") or curr.get("name") or "")
1491
+ src_loc = f"{curr.get('file')}:{curr.get('start_line')}-{curr.get('end_line')}" if curr else ""
1492
+ c_code = ""
1493
+ try:
1494
+ cf = curr.get("file")
1495
+ s = int(curr.get("start_line") or 0)
1496
+ e = int(curr.get("end_line") or 0)
1497
+ if cf and s:
1498
+ p = Path(cf)
1499
+ if not p.is_absolute():
1500
+ p = (self.project_root / p).resolve()
1501
+ if p.exists():
1502
+ lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
1503
+ s0 = max(1, s)
1504
+ e0 = min(len(lines), max(e, s0))
1505
+ c_code = "\n".join(lines[s0 - 1 : e0])
1506
+ except Exception:
1507
+ c_code = ""
1508
+ return curr, sym_name, src_loc, c_code
1509
+
1510
+ def _build_repair_prompt(self, stage: str, output: str, tags: List[str], sym_name: str, src_loc: str, c_code: str, curr: Dict[str, Any], symbols_path: str, include_output_patch_hint: bool = False) -> str:
1511
+ """
1512
+ 构建修复提示词。
1513
+
1514
+ Args:
1515
+ stage: 阶段名称("cargo check" 或 "cargo test")
1516
+ output: 构建错误输出
1517
+ tags: 错误分类标签
1518
+ sym_name: 符号名称
1519
+ src_loc: 源文件位置
1520
+ c_code: C 源码片段
1521
+ curr: 当前进度信息
1522
+ symbols_path: 符号表文件路径
1523
+ include_output_patch_hint: 是否包含"仅输出补丁"提示(test阶段需要)
1524
+ """
1525
+ base_lines = [
1526
+ f"目标:以最小的改动修复问题,使 `{stage}` 命令可以通过。",
1527
+ f"阶段:{stage}",
1528
+ f"错误分类标签: {tags}",
1529
+ "允许的修复:修正入口/模块声明/依赖;对入口文件与必要mod.rs进行轻微调整;在缺失/未实现的被调函数导致错误时,一并补齐这些依赖的Rust实现(可新增合理模块/函数);避免大范围改动。",
1530
+ "- 保持最小改动,避免与错误无关的重构或格式化;",
1531
+ "- 如构建失败源于缺失或未实现的被调函数/依赖,请阅读其 C 源码并在本次一并补齐等价的 Rust 实现;必要时可在合理的模块中新建函数;",
1532
+ "- 禁止使用 todo!/unimplemented! 作为占位;",
1533
+ "- 可使用工具 read_symbols/read_code 获取依赖符号的 C 源码与位置以辅助实现;仅精确导入所需符号,避免通配;",
1534
+ f"- 依赖管理:如修复中引入新的外部 crate 或需要启用 feature,请同步更新 Cargo.toml 的 [dependencies]/[dev-dependencies]/[features]{(',避免未声明依赖导致构建失败;版本号可使用兼容范围(如 ^x.y)或默认值' if stage == 'cargo test' else '')};",
1535
+ "",
1536
+ "【重要:依赖检查与实现要求】",
1537
+ "在修复问题之前,请务必检查以下内容:",
1538
+ "1. 检查当前函数是否已完整实现:",
1539
+ f" - 在目标模块中查找函数 {sym_name} 的实现",
1540
+ " - 如果已存在实现,检查其是否完整且正确",
1541
+ "2. 检查所有依赖函数是否已实现:",
1542
+ " - 分析构建错误,识别所有缺失或未实现的被调函数",
1543
+ " - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
1544
+ " - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
1545
+ " - 可以使用 read_code 工具读取相关模块文件进行检查",
1546
+ " - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
1547
+ "3. 对于未实现的依赖函数:",
1548
+ " - 使用 read_symbols 工具获取其 C 源码和符号信息",
1549
+ " - 使用 read_code 工具读取其 C 源码实现",
1550
+ " - 在本次修复中一并补齐这些依赖函数的 Rust 实现",
1551
+ " - 根据依赖关系选择合适的模块位置(可在同一模块或合理的新模块中)",
1552
+ " - 确保所有依赖函数都有完整实现,禁止使用 todo!/unimplemented! 占位",
1553
+ "4. 实现顺序:",
1554
+ " - 优先实现最底层的依赖函数(不依赖其他未实现函数的函数)",
1555
+ " - 然后实现依赖这些底层函数的函数",
1556
+ " - 最后修复当前目标函数",
1557
+ "5. 验证:",
1558
+ " - 确保当前函数及其所有依赖函数都已完整实现",
1559
+ " - 确保没有遗留的 todo!/unimplemented! 占位",
1560
+ " - 确保所有函数调用都能正确解析",
1561
+ ]
1562
+ if include_output_patch_hint:
1563
+ base_lines.append("- 请仅输出补丁,不要输出解释或多余文本。")
1564
+ base_lines.extend([
1565
+ "",
1566
+ "最近处理的函数上下文(供参考,优先修复构建错误):",
1567
+ f"- 函数:{sym_name}",
1568
+ f"- 源位置:{src_loc}",
1569
+ f"- 目标模块(progress):{curr.get('module') or ''}",
1570
+ f"- 建议签名(progress):{curr.get('rust_signature') or ''}",
1571
+ "",
1572
+ "原始C函数源码片段(只读参考):",
1573
+ "<C_SOURCE>",
1574
+ c_code,
1575
+ "</C_SOURCE>",
1576
+ "",
1577
+ "如需定位或交叉验证 C 符号位置,请使用符号表检索工具:",
1578
+ "- 工具: read_symbols",
1579
+ "- 参数示例(YAML):",
1580
+ f" symbols_file: \"{symbols_path}\"",
1581
+ " symbols:",
1582
+ f" - \"{sym_name}\"",
1583
+ "",
1584
+ "上下文:",
1585
+ f"- crate 根目录路径: {self.crate_dir.resolve()}",
1586
+ ])
1587
+ if stage == "cargo check":
1588
+ base_lines.append(f"- 包名称(用于 cargo -p): {self.crate_dir.name}")
1589
+ else:
1590
+ base_lines.append(f"- 包名称(用于 cargo build -p): {self.crate_dir.name}")
1591
+ if stage == "cargo test":
1592
+ base_lines.extend([
1593
+ "",
1594
+ "【测试失败信息】",
1595
+ "以下输出来自 `cargo test` 命令,包含测试执行结果和失败详情:",
1596
+ "- 如果看到测试用例名称和断言失败,说明测试逻辑或实现有问题",
1597
+ "- 如果看到编译错误,说明代码存在语法或类型错误",
1598
+ "- 请仔细阅读失败信息,包括:测试用例名称、断言失败位置、期望值与实际值、堆栈跟踪等",
1599
+ "",
1600
+ "请阅读以下测试失败信息并进行必要修复:",
1601
+ "<TEST_FAILURE>",
1602
+ output,
1603
+ "</TEST_FAILURE>",
1604
+ "",
1605
+ "修复后请再次执行 `cargo test` 进行验证。",
1606
+ ])
1607
+ else:
1608
+ base_lines.extend([
1609
+ "",
1610
+ "请阅读以下构建错误并进行必要修复:",
1611
+ "<BUILD_ERROR>",
1612
+ output,
1613
+ "</BUILD_ERROR>",
1614
+ "",
1615
+ "修复后请再次执行 `cargo check` 验证,后续将自动运行 `cargo test`。",
1616
+ ])
1617
+ return "\n".join(base_lines)
1618
+
1619
+ def _cargo_build_loop(self) -> bool:
1620
+ """在 crate 目录执行构建与测试:先 cargo check,再 cargo test。失败则最小化修复直到通过或达到上限。"""
1621
+ workspace_root = str(self.crate_dir)
1622
+ check_limit = f"最大重试: {self.check_max_retries if self.check_max_retries > 0 else '无限'}"
1623
+ test_limit = f"最大重试: {self.test_max_retries if self.test_max_retries > 0 else '无限'}"
1624
+ typer.secho(f"[c2rust-transpiler][build] 工作区={workspace_root},开始构建循环(check -> test,{check_limit} / {test_limit})", fg=typer.colors.MAGENTA)
1625
+ check_iter = 0
1626
+ test_iter = 0
1627
+ while True:
1628
+ # 阶段一:cargo check(更快)
1629
+ check_iter += 1
1630
+ res_check = subprocess.run(
1631
+ ["cargo", "check", "-q"],
1632
+ capture_output=True,
1633
+ text=True,
1634
+ check=False,
1635
+ cwd=workspace_root,
1636
+ )
1637
+ if res_check.returncode != 0:
1638
+ output = (res_check.stdout or "") + "\n" + (res_check.stderr or "")
1639
+ limit_info = f" (上限: {self.check_max_retries if self.check_max_retries > 0 else '无限'})" if check_iter % 10 == 0 or check_iter == 1 else ""
1640
+ typer.secho(f"[c2rust-transpiler][build] cargo check 失败 (第 {check_iter} 次尝试{limit_info})。", fg=typer.colors.RED)
1641
+ typer.secho(output, fg=typer.colors.RED)
1642
+ # 达到上限则记录并退出(0表示无限重试)
1643
+ maxr = self.check_max_retries
1644
+ if maxr > 0 and check_iter >= maxr:
1645
+ typer.secho(f"[c2rust-transpiler][build] 已达到最大重试次数上限({maxr}),停止构建修复循环。", fg=typer.colors.RED)
1646
+ try:
1647
+ cur = self.progress.get("current") or {}
1648
+ metrics = cur.get("metrics") or {}
1649
+ metrics["check_attempts"] = int(check_iter)
1650
+ metrics["test_attempts"] = int(test_iter)
1651
+ cur["metrics"] = metrics
1652
+ cur["impl_verified"] = False
1653
+ cur["failed_stage"] = "check"
1654
+ err_summary = (output or "").strip()
1655
+ if len(err_summary) > ERROR_SUMMARY_MAX_LENGTH:
1656
+ err_summary = err_summary[:ERROR_SUMMARY_MAX_LENGTH] + "...(truncated)"
1657
+ cur["last_build_error"] = err_summary
1658
+ self.progress["current"] = cur
1659
+ self._save_progress()
1660
+ except Exception:
1661
+ pass
1662
+ return False
1663
+ # 提示修复(分类标签)
1664
+ tags = self._classify_rust_error(output)
1665
+ symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
1666
+ curr, sym_name, src_loc, c_code = self._get_current_function_context()
1667
+ repair_prompt = self._build_repair_prompt(
1668
+ stage="cargo check",
1669
+ output=output,
1670
+ tags=tags,
1671
+ sym_name=sym_name,
1672
+ src_loc=src_loc,
1673
+ c_code=c_code,
1674
+ curr=curr,
1675
+ symbols_path=symbols_path,
1676
+ include_output_patch_hint=False,
1677
+ )
1678
+ prev_cwd = os.getcwd()
1679
+ try:
1680
+ os.chdir(str(self.crate_dir))
1681
+ agent = self._get_repair_agent()
1682
+ agent.run(self._compose_prompt_with_context(repair_prompt), prefix=f"[c2rust-transpiler][build-fix iter={check_iter}][check]", suffix="")
1683
+ # 修复后进行轻量验证:检查语法是否正确
1684
+ res_verify = subprocess.run(
1685
+ ["cargo", "check", "--message-format=short", "-q"],
1686
+ capture_output=True,
1687
+ text=True,
1688
+ check=False,
1689
+ cwd=workspace_root,
1690
+ )
1691
+ if res_verify.returncode == 0:
1692
+ typer.secho("[c2rust-transpiler][build] 修复后验证通过,继续构建循环", fg=typer.colors.GREEN)
1693
+ else:
1694
+ typer.secho("[c2rust-transpiler][build] 修复后验证仍有错误,将在下一轮循环中处理", fg=typer.colors.YELLOW)
1695
+ finally:
1696
+ os.chdir(prev_cwd)
1697
+ # 下一轮循环
1698
+ continue
1699
+
1700
+ # 阶段二:cargo test(check 通过后才执行)
1701
+ test_iter += 1
1702
+ # 测试失败时需要详细输出,移除 -q 参数以获取完整的测试失败信息(包括堆栈跟踪、断言详情等)
1703
+ res = subprocess.run(
1704
+ ["cargo", "test", "--", "--nocapture"],
1705
+ capture_output=True,
1706
+ text=True,
1707
+ check=False,
1708
+ cwd=workspace_root,
1709
+ )
1710
+ if res.returncode == 0:
1711
+ typer.secho("[c2rust-transpiler][build] Cargo 测试通过。", fg=typer.colors.GREEN)
1712
+ try:
1713
+ cur = self.progress.get("current") or {}
1714
+ metrics = cur.get("metrics") or {}
1715
+ metrics["check_attempts"] = int(check_iter)
1716
+ metrics["test_attempts"] = int(test_iter)
1717
+ cur["metrics"] = metrics
1718
+ cur["impl_verified"] = True
1719
+ cur["failed_stage"] = None
1720
+ self.progress["current"] = cur
1721
+ self._save_progress()
1722
+ except Exception:
1723
+ pass
1724
+ return True
1725
+
1726
+ output = (res.stdout or "") + "\n" + (res.stderr or "")
1727
+ limit_info = f" (上限: {self.test_max_retries if self.test_max_retries > 0 else '无限'})" if test_iter % 10 == 0 or test_iter == 1 else ""
1728
+ typer.secho(f"[c2rust-transpiler][build] Cargo 测试失败 (第 {test_iter} 次尝试{limit_info})。", fg=typer.colors.RED)
1729
+ typer.secho(output, fg=typer.colors.RED)
1730
+ maxr = self.test_max_retries
1731
+ if maxr > 0 and test_iter >= maxr:
1732
+ typer.secho(f"[c2rust-transpiler][build] 已达到最大重试次数上限({maxr}),停止构建修复循环。", fg=typer.colors.RED)
1733
+ try:
1734
+ cur = self.progress.get("current") or {}
1735
+ metrics = cur.get("metrics") or {}
1736
+ metrics["check_attempts"] = int(check_iter)
1737
+ metrics["test_attempts"] = int(test_iter)
1738
+ cur["metrics"] = metrics
1739
+ cur["impl_verified"] = False
1740
+ cur["failed_stage"] = "test"
1741
+ err_summary = (output or "").strip()
1742
+ if len(err_summary) > ERROR_SUMMARY_MAX_LENGTH:
1743
+ err_summary = err_summary[:ERROR_SUMMARY_MAX_LENGTH] + "...(truncated)"
1744
+ cur["last_build_error"] = err_summary
1745
+ self.progress["current"] = cur
1746
+ self._save_progress()
1747
+ except Exception:
1748
+ pass
1749
+ return False
1750
+
1751
+ # 构建失败(测试阶段)修复
1752
+ tags = self._classify_rust_error(output)
1753
+ symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
1754
+ curr, sym_name, src_loc, c_code = self._get_current_function_context()
1755
+ repair_prompt = self._build_repair_prompt(
1756
+ stage="cargo test",
1757
+ output=output,
1758
+ tags=tags,
1759
+ sym_name=sym_name,
1760
+ src_loc=src_loc,
1761
+ c_code=c_code,
1762
+ curr=curr,
1763
+ symbols_path=symbols_path,
1764
+ include_output_patch_hint=True,
1765
+ )
1766
+ prev_cwd = os.getcwd()
1767
+ try:
1768
+ os.chdir(str(self.crate_dir))
1769
+ agent = self._get_repair_agent()
1770
+ agent.run(self._compose_prompt_with_context(repair_prompt), prefix=f"[c2rust-transpiler][build-fix iter={test_iter}][test]", suffix="")
1771
+ # 修复后进行轻量验证:检查语法是否正确
1772
+ res_verify = subprocess.run(
1773
+ ["cargo", "test", "--message-format=short", "-q", "--no-run"],
1774
+ capture_output=True,
1775
+ text=True,
1776
+ check=False,
1777
+ cwd=workspace_root,
1778
+ )
1779
+ if res_verify.returncode == 0:
1780
+ typer.secho("[c2rust-transpiler][build] 修复后验证通过,继续构建循环", fg=typer.colors.GREEN)
1781
+ else:
1782
+ typer.secho("[c2rust-transpiler][build] 修复后验证仍有错误,将在下一轮循环中处理", fg=typer.colors.YELLOW)
1783
+ finally:
1784
+ os.chdir(prev_cwd)
1785
+ # 重置 check 迭代计数,因为修复后需要重新 check
1786
+ check_iter = 0
1787
+
1788
+ def _review_and_optimize(self, rec: FnRecord, module: str, rust_sig: str) -> None:
1789
+ """
1790
+ 审查生成的实现;若 summary 报告问题,则调用 CodeAgent 进行优化,直到无问题或次数用尽。
1791
+ 合并了功能一致性审查和类型/边界严重问题审查,避免重复审查。
1792
+ 审查只关注本次函数与相关最小上下文,避免全局重构。
1793
+ """
1794
+ def build_review_prompts() -> Tuple[str, str, str]:
1795
+ sys_p = (
1796
+ "你是Rust代码审查专家。验收标准:Rust 实现应与原始 C 实现在功能上一致,且不应包含可能导致功能错误的严重问题。\n"
1797
+ "审查标准(合并了功能一致性和严重问题检查):\n"
1798
+ "1. 功能一致性检查:\n"
1799
+ " - 核心输入输出、主要功能逻辑是否与 C 实现一致;\n"
1800
+ " - 允许 Rust 实现修复 C 代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用等),这些修复不应被视为功能不一致;\n"
1801
+ " - 允许 Rust 实现使用不同的类型设计、错误处理方式、资源管理方式等,只要功能一致即可;\n"
1802
+ "2. 严重问题检查(可能导致功能错误):\n"
1803
+ " - 明显的空指针解引用或会导致 panic 的严重错误;\n"
1804
+ " - 明显的越界访问或会导致程序崩溃的问题;\n"
1805
+ "不检查类型匹配、指针可变性、边界检查细节、资源释放细节、内存语义等技术细节(除非会导致功能错误)。\n"
1806
+ "请在总结阶段详细指出问题和修改建议,但不要尝试修复或修改任何代码,不要输出补丁。"
1807
+ )
1808
+ # 附加原始C函数源码片段,供审查作为只读参考
1809
+ c_code = self._read_source_span(rec) or ""
1810
+ # 附加被引用符号上下文与库替代上下文,以及crate目录结构,提供更完整审查背景
1811
+ callees_ctx = self._collect_callees_context(rec)
1812
+ librep_ctx = rec.lib_replacement if isinstance(rec.lib_replacement, dict) else None
1813
+ crate_tree = _dir_tree(self.crate_dir)
1814
+ usr_p = "\n".join([
1815
+ f"待审查函数:{rec.qname or rec.name}",
1816
+ f"建议签名:{rust_sig}",
1817
+ f"目标模块:{module}",
1818
+ f"crate根目录路径:{self.crate_dir.resolve()}",
1819
+ f"源文件位置:{rec.file}:{rec.start_line}-{rec.end_line}",
1820
+ "",
1821
+ "原始C函数源码片段(只读参考,不要修改C代码):",
1822
+ "<C_SOURCE>",
1823
+ c_code,
1824
+ "</C_SOURCE>",
1825
+ "",
1826
+ "审查说明(合并审查):",
1827
+ "1. 功能一致性:",
1828
+ " - 核心输入输出、主要功能逻辑是否与 C 实现一致;",
1829
+ " - 允许Rust实现修复C代码中的安全漏洞(如缓冲区溢出、空指针解引用、未初始化内存使用等),这些修复不应被视为功能不一致;",
1830
+ " - 允许Rust实现使用不同的类型设计、错误处理方式、资源管理方式等,只要功能一致即可;",
1831
+ "2. 严重问题(可能导致功能错误):",
1832
+ " - 明显的空指针解引用或会导致 panic 的严重错误;",
1833
+ " - 明显的越界访问或会导致程序崩溃的问题;",
1834
+ "不检查类型匹配、指针可变性、边界检查细节等技术细节(除非会导致功能错误)。",
1835
+ "",
1836
+ "被引用符号上下文(如已转译则包含Rust模块信息):",
1837
+ json.dumps(callees_ctx, ensure_ascii=False, indent=2),
1838
+ "",
1839
+ "库替代上下文(若存在):",
1840
+ json.dumps(librep_ctx, ensure_ascii=False, indent=2),
1841
+ "",
1842
+ "当前crate目录结构(部分):",
1843
+ "<CRATE_TREE>",
1844
+ crate_tree,
1845
+ "</CRATE_TREE>",
1846
+ "",
1847
+ "如需定位或交叉验证 C 符号位置,请使用符号表检索工具:",
1848
+ "- 工具: read_symbols",
1849
+ "- 参数示例(YAML):",
1850
+ f" symbols_file: \"{(self.data_dir / 'symbols.jsonl').resolve()}\"",
1851
+ " symbols:",
1852
+ f" - \"{rec.qname or rec.name}\"",
1853
+ "",
1854
+ "请阅读crate中该函数的当前实现(你可以在上述crate根路径下自行读取必要上下文),并准备总结。",
1855
+ ])
1856
+ sum_p = (
1857
+ "请仅输出一个 <SUMMARY> 块,块内为单个 <yaml> 对象,字段:\n"
1858
+ "ok: bool # 若满足功能一致且无严重问题,则为 true\n"
1859
+ "function_issues: [string, ...] # 功能一致性问题,每项以 [function] 开头,示例:[function] missing return value handling\n"
1860
+ "critical_issues: [string, ...] # 严重问题(可能导致功能错误),每项以 [critical] 开头,示例:[critical] obvious null pointer dereference\n"
1861
+ "注意:\n"
1862
+ "- 前置条件:必须在crate中找到该函数的实现(匹配函数名或签名)。若未找到,ok 必须为 false,function_issues 应包含 [function] function not found\n"
1863
+ "- 若Rust实现修复了C代码中的安全漏洞或使用了不同的实现方式但保持了功能一致,且无严重问题,ok 应为 true\n"
1864
+ "- 仅报告功能不一致和严重问题,不报告类型匹配、指针可变性、边界检查细节等技术细节(除非会导致功能错误)\n"
1865
+ "<SUMMARY><yaml>\nok: true\nfunction_issues: []\ncritical_issues: []\n</yaml></SUMMARY>"
1866
+ )
1867
+ return sys_p, usr_p, sum_p
1868
+
1869
+ i = 0
1870
+ max_iterations = self.review_max_iterations
1871
+ # 复用 Review Agent(仅在本函数生命周期内构建一次)
1872
+ review_key = f"review::{rec.id}"
1873
+ sys_p_init, usr_p_init, sum_p_init = build_review_prompts()
1874
+ if self._current_agents.get(review_key) is None:
1875
+ self._current_agents[review_key] = Agent(
1876
+ system_prompt=sys_p_init,
1877
+ name="C2Rust-Review-Agent",
1878
+ model_group=self.llm_group,
1879
+ summary_prompt=sum_p_init,
1880
+ need_summary=True,
1881
+ auto_complete=True,
1882
+ use_tools=["execute_script", "read_code", "retrieve_memory", "save_memory", "read_symbols"],
1883
+ plan=False,
1884
+ non_interactive=self.non_interactive,
1885
+ use_methodology=False,
1886
+ use_analysis=False,
1887
+ disable_file_edit=True,
1888
+ )
1889
+
1890
+ # 0表示无限重试,否则限制迭代次数
1891
+ use_direct_model_review = False # 标记是否使用直接模型调用
1892
+ parse_failed = False # 标记上一次解析是否失败
1893
+ parse_error_msg: Optional[str] = None # 保存上一次的YAML解析错误信息
1894
+ while max_iterations == 0 or i < max_iterations:
1895
+ agent = self._current_agents[review_key]
1896
+ prev_cwd = os.getcwd()
1897
+ try:
1898
+ os.chdir(str(self.crate_dir))
1899
+ # 如果是修复后的审查(i > 0),在提示词中明确说明代码已变更,需要重新读取
1900
+ if i > 0:
1901
+ code_changed_notice = "\n".join([
1902
+ "",
1903
+ "【重要提示:代码已变更】",
1904
+ f"在本次审查之前(第 {i} 次迭代),已根据审查意见对代码进行了修复和优化。",
1905
+ "目标函数的实现可能已经发生变化,包括但不限于:",
1906
+ "- 函数实现逻辑的修改",
1907
+ "- 类型和签名的调整",
1908
+ "- 依赖关系的更新",
1909
+ "- 错误处理的改进",
1910
+ "",
1911
+ "**请务必重新读取目标模块文件中的函数实现,不要基于之前的审查结果或缓存信息进行判断。**",
1912
+ "请使用 read_code 工具重新读取以下文件的最新内容:",
1913
+ f"- 目标模块文件: {module}",
1914
+ "- 以及相关的依赖模块文件(如有需要)",
1915
+ "",
1916
+ "审查时请基于重新读取的最新代码内容进行评估。",
1917
+ "",
1918
+ ])
1919
+ usr_p_with_notice = usr_p_init + code_changed_notice
1920
+ composed_prompt = self._compose_prompt_with_context(usr_p_with_notice)
1921
+ else:
1922
+ composed_prompt = self._compose_prompt_with_context(usr_p_init)
1923
+
1924
+ if use_direct_model_review:
1925
+ # 格式解析失败后,直接调用模型接口
1926
+ # 构造包含摘要提示词和具体错误信息的完整提示
1927
+ error_guidance = ""
1928
+ # 检查上一次的解析结果
1929
+ if parse_error_msg:
1930
+ # 如果有YAML解析错误,优先反馈
1931
+ error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- YAML解析失败: {parse_error_msg}\n\n请确保输出的YAML格式正确,包括正确的缩进、引号、冒号等。YAML 对象必须包含字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)。"
1932
+ elif parse_failed:
1933
+ error_guidance = "\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- 无法从摘要中解析出有效的 YAML 对象\n\n请确保输出格式正确:仅输出一个 <SUMMARY> 块,块内为单个 <yaml> 对象,字段:ok(布尔值)、function_issues(字符串数组)、critical_issues(字符串数组)。"
1934
+
1935
+ full_prompt = f"{composed_prompt}{error_guidance}\n\n{sum_p_init}"
1936
+ try:
1937
+ response = agent.model.chat_until_success(full_prompt) # type: ignore
1938
+ summary = str(response or "")
1939
+ except Exception as e:
1940
+ typer.secho(f"[c2rust-transpiler][review] 直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
1941
+ summary = str(agent.run(composed_prompt) or "")
1942
+ else:
1943
+ # 第一次使用 run(),让 Agent 完整运行(可能使用工具)
1944
+ summary = str(agent.run(composed_prompt) or "")
1945
+ finally:
1946
+ os.chdir(prev_cwd)
1947
+
1948
+ # 解析 YAML 格式的审查结果
1949
+ verdict, parse_error_review = _extract_json_from_summary(summary)
1950
+ parse_failed = False
1951
+ parse_error_msg = None
1952
+ if parse_error_review:
1953
+ # YAML解析失败
1954
+ parse_failed = True
1955
+ parse_error_msg = parse_error_review
1956
+ typer.secho(f"[c2rust-transpiler][review] YAML解析失败: {parse_error_review}", fg=typer.colors.YELLOW)
1957
+ # 兼容旧格式:尝试解析纯文本 OK
1958
+ m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", summary, flags=re.IGNORECASE)
1959
+ content = (m.group(1).strip() if m else summary.strip()).upper()
1960
+ if content == "OK":
1961
+ verdict = {"ok": True, "function_issues": [], "critical_issues": []}
1962
+ parse_failed = False # 兼容格式成功,不算解析失败
1963
+ parse_error_msg = None
1964
+ else:
1965
+ # 无法解析,视为有问题
1966
+ verdict = {"ok": False, "function_issues": [content], "critical_issues": []}
1967
+ # 格式解析失败,后续迭代使用直接模型调用
1968
+ use_direct_model_review = True
1969
+ elif not isinstance(verdict, dict):
1970
+ parse_failed = True
1971
+ # 兼容旧格式:尝试解析纯文本 OK
1972
+ m = re.search(r"<SUMMARY>([\s\S]*?)</SUMMARY>", summary, flags=re.IGNORECASE)
1973
+ content = (m.group(1).strip() if m else summary.strip()).upper()
1974
+ if content == "OK":
1975
+ verdict = {"ok": True, "function_issues": [], "critical_issues": []}
1976
+ parse_failed = False # 兼容格式成功,不算解析失败
1977
+ else:
1978
+ # 无法解析,视为有问题
1979
+ verdict = {"ok": False, "function_issues": [content], "critical_issues": []}
1980
+ # 格式解析失败,后续迭代使用直接模型调用
1981
+ use_direct_model_review = True
1982
+
1983
+ ok = bool(verdict.get("ok") is True)
1984
+ function_issues = verdict.get("function_issues") if isinstance(verdict.get("function_issues"), list) else []
1985
+ critical_issues = verdict.get("critical_issues") if isinstance(verdict.get("critical_issues"), list) else []
1986
+ all_issues = function_issues + critical_issues
1987
+
1988
+ typer.secho(f"[c2rust-transpiler][review][iter={i+1}] verdict ok={ok}, function_issues={len(function_issues)}, critical_issues={len(critical_issues)}", fg=typer.colors.CYAN)
1989
+
1990
+ if ok and not all_issues:
1991
+ limit_info = f" (上限: {max_iterations if max_iterations > 0 else '无限'})"
1992
+ typer.secho(f"[c2rust-transpiler][review] 代码审查通过{limit_info} (共 {i+1} 次迭代)。", fg=typer.colors.GREEN)
1993
+ # 记录审查结果到进度
1994
+ try:
1995
+ cur = self.progress.get("current") or {}
1996
+ cur["review"] = {
1997
+ "ok": True,
1998
+ "function_issues": [],
1999
+ "critical_issues": [],
2000
+ "iterations": i + 1,
2001
+ }
2002
+ metrics = cur.get("metrics") or {}
2003
+ metrics["review_iterations"] = i + 1
2004
+ metrics["function_issues"] = 0
2005
+ metrics["type_issues"] = 0
2006
+ cur["metrics"] = metrics
2007
+ self.progress["current"] = cur
2008
+ self._save_progress()
2009
+ except Exception:
2010
+ pass
2011
+ return
2012
+
2013
+ # 需要优化:提供详细上下文背景,并明确审查意见仅针对 Rust crate,不修改 C 源码
2014
+ crate_tree = _dir_tree(self.crate_dir)
2015
+ issues_text = "\n".join([
2016
+ "功能一致性问题:" if function_issues else "",
2017
+ *[f" - {issue}" for issue in function_issues],
2018
+ "严重问题(可能导致功能错误):" if critical_issues else "",
2019
+ *[f" - {issue}" for issue in critical_issues],
2020
+ ])
2021
+ fix_prompt = "\n".join([
2022
+ "请根据以下审查结论对目标函数进行最小优化(保留结构与意图,不进行大范围重构):",
2023
+ "<REVIEW>",
2024
+ issues_text if issues_text.strip() else "审查发现问题,但未提供具体问题描述",
2025
+ "</REVIEW>",
2026
+ "",
2027
+ "上下文背景信息:",
2028
+ f"- crate_dir: {self.crate_dir.resolve()}",
2029
+ f"- 目标模块文件: {module}",
2030
+ f"- 建议/当前 Rust 签名: {rust_sig}",
2031
+ "crate 目录结构(部分):",
2032
+ crate_tree,
2033
+ "",
2034
+ "约束与范围:",
2035
+ "- 本次审查意见仅针对 Rust crate 的代码与配置;不要修改任何 C/C++ 源文件(*.c、*.h 等)。",
2036
+ "- 仅允许在 crate_dir 下进行最小必要修改(Cargo.toml、src/**/*.rs);不要改动其他目录。",
2037
+ "- 保持最小改动,避免与问题无关的重构或格式化。",
2038
+ "- 优先修复严重问题(可能导致功能错误),然后修复功能一致性问题;",
2039
+ "- 如审查问题涉及缺失/未实现的被调函数或依赖,请阅读其 C 源码并在本次一并补齐等价的 Rust 实现;必要时在合理模块新增函数或引入精确 use;",
2040
+ "- 禁止使用 todo!/unimplemented! 作为占位;",
2041
+ "- 可使用工具 read_symbols/read_code 获取依赖符号的 C 源码与位置以辅助实现;仅精确导入所需符号(禁止通配);",
2042
+ "",
2043
+ "【重要:依赖检查与实现要求】",
2044
+ "在优化函数之前,请务必检查以下内容:",
2045
+ "1. 检查当前函数是否已完整实现:",
2046
+ f" - 在目标模块 {module} 中查找函数 {rec.qname or rec.name} 的实现",
2047
+ " - 如果已存在实现,检查其是否完整且正确",
2048
+ "2. 检查所有依赖函数是否已实现:",
2049
+ " - 遍历当前函数调用的所有被调函数(包括直接调用和间接调用)",
2050
+ " - 对于每个被调函数,检查其在 Rust crate 中是否已有完整实现",
2051
+ " - 可以使用 read_code 工具读取相关模块文件进行检查",
2052
+ " - 可以使用 retrieve_memory 工具检索已保存的函数实现记忆",
2053
+ "3. 对于未实现的依赖函数:",
2054
+ " - 使用 read_symbols 工具获取其 C 源码和符号信息",
2055
+ " - 使用 read_code 工具读取其 C 源码实现",
2056
+ " - 在本次优化中一并补齐这些依赖函数的 Rust 实现",
2057
+ " - 根据依赖关系选择合适的模块位置(可在同一模块或合理的新模块中)",
2058
+ " - 确保所有依赖函数都有完整实现,禁止使用 todo!/unimplemented! 占位",
2059
+ "4. 实现顺序:",
2060
+ " - 优先实现最底层的依赖函数(不依赖其他未实现函数的函数)",
2061
+ " - 然后实现依赖这些底层函数的函数",
2062
+ " - 最后优化当前目标函数",
2063
+ "5. 验证:",
2064
+ " - 确保当前函数及其所有依赖函数都已完整实现",
2065
+ " - 确保没有遗留的 todo!/unimplemented! 占位",
2066
+ " - 确保所有函数调用都能正确解析",
2067
+ "",
2068
+ "请仅以补丁形式输出修改,避免冗余解释。",
2069
+ ])
2070
+ # 在当前工作目录运行复用的修复 CodeAgent
2071
+ ca = self._get_repair_agent()
2072
+ prev_cwd = os.getcwd()
2073
+ try:
2074
+ os.chdir(str(self.crate_dir))
2075
+ limit_info = f"/{max_iterations}" if max_iterations > 0 else "/∞"
2076
+ ca.run(self._compose_prompt_with_context(fix_prompt), prefix=f"[c2rust-transpiler][review-fix iter={i+1}{limit_info}]", suffix="")
2077
+ # 优化后进行一次构建验证;若未通过则进入构建修复循环,直到通过为止
2078
+ self._cargo_build_loop()
2079
+ finally:
2080
+ os.chdir(prev_cwd)
2081
+
2082
+ # 记录本次审查结果
2083
+ try:
2084
+ cur = self.progress.get("current") or {}
2085
+ cur["review"] = {
2086
+ "ok": False,
2087
+ "function_issues": list(function_issues),
2088
+ "critical_issues": list(critical_issues),
2089
+ "iterations": i + 1,
2090
+ }
2091
+ metrics = cur.get("metrics") or {}
2092
+ metrics["function_issues"] = len(function_issues)
2093
+ metrics["type_issues"] = len(critical_issues)
2094
+ cur["metrics"] = metrics
2095
+ self.progress["current"] = cur
2096
+ self._save_progress()
2097
+ except Exception:
2098
+ pass
2099
+
2100
+ i += 1
2101
+
2102
+ # 达到迭代上限(仅当设置了上限时)
2103
+ if max_iterations > 0 and i >= max_iterations:
2104
+ typer.secho(f"[c2rust-transpiler][review] 已达到最大迭代次数上限({max_iterations}),停止审查优化。", fg=typer.colors.YELLOW)
2105
+ try:
2106
+ cur = self.progress.get("current") or {}
2107
+ cur["review_max_iterations_reached"] = True
2108
+ cur["review_iterations"] = i
2109
+ self.progress["current"] = cur
2110
+ self._save_progress()
2111
+ except Exception:
2112
+ pass
2113
+
2114
+ def _mark_converted(self, rec: FnRecord, module: str, rust_sig: str) -> None:
2115
+ """记录映射:C 符号 -> Rust 符号与模块路径(JSONL,每行一条,支持重载/同名)"""
2116
+ rust_symbol = ""
2117
+ # 从签名中提取函数名(支持生命周期参数和泛型参数)
2118
+ # 支持生命周期参数和泛型参数:fn name<'a, T>(...)
2119
+ m = re.search(r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(", rust_sig)
2120
+ if m:
2121
+ rust_symbol = m.group(1)
2122
+ # 写入 JSONL 映射(带源位置,用于区分同名符号)
2123
+ self.symbol_map.add(rec, module, rust_symbol or (rec.name or f"fn_{rec.id}"))
2124
+
2125
+ # 更新进度:已转换集合
2126
+ converted = self.progress.get("converted") or []
2127
+ if rec.id not in converted:
2128
+ converted.append(rec.id)
2129
+ self.progress["converted"] = converted
2130
+ self.progress["current"] = None
2131
+ self._save_progress()
2132
+
2133
+ def transpile(self) -> None:
2134
+ """主流程"""
2135
+ typer.secho("[c2rust-transpiler][start] 开始转译", fg=typer.colors.BLUE)
2136
+ # 准确性兜底:在未执行 prepare 的情况下,确保 crate 目录与最小 Cargo 配置存在
2137
+ try:
2138
+ cd = self.crate_dir.resolve()
2139
+ cd.mkdir(parents=True, exist_ok=True)
2140
+ cargo = cd / "Cargo.toml"
2141
+ src_dir = cd / "src"
2142
+ lib_rs = src_dir / "lib.rs"
2143
+ # 最小 Cargo.toml(不覆盖已有),edition 使用 2021 以兼容更广环境
2144
+ if not cargo.exists():
2145
+ pkg_name = cd.name
2146
+ content = (
2147
+ f'[package]\nname = "{pkg_name}"\nversion = "0.1.0"\nedition = "2021"\n\n'
2148
+ '[lib]\npath = "src/lib.rs"\n'
2149
+ )
2150
+ try:
2151
+ cargo.write_text(content, encoding="utf-8")
2152
+ typer.secho(f"[c2rust-transpiler][init] created Cargo.toml at {cargo}", fg=typer.colors.GREEN)
2153
+ except Exception:
2154
+ pass
2155
+ # 确保 src/lib.rs 存在
2156
+ src_dir.mkdir(parents=True, exist_ok=True)
2157
+ if not lib_rs.exists():
2158
+ try:
2159
+ lib_rs.write_text("// Auto-created by c2rust transpiler\n", encoding="utf-8")
2160
+ typer.secho(f"[c2rust-transpiler][init] created src/lib.rs at {lib_rs}", fg=typer.colors.GREEN)
2161
+ except Exception:
2162
+ pass
2163
+ except Exception:
2164
+ # 保持稳健,失败不阻塞主流程
2165
+ pass
2166
+
2167
+ order_path = _ensure_order_file(self.project_root)
2168
+ steps = _iter_order_steps(order_path)
2169
+ if not steps:
2170
+ typer.secho("[c2rust-transpiler] 未找到翻译步骤。", fg=typer.colors.YELLOW)
2171
+ return
2172
+
2173
+ # 构建自包含 order 索引(id -> FnRecord,name/qname -> id)
2174
+ self._load_order_index(order_path)
2175
+
2176
+ # 扁平化顺序,按单个函数处理(保持原有顺序)
2177
+ seq: List[int] = []
2178
+ for grp in steps:
2179
+ seq.extend(grp)
2180
+
2181
+ # 若支持 resume,则跳过 progress['converted'] 中已完成的
2182
+ done: Set[int] = set(self.progress.get("converted") or [])
2183
+ typer.secho(f"[c2rust-transpiler][order] 顺序信息: 步骤数={len(steps)} 总ID={sum(len(g) for g in steps)} 已转换={len(done)}", fg=typer.colors.BLUE)
2184
+
2185
+ for fid in seq:
2186
+ if fid in done:
2187
+ continue
2188
+ rec = self.fn_index_by_id.get(fid)
2189
+ if not rec:
2190
+ continue
2191
+ if self._should_skip(rec):
2192
+ typer.secho(f"[c2rust-transpiler][skip] 跳过 {rec.qname or rec.name} (id={rec.id}) 位于 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.YELLOW)
2193
+ continue
2194
+
2195
+ # 读取C函数源码
2196
+ typer.secho(f"[c2rust-transpiler][read] 读取 C 源码: {rec.qname or rec.name} (id={rec.id}) 来自 {rec.file}:{rec.start_line}-{rec.end_line}", fg=typer.colors.BLUE)
2197
+ c_code = self._read_source_span(rec)
2198
+ typer.secho(f"[c2rust-transpiler][read] 已加载 {len(c_code.splitlines()) if c_code else 0} 行", fg=typer.colors.BLUE)
2199
+
2200
+ # 若缺少源码片段且缺乏签名/参数信息,则跳过本函数,记录进度以便后续处理
2201
+ if not c_code and not (getattr(rec, "signature", "") or getattr(rec, "params", None)):
2202
+ skipped = self.progress.get("skipped_missing_source") or []
2203
+ if rec.id not in skipped:
2204
+ skipped.append(rec.id)
2205
+ self.progress["skipped_missing_source"] = skipped
2206
+ typer.secho(f"[c2rust-transpiler] 跳过:缺少源码与签名信息 -> {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.YELLOW)
2207
+ self._save_progress()
2208
+ continue
2209
+ # 1) 规划:模块路径与Rust签名
2210
+ typer.secho(f"[c2rust-transpiler][plan] 正在规划模块与签名: {rec.qname or rec.name} (id={rec.id})", fg=typer.colors.CYAN)
2211
+ module, rust_sig = self._plan_module_and_signature(rec, c_code)
2212
+ typer.secho(f"[c2rust-transpiler][plan] 已选择 模块={module}, 签名={rust_sig}", fg=typer.colors.CYAN)
2213
+
2214
+ # 记录当前进度
2215
+ self._update_progress_current(rec, module, rust_sig)
2216
+ typer.secho(f"[c2rust-transpiler][progress] 已更新当前进度记录 id={rec.id}", fg=typer.colors.CYAN)
2217
+
2218
+ # 初始化函数上下文与代码编写与修复Agent复用缓存(只在当前函数开始时执行一次)
2219
+ self._reset_function_context(rec, module, rust_sig, c_code)
2220
+
2221
+ # 1.5) 确保模块声明链(提前到生成实现之前,避免生成的代码无法被正确引用)
2222
+ try:
2223
+ self._ensure_mod_chain_for_module(module)
2224
+ typer.secho(f"[c2rust-transpiler][mod] 已补齐 {module} 的 mod.rs 声明链", fg=typer.colors.GREEN)
2225
+ # 确保顶层模块在 src/lib.rs 中被公开
2226
+ mp = Path(module)
2227
+ crate_root = self.crate_dir.resolve()
2228
+ rel = mp.resolve().relative_to(crate_root) if mp.is_absolute() else Path(module)
2229
+ rel_s = str(rel).replace("\\", "/")
2230
+ if rel_s.startswith("./"):
2231
+ rel_s = rel_s[2:]
2232
+ if rel_s.startswith("src/"):
2233
+ parts = rel_s[len("src/"):].strip("/").split("/")
2234
+ if parts and parts[0]:
2235
+ top_mod = parts[0]
2236
+ if not top_mod.endswith(".rs"):
2237
+ self._ensure_top_level_pub_mod(top_mod)
2238
+ typer.secho(f"[c2rust-transpiler][mod] 已在 src/lib.rs 确保顶层 pub mod {top_mod}", fg=typer.colors.GREEN)
2239
+ cur = self.progress.get("current") or {}
2240
+ cur["mod_chain_fixed"] = True
2241
+ cur["mod_visibility_fixed"] = True
2242
+ self.progress["current"] = cur
2243
+ self._save_progress()
2244
+ except Exception:
2245
+ pass
2246
+
2247
+ # 2) 生成实现
2248
+ unresolved = self._untranslated_callee_symbols(rec)
2249
+ typer.secho(f"[c2rust-transpiler][deps] 未解析的被调符号: {', '.join(unresolved) if unresolved else '(none)'}", fg=typer.colors.BLUE)
2250
+ typer.secho(f"[c2rust-transpiler][gen] 正在为 {rec.qname or rec.name} 生成 Rust 实现", fg=typer.colors.GREEN)
2251
+ self._codeagent_generate_impl(rec, c_code, module, rust_sig, unresolved)
2252
+ typer.secho(f"[c2rust-transpiler][gen] 已在 {module} 生成或更新实现", fg=typer.colors.GREEN)
2253
+ # 刷新精简上下文(防止签名/模块调整后提示不同步)
2254
+ try:
2255
+ self._refresh_compact_context(rec, module, rust_sig)
2256
+ except Exception:
2257
+ pass
2258
+
2259
+ # 3) 构建与修复
2260
+ typer.secho("[c2rust-transpiler][build] 开始 cargo 测试循环", fg=typer.colors.MAGENTA)
2261
+ ok = self._cargo_build_loop()
2262
+ typer.secho(f"[c2rust-transpiler][build] 构建结果: {'通过' if ok else '失败'}", fg=typer.colors.MAGENTA)
2263
+ if not ok:
2264
+ typer.secho("[c2rust-transpiler] 在重试次数限制内未能成功构建,已停止。", fg=typer.colors.RED)
2265
+ # 保留当前状态,便于下次 resume
2266
+ return
2267
+
2268
+ # 4) 审查与优化(复用 Review Agent)
2269
+ typer.secho(f"[c2rust-transpiler][review] 开始代码审查: {rec.qname or rec.name}", fg=typer.colors.MAGENTA)
2270
+ self._review_and_optimize(rec, module, rust_sig)
2271
+ typer.secho("[c2rust-transpiler][review] 代码审查完成", fg=typer.colors.MAGENTA)
2272
+
2273
+ # 5) 标记已转换与映射记录(JSONL)
2274
+ self._mark_converted(rec, module, rust_sig)
2275
+ typer.secho(f"[c2rust-transpiler][mark] 已标记并建立映射: {rec.qname or rec.name} -> {module}", fg=typer.colors.GREEN)
2276
+
2277
+ # 6) 若此前有其它函数因依赖当前符号而在源码中放置了 todo!("<symbol>"),则立即回头消除(复用代码编写与修复Agent)
2278
+ current_rust_fn = self._extract_rust_fn_name_from_sig(rust_sig)
2279
+ # 优先使用限定名匹配,其次使用简单名匹配
2280
+ for sym in [rec.qname, rec.name]:
2281
+ if sym:
2282
+ typer.secho(f"[c2rust-transpiler][todo] 清理 todo!(\'{sym}\') 的出现位置", fg=typer.colors.BLUE)
2283
+ self._resolve_pending_todos_for_symbol(sym, module, current_rust_fn, rust_sig)
2284
+ typer.secho("[c2rust-transpiler][build] 处理 todo 后重新运行 cargo test", fg=typer.colors.MAGENTA)
2285
+ self._cargo_build_loop()
2286
+
2287
+ typer.secho("[c2rust-transpiler] 所有符合条件的函数均已处理完毕。", fg=typer.colors.GREEN)
2288
+
2289
+
2290
+ def run_transpile(
2291
+ project_root: Union[str, Path] = ".",
2292
+ crate_dir: Optional[Union[str, Path]] = None,
2293
+ llm_group: Optional[str] = None,
2294
+ plan_max_retries: int = 5,
2295
+ max_retries: int = 0, # 兼容旧接口
2296
+ check_max_retries: Optional[int] = None,
2297
+ test_max_retries: Optional[int] = None,
2298
+ review_max_iterations: int = DEFAULT_REVIEW_MAX_ITERATIONS,
2299
+ resume: bool = True,
2300
+ only: Optional[List[str]] = None,
2301
+ non_interactive: bool = True,
2302
+ ) -> None:
2303
+ """
2304
+ 入口函数:执行转译流程
2305
+ - project_root: 项目根目录(包含 .jarvis/c2rust/symbols.jsonl)
2306
+ - crate_dir: Rust crate 根目录;默认遵循 "<parent>/<cwd_name>_rs"(与当前目录同级,若 project_root 为 ".")
2307
+ - llm_group: 指定 LLM 模型组
2308
+ - max_retries: 构建与审查迭代的最大次数
2309
+ - resume: 是否启用断点续跑
2310
+ - only: 仅转译给定列表中的函数(函数名或限定名)
2311
+ """
2312
+ t = Transpiler(
2313
+ project_root=project_root,
2314
+ crate_dir=crate_dir,
2315
+ llm_group=llm_group,
2316
+ plan_max_retries=plan_max_retries,
2317
+ max_retries=max_retries,
2318
+ check_max_retries=check_max_retries,
2319
+ test_max_retries=test_max_retries,
2320
+ review_max_iterations=review_max_iterations,
2321
+ resume=resume,
2322
+ only=only,
2323
+ non_interactive=non_interactive,
2324
+ )
2325
+ t.transpile()