diff --git a/app/terminal/wandbox/api.ts b/app/terminal/wandbox/api.ts index 2872d12..4de1f12 100644 --- a/app/terminal/wandbox/api.ts +++ b/app/terminal/wandbox/api.ts @@ -62,6 +62,14 @@ export interface CompileParameter { is_private?: boolean; "compiler-info"?: CompilerInfo; } +/** + * Represents a single line in the ndjson response from /api/compile.ndjson + */ +export interface CompileNdjsonResult { + type: string; + data: string; +} + export interface CompileResult { status: string; signal: string; @@ -93,17 +101,15 @@ interface CompileProps { codes: Code[]; } export interface CompileResultWithOutput extends CompileResult { - compilerOutput: ReplOutput[]; - compilerError: ReplOutput[]; - programOutput: ReplOutput[]; - programError: ReplOutput[]; + output: ReplOutput[]; } export async function compileAndRun( options: CompileProps ): Promise { - const result: CompileResult = await fetch( - new URL("/api/compile.json", WANDBOX), + // Call the ndjson API instead of json API + const response = await fetch( + new URL("/api/compile.ndjson", WANDBOX), { method: "post", headers: { @@ -121,32 +127,124 @@ export async function compileAndRun( is_private: true, } satisfies CompileParameter), } - ).then((res) => res.json()); + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + // Read the ndjson response as a stream + const reader = response.body?.getReader(); + if (!reader) { + throw new Error("Response body is not readable"); + } + + const decoder = new TextDecoder(); + let buffer = ""; + const ndjsonResults: CompileNdjsonResult[] = []; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + + // Keep the last incomplete line in the buffer + buffer = lines.pop() || ""; + + for (const line of lines) { + if (line.trim().length > 0) { + ndjsonResults.push(JSON.parse(line) as CompileNdjsonResult); + } + } + } + + // Process any remaining data in the buffer + if (buffer.trim().length > 0) { + ndjsonResults.push(JSON.parse(buffer) as CompileNdjsonResult); + } + } finally { + reader.releaseLock(); + } + + // Merge ndjson results into a CompileResult (same logic as Rust merge_compile_result) + const result: CompileResult = { + status: "", + signal: "", + compiler_output: "", + compiler_error: "", + compiler_message: "", + program_output: "", + program_error: "", + program_message: "", + permlink: "", + url: "", + }; + + // Build output array in the order messages are received + const output: ReplOutput[] = []; + + for (const r of ndjsonResults) { + switch (r.type) { + case "Control": + // Ignore control messages + break; + case "CompilerMessageS": + result.compiler_output += r.data; + result.compiler_message += r.data; + // Add to output in order + if (r.data.trim()) { + for (const line of r.data.trim().split("\n")) { + output.push({ type: "stdout", message: line }); + } + } + break; + case "CompilerMessageE": + result.compiler_error += r.data; + result.compiler_message += r.data; + // Add to output in order + if (r.data.trim()) { + for (const line of r.data.trim().split("\n")) { + output.push({ type: "error", message: line }); + } + } + break; + case "StdOut": + result.program_output += r.data; + result.program_message += r.data; + // Add to output in order + if (r.data.trim()) { + for (const line of r.data.trim().split("\n")) { + output.push({ type: "stdout", message: line }); + } + } + break; + case "StdErr": + result.program_error += r.data; + result.program_message += r.data; + // Add to output in order + if (r.data.trim()) { + for (const line of r.data.trim().split("\n")) { + output.push({ type: "stderr", message: line }); + } + } + break; + case "ExitCode": + result.status += r.data; + break; + case "Signal": + result.signal += r.data; + break; + default: + // Ignore unknown types + break; + } + } + return { ...result, - compilerOutput: result.compiler_output.trim() - ? result.compiler_output - .trim() - .split("\n") - .map((line) => ({ type: "stdout" as const, message: line })) - : [], - compilerError: result.compiler_error.trim() - ? result.compiler_error - .trim() - .split("\n") - .map((line) => ({ type: "error" as const, message: line })) - : [], - programOutput: result.program_output.trim() - ? result.program_output - .trim() - .split("\n") - .map((line) => ({ type: "stdout" as const, message: line })) - : [], - programError: result.program_error.trim() - ? result.program_error - .trim() - .split("\n") - .map((line) => ({ type: "error" as const, message: line })) - : [], + output, }; } diff --git a/app/terminal/wandbox/cpp.ts b/app/terminal/wandbox/cpp.ts index da6f188..ed0d5e2 100644 --- a/app/terminal/wandbox/cpp.ts +++ b/app/terminal/wandbox/cpp.ts @@ -101,33 +101,34 @@ export async function cppRunFiles( ], }); - const traceIndex = result.programError.findIndex( - (line) => line.message === "Stack trace:" + // Find stack trace in the output + const traceIndex = result.output.findIndex( + (line) => line.type === "stderr" && line.message === "Stack trace:" ); - const outputs: ReplOutput[] = [ - ...result.compilerOutput, - ...result.compilerError, - ...result.programOutput, - ...(traceIndex >= 0 - ? result.programError.slice(0, traceIndex) - : result.programError), - ]; + + let outputs: ReplOutput[]; + if (traceIndex >= 0) { // CPP_STACKTRACE_HANDLER のコードで出力されるスタックトレースを、js側でパースしていい感じに表示する + outputs = result.output.slice(0, traceIndex); outputs.push({ type: "trace" as const, message: "Stack trace (filtered):", }); - for (const line of result.programError.slice(traceIndex + 1)) { + + for (const line of result.output.slice(traceIndex + 1)) { // ユーザーのソースコードだけを対象にする - if (line.message.includes("/home/wandbox")) { + if (line.type === "stderr" && line.message.includes("/home/wandbox")) { outputs.push({ type: "trace" as const, message: line.message.replace("/home/wandbox/", ""), }); } } + } else { + outputs = [...result.output]; } + if (result.status !== "0") { outputs.push({ type: "system" as const,