Skip to content

Commit da66ec7

Browse files
committed
WIP: Testing REPL Capabilities
1 parent beb617c commit da66ec7

File tree

6 files changed

+353
-0
lines changed

6 files changed

+353
-0
lines changed
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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>Polyscript - MicroPython REPL</title>
7+
<style>
8+
*, *::before, *::after {
9+
box-sizing: border-box;
10+
}
11+
html {
12+
background-color: black;
13+
color: white;
14+
}
15+
</style>
16+
<script type="module">
17+
import { define } from '../../../index.js';
18+
import createTerminal from './repl.js';
19+
20+
define(null, {
21+
interpreter: 'micropython',
22+
hooks: {
23+
main: {
24+
onReady: async wrap => {
25+
const terminal = await createTerminal(wrap, output);
26+
globalThis.terminal = terminal;
27+
}
28+
}
29+
}
30+
});
31+
</script>
32+
</head>
33+
<body>
34+
<pre id="output"></pre>
35+
</body>
36+
</html>
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Xterm.js dependencies via CDN
2+
const CDN = 'https://cdn.jsdelivr.net/npm';
3+
const XTERM = '5.3.0';
4+
const ADDON_FIT = '0.10.0';
5+
const ADDON_WEB_LINKS = '0.11.0';
6+
7+
const { assign } = Object;
8+
9+
const dependencies = ({ ownerDocument }) => {
10+
const rel = 'stylesheet';
11+
const href = `${CDN}/xterm@${XTERM}/css/xterm.min.css`;
12+
const link = `link[rel="${rel}"][href="${href}"]`;
13+
if (!ownerDocument.querySelector(link)) {
14+
ownerDocument.head.append(
15+
assign(ownerDocument.createElement('link'), { rel, href })
16+
);
17+
}
18+
return [
19+
import(`${CDN}/xterm@${XTERM}/+esm`),
20+
import(`${CDN}/@xterm/addon-fit@${ADDON_FIT}/+esm`),
21+
import(`${CDN}/@xterm/addon-web-links@${ADDON_WEB_LINKS}/+esm`),
22+
];
23+
};
24+
25+
export default async ({ interpreter, io }, target) => {
26+
const [
27+
{ Terminal },
28+
{ FitAddon },
29+
{ WebLinksAddon },
30+
] = await Promise.all(dependencies(target));
31+
32+
const terminal = new Terminal({
33+
cursorBlink: true,
34+
cursorStyle: "block",
35+
theme: {
36+
background: "#191A19",
37+
foreground: "#F5F2E7",
38+
},
39+
});
40+
41+
const encoder = new TextEncoderStream;
42+
encoder.readable.pipeTo(
43+
new WritableStream({
44+
write(buffer) {
45+
for (const c of buffer)
46+
interpreter.replProcessChar(c);
47+
}
48+
})
49+
);
50+
51+
const missingReturn = new Uint8Array([13]);
52+
io.stdout = buffer => {
53+
// oddity of MicroPython WASM here:
54+
// it accepts \r but it doesn't forward it back
55+
if (buffer[0] === 10)
56+
terminal.write(missingReturn);
57+
terminal.write(buffer);
58+
};
59+
60+
const writer = encoder.writable.getWriter();
61+
terminal.onData(buffer => writer.write(buffer));
62+
63+
const fitAddon = new FitAddon;
64+
terminal.loadAddon(fitAddon);
65+
terminal.loadAddon(new WebLinksAddon);
66+
terminal.open(target);
67+
fitAddon.fit();
68+
terminal.focus();
69+
70+
interpreter.replInit();
71+
72+
return terminal;
73+
};

test/repl/micropython/index.html

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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>Polyscript - MicroPython REPL</title>
7+
<style>
8+
*, *::before, *::after {
9+
box-sizing: border-box;
10+
}
11+
html {
12+
background-color: black;
13+
color: white;
14+
}
15+
</style>
16+
<script type="module">
17+
import { define } from '../../../dist/index.js';
18+
import createTerminal from './repl.js';
19+
20+
define(null, {
21+
interpreter: 'micropython',
22+
hooks: {
23+
main: {
24+
onReady: async wrap => {
25+
const terminal = await createTerminal(wrap, output);
26+
globalThis.terminal = terminal;
27+
}
28+
}
29+
}
30+
});
31+
</script>
32+
</head>
33+
<body>
34+
<pre id="output"></pre>
35+
</body>
36+
</html>

