Skip to content

Commit b384ea0

Browse files
committed
wandboxのAPIを使ってC++のコードを実行
1 parent 8a93a9c commit b384ea0

File tree

9 files changed

+766
-23
lines changed

9 files changed

+766
-23
lines changed

app/[docs_id]/markdown.tsx

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import remarkGfm from "remark-gfm";
33
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
44
import { PythonEmbeddedTerminal } from "../terminal/python/embedded";
55
import { Heading } from "./section";
6-
import { EditorComponent } from "../terminal/editor";
7-
import { ExecFile } from "../terminal/exec";
6+
import { AceLang, EditorComponent } from "../terminal/editor";
7+
import { ExecFile, ExecLang } from "../terminal/exec";
88

99
export function StyledMarkdown({ content }: { content: string }) {
1010
return (
@@ -52,21 +52,59 @@ const components: Components = {
5252
hello, world!
5353
---------------------------
5454
*/
55-
return (
56-
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
57-
<ExecFile
58-
language={match[1]}
59-
filename={match[3]}
60-
content={String(props.children || "").replace(/\n$/, "")}
61-
/>
62-
</div>
63-
);
55+
let execLang: ExecLang | null = null;
56+
switch (match[1]) {
57+
case "python":
58+
execLang = "python";
59+
break;
60+
case "cpp":
61+
case "c++":
62+
execLang = "cpp";
63+
break;
64+
default:
65+
console.warn(`Unsupported language for exec: ${match[1]}`);
66+
break;
67+
}
68+
if (execLang) {
69+
return (
70+
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
71+
<ExecFile
72+
language={execLang}
73+
filenames={match[3].split(",")}
74+
content={String(props.children || "").replace(/\n$/, "")}
75+
/>
76+
</div>
77+
);
78+
}
6479
} else if (match[3]) {
6580
// ファイル名指定がある場合、ファイルエディター
81+
let aceLang: AceLang | null = null;
82+
switch (match[1]) {
83+
case "python":
84+
aceLang = "python";
85+
break;
86+
case "cpp":
87+
case "c++":
88+
aceLang = "c_cpp";
89+
break;
90+
case "json":
91+
aceLang = "json";
92+
break;
93+
case "csv":
94+
aceLang = "csv";
95+
break;
96+
case "text":
97+
case "txt":
98+
aceLang = "text";
99+
break;
100+
default:
101+
console.warn(`Unsupported language for editor: ${match[1]}`);
102+
break;
103+
}
66104
return (
67105
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
68106
<EditorComponent
69-
language={match[1]}
107+
language={aceLang}
70108
tabSize={4}
71109
filename={match[3]}
72110
readonly={match[2] === "-readonly"}

app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Sidebar } from "./sidebar";
55
import { ReactNode } from "react";
66
import { PyodideProvider } from "./terminal/python/pyodide";
77
import { FileProvider } from "./terminal/file";
8+
import { WandboxProvider } from "./terminal/wandbox/wandbox";
89

910
export const metadata: Metadata = {
1011
title: "Create Next App",
@@ -22,7 +23,9 @@ export default function RootLayout({
2223
<div className="drawer-content flex flex-col">
2324
<Navbar />
2425
<FileProvider>
25-
<PyodideProvider>{children}</PyodideProvider>
26+
<PyodideProvider>
27+
<WandboxProvider>{children}</WandboxProvider>
28+
</PyodideProvider>
2629
</FileProvider>
2730
</div>
2831
<div className="drawer-side shadow-md">

app/terminal/editor.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import "ace-builds/src-min-noconflict/theme-twilight";
99
import "ace-builds/src-min-noconflict/ext-language_tools";
1010
import "ace-builds/src-min-noconflict/ext-searchbox";
1111
import "ace-builds/src-min-noconflict/mode-python";
12+
import "ace-builds/src-min-noconflict/mode-c_cpp";
1213
import "ace-builds/src-min-noconflict/mode-json";
1314
import "ace-builds/src-min-noconflict/mode-csv";
1415
import "ace-builds/src-min-noconflict/mode-text";
@@ -17,8 +18,11 @@ import { useSectionCode } from "../[docs_id]/section";
1718
import clsx from "clsx";
1819
// snippetを有効化するにはsnippetもimportする必要がある: import "ace-builds/src-min-noconflict/snippets/python";
1920

21+
// mode-xxxx.js のファイル名と、AceEditorの mode プロパティの値が対応する
22+
export type AceLang = "python" | "c_cpp" | "json" | "csv" | "text";
23+
2024
interface EditorProps {
21-
language?: string;
25+
language?: AceLang;
2226
tabSize: number;
2327
filename: string;
2428
initContent: string;

app/terminal/exec.tsx

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@
22

33
import chalk from "chalk";
44
import { usePyodide } from "./python/pyodide";
5-
import { clearTerminal, getRows, useTerminal } from "./terminal";
5+
import { clearTerminal, getRows, hideCursor, useTerminal } from "./terminal";
66
import { useSectionCode } from "../[docs_id]/section";
7+
import { useWandbox } from "./wandbox/wandbox";
8+
9+
export type ExecLang = "python" | "cpp";
710

811
interface ExecProps {
9-
filename: string;
10-
language: string;
12+
/*
13+
* Pythonの場合はメインファイル1つのみを指定する。
14+
* C++の場合はソースコード(.cpp)とヘッダー(.h)を全部指定し、ExecFile内で拡張子を元にソースコードと追加コードを分ける。
15+
*/
16+
filenames: string[];
17+
language: ExecLang;
1118
content: string;
1219
}
1320
export function ExecFile(props: ExecProps) {
1421
const { terminalRef, terminalInstanceRef, termReady } = useTerminal({
1522
getRows: (cols: number) => getRows(props.content, cols) + 1,
1623
onReady: () => {
17-
// カーソル非表示
18-
terminalInstanceRef.current!.write("\x1b[?25l");
24+
hideCursor(terminalInstanceRef.current!);
1925
for (const line of props.content.split("\n")) {
2026
terminalInstanceRef.current!.writeln(line);
2127
}
@@ -24,13 +30,17 @@ export function ExecFile(props: ExecProps) {
2430
const sectionContext = useSectionCode();
2531

2632
const pyodide = usePyodide();
33+
const wandbox = useWandbox();
2734

2835
let commandline: string;
2936
let exec: () => Promise<void> | void;
3037
let runtimeInitializing: boolean;
3138
switch (props.language) {
3239
case "python":
33-
commandline = `python ${props.filename}`;
40+
if (props.filenames.length !== 1) {
41+
throw new Error("Pythonの実行にはファイル名が1つ必要です");
42+
}
43+
commandline = `python ${props.filenames[0]}`;
3444
runtimeInitializing = pyodide.initializing;
3545
exec = async () => {
3646
if (!pyodide.ready) {
@@ -41,8 +51,12 @@ export function ExecFile(props: ExecProps) {
4151
await pyodide.init();
4252
}
4353
clearTerminal(terminalInstanceRef.current!);
44-
const outputs = await pyodide.runFile(props.filename);
45-
for (const output of outputs) {
54+
const outputs = await pyodide.runFile(props.filenames[0]);
55+
for (let i = 0; i < outputs.length; i++) {
56+
const output = outputs[i];
57+
if(i > 0) {
58+
terminalInstanceRef.current!.writeln("");
59+
}
4660
// 出力内容に応じて色を変える
4761
const message = String(output.message).replace(/\n/g, "\r\n");
4862
switch (output.type) {
@@ -54,10 +68,51 @@ export function ExecFile(props: ExecProps) {
5468
break;
5569
}
5670
}
57-
sectionContext?.setExecResult(props.filename, outputs);
71+
sectionContext?.setExecResult(props.filenames[0], outputs);
72+
};
73+
break;
74+
case "cpp":
75+
if (!props.filenames || props.filenames.length === 0) {
76+
throw new Error("C++の実行には filenames プロパティが必要です");
77+
}
78+
commandline = wandbox.cppOptions
79+
? `${wandbox.cppOptions.commandline} ${props.filenames.join(" ")} && ./a.out`
80+
: "";
81+
runtimeInitializing = false;
82+
exec = async () => {
83+
clearTerminal(terminalInstanceRef.current!);
84+
const namesSource = props.filenames!.filter((name) =>
85+
name.endsWith(".cpp")
86+
);
87+
const namesAdditional = props.filenames!.filter(
88+
(name) => !name.endsWith(".cpp")
89+
);
90+
const outputs = await wandbox.runFiles(
91+
"C++",
92+
namesSource,
93+
namesAdditional
94+
);
95+
for (let i = 0; i < outputs.length; i++) {
96+
const output = outputs[i];
97+
if(i > 0) {
98+
terminalInstanceRef.current!.writeln("");
99+
}
100+
// 出力内容に応じて色を変える
101+
const message = String(output.message).replace(/\n/g, "\r\n");
102+
switch (output.type) {
103+
case "error":
104+
terminalInstanceRef.current!.write(chalk.red(message));
105+
break;
106+
default:
107+
terminalInstanceRef.current!.write(message);
108+
break;
109+
}
110+
}
111+
sectionContext?.setExecResult(props.filename!, outputs);
58112
};
59113
break;
60114
default:
115+
props.language satisfies never;
61116
commandline = `エラー: 非対応の言語 ${props.language}`;
62117
runtimeInitializing = false;
63118
exec = () => undefined;

app/terminal/wandbox/page.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use client";
2+
3+
import { EditorComponent } from "../editor";
4+
import { ExecFile } from "../exec";
5+
6+
export default function WandboxPage() {
7+
return (
8+
<div className="p-4 flex flex-col gap-4">
9+
<EditorComponent
10+
language="c_cpp"
11+
tabSize={4}
12+
filename="main.cpp"
13+
initContent=""
14+
/>
15+
<EditorComponent
16+
language="c_cpp"
17+
tabSize={4}
18+
filename="sub.h"
19+
initContent=""
20+
/>
21+
<EditorComponent
22+
language="c_cpp"
23+
tabSize={4}
24+
filename="sub.cpp"
25+
initContent=""
26+
/>
27+
<ExecFile
28+
filenames={["main.cpp", "sub.cpp", "sub.h"]}
29+
language="cpp"
30+
content=""
31+
/>
32+
</div>
33+
);
34+
}

0 commit comments

Comments
 (0)