Update to Hoot 0.5.0.

This commit is contained in:
David Thompson 2024-12-10 11:31:20 -05:00
parent b628b655cf
commit 825fdbc372
3 changed files with 339 additions and 40 deletions

View file

@ -1,28 +1,8 @@
(use-modules (guix git-download)
(guix packages)
(gnu packages autotools)
(gnu packages base)
(gnu packages compression)
(gnu packages guile)
(gnu packages guile-xyz)
(gnu packages pkg-config)
(gnu packages texinfo))
(gnu packages guile-xyz))
(define guile-hoot-next
(let ((commit "66eca43e7fc0478b265c7826b7e52eec866e0b21")
(revision "1"))
(package
(inherit guile-hoot)
(version (git-version "0.4.1" revision commit))
(source (origin
(method git-fetch)
(uri (git-reference
(url "https://gitlab.com/spritely/guile-hoot.git")
(commit commit)))
(file-name (git-file-name "guile-hoot" version))
(sha256
(base32 "0pd81wg6adzf1kvmgcxylailzwa0d5vdrhfpy4d54ddl31j6h824"))))
(native-inputs
(list autoconf automake pkg-config texinfo)))))
(packages->manifest (list guile-next guile-hoot-next gnu-make zip))
(packages->manifest (list guile-next guile-hoot gnu-make zip))

View file

