aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/exec_php.ts
blob: ca76940c22224c32490378b70f2c0009825b6218 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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(import.meta.env.BASE_URL + "/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}`,
	};
}