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
3 changes: 2 additions & 1 deletion app/terminal/repl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import { useEmbedContext } from "./embedContext";
import { emptyMutex, langConstants, RuntimeLang, useRuntime } from "./runtime";
import clsx from "clsx";

export type ReplOutputType = "stdout" | "stderr" | "error" | "return" | "trace" | "system";
export interface ReplOutput {
type: "stdout" | "stderr" | "error" | "return" | "trace" | "system"; // 出力の種類
type: ReplOutputType; // 出力の種類
message: string; // 出力メッセージ
}
export interface ReplCommand {
Expand Down
62 changes: 29 additions & 33 deletions app/terminal/worker/jsEval.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,20 @@ function format(...args: unknown[]): string {
// https://nodejs.org/api/util.html#utilformatformat-args
return args.map((a) => (typeof a === "string" ? a : inspect(a))).join(" ");
}
let jsOutput: ReplOutput[] = [];
let currentOutputCallback: ((output: ReplOutput) => void) | null = null;

// Helper function to capture console output
const originalConsole = self.console;
self.console = {
...originalConsole,
log: (...args: unknown[]) => {
jsOutput.push({ type: "stdout", message: format(...args) });
},
error: (...args: unknown[]) => {
jsOutput.push({ type: "stderr", message: format(...args) });
},
warn: (...args: unknown[]) => {
jsOutput.push({ type: "stderr", message: format(...args) });
},
info: (...args: unknown[]) => {
jsOutput.push({ type: "stdout", message: format(...args) });
},
log: (...args: unknown[]) =>
currentOutputCallback?.({ type: "stdout", message: format(...args) }),
error: (...args: unknown[]) =>
currentOutputCallback?.({ type: "stderr", message: format(...args) }),
warn: (...args: unknown[]) =>
currentOutputCallback?.({ type: "stderr", message: format(...args) }),
info: (...args: unknown[]) =>
currentOutputCallback?.({ type: "stdout", message: format(...args) }),
};

async function init(/*_interruptBuffer?: Uint8Array*/): Promise<{
Expand Down Expand Up @@ -73,65 +69,68 @@ async function replLikeEval(code: string): Promise<unknown> {
}
}

async function runCode(code: string): Promise<{
output: ReplOutput[];
async function runCode(
code: string,
onOutput: (output: ReplOutput) => void
): Promise<{
updatedFiles: Record<string, string>;
}> {
currentOutputCallback = onOutput;
try {
const result = await replLikeEval(code);
jsOutput.push({
onOutput({
type: "return",
message: inspect(result),
});
} catch (e) {
originalConsole.log(e);
// TODO: stack trace?
if (e instanceof Error) {
jsOutput.push({
onOutput({
type: "error",
message: `${e.name}: ${e.message}`,
});
} else {
jsOutput.push({
onOutput({
type: "error",
message: `${String(e)}`,
});
}
} finally {
currentOutputCallback = null;
}

const output = [...jsOutput];
jsOutput = []; // Clear output

return { output, updatedFiles: {} as Record<string, string> };
return { updatedFiles: {} as Record<string, string> };
}

function runFile(
name: string,
files: Record<string, string>
): { output: ReplOutput[]; updatedFiles: Record<string, string> } {
files: Record<string, string>,
onOutput: (output: ReplOutput) => void
): { updatedFiles: Record<string, string> } {
// pyodide worker などと異なり、複数ファイルを読み込んでimportのようなことをするのには対応していません。
currentOutputCallback = onOutput;
try {
self.eval(files[name]);
} catch (e) {
originalConsole.log(e);
// TODO: stack trace?
if (e instanceof Error) {
jsOutput.push({
onOutput({
type: "error",
message: `${e.name}: ${e.message}`,
});
} else {
jsOutput.push({
onOutput({
type: "error",
message: `${String(e)}`,
});
}
} finally {
currentOutputCallback = null;
}

const output = [...jsOutput];
jsOutput = []; // Clear output

return { output, updatedFiles: {} as Record<string, string> };
return { updatedFiles: {} as Record<string, string> };
}

async function checkSyntax(
Expand Down Expand Up @@ -162,8 +161,6 @@ async function checkSyntax(

async function restoreState(commands: string[]): Promise<object> {
// Re-execute all previously successful commands to restore state
jsOutput = []; // Clear output for restoration

for (const command of commands) {
try {
replLikeEval(command);
Expand All @@ -173,7 +170,6 @@ async function restoreState(commands: string[]): Promise<object> {
}
}

jsOutput = []; // Clear any output from restoration
return {};
}

Expand Down
51 changes: 27 additions & 24 deletions app/terminal/worker/pyodide.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const PYODIDE_CDN = `https://cdn.jsdelivr.net/pyodide/v${pyodideVersion}/full/`;
const HOME = `/home/pyodide/`;

let pyodide: PyodideInterface;
let pyodideOutput: ReplOutput[] = [];
let currentOutputCallback: ((output: ReplOutput) => void) | null = null;

// Helper function to read all files from the Pyodide file system
function readAllFiles(): Record<string, string> {
Expand Down Expand Up @@ -47,32 +47,33 @@ async function init(
pyodide = await (self as any).loadPyodide({ indexURL: PYODIDE_CDN });

pyodide.setStdout({
batched: (str: string) => {
pyodideOutput.push({ type: "stdout", message: str });
},
batched: (str: string) =>
currentOutputCallback?.({ type: "stdout", message: str }),
});
pyodide.setStderr({
batched: (str: string) => {
pyodideOutput.push({ type: "stderr", message: str });
},
batched: (str: string) =>
currentOutputCallback?.({ type: "stderr", message: str }),
});

pyodide.setInterruptBuffer(interruptBuffer);
}
return { capabilities: { interrupt: "buffer" } };
}

async function runCode(code: string): Promise<{
output: ReplOutput[];
async function runCode(
code: string,
onOutput: (output: ReplOutput) => void
): Promise<{
updatedFiles: Record<string, string>;
}> {
if (!pyodide) {
throw new Error("Pyodide not initialized");
}
currentOutputCallback = onOutput;
try {
const result = await pyodide.runPythonAsync(code);
if (result !== undefined) {
pyodideOutput.push({
onOutput({
type: "return",
message: String(result),
});
Expand All @@ -86,7 +87,7 @@ async function runCode(code: string): Promise<{
const execLineIndex = lines.findIndex((line) =>
line.includes("<exec>")
);
pyodideOutput.push({
onOutput({
type: "error",
message: lines
.slice(0, 1)
Expand All @@ -95,33 +96,35 @@ async function runCode(code: string): Promise<{
.trim(),
});
} else {
pyodideOutput.push({
onOutput({
type: "error",
message: `予期せぬエラー: ${e.message.trim()}`,
});
}
} else {
pyodideOutput.push({
onOutput({
type: "error",
message: `予期せぬエラー: ${String(e).trim()}`,
});
}
} finally {
currentOutputCallback = null;
}

const updatedFiles = readAllFiles();
const output = [...pyodideOutput];
pyodideOutput = []; // 出力をクリア

return { output, updatedFiles };
return { updatedFiles };
}

async function runFile(
name: string,
files: Record<string, string>
): Promise<{ output: ReplOutput[]; updatedFiles: Record<string, string> }> {
files: Record<string, string>,
onOutput: (output: ReplOutput) => void
): Promise<{ updatedFiles: Record<string, string> }> {
if (!pyodide) {
throw new Error("Pyodide not initialized");
}
currentOutputCallback = onOutput;
try {
// Use Pyodide FS API to write files to the file system
for (const filename of Object.keys(files)) {
Expand All @@ -142,7 +145,7 @@ async function runFile(
const execLineIndex = lines.findLastIndex((line) =>
line.includes("<exec>")
);
pyodideOutput.push({
onOutput({
type: "error",
message: lines
.slice(0, 1)
Expand All @@ -151,23 +154,23 @@ async function runFile(
.trim(),
});
} else {
pyodideOutput.push({
onOutput({
type: "error",
message: `予期せぬエラー: ${e.message.trim()}`,
});
}
} else {
pyodideOutput.push({
onOutput({
type: "error",
message: `予期せぬエラー: ${String(e).trim()}`,
});
}
} finally {
currentOutputCallback = null;
}

const updatedFiles = readAllFiles();
const output = [...pyodideOutput];
pyodideOutput = []; // 出力をクリア
return { output, updatedFiles };
return { updatedFiles };
}

async function checkSyntax(
Expand Down
Loading