@pyscript/core 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/cjs/custom-types.js +212 -0
- package/cjs/fetch-utils.js +12 -0
- package/cjs/index.js +73 -0
- package/cjs/interpreter/_python.js +16 -0
- package/cjs/interpreter/_utils.js +141 -0
- package/cjs/interpreter/micropython.js +34 -0
- package/cjs/interpreter/pyodide.js +38 -0
- package/cjs/interpreter/ruby-wasm-wasi.js +43 -0
- package/cjs/interpreter/wasmoon.js +40 -0
- package/cjs/interpreters.js +64 -0
- package/cjs/listeners.js +71 -0
- package/cjs/loader.js +43 -0
- package/cjs/package.json +1 -0
- package/cjs/plugins/pyscript.js +105 -0
- package/cjs/script-handler.js +141 -0
- package/cjs/toml.js +10 -0
- package/cjs/utils.js +19 -0
- package/cjs/worker/class.js +46 -0
- package/cjs/worker/hooks.js +2 -0
- package/cjs/worker/xworker.js +3 -0
- package/esm/custom-types.js +207 -0
- package/esm/fetch-utils.js +8 -0
- package/esm/index.js +68 -0
- package/esm/interpreter/_python.js +12 -0
- package/esm/interpreter/_utils.js +133 -0
- package/esm/interpreter/micropython.js +33 -0
- package/esm/interpreter/pyodide.js +37 -0
- package/esm/interpreter/ruby-wasm-wasi.js +42 -0
- package/esm/interpreter/wasmoon.js +39 -0
- package/esm/interpreters.js +58 -0
- package/esm/listeners.js +68 -0
- package/esm/loader.js +40 -0
- package/esm/script-handler.js +136 -0
- package/esm/toml.js +8 -0
- package/esm/utils.js +20 -0
- package/esm/worker/class.js +45 -0
- package/esm/worker/hooks.js +1 -0
- package/esm/worker/xworker.js +2 -0
- package/min.js +2 -0
- package/package.json +55 -0
- package/types/coincident/structured.d.ts +5 -0
- package/types/fetch-utils.d.ts +3 -0
- package/types/index.d.ts +2 -0
- package/types/loader.d.ts +2 -0
- package/types/plugins.d.ts +61 -0
- package/types/pyscript/pyscript.core/esm/custom-types.d.ts +54 -0
- package/types/pyscript/pyscript.core/esm/fetch-utils.d.ts +3 -0
- package/types/pyscript/pyscript.core/esm/index.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/interpreter/_python.d.ts +5 -0
- package/types/pyscript/pyscript.core/esm/interpreter/_utils.d.ts +11 -0
- package/types/pyscript/pyscript.core/esm/interpreter/micropython.d.ts +17 -0
- package/types/pyscript/pyscript.core/esm/interpreter/pyodide.d.ts +17 -0
- package/types/pyscript/pyscript.core/esm/interpreter/ruby-wasm-wasi.d.ts +15 -0
- package/types/pyscript/pyscript.core/esm/interpreter/ruby.d.ts +16 -0
- package/types/pyscript/pyscript.core/esm/interpreter/wasmoon.d.ts +21 -0
- package/types/pyscript/pyscript.core/esm/interpreters.d.ts +9 -0
- package/types/pyscript/pyscript.core/esm/listeners.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/loader.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/plugins.d.ts +54 -0
- package/types/pyscript/pyscript.core/esm/runtime/_python.d.ts +8 -0
- package/types/pyscript/pyscript.core/esm/runtime/_utils.d.ts +11 -0
- package/types/pyscript/pyscript.core/esm/runtime/micropython.d.ts +20 -0
- package/types/pyscript/pyscript.core/esm/runtime/pyodide.d.ts +20 -0
- package/types/pyscript/pyscript.core/esm/runtime/ruby.d.ts +15 -0
- package/types/pyscript/pyscript.core/esm/runtime/wasmoon.d.ts +21 -0
- package/types/pyscript/pyscript.core/esm/runtimes.d.ts +9 -0
- package/types/pyscript/pyscript.core/esm/script-handler.d.ts +4 -0
- package/types/pyscript/pyscript.core/esm/toml.d.ts +1 -0
- package/types/pyscript/pyscript.core/esm/utils.d.ts +23 -0
- package/types/pyscript/pyscript.core/esm/worker/class.d.ts +19 -0
- package/types/pyscript/pyscript.core/esm/worker/hooks.d.ts +2 -0
- package/types/pyscript/pyscript.core/esm/worker/xworker.d.ts +2 -0
- package/types/runtime/_python.d.ts +8 -0
- package/types/runtime/_utils.d.ts +11 -0
- package/types/runtime/micropython.d.ts +20 -0
- package/types/runtime/pyodide.d.ts +20 -0
- package/types/runtime/ruby.d.ts +15 -0
- package/types/runtime/wasmoon.d.ts +21 -0
- package/types/runtimes.d.ts +9 -0
- package/types/script-handler.d.ts +3 -0
- package/types/toml.d.ts +1 -0
- package/types/utils.d.ts +23 -0
- package/types/worker/class.d.ts +19 -0
- package/types/worker/hooks.d.ts +2 -0
- package/types/worker/xworker.d.ts +2 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
import "@ungap/with-resolvers";
|
2
|
+
import { $$ } from "basic-devtools";
|
3
|
+
|
4
|
+
import { assign, create } from "./utils.js";
|
5
|
+
import { getDetails } from "./script-handler.js";
|
6
|
+
import {
|
7
|
+
registry as defaultRegistry,
|
8
|
+
prefixes,
|
9
|
+
configs,
|
10
|
+
} from "./interpreters.js";
|
11
|
+
import { getRuntimeID } from "./loader.js";
|
12
|
+
import { io } from "./interpreter/_utils.js";
|
13
|
+
import { addAllListeners } from "./listeners.js";
|
14
|
+
|
15
|
+
import workerHooks from "./worker/hooks.js";
|
16
|
+
|
17
|
+
export const CUSTOM_SELECTORS = [];
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @typedef {Object} Runtime custom configuration
|
21
|
+
* @prop {object} interpreter the bootstrapped interpreter
|
22
|
+
* @prop {(url:string, options?: object) => Worker} XWorker an XWorker constructor that defaults to same interpreter on the Worker.
|
23
|
+
* @prop {object} config a cloned config used to bootstrap the interpreter
|
24
|
+
* @prop {(code:string) => any} run an utility to run code within the interpreter
|
25
|
+
* @prop {(code:string) => Promise<any>} runAsync an utility to run code asynchronously within the interpreter
|
26
|
+
* @prop {(path:string, data:ArrayBuffer) => void} writeFile an utility to write a file in the virtual FS, if available
|
27
|
+
*/
|
28
|
+
|
29
|
+
const patched = new Map();
|
30
|
+
const types = new Map();
|
31
|
+
const waitList = new Map();
|
32
|
+
|
33
|
+
// REQUIRES INTEGRATION TEST
|
34
|
+
/* c8 ignore start */
|
35
|
+
/**
|
36
|
+
* @param {Element} node any DOM element registered via define.
|
37
|
+
*/
|
38
|
+
export const handleCustomType = (node) => {
|
39
|
+
for (const selector of CUSTOM_SELECTORS) {
|
40
|
+
if (node.matches(selector)) {
|
41
|
+
const type = types.get(selector);
|
42
|
+
const { resolve } = waitList.get(type);
|
43
|
+
const { options, known } = registry.get(type);
|
44
|
+
if (!known.has(node)) {
|
45
|
+
known.add(node);
|
46
|
+
const {
|
47
|
+
interpreter: runtime,
|
48
|
+
version,
|
49
|
+
config,
|
50
|
+
env,
|
51
|
+
onRuntimeReady,
|
52
|
+
} = options;
|
53
|
+
const name = getRuntimeID(runtime, version);
|
54
|
+
const id = env || `${name}${config ? `|${config}` : ""}`;
|
55
|
+
const { interpreter: engine, XWorker } = getDetails(
|
56
|
+
runtime,
|
57
|
+
id,
|
58
|
+
name,
|
59
|
+
version,
|
60
|
+
config,
|
61
|
+
);
|
62
|
+
engine.then((interpreter) => {
|
63
|
+
if (!patched.has(id)) {
|
64
|
+
const module = create(defaultRegistry.get(runtime));
|
65
|
+
const {
|
66
|
+
onBeforeRun,
|
67
|
+
onBeforeRunAsync,
|
68
|
+
onAfterRun,
|
69
|
+
onAfterRunAsync,
|
70
|
+
codeBeforeRunWorker,
|
71
|
+
codeBeforeRunWorkerAsync,
|
72
|
+
codeAfterRunWorker,
|
73
|
+
codeAfterRunWorkerAsync,
|
74
|
+
} = options;
|
75
|
+
|
76
|
+
// These two loops mimic a `new Map(arrayContent)` without needing
|
77
|
+
// the new Map overhead so that [name, [before, after]] can be easily destructured
|
78
|
+
// and new sync or async patches become easy to add (when the logic is the same).
|
79
|
+
|
80
|
+
// patch sync
|
81
|
+
for (const [name, [before, after]] of [
|
82
|
+
["run", [onBeforeRun, onAfterRun]],
|
83
|
+
]) {
|
84
|
+
const method = module[name];
|
85
|
+
module[name] = function (interpreter, code) {
|
86
|
+
if (before) before.call(this, resolved, node);
|
87
|
+
const result = method.call(
|
88
|
+
this,
|
89
|
+
interpreter,
|
90
|
+
code,
|
91
|
+
);
|
92
|
+
if (after) after.call(this, resolved, node);
|
93
|
+
return result;
|
94
|
+
};
|
95
|
+
}
|
96
|
+
|
97
|
+
// patch async
|
98
|
+
for (const [name, [before, after]] of [
|
99
|
+
["runAsync", [onBeforeRunAsync, onAfterRunAsync]],
|
100
|
+
]) {
|
101
|
+
const method = module[name];
|
102
|
+
module[name] = async function (interpreter, code) {
|
103
|
+
if (before)
|
104
|
+
await before.call(this, resolved, node);
|
105
|
+
const result = await method.call(
|
106
|
+
this,
|
107
|
+
interpreter,
|
108
|
+
code,
|
109
|
+
);
|
110
|
+
if (after)
|
111
|
+
await after.call(this, resolved, node);
|
112
|
+
return result;
|
113
|
+
};
|
114
|
+
}
|
115
|
+
|
116
|
+
// setup XWorker hooks, allowing strings to be forwarded to the worker
|
117
|
+
// whenever it's created, as functions can't possibly be serialized
|
118
|
+
// unless these are pure with no outer scope access (or globals vars)
|
119
|
+
// so that making it strings disambiguate about their running context.
|
120
|
+
workerHooks.set(XWorker, {
|
121
|
+
beforeRun: codeBeforeRunWorker,
|
122
|
+
beforeRunAsync: codeBeforeRunWorkerAsync,
|
123
|
+
afterRun: codeAfterRunWorker,
|
124
|
+
afterRunAsync: codeAfterRunWorkerAsync,
|
125
|
+
});
|
126
|
+
|
127
|
+
module.setGlobal(interpreter, "XWorker", XWorker);
|
128
|
+
|
129
|
+
const resolved = {
|
130
|
+
type,
|
131
|
+
interpreter,
|
132
|
+
XWorker,
|
133
|
+
io: io.get(interpreter),
|
134
|
+
config: structuredClone(configs.get(name)),
|
135
|
+
run: module.run.bind(module, interpreter),
|
136
|
+
runAsync: module.runAsync.bind(module, interpreter),
|
137
|
+
};
|
138
|
+
|
139
|
+
patched.set(id, resolved);
|
140
|
+
resolve(resolved);
|
141
|
+
}
|
142
|
+
|
143
|
+
onRuntimeReady?.(patched.get(id), node);
|
144
|
+
});
|
145
|
+
}
|
146
|
+
}
|
147
|
+
}
|
148
|
+
};
|
149
|
+
|
150
|
+
/**
|
151
|
+
* @type {Map<string, {options:object, known:WeakSet<Element>}>}
|
152
|
+
*/
|
153
|
+
const registry = new Map();
|
154
|
+
|
155
|
+
/**
|
156
|
+
* @typedef {Object} PluginOptions custom configuration
|
157
|
+
* @prop {'pyodide' | 'micropython' | 'wasmoon' | 'ruby-wasm-wasi'} interpreter the interpreter to use
|
158
|
+
* @prop {string} [version] the optional interpreter version to use
|
159
|
+
* @prop {string} [config] the optional config to use within such interpreter
|
160
|
+
* @prop {(environment: object, node: Element) => void} [onRuntimeReady] the callback that will be invoked once
|
161
|
+
*/
|
162
|
+
|
163
|
+
/**
|
164
|
+
* Allows custom types and components on the page to receive interpreters to execute any code
|
165
|
+
* @param {string} type the unique `<script type="...">` identifier
|
166
|
+
* @param {PluginOptions} options the custom type configuration
|
167
|
+
*/
|
168
|
+
export const define = (type, options) => {
|
169
|
+
if (defaultRegistry.has(type) || registry.has(type))
|
170
|
+
throw new Error(`<script type="${type}"> already registered`);
|
171
|
+
|
172
|
+
if (!defaultRegistry.has(options?.interpreter))
|
173
|
+
throw new Error(`Unspecified interpreter`);
|
174
|
+
|
175
|
+
// allows reaching out the interpreter helpers on events
|
176
|
+
defaultRegistry.set(type, defaultRegistry.get(options?.interpreter));
|
177
|
+
|
178
|
+
// ensure a Promise can resolve once a custom type has been bootstrapped
|
179
|
+
whenDefined(type);
|
180
|
+
|
181
|
+
// allows selector -> registry by type
|
182
|
+
const selectors = [`script[type="${type}"]`, `${type}-script`];
|
183
|
+
for (const selector of selectors) types.set(selector, type);
|
184
|
+
|
185
|
+
CUSTOM_SELECTORS.push(...selectors);
|
186
|
+
prefixes.push(`${type}-`);
|
187
|
+
|
188
|
+
// ensure always same env for this custom type
|
189
|
+
registry.set(type, {
|
190
|
+
options: assign({ env: type }, options),
|
191
|
+
known: new WeakSet(),
|
192
|
+
});
|
193
|
+
|
194
|
+
addAllListeners(document);
|
195
|
+
$$(selectors.join(",")).forEach(handleCustomType);
|
196
|
+
};
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Resolves whenever a defined custom type is bootstrapped on the page
|
200
|
+
* @param {string} type the unique `<script type="...">` identifier
|
201
|
+
* @returns {Promise<object>}
|
202
|
+
*/
|
203
|
+
export const whenDefined = (type) => {
|
204
|
+
if (!waitList.has(type)) waitList.set(type, Promise.withResolvers());
|
205
|
+
return waitList.get(type).promise;
|
206
|
+
};
|
207
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,8 @@
|
|
1
|
+
/** @param {Response} response */
|
2
|
+
export const getBuffer = (response) => response.arrayBuffer();
|
3
|
+
|
4
|
+
/** @param {Response} response */
|
5
|
+
export const getJSON = (response) => response.json();
|
6
|
+
|
7
|
+
/** @param {Response} response */
|
8
|
+
export const getText = (response) => response.text();
|
package/esm/index.js
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
import { $$ } from "basic-devtools";
|
2
|
+
|
3
|
+
import xworker from "./worker/class.js";
|
4
|
+
import { handle } from "./script-handler.js";
|
5
|
+
import { assign } from "./utils.js";
|
6
|
+
import { selectors, prefixes } from "./interpreters.js";
|
7
|
+
import { CUSTOM_SELECTORS, handleCustomType } from "./custom-types.js";
|
8
|
+
import { listener, addAllListeners } from "./listeners.js";
|
9
|
+
|
10
|
+
export { define, whenDefined } from "./custom-types.js";
|
11
|
+
export const XWorker = xworker();
|
12
|
+
|
13
|
+
const INTERPRETER_SELECTORS = selectors.join(",");
|
14
|
+
|
15
|
+
const mo = new MutationObserver((records) => {
|
16
|
+
for (const { type, target, attributeName, addedNodes } of records) {
|
17
|
+
// attributes are tested via integration / e2e
|
18
|
+
/* c8 ignore next 17 */
|
19
|
+
if (type === "attributes") {
|
20
|
+
const i = attributeName.lastIndexOf("-") + 1;
|
21
|
+
if (i) {
|
22
|
+
const prefix = attributeName.slice(0, i);
|
23
|
+
for (const p of prefixes) {
|
24
|
+
if (prefix === p) {
|
25
|
+
const type = attributeName.slice(i);
|
26
|
+
if (type !== "env") {
|
27
|
+
const method = target.hasAttribute(attributeName)
|
28
|
+
? "add"
|
29
|
+
: "remove";
|
30
|
+
target[`${method}EventListener`](type, listener);
|
31
|
+
}
|
32
|
+
break;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
continue;
|
37
|
+
}
|
38
|
+
for (const node of addedNodes) {
|
39
|
+
if (node.nodeType === 1) {
|
40
|
+
addAllListeners(node);
|
41
|
+
if (node.matches(INTERPRETER_SELECTORS)) handle(node);
|
42
|
+
else {
|
43
|
+
$$(INTERPRETER_SELECTORS, node).forEach(handle);
|
44
|
+
if (!CUSTOM_SELECTORS.length) continue;
|
45
|
+
handleCustomType(node);
|
46
|
+
$$(CUSTOM_SELECTORS.join(","), node).forEach(
|
47
|
+
handleCustomType,
|
48
|
+
);
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
});
|
54
|
+
|
55
|
+
const observe = (root) => {
|
56
|
+
mo.observe(root, { childList: true, subtree: true, attributes: true });
|
57
|
+
return root;
|
58
|
+
};
|
59
|
+
|
60
|
+
const { attachShadow } = Element.prototype;
|
61
|
+
assign(Element.prototype, {
|
62
|
+
attachShadow(init) {
|
63
|
+
return observe(attachShadow.call(this, init));
|
64
|
+
},
|
65
|
+
});
|
66
|
+
|
67
|
+
addAllListeners(observe(document));
|
68
|
+
$$(INTERPRETER_SELECTORS, document).forEach(handle);
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { clean, writeFile as writeFileUtil } from "./_utils.js";
|
2
|
+
|
3
|
+
// REQUIRES INTEGRATION TEST
|
4
|
+
/* c8 ignore start */
|
5
|
+
export const run = (interpreter, code) => interpreter.runPython(clean(code));
|
6
|
+
|
7
|
+
export const runAsync = (interpreter, code) =>
|
8
|
+
interpreter.runPythonAsync(clean(code));
|
9
|
+
|
10
|
+
export const writeFile = ({ FS }, path, buffer) =>
|
11
|
+
writeFileUtil(FS, path, buffer);
|
12
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,133 @@
|
|
1
|
+
import "@ungap/with-resolvers";
|
2
|
+
|
3
|
+
import { getBuffer } from "../fetch-utils.js";
|
4
|
+
import { absoluteURL } from "../utils.js";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Trim code only if it's a single line that prettier or other tools might have modified.
|
8
|
+
* @param {string} code code that might be a single line
|
9
|
+
* @returns {string}
|
10
|
+
*/
|
11
|
+
export const clean = (code) =>
|
12
|
+
code.replace(/^[^\r\n]+$/, (line) => line.trim());
|
13
|
+
|
14
|
+
// REQUIRES INTEGRATION TEST
|
15
|
+
/* c8 ignore start */
|
16
|
+
export const io = new WeakMap();
|
17
|
+
export const stdio = (init) => {
|
18
|
+
const context = init || console;
|
19
|
+
const localIO = {
|
20
|
+
stderr: (context.stderr || console.error).bind(context),
|
21
|
+
stdout: (context.stdout || console.log).bind(context),
|
22
|
+
};
|
23
|
+
return {
|
24
|
+
stderr: (...args) => localIO.stderr(...args),
|
25
|
+
stdout: (...args) => localIO.stdout(...args),
|
26
|
+
async get(engine) {
|
27
|
+
const interpreter = await engine;
|
28
|
+
io.set(interpreter, localIO);
|
29
|
+
return interpreter;
|
30
|
+
},
|
31
|
+
};
|
32
|
+
};
|
33
|
+
/* c8 ignore stop */
|
34
|
+
|
35
|
+
// This should be the only helper needed for all Emscripten based FS exports
|
36
|
+
export const writeFile = (FS, path, buffer) => {
|
37
|
+
const { parentPath, name } = FS.analyzePath(path, true);
|
38
|
+
FS.mkdirTree(parentPath);
|
39
|
+
return FS.writeFile([parentPath, name].join("/"), new Uint8Array(buffer), {
|
40
|
+
canOwn: true,
|
41
|
+
});
|
42
|
+
};
|
43
|
+
|
44
|
+
// This is instead a fallback for Lua or others
|
45
|
+
export const writeFileShim = (FS, path, buffer) => {
|
46
|
+
path = resolve(FS, path);
|
47
|
+
mkdirTree(FS, dirname(path));
|
48
|
+
return FS.writeFile(path, new Uint8Array(buffer), { canOwn: true });
|
49
|
+
};
|
50
|
+
|
51
|
+
const dirname = (path) => {
|
52
|
+
const tree = path.split("/");
|
53
|
+
tree.pop();
|
54
|
+
return tree.join("/");
|
55
|
+
};
|
56
|
+
|
57
|
+
const mkdirTree = (FS, path) => {
|
58
|
+
const current = [];
|
59
|
+
for (const branch of path.split("/")) {
|
60
|
+
current.push(branch);
|
61
|
+
if (branch) FS.mkdir(current.join("/"));
|
62
|
+
}
|
63
|
+
};
|
64
|
+
|
65
|
+
const resolve = (FS, path) => {
|
66
|
+
const tree = [];
|
67
|
+
for (const branch of path.split("/")) {
|
68
|
+
switch (branch) {
|
69
|
+
case "":
|
70
|
+
break;
|
71
|
+
case ".":
|
72
|
+
break;
|
73
|
+
case "..":
|
74
|
+
tree.pop();
|
75
|
+
break;
|
76
|
+
default:
|
77
|
+
tree.push(branch);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
return [FS.cwd()].concat(tree).join("/").replace(/^\/+/, "/");
|
81
|
+
};
|
82
|
+
|
83
|
+
import { all, isArray } from "../utils.js";
|
84
|
+
|
85
|
+
const calculateFetchPaths = (config_fetch) => {
|
86
|
+
// REQUIRES INTEGRATION TEST
|
87
|
+
/* c8 ignore start */
|
88
|
+
for (const { files, to_file, from = "" } of config_fetch) {
|
89
|
+
if (files !== undefined && to_file !== undefined)
|
90
|
+
throw new Error(
|
91
|
+
`Cannot use 'to_file' and 'files' parameters together!`,
|
92
|
+
);
|
93
|
+
if (files === undefined && to_file === undefined && from.endsWith("/"))
|
94
|
+
throw new Error(
|
95
|
+
`Couldn't determine the filename from the path ${from}, please supply 'to_file' parameter.`,
|
96
|
+
);
|
97
|
+
}
|
98
|
+
/* c8 ignore stop */
|
99
|
+
return config_fetch.flatMap(
|
100
|
+
({ from = "", to_folder = ".", to_file, files }) => {
|
101
|
+
if (isArray(files))
|
102
|
+
return files.map((file) => ({
|
103
|
+
url: joinPaths([from, file]),
|
104
|
+
path: joinPaths([to_folder, file]),
|
105
|
+
}));
|
106
|
+
const filename = to_file || from.slice(1 + from.lastIndexOf("/"));
|
107
|
+
return [{ url: from, path: joinPaths([to_folder, filename]) }];
|
108
|
+
},
|
109
|
+
);
|
110
|
+
};
|
111
|
+
|
112
|
+
const joinPaths = (parts) => {
|
113
|
+
const res = parts
|
114
|
+
.map((part) => part.trim().replace(/(^[/]*|[/]*$)/g, ""))
|
115
|
+
.filter((p) => p !== "" && p !== ".")
|
116
|
+
.join("/");
|
117
|
+
|
118
|
+
return parts[0].startsWith("/") ? `/${res}` : res;
|
119
|
+
};
|
120
|
+
|
121
|
+
const fetchResolved = (config_fetch, url) =>
|
122
|
+
fetch(absoluteURL(url, base.get(config_fetch)));
|
123
|
+
|
124
|
+
export const base = new WeakMap();
|
125
|
+
|
126
|
+
export const fetchPaths = (module, interpreter, config_fetch) =>
|
127
|
+
all(
|
128
|
+
calculateFetchPaths(config_fetch).map(({ url, path }) =>
|
129
|
+
fetchResolved(config_fetch, url)
|
130
|
+
.then(getBuffer)
|
131
|
+
.then((buffer) => module.writeFile(interpreter, path, buffer)),
|
132
|
+
),
|
133
|
+
);
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { fetchPaths, stdio } from "./_utils.js";
|
2
|
+
import { run, runAsync, writeFile } from "./_python.js";
|
3
|
+
|
4
|
+
const type = "micropython";
|
5
|
+
|
6
|
+
// REQUIRES INTEGRATION TEST
|
7
|
+
/* c8 ignore start */
|
8
|
+
export default {
|
9
|
+
type,
|
10
|
+
module: (version = "1.20.0-239") =>
|
11
|
+
`https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@${version}/micropython.mjs`,
|
12
|
+
async engine({ loadMicroPython }, config, url) {
|
13
|
+
const { stderr, stdout, get } = stdio();
|
14
|
+
url = url.replace(/\.m?js$/, ".wasm");
|
15
|
+
const runtime = await get(loadMicroPython({ stderr, stdout, url }));
|
16
|
+
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
17
|
+
return runtime;
|
18
|
+
},
|
19
|
+
setGlobal(interpreter, name, value) {
|
20
|
+
const id = `__pyscript_${this.type}_${name}`;
|
21
|
+
globalThis[id] = value;
|
22
|
+
this.run(interpreter, `from js import ${id};${name}=${id};`);
|
23
|
+
},
|
24
|
+
deleteGlobal(interpreter, name) {
|
25
|
+
const id = `__pyscript_${this.type}_${name}`;
|
26
|
+
this.run(interpreter, `del ${id};del ${name}`);
|
27
|
+
delete globalThis[id];
|
28
|
+
},
|
29
|
+
run,
|
30
|
+
runAsync,
|
31
|
+
writeFile,
|
32
|
+
};
|
33
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { fetchPaths, stdio } from "./_utils.js";
|
2
|
+
import { run, runAsync, writeFile } from "./_python.js";
|
3
|
+
|
4
|
+
const type = "pyodide";
|
5
|
+
|
6
|
+
// REQUIRES INTEGRATION TEST
|
7
|
+
/* c8 ignore start */
|
8
|
+
export default {
|
9
|
+
type,
|
10
|
+
module: (version = "0.23.2") =>
|
11
|
+
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`,
|
12
|
+
async engine({ loadPyodide }, config, url) {
|
13
|
+
const { stderr, stdout, get } = stdio();
|
14
|
+
const indexURL = url.slice(0, url.lastIndexOf("/"));
|
15
|
+
const interpreter = await get(
|
16
|
+
loadPyodide({ stderr, stdout, indexURL }),
|
17
|
+
);
|
18
|
+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
19
|
+
if (config.packages) {
|
20
|
+
await interpreter.loadPackage("micropip");
|
21
|
+
const micropip = await interpreter.pyimport("micropip");
|
22
|
+
await micropip.install(config.packages);
|
23
|
+
micropip.destroy();
|
24
|
+
}
|
25
|
+
return interpreter;
|
26
|
+
},
|
27
|
+
setGlobal(interpreter, name, value) {
|
28
|
+
interpreter.globals.set(name, value);
|
29
|
+
},
|
30
|
+
deleteGlobal(interpreter, name) {
|
31
|
+
interpreter.globals.delete(name);
|
32
|
+
},
|
33
|
+
run,
|
34
|
+
runAsync,
|
35
|
+
writeFile,
|
36
|
+
};
|
37
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { clean, fetchPaths } from "./_utils.js";
|
2
|
+
|
3
|
+
const type = "ruby-wasm-wasi";
|
4
|
+
|
5
|
+
// MISSING:
|
6
|
+
// * there is no VFS apparently or I couldn't reach any
|
7
|
+
// * I've no idea how to override the stderr and stdout
|
8
|
+
// * I've no idea how to import packages
|
9
|
+
|
10
|
+
// REQUIRES INTEGRATION TEST
|
11
|
+
/* c8 ignore start */
|
12
|
+
export default {
|
13
|
+
type,
|
14
|
+
experimental: true,
|
15
|
+
module: (version = "2.0.0") =>
|
16
|
+
`https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@${version}/dist/browser.esm.js`,
|
17
|
+
async engine({ DefaultRubyVM }, config, url) {
|
18
|
+
const response = await fetch(
|
19
|
+
`${url.slice(0, url.lastIndexOf("/"))}/ruby.wasm`,
|
20
|
+
);
|
21
|
+
const module = await WebAssembly.compile(await response.arrayBuffer());
|
22
|
+
const { vm: interpreter } = await DefaultRubyVM(module);
|
23
|
+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
24
|
+
return interpreter;
|
25
|
+
},
|
26
|
+
setGlobal(interpreter, name, value) {
|
27
|
+
const id = `__pyscript_ruby_wasm_wasi_${name}`;
|
28
|
+
globalThis[id] = value;
|
29
|
+
this.run(interpreter, `require "js";$${name}=JS::eval("return ${id}")`);
|
30
|
+
},
|
31
|
+
deleteGlobal(interpreter, name) {
|
32
|
+
const id = `__pyscript_ruby_wasm_wasi_${name}`;
|
33
|
+
this.run(interpreter, `$${name}=nil`);
|
34
|
+
delete globalThis[id];
|
35
|
+
},
|
36
|
+
run: (interpreter, code) => interpreter.eval(clean(code)),
|
37
|
+
runAsync: (interpreter, code) => interpreter.evalAsync(clean(code)),
|
38
|
+
writeFile: () => {
|
39
|
+
throw new Error(`writeFile is not supported in ${type}`);
|
40
|
+
},
|
41
|
+
};
|
42
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import { clean, fetchPaths, stdio, writeFileShim } from "./_utils.js";
|
2
|
+
|
3
|
+
const type = "wasmoon";
|
4
|
+
|
5
|
+
// REQUIRES INTEGRATION TEST
|
6
|
+
/* c8 ignore start */
|
7
|
+
export default {
|
8
|
+
type,
|
9
|
+
module: (version = "1.15.0") =>
|
10
|
+
`https://cdn.jsdelivr.net/npm/wasmoon@${version}/+esm`,
|
11
|
+
async engine({ LuaFactory, LuaLibraries }, config) {
|
12
|
+
const { stderr, stdout, get } = stdio();
|
13
|
+
const interpreter = await get(new LuaFactory().createEngine());
|
14
|
+
interpreter.global.getTable(LuaLibraries.Base, (index) => {
|
15
|
+
interpreter.global.setField(index, "print", stdout);
|
16
|
+
interpreter.global.setField(index, "printErr", stderr);
|
17
|
+
});
|
18
|
+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
19
|
+
return interpreter;
|
20
|
+
},
|
21
|
+
setGlobal(interpreter, name, value) {
|
22
|
+
interpreter.global.set(name, value);
|
23
|
+
},
|
24
|
+
deleteGlobal(interpreter, name) {
|
25
|
+
interpreter.global.set(name, void 0);
|
26
|
+
},
|
27
|
+
run: (interpreter, code) => interpreter.doStringSync(clean(code)),
|
28
|
+
runAsync: (interpreter, code) => interpreter.doString(clean(code)),
|
29
|
+
writeFile: (
|
30
|
+
{
|
31
|
+
cmodule: {
|
32
|
+
module: { FS },
|
33
|
+
},
|
34
|
+
},
|
35
|
+
path,
|
36
|
+
buffer,
|
37
|
+
) => writeFileShim(FS, path, buffer),
|
38
|
+
};
|
39
|
+
/* c8 ignore stop */
|
@@ -0,0 +1,58 @@
|
|
1
|
+
// ⚠️ Part of this file is automatically generated
|
2
|
+
// The :RUNTIMES comment is a delimiter and no code should be written/changed after
|
3
|
+
// See rollup/build_interpreters.cjs to know more
|
4
|
+
|
5
|
+
import { base } from "./interpreter/_utils.js";
|
6
|
+
|
7
|
+
/** @type {Map<string, object>} */
|
8
|
+
export const registry = new Map();
|
9
|
+
|
10
|
+
/** @type {Map<string, object>} */
|
11
|
+
export const configs = new Map();
|
12
|
+
|
13
|
+
/** @type {string[]} */
|
14
|
+
export const selectors = [];
|
15
|
+
|
16
|
+
/** @type {string[]} */
|
17
|
+
export const prefixes = [];
|
18
|
+
|
19
|
+
export const interpreter = new Proxy(new Map(), {
|
20
|
+
get(map, id) {
|
21
|
+
if (!map.has(id)) {
|
22
|
+
const [type, ...rest] = id.split("@");
|
23
|
+
const interpreter = registry.get(type);
|
24
|
+
const url = /^https?:\/\//i.test(rest)
|
25
|
+
? rest.join("@")
|
26
|
+
: interpreter.module(...rest);
|
27
|
+
map.set(id, {
|
28
|
+
url,
|
29
|
+
module: import(url),
|
30
|
+
engine: interpreter.engine.bind(interpreter),
|
31
|
+
});
|
32
|
+
}
|
33
|
+
const { url, module, engine } = map.get(id);
|
34
|
+
return (config, baseURL) =>
|
35
|
+
module.then((module) => {
|
36
|
+
configs.set(id, config);
|
37
|
+
const fetch = config?.fetch;
|
38
|
+
if (fetch) base.set(fetch, baseURL);
|
39
|
+
return engine(module, config, url);
|
40
|
+
});
|
41
|
+
},
|
42
|
+
});
|
43
|
+
|
44
|
+
const register = (interpreter) => {
|
45
|
+
for (const type of [].concat(interpreter.type)) {
|
46
|
+
registry.set(type, interpreter);
|
47
|
+
selectors.push(`script[type="${type}"]`);
|
48
|
+
prefixes.push(`${type}-`);
|
49
|
+
}
|
50
|
+
};
|
51
|
+
|
52
|
+
//:RUNTIMES
|
53
|
+
import micropython from "./interpreter/micropython.js";
|
54
|
+
import pyodide from "./interpreter/pyodide.js";
|
55
|
+
import ruby_wasm_wasi from "./interpreter/ruby-wasm-wasi.js";
|
56
|
+
import wasmoon from "./interpreter/wasmoon.js";
|
57
|
+
for (const interpreter of [micropython, pyodide, ruby_wasm_wasi, wasmoon])
|
58
|
+
register(interpreter);
|