状态:Phase 1~4 已实施完成,旧 Dart 硬编码模板框架已移除;Phase 4 的前端加载链路(历史资产,已迁出)已随 2026-09-11 前端切割移出仓库;Phase 2 的同步脚本
scripts/sync_templates.py已由重构批次 R4 G1 于 2026-09-12 恢复为纯后端形态(渲染 + Clang Golden,不再产出 Flutter 资产)(现状见 §1.1、§3、§5.1、§5.4)
核心原则:算法模板即合法 C 代码 + 语法验证双重验证参照 + 自验证降低维护成本(后端原则不因切割改变)最后核对日期:2026-10-07(删区批现役对照回灌——§2.3 双验证表加现役等价物注记、§三 目录树重写为 corpus/ + clang_direct 现役版〔原 native/tests 与 shadow_verify 双驱动已随删区物理删除〕;设计原则章不变。前一沿革 2026-09-29 全库逐份翻新复核)
一、问题背景
1.1 算法模板与前端耦合
当前 82 个算法模板硬编码在 (已移除,历史资产,已迁出)。CideFlutter/lib/models/templates/*.dart 中
新框架下:
- 所有模板源码为合法 C/C++ 代码,存放于
templates/<key>/source.c(或source.cpp) - 模板的消费方只剩后端防线:渲染产物
native/tests/cases_template_generated/*.c被 Shadow Verification(防线 1)与 Rust E2E 直接复用 - 前端(Flutter/Dart)运行时的
index.json+ 模板源码加载链路已随 2026-09-11 前端切割整体移除;社区前端可直接把templates/<key>/当作模板源读取,但本仓库不再提供 index 产物与加载器 - ✅ 生成闭环已恢复(2026-09-12,R4 G1):同步脚本
scripts/sync_templates.py已从历史提交恢复为纯后端形态,"渲染templates/→cases_template_generated/*.c+ Clang Golden"重新可用;静态一致性校验test_templates.py仍未恢复(详见 §5.1 与模板维护指南.md§4.2)
1.2 语法验证重复劳动
同一个 for 循环用例当前维护多份:
| 位置 | 形式 | 问题 |
|---|---|---|
| Rust 单元测试 | C 字符串 | Parser/TypeChecker/VM 各测一遍 |
Python SHADOW_CASES |
Python 字符串 | 同一份代码再写一遍,且有 \n 转义陷阱 |
| 算法模板 Dart 文件 | Dart 字符串 | 算法代码第三遍 |
1.3 隐形成本(本次设计重点解决)
| 隐形成本 | 根因 | 后果 |
|---|---|---|
| 模板无法直接编译检查 | {{n}} 不是合法 C |
作者无法立即用 Clang 验证语法 |
| expected_stdout 静默过期 | 与源码示例数据分离 | 改数据后 expected 不匹配 |
| Tutorial 锚点漂移 | phase 与源码行号弱关联 | 重构后高亮错位 |
二、核心设计
2.1 算法模板:合法 C + 注释参数
占位符改为合法 C 注释标记,模板本身就是可编译的 C 代码。
// templates/bubble_sort/source.c
#include <stdio.h>
void bubbleSort(int arr[], int n) {
// @tutorial-anchor: outer_loop
for (int i = 0; i < n - 1; i++) {
// @tutorial-anchor: inner_loop
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int arr[/*__PARAM_n__*/ 5] = {5, 3, 8, 1, 2};
int expected[/*__PARAM_n__*/ 5] = {1, 2, 3, 5, 8};
int n = /*__PARAM_n__*/ 5;
bubbleSort(arr, n);
int ok = 1;
for (int i = 0; i < n; i++) if (arr[i] != expected[i]) ok = 0;
printf(ok ? "OK\n" : "FAIL\n");
return 0;
}
关键规则:
| 元素 | 语法 | 说明 |
|---|---|---|
| 参数占位 | /*__PARAM_n__*/ 5 |
合法 C 注释,后接默认值,可被替换 |
| 教程锚点 | // @tutorial-anchor: outer_loop |
源码中的显式标记,重构时跟着代码走 |
| 自验证 | expected[] + 循环比较 |
模板自身断言正确性,输出稳定 "OK\n" |
替换逻辑:同步脚本扫描 /*__PARAM_{key}__*/\s*(\S+),将匹配到的整段替换为目标值。(该规则由 scripts/sync_templates.py(已退役) 执行——2026-09-12 重构批次 R4 G1 恢复为纯后端形态,见 模板维护指南.md §4)
验证方式:模板作者随时可 clang templates/bubble_sort/source.c 编译运行,确认语法和逻辑正确。
2.2 元数据:templates/<key>/meta.yaml
纯声明式,不重复源码中已存在的信息:
key: bubble
name: 冒泡排序
category: 排序
params:
n:
label: 数组长度
type: int
default: 5
mutations:
- name: small
value: 2
- name: large
value: 10
tutorial:
steps:
- title: 外层循环
description: 控制趟数,每趟将最大元素沉底
anchor: outer_loop # 对应 source.c 中的 @tutorial-anchor 标记
- title: 内层循环
description: 相邻元素比较
anchor: inner_loop
knowledge_nodes:
- Array
- BubbleSort
- BoundaryCondition
2.3 双重验证参照
删区后现役对照(2026-10-07 注):下表为历史双验证形态(Rust e2e + 影子验证驱动,均已随 2026-10-05
native/删区退役)。现役等价物 = clang golden(Clang 实跑产.out锁定,义务链见 Agent Skill vitro-baseline-corpus-workflow)+ clang_direct 直拍门禁(go run ./scripts/clang_direct,corpus 六目录全量逐例对照,known 白名单 digest 锁定)+ e2e(corpus/四域,MoonBit cmd 侧驱动)——「双重验证」设计原则不变,载体已换。
| 维度 | Rust e2e 集成测试 | Python 影子验证 |
|---|---|---|
| 参照系 | Golden .out 文件(Clang 生成并锁定) |
Clang 实时输出 |
| 验证目标 | Vitro 行为是否符合锁定预期 | Vitro 行为是否与标准 C 一致 |
| 发现问题 | Vitro 自身 bug / Golden 过期 | 缺失特性 / 语义偏差 / 标准不符 |
| 运行方式 | cargo test --test vitro_e2e(已随删区退役) |
go run ./scripts/shadow_verify(已随删区退役;2026-09-13 D5 起 Go 驱动、Python 版更早退役) |
| 依赖 | 仅需 Vitro 自身 | 需 Clang |
| 反馈速度 | 秒级 | 分钟级 |
交叉验证场景:
| Rust e2e | Python 影子 | 结论 |
|---|---|---|
| ✅ | ✅ | 健康 |
| ❌ | ❌ | 预期值/Golden 写错,或两者都对但输出不同 |
| ✅ | ❌ | Vitro 行为自洽但与标准 C 偏差 |
| ❌ | ✅ | Golden 文件过期,需重新生成 |
| - | compile_gap | 缺失特性或编译器 bug |
三、目录结构(2026-10-05 删区后现役)
project_root/
│
├── templates/ # 算法模板(人类维护)
│ ├── bubble_sort/
│ │ ├── source.c # 合法 C,含 /*__PARAM_n__*/ 和 @tutorial-anchor
│ │ └── meta.yaml # 元数据 + 教程声明
│ └── ...
│
├── corpus/ # C 语料(原 native/tests/cases/ 等随删区迁入——单一事实来源)
│ ├── baseline/ # 已支持特性(385 例)
│ ├── knr/ · leetcode/ · gap/ # 其余三域(81 / 138 / 18 例)
│ ├── codegen_skeleton/ · cpp/ # 骨架域与 C++ 存量语料
│ ├── template_generated/ # 模板生成用例(82 个 .c,可重生成;生成器 sync_templates.py 已随删区退役)
│ └── (golden 义务链:Clang 实跑产 .out 锁定——见 Agent Skill vitro-baseline-corpus-workflow)
│
└── scripts/
└── clang_direct/ # Clang 直拍门禁(现役——吸收原影子验证防线,known_direct.json 白名单 digest 锁定)
删区注记(2026-10-05):下述路径已随
native/物理删除——native/tests/{cases,cases_golden,cases_template_generated,shadow_verification}/、scripts/shadow_verify{,_cpp}/(Go 驱动)、scripts/sync_templates.py;更早的前端切割期迁出件(CideFlutter/assets/templates/、Python 驱动三件等)见 2026-09-11 注记与 git 历史。历史形态均见 tagrust-oracle-freeze。
四、文件格式规范
4.1 算法模板:templates/<key>/source.c
#include <stdio.h>
void bubbleSort(int arr[], int n) {
// @tutorial-anchor: outer_loop
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int arr[/*__PARAM_n__*/ 5] = {5, 3, 8, 1, 2};
int expected[/*__PARAM_n__*/ 5] = {1, 2, 3, 5, 8};
int n = /*__PARAM_n__*/ 5;
bubbleSort(arr, n);
int ok = 1;
for (int i = 0; i < n; i++) if (arr[i] != expected[i]) ok = 0;
printf(ok ? "OK\n" : "FAIL\n");
return 0;
}
4.2 语法验证用例:native/tests/cases/baseline/*.c
// @category: baseline
// @features: for_loop, array_index, printf
// @expected_stdout: 0 1 2
int main() {
for (int i = 0; i < 3; i++) printf("%d ", i);
return 0;
}
| 注释标记 | 用途 | 消费者 |
|---|---|---|
// @category: |
baseline / gap / arch_diff_bug | shadow(Go 驱动)、Rust e2e |
// @features: |
特性标签(逗号分隔) | ⚠️ 暂无消费者:特性矩阵脚本未实现,现行 shadow 驱动(Go)也不解析该标记 |
// @expected_stdout: |
Rust e2e 断言预期 | Rust e2e |
4.3 Golden 输出文件:cases_golden/<name>.out
纯文本,无注释,就是 stdout 的精确字节:
ative/tests/cases_golden/bubble_sort_default.out
---
OK
生成方式:同步脚本先用 Clang 渲染并运行,stdout 写入 .out,首次需人工 Review 后提交锁定。同步脚本已由 R4 G1 恢复(2026-09-12):改模板后运行 python scripts/sync_templates.py(已退役) 即可重新生成;人工核对流程见 模板维护指南.md §4.3。
五、消费端实现
5.1 同步脚本(✅ 已由 R4 G1 恢复为纯后端形态 · 2026-09-12)
现状(2026-09-13 核对):scripts/sync_templates.py 已从历史提交恢复(重构批次 R4 G1,提交 3c767bb),只保留后端职责:渲染 templates/<key>/source.c → cases_template_generated/*.c、Clang 生成/锁定 Golden、扫描 // @tutorial-anchor: 锚点。原第 4、5 项 Flutter 职责随前端切割永久移除。scripts/test_templates.py(静态一致性校验)仍未恢复,模板目录核对依赖人工(见 模板维护指南.md §5.1)。
历史职责清单(保留供追溯):
- 渲染
templates/<key>/source.c→native/tests/cases_template_generated/<key>_<case>.c(替换/*__PARAM_x__*/,解析逻辑为正则/\*__PARAM_(\w+)__\*/\s*(\S+),未声明参数时保留源码中的默认值)——✅ 已恢复; - 调 Clang 编译运行渲染结果,生成/锁定
native/tests/cases_golden/<key>_<case>.out(仅当.out不存在时生成,避免覆盖人工 Review 过的 Golden)——✅ 已恢复; - 扫描
// @tutorial-anchor: <name>得到{name: line_number}映射——✅ 已恢复; - 汇总元数据 + 锚点生成
CideFlutter/assets/templates/index.json(历史资产,已迁出)——⛔ 永久移除; - 把
source.c/source.cpp复制到CideFlutter/assets/templates/<key>.c/.cpp(历史资产,已迁出)——⛔ 永久移除。
剩余缺口:
native/tests/cases_template_generated/的用例现已可随python scripts/sync_templates.py重新生成(改模板后运行即可);Golden 仅在.out不存在时生成,人工 Review 纪律保持;- 静态一致性校验(模板目录完整性 / params 与占位符对应)仍无自动化,是尚未排期的缺口,如实记录,不粉饰。
历史实现骨架(已删除,仅保留算法要点供恢复参照):
# ⚠️ 历史资产,已迁出:本代码块为已删除脚本的实现骨架,勿直接使用
PARAM_RE = re.compile(r'/\*__PARAM_(\w+)__\*/\s*(\S+)') # 占位符正则
def render_template(source, args):
# 用 args 覆盖占位符;args 中缺失的 key 保留源码中的默认值
...
def scan_tutorial_anchors(source):
# 扫描 // @tutorial-anchor: <name>,返回 {name: line_number}
...
def sync():
# 1) 渲染 templates/<key>/source.c → cases_template_generated/<key>_<case>.c(首行补 // @category:)
# 2) 仅当 cases_golden/<key>_<case>.out 不存在时,用 Clang 运行并写入 Golden
# 3) 汇总 meta.yaml + 锚点 → CideFlutter/assets/templates/index.json(已随前端迁出)
# 4) 复制 source.c/source.cpp → CideFlutter/assets/templates/<key>.c/.cpp(已随前端迁出)
...
5.2 Rust e2e 集成测试
// native/tests/vitro_e2e.rs
use std::fs;
use std::path::Path;
struct TestCase {
name: String,
source: String,
expected_stdout: String,
category: String,
}
fn load_cases(dir: &str) -> Vec<TestCase> {
let mut cases = vec![];
for entry in fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.extension().unwrap_or_default() != "c" {
continue;
}
let source = fs::read_to_string(&path).unwrap();
let expected = extract_comment_tag(&source, "@expected_stdout");
let category = extract_comment_tag(&source, "@category")
.unwrap_or_else(|| "baseline".into());
cases.push(TestCase {
name: path.file_stem().unwrap().to_string_lossy().into(),
source: source.clone(),
expected_stdout: expected.unwrap_or_default(),
category,
});
}
cases
}
fn load_golden_cases(cases_dir: &str, golden_dir: &str) -> Vec<TestCase> {
let mut cases = vec![];
for entry in fs::read_dir(cases_dir).unwrap() {
let path = entry.unwrap().path();
if path.extension().unwrap_or_default() != "c" {
continue;
}
let name = path.file_stem().unwrap().to_string_lossy().to_string();
let golden_path = Path::new(golden_dir).join(format!("{}.out", name));
if !golden_path.exists() {
continue; // 无 Golden 则跳过(或报错)
}
let source = fs::read_to_string(&path).unwrap();
let expected = fs::read_to_string(&golden_path).unwrap();
let category = extract_comment_tag(&source, "@category")
.unwrap_or_else(|| "baseline".into());
cases.push(TestCase { name, source, expected_stdout: expected, category });
}
cases
}
#[test]
fn e2e_baseline_cases() {
for case in load_cases("tests/cases/baseline") {
if case.category == "gap" { continue; }
let output = run_vitro(&case.source);
assert_eq!(output.stdout.trim(), case.expected_stdout.trim(),
"[{}] stdout mismatch", case.name);
}
}
#[test]
fn e2e_template_cases() {
for case in load_golden_cases(
"tests/cases_template_generated",
"tests/cases_golden"
) {
let output = run_vitro(&case.source);
assert_eq!(output.stdout.trim(), case.expected_stdout.trim(),
"[{}] golden mismatch", case.name);
}
}
5.3 Python 影子验证
shadow_verify.py 完整保留现有 analyze_diff 四分类逻辑,仅简化用例加载:
def load_case_files() -> List[ShadowCase]:
"""从 .c 文件加载用例,替代硬编码 SHADOW_CASES 列表"""
cases = []
for root in [
Path("tests/cases/baseline"),
Path("tests/cases_template_generated")
]:
if not root.exists():
continue
for path in root.glob("*.c"):
source = path.read_text(encoding="utf-8")
meta = parse_comment_tags(source)
cases.append(ShadowCase(
name=path.stem,
source=source,
category=meta.get("category", "baseline")
))
return cases
SHADOW_CASES = load_case_files()
# analyze_diff、classify_compile_error、generate_report 完全保留原有逻辑
5.4 前端加载链路(⚠️ 已随 2026-09-11 前端切割迁出 · 现状说明)
现状:CideFlutter/ 整目录、assets/templates/index.json 与 Dart 加载器(TemplateRepository / CodeTemplate / TemplateBar 等,均为历史资产,已迁出)均不在本仓库中。本仓库不再产出 index.json,也不再提供任何运行时模板加载实现。
仍有效的后端契约(社区前端接入时应遵守):
| 契约 | 形式 |
|---|---|
| 模板源位置 | templates/<key>/source.c 或 source.cpp(二选一)+ meta.yaml |
| 参数占位符 | /*__PARAM_<key>__*/ <默认值>(合法 C 注释,可被 Clang/Vitro 直接编译) |
| 教程锚点 | 源码中的 // @tutorial-anchor: <name>,其行号即高亮行号,无需额外维护 |
| 元数据 | meta.yaml 的 key / name / category / params / tutorial.steps / knowledge_nodes |
| 生成用例(可作对照素材) | native/tests/cases_template_generated/<key>_default.c 与 native/tests/cases_golden/<key>_default.out |
历史实现(已迁出,供参照):原 Dart 侧从 assets/templates/index.json 读取模板清单,再按需 rootBundle.loadString('assets/templates/<key>.c') 加载源码;ext 字段决定加载 .c 还是 .cpp。该实现见标签 before-frontend-split。
六、特性矩阵生成(可选增值 · 未实现)
现状核实(2026-09-11):
scripts/feature_matrix.py不存在,shadow_verify.py也不解析// @features:注释(ShadowCase无features字段)。本节为规划草案,下例为伪代码。
从 // @features: 注释自动生成特性支持矩阵:
# scripts/feature_matrix.py(规划中,文件尚不存在)
from collections import defaultdict
features = defaultdict(lambda: {"cases": 0, "match": 0})
for case in load_case_files():
for feat in case.features:
features[feat]["cases"] += 1
if case.diff_type == "match":
features[feat]["match"] += 1
# 输出:for_loop: 15/15 (100%), array_index: 12/12 (100%), ...
教学价值:直接回答"Vitro 支持哪些 C 语法特性"。
七、迁移路径
Phase 1:文件化影子用例 ✅
- ✅ 创建
native/tests/cases/baseline/ - ✅ 将
shadow_verify.py中 baseline 用例逐个提取为.c文件 - ✅ 每个
.c文件添加// @category和// @expected_stdout注释 - ✅
shadow_verify.py改为load_case_files()扫描目录 - ✅ 验证:跑一次
python shadow_verify.py,match 率不下降
Phase 2:模板文件化 + sync 脚本 ✅(脚本已于 2026-09-11 随前端切割删除)
- ✅ 创建
templates/<key>/source.c(合法 C +/*__PARAM__*/)+meta.yaml - ✅ 实现
scripts/sync_templates.py(历史资产,已迁出):- 渲染
/*__PARAM__*/→cases_template_generated/*.c - Clang 运行生成
cases_golden/*.out - 生成
CideFlutter/assets/templates/index.json(历史资产,已迁出)
- 渲染
- ✅ 验证 Rust e2e 和 Python 影子都能加载新生成的模板用例
现状(2026-09-13 更新):该脚本已由 R4 G1 恢复(2026-09-12);
cases_template_generated/的 82 个.c可随python scripts/sync_templates.py重新生成,后端自动闭环恢复(静态一致性校验仍缺,见 §5.1)。
Phase 3:Rust e2e 集成测试 ✅
- ✅ 新建
native/tests/vitro_e2e.rs - ✅ 实现
load_cases()扫描cases/baseline/ - ✅ 实现
load_golden_cases()扫描cases_template_generated/+cases_golden/ - ✅
cargo test --test vitro_e2e跑通全部 baseline + 模板用例
Phase 4:Flutter 运行时加载 + 删除旧框架 ✅(前端链路已随 2026-09-11 切割整体迁出)
- ✅
pubspec.yaml注册assets/templates/ - ✅
TemplateBar/TemplateParamDialog/TemplateTutorialPanel改为消费 JSON Index +.c文件 - ✅ 删除
CideFlutter/lib/models/templates/*.dart硬编码模板文件(11 个 Dart 文件已移除;历史资产,已迁出) - ✅
template_registry.dart清理:移除allTemplatesfallback 及全部旧 import(历史资产,已迁出) - ✅
code_template.dart移除旧语法{{key:defaultValue}}支持,仅保留/*__PARAM__*/(历史资产,已迁出) - ✅
@tutorial-anchor扫描驱动高亮
现状(2026-09-11 补记,不改动上述历史条目):
CideFlutter/整目录(含assets/templates/与index.json)已移出本仓库,上述 1、2、4、5、6 项作为历史实现保留在标签before-frontend-split。后端仍然有效的只是数据约定:模板源为合法 C/C++、占位符为/*__PARAM_x__*/、锚点为// @tutorial-anchor: <name>。运行时加载与渲染属社区前端职责。
八、与现有系统兼容性
| 现有系统 | 影响 | 措施 |
|---|---|---|
shadow_verify.py 报告格式 |
无 | analyze_diff / generate_report 逻辑不变 |
SHADOW_CASES 硬编码列表 |
删除 | 改为 load_case_files() 扫描 .c |
| Rust 单元测试(分层) | 无 | lexer/tests.rs / parser/tests.rs 等保持原样 |
CodeTemplate Dart API |
已迁出 | 随 CideFlutter/ 于 2026-09-11 移出仓库(历史资产,已迁出) |
template_registry.dart |
已迁出 | 同上(历史资产,已迁出) |
| 教程高亮 | 契约保留、实现移出 | 契约从"行号绑定"升级为 @tutorial-anchor 源码标记(后端数据约定仍有效,渲染由社区前端实现) |
templates/ 源目录 |
闭环已恢复 | 由 scripts/sync_templates.py(R4 G1)消费;静态一致性校验仍缺(见 §5.1) |
九、风险与回退
| 风险 | 缓解措施 |
|---|---|
/*__PARAM_n__*/ 默认值与替换值类型不匹配 |
由恢复后的 scripts/sync_templates.py(已退役) 在渲染后先调 Clang 编译、失败则阻断(R4 G1 恢复版,2026-09-12);人工流程(模板维护指南.md §4)保留为第二道 |
Golden .out 首次生成错误 |
必须人工 Review 后提交锁定;CI 对 .out 变更保持敏感 |
@tutorial-anchor 注释遗漏 |
恢复版脚本扫描锚点;"meta.yaml 的 anchor 必须存在于 source.c"的静态一致性校验仍缺(缺口保留) |
| 模板源与生成用例漂移 | templates/ 已有消费者(sync_templates.py → 生成用例 → shadow/e2e 防线);meta↔source 静态一致性自动校验仍缺(见 §5.1,缺口保留) |
| 已随前端迁出,属社区前端自评范围(历史资产,已迁出) | |
| Windows 无 Clang | shadow 驱动(Go)含并发 Defender 锁重试判据;Rust e2e 为必过项 |
十、总结
算法模板是合法 C 代码(
/*__PARAM__*/注释占位),作者可直接用 Clang 验证;模板自身断言正确性(expected[]自验证),输出稳定"OK\n",Golden 文件由 Clang 生成并锁定;同一份.c文件,Rust e2e 以 Golden 为参照验证 Vitro 自洽性,Python 影子以 Clang 实时输出为参照验证标准一致性——双重独立,交叉验证,Python 不降级。2026-09-11 补记(切割后现状):上述设计原则全部保留且不因前端切割改变——模板即合法 C、Golden 只能来自 Clang、双重参照交叉验证仍是后端防线的基础。发生变化的只有"谁来消费模板":Dart 前端迁出、模板的运行时展示移交社区前端。
templates/→cases_template_generated/→ Shadow/E2E 的后端生成链路曾一度失去生成器脚本(2026-09-11),已由 R4 G1 于 2026-09-12 恢复(详见 §5.1)。