test-lib.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. let readBinaryFile = (()=> {
  2. if (typeof read !== 'undefined')
  3. return f => read(f, 'binary');
  4. if (typeof readFile !== 'undefined')
  5. return f => readFile(f);
  6. let fs = require('fs');
  7. return f => fs.readFileSync(f);
  8. })();
  9. let Wtf8 = (() => {
  10. let bytes = readBinaryFile('js-runtime/wtf8.wasm');
  11. return new WebAssembly.Instance(new WebAssembly.Module(bytes));
  12. })();
  13. function wtf8_to_string(wtf8) {
  14. let { as_iter, iter_next } = Wtf8.exports;
  15. let codepoints = [];
  16. let iter = as_iter(wtf8);
  17. for (let cp = iter_next(iter); cp != -1; cp = iter_next(iter))
  18. codepoints.push(cp);
  19. return String.fromCodePoint(...codepoints);
  20. }
  21. function string_to_wtf8(str) {
  22. let { make_builder, builder_push_codepoint, finish_builder } = Wtf8.exports;
  23. let builder = make_builder()
  24. for (let cp of str)
  25. builder_push_codepoint(builder, cp.codePointAt(0));
  26. return finish_builder(builder);
  27. }
  28. class Char {
  29. constructor(codepoint) {
  30. this.codepoint = codepoint;
  31. }
  32. toString() {
  33. return `#\\x${this.codepoint.toString(16)}`;
  34. }
  35. }
  36. class Eof { toString() { return "#eof"; } }
  37. class Nil { toString() { return "#nil"; } }
  38. class Null { toString() { return "()"; } }
  39. class Unspecified { toString() { return "#<unspecified>"; } }
  40. class Complex {
  41. constructor(real, imag) {
  42. this.real = real;
  43. this.imag = imag;
  44. }
  45. toString() {
  46. return `${this.real}+${this.imag}i`;
  47. }
  48. }
  49. class Fraction {
  50. constructor(num, denom) {
  51. this.num = num;
  52. this.denom = denom;
  53. }
  54. toString() {
  55. return `${this.num}/${this.denom}`;
  56. }
  57. }
  58. class HeapObject {
  59. constructor(obj) { this.obj = obj; }
  60. }
  61. class Pair extends HeapObject { toString() { return "#<pair>"; } }
  62. class MutablePair extends HeapObject { toString() { return "#<mutable-pair>"; } }
  63. class Vector extends HeapObject { toString() { return "#<vector>"; } }
  64. class MutableVector extends HeapObject { toString() { return "#<mutable-vector>"; } }
  65. class Bytevector extends HeapObject { toString() { return "#<bytevector>"; } }
  66. class MutableBytevector extends HeapObject { toString() { return "#<mutable-bytevector>"; } }
  67. class Bitvector extends HeapObject { toString() { return "#<bitvector>"; } }
  68. class MutableBitvector extends HeapObject { toString() { return "#<mutable-bitvector>"; } }
  69. class MutableString extends HeapObject { toString() { return "#<mutable-string>"; } }
  70. class Procedure extends HeapObject { toString() { return "#<procedure>"; } }
  71. class Sym extends HeapObject { toString() { return "#<symbol>"; } }
  72. class Keyword extends HeapObject { toString() { return "#<keyword>"; } }
  73. class Variable extends HeapObject { toString() { return "#<variable>"; } }
  74. class AtomicBox extends HeapObject { toString() { return "#<atomic-box>"; } }
  75. class HashTable extends HeapObject { toString() { return "#<hash-table>"; } }
  76. class WeakTable extends HeapObject { toString() { return "#<weak-table>"; } }
  77. class Fluid extends HeapObject { toString() { return "#<fluid>"; } }
  78. class DynamicState extends HeapObject { toString() { return "#<dynamic-state>"; } }
  79. class Syntax extends HeapObject { toString() { return "#<syntax>"; } }
  80. class Port extends HeapObject { toString() { return "#<port>"; } }
  81. class Struct extends HeapObject { toString() { return "#<struct>"; } }
  82. class SCM {
  83. #argv = [];
  84. constructor(mod) {
  85. let argv = this.#argv;
  86. let rt = {
  87. prepare_return_values(n) { argv.length = n; },
  88. set_return_value(i, x) { argv[i] = x; },
  89. get_argument(i) { return argv[i]; },
  90. bignum_from_i64(n) { return n; },
  91. bignum_from_u64(n) { return n < 0n ? 0xffff_ffff_ffff_ffffn + (n + 1n) : n; },
  92. bignum_is_i64(n) {
  93. return -0x8000_0000_0000_0000n <= n && n <= 0x7FFF_FFFF_FFFF_FFFFn;
  94. },
  95. bignum_is_u64(n) {
  96. return 0n <= n && n <= 0xFFFF_FFFF_FFFF_FFFFn;
  97. },
  98. // This truncates; see https://tc39.es/ecma262/#sec-tobigint64.
  99. bignum_get_i64(n) { return n; },
  100. wtf8_to_string,
  101. string_to_wtf8,
  102. make_weak_map() { return new WeakMap; },
  103. weak_map_get(map, k) { return map.get(k); },
  104. weak_map_set(map, k, v) { return map.set(k, v); },
  105. weak_map_delete(map, k) { return map.delete(k); }
  106. };
  107. this.instance = new WebAssembly.Instance(mod, { rt });
  108. }
  109. #to_scm(js) {
  110. let api = this.instance.exports;
  111. if (typeof(js) == 'number') {
  112. return api.scm_from_f64(js);
  113. } else if (typeof(js) == 'bigint') {
  114. return api.scm_from_integer(js);
  115. } else if (typeof(js) == 'boolean') {
  116. return js ? api.scm_true() : api.scm_false();
  117. } else if (typeof(js) == 'string') {
  118. return api.scm_from_string(js);
  119. } else if (typeof(js) == 'object') {
  120. if (js instanceof Eof) return api.scm_eof();
  121. if (js instanceof Nil) return api.scm_nil();
  122. if (js instanceof Null) return api.scm_null();
  123. if (js instanceof Unspecified) return api.scm_unspecified();
  124. if (js instanceof Char) return api.scm_from_char(js.codepoint);
  125. if (js instanceof HeapObject) return js.obj;
  126. if (js instanceof Fraction)
  127. return api.scm_from_fraction(this.#to_scm(js.num),
  128. this.#to_scm(js.denom));
  129. if (js instanceof Complex)
  130. return api.scm_from_complex(js.real, js.imag);
  131. throw new Error(`unhandled; ${typeof(js)}`);
  132. } else {
  133. throw new Error(`unexpected; ${typeof(js)}`);
  134. }
  135. }
  136. #to_js(scm) {
  137. let api = this.instance.exports;
  138. let descr = api.describe(scm);
  139. let handlers = {
  140. fixnum: () => BigInt(api.fixnum_value(scm)),
  141. char: () => new Char(api.char_value(scm)),
  142. true: () => true,
  143. false: () => false,
  144. eof: () => new Eof,
  145. nil: () => new Nil,
  146. null: () => new Null,
  147. unspecified: () => new Unspecified,
  148. flonum: () => api.flonum_value(scm),
  149. bignum: () => api.bignum_value(scm),
  150. complex: () => new Complex(api.complex_real(scm),
  151. api.complex_imag(scm)),
  152. fraction: () => new Fraction(this.#to_js(api.fraction_num(scm)),
  153. this.#to_js(api.fraction_denom(scm))),
  154. pair: () => new Pair(scm),
  155. 'mutable-pair': () => new MutablePair(scm),
  156. vector: () => new Vector(scm),
  157. 'mutable-vector': () => new MutableVector(scm),
  158. bytevector: () => new Bytevector(scm),
  159. 'mutable-bytevector': () => new MutableBytevector(scm),
  160. bitvector: () => new Bitvector(scm),
  161. 'mutable-bitvector': () => new MutableBitvector(scm),
  162. string: () => api.string_value(scm),
  163. 'mutable-string': () => new MutableString(scm),
  164. procedure: () => new Procedure(scm),
  165. symbol: () => new Sym(scm),
  166. keyword: () => new Keyword(scm),
  167. variable: () => new Variable(scm),
  168. 'atomic-box': () => new AtomicBox(scm),
  169. 'hash-table': () => new HashTable(scm),
  170. 'weak-table': () => new WeakTable(scm),
  171. fluid: () => new Fluid(scm),
  172. 'dynamic-state': () => new DynamicState(scm),
  173. syntax: () => new Syntax(scm),
  174. port: () => new Port(scm),
  175. struct: () => new Struct(scm),
  176. };
  177. let handler = handlers[descr];
  178. return handler ? handler() : scm;
  179. }
  180. call_named(func_name, ...args) {
  181. let api = this.instance.exports;
  182. this.#argv.length = args.length;
  183. for (let [idx, arg] of args.entries())
  184. this.#argv[idx] = this.#to_scm(arg);
  185. let f = api[func_name];
  186. if (!f) throw new Error(`no such function: ${func_name}`);
  187. f(args.length);
  188. let values = [];
  189. for (let idx = 0; idx < this.#argv.length; idx++)
  190. values.push(this.#to_js(this.#argv[idx]));
  191. this.#argv.length = 0;
  192. return values;
  193. }
  194. }
  195. function check_true(actual, what) {
  196. print(`checking ${what} is true`);
  197. if (!actual)
  198. throw new Error(`unexpected ${what}: ${actual}`);
  199. }
  200. function check_same(expected, actual, what) {
  201. print(`checking expected ${what}: ${expected}`);
  202. if (expected !== actual)
  203. throw new Error(`unexpected ${what}: ${actual}`);
  204. }
  205. function load_wasm(file) {
  206. let bytes = readBinaryFile(file);
  207. let mod = new WebAssembly.Module(bytes);
  208. return new SCM(mod);
  209. }