Skip to content

Commit bc837f3

Browse files
committed
ドキュメントをtsdocに移動、useRuntimeAllを追加しテストを修正
1 parent 109eaf0 commit bc837f3

File tree

7 files changed

+205
-217
lines changed

7 files changed

+205
-217
lines changed

app/terminal/page.tsx

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@
22

33
import { Heading } from "@/[lang]/[pageId]/markdown";
44
import "mocha/mocha.css";
5-
import { Fragment, useEffect, useRef, useState } from "react";
6-
import { useWandbox } from "@my-code/runtime/wandbox/runtime";
7-
import { RuntimeContext } from "@my-code/runtime/interface";
5+
import { Fragment, useEffect, useState } from "react";
86
import { langConstants, RuntimeLang } from "@my-code/runtime/languages";
9-
import { usePyodide } from "@my-code/runtime/worker/pyodide";
10-
import { useRuby } from "@my-code/runtime/worker/ruby";
11-
import { useJSEval } from "@my-code/runtime/worker/jsEval";
127
import { ReplTerminal } from "./repl";
138
import { EditorComponent } from "./editor";
149
import { ExecFile } from "./exec";
15-
import { useTypeScript } from "@my-code/runtime/typescript/runtime";
1610
import { useTerminal } from "./terminal";
1711

1812
import main_py from "@my-code/runtime/samples/main.py?raw";
@@ -30,6 +24,7 @@ import {
3024
} from "@my-code/runtime/tests/utils";
3125
import { replTests } from "@my-code/runtime/tests/repl";
3226
import { fileExecutionTests } from "@my-code/runtime/tests/fileExecution";
27+
import { useRuntimeAll } from "@my-code/runtime/context";
3328

