Skip to content

Commit 4120fae

Browse files
Copilotna-trium-144
andcommitted
Refactor wandbox API to use CompileOutputEvent and stream-based processing
Co-authored-by: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
1 parent 6d0e554 commit 4120fae

3 files changed

Lines changed: 115 additions & 108 deletions

File tree

app/terminal/wandbox/api.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ export interface CompileNdjsonResult {
7070
data: string;
7171
}
7272

73+
/**
74+
* Output event with original NDJSON type and converted ReplOutput
75+
*/
76+
export interface CompileOutputEvent {
77+
ndjsonType: string;
78+
output: ReplOutput;
79+
}
80+
7381
export interface CompileResult {
7482
status: string;
7583
signal: string;
@@ -105,36 +113,48 @@ interface CompileProps {
105113

106114
export async function compileAndRun(
107115
options: CompileProps,
108-
onOutput: (output: ReplOutput) => void
116+
onOutput: (event: CompileOutputEvent) => void
109117
): Promise<CompileResult> {
110118
// Helper function to process NDJSON result and call onOutput
111119
const processNdjsonResult = (r: CompileNdjsonResult) => {
112120
switch (r.type) {
113121
case "CompilerMessageS":
114122
if (r.data.trim()) {
115123
for (const line of r.data.trim().split("\n")) {
116-
onOutput({ type: "stdout", message: line });
124+
onOutput({
125+
ndjsonType: r.type,
126+
output: { type: "stdout", message: line }
127+
});
117128
}
118129
}
119130
break;
120131
case "CompilerMessageE":
121132
if (r.data.trim()) {
122133
for (const line of r.data.trim().split("\n")) {
123-
onOutput({ type: "error", message: line });
134+
onOutput({
135+
ndjsonType: r.type,
136+
output: { type: "error", message: line }
137+
});
124138
}
125139
}
126140
break;
127141
case "StdOut":
128142
if (r.data.trim()) {
129143
for (const line of r.data.trim().split("\n")) {
130-
onOutput({ type: "stdout", message: line });
144+
onOutput({
145+
ndjsonType: r.type,
146+
output: { type: "stdout", message: line }
147+
});
131148
}
132149
}
133150
break;
134151
case "StdErr":
135152
if (r.data.trim()) {
136153
for (const line of r.data.trim().split("\n")) {
137-
onOutput({ type: "stderr", message: line });
154+
onOutput({
155+
ndjsonType: r.type,
156+
output: { type: "stderr", message: line }
157+
});
138158
}
139159
}
140160
break;

app/terminal/wandbox/cpp.ts

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,10 @@ export async function cppRunFiles(
8787
filenames: string[],
8888
onOutput: (output: ReplOutput) => void
8989
): Promise<string> {
90-
const outputs: ReplOutput[] = [];
91-
const captureOutput = (output: ReplOutput) => {
92-
outputs.push(output);
93-
onOutput(output);
94-
};
90+
// Track state for processing stack traces
91+
let inStackTrace = false;
92+
let foundSignal = false;
93+
const bufferedStderrForTrace: ReplOutput[] = [];
9594

9695
const result = await compileAndRun({
9796
...options,
@@ -101,56 +100,51 @@ export async function cppRunFiles(
101100
"_stacktrace.cpp",
102101
],
103102
codes: { ...files, "_stacktrace.cpp": _stacktrace_cpp },
104-
}, captureOutput);
105-
106-
// Find stack trace in the output
107-
const signalIndex = outputs.findIndex(
108-
(line) =>
109-
line.type === "stderr" && line.message.startsWith("#!my_code_signal:")
110-
);
111-
const traceIndex = outputs.findIndex(
112-
(line) => line.type === "stderr" && line.message === "#!my_code_stacktrace:"
113-
);
114-
115-
if (signalIndex >= 0) {
116-
outputs[signalIndex] = {
117-
type: "error",
118-
message: outputs[signalIndex].message.slice(17),
119-
} as const;
120-
}
121-
if (traceIndex >= 0) {
122-
// _stacktrace.cpp のコードで出力されるスタックトレースを、js側でパースしていい感じに表示する
123-
const trace = outputs.slice(traceIndex + 1);
124-
const traceOutputs: ReplOutput[] = [{
125-
type: "trace",
126-
message: "Stack trace (filtered):",
127-
}];
128-
129-
for (const line of trace) {
130-
if(line.type === "stderr"){
131-
// ユーザーのソースコードだけを対象にする
132-
if (line.message.includes("/home/wandbox")) {
133-
traceOutputs.push({
134-
type: "trace",
135-
message: line.message.replace("/home/wandbox/", ""),
136-
});
137-
}
138-
}
103+
}, (event) => {
104+
const { ndjsonType, output } = event;
105+
106+
// Check for signal marker in stderr
107+
if (ndjsonType === "StdErr" && output.message.startsWith("#!my_code_signal:")) {
108+
foundSignal = true;
109+
onOutput({
110+
type: "error",
111+
message: output.message.slice(17),
112+
});
113+
return;
139114
}
140115

141-
// Output trace messages
142-
for (const traceOutput of traceOutputs) {
143-
onOutput(traceOutput);
116+
// Check for stack trace marker
117+
if (ndjsonType === "StdErr" && output.message === "#!my_code_stacktrace:") {
118+
inStackTrace = true;
119+
onOutput({
120+
type: "trace",
121+
message: "Stack trace (filtered):",
122+
});
123+
return;
144124
}
145-
}
125+
126+
// Process stack trace lines
127+
if (inStackTrace && ndjsonType === "StdErr") {
128+
// Filter to show only user source code
129+
if (output.message.includes("/home/wandbox")) {
130+
onOutput({
131+
type: "trace",
132+
message: output.message.replace("/home/wandbox/", ""),
133+
});
134+
}
135+
return;
136+
}
137+
138+
// Output normally
139+
onOutput(output);
140+
});
146141

147142
if (result.status !== "0") {
148143
onOutput({
149144
type: "system",
150145
message: `ステータス ${result.status} で異常終了しました`,
151146
});
152147
}
153-
// TODO: result.signal はいつ使われるのか?
154148

155149
return result.status;
156150
}

app/terminal/wandbox/rust.ts

Lines changed: 51 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,10 @@ export async function rustRunFiles(
3333
filenames: string[],
3434
onOutput: (output: ReplOutput) => void
3535
): Promise<string> {
36-
const outputs: ReplOutput[] = [];
37-
const captureOutput = (output: ReplOutput) => {
38-
outputs.push(output);
39-
onOutput(output);
40-
};
36+
// Track state for processing panic traces
37+
let inPanicHook = false;
38+
let foundBacktraceHeader = false;
39+
const traceLines: string[] = [];
4140

4241
const mainModule = filenames[0].replace(/\.rs$/, "");
4342
const result = await compileAndRun({
@@ -57,65 +56,59 @@ export async function rustRunFiles(
5756
?.replace(/(?:pub\s+)?(fn\s+main\s*\()/g, "pub $1")
5857
.replaceAll(/mod\s+(\w+)\s*;/g, "use super::$1;"),
5958
},
60-
}, captureOutput);
61-
62-
// Find stack trace in the output
63-
const panicIndex = outputs.findIndex(
64-
(line) => line.type === "stderr" && line.message === "#!my_code_panic_hook:"
65-
);
66-
67-
if (panicIndex >= 0) {
68-
const traceIndex =
69-
panicIndex +
70-
outputs
71-
.slice(panicIndex)
72-
.findLastIndex(
73-
(line) =>
74-
line.type === "stderr" && line.message === "stack backtrace:"
75-
);
76-
const traceOutputs: ReplOutput[] = [
77-
{
78-
type: "trace",
79-
message: "Stack trace (filtered):",
80-
},
81-
];
82-
for (const line of outputs.slice(panicIndex + 1, traceIndex)) {
83-
if (line.type === "stderr") {
59+
}, (event) => {
60+
const { ndjsonType, output } = event;
61+
62+
// Check for panic hook marker
63+
if (ndjsonType === "StdErr" && output.message === "#!my_code_panic_hook:") {
64+
inPanicHook = true;
65+
return;
66+
}
67+
68+
if (inPanicHook && ndjsonType === "StdErr") {
69+
// Check for stack backtrace header
70+
if (output.message === "stack backtrace:") {
71+
foundBacktraceHeader = true;
8472
onOutput({
85-
type: "error",
86-
message: line.message,
73+
type: "trace",
74+
message: "Stack trace (filtered):",
8775
});
76+
return;
8877
}
89-
}
90-
for (let i = traceIndex + 1; i < outputs.length; i++) {
91-
const line = outputs.at(i)!;
92-
const nextLine = outputs.at(i + 1);
93-
if (line.type === "stderr") {
94-
// ユーザーのソースコードだけを対象にする
95-
if (
96-
/^\s*\d+:/.test(line.message) &&
97-
nextLine &&
98-
/^\s*at .\//.test(nextLine.message) &&
99-
!/^\s*at .\/prog.rs/.test(nextLine.message)
100-
) {
101-
traceOutputs.push({
102-
type: "trace",
103-
message: line.message.replace("prog::", ""),
104-
});
105-
traceOutputs.push({
106-
type: "trace",
107-
message: nextLine.message,
108-
});
109-
i++; // skip next line
78+
79+
if (foundBacktraceHeader) {
80+
// Process stack trace lines
81+
// Look for pattern: " N: ..." followed by " at ./file.rs:line"
82+
if (/^\s*\d+:/.test(output.message)) {
83+
traceLines.push(output.message);
84+
} else if (/^\s*at .\//.test(output.message) && traceLines.length > 0) {
85+
// Check if this is user code (not prog.rs)
86+
if (!/^\s*at .\/prog.rs/.test(output.message)) {
87+
onOutput({
88+
type: "trace",
89+
message: traceLines[traceLines.length - 1].replace("prog::", ""),
90+
});
91+
onOutput({
92+
type: "trace",
93+
message: output.message,
94+
});
95+
}
96+
traceLines.pop(); // Remove the processed line
11097
}
98+
return;
11199
}
100+
101+
// Output panic messages as errors
102+
onOutput({
103+
type: "error",
104+
message: output.message,
105+
});
106+
return;
112107
}
113-
114-
// Output trace messages
115-
for (const traceOutput of traceOutputs) {
116-
onOutput(traceOutput);
117-
}
118-
}
108+
109+
// Output normally
110+
onOutput(output);
111+
});
119112

120113
if (result.status !== "0") {
121114
onOutput({

0 commit comments

Comments
 (0)