#!/usr/bin/env bash # ============================================================ # score.sh — 按比赛计分公式计算本地分数 # # 公式(与比赛平台一致): # 性能分 = geometric_mean(参考时间 / 我们时间) × 100 # 正确分 = 通过用例数 / 总用例数 × 100 # 总分 = 正确分 × 0.5 + 性能分 × 0.5 # ============================================================ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" COMPILER="${PROJECT_DIR}/build/bin/compiler" TEST_DIR="${PROJECT_DIR}/2026test" RESULT_DIR="${PROJECT_DIR}/score_results" SYLIB="${PROJECT_DIR}/sylib/sylib.c" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' # 规范比较:忽略尾部空格和尾部空行(与 2026test.sh 的 canon_compare 一致) canon_diff() { local f1="$1" f2="$2" diff -q \ <(sed 's/\r$//; s/[[:space:]]*$//' "$f1" \ | awk '{lines[NR]=$0} END{last=NR; while(last>0&&lines[last]=="")last--; for(i=1;i<=last;i++)print lines[i]}') \ <(sed 's/\r$//; s/[[:space:]]*$//' "$f2" \ | awk '{lines[NR]=$0} END{last=NR; while(last>0&&lines[last]=="")last--; for(i=1;i<=last;i++)print lines[i]}') \ >/dev/null 2>&1 } GCC="aarch64-linux-gnu-gcc" GCC_COMPILE_FLAGS="-std=gnu89 -O2 -x c" GCC_LINK_FLAGS="-static" QEMU="qemu-aarch64" QEMU_FLAGS="-L /usr/aarch64-linux-gnu -s 209715200" COMPILER_FLAGS="-O" TIMEOUT_SEC=300 CATEGORY="all" JOBS="$(nproc)" QUIET=false usage() { cat </dev/null; then echo -e "${RED}错误: 参考编译器未找到: $GCC${NC}" exit 1 fi mkdir -p "$RESULT_DIR" # 收集测试用例 declare -a CATEGORIES if [[ "$CATEGORY" == "all" ]]; then CATEGORIES=("functional" "h_functional" "performance") else CATEGORIES=("$CATEGORY") fi declare -a TEST_FILES for cat in "${CATEGORIES[@]}"; do d="$TEST_DIR/$cat" if [[ -d "$d" ]]; then while IFS= read -r -d '' f; do TEST_FILES+=("$f") done < <(find "$d" -maxdepth 1 -name '*.sy' -print0 | sort -z) fi done TOTAL_TESTS="${#TEST_FILES[@]}" if [[ "$TOTAL_TESTS" -eq 0 ]]; then echo -e "${RED}错误: 未找到测试用例${NC}" exit 1 fi if ! $QUIET; then echo -e "${BLUE}========================================================${NC}" echo -e "${BLUE} 本地评分 (比赛计分公式)${NC}" echo -e "${BLUE}========================================================${NC}" echo -e "测试类别: ${CATEGORY}" echo -e "用例数: ${TOTAL_TESTS}" echo -e "参考编译器: ${GCC} ${GCC_COMPILE_FLAGS}" echo -e "超时: ${TIMEOUT_SEC}s 并行: ${JOBS}" echo -e "${BLUE}========================================================${NC}" echo "" fi # ============================================================ # 单个测试执行(通过临时脚本,避免函数导出兼容性问题) # ============================================================ run_one() { local sy_file="$1" local idx="$2" local base_name cat_name work_dir in_file out_file base_name="$(basename "${sy_file%.sy}")" cat_name="$(basename "$(dirname "$sy_file")")" work_dir="$RESULT_DIR/$cat_name/$base_name" mkdir -p "$work_dir" in_file="${sy_file%.sy}.in" out_file="${sy_file%.sy}.out" # ---- 我们的编译器 ---- local our_asm="$work_dir/our.s" our_exe="$work_dir/our.exe" our_out="$work_dir/our.out" local our_time=1 our_correct=0 our_compile_ok=0 if timeout 30 "$COMPILER" $COMPILER_FLAGS -S -o "$our_asm" "$sy_file" 2>/dev/null; then if "$GCC" $GCC_LINK_FLAGS "$our_asm" "$SYLIB" -o "$our_exe" -lm 2>/dev/null; then our_compile_ok=1 local start_ns end_ns exit_code=0 start_ns="$(date +%s%3N)" if [[ -f "$in_file" ]]; then timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" < "$in_file" > "$our_out" 2>/dev/null || exit_code=$? else timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" > "$our_out" 2>/dev/null || exit_code=$? fi end_ns="$(date +%s%3N)" our_time=$((end_ns - start_ns)) if [[ $our_time -lt 1 ]]; then our_time=1; fi if [[ $exit_code -eq 0 ]]; then if [[ -f "$out_file" ]]; then if diff -q "$our_out" "$out_file" >/dev/null 2>&1; then our_correct=1 fi else our_correct=1 fi fi fi fi # ---- 参考编译器 (gcc -O2) ---- local ref_asm="$work_dir/ref.s" ref_exe="$work_dir/ref.exe" ref_out="$work_dir/ref.out" local ref_time=999999 ref_ok=0 if "$GCC" $GCC_COMPILE_FLAGS -S -o "$ref_asm" "$sy_file" 2>/dev/null; then if "$GCC" $GCC_LINK_FLAGS "$ref_asm" "$SYLIB" -o "$ref_exe" -lm 2>/dev/null; then local ref_start ref_end ref_exit=0 ref_start="$(date +%s%3N)" if [[ -f "$in_file" ]]; then timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" < "$in_file" > "$ref_out" 2>/dev/null || ref_exit=$? else timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" > "$ref_out" 2>/dev/null || ref_exit=$? fi ref_end="$(date +%s%3N)" ref_time=$((ref_end - ref_start)) ref_ok=1 fi fi if [[ $ref_time -lt 1 ]]; then ref_time=1; fi # 写结果文件 echo "$our_correct $our_time $ref_time $ref_ok $our_compile_ok" > "$work_dir/result.txt" # 打印 if [[ $our_correct -eq 1 ]]; then local ratio ratio=$(awk "BEGIN { printf \"%.4f\", $ref_time / $our_time }" 2>/dev/null || echo "0") printf "[${GREEN}AC${NC}] %-45s our=%6dms ref=%6dms ratio=%s\n" \ "$cat_name/$base_name" "$our_time" "$ref_time" "$ratio" elif [[ $our_compile_ok -eq 0 ]]; then printf "[${RED}CE${NC}] %-45s (编译失败)\n" "$cat_name/$base_name" else printf "[${RED}WA${NC}] %-45s our=%6dms ref=%6dms (输出不匹配)\n" \ "$cat_name/$base_name" "$our_time" "$ref_time" fi } # ============================================================ # 并行执行 # ============================================================ if ! $QUIET; then echo -e "${BLUE}正在运行 ${TOTAL_TESTS} 个测试 (${JOBS} 并行)...${NC}" echo "" fi # 并行执行:使用后台任务 + wait 控制并发 run_with_args() { local sy_file="$1" local base_name cat_name work_dir in_file out_file base_name="$(basename "${sy_file%.sy}")" cat_name="$(basename "$(dirname "$sy_file")")" work_dir="$RESULT_DIR/$cat_name/$base_name" mkdir -p "$work_dir" in_file="${sy_file%.sy}.in" out_file="${sy_file%.sy}.out" local our_asm="$work_dir/our.s" our_exe="$work_dir/our.exe" our_out="$work_dir/our.out" local our_time=1 our_correct=0 our_compile_ok=0 if timeout 30 "$COMPILER" $COMPILER_FLAGS -S -o "$our_asm" "$sy_file" 2>/dev/null; then if "$GCC" $GCC_LINK_FLAGS "$our_asm" "$SYLIB" -o "$our_exe" -lm 2>/dev/null; then our_compile_ok=1 local start_ns end_ns exit_code=0 start_ns="$(date +%s%3N)" if [[ -f "$in_file" ]]; then timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" < "$in_file" > "$our_out" 2>/dev/null || exit_code=$? else timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" > "$our_out" 2>/dev/null || exit_code=$? fi end_ns="$(date +%s%3N)" our_time=$((end_ns - start_ns)) [[ $our_time -lt 1 ]] && our_time=1 # SysY 程序可返回任意值,不能用 exit_code==0 判断正确性 # .out 文件格式(与 2026test.sh 一致): stdout + [换行] + 退出码 local our_actual="$work_dir/our.actual.out" { cat "$our_out" if [[ -s "$our_out" ]] && (( $(tail -c 1 "$our_out" | wc -l) == 0 )); then printf '\n' fi echo "$exit_code" } > "$our_actual" if [[ -f "$out_file" ]]; then canon_diff "$our_actual" "$out_file" && our_correct=1 else our_correct=1 fi fi fi local ref_asm="$work_dir/ref.s" ref_exe="$work_dir/ref.exe" ref_out="$work_dir/ref.out" local ref_time=999999 if "$GCC" $GCC_COMPILE_FLAGS -S -o "$ref_asm" "$sy_file" 2>/dev/null; then if "$GCC" $GCC_LINK_FLAGS "$ref_asm" "$SYLIB" -o "$ref_exe" -lm 2>/dev/null; then local ref_start ref_end ref_exit=0 ref_start="$(date +%s%3N)" if [[ -f "$in_file" ]]; then timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" < "$in_file" > "$ref_out" 2>/dev/null || ref_exit=$? else timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" > "$ref_out" 2>/dev/null || ref_exit=$? fi ref_end="$(date +%s%3N)" ref_time=$((ref_end - ref_start)) fi fi [[ $ref_time -lt 1 ]] && ref_time=1 echo "$our_correct $our_time $ref_time $our_compile_ok" > "$work_dir/result.txt" if [[ $our_correct -eq 1 ]]; then local ratio ratio=$(awk "BEGIN { printf \"%.4f\", $ref_time / $our_time }" 2>/dev/null || echo "0") printf "[${GREEN}AC${NC}] %-45s our=%6dms ref=%6dms ratio=%s\n" \ "$cat_name/$base_name" "$our_time" "$ref_time" "$ratio" elif [[ $our_compile_ok -eq 0 ]]; then printf "[${RED}CE${NC}] %-45s (编译失败)\n" "$cat_name/$base_name" else printf "[${RED}WA${NC}] %-45s our=%6dms ref=%6dms (输出不匹配)\n" \ "$cat_name/$base_name" "$our_time" "$ref_time" fi } # 简单后台并行 running=0 for f in "${TEST_FILES[@]}"; do run_with_args "$f" & running=$((running + 1)) if [[ $running -ge $JOBS ]]; then wait -n 2>/dev/null || true running=$((running - 1)) fi done wait echo "" # ============================================================ # 收集结果 # ============================================================ passed=0 declare -a ratios for cat in "${CATEGORIES[@]}"; do for sy_file in "$TEST_DIR/$cat"/*.sy; do [[ -f "$sy_file" ]] || continue base_name="$(basename "${sy_file%.sy}")" work_dir="$RESULT_DIR/$cat/$base_name" result_file="$work_dir/result.txt" if [[ -f "$result_file" ]]; then read -r test_correct our_time ref_time ref_ok compile_ok < "$result_file" || true if [[ "$test_correct" == "1" ]]; then passed=$((passed + 1)) if [[ $ref_time -gt 0 ]] && [[ $our_time -gt 0 ]]; then ratio=$(echo "scale=10; $ref_time / $our_time" | bc 2>/dev/null || echo "1.0") ratios+=("$ratio") fi fi fi done done # 计算几何平均 GEOM_MEAN="0.0" if [[ ${#ratios[@]} -gt 0 ]]; then product="1.0" for r in "${ratios[@]}"; do product=$(echo "scale=20; $product * $r" | bc 2>/dev/null) done GEOM_MEAN=$(echo "scale=10; e(l($product) / ${#ratios[@]})" | bc -l 2>/dev/null || echo "1.0") fi PERF_SCORE=$(echo "scale=4; $GEOM_MEAN * 100" | bc -l 2>/dev/null) if (( $(echo "$PERF_SCORE > 100" | bc -l 2>/dev/null) )); then PERF_SCORE="100.0000" fi CORRECT_SCORE=$(echo "scale=4; $passed / $TOTAL_TESTS * 100" | bc -l 2>/dev/null) TOTAL_SCORE=$(echo "scale=4; $CORRECT_SCORE * 0.5 + $PERF_SCORE * 0.5" | bc -l 2>/dev/null) echo -e "${BLUE}========================================================${NC}" echo -e "${BLUE} 评分结果${NC}" echo -e "${BLUE}========================================================${NC}" echo "" printf " %-20s ${GREEN}%d / %d${NC}\n" "正确用例:" "$passed" "$TOTAL_TESTS" echo "" printf " ${BOLD}%-20s %8s${NC}\n" "指标" "分数" printf " %-20s %8s\n" "--------------------" "--------" printf " %-20s ${GREEN}%8.4f${NC}\n" "正确分:" "$CORRECT_SCORE" printf " %-20s ${YELLOW}%8.4f${NC}\n" "性能分:" "$PERF_SCORE" printf " %-20s ${BLUE}${BOLD}%8.4f${NC}\n" "总分:" "$TOTAL_SCORE" echo "" printf " 几何平均 (ref/our): %.6f\n" "$GEOM_MEAN" printf " 有效用例数: %d\n" "${#ratios[@]}" echo "" # 保存 { echo "正确分: $CORRECT_SCORE" echo "性能分: $PERF_SCORE" echo "总分: $TOTAL_SCORE" echo "几何平均: $GEOM_MEAN" echo "通过: $passed / $TOTAL_TESTS" } > "$RESULT_DIR/score.txt" echo -e "${BLUE}结果已保存到: ${RESULT_DIR}/score.txt${NC}"