Skip to content

Commit 02e986a

Browse files
committed
wandbox周りのリファクタリング
1 parent 2e8db89 commit 02e986a

File tree

4 files changed

+385
-342
lines changed

4 files changed

+385
-342
lines changed

app/terminal/exec.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export type ExecLang = "python" | "cpp";
1818
interface ExecProps {
1919
/*
2020
* Pythonの場合はメインファイル1つのみを指定する。
21-
* C++の場合はソースコード(.cpp)とヘッダー(.h)を全部指定し、ExecFile内で拡張子を元にソースコードと追加コードを分ける
21+
* C++の場合はソースコード(.cpp)とヘッダー(.h)を全部指定し、cppRunFiles()内で拡張子を元にソースコードと追加コードが分けられる
2222
*/
2323
filenames: string[];
2424
language: ExecLang;
@@ -63,17 +63,9 @@ export function ExecFile(props: ExecProps) {
6363
if (!props.filenames || props.filenames.length === 0) {
6464
throw new Error("C++の実行には filenames プロパティが必要です");
6565
}
66-
commandline = wandbox.cppOptions
67-
? `${wandbox.cppOptions.commandline} ${props.filenames.join(" ")} && ./a.out`
68-
: "";
66+
commandline = wandbox.getCommandlineStr("C++", props.filenames);
6967
runtimeInitializing = false;
70-
const namesSource = props.filenames!.filter((name) =>
71-
name.endsWith(".cpp")
72-
);
73-
const namesAdditional = props.filenames!.filter(
74-
(name) => !name.endsWith(".cpp")
75-
);
76-
exec = () => wandbox.runFiles("C++", namesSource, namesAdditional);
68+
exec = () => wandbox.runFiles("C++", props.filenames);
7769
break;
7870
default:
7971
props.language satisfies never;

app/terminal/wandbox/api.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { type Fetcher } from "swr";
2+
import { type ReplOutput } from "../repl";
3+
4+
const WANDBOX = "https://wandbox.org";
5+
// https://github.com/melpon/wandbox/blob/ajax/kennel2/API.rst <- 古いけど、説明と例がある
6+
// https://github.com/melpon/wandbox/blob/master/feline/src/types.rs
7+
/* RustのVec<u8>はバイト配列ですが、serialize_with = "serialize_utf8"という指定があるため、
8+
JSONにシリアライズされる際にはUTF-8文字列に変換されると解釈し、TypeScriptの型はstringとします。
9+
by gemini
10+
*/
11+
export interface SwitchOption {
12+
name: string;
13+
"display-flags": string;
14+
"display-name": string;
15+
}
16+
export interface SwitchSingle {
17+
type: "single";
18+
name: string;
19+
"display-name": string;
20+
"display-flags": string;
21+
default: boolean;
22+
}
23+
export interface SwitchSelect {
24+
type: "select";
25+
name: string;
26+
options: SwitchOption[];
27+
default: string;
28+
}
29+
/**
30+
* Rustの 'Switch' enum に対応するディスクリミネேテッドユニオン型です。
31+
* 'type' プロパティの値によって `SwitchSingle` か `SwitchSelect` かを判別できます。
32+
*/
33+
export type Switch = SwitchSingle | SwitchSelect;
34+
export interface CompilerInfo {
35+
name: string;
36+
version: string;
37+
language: string;
38+
"display-name": string;
39+
templates: string[];
40+
"compiler-option-raw": boolean;
41+
"runtime-option-raw": boolean;
42+
"display-compile-command": string;
43+
switches: Switch[];
44+
}
45+
interface Code {
46+
file: string;
47+
code: string;
48+
}
49+
export interface CompileParameter {
50+
compiler: string;
51+
code: string;
52+
codes?: Code[];
53+
options?: string;
54+
stdin?: string;
55+
"compiler-option-raw"?: string;
56+
"runtime-option-raw"?: string;
57+
github_user?: string;
58+
title?: string;
59+
description?: string;
60+
save?: boolean;
61+
created_at?: number;
62+
is_private?: boolean;
63+
"compiler-info"?: CompilerInfo;
64+
}
65+
export interface CompileResult {
66+
status: string;
67+
signal: string;
68+
compiler_output: string;
69+
compiler_error: string;
70+
compiler_message: string;
71+
program_output: string;
72+
program_error: string;
73+
program_message: string;
74+
permlink: string;
75+
url: string;
76+
}
77+
78+
export const compilerInfoFetcher: Fetcher<CompilerInfo[]> = () =>
79+
fetch(new URL("/api/list.json", WANDBOX)).then(
80+
(res) => res.json() as Promise<CompilerInfo[]>
81+
);
82+
83+
interface CompileProps {
84+
compilerName: string;
85+
compilerOptions: string[];
86+
compilerOptionsRaw: string[];
87+
codes: Code[];
88+
}
89+
export interface CompileResultWithOutput extends CompileResult {
90+
compilerOutput: ReplOutput[];
91+
compilerError: ReplOutput[];
92+
programOutput: ReplOutput[];
93+
programError: ReplOutput[];
94+
}
95+
96+
export async function compileAndRun(
97+
options: CompileProps
98+
): Promise<CompileResultWithOutput> {
99+
const result: CompileResult = await fetch(
100+
new URL("/api/compile.json", WANDBOX),
101+
{
102+
method: "post",
103+
headers: {
104+
"Content-Type": "application/json",
105+
},
106+
body: JSON.stringify({
107+
compiler: options.compilerName,
108+
code: "",
109+
codes: options.codes,
110+
options: options.compilerOptions.join(","),
111+
stdin: "",
112+
"compiler-option-raw": options.compilerOptionsRaw.join("\n"),
113+
"runtime-option-raw": "",
114+
save: false,
115+
is_private: true,
116+
} satisfies CompileParameter),
117+
}
118+
).then((res) => res.json());
119+
return {
120+
...result,
121+
compilerOutput: result.compiler_output.trim()
122+
? result.compiler_output
123+
.trim()
124+
.split("\n")
125+
.map((line) => ({ type: "stdout" as const, message: line }))
126+
: [],
127+
compilerError: result.compiler_error.trim()
128+
? result.compiler_error
129+
.trim()
130+
.split("\n")
131+
.map((line) => ({ type: "error" as const, message: line }))
132+
: [],
133+
programOutput: result.program_output.trim()
134+
? result.program_output
135+
.trim()
136+
.split("\n")
137+
.map((line) => ({ type: "stdout" as const, message: line }))
138+
: [],
139+
programError: result.program_error.trim()
140+
? result.program_error
141+
.trim()
142+
.split("\n")
143+
.map((line) => ({ type: "error" as const, message: line }))
144+
: [],
145+
};
146+
}

app/terminal/wandbox/cpp.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import { ReplOutput } from "../repl";
2+
import { compileAndRun, CompilerInfo } from "./api";
3+
4+
const CPP_STACKTRACE_HANDLER = `
5+
#define BOOST_STACKTRACE_USE_ADDR2LINE
6+
#include <boost/stacktrace.hpp>
7+
#include <iostream>
8+
#include <signal.h>
9+
void signal_handler(int signum) {
10+
signal(signum, SIG_DFL);
11+
switch(signum) {
12+
case SIGILL:
13+
std::cerr << "Illegal instruction" << std::endl;
14+
break;
15+
case SIGABRT:
16+
std::cerr << "Aborted" << std::endl;
17+
break;
18+
case SIGBUS:
19+
std::cerr << "Bus error" << std::endl;
20+
break;
21+
case SIGFPE:
22+
std::cerr << "Floating point exception" << std::endl;
23+
break;
24+
case SIGSEGV:
25+
std::cerr << "Segmentation fault" << std::endl;
26+
break;
27+
default:
28+
std::cerr << "Signal " << signum << " received" << std::endl;
29+
break;
30+
}
31+
std::cerr << "Stack trace:" << std::endl;
32+
std::cerr << boost::stacktrace::stacktrace();
33+
raise(signum);
34+
}
35+
struct _init_signal_handler {
36+
_init_signal_handler() {
37+
signal(SIGILL, signal_handler);
38+
signal(SIGABRT, signal_handler);
39+
signal(SIGBUS, signal_handler);
40+
signal(SIGFPE, signal_handler);
41+
signal(SIGSEGV, signal_handler);
42+
}
43+
} _init_signal_handler_instance;
44+
`;
45+
46+
interface CppOptions {
47+
compilerName: string;
48+
compilerOptions: string[];
49+
compilerOptionsRaw: string[];
50+
commandline: string[]; // 表示用
51+
}
52+
53+
// 使用可能なコンパイラーの情報からコンパイルオプションを決定する
54+
// コマンドライン文字列表示用と実行時用で処理を共通化するため
55+
function cppOptions(compilerList: CompilerInfo[]): CppOptions {
56+
const compilerListForLang = compilerList.filter((c) => c.language === "C++");
57+
// headでない最初のgccを使う
58+
const selectedCompiler = compilerListForLang.find(
59+
(c) => c.name.includes("gcc") && !c.name.includes("head")
60+
);
61+
if (!selectedCompiler) {
62+
throw new Error("compiler not found");
63+
}
64+
65+
const options: CppOptions = {
66+
compilerName: selectedCompiler.name,
67+
compilerOptions: [],
68+
commandline: ["g++"], // selectedCompiler["display-compile-command"]
69+
compilerOptionsRaw: [],
70+
};
71+
72+
// singleオプション:
73+
const warningSwitch = selectedCompiler.switches.find(
74+
(s) => s.name === "warning"
75+
);
76+
if (warningSwitch && warningSwitch.type === "single") {
77+
options.compilerOptions.push("warning");
78+
options.commandline.push(warningSwitch["display-flags"]);
79+
} else {
80+
console.warn("warning switch not found");
81+
}
82+
83+
// selectオプション:
84+
for (const switchSelect of selectedCompiler.switches.filter(
85+
(s) => s.type === "select"
86+
)) {
87+
// boost最新、stdは最新を選ぶ ほかはデフォルト
88+
if (switchSelect.name.includes("boost")) {
89+
const boostLatestOption = switchSelect.options
90+
.filter((o) => !o.name.includes("nothing"))
91+
.sort()
92+
.reverse()[0];
93+
if (boostLatestOption) {
94+
options.compilerOptions.push(boostLatestOption.name);
95+
// options.commandline.push(boostLatestOption["display-flags"]);
96+
} else {
97+
console.warn("boost option not found");
98+
}
99+
} else if (switchSelect.name.includes("std")) {
100+
const stdLatestOption = switchSelect.options
101+
.filter((o) => o.name.startsWith("c++"))
102+
.sort()
103+
.reverse()[0];
104+
if (stdLatestOption) {
105+
options.compilerOptions.push(stdLatestOption.name);
106+
options.commandline.push(stdLatestOption["display-flags"]);
107+
} else {
108+
console.warn("std option not found");
109+
}
110+
} else {
111+
const defaultOption = switchSelect.options.find(
112+
(o) => o.name === switchSelect.default
113+
);
114+
options.compilerOptions.push(switchSelect.default);
115+
options.commandline.push(defaultOption!["display-flags"]);
116+
}
117+
}
118+
119+
// その他オプション
120+
options.compilerOptionsRaw.push("-g");
121+
// options.commandline.push("-g");
122+
123+
return options;
124+
}
125+
126+
export function getCppCommandlineStr(
127+
compilerList: CompilerInfo[],
128+
filenames: string[]
129+
) {
130+
const namesSource = filenames.filter((name) => name.endsWith(".cpp"));
131+
return [
132+
...cppOptions(compilerList).commandline,
133+
...namesSource,
134+
"&&",
135+
"./a.out",
136+
].join(" ");
137+
}
138+
139+
export async function cppRunFiles(
140+
compilerList: CompilerInfo[],
141+
files: Record<string, string | undefined>,
142+
filenames: string[]
143+
) {
144+
const options = cppOptions(compilerList);
145+
const namesSource = filenames.filter((name) => name.endsWith(".cpp"));
146+
const namesAdditional = filenames.filter((name) => !name.endsWith(".cpp"));
147+
const result = await compileAndRun({
148+
compilerName: options.compilerName,
149+
compilerOptions: options.compilerOptions,
150+
compilerOptionsRaw: [
151+
...options.compilerOptionsRaw,
152+
...namesSource,
153+
"_stacktrace.cpp",
154+
],
155+
codes: [
156+
...namesSource.map((name) => ({
157+
file: name,
158+
code: files[name] || "",
159+
})),
160+
...namesAdditional.map((name) => ({
161+
file: name,
162+
code: files[name] || "",
163+
})),
164+
{ file: "_stacktrace.cpp", code: CPP_STACKTRACE_HANDLER },
165+
],
166+
});
167+
168+
const traceIndex = result.programError.findIndex(
169+
(line) => line.message === "Stack trace:"
170+
);
171+
const outputs: ReplOutput[] = [
172+
...result.compilerOutput,
173+
...result.compilerError,
174+
...result.programOutput,
175+
...(traceIndex >= 0
176+
? result.programError.slice(0, traceIndex)
177+
: result.programError),
178+
];
179+
if (traceIndex >= 0) {
180+
// CPP_STACKTRACE_HANDLER のコードで出力されるスタックトレースを、js側でパースしていい感じに表示する
181+
outputs.push({
182+
type: "trace" as const,
183+
message: "Stack trace (filtered):",
184+
});
185+
for (const line of result.programError.slice(traceIndex + 1)) {
186+
// ユーザーのソースコードだけを対象にする
187+
if (line.message.includes("/home/wandbox")) {
188+
outputs.push({
189+
type: "trace" as const,
190+
message: line.message.replace("/home/wandbox/", ""),
191+
});
192+
}
193+
}
194+
}
195+
if (result.status !== "0") {
196+
outputs.push({
197+
type: "system" as const,
198+
message: `ステータス ${result.status} で異常終了しました`,
199+
});
200+
}
201+
// TODO: result.signal はいつ使われるのか?
202+
203+
return outputs;
204+
}

0 commit comments

Comments
 (0)