summaryrefslogtreecommitdiff
path: root/embeds
diff options
context:
space:
mode:
authorDavid Thompson <dthompson2@worcester.edu>2024-07-02 18:39:58 -0400
committerDavid Thompson <dthompson2@worcester.edu>2024-07-08 11:50:53 -0400
commitb7170b01b47a825d9de5664a0e8865ee5f655908 (patch)
tree54bc45c3821a99de42cdef3bb46f852383761107 /embeds
parent0db5a7271177e9792bf8cb24827ba4afa26c7a6d (diff)
Add FRP with propagators post.
Diffstat (limited to 'embeds')
-rw-r--r--embeds/frp-color-picker/hoot/reflect.js738
-rw-r--r--embeds/frp-color-picker/hoot/reflect.wasmbin0 -> 5196 bytes
-rw-r--r--embeds/frp-color-picker/hoot/wtf8.wasmbin0 -> 1071 bytes
-rw-r--r--embeds/frp-color-picker/index.html26
-rw-r--r--embeds/frp-color-picker/propagators.css38
-rw-r--r--embeds/frp-color-picker/propagators.js30
-rw-r--r--embeds/frp-color-picker/propagators.wasmbin0 -> 458030 bytes
7 files changed, 832 insertions, 0 deletions
diff --git a/embeds/frp-color-picker/hoot/reflect.js b/embeds/frp-color-picker/hoot/reflect.js
new file mode 100644
index 0000000..b00874c
--- /dev/null
+++ b/embeds/frp-color-picker/hoot/reflect.js
@@ -0,0 +1,738 @@
+// -*- js2-basic-offset: 4 -*-
+class Char {
+ constructor(codepoint) {
+ this.codepoint = codepoint;
+ }
+ toString() {
+ let ch = String.fromCodePoint(this.codepoint);
+ if (ch.match(/[a-zA-Z0-9$[\]().]/)) return `#\\${ch}`;
+ return `#\\x${this.codepoint.toString(16)}`;
+ }
+}
+class Eof { toString() { return "#<eof>"; } }
+class Nil { toString() { return "#nil"; } }
+class Null { toString() { return "()"; } }
+class Unspecified { toString() { return "#<unspecified>"; } }
+
+class Complex {
+ constructor(real, imag) {
+ this.real = real;
+ this.imag = imag;
+ }
+ toString() {
+ const sign = this.imag >= 0 && Number.isFinite(this.imag) ? "+": "";
+ return `${flonum_to_string(this.real)}${sign}${flonum_to_string(this.imag)}i`;
+ }
+}
+class Fraction {
+ constructor(num, denom) {
+ this.num = num;
+ this.denom = denom;
+ }
+ toString() {
+ return `${this.num}/${this.denom}`;
+ }
+}
+
+class HeapObject {
+ constructor(reflector, obj) {
+ this.reflector = reflector;
+ this.obj = obj;
+ }
+ repr() { return this.toString(); } // Default implementation.
+}
+
+class Pair extends HeapObject {
+ toString() { return "#<pair>"; }
+ repr() {
+ let car_repr = repr(this.reflector.car(this));
+ let cdr_repr = repr(this.reflector.cdr(this));
+ if (cdr_repr == '()')
+ return `(${car_repr})`;
+ if (cdr_repr.charAt(0) == '(')
+ return `(${car_repr} ${cdr_repr.substring(1)}`;
+ return `(${car_repr} . ${cdr_repr})`;
+ }
+}
+class MutablePair extends Pair { toString() { return "#<mutable-pair>"; } }
+
+class Vector extends HeapObject {
+ toString() { return "#<vector>"; }
+ repr() {
+ let len = this.reflector.vector_length(this);
+ let out = '#(';
+ for (let i = 0; i < len; i++) {
+ if (i) out += ' ';
+ out += repr(this.reflector.vector_ref(this, i));
+ }
+ out += ')';
+ return out;
+ }
+}
+class MutableVector extends Vector {
+ toString() { return "#<mutable-vector>"; }
+}
+
+class Bytevector extends HeapObject {
+ toString() { return "#<bytevector>"; }
+ repr() {
+ let len = this.reflector.bytevector_length(this);
+ let out = '#vu8(';
+ for (let i = 0; i < len; i++) {
+ if (i) out += ' ';
+ out += this.reflector.bytevector_ref(this, i);
+ }
+ out += ')';
+ return out;
+ }
+}
+class MutableBytevector extends Bytevector {
+ toString() { return "#<mutable-bytevector>"; }
+}
+
+class Bitvector extends HeapObject {
+ toString() { return "#<bitvector>"; }
+ repr() {
+ let len = this.reflector.bitvector_length(this);
+ let out = '#*';
+ for (let i = 0; i < len; i++) {
+ out += this.reflector.bitvector_ref(this, i) ? '1' : '0';
+ }
+ return out;
+ }
+}
+class MutableBitvector extends Bitvector {
+ toString() { return "#<mutable-bitvector>"; }
+}
+
+class MutableString extends HeapObject {
+ toString() { return "#<mutable-string>"; }
+ repr() { return string_repr(this.reflector.string_value(this)); }
+}
+
+class Procedure extends HeapObject {
+ toString() { return "#<procedure>"; }
+ call(...arg) {
+ return this.reflector.call(this, ...arg);
+ }
+ async call_async(...arg) {
+ return await this.reflector.call_async(this, ...arg);
+ }
+}
+
+class Sym extends HeapObject {
+ toString() { return "#<symbol>"; }
+ repr() { return this.reflector.symbol_name(this); }
+}
+
+class Keyword extends HeapObject {
+ toString() { return "#<keyword>"; }
+ repr() { return `#:${this.reflector.keyword_name(this)}`; }
+}
+
+class Variable extends HeapObject { toString() { return "#<variable>"; } }
+class AtomicBox extends HeapObject { toString() { return "#<atomic-box>"; } }
+class HashTable extends HeapObject { toString() { return "#<hash-table>"; } }
+class WeakTable extends HeapObject { toString() { return "#<weak-table>"; } }
+class Fluid extends HeapObject { toString() { return "#<fluid>"; } }
+class DynamicState extends HeapObject { toString() { return "#<dynamic-state>"; } }
+class Syntax extends HeapObject { toString() { return "#<syntax>"; } }
+class Port extends HeapObject { toString() { return "#<port>"; } }
+class Struct extends HeapObject { toString() { return "#<struct>"; } }
+
+function instantiate_streaming(path, imports) {
+ if (typeof fetch !== 'undefined')
+ return WebAssembly.instantiateStreaming(fetch(path), imports);
+ let bytes;
+ if (typeof read !== 'undefined') {
+ bytes = read(path, 'binary');
+ } else if (typeof readFile !== 'undefined') {
+ bytes = readFile(path);
+ } else {
+ let fs = require('fs');
+ bytes = fs.readFileSync(path);
+ }
+ return WebAssembly.instantiate(bytes, imports);
+}
+
+class Scheme {
+ #instance;
+ #abi;
+ constructor(instance, abi) {
+ this.#instance = instance;
+ this.#abi = abi;
+ }
+
+ static async reflect(abi, {reflect_wasm_dir = '.'}) {
+ let debug = {
+ debug_str(x) { console.log(`reflect debug: ${x}`); },
+ debug_str_i32(x, y) { console.log(`reflect debug: ${x}: ${y}`); },
+ debug_str_scm: (x, y) => {
+ console.log(`reflect debug: ${x}: #<scm>`);
+ },
+ };
+ let reflect_wasm = reflect_wasm_dir + '/reflect.wasm';
+ let rt = {
+ die(tag, data) { throw new SchemeTrapError(tag, data); },
+ wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
+ string_to_wtf8(str) { return string_to_wtf8(str); },
+ };
+ let { module, instance } =
+ await instantiate_streaming(reflect_wasm, { abi, debug, rt });
+ return new Scheme(instance, abi);
+ }
+
+ #init_module(mod) {
+ mod.set_debug_handler({
+ debug_str(x) { console.log(`debug: ${x}`); },
+ debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
+ debug_str_scm: (x, y) => {
+ console.log(`debug: ${x}: ${repr(this.#to_js(y))}`);
+ },
+ });
+ mod.set_ffi_handler({
+ procedure_to_extern: (obj) => {
+ const proc = this.#to_js(obj);
+ return (...args) => {
+ return proc.call(...args);
+ };
+ }
+ });
+ let proc = new Procedure(this, mod.get_export('$load').value);
+ return proc.call();
+ }
+ static async load_main(path, opts = {}) {
+ let mod = await SchemeModule.fetch_and_instantiate(path, opts);
+ let reflect = await mod.reflect(opts);
+ return reflect.#init_module(mod);
+ }
+ async load_extension(path, opts = {}) {
+ opts = Object.assign({ abi: this.#abi }, opts);
+ let mod = await SchemeModule.fetch_and_instantiate(path, opts);
+ return this.#init_module(mod);
+ }
+
+ #to_scm(js) {
+ let api = this.#instance.exports;
+ if (typeof(js) == 'number') {
+ return api.scm_from_f64(js);
+ } else if (typeof(js) == 'bigint') {
+ if (BigInt(api.scm_most_negative_fixnum()) <= js
+ && js <= BigInt(api.scm_most_positive_fixnum()))
+ return api.scm_from_fixnum(Number(js));
+ return api.scm_from_bignum(js);
+ } else if (typeof(js) == 'boolean') {
+ return js ? api.scm_true() : api.scm_false();
+ } else if (typeof(js) == 'string') {
+ return api.scm_from_string(js);
+ } else if (typeof(js) == 'object') {
+ if (js instanceof Eof) return api.scm_eof();
+ if (js instanceof Nil) return api.scm_nil();
+ if (js instanceof Null) return api.scm_null();
+ if (js instanceof Unspecified) return api.scm_unspecified();
+ if (js instanceof Char) return api.scm_from_char(js.codepoint);
+ if (js instanceof HeapObject) return js.obj;
+ if (js instanceof Fraction)
+ return api.scm_from_fraction(this.#to_scm(js.num),
+ this.#to_scm(js.denom));
+ if (js instanceof Complex)
+ return api.scm_from_complex(js.real, js.imag);
+ return api.scm_from_extern(js);
+ } else if (typeof(js) == 'function') {
+ return api.scm_from_extern(js);
+ } else {
+ throw new Error(`unexpected; ${typeof(js)}`);
+ }
+ }
+
+ #to_js(scm) {
+ let api = this.#instance.exports;
+ let descr = api.describe(scm);
+ let handlers = {
+ fixnum: () => BigInt(api.fixnum_value(scm)),
+ char: () => new Char(api.char_value(scm)),
+ true: () => true,
+ false: () => false,
+ eof: () => new Eof,
+ nil: () => new Nil,
+ null: () => new Null,
+ unspecified: () => new Unspecified,
+ flonum: () => api.flonum_value(scm),
+ bignum: () => api.bignum_value(scm),
+ complex: () => new Complex(api.complex_real(scm),
+ api.complex_imag(scm)),
+ fraction: () => new Fraction(this.#to_js(api.fraction_num(scm)),
+ this.#to_js(api.fraction_denom(scm))),
+ pair: () => new Pair(this, scm),
+ 'mutable-pair': () => new MutablePair(this, scm),
+ vector: () => new Vector(this, scm),
+ 'mutable-vector': () => new MutableVector(this, scm),
+ bytevector: () => new Bytevector(this, scm),
+ 'mutable-bytevector': () => new MutableBytevector(this, scm),
+ bitvector: () => new Bitvector(this, scm),
+ 'mutable-bitvector': () => new MutableBitvector(this, scm),
+ string: () => api.string_value(scm),
+ 'mutable-string': () => new MutableString(this, scm),
+ procedure: () => new Procedure(this, scm),
+ symbol: () => new Sym(this, scm),
+ keyword: () => new Keyword(this, scm),
+ variable: () => new Variable(this, scm),
+ 'atomic-box': () => new AtomicBox(this, scm),
+ 'hash-table': () => new HashTable(this, scm),
+ 'weak-table': () => new WeakTable(this, scm),
+ fluid: () => new Fluid(this, scm),
+ 'dynamic-state': () => new DynamicState(this, scm),
+ syntax: () => new Syntax(this, scm),
+ port: () => new Port(this, scm),
+ struct: () => new Struct(this, scm),
+ 'extern-ref': () => api.extern_value(scm)
+ };
+ let handler = handlers[descr];
+ return handler ? handler() : scm;
+ }
+
+ call(func, ...args) {
+ let api = this.#instance.exports;
+ let argv = api.make_vector(args.length + 1, api.scm_false());
+ func = this.#to_scm(func);
+ api.vector_set(argv, 0, func);
+ for (let [idx, arg] of args.entries())
+ api.vector_set(argv, idx + 1, this.#to_scm(arg));
+ argv = api.call(func, argv);
+ let results = [];
+ for (let idx = 0; idx < api.vector_length(argv); idx++)
+ results.push(this.#to_js(api.vector_ref(argv, idx)))
+ return results;
+ }
+
+ call_async(func, ...args) {
+ return new Promise((resolve, reject) => {
+ this.call(func,
+ val => resolve(this.#to_js(val)),
+ err => reject(this.#to_js(err)),
+ ...args);
+ })
+ }
+
+ car(x) { return this.#to_js(this.#instance.exports.car(x.obj)); }
+ cdr(x) { return this.#to_js(this.#instance.exports.cdr(x.obj)); }
+
+ vector_length(x) { return this.#instance.exports.vector_length(x.obj); }
+ vector_ref(x, i) {
+ return this.#to_js(this.#instance.exports.vector_ref(x.obj, i));
+ }
+
+ bytevector_length(x) {
+ return this.#instance.exports.bytevector_length(x.obj);
+ }
+ bytevector_ref(x, i) {
+ return this.#instance.exports.bytevector_ref(x.obj, i);
+ }
+
+ bitvector_length(x) {
+ return this.#instance.exports.bitvector_length(x.obj);
+ }
+ bitvector_ref(x, i) {
+ return this.#instance.exports.bitvector_ref(x.obj, i) == 1;
+ }
+
+ string_value(x) { return this.#instance.exports.string_value(x.obj); }
+ symbol_name(x) { return this.#instance.exports.symbol_name(x.obj); }
+ keyword_name(x) { return this.#instance.exports.keyword_name(x.obj); }
+}
+
+class SchemeTrapError extends Error {
+ constructor(tag, data) { super(); this.tag = tag; this.data = data; }
+ // FIXME: data is raw Scheme object; would need to be reflected to
+ // have a toString.
+ toString() { return `SchemeTrap(${this.tag}, <data>)`; }
+}
+
+function string_repr(str) {
+ // FIXME: Improve to match Scheme.
+ return '"' + str.replace(/(["\\])/g, '\\$1').replace(/\n/g, '\\n') + '"';
+}
+
+function flonum_to_string(f64) {
+ if (Object.is(f64, -0)) {
+ return '-0.0';
+ } else if (Number.isFinite(f64)) {
+ let repr = f64 + '';
+ return /^-?[0-9]+$/.test(repr) ? repr + '.0' : repr;
+ } else if (Number.isNaN(f64)) {
+ return '+nan.0';
+ } else {
+ return f64 < 0 ? '-inf.0' : '+inf.0';
+ }
+}
+
+let async_invoke = typeof queueMicrotask !== 'undefined'
+ ? queueMicrotask
+ : thunk => setTimeout(thunk, 0);
+function async_invoke_later(thunk, jiffies) {
+ setTimeout(thunk, jiffies / 1000);
+}
+
+let wtf8_helper;
+
+function wtf8_to_string(wtf8) {
+ let { as_iter, iter_next } = wtf8_helper.exports;
+ let codepoints = [];
+ let iter = as_iter(wtf8);
+ for (let cp = iter_next(iter); cp != -1; cp = iter_next(iter))
+ codepoints.push(cp);
+
+ // Passing too many codepoints can overflow the stack.
+ let maxcp = 100000;
+ if (codepoints.length <= maxcp) {
+ return String.fromCodePoint(...codepoints);
+ }
+
+ // For converting large strings, concatenate several smaller
+ // strings.
+ let substrings = [];
+ let end = 0;
+ for (let start = 0; start != codepoints.length; start = end) {
+ end = Math.min(start + maxcp, codepoints.length);
+ substrings.push(String.fromCodePoint(...codepoints.slice(start, end)));
+ }
+ return substrings.join('');
+}
+
+function string_to_wtf8(str) {
+ let { string_builder, builder_push_codepoint, finish_builder } =
+ wtf8_helper.exports;
+ let builder = string_builder()
+ for (let cp of str)
+ builder_push_codepoint(builder, cp.codePointAt(0));
+ return finish_builder(builder);
+}
+
+async function load_wtf8_helper_module(reflect_wasm_dir = '') {
+ if (wtf8_helper) return;
+ let wtf8_wasm = reflect_wasm_dir + "/wtf8.wasm";
+ let { module, instance } = await instantiate_streaming(wtf8_wasm);
+ wtf8_helper = instance;
+}
+
+class SchemeModule {
+ #instance;
+ #io_handler;
+ #debug_handler;
+ #ffi_handler;
+ static #rt = {
+ bignum_from_string(str) { return BigInt(str); },
+ bignum_from_i32(n) { return BigInt(n); },
+ bignum_from_i64(n) { return n; },
+ bignum_from_u64(n) { return n < 0n ? 0xffff_ffff_ffff_ffffn + (n + 1n) : n; },
+ bignum_is_i64(n) {
+ return -0x8000_0000_0000_0000n <= n && n <= 0x7FFF_FFFF_FFFF_FFFFn;
+ },
+ bignum_is_u64(n) {
+ return 0n <= n && n <= 0xFFFF_FFFF_FFFF_FFFFn;
+ },
+ // This truncates; see https://tc39.es/ecma262/#sec-tobigint64.
+ bignum_get_i64(n) { return n; },
+
+ bignum_add(a, b) { return BigInt(a) + BigInt(b) },
+ bignum_sub(a, b) { return BigInt(a) - BigInt(b) },
+ bignum_mul(a, b) { return BigInt(a) * BigInt(b) },
+ bignum_lsh(a, b) { return BigInt(a) << BigInt(b) },
+ bignum_rsh(a, b) { return BigInt(a) >> BigInt(b) },
+ bignum_quo(a, b) { return BigInt(a) / BigInt(b) },
+ bignum_rem(a, b) { return BigInt(a) % BigInt(b) },
+ bignum_mod(a, b) {
+ let r = BigInt(a) % BigInt(b);
+ if ((b > 0n && r < 0n) || (b < 0n && r > 0n)) {
+ return b + r;
+ } else {
+ return r;
+ }
+ },
+ bignum_gcd(a, b) {
+ a = BigInt(a);
+ b = BigInt(b);
+ if (a < 0n) { a = -a; }
+ if (b < 0n) { b = -b; }
+ if (a == 0n) { return b; }
+ if (b == 0n) { return a; }
+
+ let r;
+ while (b != 0n) {
+ r = a % b;
+ a = b;
+ b = r;
+ }
+ return a;
+ },
+
+ bignum_logand(a, b) { return BigInt(a) & BigInt(b); },
+ bignum_logior(a, b) { return BigInt(a) | BigInt(b); },
+ bignum_logxor(a, b) { return BigInt(a) ^ BigInt(b); },
+ bignum_logsub(a, b) { return BigInt(a) & (~ BigInt(b)); },
+
+ bignum_lt(a, b) { return a < b; },
+ bignum_le(a, b) { return a <= b; },
+ bignum_eq(a, b) { return a == b; },
+
+ bignum_to_f64(n) { return Number(n); },
+
+ f64_is_nan(n) { return Number.isNaN(n); },
+ f64_is_infinite(n) { return !Number.isFinite(n); },
+
+ flonum_to_string,
+
+ string_upcase: Function.call.bind(String.prototype.toUpperCase),
+ string_downcase: Function.call.bind(String.prototype.toLowerCase),
+
+ make_weak_map() { return new WeakMap; },
+ weak_map_get(map, k, fail) {
+ const val = map.get(k);
+ return val === undefined ? fail: val;
+ },
+ weak_map_set(map, k, v) { return map.set(k, v); },
+ weak_map_delete(map, k) { return map.delete(k); },
+
+ fsqrt: Math.sqrt,
+ fsin: Math.sin,
+ fcos: Math.cos,
+ ftan: Math.tan,
+ fasin: Math.asin,
+ facos: Math.acos,
+ fatan: Math.atan,
+ fatan2: Math.atan2,
+ flog: Math.log,
+ fexp: Math.exp,
+
+ jiffies_per_second() { return 1000000; },
+ current_jiffy() { return performance.now() * 1000; },
+ current_second() { return Date.now() / 1000; },
+
+ async_invoke,
+ async_invoke_later,
+ promise_on_completed(p, kt, kf) { p.then(kt, kf); },
+ promise_complete(callback, val) { callback(val); },
+
+ // Wrap in functions to allow for lazy loading of the wtf8
+ // module.
+ wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
+ string_to_wtf8(str) { return string_to_wtf8(str); },
+
+ die(tag, data) { throw new SchemeTrapError(tag, data); }
+ };
+
+ constructor(instance) {
+ this.#instance = instance;
+ let open_file_error = (filename) => {
+ throw new Error('No file system access');
+ };
+ if (typeof printErr === 'function') { // v8/sm dev console
+ // On the console, try to use 'write' (v8) or 'putstr' (sm),
+ // as these don't add an extraneous newline. Unfortunately
+ // JSC doesn't have a printer that doesn't add a newline.
+ let write_no_newline =
+ typeof write === 'function' ? write
+ : typeof putstr === 'function' ? putstr : print;
+ // Use readline when available. v8 strips newlines so
+ // we need to add them back.
+ let read_stdin =
+ typeof readline == 'function' ? () => {
+ let line = readline();
+ if (line) {
+ return `${line}\n`;
+ } else {
+ return '\n';
+ }
+ }: () => '';
+ this.#io_handler = {
+ write_stdout: write_no_newline,
+ write_stderr: printErr,
+ read_stdin,
+ file_exists: (filename) => false,
+ open_input_file: open_file_error,
+ open_output_file: open_file_error,
+ close_file: () => undefined,
+ read_file: (handle, length) => 0,
+ write_file: (handle, length) => 0,
+ seek_file: (handle, offset, whence) => -1,
+ file_random_access: (handle) => false,
+ file_buffer_size: (handle) => 0,
+ file_buffer_ref: (handle, i) => 0,
+ file_buffer_set: (handle, i, x) => undefined,
+ delete_file: (filename) => undefined
+ };
+ } else if (typeof window !== 'undefined') { // web browser
+ this.#io_handler = {
+ write_stdout: console.log,
+ write_stderr: console.error,
+ read_stdin: () => '',
+ file_exists: (filename) => false,
+ open_input_file: open_file_error,
+ open_output_file: open_file_error,
+ close_file: () => undefined,
+ read_file: (handle, length) => 0,
+ write_file: (handle, length) => 0,
+ seek_file: (handle, offset, whence) => -1,
+ file_random_access: (handle) => false,
+ file_buffer_size: (handle) => 0,
+ file_buffer_ref: (handle, i) => 0,
+ file_buffer_set: (handle, i, x) => undefined,
+ delete_file: (filename) => undefined
+ };
+ } else { // nodejs
+ const fs = require('fs');
+ const process = require('process');
+ const bufLength = 1024;
+ const stdinBuf = Buffer.alloc(bufLength);
+ const SEEK_SET = 0, SEEK_CUR = 1, SEEK_END = 2;
+ this.#io_handler = {
+ write_stdout: console.log,
+ write_stderr: console.error,
+ read_stdin: () => {
+ let n = fs.readSync(process.stdin.fd, stdinBuf, 0, stdinBuf.length);
+ return stdinBuf.toString('utf8', 0, n);
+ },
+ file_exists: fs.existsSync.bind(fs),
+ open_input_file: (filename) => {
+ let fd = fs.openSync(filename, 'r');
+ return {
+ fd,
+ buf: Buffer.alloc(bufLength),
+ pos: 0
+ };
+ },
+ open_output_file: (filename) => {
+ let fd = fs.openSync(filename, 'w');
+ return {
+ fd,
+ buf: Buffer.alloc(bufLength),
+ pos: 0
+ };
+ },
+ close_file: (handle) => {
+ fs.closeSync(handle.fd);
+ },
+ read_file: (handle, count) => {
+ const n = fs.readSync(handle.fd, handle.buf, 0, count, handle.pos);
+ handle.pos += n;
+ return n;
+ },
+ write_file: (handle, count) => {
+ const n = fs.writeSync(handle.fd, handle.buf, 0, count, handle.pos);
+ handle.pos += n;
+ return n;
+ },
+ seek_file: (handle, offset, whence) => {
+ // There doesn't seem to be a way to ask NodeJS if
+ // a position is valid or not.
+ if (whence == SEEK_SET) {
+ handle.pos = offset;
+ return handle.pos;
+ } else if (whence == SEEK_CUR) {
+ handle.pos += offset;
+ return handle.pos;
+ }
+
+ // SEEK_END not supported.
+ return -1;
+ },
+ file_random_access: (handle) => {
+ return true;
+ },
+ file_buffer_size: (handle) => {
+ return handle.buf.length;
+ },
+ file_buffer_ref: (handle, i) => {
+ return handle.buf[i];
+ },
+ file_buffer_set: (handle, i, x) => {
+ handle.buf[i] = x;
+ },
+ delete_file: fs.rmSync.bind(fs)
+ };
+ }
+ this.#debug_handler = {
+ debug_str(x) { console.log(`debug: ${x}`); },
+ debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
+ debug_str_scm(x, y) { console.log(`debug: ${x}: #<scm>`); },
+ };
+ }
+ static async fetch_and_instantiate(path, { abi, reflect_wasm_dir = '.',
+ user_imports = {} }) {
+ await load_wtf8_helper_module(reflect_wasm_dir);
+ let io = {
+ write_stdout(str) { mod.#io_handler.write_stdout(str); },
+ write_stderr(str) { mod.#io_handler.write_stderr(str); },
+ read_stdin() { return mod.#io_handler.read_stdin(); },
+ file_exists(filename) { return mod.#io_handler.file_exists(filename); },
+ open_input_file(filename) { return mod.#io_handler.open_input_file(filename); },
+ open_output_file(filename) { return mod.#io_handler.open_output_file(filename); },
+ close_file(handle) { mod.#io_handler.close_file(handle); },
+ read_file(handle, length) { return mod.#io_handler.read_file(handle, length); },
+ write_file(handle, length) { return mod.#io_handler.write_file(handle, length); },
+ seek_file(handle, offset, whence) { return mod.#io_handler.seek_file(handle, offset, whence); },
+ file_random_access(handle) { return mod.#io_handler.file_random_access(handle); },
+ file_buffer_size(handle) { return mod.#io_handler.file_buffer_size(handle); },
+ file_buffer_ref(handle, i) { return mod.#io_handler.file_buffer_ref(handle, i); },
+ file_buffer_set(handle, i, x) { return mod.#io_handler.file_buffer_set(handle, i, x); },
+ delete_file(filename) { mod.#io_handler.delete_file(filename); }
+ };
+ let debug = {
+ debug_str(x) { mod.#debug_handler.debug_str(x); },
+ debug_str_i32(x, y) { mod.#debug_handler.debug_str_i32(x, y); },
+ debug_str_scm(x, y) { mod.#debug_handler.debug_str_scm(x, y); },
+ }
+ let ffi = {
+ procedure_to_extern(proc) {
+ return mod.#ffi_handler.procedure_to_extern(proc);
+ }
+ };
+ let imports = {
+ rt: SchemeModule.#rt,
+ abi, debug, io, ffi, ...user_imports
+ };
+ let { module, instance } = await instantiate_streaming(path, imports);
+ let mod = new SchemeModule(instance);
+ return mod;
+ }
+ set_io_handler(h) { this.#io_handler = h; }
+ set_debug_handler(h) { this.#debug_handler = h; }
+ set_ffi_handler(h) { this.#ffi_handler = h; }
+ all_exports() { return this.#instance.exports; }
+ exported_abi() {
+ let abi = {}
+ for (let [k, v] of Object.entries(this.all_exports())) {
+ if (k.startsWith("$"))
+ abi[k] = v;
+ }
+ return abi;
+ }
+ exports() {
+ let ret = {}
+ for (let [k, v] of Object.entries(this.all_exports())) {
+ if (!k.startsWith("$"))
+ ret[k] = v;
+ }
+ return ret;
+ }
+ get_export(name) {
+ if (name in this.all_exports())
+ return this.all_exports()[name];
+ throw new Error(`unknown export: ${name}`)
+ }
+ async reflect(opts = {}) {
+ return await Scheme.reflect(this.exported_abi(), opts);
+ }
+}
+
+function repr(obj) {
+ if (obj instanceof HeapObject)
+ return obj.repr();
+ if (typeof obj === 'boolean')
+ return obj ? '#t' : '#f';
+ if (typeof obj === 'number')
+ return flonum_to_string(obj);
+ if (typeof obj === 'string')
+ return string_repr(obj);
+ return obj + '';
+}
diff --git a/embeds/frp-color-picker/hoot/reflect.wasm b/embeds/frp-color-picker/hoot/reflect.wasm
new file mode 100644
index 0000000..770c94c
--- /dev/null
+++ b/embeds/frp-color-picker/hoot/reflect.wasm
Binary files differ
diff --git a/embeds/frp-color-picker/hoot/wtf8.wasm b/embeds/frp-color-picker/hoot/wtf8.wasm
new file mode 100644
index 0000000..ca1079d
--- /dev/null
+++ b/embeds/frp-color-picker/hoot/wtf8.wasm
Binary files differ
diff --git a/embeds/frp-color-picker/index.html b/embeds/frp-color-picker/index.html
new file mode 100644
index 0000000..e5c4372
--- /dev/null
+++ b/embeds/frp-color-picker/index.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>FRP color picker</title>
+ <link rel="stylesheet" type="text/css" href="propagators.css"/>
+ <script type="text/javascript" src="hoot/reflect.js"></script>
+ <script type="text/javascript" src="propagators.js"></script>
+ </head>
+ <body>
+ <article id="wasm-error" hidden="true">
+ <h1>Uh oh!</h1>
+ <p>
+ A browser with Wasm GC and tail call support is required for
+ this demo.
+ </p>
+ <p>
+ We recommend using either Firefox or Chrome.
+ </p>
+ <p>
+ Safari is currently unsupported. Likewise, <emph>all browsers</emph> on
+ iOS are unsupported, as they are all secretly Safari under the
+ hood.
+ </p>
+ </article>
+ </body>
+</html>
diff --git a/embeds/frp-color-picker/propagators.css b/embeds/frp-color-picker/propagators.css
new file mode 100644
index 0000000..1b803e2
--- /dev/null
+++ b/embeds/frp-color-picker/propagators.css
@@ -0,0 +1,38 @@
+body {
+ font-family: sans;
+ background-color: #fbfbfb;
+ margin: auto;
+ max-width: 20em;
+}
+
+h1 {
+ text-align: center;
+}
+
+.preview {
+ width: 50%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.hex {
+ text-align: center;
+ margin-top: 0.5em;
+}
+
+fieldset {
+ margin-bottom: 1em;
+ margin-top: 1em;
+}
+
+.color-block {
+ width: 100%;
+ aspect-ratio: 1;
+ border: 1px black solid;
+}
+
+.slider {
+ display: grid;
+ grid-template-columns: 35% max-content;
+ grip-gap: 1em;
+}
diff --git a/embeds/frp-color-picker/propagators.js b/embeds/frp-color-picker/propagators.js
new file mode 100644
index 0000000..fe0034a
--- /dev/null
+++ b/embeds/frp-color-picker/propagators.js
@@ -0,0 +1,30 @@
+window.addEventListener("load", async () => {
+ try {
+ await Scheme.load_main("propagators.wasm", {
+ reflect_wasm_dir: "hoot",
+ user_imports: {
+ window: {
+ setTimeout: setTimeout
+ },
+ document: {
+ makeTextNode: Document.prototype.createTextNode.bind(document),
+ makeElement: Document.prototype.createElement.bind(document),
+ body: () => document.body,
+ },
+ element: {
+ appendChild: (parent, child) => parent.appendChild(child),
+ setAttribute: (elem, attr, value) => elem.setAttribute(attr, value),
+ getValue: (elem) => elem.value,
+ setValue: (elem, val) => elem.value = val,
+ replaceWith: (oldElem, newElem) => oldElem.replaceWith(newElem),
+ addEventListener: (elem, name, f) => elem.addEventListener(name, f)
+ },
+ }
+ });
+ } catch(e) {
+ if(e instanceof WebAssembly.CompileError) {
+ document.getElementById("wasm-error").hidden = false;
+ }
+ throw e;
+ }
+});
diff --git a/embeds/frp-color-picker/propagators.wasm b/embeds/frp-color-picker/propagators.wasm
new file mode 100644
index 0000000..d33412e
--- /dev/null
+++ b/embeds/frp-color-picker/propagators.wasm
Binary files differ