Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions wasm/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@
"xterm": "^3.8.0"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "0.2.0",
"copy-webpack-plugin": "^4.5.2",
"css-loader": "^2.0.1",
"@wasm-tool/wasm-pack-plugin": "^1.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.1",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"raw-loader": "^1.0.0",
"mini-css-extract-plugin": "^0.9.0",
"raw-loader": "^4.0.0",
"serve": "^11.0.2",
"start-server-and-test": "^1.7.11",
"webpack": "^4.16.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5"
Expand All @@ -25,8 +24,7 @@
"dev": "webpack-dev-server -d",
"build": "webpack",
"dist": "webpack --mode production",
"test": "cd ../tests; pipenv run pytest",
"ci": "start-server-and-test 'npm run build && serve dist/ -n -p 8080' :8080 test"
"test": "webpack && cd ../tests && pipenv run pytest"
},
"repository": {
"type": "git",
Expand Down
68 changes: 68 additions & 0 deletions wasm/demo/snippets/asyncbrowser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import browser
import functools


# just setting up the framework, skip to the bottom to see the real code

ready = object()
go = object()


def run(coro, *, payload=None, error=False):
send = coro.throw if error else coro.send
try:
cmd = send(payload)
except StopIteration:
return
if cmd is ready:
coro.send(
(
lambda *args: run(coro, payload=args),
lambda *args: run(coro, payload=args, error=True),
)
)
elif cmd is go:
pass
else:
raise RuntimeError(f"expected cmd to be ready or go, got {cmd}")


class JSFuture:
def __init__(self, prom):
self._prom = prom

def __await__(self):
done, error = yield ready
self._prom.then(done, error)
res, = yield go
return res


def wrap_prom_func(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
return await JSFuture(func(*args, **kwargs))

return wrapper


fetch = wrap_prom_func(browser.fetch)

###################
# Real code start #
###################


async def main(delay):
url = f"https://httpbin.org/delay/{delay}"
print(f"fetching {url}...")
res = await fetch(
url, response_format="json", headers={"X-Header-Thing": "rustpython is neat!"}
)
print(f"got res from {res['url']}:")
print(res, end="\n\n")


for delay in range(3):
run(main(delay))
print()
12 changes: 8 additions & 4 deletions wasm/demo/src/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<p>
RustPython is a Python interpreter written in Rust. This demo is
compiled from Rust to WebAssembly so it runs in the browser.<br>
Please input your Python code below and click <kbd>Run</kbd>
Input your Python code below and click <kbd>Run</kbd>
(or <kbd>Ctrl+Enter</kbd>), or you can open up your
browser's devtools and play with <code>rp.pyEval('print("a")')</code>
browser's devtools and play with <code>rp.pyEval('1 + 1')</code>
</p>
<div id="code-wrapper">
<textarea id="code">
Expand All @@ -31,9 +31,13 @@
<textarea id="console" readonly>Loading...</textarea>

<h3>Interactive shell</h3>
<div id="terminal"></div>
<div id="terminal"></div>

<p>Here's some info regarding the <code>rp.pyEval()</code> function</p>
<p>
Here's some info regarding the <code>rp.pyEval()</code>,
<code>rp.pyExec()</code>, and <code>rp.pyExecSingle()</code>
functions
</p>
<ul>
<li>
You can return variables from python and get them returned to
Expand Down
161 changes: 154 additions & 7 deletions wasm/demo/src/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,158 @@
import './style.css';
import 'codemirror/lib/codemirror.css';
import 'xterm/lib/xterm.css';
import CodeMirror from 'codemirror';
import 'codemirror/mode/python/python';
import 'codemirror/addon/comment/comment';
import 'codemirror/lib/codemirror.css';
import { Terminal } from 'xterm';
import LocalEchoController from 'local-echo';

let rp;

// A dependency graph that contains any wasm must all be imported
// asynchronously. This `index.js` file does the single async import, so
// that no one else needs to worry about it again.
import('./main.js').catch(e => {
console.error('Error importing `main.js`:', e);
document.getElementById('error').textContent = e;
// A dependency graph that contains any wasm must be imported asynchronously.
import('rustpython')
.then(rustpy => {
rp = rustpy;
// so people can play around with it
window.rp = rustpy;
onReady();
})
.catch(e => {
console.error('Error importing `rustpython`:', e);
document.getElementById('error').textContent = e;
});

const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
extraKeys: {
'Ctrl-Enter': runCodeFromTextarea,
'Cmd-Enter': runCodeFromTextarea,
'Shift-Tab': 'indentLess',
'Ctrl-/': 'toggleComment',
'Cmd-/': 'toggleComment',
Tab: editor => {
var spaces = Array(editor.getOption('indentUnit') + 1).join(' ');
editor.replaceSelection(spaces);
}
},
lineNumbers: true,
mode: 'text/x-python',
indentUnit: 4,
autofocus: true
});

const consoleElement = document.getElementById('console');
const errorElement = document.getElementById('error');

function runCodeFromTextarea() {
// Clean the console and errors
consoleElement.value = '';
errorElement.textContent = '';

const code = editor.getValue();
try {
rp.pyExec(code, {
stdout: output => {
const shouldScroll =
consoleElement.scrollHeight - consoleElement.scrollTop ===
consoleElement.clientHeight;
consoleElement.value += output;
if (shouldScroll) {
consoleElement.scrollTop = consoleElement.scrollHeight;
}
}
});
} catch (err) {
if (err instanceof WebAssembly.RuntimeError) {
err = window.__RUSTPYTHON_ERROR || err;
}
errorElement.textContent = err;
console.error(err);
}
}

const snippets = document.getElementById('snippets');

function updateSnippet() {
const selected = snippets.value;

// the require here creates a webpack context; it's fine to use it
// dynamically.
// https://webpack.js.org/guides/dependency-management/
const {
default: snippet
} = require(`raw-loader!../snippets/${selected}.py`);

editor.setValue(snippet);
Comment thread
coolreader18 marked this conversation as resolved.
runCodeFromTextarea();
}

const term = new Terminal();
term.open(document.getElementById('terminal'));

const localEcho = new LocalEchoController(term);

let terminalVM;

function getPrompt(name) {
terminalVM.exec(`
try:
import sys as __sys
__prompt = __sys.${name}
except:
__prompt = ''
finally:
del __sys
`);
return String(terminalVM.eval('__prompt'));
}

async function readPrompts() {
let continuing = false;

while (true) {
const ps1 = getPrompt('ps1');
const ps2 = getPrompt('ps2');
let input;
if (continuing) {
const prom = localEcho.read(ps2, ps2);
localEcho._activePrompt.prompt = ps1;
localEcho._input = localEcho.history.entries.pop() + '\n';
localEcho._cursor = localEcho._input.length;
localEcho._active = true;
input = await prom;
if (!input.endsWith('\n')) continue;
} else {
input = await localEcho.read(ps1, ps2);
}
try {
terminalVM.execSingle(input);
} catch (err) {
if (err instanceof SyntaxError && err.message.includes('EOF')) {
continuing = true;
continue;
} else if (err instanceof WebAssembly.RuntimeError) {
err = window.__RUSTPYTHON_ERROR || err;
}
localEcho.println(err);
}
continuing = false;
}
}

function onReady() {
snippets.addEventListener('change', updateSnippet);
document
.getElementById('run-btn')
.addEventListener('click', runCodeFromTextarea);
// Run once for demo
runCodeFromTextarea();

terminalVM = rp.vmStore.init('term_vm');
terminalVM.setStdout(data => localEcho.println(data));
readPrompts().catch(err => console.error(err));

// so that the test knows that we're ready
const readyElement = document.createElement('div');
readyElement.id = 'rp_loaded';
document.head.appendChild(readyElement);
}
Loading