reflect.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  1. // -*- js2-basic-offset: 4 -*-
  2. class Char {
  3. constructor(codepoint) {
  4. this.codepoint = codepoint;
  5. }
  6. toString() {
  7. let ch = String.fromCodePoint(this.codepoint);
  8. if (ch == '\n') return '#\\newline';
  9. if (ch == '\r') return '#\\return';
  10. if (ch.match(/[a-zA-Z0-9$[\]().]/)) return `#\\${ch}`;
  11. return `#\\x${this.codepoint.toString(16)}`;
  12. }
  13. }
  14. class Eof { toString() { return "#<eof>"; } }
  15. class Nil { toString() { return "#nil"; } }
  16. class Null { toString() { return "()"; } }
  17. class Unspecified { toString() { return "#<unspecified>"; } }
  18. class Complex {
  19. constructor(real, imag) {
  20. this.real = real;
  21. this.imag = imag;
  22. }
  23. toString() {
  24. const sign = this.imag >= 0 && Number.isFinite(this.imag) ? "+": "";
  25. return `${flonum_to_string(this.real)}${sign}${flonum_to_string(this.imag)}i`;
  26. }
  27. }
  28. class Fraction {
  29. constructor(num, denom) {
  30. this.num = num;
  31. this.denom = denom;
  32. }
  33. toString() {
  34. return `${this.num}/${this.denom}`;
  35. }
  36. }
  37. class HeapObject {
  38. constructor(reflector, obj) {
  39. this.reflector = reflector;
  40. this.obj = obj;
  41. }
  42. repr() { return this.toString(); } // Default implementation.
  43. }
  44. class Pair extends HeapObject {
  45. toString() { return "#<pair>"; }
  46. repr() {
  47. let car_repr = repr(this.reflector.car(this));
  48. let cdr_repr = repr(this.reflector.cdr(this));
  49. if (cdr_repr == '()')
  50. return `(${car_repr})`;
  51. if (cdr_repr.charAt(0) == '(')
  52. return `(${car_repr} ${cdr_repr.substring(1)}`;
  53. return `(${car_repr} . ${cdr_repr})`;
  54. }
  55. }
  56. class MutablePair extends Pair { toString() { return "#<mutable-pair>"; } }
  57. class Vector extends HeapObject {
  58. toString() { return "#<vector>"; }
  59. repr() {
  60. let len = this.reflector.vector_length(this);
  61. let out = '#(';
  62. for (let i = 0; i < len; i++) {
  63. if (i) out += ' ';
  64. out += repr(this.reflector.vector_ref(this, i));
  65. }
  66. out += ')';
  67. return out;
  68. }
  69. }
  70. class MutableVector extends Vector {
  71. toString() { return "#<mutable-vector>"; }
  72. }
  73. class Bytevector extends HeapObject {
  74. toString() { return "#<bytevector>"; }
  75. repr() {
  76. let len = this.reflector.bytevector_length(this);
  77. let out = '#vu8(';
  78. for (let i = 0; i < len; i++) {
  79. if (i) out += ' ';
  80. out += this.reflector.bytevector_ref(this, i);
  81. }
  82. out += ')';
  83. return out;
  84. }
  85. }
  86. class MutableBytevector extends Bytevector {
  87. toString() { return "#<mutable-bytevector>"; }
  88. }
  89. class Bitvector extends HeapObject {
  90. toString() { return "#<bitvector>"; }
  91. repr() {
  92. let len = this.reflector.bitvector_length(this);
  93. let out = '#*';
  94. for (let i = 0; i < len; i++) {
  95. out += this.reflector.bitvector_ref(this, i) ? '1' : '0';
  96. }
  97. return out;
  98. }
  99. }
  100. class MutableBitvector extends Bitvector {
  101. toString() { return "#<mutable-bitvector>"; }
  102. }
  103. class MutableString extends HeapObject {
  104. toString() { return "#<mutable-string>"; }
  105. repr() { return string_repr(this.reflector.string_value(this)); }
  106. }
  107. class Procedure extends HeapObject {
  108. toString() { return "#<procedure>"; }
  109. call(...arg) {
  110. return this.reflector.call(this, ...arg);
  111. }
  112. async call_async(...arg) {
  113. return await this.reflector.call_async(this, ...arg);
  114. }
  115. }
  116. class Sym extends HeapObject {
  117. toString() { return "#<symbol>"; }
  118. repr() { return this.reflector.symbol_name(this); }
  119. }
  120. class Keyword extends HeapObject {
  121. toString() { return "#<keyword>"; }
  122. repr() { return `#:${this.reflector.keyword_name(this)}`; }
  123. }
  124. class Variable extends HeapObject { toString() { return "#<variable>"; } }
  125. class AtomicBox extends HeapObject { toString() { return "#<atomic-box>"; } }
  126. class HashTable extends HeapObject { toString() { return "#<hash-table>"; } }
  127. class WeakTable extends HeapObject { toString() { return "#<weak-table>"; } }
  128. class Fluid extends HeapObject { toString() { return "#<fluid>"; } }
  129. class DynamicState extends HeapObject { toString() { return "#<dynamic-state>"; } }
  130. class Syntax extends HeapObject { toString() { return "#<syntax>"; } }
  131. class SyntaxTransformer extends HeapObject { toString() { return "#<syntax-transformer>"; } }
  132. class Port extends HeapObject { toString() { return "#<port>"; } }
  133. class Struct extends HeapObject { toString() { return "#<struct>"; } }
  134. function instantiate_streaming(path, imports) {
  135. if (typeof fetch !== 'undefined' && typeof window !== 'undefined')
  136. return WebAssembly.instantiateStreaming(fetch(path), imports);
  137. let bytes;
  138. if (typeof read !== 'undefined') {
  139. bytes = read(path, 'binary');
  140. } else if (typeof readFile !== 'undefined') {
  141. bytes = readFile(path);
  142. } else {
  143. let fs = require('fs');
  144. bytes = fs.readFileSync(path);
  145. }
  146. return WebAssembly.instantiate(bytes, imports);
  147. }
  148. class IterableWeakSet {
  149. #array;
  150. #set;
  151. constructor() { this.#array = []; this.#set = new WeakSet; }
  152. #kill(i) {
  153. let tail = this.#array.pop();
  154. if (i < this.#array.length)
  155. this.#array[i] = tail;
  156. }
  157. #get(i) {
  158. if (i >= this.#array.length)
  159. return null;
  160. let obj = this.#array[i].deref();
  161. if (obj)
  162. return obj;
  163. this.#kill(i);
  164. return null;
  165. }
  166. #cleanup() {
  167. let i = 0;
  168. while (this.#get(i)) i++;
  169. }
  170. has(x) { return this.#set.has(x); }
  171. add(x) {
  172. if (this.has(x))
  173. return;
  174. if (this.#array.length % 32 == 0)
  175. this.#cleanup();
  176. this.#set.add(x);
  177. this.#array.push(new WeakRef(x));
  178. }
  179. delete(x) {
  180. if (!this.has(x))
  181. return;
  182. this.#set.delete(x);
  183. let i = 0;
  184. while (this.#get(i) != x) i++;
  185. this.#kill(i);
  186. }
  187. *[Symbol.iterator]() {
  188. for (let i = 0, x; x = this.#get(i); i++)
  189. yield x;
  190. }
  191. }
  192. class Scheme {
  193. #instance;
  194. #abi;
  195. constructor(instance, abi) {
  196. this.#instance = instance;
  197. this.#abi = abi;
  198. }
  199. static async reflect(abi, {reflect_wasm_dir = '.'}) {
  200. let debug = {
  201. debug_str(x) { console.log(`reflect debug: ${x}`); },
  202. debug_str_i32(x, y) { console.log(`reflect debug: ${x}: ${y}`); },
  203. debug_str_scm: (x, y) => {
  204. console.log(`reflect debug: ${x}: #<scm>`);
  205. },
  206. code_source(x) { return ['???', 0, 0]; }
  207. };
  208. let reflect_wasm = reflect_wasm_dir + '/reflect.wasm';
  209. let rt = {
  210. quit(status) { throw new SchemeQuitError(status); },
  211. die(tag, data) { throw new SchemeTrapError(tag, data); },
  212. wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
  213. string_to_wtf8(str) { return string_to_wtf8(str); },
  214. };
  215. let { module, instance } =
  216. await instantiate_streaming(reflect_wasm, { abi, debug, rt });
  217. return new Scheme(instance, abi);
  218. }
  219. #init_module(mod) {
  220. mod.set_debug_handler({
  221. debug_str(x) { console.log(`debug: ${x}`); },
  222. debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
  223. debug_str_scm: (x, y) => {
  224. console.log(`debug: ${x}: ${repr(this.#to_js(y))}`);
  225. },
  226. });
  227. mod.set_ffi_handler({
  228. is_extern_func: (x) => typeof(x) === "function",
  229. call_extern: (f, args) => {
  230. const jsArgs = [];
  231. for(args = this.#to_js(args); !(args instanceof Null); args = this.cdr(args)) {
  232. jsArgs.push(this.car(args));
  233. }
  234. let result = f(...jsArgs);
  235. if(result === undefined || result === null) {
  236. result = new Unspecified();
  237. }
  238. return this.#to_scm(result);
  239. },
  240. procedure_to_extern: (obj) => {
  241. const proc = this.#to_js(obj);
  242. return (...args) => {
  243. return proc.call(...args)[0];
  244. };
  245. }
  246. });
  247. mod.set_finalization_handler({
  248. make_finalization_registry: (f) => new FinalizationRegistry(f),
  249. finalization_registry_register: (registry, target, heldValue) => {
  250. // heldValue is a Wasm struct and needs to be wrapped
  251. // so that when it goes back to Scheme via the
  252. // finalizer callback it is seen as a Scheme value and
  253. // not an external one.
  254. registry.register(target, this.#to_js(heldValue));
  255. },
  256. finalization_registry_register_with_token: (registry, target, heldValue, unregisterToken) => {
  257. registry.register(target, this.#to_js(heldValue), unregisterToken);
  258. },
  259. finalization_registry_unregister: (registry, unregisterToken) => {
  260. return registry.unregister(unregisterToken);
  261. }
  262. });
  263. let proc = new Procedure(this, mod.get_export('$load').value);
  264. return proc.call();
  265. }
  266. static async load_main(path, opts = {}) {
  267. let mod = await SchemeModule.fetch_and_instantiate(path, opts);
  268. let reflect = await mod.reflect(opts);
  269. return reflect.#init_module(mod);
  270. }
  271. async load_extension(path, opts = {}) {
  272. opts = Object.assign({ abi: this.#abi }, opts);
  273. let mod = await SchemeModule.fetch_and_instantiate(path, opts);
  274. return this.#init_module(mod);
  275. }
  276. #to_scm(js) {
  277. let api = this.#instance.exports;
  278. if (typeof(js) == 'number') {
  279. return api.scm_from_f64(js);
  280. } else if (typeof(js) == 'bigint') {
  281. if (BigInt(api.scm_most_negative_fixnum()) <= js
  282. && js <= BigInt(api.scm_most_positive_fixnum()))
  283. return api.scm_from_fixnum(Number(js));
  284. return api.scm_from_bignum(js);
  285. } else if (typeof(js) == 'boolean') {
  286. return js ? api.scm_true() : api.scm_false();
  287. } else if (typeof(js) == 'string') {
  288. return api.scm_from_string(js);
  289. } else if (typeof(js) == 'object') {
  290. if (js instanceof Eof) return api.scm_eof();
  291. if (js instanceof Nil) return api.scm_nil();
  292. if (js instanceof Null) return api.scm_null();
  293. if (js instanceof Unspecified) return api.scm_unspecified();
  294. if (js instanceof Char) return api.scm_from_char(js.codepoint);
  295. if (js instanceof HeapObject) return js.obj;
  296. if (js instanceof Fraction)
  297. return api.scm_from_fraction(this.#to_scm(js.num),
  298. this.#to_scm(js.denom));
  299. if (js instanceof Complex)
  300. return api.scm_from_complex(js.real, js.imag);
  301. return api.scm_from_extern(js);
  302. } else if (typeof(js) == 'function') {
  303. return api.scm_from_extern(js);
  304. } else {
  305. throw new Error(`unexpected; ${typeof(js)}`);
  306. }
  307. }
  308. #to_js(scm) {
  309. let api = this.#instance.exports;
  310. let descr = api.describe(scm);
  311. let handlers = {
  312. fixnum: () => BigInt(api.fixnum_value(scm)),
  313. char: () => new Char(api.char_value(scm)),
  314. true: () => true,
  315. false: () => false,
  316. eof: () => new Eof,
  317. nil: () => new Nil,
  318. null: () => new Null,
  319. unspecified: () => new Unspecified,
  320. flonum: () => api.flonum_value(scm),
  321. bignum: () => api.bignum_value(scm),
  322. complex: () => new Complex(api.complex_real(scm),
  323. api.complex_imag(scm)),
  324. fraction: () => new Fraction(this.#to_js(api.fraction_num(scm)),
  325. this.#to_js(api.fraction_denom(scm))),
  326. pair: () => new Pair(this, scm),
  327. 'mutable-pair': () => new MutablePair(this, scm),
  328. vector: () => new Vector(this, scm),
  329. 'mutable-vector': () => new MutableVector(this, scm),
  330. bytevector: () => new Bytevector(this, scm),
  331. 'mutable-bytevector': () => new MutableBytevector(this, scm),
  332. bitvector: () => new Bitvector(this, scm),
  333. 'mutable-bitvector': () => new MutableBitvector(this, scm),
  334. string: () => api.string_value(scm),
  335. 'mutable-string': () => new MutableString(this, scm),
  336. procedure: () => new Procedure(this, scm),
  337. symbol: () => new Sym(this, scm),
  338. keyword: () => new Keyword(this, scm),
  339. variable: () => new Variable(this, scm),
  340. 'atomic-box': () => new AtomicBox(this, scm),
  341. 'hash-table': () => new HashTable(this, scm),
  342. 'weak-table': () => new WeakTable(this, scm),
  343. fluid: () => new Fluid(this, scm),
  344. 'dynamic-state': () => new DynamicState(this, scm),
  345. syntax: () => new Syntax(this, scm),
  346. 'syntax-transformer': () => new SyntaxTransformer(this, scm),
  347. port: () => new Port(this, scm),
  348. struct: () => new Struct(this, scm),
  349. 'extern-ref': () => api.extern_value(scm)
  350. };
  351. let handler = handlers[descr];
  352. return handler ? handler() : scm;
  353. }
  354. call(func, ...args) {
  355. let api = this.#instance.exports;
  356. let argv = api.make_vector(args.length + 1, api.scm_false());
  357. func = this.#to_scm(func);
  358. api.vector_set(argv, 0, func);
  359. for (let [idx, arg] of args.entries())
  360. api.vector_set(argv, idx + 1, this.#to_scm(arg));
  361. argv = api.call(func, argv);
  362. let results = [];
  363. for (let idx = 0; idx < api.vector_length(argv); idx++)
  364. results.push(this.#to_js(api.vector_ref(argv, idx)))
  365. return results;
  366. }
  367. call_async(func, ...args) {
  368. return new Promise((resolve, reject) => {
  369. this.call(func,
  370. val => resolve(this.#to_js(val)),
  371. err => reject(this.#to_js(err)),
  372. ...args);
  373. })
  374. }
  375. car(x) { return this.#to_js(this.#instance.exports.car(x.obj)); }
  376. cdr(x) { return this.#to_js(this.#instance.exports.cdr(x.obj)); }
  377. vector_length(x) { return this.#instance.exports.vector_length(x.obj); }
  378. vector_ref(x, i) {
  379. return this.#to_js(this.#instance.exports.vector_ref(x.obj, i));
  380. }
  381. bytevector_length(x) {
  382. return this.#instance.exports.bytevector_length(x.obj);
  383. }
  384. bytevector_ref(x, i) {
  385. return this.#instance.exports.bytevector_ref(x.obj, i);
  386. }
  387. bitvector_length(x) {
  388. return this.#instance.exports.bitvector_length(x.obj);
  389. }
  390. bitvector_ref(x, i) {
  391. return this.#instance.exports.bitvector_ref(x.obj, i) == 1;
  392. }
  393. string_value(x) { return this.#instance.exports.string_value(x.obj); }
  394. symbol_name(x) { return this.#instance.exports.symbol_name(x.obj); }
  395. keyword_name(x) { return this.#instance.exports.keyword_name(x.obj); }
  396. }
  397. class SchemeTrapError extends Error {
  398. constructor(tag, data) { super(); this.tag = tag; this.data = data; }
  399. // FIXME: data is raw Scheme object; would need to be reflected to
  400. // have a toString.
  401. toString() { return `SchemeTrap(${this.tag}, <data>)`; }
  402. }
  403. class SchemeQuitError extends Error {
  404. constructor(status) { super(); this.status = status; }
  405. toString() { return `SchemeQuit(status=${this.status})`; }
  406. }
  407. function string_repr(str) {
  408. // FIXME: Improve to match Scheme.
  409. return '"' + str.replace(/(["\\])/g, '\\$1').replace(/\n/g, '\\n') + '"';
  410. }
  411. function flonum_to_string(f64) {
  412. if (Object.is(f64, -0)) {
  413. return '-0.0';
  414. } else if (Number.isFinite(f64)) {
  415. let repr = f64 + '';
  416. return /^-?[0-9]+$/.test(repr) ? repr + '.0' : repr;
  417. } else if (Number.isNaN(f64)) {
  418. return '+nan.0';
  419. } else {
  420. return f64 < 0 ? '-inf.0' : '+inf.0';
  421. }
  422. }
  423. let async_invoke = typeof queueMicrotask !== 'undefined'
  424. ? queueMicrotask
  425. : thunk => setTimeout(thunk, 0);
  426. function async_invoke_later(thunk, jiffies) {
  427. setTimeout(thunk, jiffies / 1000);
  428. }
  429. let wtf8_helper;
  430. function wtf8_to_string(wtf8) {
  431. let { as_iter, iter_next } = wtf8_helper.exports;
  432. let codepoints = [];
  433. let iter = as_iter(wtf8);
  434. for (let cp = iter_next(iter); cp != -1; cp = iter_next(iter))
  435. codepoints.push(cp);
  436. // Passing too many codepoints can overflow the stack.
  437. let maxcp = 100000;
  438. if (codepoints.length <= maxcp) {
  439. return String.fromCodePoint(...codepoints);
  440. }
  441. // For converting large strings, concatenate several smaller
  442. // strings.
  443. let substrings = [];
  444. let end = 0;
  445. for (let start = 0; start != codepoints.length; start = end) {
  446. end = Math.min(start + maxcp, codepoints.length);
  447. substrings.push(String.fromCodePoint(...codepoints.slice(start, end)));
  448. }
  449. return substrings.join('');
  450. }
  451. function string_to_wtf8(str) {
  452. let { string_builder, builder_push_codepoint, finish_builder } =
  453. wtf8_helper.exports;
  454. let builder = string_builder()
  455. for (let cp of str)
  456. builder_push_codepoint(builder, cp.codePointAt(0));
  457. return finish_builder(builder);
  458. }
  459. async function load_wtf8_helper_module(reflect_wasm_dir = '') {
  460. if (wtf8_helper) return;
  461. let wtf8_wasm = reflect_wasm_dir + "/wtf8.wasm";
  462. let { module, instance } = await instantiate_streaming(wtf8_wasm);
  463. wtf8_helper = instance;
  464. }
  465. function make_textual_writable_stream(write_chars) {
  466. const decoder = new TextDecoder("utf-8");
  467. return new WritableStream({
  468. write(chunk) {
  469. return new Promise((resolve, reject) => {
  470. write_chars(decoder.decode(chunk, { stream: true }));
  471. resolve();
  472. });
  473. }
  474. });
  475. }
  476. class SchemeModule {
  477. #instance;
  478. #io_handler;
  479. #debug_handler;
  480. #ffi_handler;
  481. #finalization_handler;
  482. static #rt = {
  483. bignum_from_string(str) { return BigInt(str); },
  484. bignum_from_i32(n) { return BigInt(n); },
  485. bignum_from_i64(n) { return n; },
  486. bignum_from_u64(n) { return n < 0n ? 0xffff_ffff_ffff_ffffn + (n + 1n) : n; },
  487. bignum_is_i64(n) {
  488. return -0x8000_0000_0000_0000n <= n && n <= 0x7FFF_FFFF_FFFF_FFFFn;
  489. },
  490. bignum_is_u64(n) {
  491. return 0n <= n && n <= 0xFFFF_FFFF_FFFF_FFFFn;
  492. },
  493. // This truncates; see https://tc39.es/ecma262/#sec-tobigint64.
  494. bignum_get_i64(n) { return n; },
  495. bignum_add(a, b) { return BigInt(a) + BigInt(b) },
  496. bignum_sub(a, b) { return BigInt(a) - BigInt(b) },
  497. bignum_mul(a, b) { return BigInt(a) * BigInt(b) },
  498. bignum_lsh(a, b) { return BigInt(a) << BigInt(b) },
  499. bignum_rsh(a, b) { return BigInt(a) >> BigInt(b) },
  500. bignum_quo(a, b) { return BigInt(a) / BigInt(b) },
  501. bignum_rem(a, b) { return BigInt(a) % BigInt(b) },
  502. bignum_mod(a, b) {
  503. let r = BigInt(a) % BigInt(b);
  504. if ((b > 0n && r < 0n) || (b < 0n && r > 0n)) {
  505. return b + r;
  506. } else {
  507. return r;
  508. }
  509. },
  510. bignum_gcd(a, b) {
  511. a = BigInt(a);
  512. b = BigInt(b);
  513. if (a < 0n) { a = -a; }
  514. if (b < 0n) { b = -b; }
  515. if (a == 0n) { return b; }
  516. if (b == 0n) { return a; }
  517. let r;
  518. while (b != 0n) {
  519. r = a % b;
  520. a = b;
  521. b = r;
  522. }
  523. return a;
  524. },
  525. bignum_logand(a, b) { return BigInt(a) & BigInt(b); },
  526. bignum_logior(a, b) { return BigInt(a) | BigInt(b); },
  527. bignum_logxor(a, b) { return BigInt(a) ^ BigInt(b); },
  528. bignum_lt(a, b) { return a < b; },
  529. bignum_le(a, b) { return a <= b; },
  530. bignum_eq(a, b) { return a == b; },
  531. bignum_to_f64(n) { return Number(n); },
  532. flonum_to_string,
  533. string_upcase: Function.call.bind(String.prototype.toUpperCase),
  534. string_downcase: Function.call.bind(String.prototype.toLowerCase),
  535. make_weak_ref(x) { return new WeakRef(x); },
  536. weak_ref_deref(ref, fail) {
  537. const val = ref.deref();
  538. return val === undefined ? fail: val;
  539. },
  540. make_weak_map() { return new WeakMap; },
  541. weak_map_get(map, k, fail) {
  542. const val = map.get(k);
  543. return val === undefined ? fail: val;
  544. },
  545. weak_map_set(map, k, v) { return map.set(k, v); },
  546. weak_map_delete(map, k) { return map.delete(k); },
  547. fsqrt: Math.sqrt,
  548. fsin: Math.sin,
  549. fcos: Math.cos,
  550. ftan: Math.tan,
  551. fasin: Math.asin,
  552. facos: Math.acos,
  553. fatan: Math.atan,
  554. fatan2: Math.atan2,
  555. flog: Math.log,
  556. fexp: Math.exp,
  557. jiffies_per_second() { return 1000000; },
  558. current_jiffy() { return performance.now() * 1000; },
  559. current_second() { return Date.now() / 1000; },
  560. async_invoke,
  561. async_invoke_later,
  562. promise_on_completed(p, kt, kf) {
  563. p.then((val) => {
  564. if (val === undefined) {
  565. kt(false);
  566. } else {
  567. kt(val);
  568. }
  569. }, kf);
  570. },
  571. promise_complete(callback, val) { callback(val); },
  572. // Wrap in functions to allow for lazy loading of the wtf8
  573. // module.
  574. wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
  575. string_to_wtf8(str) { return string_to_wtf8(str); },
  576. make_regexp(pattern, flags) { return new RegExp(pattern, flags); },
  577. regexp_exec(re, str) { return re.exec(str); },
  578. regexp_match_string(m) { return m.input; },
  579. regexp_match_start(m) { return m.index; },
  580. regexp_match_end(m) { return m.index + m[0].length; },
  581. regexp_match_count(m) { return m.length; },
  582. regexp_match_substring(m, i) {
  583. const str = m[i];
  584. if (str === undefined) {
  585. return null;
  586. }
  587. return str;
  588. },
  589. die(tag, data) { throw new SchemeTrapError(tag, data); },
  590. quit(status) { throw new SchemeQuitError(status); },
  591. stream_make_chunk(len) { return new Uint8Array(len); },
  592. stream_chunk_length(chunk) { return chunk.length; },
  593. stream_chunk_ref(chunk, idx) { return chunk[idx]; },
  594. stream_chunk_set(chunk, idx, val) { chunk[idx] = val; },
  595. stream_get_reader(stream) { return stream.getReader(); },
  596. stream_read(reader) { return reader.read(); },
  597. stream_result_chunk(result) { return result.value; },
  598. stream_result_done(result) { return result.done ? 1 : 0; },
  599. stream_get_writer(stream) { return stream.getWriter(); },
  600. stream_write(writer, chunk) { return writer.write(chunk); },
  601. stream_close_writer(writer) { return writer.close(); },
  602. };
  603. static #code_origins = new WeakMap;
  604. static #all_modules = new IterableWeakSet;
  605. static #code_origin(code) {
  606. if (SchemeModule.#code_origins.has(code))
  607. return SchemeModule.#code_origins.get(code);
  608. for (let mod of SchemeModule.#all_modules) {
  609. for (let i = 0, x = null; x = mod.instance_code(i); i++) {
  610. let origin = [mod, i];
  611. if (!SchemeModule.#code_origins.has(x))
  612. SchemeModule.#code_origins.set(x, origin);
  613. if (x === code)
  614. return origin;
  615. }
  616. }
  617. return [null, 0];
  618. }
  619. static #code_name(code) {
  620. let [mod, idx] = SchemeModule.#code_origin(code);
  621. if (mod)
  622. return mod.instance_code_name(idx);
  623. return null;
  624. }
  625. static #code_source(code) {
  626. let [mod, idx] = SchemeModule.#code_origin(code);
  627. if (mod)
  628. return mod.instance_code_source(idx);
  629. return [null, 0, 0];
  630. }
  631. constructor(instance) {
  632. SchemeModule.#all_modules.add(this);
  633. this.#instance = instance;
  634. let open_file_error = (filename) => {
  635. throw new Error('No file system access');
  636. };
  637. if (typeof printErr === 'function') { // v8/sm dev console
  638. // On the console, try to use 'write' (v8) or 'putstr' (sm),
  639. // as these don't add an extraneous newline. Unfortunately
  640. // JSC doesn't have a printer that doesn't add a newline.
  641. let write_no_newline =
  642. typeof write === 'function' ? write
  643. : typeof putstr === 'function' ? putstr : print;
  644. // Use readline when available. v8 strips newlines so
  645. // we need to add them back.
  646. let read_stdin =
  647. typeof readline == 'function' ? () => {
  648. let line = readline();
  649. if (line) {
  650. return `${line}\n`;
  651. } else {
  652. return '\n';
  653. }
  654. }: () => '';
  655. this.#io_handler = {
  656. write_stdout: write_no_newline,
  657. write_stderr: printErr,
  658. read_stdin,
  659. file_exists: (filename) => false,
  660. open_input_file: open_file_error,
  661. open_output_file: open_file_error,
  662. close_file: () => undefined,
  663. read_file: (handle, length) => 0,
  664. write_file: (handle, length) => 0,
  665. seek_file: (handle, offset, whence) => -1,
  666. file_random_access: (handle) => false,
  667. file_buffer_size: (handle) => 0,
  668. file_buffer_ref: (handle, i) => 0,
  669. file_buffer_set: (handle, i, x) => undefined,
  670. delete_file: (filename) => undefined,
  671. // FIXME: We should polyfill these out.
  672. stream_stdin() { throw new Error('stream_stdin not implemented'); },
  673. stream_stdout() { throw new Error('stream_stderr not implemented'); },
  674. stream_stderr() { throw new Error('stream_stderr not implemented'); },
  675. };
  676. } else if (typeof window !== 'undefined') { // web browser
  677. this.#io_handler = {
  678. write_stdout: console.log,
  679. write_stderr: console.error,
  680. read_stdin: () => '',
  681. file_exists: (filename) => false,
  682. open_input_file: open_file_error,
  683. open_output_file: open_file_error,
  684. close_file: () => undefined,
  685. read_file: (handle, length) => 0,
  686. write_file: (handle, length) => 0,
  687. seek_file: (handle, offset, whence) => -1,
  688. file_random_access: (handle) => false,
  689. file_buffer_size: (handle) => 0,
  690. file_buffer_ref: (handle, i) => 0,
  691. file_buffer_set: (handle, i, x) => undefined,
  692. delete_file: (filename) => undefined,
  693. stream_stdin() { return new ReadableStream; },
  694. stream_stdout() {
  695. return make_textual_writable_stream(s => console.log(s));
  696. },
  697. stream_stderr() {
  698. return make_textual_writable_stream(s => console.error(s));
  699. },
  700. };
  701. } else { // nodejs
  702. const fs = require('fs');
  703. const process = require('process');
  704. const { ReadableStream, WritableStream } = require('node:stream/web');
  705. const bufLength = 1024;
  706. const stdinBuf = Buffer.alloc(bufLength);
  707. const SEEK_SET = 0, SEEK_CUR = 1, SEEK_END = 2;
  708. this.#io_handler = {
  709. write_stdout: process.stdout.write.bind(process.stdout),
  710. write_stderr: process.stderr.write.bind(process.stderr),
  711. read_stdin: () => {
  712. let n = fs.readSync(process.stdin.fd, stdinBuf, 0, stdinBuf.length);
  713. return stdinBuf.toString('utf8', 0, n);
  714. },
  715. file_exists: fs.existsSync.bind(fs),
  716. open_input_file: (filename) => {
  717. let fd = fs.openSync(filename, 'r');
  718. return {
  719. fd,
  720. buf: Buffer.alloc(bufLength),
  721. pos: 0
  722. };
  723. },
  724. open_output_file: (filename) => {
  725. let fd = fs.openSync(filename, 'w');
  726. return {
  727. fd,
  728. buf: Buffer.alloc(bufLength),
  729. pos: 0
  730. };
  731. },
  732. close_file: (handle) => {
  733. fs.closeSync(handle.fd);
  734. },
  735. read_file: (handle, count) => {
  736. const n = fs.readSync(handle.fd, handle.buf, 0, count, handle.pos);
  737. handle.pos += n;
  738. return n;
  739. },
  740. write_file: (handle, count) => {
  741. const n = fs.writeSync(handle.fd, handle.buf, 0, count, handle.pos);
  742. handle.pos += n;
  743. return n;
  744. },
  745. seek_file: (handle, offset, whence) => {
  746. // There doesn't seem to be a way to ask NodeJS if
  747. // a position is valid or not.
  748. if (whence == SEEK_SET) {
  749. handle.pos = offset;
  750. return handle.pos;
  751. } else if (whence == SEEK_CUR) {
  752. handle.pos += offset;
  753. return handle.pos;
  754. }
  755. // SEEK_END not supported.
  756. return -1;
  757. },
  758. file_random_access: (handle) => {
  759. return true;
  760. },
  761. file_buffer_size: (handle) => {
  762. return handle.buf.length;
  763. },
  764. file_buffer_ref: (handle, i) => {
  765. return handle.buf[i];
  766. },
  767. file_buffer_set: (handle, i, x) => {
  768. handle.buf[i] = x;
  769. },
  770. delete_file: fs.rmSync.bind(fs),
  771. stream_stdin() {
  772. return new ReadableStream({
  773. async start(controller) {
  774. for await (const chunk of process.stdin) {
  775. controller.enqueue(chunk);
  776. }
  777. controller.close();
  778. }
  779. });
  780. },
  781. stream_stdout() {
  782. return make_textual_writable_stream(s => process.stdout.write(s));
  783. },
  784. stream_stderr() {
  785. return make_textual_writable_stream(s => process.stderr.write(s));
  786. },
  787. };
  788. }
  789. this.#debug_handler = {
  790. debug_str(x) { console.log(`debug: ${x}`); },
  791. debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
  792. debug_str_scm(x, y) { console.log(`debug: ${x}: #<scm>`); },
  793. };
  794. }
  795. static async fetch_and_instantiate(path, { abi, reflect_wasm_dir = '.',
  796. user_imports = {} }) {
  797. await load_wtf8_helper_module(reflect_wasm_dir);
  798. let io = {
  799. write_stdout(str) { mod.#io_handler.write_stdout(str); },
  800. write_stderr(str) { mod.#io_handler.write_stderr(str); },
  801. read_stdin() { return mod.#io_handler.read_stdin(); },
  802. file_exists(filename) { return mod.#io_handler.file_exists(filename); },
  803. open_input_file(filename) { return mod.#io_handler.open_input_file(filename); },
  804. open_output_file(filename) { return mod.#io_handler.open_output_file(filename); },
  805. close_file(handle) { mod.#io_handler.close_file(handle); },
  806. read_file(handle, length) { return mod.#io_handler.read_file(handle, length); },
  807. write_file(handle, length) { return mod.#io_handler.write_file(handle, length); },
  808. seek_file(handle, offset, whence) { return mod.#io_handler.seek_file(handle, offset, whence); },
  809. file_random_access(handle) { return mod.#io_handler.file_random_access(handle); },
  810. file_buffer_size(handle) { return mod.#io_handler.file_buffer_size(handle); },
  811. file_buffer_ref(handle, i) { return mod.#io_handler.file_buffer_ref(handle, i); },
  812. file_buffer_set(handle, i, x) { return mod.#io_handler.file_buffer_set(handle, i, x); },
  813. delete_file(filename) { mod.#io_handler.delete_file(filename); },
  814. stream_stdin() { return mod.#io_handler.stream_stdin(); },
  815. stream_stdout() { return mod.#io_handler.stream_stdout(); },
  816. stream_stderr() { return mod.#io_handler.stream_stderr(); },
  817. };
  818. let debug = {
  819. debug_str(x) { mod.#debug_handler.debug_str(x); },
  820. debug_str_i32(x, y) { mod.#debug_handler.debug_str_i32(x, y); },
  821. debug_str_scm(x, y) { mod.#debug_handler.debug_str_scm(x, y); },
  822. code_name(code) { return SchemeModule.#code_name(code); },
  823. code_source(code) { return SchemeModule.#code_source(code); },
  824. };
  825. let ffi = {
  826. is_extern_func(x) {
  827. return mod.#ffi_handler.is_extern_func(x);
  828. },
  829. call_extern(f, args) {
  830. return mod.#ffi_handler.call_extern(f, args);
  831. },
  832. procedure_to_extern(proc) {
  833. return mod.#ffi_handler.procedure_to_extern(proc);
  834. }
  835. };
  836. let finalization = {
  837. make_finalization_registry(f) {
  838. return mod.#finalization_handler.make_finalization_registry(f);
  839. },
  840. finalization_registry_register(registry, target, heldValue) {
  841. mod.#finalization_handler.finalization_registry_register(registry, target, heldValue);
  842. },
  843. finalization_registry_register_with_token(registry, target, heldValue, unregisterToken) {
  844. mod.#finalization_handler.finalization_registry_register_with_token(registry, target, heldValue, unregisterToken);
  845. },
  846. finalization_registry_unregister(registry, unregisterToken) {
  847. return mod.#finalization_handler.finalization_registry_unregister(registry, unregisterToken);
  848. }
  849. };
  850. let imports = {
  851. rt: SchemeModule.#rt,
  852. abi, debug, io, ffi, finalization, ...user_imports
  853. };
  854. let { module, instance } = await instantiate_streaming(path, imports);
  855. let mod = new SchemeModule(instance);
  856. return mod;
  857. }
  858. set_io_handler(h) { this.#io_handler = h; }
  859. set_debug_handler(h) { this.#debug_handler = h; }
  860. set_ffi_handler(h) { this.#ffi_handler = h; }
  861. set_finalization_handler(h) { this.#finalization_handler = h; }
  862. all_exports() { return this.#instance.exports; }
  863. exported_abi() {
  864. let abi = {}
  865. for (let [k, v] of Object.entries(this.all_exports())) {
  866. if (k.startsWith("$"))
  867. abi[k] = v;
  868. }
  869. return abi;
  870. }
  871. exports() {
  872. let ret = {}
  873. for (let [k, v] of Object.entries(this.all_exports())) {
  874. if (!k.startsWith("$"))
  875. ret[k] = v;
  876. }
  877. return ret;
  878. }
  879. get_export(name) {
  880. if (name in this.all_exports())
  881. return this.all_exports()[name];
  882. throw new Error(`unknown export: ${name}`)
  883. }
  884. instance_code(idx) {
  885. if ('%instance-code' in this.all_exports()) {
  886. return this.all_exports()['%instance-code'](idx);
  887. }
  888. return null;
  889. }
  890. instance_code_name(idx) {
  891. if ('%instance-code-name' in this.all_exports()) {
  892. return this.all_exports()['%instance-code-name'](idx);
  893. }
  894. return null;
  895. }
  896. instance_code_source(idx) {
  897. if ('%instance-code-source' in this.all_exports()) {
  898. return this.all_exports()['%instance-code-source'](idx);
  899. }
  900. return [null, 0, 0];
  901. }
  902. async reflect(opts = {}) {
  903. return await Scheme.reflect(this.exported_abi(), opts);
  904. }
  905. }
  906. function repr(obj) {
  907. if (obj instanceof HeapObject)
  908. return obj.repr();
  909. if (typeof obj === 'boolean')
  910. return obj ? '#t' : '#f';
  911. if (typeof obj === 'number')
  912. return flonum_to_string(obj);
  913. if (typeof obj === 'string')
  914. return string_repr(obj);
  915. return obj + '';
  916. }
  917. // Modulize when possible.
  918. if (typeof exports !== 'undefined') {
  919. exports.Scheme = Scheme;
  920. exports.SchemeQuitError = SchemeQuitError;
  921. exports.repr = repr;
  922. }