Implement a simple web playground REPL with emscripten

This commit is contained in:
Laria 2022-06-05 22:31:02 +02:00
parent 64f2be5afc
commit 2c04e57a0c
4 changed files with 281 additions and 0 deletions

18
webpage/build.sh Executable file
View file

@ -0,0 +1,18 @@
#!/bin/sh
set -e
PACKAGE=$(sed -n 's/^PACKAGE\s*=\s*//p' ../Makefile)
VERSION=$(sed -n 's/^VERSION\s*=\s*//p' ../Makefile)
(
cd ..
make dist-gzip
)
cd playground
tar xzf ../../"$PACKAGE-$VERSION".tar.gz
rm -rf dist
mv "$PACKAGE-$VERSION" dist
cd dist
emconfigure ./configure CFLAGS="-O2" LDFLAGS="-sASYNCIFY"
cd src
emmake make -j"$(nproc)" libapfl.a
cd ../..
emcc -sASYNCIFY -O3 -oplayground.js playground.c dist/src/libapfl.a

3
webpage/playground/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
dist/
playground.wasm
playground.js

View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>apfl Playground</title>
<style type="text/css">
.apfl_playground {
--bg: #E0E0E0;
--fg: #101010;
--err: #FF0086;
--prompt-bg: #ececec;
--prompt-placeholder: #555;
}
@media screen and (prefers-color-scheme: dark) {
html {
background: #000;
}
.apfl_playground {
--bg: #101010;
--fg: #E0E0E0;
--err: #EC3308;
--prompt-bg: #333;
--prompt-placeholder: #ccc;
}
}
.apfl_playground {
background: var(--bg);
color: var(--fg);
padding: 0;
border-radius: 4px;
font-family: monospace;
font-size: 1em;
overflow: hidden;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 30%), 0 0 2px rgba(0,0,0,30%);
}
.apfl_playground .error {
color: var(--err);
}
.apfl_playground_output {
margin: 0;
padding: 4px 10px 0;
min-height: 50px;
max-height: 80vh;
overflow-y: auto;
}
.apfl_playground form {
display: flex;
padding: 2px 10px 4px;
background: var(--prompt-bg);
margin-top: 2px;
}
.apfl_playground_input {
flex-grow: 1;
background: transparent;
border: 0;
font-family: monospace;
padding: 0;
margin: 0;
font-size: 1em;
color: inherit;
}
.apfl_playground_input::placeholder {
font-style: italic;
color: var(--prompt-placeholder);
}
.apfl_playground_prompt {
padding-right: 1ch;
}
</style>
<script>
var input_resolve = null;
var queue = [];
function playground_write(error, s) {
var span = document.createElement("span");
if (error) {
span.classList.add("error");
}
var lines = s.split("\n");
var scroll = false;
for (var i = 0 ; i < lines.length; i++) {
if (i > 0) {
span.appendChild(document.createElement("br"));
scroll = true;
}
var line = lines[i];
if (line != "") {
span.appendChild(document.createTextNode(lines[i]));
}
}
output = document.getElementById("output");
output.append(span);
if (scroll) {
output.scrollTop = output.scrollHeight - output.clientHeight;
}
}
function enqueue(s) {
if (input_resolve) {
input_resolve(s);
input_resolve = null;
} else {
queue.push(s);
}
}
function playground_get_input(need) {
document.getElementById("prompt").innerText = need ? "..." : ">";
if (queue.length > 0) {
return Promise.resolve(queue.shift());
} else {
return new Promise(function (resolve, _) {
input_resolve = resolve;
});
}
}
function do_submit(ev) {
ev.preventDefault();
var input = document.getElementById("input");
var s = input.value;
input.value = "";
if (s == "") {
return;
}
s += "\n";
playground_write(false, document.getElementById("prompt").innerText + " " + s);
enqueue(s);
}
</script>
<script type="text/javascript" src="playground.js"></script>
</head>
<body>
<div class="apfl_playground" id="terminal">
<pre class="apfl_playground_output" id="output"></pre>
<form onsubmit="do_submit(event)">
<span class="apfl_playground_prompt" id="prompt">&gt;</span>
<input type="text" class="apfl_playground_input" id="input" placeholder="code ...">
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,117 @@
#include <emscripten.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "dist/src/apfl.h"
#include "dist/src/format.h"
struct playground_source_reader_data {
char *str;
size_t len;
size_t off;
bool eof;
};
EM_ASYNC_JS(char *, get_input, (int need), {
var s = await window.playground_get_input(!!need);
var lengthBytes = lengthBytesUTF8(s)+1;
var stringOnWasmHeap = _malloc(lengthBytes);
stringToUTF8(s, stringOnWasmHeap, lengthBytes);
return stringOnWasmHeap;
});
EM_JS(void, web_writer, (int error, const char* str, int size), {
window.playground_write(!!error, UTF8ToString(str, size));
});
static bool
web_fmt_write(void *opaque, const char *buf, size_t len)
{
int error = (int)opaque;
web_writer(error, buf, (int)len);
return true;
}
static bool
playground_source_reader_cb(void *context, char *buf, size_t *len, bool need)
{
struct playground_source_reader_data *r = context;
size_t remain = r->len - r->off;
if (remain == 0) {
free(r->str);
r->str = get_input(need ? 1 : 0);
assert(r->str != NULL);
r->len = strlen(r->str);
r->off = 0;
}
remain = r->len - r->off;
assert(remain > 0);
if (remain < *len) {
*len = remain;
}
memcpy(buf, r->str+r->off, *len);
r->off += *len;
return true;
}
int
main(void)
{
int rv = 0;
struct apfl_format_writer w_ok = {.write = web_fmt_write, .opaque = (void *)0};
struct apfl_format_writer w_err = {.write = web_fmt_write, .opaque = (void *)1};
apfl_ctx ctx = apfl_ctx_new(apfl_allocator_default());
if (ctx == NULL) {
assert(apfl_format_put_string(w_err, "Failed to init the context\n"));
return 1;
}
struct playground_source_reader_data source_reader_data = {
.str = NULL,
.len = 0,
.off = 0,
.eof = false,
};
apfl_iterative_runner runner = apfl_iterative_runner_new(ctx, (struct apfl_source_reader) {
.callback = playground_source_reader_cb,
.opaque = &source_reader_data,
});
if (runner == NULL) {
apfl_ctx_destroy(ctx);
assert(apfl_format_put_string(w_err, "Failed to init runner\n"));
return 1;
}
while (apfl_iterative_runner_next(runner)) {
switch (apfl_iterative_runner_get_result(runner)) {
case APFL_RESULT_OK :
assert(apfl_debug_print_val(ctx, -1, w_ok));
break;
case APFL_RESULT_ERR:
assert(apfl_debug_print_val(ctx, -1, w_err));
assert(apfl_format_put_string(w_err, "Error occurred during evaluation.\n"));
break;
case APFL_RESULT_ERR_FATAL:
assert(apfl_format_put_string(w_err, "Fatal error occurred during evaluation.\n"));
rv = 1;
goto exit;
}
}
exit:
free(source_reader_data.str);
apfl_iterative_runner_destroy(runner);
apfl_ctx_destroy(ctx);
return rv;
}