@ -141,7 +141,7 @@ class Port extends HeapObject { toString() { return "#<port>"; } }
class Struct extends HeapObject { toString() { return "#<struct>"; } }
function instantiate_streaming(path, imports) {
if (typeof fetch !== 'undefined')
if (typeof fetch !== 'undefined' && typeof window !== 'undefined')
return WebAssembly.instantiateStreaming(fetch(path), imports);
let bytes;
if (typeof read !== 'undefined') {
@ -155,6 +155,168 @@ function instantiate_streaming(path, imports) {
return WebAssembly.instantiate(bytes, imports);
}
class IterableWeakSet {
#array;
#set;
constructor() { this.#array = []; this.#set = new WeakSet; }
#kill(i) {
let tail = this.#array.pop();
if (i < this.#array.length)
this.#array[i] = tail;
}
#get(i) {
if (i >= this.#array.length)
return null;
let obj = this.#array[i].deref();
if (obj)
return obj;
this.#kill(i);
return null;
}
#cleanup() {
let i = 0;
while (this.#get(i)) i++;
}
has(x) { return this.#set.has(x); }
add(x) {
if (this.has(x))
return;
if (this.#array.length % 32 == 0)
this.#cleanup();
this.#set.add(x);
this.#array.push(new WeakRef(x));
}
delete(x) {
if (!this.has(x))
return;
this.#set.delete(x);
let i = 0;
while (this.#get(i) != x) i++;
this.#kill(i);
}
*[Symbol.iterator]() {
for (let i = 0, x; x = this.#get(i); i++)
yield x;
}
}
// See:
// https://github.com/tc39/proposal-weakrefs?tab=readme-ov-file#iterable-weakmaps
class IterableWeakMap {
#weakMap = new WeakMap();
#refSet = new Set();
#finalizationGroup = new FinalizationRegistry(IterableWeakMap.#cleanup);
static #cleanup({ set, ref }) {
set.delete(ref);
}
constructor(iterable) {
for (const [key, value] of iterable) {
this.set(key, value);
}
}
set(key, value) {
const ref = new WeakRef(key);
this.#weakMap.set(key, { value, ref });
this.#refSet.add(ref);
this.#finalizationGroup.register(key, {
set: this.#refSet,
ref
}, ref);
}
get(key) {
const entry = this.#weakMap.get(key);
return entry && entry.value;
}
delete(key) {
const entry = this.#weakMap.get(key);
if (!entry) {
return false;
}
this.#weakMap.delete(key);
this.#refSet.delete(entry.ref);
this.#finalizationGroup.unregister(entry.ref);
return true;
}
*[Symbol.iterator]() {
for (const ref of this.#refSet) {
const key = ref.deref();
if (!key) continue;
const { value } = this.#weakMap.get(key);
yield [key, value];
}
}
entries() {
return this[Symbol.iterator]();
}
*keys() {
for (const [key, value] of this) {
yield key;
}
}
*values() {
for (const [key, value] of this) {
yield value;
}
}
}
// class IterableWeakMap {
// #array;
// #map;
// constructor() { this.#array = []; this.#map = new WeakMap; }
// #kill(i) {
// let tail = this.#array.pop();
// if (i < this.#array.length)
// this.#array[i] = tail;
// }
// #geti(i) {
// // If a dead weak ref is at i then kill it and try again with
// // the next weak ref.
// while (true) {
// if (i >= this.#array.length)
// return null;
// let obj = this.#array[i].deref();
// if (obj)
// return obj;
// this.#kill(i);
// }
// }
// #cleanup() {
// let i = 0;
// while (this.#get(i)) i++;
// }
// has(k) { return this.#map.has(k); }
// get(k) { return this.#map.get(k); }
// set(k, v) {
// if (this.#array.length % 32 == 0)
// this.#cleanup();
// this.#map.set(k, v);
// this.#array.push(new WeakRef(k));
// }
// delete(k) {
// if (!this.has(k))
// return;
// this.#map.delete(k);
// let i = 0;
// while (this.#geti(i) != k) i++;
// this.#kill(i);
// }
// *[Symbol.iterator]() {
// for (let i = 0, k; k = this.#geti(i); i++)
// yield [k, this.get(k)];
// }
// }
class Scheme {
#instance;
#abi;
@ -170,9 +332,11 @@ class Scheme {
debug_str_scm: (x, y) => {
console.log(`reflect debug: ${x}: #<scm>`);
},
code_source(x) { return ['???', 0, 0]; }
};
let reflect_wasm = reflect_wasm_dir + '/reflect.wasm';
let rt = {
quit(status) { throw new SchemeQuitError(status); },
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); },
@ -348,6 +512,11 @@ class SchemeTrapError extends Error {
toString() { return `SchemeTrap(${this.tag}, <data>)`; }
}
class SchemeQuitError extends Error {
constructor(status) { super(); this.status = status; }
toString() { return `SchemeQuit(status=${this.status})`; }
}
function string_repr(str) {
// FIXME: Improve to match Scheme.
return '"' + str.replace(/(["\\])/g, '\\$1').replace(/\n/g, '\\n') + '"';
@ -415,6 +584,18 @@ async function load_wtf8_helper_module(reflect_wasm_dir = '') {
wtf8_helper = instance;
}
function make_textual_writable_stream(write_chars) {
const decoder = new TextDecoder("utf-8");
return new WritableStream({
write(chunk) {
return new Promise((resolve, reject) => {
write_chars(decoder.decode(chunk, { stream: true }));
resolve();
});
}
});
}
class SchemeModule {
#instance;
#io_handler;
@ -469,7 +650,6 @@ class SchemeModule {
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; },
@ -477,14 +657,17 @@ class SchemeModule {
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_ref(x) { return new WeakRef(x); },
weak_ref_deref(ref, fail) {
const val = ref.deref();
return val === undefined ? fail: val;
},
make_weak_map() { return new WeakMap; },
weak_map_get(map, k, fail) {
const val = map.get(k);
@ -493,6 +676,14 @@ class SchemeModule {
weak_map_set(map, k, v) { return map.set(k, v); },
weak_map_delete(map, k) { return map.delete(k); },
make_finalization_registry(f) { return new FinalizationRegistry(f); },
finalization_registry_register(registry, target, heldValue, unregisterToken) {
registry.register(target, heldValue, unregisterToken);
},
finalization_registry_unregister(registry, unregisterToken) {
registry.unregister(unregisterToken);
},
fsqrt: Math.sqrt,
fsin: Math.sin,
fcos: Math.cos,
@ -510,7 +701,15 @@ class SchemeModule {
async_invoke,
async_invoke_later,
promise_on_completed(p, kt, kf) { p.then(kt, kf); },
promise_on_completed(p, kt, kf) {
p.then((val) => {
if (val === undefined) {
kt(false);
} else {
kt(val);
}
}, kf);
},
promise_complete(callback, val) { callback(val); },
// Wrap in functions to allow for lazy loading of the wtf8
@ -518,11 +717,73 @@ class SchemeModule {
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); }
make_regexp(pattern, flags) { return new RegExp(pattern, flags); },
regexp_exec(re, str) { return re.exec(str); },
regexp_match_string(m) { return m.input; },
regexp_match_start(m) { return m.index; },
regexp_match_end(m) { return m.index + m[0].length; },
regexp_match_count(m) { return m.length; },
regexp_match_substring(m, i) {
const str = m[i];
if (str === undefined) {
return null;
}
return str;
},
die(tag, data) { throw new SchemeTrapError(tag, data); },
quit(status) { throw new SchemeQuitError(status); },
stream_make_chunk(len) { return new Uint8Array(len); },
stream_chunk_length(chunk) { return chunk.length; },
stream_chunk_ref(chunk, idx) { return chunk[idx]; },
stream_chunk_set(chunk, idx, val) { chunk[idx] = val; },
stream_get_reader(stream) { return stream.getReader(); },
stream_read(reader) { return reader.read(); },
stream_result_chunk(result) { return result.value; },
stream_result_done(result) { return result.done ? 1 : 0; },
stream_get_writer(stream) { return stream.getWriter(); },
stream_write(writer, chunk) { return writer.write(chunk); },
stream_close_writer(writer) { return writer.close(); },
};
static #code_origins = new WeakMap;
static #all_modules = new IterableWeakSet;
static #code_origin(code) {
if (SchemeModule.#code_origins.has(code))
return SchemeModule.#code_origins.get(code);
for (let mod of SchemeModule.#all_modules) {
for (let i = 0, x = null; x = mod.instance_code(i); i++) {
let origin = [mod, i];
if (!SchemeModule.#code_origins.has(x))
SchemeModule.#code_origins.set(x, origin);
if (x === code)
return origin;
}
}
return [null, 0];
}
static #code_name(code) {
let [mod, idx] = SchemeModule.#code_origin(code);
if (mod)
return mod.instance_code_name(idx);
return null;
}
static #code_source(code) {
let [mod, idx] = SchemeModule.#code_origin(code);
if (mod)
return mod.instance_code_source(idx);
return [null, 0, 0];
}
constructor(instance) {
SchemeModule.#all_modules.add(this);
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
@ -541,14 +802,13 @@ class SchemeModule {
return '\n';
}
}: () => '';
let delete_file = (filename) => false;
this.#io_handler = {
write_stdout: write_no_newline,
write_stderr: printErr,
read_stdin,
file_exists: (filename) => false,
open_input_file: (filename) => {},
open_output_file: (filename) => {},
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,
@ -557,7 +817,11 @@ class SchemeModule {
file_buffer_size: (handle) => 0,
file_buffer_ref: (handle, i) => 0,
file_buffer_set: (handle, i, x) => undefined,
delete_file: (filename) => undefined
delete_file: (filename) => undefined,
// FIXME: We should polyfill these out.
stream_stdin() { throw new Error('stream_stdin not implemented'); },
stream_stdout() { throw new Error('stream_stderr not implemented'); },
stream_stderr() { throw new Error('stream_stderr not implemented'); },
};
} else if (typeof window !== 'undefined') { // web browser
this.#io_handler = {
@ -565,8 +829,8 @@ class SchemeModule {
write_stderr: console.error,
read_stdin: () => '',
file_exists: (filename) => false,
open_input_file: (filename) => {},
open_output_file: (filename) => {},
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,
@ -575,17 +839,26 @@ class SchemeModule {
file_buffer_size: (handle) => 0,
file_buffer_ref: (handle, i) => 0,
file_buffer_set: (handle, i, x) => undefined,
delete_file: (filename) => undefined
delete_file: (filename) => undefined,
stream_stdin() { return new ReadableStream; },
stream_stdout() {
return make_textual_writable_stream(s => console.log(s));
},
stream_stderr() {
return make_textual_writable_stream(s => console.error(s));
},
};
} else { // nodejs
const fs = require('fs');
const process = require('process');
const { ReadableStream, WritableStream } = require('node:stream/web');
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,
write_stdout: process.stdout.write.bind(process.stdout),
write_stderr: process.stderr.write.bind(process.stderr),
read_stdin: () => {
let n = fs.readSync(process.stdin.fd, stdinBuf, 0, stdinBuf.length);
return stdinBuf.toString('utf8', 0, n);
@ -646,7 +919,23 @@ class SchemeModule {
file_buffer_set: (handle, i, x) => {
handle.buf[i] = x;
},
delete_file: fs.rmSync.bind(fs)
delete_file: fs.rmSync.bind(fs),
stream_stdin() {
return new ReadableStream({
async start(controller) {
for await (const chunk of process.stdin) {
controller.enqueue(chunk);
}
controller.close();
}
});
},
stream_stdout() {
return make_textual_writable_stream(s => process.stdout.write(s));
},
stream_stderr() {
return make_textual_writable_stream(s => process.stderr.write(s));
},
};
}
this.#debug_handler = {
@ -673,12 +962,17 @@ class SchemeModule {
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); }
delete_file(filename) { mod.#io_handler.delete_file(filename); },
stream_stdin() { return mod.#io_handler.stream_stdin(); },
stream_stdout() { return mod.#io_handler.stream_stdout(); },
stream_stderr() { return mod.#io_handler.stream_stderr(); },
};
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); },
code_name(code) { return SchemeModule.#code_name(code); },
code_source(code) { return SchemeModule.#code_source(code); },
}
let ffi = {
procedure_to_extern(proc) {
@ -718,6 +1012,24 @@ class SchemeModule {
return this.all_exports()[name];
throw new Error(`unknown export: ${name}`)
}
instance_code(idx) {
if ('%instance-code' in this.all_exports()) {
return this.all_exports()['%instance-code'](idx);
}
return null;
}
instance_code_name(idx) {
if ('%instance-code-name' in this.all_exports()) {
return this.all_exports()['%instance-code-name'](idx);
}
return null;
}
instance_code_source(idx) {
if ('%instance-code-source' in this.all_exports()) {
return this.all_exports()['%instance-code-source'](idx);
}
return [null, 0, 0];
}
async reflect(opts = {}) {
return await Scheme.reflect(this.exported_abi(), opts);
}
@ -734,3 +1046,10 @@ function repr(obj) {
return string_repr(obj);
return obj + '';
}
// Modulize when possible.
if (typeof exports !== 'undefined') {
exports.Scheme = Scheme;
exports.SchemeQuitError = SchemeQuitError;
exports.repr = repr;
}

Binary file not shown.