diff --git a/webpage/build.sh b/webpage/build.sh
new file mode 100755
index 0000000..b539682
--- /dev/null
+++ b/webpage/build.sh
@@ -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
diff --git a/webpage/playground/.gitignore b/webpage/playground/.gitignore
new file mode 100644
index 0000000..e603fde
--- /dev/null
+++ b/webpage/playground/.gitignore
@@ -0,0 +1,3 @@
+dist/
+playground.wasm
+playground.js
diff --git a/webpage/playground/index.html b/webpage/playground/index.html
new file mode 100644
index 0000000..c3b9df3
--- /dev/null
+++ b/webpage/playground/index.html
@@ -0,0 +1,143 @@
+
+
+
+
+ apfl Playground
+
+
+
+
+
+
+
+
+
diff --git a/webpage/playground/playground.c b/webpage/playground/playground.c
new file mode 100644
index 0000000..049bb1f
--- /dev/null
+++ b/webpage/playground/playground.c
@@ -0,0 +1,117 @@
+#include
+
+#include
+#include
+#include
+#include
+
+#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;
+}