reflect.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  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.match(/[a-zA-Z0-9$[\]().]/)) return `#\\${ch}`;
  9. return `#\\x${this.codepoint.toString(16)}`;
  10. }
  11. }
  12. class Eof { toString() { return "#<eof>"; } }
  13. class Nil { toString() { return "#nil"; } }
  14. class Null { toString() { return "()"; } }
  15. class Unspecified { toString() { return "#<unspecified>"; } }
  16. class Complex {
  17. constructor(real, imag) {
  18. this.real = real;
  19. this.imag = imag;
  20. }
  21. toString() {
  22. const sign = this.imag >= 0 && Number.isFinite(this.imag) ? "+": "";
  23. return `${flonum_to_string(this.real)}${sign}${flonum_to_string(this.imag)}i`;
  24. }
  25. }
  26. class Fraction {
  27. constructor(num, denom) {
  28. this.num = num;
  29. this.denom = denom;
  30. }
  31. toString() {
  32. return `${this.num}/${this.denom}`;
  33. }
  34. }
  35. class HeapObject {
  36. constructor(reflector, obj) {
  37. this.reflector = reflector;
  38. this.obj = obj;
  39. }
  40. repr() { return this.toString(); } // Default implementation.
  41. }
  42. class Pair extends HeapObject {
  43. toString() { return "#<pair>"; }
  44. repr() {
  45. let car_repr = repr(this.reflector.car(this));
  46. let cdr_repr = repr(this.reflector.cdr(this));
  47. if (cdr_repr == '()')
  48. return `(${car_repr})`;
  49. if (cdr_repr.charAt(0) == '(')
  50. return `(${car_repr} ${cdr_repr.substring(1)}`;
  51. return `(${car_repr} . ${cdr_repr})`;
  52. }
  53. }
  54. class MutablePair extends Pair { toString() { return "#<mutable-pair>"; } }
  55. class Vector extends HeapObject {
  56. toString() { return "#<vector>"; }
  57. repr() {
  58. let len = this.reflector.vector_length(this);
  59. let out = '#(';
  60. for (let i = 0; i < len; i++) {
  61. if (i) out += ' ';
  62. out += repr(this.reflector.vector_ref(this, i));
  63. }
  64. out += ')';
  65. return out;
  66. }
  67. }
  68. class MutableVector extends Vector {
  69. toString() { return "#<mutable-vector>"; }
  70. }
  71. class Bytevector extends HeapObject {
  72. toString() { return "#<bytevector>"; }
  73. repr() {
  74. let len = this.reflector.bytevector_length(this);
  75. let out = '#vu8(';
  76. for (let i = 0; i < len; i++) {
  77. if (i) out += ' ';
  78. out += this.reflector.bytevector_ref(this, i);
  79. }
  80. out += ')';
  81. return out;
  82. }
  83. }
  84. class MutableBytevector extends Bytevector {
  85. toString() { return "#<mutable-bytevector>"; }
  86. }
  87. class Bitvector extends HeapObject {
  88. toString() { return "#<bitvector>"; }
  89. repr() {
  90. let len = this.reflector.bitvector_length(this);
  91. let out = '#*';
  92. for (let i = 0; i < len; i++) {
  93. out += this.reflector.bitvector_ref(this, i) ? '1' : '0';
  94. }
  95. return out;
  96. }
  97. }
  98. class MutableBitvector extends Bitvector {
  99. toString() { return "#<mutable-bitvector>"; }
  100. }
  101. class MutableString extends HeapObject {
  102. toString() { return "#<mutable-string>"; }
  103. repr() { return string_repr(this.reflector.string_value(this)); }
  104. }
  105. class Procedure extends HeapObject {
  106. toString() { return "#<procedure>"; }
  107. call(...arg) {
  108. return this.reflector.call(this, ...arg);
  109. }
  110. async call_async(...arg) {
  111. return await this.reflector.call_async(this, ...arg);
  112. }
  113. }
  114. class Sym extends HeapObject {
  115. toString() { return "#<symbol>"; }
  116. repr() { return this.reflector.symbol_name(this); }
  117. }
  118. class Keyword extends HeapObject {
  119. toString() { return "#<keyword>"; }
  120. repr() { return `#:${this.reflector.keyword_name(this)}`; }
  121. }
  122. class Variable extends HeapObject { toString() { return "#<variable>"; } }
  123. class AtomicBox extends HeapObject { toString() { return "#<atomic-box>"; } }
  124. class HashTable extends HeapObject { toString() { return "#<hash-table>"; } }
  125. class WeakTable extends HeapObject { toString() { return "#<weak-table>"; } }
  126. class Fluid extends HeapObject { toString() { return "#<fluid>"; } }
  127. class DynamicState extends HeapObject { toString() { return "#<dynamic-state>"; } }
  128. class Syntax extends HeapObject { toString() { return "#<syntax>"; } }
  129. class Port extends HeapObject { toString() { return "#<port>"; } }
  130. class Struct extends HeapObject { toString() { return "#<struct>"; } }
  131. function instantiate_streaming(path, imports) {
  132. if (typeof fetch !== 'undefined' && typeof window !== 'undefined')
  133. return WebAssembly.instantiateStreaming(fetch(path), imports);
  134. let bytes;
  135. if (typeof read !== 'undefined') {
  136. bytes = read(path, 'binary');
  137. } else if (typeof readFile !== 'undefined') {
  138. bytes = readFile(path);
  139. } else {
  140. let fs = require('fs');
  141. bytes = fs.readFileSync(path);
  142. }
  143. return WebAssembly.instantiate(bytes, imports);
  144. }
  145. class Scheme {
  146. #instance;
  147. #abi;
  148. constructor(instance, abi) {
  149. this.#instance = instance;
  150. this.#abi = abi;
  151. }
  152. static async reflect(abi, {reflect_wasm_dir = '.'}) {
  153. let debug = {
  154. debug_str(x) { console.log(`reflect debug: ${x}`); },
  155. debug_str_i32(x, y) { console.log(`reflect debug: ${x}: ${y}`); },
  156. debug_str_scm: (x, y) => {
  157. console.log(`reflect debug: ${x}: #<scm>`);
  158. },
  159. };
  160. let reflect_wasm = reflect_wasm_dir + '/reflect.wasm';
  161. let rt = {
  162. die(tag, data) { throw new SchemeTrapError(tag, data); },
  163. wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
  164. string_to_wtf8(str) { return string_to_wtf8(str); },
  165. };
  166. let { module, instance } =
  167. await instantiate_streaming(reflect_wasm, { abi, debug, rt });
  168. return new Scheme(instance, abi);
  169. }
  170. #init_module(mod) {
  171. mod.set_debug_handler({
  172. debug_str(x) { console.log(`debug: ${x}`); },
  173. debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
  174. debug_str_scm: (x, y) => {
  175. console.log(`debug: ${x}: ${repr(this.#to_js(y))}`);
  176. },
  177. });
  178. mod.set_ffi_handler({
  179. procedure_to_extern: (obj) => {
  180. const proc = this.#to_js(obj);
  181. return (...args) => {
  182. return proc.call(...args);
  183. };
  184. }
  185. });
  186. let proc = new Procedure(this, mod.get_export('$load').value);
  187. return proc.call();
  188. }
  189. static async load_main(path, opts = {}) {
  190. let mod = await SchemeModule.fetch_and_instantiate(path, opts);
  191. let reflect = await mod.reflect(opts);
  192. return reflect.#init_module(mod);
  193. }
  194. async load_extension(path, opts = {}) {
  195. opts = Object.assign({ abi: this.#abi }, opts);
  196. let mod = await SchemeModule.fetch_and_instantiate(path, opts);
  197. return this.#init_module(mod);
  198. }
  199. #to_scm(js) {
  200. let api = this.#instance.exports;
  201. if (typeof(js) == 'number') {
  202. return api.scm_from_f64(js);
  203. } else if (typeof(js) == 'bigint') {
  204. if (BigInt(api.scm_most_negative_fixnum()) <= js
  205. && js <= BigInt(api.scm_most_positive_fixnum()))
  206. return api.scm_from_fixnum(Number(js));
  207. return api.scm_from_bignum(js);
  208. } else if (typeof(js) == 'boolean') {
  209. return js ? api.scm_true() : api.scm_false();
  210. } else if (typeof(js) == 'string') {
  211. return api.scm_from_string(js);
  212. } else if (typeof(js) == 'object') {
  213. if (js instanceof Eof) return api.scm_eof();
  214. if (js instanceof Nil) return api.scm_nil();
  215. if (js instanceof Null) return api.scm_null();
  216. if (js instanceof Unspecified) return api.scm_unspecified();
  217. if (js instanceof Char) return api.scm_from_char(js.codepoint);
  218. if (js instanceof HeapObject) return js.obj;
  219. if (js instanceof Fraction)
  220. return api.scm_from_fraction(this.#to_scm(js.num),
  221. this.#to_scm(js.denom));
  222. if (js instanceof Complex)
  223. return api.scm_from_complex(js.real, js.imag);
  224. return api.scm_from_extern(js);
  225. } else if (typeof(js) == 'function') {
  226. return api.scm_from_extern(js);
  227. } else {
  228. throw new Error(`unexpected; ${typeof(js)}`);
  229. }
  230. }
  231. #to_js(scm) {
  232. let api = this.#instance.exports;
  233. let descr = api.describe(scm);
  234. let handlers = {
  235. fixnum: () => BigInt(api.fixnum_value(scm)),
  236. char: () => new Char(api.char_value(scm)),
  237. true: () => true,
  238. false: () => false,
  239. eof: () => new Eof,
  240. nil: () => new Nil,
  241. null: () => new Null,
  242. unspecified: () => new Unspecified,
  243. flonum: () => api.flonum_value(scm),
  244. bignum: () => api.bignum_value(scm),
  245. complex: () => new Complex(api.complex_real(scm),
  246. api.complex_imag(scm)),
  247. fraction: () => new Fraction(this.#to_js(api.fraction_num(scm)),
  248. this.#to_js(api.fraction_denom(scm))),
  249. pair: () => new Pair(this, scm),
  250. 'mutable-pair': () => new MutablePair(this, scm),
  251. vector: () => new Vector(this, scm),
  252. 'mutable-vector': () => new MutableVector(this, scm),
  253. bytevector: () => new Bytevector(this, scm),
  254. 'mutable-bytevector': () => new MutableBytevector(this, scm),
  255. bitvector: () => new Bitvector(this, scm),
  256. 'mutable-bitvector': () => new MutableBitvector(this, scm),
  257. string: () => api.string_value(scm),
  258. 'mutable-string': () => new MutableString(this, scm),
  259. procedure: () => new Procedure(this, scm),
  260. symbol: () => new Sym(this, scm),
  261. keyword: () => new Keyword(this, scm),
  262. variable: () => new Variable(this, scm),
  263. 'atomic-box': () => new AtomicBox(this, scm),
  264. 'hash-table': () => new HashTable(this, scm),
  265. 'weak-table': () => new WeakTable(this, scm),
  266. fluid: () => new Fluid(this, scm),
  267. 'dynamic-state': () => new DynamicState(this, scm),
  268. syntax: () => new Syntax(this, scm),
  269. port: () => new Port(this, scm),
  270. struct: () => new Struct(this, scm),
  271. 'extern-ref': () => api.extern_value(scm)
  272. };
  273. let handler = handlers[descr];
  274. return handler ? handler() : scm;
  275. }
  276. call(func, ...args) {
  277. let api = this.#instance.exports;
  278. let argv = api.make_vector(args.length + 1, api.scm_false());
  279. func = this.#to_scm(func);
  280. api.vector_set(argv, 0, func);
  281. for (let [idx, arg] of args.entries())
  282. api.vector_set(argv, idx + 1, this.#to_scm(arg));
  283. argv = api.call(func, argv);
  284. let results = [];
  285. for (let idx = 0; idx < api.vector_length(argv); idx++)
  286. results.push(this.#to_js(api.vector_ref(argv, idx)))
  287. return results;
  288. }
  289. call_async(func, ...args) {
  290. return new Promise((resolve, reject) => {
  291. this.call(func,
  292. val => resolve(this.#to_js(val)),
  293. err => reject(this.#to_js(err)),
  294. ...args);
  295. })
  296. }
  297. car(x) { return this.#to_js(this.#instance.exports.car(x.obj)); }
  298. cdr(x) { return this.#to_js(this.#instance.exports.cdr(x.obj)); }
  299. vector_length(x) { return this.#instance.exports.vector_length(x.obj); }
  300. vector_ref(x, i) {
  301. return this.#to_js(this.#instance.exports.vector_ref(x.obj, i));
  302. }
  303. bytevector_length(x) {
  304. return this.#instance.exports.bytevector_length(x.obj);
  305. }
  306. bytevector_ref(x, i) {
  307. return this.#instance.exports.bytevector_ref(x.obj, i);
  308. }
  309. bitvector_length(x) {
  310. return this.#instance.exports.bitvector_length(x.obj);
  311. }
  312. bitvector_ref(x, i) {
  313. return this.#instance.exports.bitvector_ref(x.obj, i) == 1;
  314. }
  315. string_value(x) { return this.#instance.exports.string_value(x.obj); }
  316. symbol_name(x) { return this.#instance.exports.symbol_name(x.obj); }
  317. keyword_name(x) { return this.#instance.exports.keyword_name(x.obj); }
  318. }
  319. class SchemeTrapError extends Error {
  320. constructor(tag, data) { super(); this.tag = tag; this.data = data; }
  321. // FIXME: data is raw Scheme object; would need to be reflected to
  322. // have a toString.
  323. toString() { return `SchemeTrap(${this.tag}, <data>)`; }
  324. }
  325. function string_repr(str) {
  326. // FIXME: Improve to match Scheme.
  327. return '"' + str.replace(/(["\\])/g, '\\$1').replace(/\n/g, '\\n') + '"';
  328. }
  329. function flonum_to_string(f64) {
  330. if (Object.is(f64, -0)) {
  331. return '-0.0';
  332. } else if (Number.isFinite(f64)) {
  333. let repr = f64 + '';
  334. return /^-?[0-9]+$/.test(repr) ? repr + '.0' : repr;
  335. } else if (Number.isNaN(f64)) {
  336. return '+nan.0';
  337. } else {
  338. return f64 < 0 ? '-inf.0' : '+inf.0';
  339. }
  340. }
  341. let async_invoke = typeof queueMicrotask !== 'undefined'
  342. ? queueMicrotask
  343. : thunk => setTimeout(thunk, 0);
  344. function async_invoke_later(thunk, jiffies) {
  345. setTimeout(thunk, jiffies / 1000);
  346. }
  347. let wtf8_helper;
  348. function wtf8_to_string(wtf8) {
  349. let { as_iter, iter_next } = wtf8_helper.exports;
  350. let codepoints = [];
  351. let iter = as_iter(wtf8);
  352. for (let cp = iter_next(iter); cp != -1; cp = iter_next(iter))
  353. codepoints.push(cp);
  354. // Passing too many codepoints can overflow the stack.
  355. let maxcp = 100000;
  356. if (codepoints.length <= maxcp) {
  357. return String.fromCodePoint(...codepoints);
  358. }
  359. // For converting large strings, concatenate several smaller
  360. // strings.
  361. let substrings = [];
  362. let end = 0;
  363. for (let start = 0; start != codepoints.length; start = end) {
  364. end = Math.min(start + maxcp, codepoints.length);
  365. substrings.push(String.fromCodePoint(...codepoints.slice(start, end)));
  366. }
  367. return substrings.join('');
  368. }
  369. function string_to_wtf8(str) {
  370. let { string_builder, builder_push_codepoint, finish_builder } =
  371. wtf8_helper.exports;
  372. let builder = string_builder()
  373. for (let cp of str)
  374. builder_push_codepoint(builder, cp.codePointAt(0));
  375. return finish_builder(builder);
  376. }
  377. async function load_wtf8_helper_module(reflect_wasm_dir = '') {
  378. if (wtf8_helper) return;
  379. let wtf8_wasm = reflect_wasm_dir + "/wtf8.wasm";
  380. let { module, instance } = await instantiate_streaming(wtf8_wasm);
  381. wtf8_helper = instance;
  382. }
  383. class SchemeModule {
  384. #instance;
  385. #io_handler;
  386. #debug_handler;
  387. #ffi_handler;
  388. static #rt = {
  389. bignum_from_string(str) { return BigInt(str); },
  390. bignum_from_i32(n) { return BigInt(n); },
  391. bignum_from_i64(n) { return n; },
  392. bignum_from_u64(n) { return n < 0n ? 0xffff_ffff_ffff_ffffn + (n + 1n) : n; },
  393. bignum_is_i64(n) {
  394. return -0x8000_0000_0000_0000n <= n && n <= 0x7FFF_FFFF_FFFF_FFFFn;
  395. },
  396. bignum_is_u64(n) {
  397. return 0n <= n && n <= 0xFFFF_FFFF_FFFF_FFFFn;
  398. },
  399. // This truncates; see https://tc39.es/ecma262/#sec-tobigint64.
  400. bignum_get_i64(n) { return n; },
  401. bignum_add(a, b) { return BigInt(a) + BigInt(b) },
  402. bignum_sub(a, b) { return BigInt(a) - BigInt(b) },
  403. bignum_mul(a, b) { return BigInt(a) * BigInt(b) },
  404. bignum_lsh(a, b) { return BigInt(a) << BigInt(b) },
  405. bignum_rsh(a, b) { return BigInt(a) >> BigInt(b) },
  406. bignum_quo(a, b) { return BigInt(a) / BigInt(b) },
  407. bignum_rem(a, b) { return BigInt(a) % BigInt(b) },
  408. bignum_mod(a, b) {
  409. let r = BigInt(a) % BigInt(b);
  410. if ((b > 0n && r < 0n) || (b < 0n && r > 0n)) {
  411. return b + r;
  412. } else {
  413. return r;
  414. }
  415. },
  416. bignum_gcd(a, b) {
  417. a = BigInt(a);
  418. b = BigInt(b);
  419. if (a < 0n) { a = -a; }
  420. if (b < 0n) { b = -b; }
  421. if (a == 0n) { return b; }
  422. if (b == 0n) { return a; }
  423. let r;
  424. while (b != 0n) {
  425. r = a % b;
  426. a = b;
  427. b = r;
  428. }
  429. return a;
  430. },
  431. bignum_logand(a, b) { return BigInt(a) & BigInt(b); },
  432. bignum_logior(a, b) { return BigInt(a) | BigInt(b); },
  433. bignum_logxor(a, b) { return BigInt(a) ^ BigInt(b); },
  434. bignum_logsub(a, b) { return BigInt(a) & (~ BigInt(b)); },
  435. bignum_lt(a, b) { return a < b; },
  436. bignum_le(a, b) { return a <= b; },
  437. bignum_eq(a, b) { return a == b; },
  438. bignum_to_f64(n) { return Number(n); },
  439. f64_is_nan(n) { return Number.isNaN(n); },
  440. f64_is_infinite(n) { return !Number.isFinite(n); },
  441. flonum_to_string,
  442. string_upcase: Function.call.bind(String.prototype.toUpperCase),
  443. string_downcase: Function.call.bind(String.prototype.toLowerCase),
  444. make_weak_map() { return new WeakMap; },
  445. weak_map_get(map, k, fail) {
  446. const val = map.get(k);
  447. return val === undefined ? fail: val;
  448. },
  449. weak_map_set(map, k, v) { return map.set(k, v); },
  450. weak_map_delete(map, k) { return map.delete(k); },
  451. fsqrt: Math.sqrt,
  452. fsin: Math.sin,
  453. fcos: Math.cos,
  454. ftan: Math.tan,
  455. fasin: Math.asin,
  456. facos: Math.acos,
  457. fatan: Math.atan,
  458. fatan2: Math.atan2,
  459. flog: Math.log,
  460. fexp: Math.exp,
  461. jiffies_per_second() { return 1000000; },
  462. current_jiffy() { return performance.now() * 1000; },
  463. current_second() { return Date.now() / 1000; },
  464. async_invoke,
  465. async_invoke_later,
  466. promise_on_completed(p, kt, kf) { p.then(kt, kf); },
  467. promise_complete(callback, val) { callback(val); },
  468. // Wrap in functions to allow for lazy loading of the wtf8
  469. // module.
  470. wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
  471. string_to_wtf8(str) { return string_to_wtf8(str); },
  472. die(tag, data) { throw new SchemeTrapError(tag, data); }
  473. };
  474. constructor(instance) {
  475. this.#instance = instance;
  476. let open_file_error = (filename) => {
  477. throw new Error('No file system access');
  478. };
  479. if (typeof printErr === 'function') { // v8/sm dev console
  480. // On the console, try to use 'write' (v8) or 'putstr' (sm),
  481. // as these don't add an extraneous newline. Unfortunately
  482. // JSC doesn't have a printer that doesn't add a newline.
  483. let write_no_newline =
  484. typeof write === 'function' ? write
  485. : typeof putstr === 'function' ? putstr : print;
  486. // Use readline when available. v8 strips newlines so
  487. // we need to add them back.
  488. let read_stdin =
  489. typeof readline == 'function' ? () => {
  490. let line = readline();
  491. if (line) {
  492. return `${line}\n`;
  493. } else {
  494. return '\n';
  495. }
  496. }: () => '';
  497. this.#io_handler = {
  498. write_stdout: write_no_newline,
  499. write_stderr: printErr,
  500. read_stdin,
  501. file_exists: (filename) => false,
  502. open_input_file: open_file_error,
  503. open_output_file: open_file_error,
  504. close_file: () => undefined,
  505. read_file: (handle, length) => 0,
  506. write_file: (handle, length) => 0,
  507. seek_file: (handle, offset, whence) => -1,
  508. file_random_access: (handle) => false,
  509. file_buffer_size: (handle) => 0,
  510. file_buffer_ref: (handle, i) => 0,
  511. file_buffer_set: (handle, i, x) => undefined,
  512. delete_file: (filename) => undefined
  513. };
  514. } else if (typeof window !== 'undefined') { // web browser
  515. this.#io_handler = {
  516. write_stdout: console.log,
  517. write_stderr: console.error,
  518. read_stdin: () => '',
  519. file_exists: (filename) => false,
  520. open_input_file: open_file_error,
  521. open_output_file: open_file_error,
  522. close_file: () => undefined,
  523. read_file: (handle, length) => 0,
  524. write_file: (handle, length) => 0,
  525. seek_file: (handle, offset, whence) => -1,
  526. file_random_access: (handle) => false,
  527. file_buffer_size: (handle) => 0,
  528. file_buffer_ref: (handle, i) => 0,
  529. file_buffer_set: (handle, i, x) => undefined,
  530. delete_file: (filename) => undefined
  531. };
  532. } else { // nodejs
  533. const fs = require('fs');
  534. const process = require('process');
  535. const bufLength = 1024;
  536. const stdinBuf = Buffer.alloc(bufLength);
  537. const SEEK_SET = 0, SEEK_CUR = 1, SEEK_END = 2;
  538. this.#io_handler = {
  539. write_stdout: process.stdout.write.bind(process.stdout),
  540. write_stderr: process.stderr.write.bind(process.stderr),
  541. read_stdin: () => {
  542. let n = fs.readSync(process.stdin.fd, stdinBuf, 0, stdinBuf.length);
  543. return stdinBuf.toString('utf8', 0, n);
  544. },
  545. file_exists: fs.existsSync.bind(fs),
  546. open_input_file: (filename) => {
  547. let fd = fs.openSync(filename, 'r');
  548. return {
  549. fd,
  550. buf: Buffer.alloc(bufLength),
  551. pos: 0
  552. };
  553. },
  554. open_output_file: (filename) => {
  555. let fd = fs.openSync(filename, 'w');
  556. return {
  557. fd,
  558. buf: Buffer.alloc(bufLength),
  559. pos: 0
  560. };
  561. },
  562. close_file: (handle) => {
  563. fs.closeSync(handle.fd);
  564. },
  565. read_file: (handle, count) => {
  566. const n = fs.readSync(handle.fd, handle.buf, 0, count, handle.pos);
  567. handle.pos += n;
  568. return n;
  569. },
  570. write_file: (handle, count) => {
  571. const n = fs.writeSync(handle.fd, handle.buf, 0, count, handle.pos);
  572. handle.pos += n;
  573. return n;
  574. },
  575. seek_file: (handle, offset, whence) => {
  576. // There doesn't seem to be a way to ask NodeJS if
  577. // a position is valid or not.
  578. if (whence == SEEK_SET) {
  579. handle.pos = offset;
  580. return handle.pos;
  581. } else if (whence == SEEK_CUR) {
  582. handle.pos += offset;
  583. return handle.pos;
  584. }
  585. // SEEK_END not supported.
  586. return -1;
  587. },
  588. file_random_access: (handle) => {
  589. return true;
  590. },
  591. file_buffer_size: (handle) => {
  592. return handle.buf.length;
  593. },
  594. file_buffer_ref: (handle, i) => {
  595. return handle.buf[i];
  596. },
  597. file_buffer_set: (handle, i, x) => {
  598. handle.buf[i] = x;
  599. },
  600. delete_file: fs.rmSync.bind(fs)
  601. };
  602. }
  603. this.#debug_handler = {
  604. debug_str(x) { console.log(`debug: ${x}`); },
  605. debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
  606. debug_str_scm(x, y) { console.log(`debug: ${x}: #<scm>`); },
  607. };
  608. }
  609. static async fetch_and_instantiate(path, { abi, reflect_wasm_dir = '.',
  610. user_imports = {} }) {
  611. await load_wtf8_helper_module(reflect_wasm_dir);
  612. let io = {
  613. write_stdout(str) { mod.#io_handler.write_stdout(str); },
  614. write_stderr(str) { mod.#io_handler.write_stderr(str); },
  615. read_stdin() { return mod.#io_handler.read_stdin(); },
  616. file_exists(filename) { return mod.#io_handler.file_exists(filename); },
  617. open_input_file(filename) { return mod.#io_handler.open_input_file(filename); },
  618. open_output_file(filename) { return mod.#io_handler.open_output_file(filename); },
  619. close_file(handle) { mod.#io_handler.close_file(handle); },
  620. read_file(handle, length) { return mod.#io_handler.read_file(handle, length); },
  621. write_file(handle, length) { return mod.#io_handler.write_file(handle, length); },
  622. seek_file(handle, offset, whence) { return mod.#io_handler.seek_file(handle, offset, whence); },
  623. file_random_access(handle) { return mod.#io_handler.file_random_access(handle); },
  624. file_buffer_size(handle) { return mod.#io_handler.file_buffer_size(handle); },
  625. file_buffer_ref(handle, i) { return mod.#io_handler.file_buffer_ref(handle, i); },
  626. file_buffer_set(handle, i, x) { return mod.#io_handler.file_buffer_set(handle, i, x); },
  627. delete_file(filename) { mod.#io_handler.delete_file(filename); }
  628. };
  629. let debug = {
  630. debug_str(x) { mod.#debug_handler.debug_str(x); },
  631. debug_str_i32(x, y) { mod.#debug_handler.debug_str_i32(x, y); },
  632. debug_str_scm(x, y) { mod.#debug_handler.debug_str_scm(x, y); },
  633. }
  634. let ffi = {
  635. procedure_to_extern(proc) {
  636. return mod.#ffi_handler.procedure_to_extern(proc);
  637. }
  638. };
  639. let imports = {
  640. rt: SchemeModule.#rt,
  641. abi, debug, io, ffi, ...user_imports
  642. };
  643. let { module, instance } = await instantiate_streaming(path, imports);
  644. let mod = new SchemeModule(instance);
  645. return mod;
  646. }
  647. set_io_handler(h) { this.#io_handler = h; }
  648. set_debug_handler(h) { this.#debug_handler = h; }
  649. set_ffi_handler(h) { this.#ffi_handler = h; }
  650. all_exports() { return this.#instance.exports; }
  651. exported_abi() {
  652. let abi = {}
  653. for (let [k, v] of Object.entries(this.all_exports())) {
  654. if (k.startsWith("$"))
  655. abi[k] = v;
  656. }
  657. return abi;
  658. }
  659. exports() {
  660. let ret = {}
  661. for (let [k, v] of Object.entries(this.all_exports())) {
  662. if (!k.startsWith("$"))
  663. ret[k] = v;
  664. }
  665. return ret;
  666. }
  667. get_export(name) {
  668. if (name in this.all_exports())
  669. return this.all_exports()[name];
  670. throw new Error(`unknown export: ${name}`)
  671. }
  672. async reflect(opts = {}) {
  673. return await Scheme.reflect(this.exported_abi(), opts);
  674. }
  675. }
  676. function repr(obj) {
  677. if (obj instanceof HeapObject)
  678. return obj.repr();
  679. if (typeof obj === 'boolean')
  680. return obj ? '#t' : '#f';
  681. if (typeof obj === 'number')
  682. return flonum_to_string(obj);
  683. if (typeof obj === 'string')
  684. return string_repr(obj);
  685. return obj + '';
  686. }
  687. // Modulize when possible.
  688. if (typeof exports !== 'undefined') {
  689. exports.Scheme = Scheme;
  690. exports.repr = repr;
  691. }