reflect.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  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. return String.fromCodePoint(...codepoints);
  328. }
  329. function string_to_wtf8(str) {
  330. let { string_builder, builder_push_codepoint, finish_builder } =
  331. wtf8_helper.exports;
  332. let builder = string_builder()
  333. for (let cp of str)
  334. builder_push_codepoint(builder, cp.codePointAt(0));
  335. return finish_builder(builder);
  336. }
  337. async function load_wtf8_helper_module() {
  338. if (wtf8_helper) return;
  339. let { module, instance } = await instantiate_streaming("js-runtime/wtf8.wasm");
  340. wtf8_helper = instance;
  341. }
  342. class SchemeModule {
  343. #instance;
  344. #io_handler;
  345. #debug_handler;
  346. #ffi_handler;
  347. static #rt = {
  348. bignum_from_string(str) { return BigInt(str); },
  349. bignum_from_i32(n) { return BigInt(n); },
  350. bignum_from_i64(n) { return n; },
  351. bignum_from_u64(n) { return n < 0n ? 0xffff_ffff_ffff_ffffn + (n + 1n) : n; },
  352. bignum_is_i64(n) {
  353. return -0x8000_0000_0000_0000n <= n && n <= 0x7FFF_FFFF_FFFF_FFFFn;
  354. },
  355. bignum_is_u64(n) {
  356. return 0n <= n && n <= 0xFFFF_FFFF_FFFF_FFFFn;
  357. },
  358. // This truncates; see https://tc39.es/ecma262/#sec-tobigint64.
  359. bignum_get_i64(n) { return n; },
  360. bignum_add(a, b) { return BigInt(a) + BigInt(b) },
  361. bignum_sub(a, b) { return BigInt(a) - BigInt(b) },
  362. bignum_mul(a, b) { return BigInt(a) * BigInt(b) },
  363. bignum_lsh(a, b) { return BigInt(a) << BigInt(b) },
  364. bignum_rsh(a, b) { return BigInt(a) >> BigInt(b) },
  365. bignum_quo(a, b) { return BigInt(a) / BigInt(b) },
  366. bignum_rem(a, b) { return BigInt(a) % BigInt(b) },
  367. bignum_mod(a, b) {
  368. let r = BigInt(a) % BigInt(b);
  369. if ((b > 0n && r < 0n) || (b < 0n && r > 0n)) {
  370. return b + r;
  371. } else {
  372. return r;
  373. }
  374. },
  375. bignum_gcd(a, b) {
  376. a = BigInt(a);
  377. b = BigInt(b);
  378. if (a < 0n) { a = -a; }
  379. if (b < 0n) { b = -b; }
  380. if (a == 0n) { return b; }
  381. if (b == 0n) { return a; }
  382. let r;
  383. while (b != 0n) {
  384. r = a % b;
  385. a = b;
  386. b = r;
  387. }
  388. return a;
  389. },
  390. bignum_logand(a, b) { return BigInt(a) & BigInt(b); },
  391. bignum_logior(a, b) { return BigInt(a) | BigInt(b); },
  392. bignum_logxor(a, b) { return BigInt(a) ^ BigInt(b); },
  393. bignum_logsub(a, b) { return BigInt(a) & (~ BigInt(b)); },
  394. bignum_lt(a, b) { return a < b; },
  395. bignum_le(a, b) { return a <= b; },
  396. bignum_eq(a, b) { return a == b; },
  397. bignum_to_f64(n) { return Number(n); },
  398. f64_is_nan(n) { return Number.isNaN(n); },
  399. f64_is_infinite(n) { return !Number.isFinite(n); },
  400. flonum_to_string,
  401. string_upcase: Function.call.bind(String.prototype.toUpperCase),
  402. string_downcase: Function.call.bind(String.prototype.toLowerCase),
  403. make_weak_map() { return new WeakMap; },
  404. weak_map_get(map, k, fail) {
  405. const val = map.get(k);
  406. return val === undefined ? fail: val;
  407. },
  408. weak_map_set(map, k, v) { return map.set(k, v); },
  409. weak_map_delete(map, k) { return map.delete(k); },
  410. fsqrt: Math.sqrt,
  411. fsin: Math.sin,
  412. fcos: Math.cos,
  413. ftan: Math.tan,
  414. fasin: Math.asin,
  415. facos: Math.acos,
  416. fatan: Math.atan,
  417. fatan2: Math.atan2,
  418. flog: Math.log,
  419. fexp: Math.exp,
  420. jiffies_per_second() { return 1000000; },
  421. current_jiffy() { return performance.now() * 1000; },
  422. current_second() { return Date.now() / 1000; },
  423. // Wrap in functions to allow for lazy loading of the wtf8
  424. // module.
  425. wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); },
  426. string_to_wtf8(str) { return string_to_wtf8(str); },
  427. die(tag, data) { throw new SchemeTrapError(tag, data); }
  428. };
  429. constructor(instance) {
  430. this.#instance = instance;
  431. let read_stdin = () => '';
  432. if (typeof printErr === 'function') {
  433. // On the console, try to use 'write' (v8) or 'putstr' (sm),
  434. // as these don't add an extraneous newline. Unfortunately
  435. // JSC doesn't have a printer that doesn't add a newline.
  436. let write_no_newline =
  437. typeof write === 'function' ? write
  438. : typeof putstr === 'function' ? putstr : print;
  439. this.#io_handler = {
  440. write_stdout: write_no_newline,
  441. write_stderr: printErr,
  442. read_stdin
  443. };
  444. } else {
  445. this.#io_handler = {
  446. write_stdout: console.log,
  447. write_stderr: console.error,
  448. read_stdin
  449. }
  450. }
  451. this.#debug_handler = {
  452. debug_str(x) { console.log(`debug: ${x}`); },
  453. debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); },
  454. debug_str_scm(x, y) { console.log(`debug: ${x}: #<scm>`); },
  455. }
  456. }
  457. static async fetch_and_instantiate(path, imported_abi, user_imports = {}) {
  458. await load_wtf8_helper_module();
  459. let io = {
  460. write_stdout(str) { mod.#io_handler.write_stdout(str); },
  461. write_stderr(str) { mod.#io_handler.write_stderr(str); },
  462. read_stdin() { return mod.#io_handler.read_stdin(); },
  463. }
  464. let debug = {
  465. debug_str(x) { mod.#debug_handler.debug_str(x); },
  466. debug_str_i32(x, y) { mod.#debug_handler.debug_str_i32(x, y); },
  467. debug_str_scm(x, y) { mod.#debug_handler.debug_str_scm(x, y); },
  468. }
  469. let ffi = {
  470. procedure_to_extern(proc) {
  471. return mod.#ffi_handler.procedure_to_extern(proc);
  472. }
  473. };
  474. let imports = {
  475. rt: SchemeModule.#rt,
  476. abi: imported_abi,
  477. debug, io, ffi, ...user_imports
  478. };
  479. let { module, instance } = await instantiate_streaming(path, imports);
  480. let mod = new SchemeModule(instance);
  481. return mod;
  482. }
  483. set_io_handler(h) { this.#io_handler = h; }
  484. set_debug_handler(h) { this.#debug_handler = h; }
  485. set_ffi_handler(h) { this.#ffi_handler = h; }
  486. all_exports() { return this.#instance.exports; }
  487. exported_abi() {
  488. let abi = {}
  489. for (let [k, v] of Object.entries(this.all_exports())) {
  490. if (k.startsWith("$"))
  491. abi[k] = v;
  492. }
  493. return abi;
  494. }
  495. exports() {
  496. let ret = {}
  497. for (let [k, v] of Object.entries(this.all_exports())) {
  498. if (!k.startsWith("$"))
  499. ret[k] = v;
  500. }
  501. return ret;
  502. }
  503. get_export(name) {
  504. if (name in this.all_exports())
  505. return this.all_exports()[name];
  506. throw new Error(`unknown export: ${name}`)
  507. }
  508. async reflect() {
  509. return await Scheme.reflect(this.exported_abi());
  510. }
  511. }
  512. function repr(obj) {
  513. if (obj instanceof HeapObject)
  514. return obj.repr();
  515. if (typeof obj === 'boolean')
  516. return obj ? '#t' : '#f';
  517. if (typeof obj === 'number')
  518. return flonum_to_string(obj);
  519. if (typeof obj === 'string')
  520. return string_repr(obj);
  521. return obj + '';
  522. }