aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/exec_php.ts
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-03-17 22:19:18 +0900
committernsfisis <nsfisis@gmail.com>2025-03-17 22:19:18 +0900
commitfacdf9357e576f6d47e7dd23f4f7b2b34f6a8d30 (patch)
tree21c6aee60b3ddc26ff88ee3273c887125bb5828f /src/exec_php.ts
parentc739bbfa7cbb68a51755254d59abf15e514795ca (diff)
downloadPHPerKaigi2025-tokens-facdf9357e576f6d47e7dd23f4f7b2b34f6a8d30.tar.gz
PHPerKaigi2025-tokens-facdf9357e576f6d47e7dd23f4f7b2b34f6a8d30.tar.zst
PHPerKaigi2025-tokens-facdf9357e576f6d47e7dd23f4f7b2b34f6a8d30.zip
commit files
Diffstat (limited to 'src/exec_php.ts')
-rw-r--r--src/exec_php.ts95
1 files changed, 95 insertions, 0 deletions
diff --git a/src/exec_php.ts b/src/exec_php.ts
new file mode 100644
index 0000000..3c38cf4
--- /dev/null
+++ b/src/exec_php.ts
@@ -0,0 +1,95 @@
+import phpWasmBridgeUrl from "./php-wasm-bridge.js?url";
+
+export type PHPExecResult = {
+ success: boolean;
+ stdout: string;
+ stderr: string;
+};
+
+const BUFFER_MAX = 1024;
+
+let fetchingWasmBinaryPromise: Promise<ArrayBuffer> | null = null;
+
+// Fetching is performed only once.
+async function fetchWasmBinary(): Promise<ArrayBuffer> {
+ if (!fetchingWasmBinaryPromise) {
+ fetchingWasmBinaryPromise = new Promise((resolve, reject) => {
+ fetch("/php-wasm.wasm")
+ .then((res) => {
+ if (!res.ok) {
+ reject(`Failed to fetch wasm binary: ${res.status} (${res.url})`);
+ }
+ return res;
+ })
+ .then((res) => res.arrayBuffer())
+ .then((buf) => resolve(buf));
+ });
+ }
+ return fetchingWasmBinaryPromise;
+}
+
+export async function execPHP(code: string): Promise<PHPExecResult> {
+ let stdinPos = 0; // bytewise
+ const stdinBuf = new TextEncoder().encode(code);
+ let stdoutPos = 0; // bytewise
+ const stdoutBuf = new Uint8Array(BUFFER_MAX);
+ let stderrPos = 0; // bytewise
+ const stderrBuf = new Uint8Array(BUFFER_MAX);
+
+ const { default: PHPWasm } = await import(
+ /* @vite-ignore */ phpWasmBridgeUrl
+ );
+ const { ccall } = await PHPWasm({
+ wasmBinary: await fetchWasmBinary(),
+ stdin: () => {
+ if (stdinBuf.length <= stdinPos) {
+ return null;
+ }
+ return stdinBuf[stdinPos++];
+ },
+ stdout: (asciiCode: number | null) => {
+ if (asciiCode === null) {
+ return; // flush
+ }
+ if (BUFFER_MAX <= stdoutPos) {
+ return; // ignore
+ }
+ if (asciiCode < 0) {
+ asciiCode += 256;
+ }
+ stdoutBuf[stdoutPos++] = asciiCode;
+ },
+ stderr: (asciiCode: number | null) => {
+ if (asciiCode === null) {
+ return; // flush
+ }
+ if (BUFFER_MAX <= stderrPos) {
+ return; // ignore
+ }
+ if (asciiCode < 0) {
+ asciiCode += 256;
+ }
+ stderrBuf[stderrPos++] = asciiCode;
+ },
+ });
+
+ let result;
+ let extraError = null;
+ try {
+ result = ccall("php_wasm_run", "number", ["string"], [code]);
+ } catch (e) {
+ if (e instanceof WebAssembly.RuntimeError) {
+ extraError = e.message;
+ } else {
+ throw e;
+ }
+ }
+ const stdout = new TextDecoder().decode(stdoutBuf.subarray(0, stdoutPos));
+ const stderr = new TextDecoder().decode(stderrBuf.subarray(0, stderrPos));
+
+ return {
+ success: result === 0,
+ stdout,
+ stderr: extraError == null ? stderr : `${stderr}\n${extraError}`,
+ };
+}