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* 0 || /\bheapq\b|\bPriorityQueue\b|\bpriority_queue\s* 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); } }