move delarations to @hydrooj/common

This commit is contained in:
undefined committed 2025-02-09 15:17:10 +00:00
1 parent 100ccf718f
commit 4a33816a37
49 files changed
+496 -465

No files matched your search

-60
View File
@@ -1,60 +0,0 @@
import { findFileSync, parseMemoryMB, parseTimeMS } from './utils';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default async function readYamlCases(cfg: Record<string, any> = {}, checkFile = (s: string, errMsg: string) => s) {
const config: any = {
checker_type: cfg.checker_type || 'default',
judge_extra_files: [],
user_extra_files: [],
};
if (cfg.type === 'objective') {
if (cfg.checker || cfg.interactor || cfg.validator) {
throw new Error('You cannot use checker, interactor or validator for objective questions');
}
} else {
if (cfg.checker) {
if (!cfg.checker.includes('.')) {
config.checker = findFileSync(`@hydrooj/hydrojudge/vendor/testlib/checkers/${cfg.checker}.cpp`, false);
}
config.checker ||= checkFile(cfg.checker, 'Cannot find checker {0}.');
}
if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.');
if (cfg.manager) config.manager = checkFile(cfg.manager, 'Cannot find Manager {0}.');
if (cfg.validator) config.validator = checkFile(cfg.validator, 'Cannot find validator {0}.');
for (const n of ['judge', 'user']) {
const conf = cfg[`${n}_extra_files`];
if (!conf) continue;
if (conf instanceof Array) {
config[`${n}_extra_files`] = conf.map((file) => checkFile(file, `Cannot find ${n} extra file {0}.`));
} else throw new Error(`Invalid ${n}_extra_files config.`);
}
}
if (cfg.cases?.length) {
config.subtasks = [{
cases: cfg.cases,
type: 'sum',
}];
}
if (cfg.time) config.time = parseTimeMS(cfg.time);
if (cfg.memory) config.memory = parseMemoryMB(cfg.memory);
return Object.assign(cfg, config);
}
export function convertIniConfig(ini: string) {
const f = ini.split('\n');
const count = parseInt(f[0], 10);
const res = { subtasks: [] };
for (let i = 1; i <= count; i++) {
if (!f[i]?.trim()) throw new Error('Testdata count incorrect.');
const [input, output, time, score, memory] = f[i].split('|');
const cur = {
cases: [{ input, output }],
score: parseInt(score, 10),
time: `${time}s`,
memory: '256m',
};
if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`;
res.subtasks.push(cur);
}
return res;
}
-156
View File
@@ -236,159 +236,3 @@ export function sortFiles(files: Record<string, any>[] | string[], key = '_id')
});
return result.map((x) => (isString ? x.name : (delete x._weights && x)));
}
interface MatchRule {
regex: RegExp;
output: (a: RegExpExecArray) => string[];
id: (a: RegExpExecArray) => number;
subtask: (a: RegExpExecArray) => number;
preferredScorerType: (a: RegExpExecArray) => 'min' | 'max' | 'sum';
}
const SubtaskMatcher: MatchRule[] = [
{
regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/,
output: (a) => ['out', 'ans']
.flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`])
.flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')])
.concat(a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null),
id: (a) => +a[4],
subtask: (a) => +(a[3] || 1),
preferredScorerType: (a) => (a[3] ? 'min' : 'sum'),
},
{
regex: /^([^\d]*)\.(in|IN)(\d+)$/,
output: (a) => [
`${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`,
`${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`,
].flatMap((i) => [i, i.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]),
id: (a) => +a[2],
subtask: () => 1,
preferredScorerType: () => 'sum',
},
{
regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/,
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`),
id: (a) => +a[4],
subtask: (a) => +a[2],
preferredScorerType: () => 'min',
},
{
regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/,
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`),
id: (a) => +a[2],
subtask: () => 1,
preferredScorerType: () => 'sum',
},
];
function* getScore(totalScore: number, count: number) {
const base = Math.floor(totalScore / count);
const extra = count - (totalScore % count);
for (let i = 0; i < count; i++) {
if (i >= extra) yield base + 1;
else yield base;
}
}
interface ParsedCase {
id?: number;
time?: number | string;
memory?: number | string;
score?: number;
input?: string;
output?: string;
}
interface ParsedSubtask {
cases: ParsedCase[];
type: 'min' | 'max' | 'sum';
time?: number | string;
memory?: number | string;
score?: number;
id?: number;
if?: number[];
}
export function readSubtasksFromFiles(files: string[], config) {
const subtask: Record<number, ParsedSubtask> = {};
for (const s of config.subtasks || []) if (s.id) subtask[s.id] = s;
for (const file of files) {
let match = false;
for (const rule of SubtaskMatcher) {
const data = rule.regex.exec(file);
if (!data) continue;
const sid = rule.subtask(data);
const c = { input: file, output: '', id: rule.id(data) };
const type = rule.preferredScorerType(data);
const outputs = (config.noOutputFile ? ['/dev/null'] : rule.output(data)).filter((i) => i);
for (const output of outputs) {
if (output === file) continue;
if (output === '/dev/null' || files.includes(output)) {
match = true;
c.output = output;
if (!subtask[sid]) {
subtask[sid] = {
time: config.time,
memory: config.memory,
type,
cases: [c],
id: sid,
};
} else if (!subtask[sid].cases) subtask[sid].cases = [c];
else subtask[sid].cases.push(c);
break;
}
}
if (match) break;
}
}
return Object.values(subtask);
}
export interface NormalizedCase extends Required<ParsedCase> {
time: number;
memory: number;
}
export interface NormalizedSubtask extends Required<ParsedSubtask> {
cases: NormalizedCase[];
time: number;
memory: number;
}
export function normalizeSubtasks(
subtasks: ParsedSubtask[], checkFile: (name: string, errMsg: string) => string,
time: number | string = '1000ms', memory: number | string = '256m', ignoreParseError = false,
timeRate = 1, memoryRate = 1,
): NormalizedSubtask[] {
subtasks.sort((a, b) => (a.id - b.id));
const subtaskScore = getScore(
Math.max(100 - Math.sum(subtasks.map((i) => i.score || 0)), 0),
subtasks.filter((i) => !i.score).length,
);
return subtasks.map((s, id) => {
s.cases.sort((a, b) => (a.id - b.id));
const score = s.score || subtaskScore.next().value as number;
const caseScore = getScore(
Math.max(score - Math.sum(s.cases.map((i) => i.score || 0)), 0),
s.cases.filter((i) => !i.score).length,
);
return {
id: id + 1,
type: 'min',
if: [],
...s,
score,
time: parseTimeMS(s.time || time, !ignoreParseError) * timeRate,
memory: parseMemoryMB(s.memory || memory, !ignoreParseError) * memoryRate,
cases: s.cases.map((c, index) => ({
id: index + 1,
...c,
score: c.score || (s.type === 'sum' ? caseScore.next().value as number : score),
time: parseTimeMS(c.time || s.time || time, !ignoreParseError) * timeRate,
memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError) * memoryRate,
input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null',
output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null',
})) as NormalizedCase[],
};
});
}
-60
View File
@@ -1,60 +0,0 @@
import yaml from 'js-yaml';
export interface LangConfig {
disabled?: boolean;
compile?: string;
execute: string;
code_file: string;
highlight: string;
monaco: string;
time_limit_rate: number;
memory_limit_rate: number;
address_space_limit?: boolean;
process_limit?: number;
display: string;
target?: string;
key: string;
hidden: boolean;
isBinary?: boolean;
analysis?: string;
/** @deprecated */
remote?: string;
validAs?: Record<string, string>;
/** @deprecated */
pretest?: string | false;
comment?: string | [string, string];
compile_time_limit?: number;
compile_memory_limit?: number;
version?: string;
}
export function parseLang(config: string): Record<string, LangConfig> {
const file = yaml.load(config) as Record<string, LangConfig>;
if (typeof file === 'undefined' || typeof file === 'string' || typeof file === 'number') throw new Error();
for (const key of Object.keys(file)) if (key.startsWith('_')) delete file[key];
for (const key in file) {
const entry = file[key];
if (key.includes('.')) {
const base = key.split('.')[0];
const baseCfg = file[base];
for (const bkey in baseCfg || {}) {
if (!(bkey in entry)) entry[bkey] = baseCfg[bkey];
}
}
}
for (const key in file) {
const entry = file[key];
entry.highlight ||= key;
entry.monaco ||= entry.highlight;
entry.time_limit_rate ||= 1;
entry.memory_limit_rate ||= 1;
entry.code_file ||= `foo.${key}`;
entry.execute ||= '/w/foo';
entry.key = key;
entry.hidden ||= false;
entry.disabled ||= false;
entry.isBinary ||= false;
entry.validAs ||= {};
}
return file;
}
-112
View File
@@ -1,112 +0,0 @@
export enum STATUS {
STATUS_WAITING = 0,
STATUS_ACCEPTED = 1,
STATUS_WRONG_ANSWER = 2,
STATUS_TIME_LIMIT_EXCEEDED = 3,
STATUS_MEMORY_LIMIT_EXCEEDED = 4,
STATUS_OUTPUT_LIMIT_EXCEEDED = 5,
STATUS_RUNTIME_ERROR = 6,
STATUS_COMPILE_ERROR = 7,
STATUS_SYSTEM_ERROR = 8,
STATUS_CANCELED = 9,
STATUS_ETC = 10,
STATUS_HACKED = 11,
STATUS_JUDGING = 20,
STATUS_COMPILING = 21,
STATUS_FETCHED = 22,
STATUS_IGNORED = 30,
STATUS_FORMAT_ERROR = 31,
STATUS_HACK_SUCCESSFUL = 32,
STATUS_HACK_UNSUCCESSFUL = 33,
}
export const STATUS_TEXTS: Record<STATUS, string> = {
[STATUS.STATUS_WAITING]: 'Waiting',
[STATUS.STATUS_ACCEPTED]: 'Accepted',
[STATUS.STATUS_WRONG_ANSWER]: 'Wrong Answer',
[STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'Time Exceeded',
[STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'Memory Exceeded',
[STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'Output Exceeded',
[STATUS.STATUS_RUNTIME_ERROR]: 'Runtime Error',
[STATUS.STATUS_COMPILE_ERROR]: 'Compile Error',
[STATUS.STATUS_SYSTEM_ERROR]: 'System Error',
[STATUS.STATUS_CANCELED]: 'Cancelled',
[STATUS.STATUS_ETC]: 'Unknown Error',
[STATUS.STATUS_HACKED]: 'Hacked',
[STATUS.STATUS_JUDGING]: 'Running',
[STATUS.STATUS_COMPILING]: 'Compiling',
[STATUS.STATUS_FETCHED]: 'Fetched',
[STATUS.STATUS_IGNORED]: 'Ignored',
[STATUS.STATUS_FORMAT_ERROR]: 'Format Error',
[STATUS.STATUS_HACK_SUCCESSFUL]: 'Hack Successful',
[STATUS.STATUS_HACK_UNSUCCESSFUL]: 'Hack Unsuccessful',
};
export const STATUS_SHORT_TEXTS: Partial<Record<STATUS, string>> = {
[STATUS.STATUS_ACCEPTED]: 'AC',
[STATUS.STATUS_WRONG_ANSWER]: 'WA',
[STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'TLE',
[STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'MLE',
[STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'OLE',
[STATUS.STATUS_RUNTIME_ERROR]: 'RE',
[STATUS.STATUS_COMPILE_ERROR]: 'CE',
[STATUS.STATUS_SYSTEM_ERROR]: 'SE',
[STATUS.STATUS_CANCELED]: 'IGN',
[STATUS.STATUS_HACKED]: 'HK',
[STATUS.STATUS_IGNORED]: 'IGN',
[STATUS.STATUS_FORMAT_ERROR]: 'FE',
};
export const STATUS_CODES: Record<STATUS, string> = {
[STATUS.STATUS_WAITING]: 'pending',
[STATUS.STATUS_ACCEPTED]: 'pass',
[STATUS.STATUS_WRONG_ANSWER]: 'fail',
[STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'fail',
[STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'fail',
[STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'fail',
[STATUS.STATUS_RUNTIME_ERROR]: 'fail',
[STATUS.STATUS_COMPILE_ERROR]: 'fail',
[STATUS.STATUS_SYSTEM_ERROR]: 'fail',
[STATUS.STATUS_CANCELED]: 'ignored',
[STATUS.STATUS_ETC]: 'fail',
[STATUS.STATUS_HACKED]: 'fail',
[STATUS.STATUS_JUDGING]: 'progress',
[STATUS.STATUS_COMPILING]: 'progress',
[STATUS.STATUS_FETCHED]: 'progress',
[STATUS.STATUS_IGNORED]: 'ignored',
[STATUS.STATUS_FORMAT_ERROR]: 'ignored',
[STATUS.STATUS_HACK_SUCCESSFUL]: 'pass',
[STATUS.STATUS_HACK_UNSUCCESSFUL]: 'fail',
};
export function getScoreColor(score: number | string): string {
if (score === null || score === undefined || !Number.isFinite(+score)) return '#000000';
return [
'#ff4f4f',
'#ff694f',
'#f8603a',
'#fc8354',
'#fa9231',
'#f7bb3b',
'#ecdb44',
'#e2ec52',
'#b0d628',
'#93b127',
'#25ad40',
][Math.floor((Number(score) || 0) / 10)];
}
export const USER_GENDER_MALE = 0;
export const USER_GENDER_FEMALE = 1;
export const USER_GENDER_OTHER = 2;
export const USER_GENDERS = [USER_GENDER_MALE, USER_GENDER_FEMALE, USER_GENDER_OTHER];
export const USER_GENDER_RANGE = {
[USER_GENDER_MALE]: 'Boy ♂',
[USER_GENDER_FEMALE]: 'Girl ♀',
[USER_GENDER_OTHER]: 'Other',
};
export const USER_GENDER_ICONS = {
[USER_GENDER_MALE]: '♂',
[USER_GENDER_FEMALE]: '♀',
[USER_GENDER_OTHER]: '?',
};