reflect.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. class Char {
  2. constructor(codepoint) {
  3. this.codepoint = codepoint;
  4. }
  5. toString() {
  6. let ch = String.fromCodePoint(this.codepoint);
  7. if (ch.match(/[a-zA-Z0-9$[\]().]/)) return `#\\${ch}`;
  8. return `#\\x${this.codepoint.toString(16)}`;
  9. }
  10. }
  11. class Eof { toString() { return "#<eof>"; } }
  12. class Nil { toString() { return "#nil"; } }
  13. class Null { toString() { return "()"; } }
  14. class Unspecified { toString() { return "#<unspecified>"; } }
  15. class Complex {
  16. constructor(real, imag) {
  17. this.real = real;
  18. this.imag = imag;
  19. }
  20. toString() {
  21. return `${flonum_to_string(this.real)}+${flonum_to_string(this.imag)}i`;
  22. }
  23. }
  24. class Fraction {
  25. constructor(num, denom) {
  26. this.num = num;
  27. this.denom = denom;
  28. }
  29. toString() {
  30. return `${this.num}/${this.denom}`;
  31. }
  32. }
  33. class HeapObject {
  34. constructor(reflector, obj) {
  35. this.reflector = reflector;
  36. this.obj = obj;
  37. }
  38. repr() { return this.toString(); } // Default implementation.
  39. }
  40. class Pair extends HeapObject {
  41. toString() { return "#<pair>"; }
  42. repr() {
  43. let car_repr = repr(this.reflector.car(this));
  44. let cdr_repr = repr(this.reflector.cdr(this));
  45. if (cdr_repr == '()')
  46. return `(${car_repr})`;
  47. if (cdr_repr.charAt(0) == '(')
  48. return `(${car_repr} ${cdr_repr.substring(1)}`;
  49. return `(${car_repr} . ${cdr_repr})`;
  50. }
  51. }
  52. class MutablePair extends Pair { toString() { return "#<mutable-pair>"; } }
  53. class Vector extends HeapObject {
  54. toString() { return "#<vector>"; }
  55. repr() {
  56. let len = this.reflector.vector_length(this);
  57. let out = '#(';
  58. for (let i = 0; i < len; i++) {
  59. if (i) out += ' ';
  60. out += repr(this.reflector.vector_ref(this, i));
  61. }
  62. out += ')';
  63. return out;
  64. }
  65. }
  66. class MutableVector extends Vector {
  67. toString() { return "#<mutable-vector>"; }
  68. }
  69. class Bytevector extends HeapObject {
  70. toString() { return "#<bytevector>"; }
  71. repr() {
  72. let len = this.reflector.bytevector_length(this);
  73. let out = '#vu8(';
  74. for (let i = 0; i < len; i++) {
  75. if (i) out += ' ';
  76. out += this.reflector.bytevector_ref(this, i);
  77. }
  78. out += ')';
  79. return out;
  80. }
  81. }
  82. class MutableBytevector extends Bytevector {
  83. toString() { return "#<mutable-bytevector>"; }
  84. }
  85. class Bitvector extends HeapObject {
  86. toString() { return "#<bitvector>"; }
  87. repr() {
  88. let len = this.reflector.bitvector_length(this);
  89. let out = '#*';
  90. for (let i = 0; i < len; i++) {
  91. out += this.reflector.bitvector_ref(this, i) ? '1' : '0';
  92. }
  93. return out;
  94. }
  95. }
  96. class MutableBitvector extends Bitvector {
  97. toString() { return "#<mutable-bitvector>"; }
  98. }
  99. class MutableString extends HeapObject {
  100. toString() { return "#<mutable-string>"; }
  101. repr() { return this.reflector.string_value(this); }
  102. }
  103. class Procedure extends HeapObject {
  104. toString() { return "#<procedure>"; }
  105. call(...arg) {
  106. return this.reflector.call(this, ...arg);
  107. }
  108. }
  109. class Sym extends HeapObject {
  110. toString() { return "#<symbol>"; }
  111. repr() { return this.reflector.symbol_name(this); }
  112. }
  113. class Keyword extends HeapObject {
  114. toString() { return "#<keyword>"; }
  115. repr() { return `#:${this.reflector.keyword_name(this)}`; }
  116. }
  117. class Variable extends HeapObject { toString() { return "#<variable>"; } }
  118. class AtomicBox extends HeapObject { toString() { return "#<atomic-box>"; } }
  119. class HashTable extends HeapObject { toString() { return "#<hash-table>"; } }
  120. class WeakTable extends HeapObject { toString() { return "#<weak-table>"; } }
  121. class Fluid extends HeapObject { toString() { return "#<fluid>"; } }
  122. class DynamicState extends HeapObject { toString() { return "#<dynamic-state>"; } }
  123. class Syntax extends HeapObject { toString() { return "#<syntax>"; } }
  124. class Port extends HeapObject { toString() { return "#<port>"; } }
  125. class Struct extends HeapObject { toString() { return "#<struct>"; } }
  126. function instantiate_streaming(path, imports) {
  127. if (typeof fetch !== 'undefined')
  128. return WebAssembly.instantiateStreaming(fetch(path), imports);
  129. let bytes;
  130. if (typeof read !== 'undefined') {
  131. bytes = read(path, 'binary');
  132. } else if (typeof readFile !== 'undefined') {
  133. bytes = readFile(path);
  134. } else {
  135. let fs = require('fs');
  136. bytes = fs.readFileSync(path);
  137. }
  138. return WebAssembly.instantiate(bytes, imports);
  139. }
  140. class Scheme {
  141. #instance;
  142. #abi;
  143. constructor(instance, abi) {
  144. this.#instance = instance;
  145. this.#abi = abi;
  146. }
  147. static async reflect(abi) {
  148. let { module, instance } =
  149. await instantiate_streaming('js-runtime/reflect.wasm', {
  150. abi,
  151. rt: {
  152. wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
  153. string_to_wtf8(str) { return string_to_wtf8(str); },
  154. }
  155. });
  156. return new Scheme(instance, abi);
  157. }
  158. #init_module(mod) {
  159. mod.set_debug_handler({
  160. debug_str(x) { console.log(`debug: ${x}`); },
  161. debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
  162. debug_str_scm: (x, y) => {
  163. console.log(`debug: ${x}: ${repr(this.#to_js(y))}`);
  164. },
  165. });
  166. let proc = new Procedure(this, mod.get_export('$load').value)
  167. return proc.call();
  168. }
  169. static async load_main(path, abi) {
  170. let mod = await SchemeModule.fetch_and_instantiate(path, abi);
  171. let reflect = await mod.reflect();
  172. return reflect.#init_module(mod);
  173. }
  174. async load_extension(path) {
  175. let mod = await SchemeModule.fetch_and_instantiate(path, this.#abi);
  176. return this.#init_module(mod);
  177. }
  178. #to_scm(js) {
  179. let api = this.#instance.exports;
  180. if (typeof(js) == 'number') {
  181. return api.scm_from_f64(js);
  182. } else if (typeof(js) == 'bigint') {
  183. if (BigInt(api.scm_most_negative_fixnum()) <= js
  184. && js <= BigInt(api.scm_most_positive_fixnum()))
  185. return api.scm_from_fixnum(Number(js));
  186. return api.scm_from_bignum(js);
  187. } else if (typeof(js) == 'boolean') {
  188. return js ? api.scm_true() : api.scm_false();
  189. } else if (typeof(js) == 'string') {
  190. return api.scm_from_string(js);
  191. } else if (typeof(js) == 'object') {
  192. if (js instanceof Eof) return api.scm_eof();
  193. if (js instanceof Nil) return api.scm_nil();
  194. if (js instanceof Null) return api.scm_null();
  195. if (js instanceof Unspecified) return api.scm_unspecified();
  196. if (js instanceof Char) return api.scm_from_char(js.codepoint);
  197. if (js instanceof HeapObject) return js.obj;
  198. if (js instanceof Fraction)
  199. return api.scm_from_fraction(this.#to_scm(js.num),
  200. this.#to_scm(js.denom));
  201. if (js instanceof Complex)
  202. return api.scm_from_complex(js.real, js.imag);
  203. throw new Error(`unhandled; ${typeof(js)}`);
  204. } else {
  205. throw new Error(`unexpected; ${typeof(js)}`);
  206. }
  207. }
  208. #to_js(scm) {
  209. let api = this.#instance.exports;
  210. let descr = api.describe(scm);
  211. let handlers = {
  212. fixnum: () => BigInt(api.fixnum_value(scm)),
  213. char: () => new Char(api.char_value(scm)),
  214. true: () => true,
  215. false: () => false,
  216. eof: () => new Eof,
  217. nil: () => new Nil,
  218. null: () => new Null,
  219. unspecified: () => new Unspecified,
  220. flonum: () => api.flonum_value(scm),
  221. bignum: () => api.bignum_value(scm),
  222. complex: () => new Complex(api.complex_real(scm),
  223. api.complex_imag(scm)),
  224. fraction: () => new Fraction(this.#to_js(api.fraction_num(scm)),
  225. this.#to_js(api.fraction_denom(scm))),
  226. pair: () => new Pair(this, scm),
  227. 'mutable-pair': () => new MutablePair(this, scm),
  228. vector: () => new Vector(this, scm),
  229. 'mutable-vector': () => new MutableVector(this, scm),
  230. bytevector: () => new Bytevector(this, scm),
  231. 'mutable-bytevector': () => new MutableBytevector(this, scm),
  232. bitvector: () => new Bitvector(this, scm),
  233. 'mutable-bitvector': () => new MutableBitvector(this, scm),
  234. string: () => api.string_value(scm),
  235. 'mutable-string': () => new MutableString(this, scm),
  236. procedure: () => new Procedure(this, scm),
  237. symbol: () => new Sym(this, scm),
  238. keyword: () => new Keyword(this, scm),
  239. variable: () => new Variable(this, scm),
  240. 'atomic-box': () => new AtomicBox(this, scm),
  241. 'hash-table': () => new HashTable(this, scm),
  242. 'weak-table': () => new WeakTable(this, scm),
  243. fluid: () => new Fluid(this, scm),
  244. 'dynamic-state': () => new DynamicState(this, scm),
  245. syntax: () => new Syntax(this, scm),
  246. port: () => new Port(this, scm),
  247. struct: () => new Struct(this, scm),
  248. };
  249. let handler = handlers[descr];
  250. return handler ? handler() : scm;
  251. }
  252. call(func, ...args) {
  253. let api = this.#instance.exports;
  254. let argv = api.make_vector(args.length + 1, api.scm_false());
  255. func = this.#to_scm(func);
  256. api.vector_set(argv, 0, func);
  257. for (let [idx, arg] of args.entries())
  258. api.vector_set(argv, idx + 1, this.#to_scm(arg));
  259. argv = api.call(func, argv);
  260. let results = [];
  261. for (let idx = 0; idx < api.vector_length(argv); idx++)
  262. results.push(this.#to_js(api.vector_ref(argv, idx)))
  263. return results;
  264. }
  265. car(x) { return this.#to_js(this.#instance.exports.car(x.obj)); }
  266. cdr(x) { return this.#to_js(this.#instance.exports.cdr(x.obj)); }
  267. vector_length(x) { return this.#instance.exports.vector_length(x.obj); }
  268. vector_ref(x, i) {
  269. return this.#to_js(this.#instance.exports.vector_ref(x.obj, i));
  270. }
  271. bytevector_length(x) {
  272. return this.#instance.exports.bytevector_length(x.obj);
  273. }
  274. bytevector_ref(x, i) {
  275. return this.#instance.exports.bytevector_ref(x.obj, i);
  276. }
  277. bitvector_length(x) {
  278. return this.#instance.exports.bitvector_length(x.obj);
  279. }
  280. bitvector_ref(x, i) {
  281. return this.#instance.exports.bitvector_ref(x.obj, i) == 1;
  282. }
  283. string_value(x) { return this.#instance.exports.string_value(x.obj); }
  284. symbol_name(x) { return this.#instance.exports.symbol_name(x.obj); }
  285. keyword_name(x) { return this.#instance.exports.keyword_name(x.obj); }
  286. }
  287. class SchemeTrapError extends Error {
  288. constructor(tag, data) { super(); this.tag = tag; this.data = data; }
  289. // FIXME: data is raw Scheme object; would need to be reflected to
  290. // have a toString.
  291. toString() { return `SchemeTrap(${this.tag}, <data>)`; }
  292. }
  293. function flonum_to_string(f64) {
  294. if (Object.is(f64, -0)) {
  295. return '-0.0';
  296. } else if (Number.isFinite(f64)) {
  297. let repr = f64 + '';
  298. return /^-?[0-9]+$/.test(repr) ? repr + '.0' : repr;
  299. } else if (Number.isNaN(f64)) {
  300. return '+nan.0';
  301. } else {
  302. return f64 < 0 ? '-inf.0' : '+inf.0';
  303. }
  304. }
  305. let wtf8_helper;
  306. function wtf8_to_string(wtf8) {
  307. let { as_iter, iter_next } = wtf8_helper.exports;
  308. let codepoints = [];
  309. let iter = as_iter(wtf8);
  310. for (let cp = iter_next(iter); cp != -1; cp = iter_next(iter))
  311. codepoints.push(cp);
  312. return String.fromCodePoint(...codepoints);
  313. }
  314. function string_to_wtf8(str) {
  315. let { string_builder, builder_push_codepoint, finish_builder } =
  316. wtf8_helper.exports;
  317. let builder = string_builder()
  318. for (let cp of str)
  319. builder_push_codepoint(builder, cp.codePointAt(0));
  320. return finish_builder(builder);
  321. }
  322. async function load_wtf8_helper_module() {
  323. if (wtf8_helper) return;
  324. let { module, instance } = await instantiate_streaming("js-runtime/wtf8.wasm");
  325. wtf8_helper = instance;
  326. }
  327. class SchemeModule {
  328. #instance;
  329. #io_handler;
  330. #debug_handler;
  331. static #rt = {
  332. bignum_from_i32(n) { return BigInt(n); },
  333. bignum_from_i64(n) { return n; },
  334. bignum_from_u64(n) { return n < 0n ? 0xffff_ffff_ffff_ffffn + (n + 1n) : n; },
  335. bignum_is_i64(n) {
  336. return -0x8000_0000_0000_0000n <= n && n <= 0x7FFF_FFFF_FFFF_FFFFn;
  337. },
  338. bignum_is_u64(n) {
  339. return 0n <= n && n <= 0xFFFF_FFFF_FFFF_FFFFn;
  340. },
  341. // This truncates; see https://tc39.es/ecma262/#sec-tobigint64.
  342. bignum_get_i64(n) { return n; },
  343. bignum_add(a, b) { return BigInt(a) + BigInt(b) },
  344. bignum_sub(a, b) { return BigInt(a) - BigInt(b) },
  345. bignum_mul(a, b) { return BigInt(a) * BigInt(b) },
  346. bignum_lsh(a, b) { return BigInt(a) << BigInt(b) },
  347. bignum_rsh(a, b) { return BigInt(a) >> BigInt(b) },
  348. bignum_quo(a, b) { return BigInt(a) / BigInt(b) },
  349. bignum_rem(a, b) { return BigInt(a) % BigInt(b) },
  350. bignum_mod(a, b) {
  351. let r = BigInt(a) % BigInt(b);
  352. if ((b > 0n && r < 0n) || (b < 0n && r > 0n)) {
  353. return b + r;
  354. } else {
  355. return r;
  356. }
  357. },
  358. bignum_gcd(a, b) {
  359. a = BigInt(a);
  360. b = BigInt(b);
  361. if (a < 0n) { a = -a; }
  362. if (b < 0n) { b = -b; }
  363. if (a == 0n) { return b; }
  364. if (b == 0n) { return a; }
  365. let r;
  366. while (b != 0n) {
  367. r = a % b;
  368. a = b;
  369. b = r;
  370. }
  371. return a;
  372. },
  373. bignum_logand(a, b) { return BigInt(a) & BigInt(b); },
  374. bignum_logior(a, b) { return BigInt(a) | BigInt(b); },
  375. bignum_logxor(a, b) { return BigInt(a) ^ BigInt(b); },
  376. bignum_logsub(a, b) { return BigInt(a) & (~ BigInt(b)); },
  377. bignum_lt(a, b) { return a < b; },
  378. bignum_le(a, b) { return a <= b; },
  379. bignum_eq(a, b) { return a == b; },
  380. bignum_to_f64(n) { return Number(n); },
  381. f64_is_nan(n) { return Number.isNaN(n); },
  382. f64_is_infinite(n) { return !Number.isFinite(n); },
  383. flonum_to_string,
  384. string_upcase: Function.call.bind(String.prototype.toUpperCase),
  385. string_downcase: Function.call.bind(String.prototype.toLowerCase),
  386. make_weak_map() { return new WeakMap; },
  387. weak_map_get(map, k) { return map.get(k); },
  388. weak_map_set(map, k, v) { return map.set(k, v); },
  389. weak_map_delete(map, k) { return map.delete(k); },
  390. fsqrt: Math.sqrt,
  391. fsin: Math.sin,
  392. fcos: Math.cos,
  393. ftan: Math.tan,
  394. fasin: Math.asin,
  395. facos: Math.acos,
  396. fatan: Math.atan,
  397. fatan2: Math.atan2,
  398. flog: Math.log,
  399. fexp: Math.exp,
  400. // Wrap in functions to allow for lazy loading of the wtf8
  401. // module.
  402. wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
  403. string_to_wtf8(str) { return string_to_wtf8(str); },
  404. die(tag, data) { throw new SchemeTrapError(tag, data); }
  405. };
  406. constructor(instance) {
  407. this.#instance = instance;
  408. let read_stdin = () => '';
  409. if (typeof printErr === 'function') {
  410. // On the console, try to use 'write' (v8) or 'putstr' (sm),
  411. // as these don't add an extraneous newline. Unfortunately
  412. // JSC doesn't have a printer that doesn't add a newline.
  413. let write_no_newline =
  414. typeof write === 'function' ? write
  415. : typeof putstr === 'function' ? putstr : print;
  416. this.#io_handler = {
  417. write_stdout: write_no_newline,
  418. write_stderr: printErr,
  419. read_stdin
  420. };
  421. } else {
  422. this.#io_handler = {
  423. write_stdout: console.log,
  424. write_stderr: console.error,
  425. read_stdin
  426. }
  427. }
  428. this.#debug_handler = {
  429. debug_str(x) { console.log(`debug: ${x}`); },
  430. debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
  431. debug_str_scm(x, y) { console.log(`debug: ${x}: #<scm>`); },
  432. }
  433. }
  434. static async fetch_and_instantiate(path, imported_abi) {
  435. await load_wtf8_helper_module();
  436. let io = {
  437. write_stdout(str) { mod.#io_handler.write_stdout(str); },
  438. write_stderr(str) { mod.#io_handler.write_stderr(str); },
  439. read_stdin() { return mod.#io_handler.read_stdin(); },
  440. }
  441. let debug = {
  442. debug_str(x) { mod.#debug_handler.debug_str(x); },
  443. debug_str_i32(x, y) { mod.#debug_handler.debug_str_i32(x, y); },
  444. debug_str_scm(x, y) { mod.#debug_handler.debug_str_scm(x, y); },
  445. }
  446. let imports = { rt: SchemeModule.#rt, debug, io, abi: imported_abi }
  447. let { module, instance } = await instantiate_streaming(path, imports);
  448. let mod = new SchemeModule(instance);
  449. return mod;
  450. }
  451. set_io_handler(h) { this.#io_handler = h; }
  452. set_debug_handler(h) { this.#debug_handler = h; }
  453. all_exports() { return this.#instance.exports; }
  454. exported_abi() {
  455. let abi = {}
  456. for (let [k, v] of Object.entries(this.all_exports())) {
  457. if (k.startsWith("$"))
  458. abi[k] = v;
  459. }
  460. return abi;
  461. }
  462. exports() {
  463. let ret = {}
  464. for (let [k, v] of Object.entries(this.all_exports())) {
  465. if (!k.startsWith("$"))
  466. ret[k] = v;
  467. }
  468. return ret;
  469. }
  470. get_export(name) {
  471. if (name in this.all_exports())
  472. return this.all_exports()[name];
  473. throw new Error(`unknown export: ${name}`)
  474. }
  475. async reflect() {
  476. return await Scheme.reflect(this.exported_abi());
  477. }
  478. }
  479. function repr(obj) {
  480. if (obj instanceof HeapObject)
  481. return obj.repr();
  482. if (typeof obj === 'boolean')
  483. return obj ? '#t' : '#f';
  484. if (typeof obj === 'number')
  485. return flonum_to_string(obj);
  486. if (typeof obj === 'string')
  487. // FIXME: Improve to match Scheme.
  488. return '"' + obj.replace(/(["\\])/g, '\\$1') + '"';
  489. return obj + '';
  490. }