Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 129 additions & 31 deletions app/terminal/wandbox/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<CompileResultWithOutput> {
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: {
Expand All @@ -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,
};
}
25 changes: 13 additions & 12 deletions app/terminal/wandbox/cpp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down