Files
jp-editor/scripts/run-m1-corpus.mjs
T
2026-09-22 16:33:35 +08:00

95 lines
4.9 KiB
JavaScript

import { mkdir, readFile, writeFile } from "node:fs/promises";
import { parseJpwabc, hasExactRoundTrip } from "../dist/domain/jpwabc.js";
const manifest = JSON.parse(await readFile("fixtures/manifest.json", "utf8"));
const rows = [];
const failures = [];
for (const fixture of manifest.samples) {
try {
const bytes = await readFile(fixture.rawPath);
const result = parseJpwabc(bytes);
const exactRoundTrip = hasExactRoundTrip(result);
const errors = result.diagnostics.filter((item) => item.severity === "error");
const warnings = result.diagnostics.filter((item) => item.severity === "warning");
const status = errors.length ? "recovered-error" : warnings.length || result.unknownTokens.length || result.unknownBlocks.length ? "parsed-with-diagnostics" : "parsed-clean";
const row = {
id: fixture.id,
rawPath: fixture.rawPath,
decodedPath: fixture.decodedPath,
rawSha256: fixture.rawSha256,
rawBytes: bytes.byteLength,
status,
sourcePreserved: exactRoundTrip,
encoding: result.source.encoding,
bom: result.source.bom,
formatVersion: result.header.formatVersion,
productVersion: result.header.productVersion,
sections: result.sections.map((section) => ({ name: section.name, known: section.known, lineCount: section.lines.length })),
semanticCounts: {
options: result.options.document.length,
unknownOptions: result.options.unknownDocument.length,
fonts: result.fonts.length,
titles: result.titles.length,
voiceLines: result.voices.length,
voiceTokens: result.voices.reduce((count, line) => count + line.tokens.length, 0),
wordBlocks: result.words.length,
attachments: result.attachments.length,
pages: result.pages.length
},
preservationCounts: {
rawTokens: result.rawTokens.length,
unknownTokens: result.unknownTokens.length,
unknownBlocks: result.unknownBlocks.length
},
diagnostics: result.diagnostics.map(({ code, severity, message, recovery }) => ({ code, severity, message, recovery })),
compatibility: result.compatibility
};
rows.push(row);
if (!exactRoundTrip) failures.push(`${fixture.id}: source bytes changed on no-edit serialization`);
} catch (error) {
failures.push(`${fixture.id}: parser threw ${error instanceof Error ? error.message : String(error)}`);
rows.push({ id: fixture.id, rawPath: fixture.rawPath, status: "parser-threw", sourcePreserved: false, error: String(error) });
}
}
if (rows.length !== manifest.sampleCount || rows.length !== 33) failures.push(`expected 33 classifications, got ${rows.length}`);
if (rows.some((row) => !row.status || row.sourcePreserved !== true)) failures.push("one or more samples has no status or failed source preservation");
const report = {
reportVersion: 1,
milestone: "M1",
purpose: "33-sample parse, preservation and diagnostic classification; not an external JPW7 compatibility pass",
sampleCountExpected: 33,
sampleCountActual: rows.length,
statusCounts: Object.fromEntries([...new Set(rows.map((row) => row.status))].sort().map((status) => [status, rows.filter((row) => row.status === status).length])),
allSourceBytesPreserved: failures.length === 0,
failures,
samples: rows
};
await mkdir("acceptance/m1", { recursive: true });
await writeFile("acceptance/m1/sample-classification.json", `${JSON.stringify(report, null, 2)}\n`, "utf8");
const markdown = [
"# M1 33 样例分类报告",
"",
"> 分类、诊断和保留结果是 Web parser 的实测结果,不代表 JPW7 原程序兼容通过;外部回验仍由 R0/R1 单独执行。",
"",
`- 样例:${report.sampleCountActual}/${report.sampleCountExpected}`,
`- 无编辑字节级保留:${report.allSourceBytesPreserved ? "是" : "否"}`,
`- 状态:${JSON.stringify(report.statusCounts)}`,
`- 失败:${report.failures.length}`,
"",
"| 样例 | 状态 | 编码/BOM | 区块 | 未知 token | 未知区块 | 诊断 |",
"|---|---|---|---:|---:|---:|---:|",
...rows.map((row) => `| ${row.id} | ${row.status} | ${row.encoding}/${row.bom} | ${row.sections?.length ?? 0} | ${row.preservationCounts?.unknownTokens ?? "-"} | ${row.preservationCounts?.unknownBlocks ?? "-"} | ${row.diagnostics?.length ?? "-"} |`),
"",
"## 解释边界",
"",
"- `parsed-with-diagnostics` 表示语义/未知内容被保留并有报告,不是全量语法支持声明。",
"- `recovered-error` 表示解析器发现错误但保留了原始字节;安全兼容导出必须阻止。",
"- `sourcePreserved` 仅证明无编辑序列化稳定,不替代 JPW7 原程序回验。",
""
].join("\n");
await writeFile("acceptance/m1/sample-classification.md", markdown, "utf8");
console.log(JSON.stringify({ ok: failures.length === 0, samples: rows.length, statusCounts: report.statusCounts, failures: failures.length }, null, 2));
if (failures.length) process.exitCode = 1;