Implement a simple web playground REPL with emscripten
This commit is contained in:
parent
64f2be5afc
commit
2c04e57a0c
4 changed files with 281 additions and 0 deletions
18
webpage/build.sh
Executable file
18
webpage/build.sh
Executable 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
3
webpage/playground/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
dist/
|
||||
playground.wasm
|
||||
playground.js
|
||||
143
webpage/playground/index.html
Normal file
143
webpage/playground/index.html
Normal 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">></span>
|
||||
<input type="text" class="apfl_playground_input" id="input" placeholder="code ...">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
117
webpage/playground/playground.c
Normal file
117
webpage/playground/playground.c
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue