Skip to content

Commit 19bf9ca

Browse files
committed
WebR - Expreimental
1 parent 82433df commit 19bf9ca

File tree

11 files changed

+3255
-7
lines changed

11 files changed

+3255
-7
lines changed

docs/index.js

Lines changed: 3043 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/toml-CkEFU7ly.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* c8 ignore start */
2+
/*! (c) Andrea Giammarchi - ISC */
3+
4+
const {isArray} = Array;
5+
const {parse: jsonParse} = JSON;
6+
7+
/** @typedef {{s: string[], d: Date[]}} Foreign foreign strings and dates */
8+
9+
/**
10+
* Transform quoted keys into regular keys.
11+
* @param {string} str the key to eventually normalize
12+
* @param {Foreign} foreign foreign strings and dates
13+
* @returns
14+
*/
15+
const getKey = (str, {s}) => str.replace(/"s(\d+)"/g, (_, $1) => s[$1]);
16+
17+
/**
18+
* Given a `'string'` or a `"string"` returns a JSON compatible string.
19+
* @param {string} str a TOML entry to parse
20+
* @param {Foreign} foreign foreign strings and dates
21+
* @returns {string}
22+
*/
23+
const getValue = (str, foreign) => jsonParse(
24+
str.replace(/(\S+?)\s*=/g, '"$1":'),
25+
(_, value) => typeof value === 'string' ?
26+
foreign[value[0]][value.slice(1)] :
27+
value
28+
);
29+
30+
/**
31+
* Crawl the `json` object via the given array of keys and handle array entries.
32+
* @param {string[]} keys a path with all keys to reach the entry
33+
* @param {Foreign} foreign foreign strings and dates
34+
* @param {object} entry the root entry of the TOML
35+
* @param {boolean} asArray handle array entries
36+
* @returns {object} the current entry to handle
37+
*/
38+
const getPath = (keys, foreign, entry, asArray) => {
39+
for (let i = 0, {length} = keys, last = length - 1; i < length; i++) {
40+
const key = getKey(keys[i], foreign);
41+
entry = entry[key] || (entry[key] = (asArray && (i === last) ? [] : {}));
42+
if (isArray(entry)) {
43+
if ((i === last) || !entry.length)
44+
entry.push({});
45+
entry = entry.at(-1);
46+
}
47+
}
48+
return entry;
49+
};
50+
51+
/**
52+
* Given a TOML text, removes stirngs and dates for easier parsing +
53+
* remove multi-line arrays to not need evaluation.
54+
* @param {string} toml the TOML text to map
55+
* @param {string[]} strings mapped strings
56+
* @param {Date[]} dates mapped Dates
57+
* @returns {[string, Foreign]}
58+
*/
59+
const mapForeign = (toml, strings, dates) => [
60+
toml
61+
// map strings in the TOML
62+
.replace(
63+
/(["'])(?:(?=(\\?))\2.)*?\1/g,
64+
value => `"s${strings.push(value.slice(1, -1)) - 1}"`
65+
)
66+
// map dates in the TOML
67+
.replace(
68+
/\d{2,}([:-]\d{2}){2}([ T:-][\dZ:-]+)?/g,
69+
value => `"d${dates.push(new Date(value)) - 1}"`
70+
)
71+
// avoid multi-line array entries
72+
.replace(/,\s*[\r\n]+/g, ', ')
73+
.replace(/\[\s*[\r\n]+/g, '[')
74+
.replace(/[\r\n]+\s*]/g, ']'),
75+
{s: strings, d: dates}
76+
];
77+
78+
/**
79+
* Given a simple subset of a TOML file, returns its JS equivalent.
80+
* @param {string} toml the TOML text to parse
81+
* @returns {object} the TOML equivalent as JSON serializable
82+
*/
83+
const parse = toml => {
84+
const [text, foreign] = mapForeign(toml, [], []);
85+
const json = {};
86+
let entry = json;
87+
for (let line of text.split(/[\r\n]+/)) {
88+
if ((line = line.trim()) && !line.startsWith('#')) {
89+
if (/^(\[+)(.*?)\]+/.test(line))
90+
entry = getPath(RegExp.$2.trim().split('.'), foreign, json, RegExp.$1 !== '[');
91+
else if (/^(\S+?)\s*=([^#]+)/.test(line)) {
92+
const {$1: key, $2: value} = RegExp;
93+
entry[getKey(key, foreign)] = getValue(value.trim(), foreign);
94+
}
95+
}
96+
}
97+
return json;
98+
};
99+
100+
/* c8 ignore stop */
101+
102+
export { parse };

docs/toml-CkEFU7ly.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/zip-B0L2NISB.js

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/zip-B0L2NISB.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esm/interpreter/webr.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { create } from 'gc-hook';
2+
import { dedent } from '../utils.js';
3+
import { fetchFiles, fetchJSModules, fetchPaths } from './_utils.js';
4+
import { io, stdio } from './_io.js';
5+
6+
const type = 'webr';
7+
const r = new WeakMap();
8+
9+
// REQUIRES INTEGRATION TEST
10+
/* c8 ignore start */
11+
const run = async (interpreter, code) => {
12+
const { shelter, destroy, io } = r.get(interpreter);
13+
const { output, result } = await shelter.captureR(dedent(code));
14+
for (const { type, data } of output) io[type](data);
15+
// this is a double proxy but it's OK as the consumer
16+
// of the result here needs to invoke explicitly a conversion
17+
// or trust the `(await p.toJs()).values` returns what's expected.
18+
return create(result, destroy, { token: false });
19+
};
20+
21+
export default {
22+
type,
23+
experimental: true,
24+
module: (version = '0.3.2') =>
25+
`https://cdn.jsdelivr.net/npm/webr@${version}/dist/webr.mjs`,
26+
async engine(module, config) {
27+
const { get } = stdio();
28+
const interpreter = new module.WebR();
29+
await get(interpreter.init().then(() => interpreter));
30+
const shelter = await new interpreter.Shelter();
31+
r.set(interpreter, {
32+
module,
33+
shelter,
34+
destroy: shelter.destroy.bind(shelter),
35+
io: io.get(interpreter),
36+
});
37+
if (config.files) await fetchFiles(this, interpreter, config.files);
38+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
39+
if (config.js_modules) await fetchJSModules(config.js_modules);
40+
return interpreter;
41+
},
42+
// Fallback to globally defined module fields (i.e. $xworker)
43+
registerJSModule(_, name) {
44+
console.warn(`Experimental interpreter: module ${name} is not supported (yet)`);
45+
// TODO: as complex JS objects / modules are not allowed
46+
// it's not clear how we can bind anything or import a module
47+
// in a context that doesn't understand methods from JS
48+
// https://docs.r-wasm.org/webr/latest/convert-js-to-r.html#constructing-r-objects-from-javascript-objects
49+
},
50+
run,
51+
runAsync: run,
52+
async runEvent(interpreter, code, event) {
53+
// TODO: WebR cannot convert exoteric objects or any literal
54+
// to an easy to reason about data/frame ... that convertion
55+
// is reserved for the future:
56+
// https://docs.r-wasm.org/webr/latest/convert-js-to-r.html#constructing-r-objects-from-javascript-objects
57+
await interpreter.evalRVoid(`${code}(event)`, {
58+
env: { event: { type: [ event.type ] } }
59+
});
60+
},
61+
transform: (_, value) => {
62+
console.log('transforming', value);
63+
return value;
64+
},
65+
writeFile: () => {
66+
throw new Error(`writeFile is not supported in ${type}`);
67+
},
68+
};
69+
/* c8 ignore stop */

esm/interpreters.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,6 @@ import micropython from './interpreter/micropython.js';
6262
import pyodide from './interpreter/pyodide.js';
6363
import ruby_wasm_wasi from './interpreter/ruby-wasm-wasi.js';
6464
import wasmoon from './interpreter/wasmoon.js';
65-
for (const interpreter of [micropython, pyodide, ruby_wasm_wasi, wasmoon])
65+
import webr from './interpreter/webr.js';
66+
for (const interpreter of [micropython, pyodide, ruby_wasm_wasi, wasmoon, webr])
6667
register(interpreter);

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@
2323
"size:module": "echo module is $(cat dist/index.js | brotli | wc -c) bytes once compressed",
2424
"size:worker": "echo worker is $(cat esm/worker/xworker.js | brotli | wc -c) bytes once compressed",
2525
"ts": "rm -rf types && tsc -p .",
26-
"update:interpreters": "npm run version:pyodide && npm run version:wasmoon && npm run version:micropython && npm run version:ruby-wasm-wasi && node rollup/update_versions.cjs && npm run build && npm run test",
26+
"update:interpreters": "npm run version:pyodide && npm run version:wasmoon && npm run version:webr && npm run version:micropython && npm run version:ruby-wasm-wasi && node rollup/update_versions.cjs && npm run build && npm run test",
2727
"version:micropython": "npm view @micropython/micropython-webassembly-pyscript version>versions/micropython",
2828
"version:pyodide": "npm view pyodide version>versions/pyodide",
2929
"version:ruby-wasm-wasi": "git ls-remote --tags --refs --sort='v:refname' https://github.com/ruby/ruby.wasm.git | grep 'tags/[[:digit:]]\\.' | tail -n1 | sed 's/.*\\///'>versions/ruby-wasm-wasi",
30-
"version:wasmoon": "npm view wasmoon version>versions/wasmoon"
30+
"version:wasmoon": "npm view wasmoon version>versions/wasmoon",
31+
"version:webr": "npm view webr version>versions/webr"
3132
},
3233
"keywords": [
3334
"polyscript",
@@ -89,6 +90,6 @@
8990
"to-json-callback": "^0.1.1"
9091
},
9192
"worker": {
92-
"blob": "sha256-x9FnCN9Oz1l5i3eTcOPg2IrqbdU7VfskuTHKznW4/L0="
93+
"blob": "sha256-iylBcz+1PGBPlUUA6Bb01fPXFB5kf89nm/gclV2I0Ko="
9394
}
9495
}

test/webr.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
6+
<title>WebR</title>
7+
<link rel="stylesheet" href="style.css">
8+
<script type="module" src="/dist/index.js"></script>
9+
</head>
10+
<body>
11+
<script type="webr">
12+
print_version <- function(event) {
13+
if (event["type"] == "click")
14+
print(R.version.string)
15+
}
16+
</script>
17+
<button webr-click="print_version">webr version</button>
18+
</body>
19+
</html>

0 commit comments

Comments
 (0)