中职学生看不懂英文报错。原来提交前用浏览器里的 Skulpt 拦语法错误,只弹一句 「第 N 行存在语法错误」——Skulpt 九成报的是 bad input / bad token,没有列号。 - 提交前的语法检查挪到服务端:/code/format 先用 CPython 编译一遍(只编译不执行), api 镜像加 python3-minimal(3.13,与判题机同版本,+17MB),前端删掉 Skulpt - 报错按规则表翻成中文(pythonError.ts),生产库 2695 条 Python 编译错误覆盖 99.7%; 提交前拦下的和判题回来的编译错误共用同一张卡片 - 编辑器里标红出错行和 ^ 指的位置 - 中文标点(历史编译错误的 22%)一键换成英文,跳过字符串和注释、处理 f 字符串花括号; 594 条里修好 593 条,经 tokenize 核对没有改到字符串内容;走编辑器事务,可 Ctrl+Z Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
039992dc86
commit
2a4405d2ea
16 files changed
+733
-100
No files matched your search
@@ -47,16 +47,55 @@ function formatSql(code: string) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 用 CPython 编译一遍(只编译不执行),有语法错误就返回报错原文,没有返回 null。
|
||||
*
|
||||
* 这一步原来在浏览器里用 Skulpt 做,但 Skulpt 的报错九成是「bad input」「bad token」、
|
||||
* 只有行号没有列号,前端没法据此告诉学生错在哪。换成和判题机同版本的 CPython 之后,
|
||||
* 报错和判题机的编译错误一字不差,前端那张中文翻译表(pythonError.ts)两边通用。
|
||||
*
|
||||
* 输出刻意对齐判题机的 `err_info`:`format_exception_only` 再整体 strip,文件名也叫
|
||||
* solution.py。子进程里不 import 标准库之外的东西,python3-minimal 就够。
|
||||
*
|
||||
* 解释器起不来(开发机没装、路径配错)就当没检查,返回 undefined —— 放行之后判题机
|
||||
* 照样会判出编译错误,只是慢几秒。
|
||||
*/
|
||||
const SYNTAX_CHECK_SCRIPT = `
|
||||
import sys, traceback
|
||||
source = sys.stdin.buffer.read().decode("utf-8", "replace")
|
||||
try:
|
||||
compile(source, "solution.py", "exec")
|
||||
except SyntaxError as error:
|
||||
sys.stdout.write("".join(traceback.format_exception_only(error)).strip())
|
||||
sys.exit(1)
|
||||
`
|
||||
|
||||
async function checkPythonSyntax(code: string): Promise<string | null | undefined> {
|
||||
let result: Awaited<ReturnType<typeof runFormatter>>
|
||||
try {
|
||||
result = await runFormatter([config.pythonPath, "-I", "-c", SYNTAX_CHECK_SCRIPT], code)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
if (result.exitCode === 0) return null
|
||||
// 退出码 1 且有输出才是语法错误;别的(被超时杀掉、解释器自己报错)都当没检查
|
||||
return result.exitCode === 1 && result.stdout ? result.stdout : undefined
|
||||
}
|
||||
|
||||
export async function formatCode(code: string, language: "python" | "c" | "cpp" | "sql") {
|
||||
if (language === "sql") return formatSql(code)
|
||||
|
||||
if (language === "python") {
|
||||
const syntaxError = await checkPythonSyntax(code)
|
||||
if (syntaxError) throw new CodeFormatError(syntaxError, "syntax")
|
||||
const result = await runFormatter(
|
||||
[config.ruffPath, "format", "--stdin-filename", "code.py", "-"],
|
||||
code,
|
||||
)
|
||||
// CPython 都编译过了 ruff 还失败,是 ruff 的问题(比如还不认识新语法),
|
||||
// 不是学生的:按工具故障报,前端会原样提交
|
||||
if (result.exitCode !== 0) {
|
||||
throw new CodeFormatError(result.stderr || "Invalid Python syntax", "syntax")
|
||||
throw new CodeFormatError(result.stderr || "ruff format failed", "tool")
|
||||
}
|
||||
return result.stdout
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user