diff --git a/pages/[id].tsx b/pages/[id].tsx index 63dcbd6..076d516 100644 --- a/pages/[id].tsx +++ b/pages/[id].tsx @@ -31,7 +31,6 @@ import useUserPermission from '../src/hooks/useUserPermission'; import { SettingsModal } from '../src/components/settings/SettingsModal'; import { getSampleIndex } from '../src/components/JudgeInterface/Samples'; import useJudgeResults from '../src/hooks/useJudgeResults'; -import { cleanJudgeResult } from '../src/editorUtils'; import JudgeResult from '../src/types/judge'; import useUserFileConnection from '../src/hooks/useUserFileConnection'; import useUpdateUserDashboard from '../src/hooks/useUpdateUserDashboard'; @@ -79,7 +78,11 @@ function EditorPage() { isCodeRunning: isRunning, }); }; - const fetchJudge = (code: string, input: string): Promise => { + const fetchJudge = ( + code: string, + input: string, + expectedOutput?: string + ): Promise => { return submitToJudge( fileData.settings.language, code, @@ -87,7 +90,8 @@ function EditorPage() { fileData.settings.compilerOptions[fileData.settings.language], problem?.input?.endsWith('.in') ? problem.input.substring(0, problem.input.length - 3) - : undefined + : undefined, + expectedOutput ); }; @@ -112,10 +116,12 @@ function EditorPage() { setResultAt(inputTabIndex, null); const code = getMainEditorValue(); - fetchJudge(code, input) + fetchJudge(code, input, expectedOutput) .then(async resp => { const data: JudgeResult = resp; - cleanJudgeResult(data, expectedOutput, prefix); + if (prefix && data.status !== 'compile_error') { + data.statusDescription = prefix + data.statusDescription; + } setResultAt(inputTabIndex, data); }) .catch(e => { @@ -144,19 +150,19 @@ function EditorPage() { const promises = []; for (let index = 0; index < samples.length; ++index) { const sample = samples[index]; - promises.push(fetchJudge(code, sample.input)); + promises.push(fetchJudge(code, sample.input, sample.output)); } const newJudgeResults = judgeResults; const results: JudgeResult[] = []; for (let index = 0; index < samples.length; ++index) { - const sample = samples[index]; - const resp = await promises[index]; - const data: JudgeResult = await resp; + const data = await promises[index]; let prefix = 'Sample'; if (samples.length > 1) prefix += ` ${index + 1}`; prefix += ': '; - cleanJudgeResult(data, sample.output, prefix); + if (data.status !== 'compile_error') { + data.statusDescription = prefix + data.statusDescription; + } results.push(data); newJudgeResults[2 + index] = data; } diff --git a/src/editorUtils.ts b/src/editorUtils.ts index 095bce5..d10779a 100644 --- a/src/editorUtils.ts +++ b/src/editorUtils.ts @@ -10,51 +10,3 @@ export function isFirebaseId(queryId: string): boolean { queryId.length === 19 ); } - -function cleanAndReplaceOutput(output: string): { - replaced: string; - cleaned: string; -} { - const replaced = output.replace(/ /g, '\u2423'); // spaces made visible - const lines = output.split('\n'); - for (let i = 0; i < lines.length; ++i) lines[i] = lines[i].trim(); - const cleaned = lines.join('\n').trim(); // remove leading / trailing whitespace on each line, trim - return { replaced, cleaned }; -} - -export function cleanJudgeResult( - data: JudgeResult, - expectedOutput?: string, - prefix?: string -): void { - const statusDescriptions: { [key in JudgeResultStatuses]: string } = { - success: 'Successful', - compile_error: 'Compilation Error', - runtime_error: 'Runtime Error', - internal_error: 'Internal Server Error', - time_limit_exceeded: 'Time Limit Exceeded', - wrong_answer: 'Wrong Answer', - }; - data.statusDescription = statusDescriptions[data.status]; - if (data.fileOutput) { - data.stdout = data.fileOutput; - } - if (expectedOutput && data.status === 'success') { - data.statusDescription = 'Successful'; - let stdout = data.stdout ?? ''; - if (!stdout.endsWith('\n')) stdout += '\n'; - if (data.status === 'success' && stdout !== expectedOutput) { - data.status = 'wrong_answer'; - const { cleaned, replaced } = cleanAndReplaceOutput(stdout); - if (cleaned === expectedOutput.trim()) { - data.statusDescription = 'Wrong Answer (Extra Whitespace)'; - data.stdout = replaced; // show the extra whitespace - } else { - data.statusDescription = 'Wrong Answer'; - } - } - } - if (prefix && data.status !== 'compile_error') - // only add prefix when no compilation error - data.statusDescription = prefix + data.statusDescription; -} diff --git a/src/scripts/judge.ts b/src/scripts/judge.ts index ca8a0af..647b2e3 100644 --- a/src/scripts/judge.ts +++ b/src/scripts/judge.ts @@ -1,10 +1,29 @@ -// Source: https://javascript.info/regexp-groups -export const extractJavaFilename = (code: string): string => { - const matches = Array.from(code.matchAll(/public +class +(\w+)/g)); - if (matches.length > 0) { - return matches[0][1] + '.java'; - } - return 'Main.java'; // fallback, something went wrong +import invariant from 'tiny-invariant'; +import JudgeResult, { JudgeResultStatuses } from '../types/judge'; + +type CompileAndExecuteResponse = { + compile: { + stdout: string; + stderr: string; + wall_time: string; + memory_usage: string; + exit_code: number; + exit_signal: string | null; + }; + execute: { + stdout: string; + file_output: string | null; + stderr: string; + wall_time: string; + memory_usage: string; + exit_code: number; + exit_signal: string | null; + verdict: + | 'accepted' + | 'wrong_answer' + | 'time_limit_exceeded' + | 'runtime_error'; + } | null; }; export const submitToJudge = ( @@ -12,8 +31,9 @@ export const submitToJudge = ( code: string, input: string, compilerOptions: string, - fileIOName?: string -): Promise => { + fileIOName?: string, + expectedOutput?: string +): Promise => { const data = { compile: { source_code: code, @@ -24,8 +44,8 @@ export const submitToJudge = ( execute: { timeout_ms: 5000, stdin: input, + file_io_name: fileIOName, }, - // todo: fileioname }; return fetch( `https://v3nuswv3poqzw6giv37wmrt6su0krxvt.lambda-url.us-east-1.on.aws/compile-and-execute`, @@ -41,29 +61,83 @@ export const submitToJudge = ( const msg = await resp.text(); throw new Error(msg); } - const data = await resp.json(); - if (data.compile?.exit_code !== 0) { - return { - status: 'compile_error', - stdout: data.compile.stdout, - message: data.compile.stderr, - compilationMessage: data.compile.stderr, - time: data.compile.wall_time, - memory: data.compile.memory_usage, - }; - } + const data = (await resp.json()) as CompileAndExecuteResponse; + return cleanJudgeResult(data, expectedOutput); + }); +}; + +function cleanJudgeResult( + data: CompileAndExecuteResponse, + expectedOutput?: string +): JudgeResult { + const statusDescriptions: { [key in JudgeResultStatuses]: string } = { + success: 'Successful', + compile_error: 'Compilation Error', + runtime_error: 'Runtime Error', + internal_error: 'Internal Server Error', + time_limit_exceeded: 'Time Limit Exceeded', + wrong_answer: 'Wrong Answer', + }; + + if (data.compile.exit_code !== 0) { return { - status: - data.execute.exit_code === 0 - ? 'success' - : data.execute.exit_code === 124 - ? 'time_limit_exceeded' - : 'runtime_error', - stdout: data.execute.stdout, - stderr: data.execute.stderr, + status: 'compile_error', + statusDescription: statusDescriptions['compile_error'], + stdout: data.compile.stdout, + message: data.compile.stderr, compilationMessage: data.compile.stderr, - time: data.execute.wall_time, - memory: data.execute.memory_usage, + time: data.compile.wall_time, + memory: data.compile.memory_usage, + stderr: null, + debugData: null, + fileOutput: null, }; - }); -}; + } + invariant( + data.execute != null, + 'received invalid compile and execute response' + ); + + let status: JudgeResultStatuses = + data.execute.verdict === 'accepted' ? 'success' : data.execute.verdict; + + let stdout = data.execute.file_output ?? data.execute.stdout; + let statusDescription = statusDescriptions[status]; + if (expectedOutput && status === 'success') { + if (!stdout.endsWith('\n')) stdout += '\n'; + if (status === 'success' && stdout !== expectedOutput) { + status = 'wrong_answer'; + const { cleaned, replaced } = cleanAndReplaceOutput(stdout); + if (cleaned === expectedOutput.trim()) { + statusDescription = 'Wrong Answer (Extra Whitespace)'; + stdout = replaced; // show the extra whitespace + } else { + statusDescription = 'Wrong Answer'; + } + } + } + + return { + status, + statusDescription, + stdout, + stderr: data.execute.stderr, + compilationMessage: data.compile.stderr, + time: data.execute.wall_time, + memory: data.execute.memory_usage, + fileOutput: data.execute.file_output, + message: null, + debugData: null, + }; +} + +function cleanAndReplaceOutput(output: string): { + replaced: string; + cleaned: string; +} { + const replaced = output.replace(/ /g, '\u2423'); // spaces made visible + const lines = output.split('\n'); + for (let i = 0; i < lines.length; ++i) lines[i] = lines[i].trim(); + const cleaned = lines.join('\n').trim(); // remove leading / trailing whitespace on each line, trim + return { replaced, cleaned }; +} diff --git a/src/types/judge.d.ts b/src/types/judge.d.ts index e602b16..c29d2e7 100644 --- a/src/types/judge.d.ts +++ b/src/types/judge.d.ts @@ -8,12 +8,12 @@ export type JudgeResultStatuses = export default interface JudgeResult { statusDescription: string; status: JudgeResultStatuses; - stdout?: string; - stderr?: string; - message?: string; - compilationMessage?: string; - time?: string; - memory?: string; - debugData?: any; - fileOutput?: string; + stdout: string | null; + stderr: string | null; + message: string | null; + compilationMessage: string | null; + time: string | null; + memory: string | null; + debugData: any | null; + fileOutput: string | null; }