Skip to content

Commit

Permalink
support file I/O. refactor judge output cleaning.
Browse files Browse the repository at this point in the history
  • Loading branch information
thecodingwizard committed Jul 17, 2024
1 parent 86db8f6 commit a53b1c3
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 99 deletions.
26 changes: 16 additions & 10 deletions pages/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -79,15 +78,20 @@ function EditorPage() {
isCodeRunning: isRunning,
});
};
const fetchJudge = (code: string, input: string): Promise<any> => {
const fetchJudge = (
code: string,
input: string,
expectedOutput?: string
): Promise<JudgeResult> => {
return submitToJudge(
fileData.settings.language,
code,
input,
fileData.settings.compilerOptions[fileData.settings.language],
problem?.input?.endsWith('.in')
? problem.input.substring(0, problem.input.length - 3)
: undefined
: undefined,
expectedOutput
);
};

Expand All @@ -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 => {
Expand Down Expand Up @@ -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;
}
Expand Down
48 changes: 0 additions & 48 deletions src/editorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
140 changes: 107 additions & 33 deletions src/scripts/judge.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
// 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 = (
language: 'cpp' | 'java' | 'py',
code: string,
input: string,
compilerOptions: string,
fileIOName?: string
): Promise<any> => {
fileIOName?: string,
expectedOutput?: string
): Promise<JudgeResult> => {
const data = {
compile: {
source_code: code,
Expand All @@ -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`,
Expand All @@ -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 };
}
16 changes: 8 additions & 8 deletions src/types/judge.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit a53b1c3

Please sign in to comment.