diff --git a/solution/Lab1-修改记录.md b/solution/Lab1-修改记录.md index 525b2a0..54faa0d 100644 --- a/solution/Lab1-修改记录.md +++ b/solution/Lab1-修改记录.md @@ -16,6 +16,9 @@ - `solution/Lab1-修改记录.md` - `solution/RUN.md` - `solution/run_lab1_batch.sh` +- `test/test_case/negative/missing_semicolon.sy` +- `test/test_case/negative/missing_rparen.sy` +- `test/test_case/negative/unexpected_else.sy` ## 2. 文法扩展 @@ -116,9 +119,31 @@ cmake --build build -j 4 补充更新了 `solution/run_lab1_batch.sh`: +- 默认使用 `COMPILER_PARSE_ONLY=ON` 进行 Lab1 构建 - 新增可选参数 `--save-tree` - 启用后,会在仓库根目录下创建 `test_tree/` - 并按照 `functional/`、`performance/` 的目录结构保存每个样例对应的语法树 +- 增加正例总数、通过数、失败数统计 +- 增加反例总数、通过数、失败数统计 +- 增加失败样例列表打印,便于直接汇报“覆盖样例数 + 通过率” + +## 9. 反例测试补充 + +新增目录: + +- `test/test_case/negative` + +新增负例样例: + +- `missing_semicolon.sy` +- `missing_rparen.sy` +- `unexpected_else.sy` + +这些反例用于证明: + +- 合法程序可以成功解析 +- 非法程序会触发 `parse` 错误 +- 报错信息包含位置信息,便于定位问题 同时同步更新了: @@ -126,7 +151,7 @@ cmake --build build -j 4 - `solution/Lab1-设计方案.md` - `solution/Lab1-修改记录.md` -## 9. 已知边界 +## 10. 已知边界 当前提交完成的是 Lab1 所需的“语法分析与语法树构建”。以下能力仍属于后续实验范围: diff --git a/solution/Lab1-设计方案.md b/solution/Lab1-设计方案.md index 9d9f54f..4bd8c81 100644 --- a/solution/Lab1-设计方案.md +++ b/solution/Lab1-设计方案.md @@ -160,25 +160,30 @@ 1. `main.cpp` 在“只输出语法树”时提前返回。 2. 同时把 `sema/irgen` 的接口适配到新文法,使最小子集仍可编译通过。 +3. `solution/run_lab1_batch.sh` 默认使用 `COMPILER_PARSE_ONLY=ON` 配置 CMake,确保批量验证只依赖前端解析与语法树打印。 这样既满足 Lab1,又不破坏当前工程的构建链路。 ## 6. 验证方案 -验证分两步: +验证分三步: 1. 使用代表性样例检查语法树结构。 2. 批量遍历 `test/test_case/functional/*.sy` 与 `test/test_case/performance/*.sy`,执行 `./build/bin/compiler --emit-parse-tree`。 +3. 增加 `test/test_case/negative/*.sy` 反例,验证非法输入会触发 `parse` 错误。 另外补充一个批量自动化脚本 `solution/run_lab1_batch.sh`,用于统一执行: - ANTLR 文件重新生成 -- CMake 配置与编译 -- 所有 `.sy` 用例的语法树回归 +- `parse-only` 模式下的 CMake 配置与编译 +- 所有正例 `.sy` 用例的语法树回归 +- 所有反例 `.sy` 用例的错误回归 - 在需要时通过 `--save-tree` 将语法树保存到 `test_tree/` +- 输出正例/反例/总体统计信息与失败列表 验证目标是: - 文法能接受测试目录中的 SysY 程序 - 语法树可稳定输出 +- 非法输入能稳定报出 `parse` 错误 - 工程可以重新生成 ANTLR 文件并成功编译 diff --git a/solution/RUN.md b/solution/RUN.md index fea7a5e..aafd558 100644 --- a/solution/RUN.md +++ b/solution/RUN.md @@ -68,6 +68,14 @@ cmake --build build -j "$(nproc)" ./solution/run_lab1_batch.sh ``` +该脚本默认使用 **parse-only 构建模式**: + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=ON +``` + +这样即使 `sem` / `irgen` / `mir` 还没有完成,Lab1 的语法树验证也不会被后续实验模块阻塞。 + 如果希望在批量测试时把每个样例的语法树保存到 `test_tree/` 目录,可以加可选项: ```bash @@ -77,38 +85,66 @@ cmake --build build -j "$(nproc)" 该脚本会自动完成: 1. 重新生成 `build/generated/antlr4` 下的 ANTLR 文件 -2. 执行 `cmake -S . -B build -DCMAKE_BUILD_TYPE=Release` +2. 执行 `cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=ON` 3. 执行 `cmake --build build -j "$(nproc)"` 4. 批量测试 `test/test_case/functional/*.sy` 5. 批量测试 `test/test_case/performance/*.sy` +6. 批量测试 `test/test_case/negative/*.sy`,确认非法输入会触发 `parse` 报错 若使用 `--save-tree`,还会额外: -6. 在仓库根目录下创建 `test_tree/` -7. 将语法树按测试集目录结构保存,例如: +7. 在仓库根目录下创建 `test_tree/` +8. 将语法树按测试集目录结构保存,例如: ```bash test_tree/functional/simple_add.tree test_tree/performance/fft0.tree ``` +脚本结束时会输出: + +- 正例总数 / 通过数 / 失败数 +- 反例总数 / 通过数 / 失败数 +- 总覆盖样例数与整体通过情况 +- 失败样例列表 + 若某个用例失败,脚本会打印失败用例名并返回非零退出码。 -## 6. 常用附加命令 +## 6. 反例测试说明 + +新增了负例目录: + +```bash +test/test_case/negative +``` + +当前提供了 3 个非法样例: + +- `missing_semicolon.sy` +- `missing_rparen.sy` +- `unexpected_else.sy` + +这些样例用于验证: + +- 合法输入能够成功输出语法树 +- 非法输入能够触发 `parse` 报错 +- 报错信息带有位置,便于定位问题 + +## 7. 常用附加命令 -### 6.1 查看帮助 +### 7.1 查看帮助 ```bash ./build/bin/compiler --help ``` -### 6.2 指定单个样例文件 +### 7.2 指定单个样例文件 ```bash ./build/bin/compiler --emit-parse-tree ``` -### 6.3 重新从零开始构建 +### 7.3 重新从零开始构建 ```bash rm -rf build @@ -123,12 +159,13 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j "$(nproc)" ``` -## 7. 结果判定 +## 8. 结果判定 Lab1 主要检查点是: - 合法 SysY 程序可以被 `SysY.g4` 成功解析 - `--emit-parse-tree` 能输出语法树 -- `test/test_case` 下样例可以批量通过语法树模式 +- `test/test_case` 下正例可以批量通过语法树模式 +- `test/test_case/negative` 下反例会稳定触发 `parse` 报错 本项目当前实现中,Lab1 的重点是“语法分析与语法树构建”,不是完整语义分析和完整 IR/汇编支持。 diff --git a/solution/run_lab1_batch.sh b/solution/run_lab1_batch.sh index 3a70875..e82602b 100755 --- a/solution/run_lab1_batch.sh +++ b/solution/run_lab1_batch.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -euo pipefail +shopt -s nullglob ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" BUILD_DIR="$ROOT_DIR/build" @@ -10,6 +11,38 @@ GRAMMAR_PATH="$ROOT_DIR/src/antlr4/SysY.g4" COMPILER="$BUILD_DIR/bin/compiler" SAVE_TREE=false TREE_DIR="$ROOT_DIR/test_tree" +POSITIVE_CASES=( + "$ROOT_DIR"/test/test_case/functional/*.sy + "$ROOT_DIR"/test/test_case/performance/*.sy +) +NEGATIVE_CASES=( + "$ROOT_DIR"/test/test_case/negative/*.sy +) +positive_total=0 +positive_passed=0 +positive_failed=0 +negative_total=0 +negative_passed=0 +negative_failed=0 +failed_cases=() + +print_summary() { + local total passed failed + total=$((positive_total + negative_total)) + passed=$((positive_passed + negative_passed)) + failed=$((positive_failed + negative_failed)) + + echo + echo "Summary:" + echo " Positive cases: total=$positive_total, passed=$positive_passed, failed=$positive_failed" + echo " Negative cases: total=$negative_total, passed=$negative_passed, failed=$negative_failed" + echo " Overall: total=$total, passed=$passed, failed=$failed" + + if (( ${#failed_cases[@]} > 0 )); then + echo "Failed cases:" + printf ' - %s\n' "${failed_cases[@]}" + fi +} while [[ $# -gt 0 ]]; do case "$1" in @@ -35,20 +68,20 @@ java -jar "$JAR_PATH" \ "$GRAMMAR_PATH" echo "[2/4] Configuring CMake..." -cmake -S "$ROOT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release +cmake -S "$ROOT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=ON echo "[3/4] Building project..." cmake --build "$BUILD_DIR" -j "$(nproc)" -echo "[4/4] Running parse-tree tests..." -failed=0 +echo "[4/4] Running parse-tree tests in parse-only mode..." if [[ "$SAVE_TREE" == true ]]; then rm -rf "$TREE_DIR" mkdir -p "$TREE_DIR" fi -for case_file in "$ROOT_DIR"/test/test_case/functional/*.sy "$ROOT_DIR"/test/test_case/performance/*.sy; do +for case_file in "${POSITIVE_CASES[@]}"; do + ((positive_total += 1)) if [[ "$SAVE_TREE" == true ]]; then rel_path="${case_file#"$ROOT_DIR"/test/test_case/}" rel_dir="$(dirname "$rel_path")" @@ -60,22 +93,51 @@ for case_file in "$ROOT_DIR"/test/test_case/functional/*.sy "$ROOT_DIR"/test/tes echo "FAIL: $case_file" cat /tmp/lab1_parse.err rm -f "$out_file" - failed=1 + ((positive_failed += 1)) + failed_cases+=("$case_file") else echo "PASS: $case_file -> $out_file" + ((positive_passed += 1)) fi else if ! "$COMPILER" --emit-parse-tree "$case_file" >/dev/null 2>/tmp/lab1_parse.err; then echo "FAIL: $case_file" cat /tmp/lab1_parse.err - failed=1 + ((positive_failed += 1)) + failed_cases+=("$case_file") else echo "PASS: $case_file" + ((positive_passed += 1)) fi fi done -if [[ "$failed" -ne 0 ]]; then +if (( ${#NEGATIVE_CASES[@]} > 0 )); then + echo + echo "Running negative parse tests..." + for case_file in "${NEGATIVE_CASES[@]}"; do + ((negative_total += 1)) + if "$COMPILER" --emit-parse-tree "$case_file" >/tmp/lab1_negative.out 2>/tmp/lab1_negative.err; then + echo "FAIL: $case_file (expected parse failure, but parsing succeeded)" + ((negative_failed += 1)) + failed_cases+=("$case_file") + else + if grep -q '^\[error\] \[parse\]' /tmp/lab1_negative.err; then + echo "PASS: $case_file -> expected parse error" + ((negative_passed += 1)) + else + echo "FAIL: $case_file (did not report parse error as expected)" + cat /tmp/lab1_negative.err + ((negative_failed += 1)) + failed_cases+=("$case_file") + fi + fi + done +fi + +print_summary + +if (( positive_failed + negative_failed > 0 )); then echo "Batch test finished with failures." exit 1 fi diff --git a/test/test_case/negative/missing_rparen.sy b/test/test_case/negative/missing_rparen.sy new file mode 100644 index 0000000..58d6cc2 --- /dev/null +++ b/test/test_case/negative/missing_rparen.sy @@ -0,0 +1,3 @@ +int main( { + return 0; +} diff --git a/test/test_case/negative/missing_semicolon.sy b/test/test_case/negative/missing_semicolon.sy new file mode 100644 index 0000000..c6b39d3 --- /dev/null +++ b/test/test_case/negative/missing_semicolon.sy @@ -0,0 +1,4 @@ +int main() { + int a = 1 + return a; +} diff --git a/test/test_case/negative/unexpected_else.sy b/test/test_case/negative/unexpected_else.sy new file mode 100644 index 0000000..f14a659 --- /dev/null +++ b/test/test_case/negative/unexpected_else.sy @@ -0,0 +1,3 @@ +int main() { + else return 0; +}