diff --git a/src/runtime/c/gu/exn.c b/src/runtime/c/gu/exn.c index c6c7652d6..951445dd7 100644 --- a/src/runtime/c/gu/exn.c +++ b/src/runtime/c/gu/exn.c @@ -18,12 +18,24 @@ gu_exn_is_raised(GuExn* err) { return err && (err->state == GU_EXN_RAISED); } +GU_API_DECL void +gu_exn_clear(GuExn* err) { + err->caught = NULL; + err->state = GU_EXN_OK; +} + GU_API bool gu_exn_caught_(GuExn* err, const char* type) { return (err->caught && strcmp(err->caught, type) == 0); } +GU_API_DECL void* +gu_exn_caught_data(GuExn* err) +{ + return err->data.data; +} + GU_API void gu_exn_block(GuExn* err) { diff --git a/src/runtime/c/gu/exn.h b/src/runtime/c/gu/exn.h index 6f5d0ff38..968c3f709 100644 --- a/src/runtime/c/gu/exn.h +++ b/src/runtime/c/gu/exn.h @@ -71,11 +71,13 @@ gu_new_exn(GuPool* pool); GU_API_DECL bool gu_exn_is_raised(GuExn* err); -static inline void -gu_exn_clear(GuExn* err) { - err->caught = NULL; - err->state = GU_EXN_OK; -} +// static inline void +// gu_exn_clear(GuExn* err) { +// err->caught = NULL; +// err->state = GU_EXN_OK; +// } +GU_API_DECL void +gu_exn_clear(GuExn* err); #define gu_exn_caught(err, type) \ (err->caught && strcmp(err->caught, #type) == 0) @@ -83,11 +85,13 @@ gu_exn_clear(GuExn* err) { GU_API_DECL bool gu_exn_caught_(GuExn* err, const char* type); -static inline const void* -gu_exn_caught_data(GuExn* err) -{ - return err->data.data; -} +// static inline const void* +// gu_exn_caught_data(GuExn* err) +// { +// return err->data.data; +// } +GU_API_DECL void* +gu_exn_caught_data(GuExn* err); /// Temporarily block a raised exception. GU_API_DECL void diff --git a/src/runtime/javascript/.gitignore b/src/runtime/javascript/.gitignore new file mode 100644 index 000000000..425c5e964 --- /dev/null +++ b/src/runtime/javascript/.gitignore @@ -0,0 +1 @@ +.libs/ diff --git a/src/runtime/c/Dockerfile-wasm b/src/runtime/javascript/Dockerfile similarity index 93% rename from src/runtime/c/Dockerfile-wasm rename to src/runtime/javascript/Dockerfile index d58f8d301..13d37dc23 100644 --- a/src/runtime/c/Dockerfile-wasm +++ b/src/runtime/javascript/Dockerfile @@ -16,8 +16,7 @@ COPY \ RUN autoreconf -i RUN emconfigure ./configure RUN emmake make -RUN emcc .libs/libgu.a .libs/libpgf.a -o pgf.html \ - -sFORCE_FILESYSTEM \ +RUN emcc .libs/libgu.a .libs/libpgf.a -o pgf.js \ -sALLOW_MEMORY_GROWTH \ -sEXPORTED_FUNCTIONS="\ _pgf_read,\ diff --git a/src/runtime/javascript/README.md b/src/runtime/javascript/README.md new file mode 100644 index 000000000..efea0fd5b --- /dev/null +++ b/src/runtime/javascript/README.md @@ -0,0 +1,11 @@ +# JavaScript runtime using Web Assembly + +This folder contains very early work experimenting with a pure JavaScript runtime, +copiled to Web Assembly (WASM) using [Emscripten](https://emscripten.org/). + +1. Compile the WASM files (inside Docker) using `build-wasm.js`, placing them in `.libs/` +2. Test in Node.js by running `node test-node.js [path to PGF]` +3. Test in a web browser + a. Start a server with `npx serve -l 41296` + b. Browse to `http://localhost:41296/test-web.html` + c. Check JavaScript console diff --git a/src/runtime/javascript/build-wasm.sh b/src/runtime/javascript/build-wasm.sh index 1f70bc39f..525053318 100755 --- a/src/runtime/javascript/build-wasm.sh +++ b/src/runtime/javascript/build-wasm.sh @@ -1,10 +1,10 @@ #! /usr/bin/env bash set -e -cd ../c # Build inside Docker image IMAGE="gf/build-c-runtime-wasm" -docker build . --file Dockerfile-wasm --tag $IMAGE +docker build ../c --file Dockerfile --tag $IMAGE # Copy bulit files from container to host -docker run --rm --volume "$PWD":/tmp/host $IMAGE bash -c "cp pgf.js pgf.wasm /tmp/host/" +mkdir -p .libs +docker run --rm --volume "$PWD":/tmp/host $IMAGE bash -c "cp pgf.js pgf.wasm /tmp/host/.libs/" diff --git a/src/runtime/javascript/jspgf.js b/src/runtime/javascript/jspgf.js new file mode 100644 index 000000000..5006e7b23 --- /dev/null +++ b/src/runtime/javascript/jspgf.js @@ -0,0 +1,104 @@ +/** + * This module is the high-level JavaScript wrapper around the WASM-compiled version. + */ + +function mkAPI(Module) { + + function checkError(err) { + if (Module._gu_exn_is_raised(err)) { + console.error('raised'); + + const isCaught = Module.ccall( + 'gu_exn_caught_', + 'boolean', + ['number', 'string'], + [err, 'GuErrno'] + ); + if (isCaught) { + console.error('caught'); + + errDataPtr = Module._gu_exn_caught_data(err); + errno = Module.getValue(errDataPtr, '*'); + console.error('errno', errno); + } + + Module._gu_exn_clear(err); + return true; + } + return false; + } + + function readPGF(pgfPath) { + const pool = Module._gu_new_pool(); + + const tmp_pool = Module._gu_new_pool(); + const err = Module._gu_new_exn(tmp_pool); + + // const pgf = Module.ccall( + // 'pgf_read', + // 'number', + // ['string', 'number', 'number'], + // [pgfPath, pool, err] + // ); + const strPtr = Module.allocateUTF8(pgfPath); + const pgf = Module._pgf_read(strPtr, pool, err); + + if (checkError(err)) { + Module._free(tmp_pool); + throw new Error('Cannot read PGF'); + } + Module._free(tmp_pool); + return pgf; + } + + function abstractName(pgf) { + const namePtr = Module._pgf_abstract_name(pgf); + return Module.UTF8ToString(namePtr); + } + + function readExpr(exprStr) { + const tmp_pool = Module._gu_new_pool(); + + const strPtr = Module.allocateUTF8(exprStr); + const in_ = Module._gu_data_in(strPtr, exprStr.length, tmp_pool); + const err = Module._gu_new_exn(tmp_pool); + const pool = Module._gu_new_pool(); + const expr = Module._pgf_read_expr(in_, pool, tmp_pool, err); + Module._free(strPtr); + if (checkError(err)) { + throw new Error(); + } + if (expr == 0) { + throw new Error('Expression cannot be parsed'); + } + Module._free(tmp_pool); + return expr; + } + + function arity(expr) { + return Module._pgf_expr_arity(expr); + } + + function showExpr(expr) { + const tmp_pool = Module._gu_new_pool(); + + const sb = Module._gu_new_string_buf(tmp_pool); + const out = Module._gu_string_buf_out(sb); + const err = Module._gu_new_exn(tmp_pool); + Module._pgf_print_expr(expr, 0, 0, out, err); + if (checkError(err)) { + Module._free(tmp_pool); + throw new Error('Cannot print expression'); + } + + const str = Module._gu_string_buf_data(sb); + return Module.UTF8ToString(str); + } + + return { readPGF, abstractName, readExpr, arity, showExpr }; +} + +// This allows us to use both from Node and in browser +if (typeof module != 'undefined') { + module.exports = mkAPI; +} diff --git a/src/runtime/javascript/test-node.js b/src/runtime/javascript/test-node.js new file mode 100644 index 000000000..92fc82b3f --- /dev/null +++ b/src/runtime/javascript/test-node.js @@ -0,0 +1,33 @@ +const Module = require('./.libs/pgf.js'); +const JSPGF = require('./jspgf.js')(Module); +const fs = require('fs'); +const path = require('path'); + +Module.onRuntimeInitialized = () => { + + // Read PGF path from args + if (process.argv.length > 2) { + const pgfPathHost = process.argv[2]; + + // Copy file into filesystem + const pgfPathFS = '/tmp/' + path.basename(pgfPathHost); + const rawPgf = fs.readFileSync(pgfPathHost); + Module.FS.writeFile(pgfPathFS, rawPgf); + + // Read PGF + const pgf = JSPGF.readPGF(pgfPathFS); + + // Print its name + console.log(JSPGF.abstractName(pgf)); + } + + // Parse expression + const expr = JSPGF.readExpr("Pred (Another (x f))"); + + // Show it + console.log(JSPGF.showExpr(expr)); + + // Print its arity + console.log('arity', JSPGF.arity(expr)); +} + diff --git a/src/runtime/javascript/test-web.html b/src/runtime/javascript/test-web.html new file mode 100644 index 000000000..65a21e377 --- /dev/null +++ b/src/runtime/javascript/test-web.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/runtime/javascript/test-web.js b/src/runtime/javascript/test-web.js new file mode 100644 index 000000000..83b72ca41 --- /dev/null +++ b/src/runtime/javascript/test-web.js @@ -0,0 +1,13 @@ +Module.onRuntimeInitialized = () => { + const JSPGF = mkAPI(Module); + + // Parse expression + const expr = JSPGF.readExpr("Pred (Another (x f))"); + + // Show it + console.log(JSPGF.showExpr(expr)); + + // Print its arity + console.log('arity', JSPGF.arity(expr)); +} +