test/repl/micropython/repl.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Xterm.js dependencies via CDN
2+
const CDN = 'https://cdn.jsdelivr.net/npm';
3+
const XTERM = '5.3.0';
4+
const ADDON_FIT = '0.10.0';
5+
const ADDON_WEB_LINKS = '0.11.0';
6+
7+
const { assign } = Object;
8+
9+
const dependencies = ({ ownerDocument }) => {
10+
const rel = 'stylesheet';
11+
const href = `${CDN}/xterm@${XTERM}/css/xterm.min.css`;
12+
const link = `link[rel="${rel}"][href="${href}"]`;
13+
if (!ownerDocument.querySelector(link)) {
14+
ownerDocument.head.append(
15+
assign(ownerDocument.createElement('link'), { rel, href })
16+
);
17+
}
18+
return [
19+
import(`${CDN}/xterm@${XTERM}/+esm`),
20+
import(`${CDN}/@xterm/addon-fit@${ADDON_FIT}/+esm`),
21+
import(`${CDN}/@xterm/addon-web-links@${ADDON_WEB_LINKS}/+esm`),
22+
];
23+
};
24+
25+
export default async ({ interpreter, io }, target) => {
26+
const [
27+
{ Terminal },
28+
{ FitAddon },
29+
{ WebLinksAddon },
30+
] = await Promise.all(dependencies(target));
31+
32+
const terminal = new Terminal({
33+
cursorBlink: true,
34+
cursorStyle: "block",
35+
theme: {
36+
background: "#191A19",
37+
foreground: "#F5F2E7",
38+
},
39+
});
40+
41+
const encoder = new TextEncoderStream;
42+
encoder.readable.pipeTo(
43+
new WritableStream({
44+
write(buffer) {
45+
for (const c of buffer)
46+
interpreter.replProcessChar(c);
47+
}
48+
})
49+
);
50+
51+
const missingReturn = new Uint8Array([13]);
52+
io.stdout = buffer => {
53+
// oddity of MicroPython WASM here:
54+
// it accepts \r but it doesn't forward it back
55+
if (buffer[0] === 10)
56+
terminal.write(missingReturn);
57+
terminal.write(buffer);
58+
};
59+
60+
const writer = encoder.writable.getWriter();
61+
terminal.onData(buffer => writer.write(buffer));
62+
63+
const fitAddon = new FitAddon;
64+
terminal.loadAddon(fitAddon);
65+
terminal.loadAddon(new WebLinksAddon);
66+
terminal.open(target);
67+
fitAddon.fit();
68+
terminal.focus();
69+
70+
interpreter.replInit();
71+
72+
return terminal;
73+
};

test/repl/pyodide/index.html

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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>Polyscript - Pyodide REPL</title>
7+
<style>
8+
*, *::before, *::after {
9+
box-sizing: border-box;
10+
}
11+
html {
12+
background-color: black;
13+
color: white;
14+
}
15+
</style>
16+
<script type="module">
17+
import { define } from '../../../dist/index.js';
18+
import createTerminal from './repl.js';
19+
20+
define(null, {
21+
interpreter: 'pyodide',
22+
hooks: {
23+
main: {
24+
onReady: async wrap => {
25+
const terminal = await createTerminal(wrap, output);
26+
globalThis.terminal = terminal;
27+
}
28+
}
29+
}
30+
});
31+
</script>
32+
</head>
33+
<body>
34+
<pre id="output"></pre>
35+
</body>
36+
</html>

test/repl/pyodide/repl.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Xterm.js dependencies via CDN
2+
const CDN = 'https://cdn.jsdelivr.net/npm';
3+
const XTERM = '5.3.0';
4+
const ADDON_FIT = '0.10.0';
5+
const ADDON_WEB_LINKS = '0.11.0';
6+
const READLINE = '1.1.1';
7+
8+
const { assign } = Object;
9+
10+
const dependencies = ({ ownerDocument }) => {
11+
const rel = 'stylesheet';
12+
const href = `${CDN}/xterm@${XTERM}/css/xterm.min.css`;
13+
const link = `link[rel="${rel}"][href="${href}"]`;
14+
if (!ownerDocument.querySelector(link)) {
15+
ownerDocument.head.append(
16+
assign(ownerDocument.createElement('link'), { rel, href })
17+
);
18+
}
19+
return [
20+
import(`${CDN}/xterm@${XTERM}/+esm`),
21+
import(`${CDN}/@xterm/addon-fit@${ADDON_FIT}/+esm`),
22+
import(`${CDN}/@xterm/addon-web-links@${ADDON_WEB_LINKS}/+esm`),
23+
import(`${CDN}/xterm-readline@${READLINE}/+esm`),
24+
];
25+
};
26+
27+
export default async ({ interpreter, run }, target) => {
28+
const [
29+
{ Terminal },
30+
{ FitAddon },
31+
{ WebLinksAddon },
32+
{ Readline },
33+
] = await Promise.all(dependencies(target));
34+
35+
const readline = new Readline();
36+
const terminal = new Terminal({
37+
cursorBlink: true,
38+
cursorStyle: "block",
39+
theme: {
40+
background: "#191A19",
41+
foreground: "#F5F2E7",
42+
},
43+
});
44+
45+
const encoder = new TextEncoderStream;
46+
const te = new TextEncoder;
47+
let lastBuffer = new Uint8Array(0);
48+
encoder.readable.pipeTo(
49+
new WritableStream({
50+
write(buffer) {
51+
lastBuffer = buffer;
52+
}
53+
})
54+
);
55+
56+
const { fromCharCode } = String;
57+
const isatty = true;
58+
interpreter.setStdout({
59+
isatty,
60+
write(buffer) {
61+
for (const c of buffer) {
62+
if (c === 10)
63+
terminal.write('\r');
64+
terminal.write(fromCharCode(c));
65+
}
66+
return buffer.length;
67+
},
68+
});
69+
interpreter.setStderr({
70+
isatty,
71+
write(buffer) {
72+
for (const c of buffer) {
73+
if (c === 10)
74+
terminal.write('\r');
75+
terminal.write(fromCharCode(c));
76+
}
77+
return buffer.length;
78+
},
79+
});
80+
interpreter.setStdin({
81+
isatty,
82+
stdin: () => readline.read(),
83+
});
84+
85+
const writer = encoder.writable.getWriter();
86+
terminal.onData(buffer => readline.write(buffer));
87+
88+
const fitAddon = new FitAddon;
89+
terminal.loadAddon(fitAddon);
90+
terminal.loadAddon(readline);
91+
terminal.loadAddon(new WebLinksAddon);
92+
terminal.open(target);
93+
fitAddon.fit();
94+
terminal.focus();
95+
96+
run('import code;code.interact()');
97+
98+
return terminal;
99+
};

0 commit comments

Comments
 (0)