From c2ef55b10bae43f3d1d1c4097cdb1fe7fdf2db74 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 30 Sep 2026 11:29:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E6=8F=90=E4=BA=A4):=20C=20=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BC=96=E8=AF=91=E4=B8=8D=E8=BF=87=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=89=8D=E7=9A=84=E8=87=AA=E5=8A=A8=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=E6=8A=8A=E5=87=BA=E9=94=99=E7=9A=84=E5=87=A0?= =?UTF-8?q?=E8=A1=8C=E5=B9=B6=E6=88=90=E4=B8=80=E8=A1=8C=EF=BC=8C=E5=AD=A6?= =?UTF-8?q?=E7=94=9F=E8=AE=A4=E4=B8=8D=E5=87=BA=E8=87=AA=E5=B7=B1=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clang-format 不认中文分号、缺了分号的语句,会把它和下一行并起来(int n; 和 scanf 成了 一行),报错的行号也对不上学生记得的位置。现在 C / C++ 格式化之前先套上题目模板、在 判题机上编一次,编不过就原样返回、不格式化,代码照常提交、照常判成编译错误。 api 容器里没有 gcc,判题机的编译器和参数本来就是判的标准,所以借它编。这一下不进判题 队列、不建提交记录;判题机连不上时照旧格式化。代价是每次交 C 多一次编译,零点几秒。 Co-Authored-By: Claude Opus 5.5 --- apps/api/src/judge/run.ts | 21 +++++++++++++++++++++ apps/api/src/routes/submission.ts | 24 +++++++++++++++++++++++- apps/web/src/oj/api.ts | 3 ++- apps/web/src/oj/store/submission.ts | 4 +++- packages/contract/src/submission.ts | 2 ++ 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/apps/api/src/judge/run.ts b/apps/api/src/judge/run.ts index 5dd1cae..6c7481b 100644 --- a/apps/api/src/judge/run.ts +++ b/apps/api/src/judge/run.ts @@ -112,6 +112,27 @@ export async function postJudge( return response } +/** + * 这份代码在判题机上编不编得过。给提交前的自动格式化用:clang-format 碰上编译不过的 + * C 代码会把行并起来(`int n;` 和下一行、少了分号的两行),学生回头看到的就不是自己 + * 写的代码了,报错的行号也对不上他记得的位置。api 容器里没有 gcc,而判题机的编译器、 + * 编译参数本来就是判的标准,所以借它编一次。 + * + * 判题机只有 /judge 一个入口,得带一组测试点:给一组空输入,跑的那一下限 1 秒。 + * 判题机连不上、超时,一律当编得过 —— 那就和原来一样照常格式化。 + */ +export async function compileFails(language: string, source: string) { + try { + const response = await postJudge(language, source, 1000, 256, [{ input: "", output: "" }], { + signal: AbortSignal.timeout(8000), + }) + const body = (await response.json()) as JudgeResponse + return body.err === "CompileError" + } catch { + return false + } +} + async function requestJudge( language: string, code: string, diff --git a/apps/api/src/routes/submission.ts b/apps/api/src/routes/submission.ts index 4c838f9..35819ac 100644 --- a/apps/api/src/routes/submission.ts +++ b/apps/api/src/routes/submission.ts @@ -17,7 +17,9 @@ import { optionalAuth, requireAuth, requireTeacher } from "../auth/middleware" import type { AuthUser } from "../auth/session" import { db, schema } from "../db" import { failure, parseBody, success } from "../http" +import { compileFails, templateForLanguage } from "../judge/run" import { JudgeStatus } from "../judge/status" +import { parseProblemTemplate } from "../judge/template" import { judgeQueue } from "../queue" import { canAccessContest, @@ -244,8 +246,28 @@ submissionRoutes.post("/submissions/:id/rejudge", requireTeacher, async (c) => { submissionRoutes.post("/code/format", requireAuth, async (c) => { const parsed = await parseBody(c, formatCodeRequestSchema, "Invalid format payload") if (!parsed.success) return parsed.response + // C / C++ 编不过就原样返回,不格式化(理由见 judge/run.ts 的 compileFails)。 + // 代码照常提交、照常判成编译错误,只是不改动学生的代码 + const { code: source, language, problemId } = parsed.data + if (language === "c" || language === "cpp") { + const judgeLanguage = language === "c" ? "C" : "C++" + const [problem] = + problemId === undefined + ? [] + : await db + .select({ template: schema.problem.template }) + .from(schema.problem) + .where(eq(schema.problem.id, problemId)) + .limit(1) + const rawTemplate = problem ? templateForLanguage(problem.template, judgeLanguage) : null + const template = rawTemplate ? parseProblemTemplate(rawTemplate) : null + const full = template ? `${template.prepend}\n${source}\n${template.append}` : source + if (await compileFails(judgeLanguage, full)) { + return success(c, { code: source } satisfies FormatCodeResponse) + } + } try { - const code = await formatCode(parsed.data.code, parsed.data.language) + const code = await formatCode(source, language) return success(c, { code } satisfies FormatCodeResponse) } catch (error) { if (error instanceof CodeFormatError) { diff --git a/apps/web/src/oj/api.ts b/apps/web/src/oj/api.ts index c169948..1befaf8 100644 --- a/apps/web/src/oj/api.ts +++ b/apps/web/src/oj/api.ts @@ -134,7 +134,7 @@ export function submitCode(data: SubmitCodePayload) { return api.post("submissions", data) } -export function formatCode(data: { code: string; language: string }) { +export function formatCode(data: { code: string; language: string; problemId: number }) { const languages: Record = { Python: "python", C: "c", @@ -144,6 +144,7 @@ export function formatCode(data: { code: string; language: string }) { return api.post("code/format", { code: data.code, language: languages[data.language] ?? data.language.toLowerCase(), + problemId: data.problemId, }) } diff --git a/apps/web/src/oj/store/submission.ts b/apps/web/src/oj/store/submission.ts index 6537bcc..72d1163 100644 --- a/apps/web/src/oj/store/submission.ts +++ b/apps/web/src/oj/store/submission.ts @@ -103,7 +103,8 @@ export const useSubmissionStore = defineStore("submission", () => { formattedBeforeSubmit.value = false // 0. 提交前自动格式化(Python 用 ruff,C/C++ 用 clang-format,SQL 用 sqlparse)。 - // Python 在格式化之前先由服务端的 CPython 查一遍语法,有错就不提交 + // Python 在格式化之前先由服务端的 CPython 查一遍语法,有错就不提交; + // C / C++ 先在判题机上编一遍,编不过就原样交(格式化会把错的几行并成一行) const formatLang = LANGUAGE_FORMAT_VALUE[codeStore.code.language] if (["python", "c", "cpp", "sql"].includes(formatLang)) { isFormatting.value = true @@ -111,6 +112,7 @@ export const useSubmissionStore = defineStore("submission", () => { const res = await formatCode({ code: codeStore.code.value, language: formatLang, + problemId: problem.id, }) if (stale()) return if (res.code !== codeStore.code.value) { diff --git a/packages/contract/src/submission.ts b/packages/contract/src/submission.ts index 470aeb1..a0b2297 100644 --- a/packages/contract/src/submission.ts +++ b/packages/contract/src/submission.ts @@ -534,6 +534,8 @@ export const todaySubmissionStatisticsSchema = z.object({ export const formatCodeRequestSchema = z.object({ code: z.string().max(1024 * 1024), language: z.enum(["python", "c", "cpp", "sql"]), + /** C / C++ 格式化前要先编一遍,得套上这道题的模板才编得过 */ + problemId: z.number().int().optional(), }) export const formatCodeResponseSchema = z.object({ code: z.string() })