3429
export default function RuntimeTestPage() {
3530
return (
@@ -206,21 +201,7 @@ function AnsiColorSample() {
206201
}
207202

208203
function MochaTest() {
209-
const pyodide = usePyodide();
210-
const ruby = useRuby();
211-
const jsEval = useJSEval();
212-
const typescript = useTypeScript(jsEval);
213-
const wandboxCpp = useWandbox("cpp");
214-
const wandboxRust = useWandbox("rust");
215-
const runtimeRef = useRef<Record<RuntimeLang, RuntimeContext>>(null!);
216-
runtimeRef.current = {
217-
python: pyodide,
218-
ruby: ruby,
219-
javascript: jsEval,
220-
typescript: typescript,
221-
cpp: wandboxCpp,
222-
rust: wandboxRust,
223-
};
204+
const runtimeRef = useRuntimeAll();
224205

225206
const [searchParams, setSearchParams] = useState<string>("");
226207
useEffect(() => {

app/terminal/terminal.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useEffect, useRef, useState } from "react";
3+
import { RefObject, useEffect, useRef, useState } from "react";
44
import type { Terminal } from "@xterm/xterm";
55
import type { FitAddon } from "@xterm/addon-fit";
66
import "@xterm/xterm/css/xterm.css";
@@ -85,10 +85,33 @@ function computeTerminalTheme() {
8585
}
8686

8787
interface TerminalProps {
88+
/**
89+
* ターミナルの幅がcolsの場合の高さの最小値を指定します。
90+
* 未指定または5未満の場合5になります。
91+
* 内部でuseRefを使用しターミナル初期化完了の瞬間のgetRows関数インスタンスが呼び出されるので、一時オブジェクトでも大丈夫
92+
*/
8893
getRows?: (cols: number) => number;
94+
/**
95+
* ターミナルが初期化された際に呼び出されます。
96+
* 内部でuseRefを使用しターミナル初期化完了の瞬間のonReady関数インスタンスが呼び出されるので、一時オブジェクトでも大丈夫
97+
*/
8998
onReady?: () => void;
9099
}
91-
export function useTerminal(props: TerminalProps) {
100+
interface TerminalContext {
101+
/**
102+
* ターミナルを描画するためのdiv要素にこのrefを渡してください。
103+
*/
104+
terminalRef: RefObject<HTMLDivElement>;
105+
/**
106+
* xterm.jsのTerminalインスタンスへのref
107+
*/
108+
terminalInstanceRef: RefObject<Terminal | null>;
109+
/**
110+
* ターミナルが初期化されたかどうかを示します。
111+
*/
112+
termReady: boolean;
113+
}
114+
export function useTerminal(props: TerminalProps): TerminalContext {
92115
const terminalRef = useRef<HTMLDivElement>(null!);
93116
const terminalInstanceRef = useRef<Terminal | null>(null);
94117
const fitAddonRef = useRef<FitAddon | null>(null);
@@ -110,7 +133,7 @@ export function useTerminal(props: TerminalProps) {
110133
const rows = Math.max(5, getRowsRef.current?.(dims.cols) ?? 0);
111134
terminalInstanceRef.current?.resize(dims.cols, rows);
112135
}
113-
}
136+
};
114137
/*
115138
globals.cssでフォントを指定し読み込んでいるが、
116139
それが読み込まれる前にterminalを初期化してしまうとバグるので、

packages/runtime/README.md

Lines changed: 28 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,28 @@
11
# my.code(); Runtime API
22

3-
43
## RuntimeContext (interface.ts)
54

6-
各言語のランタイムはRuntimeContextインターフェースの実装を返すフックを実装する必要があります。
7-
8-
context.tsx の `useRuntime(lang)` は各言語のフックを呼び出し、その中で指定された言語のランタイムを返します。
9-
10-
関数はすべてuseCallbackやuseMemoなどを用いレンダリングごとに同じインスタンスを返すように実装してください。
11-
12-
### 共通
13-
14-
* init?: `() => void`
15-
* useRuntime() 内のuseEffectなどで呼び出されます。ランタイムを使う側では通常呼び出す必要はないです。
16-
* ランタイムの初期化にコストがかかるものは、init()で初期化フラグがトリガーされたときだけ初期化するようにします。
17-
* useRuntime() が複数回使われた場合はinitも複数回呼ばれます。
18-
* init()はフラグを立てるだけにし、完了する前にreturnしてよいです。初期化とcleanupはuseEffect()で非同期に行うのがよいと思います。
19-
* ready: `boolean`
20-
* ランタイムの初期化が完了したか、不要である場合true
21-
* mutex?: `MutexInterface`
22-
* ランタイムに排他制御が必要な場合、MutexInterfaceのインスタンスを返してください。
23-
* interrupt?: `() => void`
24-
* 実行中のコマンドを中断します。
25-
* 呼び出し側でmutexのロックはしません。interrupt()を呼ぶ際にはrunCommand()やrunFiles()が実行中であるためmutexはすでにロックされているはずです。
26-
* interrupt()内で実行中の処理のPromiseをrejectしたあと、runtimeを再開する際の処理に必要であればmutexをロックすることも可能です。
27-
28-
### REPL用
29-
30-
* runCommand?: `(command: string) => Promise<ReplOutput[]>`
31-
* コマンドを実行します。実行結果をReplOutputの配列で返します。
32-
* runCommandを呼び出す際には呼び出し側 (主に repl.tsx) でmutexをロックします。複数のコマンドを連続実行したい場合があるからです。
33-
* checkSyntax?: `(code: string) => Promise<SyntaxStatus>`
34-
* コードの構文チェックを行います。行がコマンドとして完結していれば`complete`、次の行に続く場合(if文の条件式の途中など)は`incomplete`を返してください。
35-
* REPLでEnterを押した際の動作に影響します。
36-
* 呼び出し側でmutexのロックはせず、必要であればcheckSyntax()内でロックします。
37-
* splitReplExamples?: `(code: string) => ReplCommands[]`
38-
* markdown内に記述されているREPLのサンプルコードをパースします。例えば
39-
```
40-
>>> if True:
41-
... print("Hello")
42-
Hello
43-
```
44-
をsplitReplExamplesに通すと
45-
```ts
46-
[
47-
{
48-
command: 'if True:\n print("Hello")',
49-
output: {
50-
type: 'output',
51-
content: 'Hello'
52-
}
53-
}
54-
]
55-
```
56-
が返されるようにします。
57-
58-
### ファイル実行用
59-
60-
* runFiles: `(filenames: string[], files: Record<string, string>) => Promise<ReplOutput[]>`
61-
* 指定されたファイルを実行します。
62-
* EmbedContextから取得したfilesを呼び出し側で引数に渡します
63-
* 呼び出し側でmutexのロックはせず、必要であればrunFiles()内でロックします。
64-
* getCommandlineStr: `(filenames: string[]) => string`
65-
* 指定されたファイルを実行するためのコマンドライン引数文字列を返します。表示用です。
66-
67-
## languages.ts
68-
69-
### LangConstant
70-
71-
言語ごとに固定の定数です。
72-
73-
* tabSize: `number`
74-
* REPLおよびコードエディターののタブ幅を指定します。1以上
75-
* prompt?: `string`
76-
* REPLの1行目のプロンプト文字列を指定します。
77-
* promptMore?: `string`
78-
* REPLの2行目以降のプロンプト文字列を指定します。省略時はpromptが使われます
79-
80-
## embedContext.tsx
81-
82-
Replの実行結果(`replOutputs`)、ユーザーが編集したファイル(`files`)、ファイルの実行結果(`execResults`)の情報を保持します。
83-
84-
## terminal.tsx
85-
86-
xterm.jsを制御する useTerminal() フックを提供します。
87-
リサイズやテーマ切り替えなどの処理を行います。
88-
89-
引数:
90-
* getRows?: `(cols: number) => number`
91-
* ターミナルの幅がcolsの場合の高さの最小値を指定します。
92-
* 未指定または5未満の場合5になります。
93-
* 内部でuseRefを使用しターミナル初期化完了の瞬間のgetRows関数インスタンスが呼び出されるので、一時オブジェクトでも大丈夫
94-
* onReady?: `() => void`
95-
* ターミナルが初期化された際に呼び出されます。
96-
* 内部でuseRefを使用しターミナル初期化完了の瞬間のonReady関数インスタンスが呼び出されるので、一時オブジェクトでも大丈夫
97-
98-
返り値:
99-
* terminalRef: `RefObject<HTMLDivElement>`
100-
* ターミナルを描画するためのdiv要素にこのrefを渡してください。
101-
* terminalInstanceRef: `RefObject<Terminal | null>`
102-
* xterm.jsのTerminalインスタンスへのrefです。
103-
* termReady: `boolean`
104-
* ターミナルが初期化されたかどうかを示します。
5+
各言語の実行環境は`RuntimeContext`インターフェースの実装を返すフックを実装する必要があります。
1056

106-
## repl.tsx
7+
## context.tsx
1078

108-
ReplTerminal コンポーネントを提供します。
109-
useRuntimeとuseTerminalを呼び出し、REPLの入出力、キーハンドリング処理を行います。
9+
* RuntimeProvider がすべての言語の実行環境のコンテキストを管理します。
10+
* useRuntime() で使用したい言語のコンテキストを取得します。
11+
* useRuntimeAll() ですべての言語のコンテキストを取得します。ただしinit()は自動的に呼び出されません
11012

111-
また、実行したコマンド結果はEmbedContextに送信されます。
112-
113-
シンタックスハイライトはprism.jsでパースしたものを独自処理で色付けしています。(highlight.ts 内の highlightCodeToAnsi 関数)
114-
パース処理の実装は不要ですがhighlight.tsに言語定義のインポートとswitch文分岐の追加が必要です。
115-
116-
## editor.tsx
117-
118-
EditorComponent コンポーネントを提供します。
119-
120-
ファイルの内容はEmbedContextと同期されます。
121-
122-
## exec.tsx
123-
124-
実行ボタンと結果を表示する ExecFile コンポーネントを提供します。
125-
126-
実行結果はEmbedContextに送信されます。
13+
## languages.ts
12714

128-
## page.tsx, tests.ts
15+
* markdownで指定される可能性のある言語名をすべて列挙します。
16+
* ReactAce, ReactSyntaxHighlighter, Prism.js における言語名との対応関係を定義します。
17+
* ReactAceで利用可能な言語の場合tab幅も指定します。
18+
* REPL実行環境が利用可能な言語の場合プロンプト文字列などのパラメータを指定します。
12919

130-
ブラウザーで localhost:3000/terminal を開くと、各実行環境のテストを行います。
20+
## tests
13121

132-
ランタイムを追加した場合、ここにテストケースを追加してください。
22+
* `npm run test -w packages/runtime` でvitestを使ってテストを実行できます
23+
* my.code(); の /terminal ページでMochaを使ってテストを実行できます
24+
* どちらからでも実行できるようテスト本体は ./tests/repl.ts, ./tests/fileExecution.ts に記述しています。
25+
新しい言語の実行環境を追加した場合、ここにテストケースを追加してください。
13326

13427
## 各言語の実装
13528

@@ -138,24 +31,26 @@ EditorComponent コンポーネントを提供します。
13831
web worker でコードを実行する実装です。
13932
workerとの通信部分は言語によらず共通なので、それをworker/runtime.tsxで定義しています。
14033

141-
Pythonの実行環境にはPyodideを使用しています。
142-
PyodideにはKeyboardInterruptを送信する機能があるのでinterrupt()でそれを利用しています。
143-
144-
Rubyの実行環境にはruby.wasmを使用しています。
145-
146-
JavaScriptはeval()を使用しています。
34+
* Python (Pyodide)
35+
* PyodideにはKeyboardInterruptを送信する機能があるのでinterrupt()でそれを利用しています。
36+
* next.config.tsで指定しているwebpackのPyodidePluginにより、pyodide本体は `/_next/static/pyodide/バージョン/` 以下に出力され、それをimportします。
37+
* ただしvitest時には利用できないのでCDNにフォールバックしています
38+
* Ruby (ruby.wasm)
39+
* JavaScript (eval)
40+
* 実装は複雑になるので別パッケージ (packages/jsEval) に分離し、独立したテストもそちらに記述しています
14741

14842
### Wandbox
14943

15044
wandbox.org のAPIを利用してコードを実行します。
15145

15246
APIから利用可能なコンパイラとオプションのリストが得られるので、言語ごとにそこからオプションを選択するロジックを実装しています。
15347

154-
C++ではg++の中でheadでない最新のものを選択し、warningスイッチオン、boost有効、std=最新を指定しています。
155-
また、コード実行時にシグナルハンドラーをユーザーのコードに挿入し、エラー時にスタックトレースを表示する処理とそれをjs側でパースする処理を実装しています。
156-
157-
Rustは最新のものを選択し、-Cdebuginfo=1を追加しています。
158-
ユーザーのコードをモジュールとしてprog.rsのmain()から呼び出す形に変更しており、ユーザーのコードに `mod foo;` → `use super::foo;`, `fn main()` → `pub fn main()` の改変を加えています。
48+
* C++
49+
* g++の中でheadでない最新のものを選択し、warningスイッチオン、boost有効、std=最新を指定しています。
50+
* また、コード実行時にシグナルハンドラーをユーザーのコードに挿入し、エラー時にスタックトレースを表示する処理とそれをjs側でパースする処理を実装しています。
51+
* Rust
52+
* 最新のrustcを選択し、-Cdebuginfo=1を追加しています。
53+
* ユーザーのコードをモジュールとしてprog.rsのmain()から呼び出す形に変更しており、ユーザーのコードに `mod foo;``use super::foo;`, `fn main()``pub fn main()` の改変を加えています。
15954

16055
### TypeScript
16156

packages/runtime/src/context.tsx

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { ReactNode, useEffect } from "react";
3+
import { ReactNode, RefObject, useEffect, useRef } from "react";
44
import { RuntimeContext } from "./interface";
55
import { RuntimeLang } from "./languages";
66
import { TypeScriptProvider, useTypeScript } from "./typescript/runtime";
@@ -11,6 +11,15 @@ import { RubyContext, useRuby } from "./worker/ruby";
1111
import { WorkerProvider } from "./worker/runtime";
1212

1313
export function useRuntime(language: RuntimeLang): RuntimeContext {
14+
const runtimes = useRuntimeAll();
15+
const runtime = runtimes.current[language];
16+
const { init } = runtime;
17+
useEffect(() => {
18+
init?.();
19+
}, [init]);
20+
return runtime;
21+
}
22+
export function useRuntimeAll(): RefObject<Record<RuntimeLang, RuntimeContext>> {
1423
// すべての言語のcontextをインスタンス化
1524
const pyodide = usePyodide();
1625
const ruby = useRuby();
@@ -19,35 +28,16 @@ export function useRuntime(language: RuntimeLang): RuntimeContext {
1928
const wandboxCpp = useWandbox("cpp");
2029
const wandboxRust = useWandbox("rust");
2130

22-
let runtime: RuntimeContext;
23-
switch (language) {
24-
case "python":
25-
runtime = pyodide;
26-
break;
27-
case "ruby":
28-
runtime = ruby;
29-
break;
30-
case "javascript":
31-
runtime = jsEval;
32-
break;
33-
case "typescript":
34-
runtime = typescript;
35-
break;
36-
case "cpp":
37-
runtime = wandboxCpp;
38-
break;
39-
case "rust":
40-
runtime = wandboxRust;
41-
break;
42-
default:
43-
language satisfies never;
44-
throw new Error(`Runtime not implemented for language: ${language}`);
45-
}
46-
const { init } = runtime;
47-
useEffect(() => {
48-
init?.();
49-
}, [init]);
50-
return runtime;
31+
const runtimes = useRef<Record<RuntimeLang, RuntimeContext>>({} as never);
32+
runtimes.current.python = pyodide;
33+
runtimes.current.ruby = ruby;
34+
runtimes.current.javascript = jsEval;
35+
runtimes.current.typescript = typescript;
36+
runtimes.current.cpp = wandboxCpp;
37+
runtimes.current.rust = wandboxRust;
38+
39+
// initはしない。呼び出し側でする必要がある
40+
return runtimes;
5141
}
5242
export function RuntimeProvider({ children }: { children: ReactNode }) {
5343
return (

0 commit comments

Comments
 (0)