reflect.js 25 KB

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