RYP / server /services /pistonService.js
Soumya79's picture
Upload 1361 files
f91a684 verified
const DEFAULT_PISTON_EXECUTE_URL = 'https://emkc.org/api/v2/piston/execute';
const REQUEST_TIMEOUT_MS = 15000;
const RUN_TIMEOUT_MS = 10000;
const COMPILE_TIMEOUT_MS = 10000;
const LANGUAGE_RUNTIMES = {
python: {
pistonLanguage: 'python',
version: '3.10.0',
fileName: 'main.py',
},
cpp: {
pistonLanguage: 'c++',
version: '10.2.0',
fileName: 'main.cpp',
},
java: {
pistonLanguage: 'java',
version: '15.0.2',
fileName: 'Main.java',
},
javascript: {
pistonLanguage: 'javascript',
version: '18.15.0',
fileName: 'main.js',
},
c: {
pistonLanguage: 'c',
version: '10.2.0',
fileName: 'main.c',
},
};
const LANGUAGE_ALIASES = {
py: 'python',
python3: 'python',
'c++': 'cpp',
js: 'javascript',
node: 'javascript',
};
export class CompilerError extends Error {
constructor(message, status, statusCode = 500, executionTime = 0) {
super(message);
this.name = 'CompilerError';
this.status = status;
this.statusCode = statusCode;
this.executionTime = executionTime;
}
}
export function getSupportedLanguages() {
return Object.keys(LANGUAGE_RUNTIMES);
}
function normalizeLanguage(language) {
const key = String(language ?? '').trim().toLowerCase();
return LANGUAGE_ALIASES[key] ?? key;
}
function getRuntime(language) {
const normalized = normalizeLanguage(language);
return LANGUAGE_RUNTIMES[normalized] ?? null;
}
function toText(value) {
return typeof value === 'string' ? value : '';
}
function combinedStream(stream) {
if (!stream) {
return '';
}
return [stream.stdout, stream.stderr, stream.output]
.map(toText)
.filter(Boolean)
.join('')
.trim();
}
function nonEmptyLines(text) {
return toText(text)
.split(/\r\n|\r|\n/)
.map((line) => line.trim())
.filter(Boolean);
}
function firstDiagnostic(text) {
return nonEmptyLines(text).slice(0, 6);
}
function stripForComplexityScan(source, language) {
let text = toText(source);
text = text.replace(/\/\*[\s\S]*?\*\//g, ' ');
if (language === 'python') {
text = text.replace(/#.*$/gm, ' ');
} else {
text = text.replace(/\/\/.*$/gm, ' ');
}
return text
.replace(/"""[\s\S]*?"""/g, '""')
.replace(/'''[\s\S]*?'''/g, "''")
.replace(/`(?:\\.|[^`])*`/g, '""')
.replace(/"(?:\\.|[^"\\])*"/g, '""')
.replace(/'(?:\\.|[^'\\])*'/g, "''");
}
function estimatePythonLoopDepth(source) {
const lines = source.split(/\r\n|\r|\n/);
const loopIndents = [];
let maxDepth = 0;
let hasLoop = false;
for (const line of lines) {
if (!line.trim()) {
continue;
}
const indent = line.match(/^\s*/)?.[0].replace(/\t/g, ' ').length ?? 0;
while (loopIndents.length && indent <= loopIndents[loopIndents.length - 1]) {
loopIndents.pop();
}
if (/^\s*(for|while)\b/.test(line)) {
hasLoop = true;
loopIndents.push(indent);
maxDepth = Math.max(maxDepth, loopIndents.length);
}
}
return { maxDepth, hasLoop };
}
function estimateBraceLoopDepth(source) {
const lines = source.split(/\r\n|\r|\n/);
let braceDepth = 0;
const loopBlockDepths = [];
let maxDepth = 0;
let hasLoop = false;
for (const line of lines) {
const trimmed = line.trim();
while (loopBlockDepths.length && braceDepth < loopBlockDepths[loopBlockDepths.length - 1]) {
loopBlockDepths.pop();
}
const loopMatches = trimmed.match(/\b(for|while)\s*\(/g) ?? [];
if (loopMatches.length > 0) {
hasLoop = true;
maxDepth = Math.max(maxDepth, loopBlockDepths.length + loopMatches.length);
}
const opens = (line.match(/\{/g) ?? []).length;
const closes = (line.match(/\}/g) ?? []).length;
const loopBlocksOnLine = Math.min(loopMatches.length, opens);
for (let index = 0; index < loopBlocksOnLine; index += 1) {
loopBlockDepths.push(braceDepth + index + 1);
}
braceDepth = Math.max(0, braceDepth + opens - closes);
while (loopBlockDepths.length && braceDepth < loopBlockDepths[loopBlockDepths.length - 1]) {
loopBlockDepths.pop();
}
}
return { maxDepth, hasLoop };
}
function hasLogarithmicLoopHint(source) {
const patterns = [
/\b\w+\s*(\*=|\/=|<<=|>>=)\s*2\b/,
/\b\w+\s*=\s*\w+\s*([*/]|<<|>>)\s*2\b/,
/\b\w+\s*=\s*\w+\s*\/\s*2\b/,
/\bmid\s*=/i,
/\bbinary\s*search\b/i,
];
return patterns.some((pattern) => pattern.test(source));
}
function countHeapOperations(source) {
const pythonHeapOps = source.match(/\b(?:heapq\.)?(?:heappush|heappop|heapreplace|heappushpop|heapify)\s*\(/g)?.length ?? 0;
const namedHeapOps = source.match(/\b(?:heap|pq|queue|priorityQueue|minHeap|maxHeap)\.(?:push|pop|offer|poll|add|remove|enqueue|dequeue)\s*\(/g)?.length ?? 0;
const cppPriorityQueueOps = /\bpriority_queue\s*</.test(source)
? source.match(/\.(?:push|pop|emplace)\s*\(/g)?.length ?? 0
: 0;
const javaPriorityQueueOps = /\bPriorityQueue\s*</.test(source)
? source.match(/\.(?:offer|poll|add|remove)\s*\(/g)?.length ?? 0
: 0;
return pythonHeapOps + namedHeapOps + cppPriorityQueueOps + javaPriorityQueueOps;
}
function hasKBoundedHeapSignal(source) {
return /\bk\b|merge\s+k|k\s+sorted|lists?\s*\.length|lists?\s*\.size\s*\(|len\s*\(\s*lists?\s*\)|enumerate\s*\(\s*lists?\s*\)|for\s+\w+\s+in\s+lists?\b/i.test(source);
}
function estimateComplexity(code, runtime) {
const source = stripForComplexityScan(code, runtime.pistonLanguage);
const loopInfo =
runtime.pistonLanguage === 'python'
? estimatePythonLoopDepth(source)
: estimateBraceLoopDepth(source);
const logHint = hasLogarithmicLoopHint(source);
const heapOperationCount = countHeapOperations(source);
const heapDetected = heapOperationCount > 0 || /\bheapq\b|\bPriorityQueue\b|\bpriority_queue\s*</.test(source);
const heapWorkDetected = heapOperationCount > 0;
const kBoundedHeap = heapDetected && hasKBoundedHeapSignal(source);
let timeComplexity = 'O(1)';
let summary = 'No loops were detected, so this looks constant for the scanned code path.';
const evidence = [];
if (loopInfo.hasLoop) {
evidence.push(`Detected loop nesting depth: ${loopInfo.maxDepth || 1}.`);
} else {
evidence.push('No for/while loops detected.');
}
if (logHint) {
evidence.push('Detected halving/doubling or binary-search style loop update.');
}
if (heapDetected) {
evidence.push(`Detected ${Math.max(heapOperationCount, 1)} heap/priority-queue operation${heapOperationCount === 1 ? '' : 's'}.`);
}
if (kBoundedHeap) {
evidence.push('Heap size appears bounded by k input lists.');
}
if (loopInfo.hasLoop && heapWorkDetected) {
timeComplexity = kBoundedHeap ? 'O(N log k)' : 'O(n log n)';
summary = kBoundedHeap
? 'Heap operations are performed while merging k input lists, so each of the N output elements costs log k.'
: 'Heap operations inside traversal add a logarithmic factor to the repeated work.';
} else if (loopInfo.maxDepth >= 2) {
timeComplexity = 'O(n^2)';
summary = 'Nested loops were detected, so the highlighted curve is quadratic.';
} else if (loopInfo.hasLoop && logHint) {
timeComplexity = 'O(log n)';
summary = 'A single loop with halving/doubling behavior was detected, so the highlighted curve is logarithmic.';
} else if (loopInfo.hasLoop) {
timeComplexity = 'O(n)';
summary = 'A single loop was detected, so the highlighted curve is linear.';
}
return {
timeComplexity,
source: 'static_code_scan',
confidence: heapWorkDetected || loopInfo.maxDepth >= 2 || (loopInfo.hasLoop && !logHint) ? 'medium' : 'low',
summary,
evidence,
};
}
function isTimeoutSignal(signal) {
return ['SIGKILL', 'SIGTERM', 'SIGXCPU'].includes(String(signal ?? '').toUpperCase());
}
function looksLikeTimeout(text) {
return /timeout|timed out|time limit|killed/i.test(text);
}
function normalizePistonApiError(payload, fallback) {
if (!payload) {
return fallback;
}
if (typeof payload === 'string') {
return payload;
}
if (typeof payload.message === 'string') {
return payload.message;
}
if (typeof payload.error === 'string') {
return payload.error;
}
return fallback;
}
function pistonExecuteUrl() {
return (process.env.PISTON_EXECUTE_URL || DEFAULT_PISTON_EXECUTE_URL).trim();
}
function pistonHeaders() {
const headers = {
'Content-Type': 'application/json',
};
const authorization =
process.env.PISTON_AUTHORIZATION ||
process.env.PISTON_API_KEY ||
process.env.PISTON_TOKEN ||
'';
if (authorization.trim()) {
headers.Authorization = authorization.trim();
}
return headers;
}
function buildCompilerAnalysis({
runtime,
code,
phase,
status,
exitCode = null,
signal = null,
stdout = '',
stderr = '',
diagnostics = '',
summary,
}) {
return {
source: 'compiler',
language: runtime.pistonLanguage,
version: runtime.version,
phase,
status,
exitCode,
signal,
stdoutLines: nonEmptyLines(stdout).length,
stderrLines: nonEmptyLines(stderr).length,
summary,
details: firstDiagnostic(diagnostics || stderr || stdout),
complexity: estimateComplexity(code, runtime),
};
}
function classifyPistonResult(result, executionTime, runtime, code) {
const compile = result?.compile;
const run = result?.run;
const compileCode = typeof compile?.code === 'number' ? compile.code : 0;
const runCode = typeof run?.code === 'number' ? run.code : 0;
const compileText = combinedStream(compile);
const runStdout = toText(run?.stdout);
const runStderr = toText(run?.stderr);
const runOutput = toText(run?.output) || runStdout;
const runText = [runStderr, runOutput].filter(Boolean).join('\n');
if (compile && compileCode !== 0) {
return {
output: toText(compile.stdout),
error: compileText || 'Compilation failed.',
executionTime,
status: 'compile_error',
analysis: buildCompilerAnalysis({
runtime,
code,
phase: 'compile',
status: 'compile_error',
exitCode: compileCode,
signal: compile.signal ?? null,
stdout: compile?.stdout,
stderr: compile?.stderr,
diagnostics: compileText,
summary: 'The compiler rejected the source before execution.',
}),
};
}
if (run && (isTimeoutSignal(run.signal) || looksLikeTimeout(runText))) {
return {
output: runStdout,
error: runStderr || 'Execution timed out.',
executionTime,
status: 'timeout',
analysis: buildCompilerAnalysis({
runtime,
code,
phase: 'run',
status: 'timeout',
exitCode: runCode,
signal: run.signal ?? null,
stdout: runStdout,
stderr: runStderr,
diagnostics: runText,
summary: 'The runtime stopped the program because it exceeded the execution limit.',
}),
};
}
if (run && runCode !== 0) {
return {
output: runStdout,
error: runStderr || runOutput || `Runtime error. Process exited with code ${runCode}.`,
executionTime,
status: 'runtime_error',
analysis: buildCompilerAnalysis({
runtime,
code,
phase: 'run',
status: 'runtime_error',
exitCode: runCode,
signal: run.signal ?? null,
stdout: runStdout,
stderr: runStderr,
diagnostics: runText,
summary: `The program started, then exited with code ${runCode}.`,
}),
};
}
return {
output: runStdout || runOutput,
error: runStderr,
executionTime,
status: 'success',
analysis: buildCompilerAnalysis({
runtime,
code,
phase: run ? 'run' : 'compile',
status: 'success',
exitCode: runCode,
signal: run?.signal ?? null,
stdout: runStdout || runOutput,
stderr: runStderr,
diagnostics: runStderr,
summary: runStdout || runOutput
? 'The compiler/runtime completed successfully and produced stdout.'
: 'The compiler/runtime completed successfully with no stdout.',
}),
};
}
async function readResponsePayload(response) {
const text = await response.text();
if (!text) {
return null;
}
try {
return JSON.parse(text);
} catch {
return text;
}
}
export async function executeCode({ code, language, input = '' }) {
const runtime = getRuntime(language);
const startedAt = Date.now();
if (!runtime) {
throw new CompilerError(
`Unsupported language: ${language}. Supported languages are ${getSupportedLanguages().join(', ')}.`,
'unsupported_language',
400,
);
}
if (typeof code !== 'string' || code.trim().length === 0) {
throw new CompilerError('Code is required.', 'validation_error', 400);
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
try {
const response = await fetch(pistonExecuteUrl(), {
method: 'POST',
headers: pistonHeaders(),
signal: controller.signal,
body: JSON.stringify({
language: runtime.pistonLanguage,
version: runtime.version,
files: [
{
name: runtime.fileName,
content: code,
},
],
stdin: typeof input === 'string' ? input : String(input ?? ''),
compile_timeout: COMPILE_TIMEOUT_MS,
run_timeout: RUN_TIMEOUT_MS,
}),
});
const executionTime = Date.now() - startedAt;
if (!response.ok) {
const payload = await readResponsePayload(response);
const message = normalizePistonApiError(payload, `Piston API returned HTTP ${response.status}.`);
const status = response.status === 408 || response.status === 504 ? 'timeout' : 'piston_error';
throw new CompilerError(message, status, response.status === 408 ? 408 : 502, executionTime);
}
const result = await response.json();
return classifyPistonResult(result, executionTime, runtime, code);
} catch (error) {
const executionTime = Date.now() - startedAt;
if (error instanceof CompilerError) {
throw error;
}
if (error?.name === 'AbortError') {
throw new CompilerError('Execution timed out before Piston responded.', 'timeout', 504, executionTime);
}
throw new CompilerError(
`Unable to reach Piston execution service: ${error?.message ?? 'network error'}`,
'piston_unavailable',
502,
executionTime,
);
} finally {
clearTimeout(timeoutId);
}
}