@live-codes/ocaml 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +23 -16
  2. package/assets/compiler/compile_stdout.browser.js +18185 -18181
  3. package/assets/compiler/libs/base64.cma +0 -0
  4. package/assets/compiler/libs/base64.cmi +0 -0
  5. package/assets/compiler/libs/big_int_Z.cmi +0 -0
  6. package/assets/compiler/libs/digestif.cmi +0 -0
  7. package/assets/compiler/libs/dllcamlruntime_eventsbyt-x86_64-pc-linux-gnu-a100.so +0 -0
  8. package/assets/compiler/libs/dllcamlstrbyt-x86_64-pc-linux-gnu-a100.so +0 -0
  9. package/assets/compiler/libs/dlldigestif_c_stubs.so +0 -0
  10. package/assets/compiler/libs/dllgraphics_js_stubs.so +0 -0
  11. package/assets/compiler/libs/dllgraphics_stubs.so +0 -0
  12. package/assets/compiler/libs/dlljs_of_ocaml_compiler_dynlink_stubs.so +0 -0
  13. package/assets/compiler/libs/dlljs_of_ocaml_stubs.so +0 -0
  14. package/assets/compiler/libs/dlljsoo_runtime_stubs.so +0 -0
  15. package/assets/compiler/libs/dlllwt_unix_stubs.so +0 -0
  16. package/assets/compiler/libs/dllthreads-x86_64-pc-linux-gnu-a100.so +0 -0
  17. package/assets/compiler/libs/dllunixbyt-x86_64-pc-linux-gnu-a100.so +0 -0
  18. package/assets/compiler/libs/dllzarith.so +0 -0
  19. package/assets/compiler/libs/fmt.cma +0 -0
  20. package/assets/compiler/libs/fmt.cmi +0 -0
  21. package/assets/compiler/libs/graphics.cma +0 -0
  22. package/assets/compiler/libs/graphics.cmi +0 -0
  23. package/assets/compiler/libs/graphicsX11.cmi +0 -0
  24. package/assets/compiler/libs/index.txt +105 -0
  25. package/assets/compiler/libs/js_of_ocaml.cma +0 -0
  26. package/assets/compiler/libs/js_of_ocaml.cmi +0 -0
  27. package/assets/compiler/libs/js_of_ocaml__.cmi +0 -0
  28. package/assets/compiler/libs/js_of_ocaml__Abort.cmi +0 -0
  29. package/assets/compiler/libs/js_of_ocaml__CSS.cmi +0 -0
  30. package/assets/compiler/libs/js_of_ocaml__Console.cmi +0 -0
  31. package/assets/compiler/libs/js_of_ocaml__Dom.cmi +0 -0
  32. package/assets/compiler/libs/js_of_ocaml__Dom_events.cmi +0 -0
  33. package/assets/compiler/libs/js_of_ocaml__Dom_html.cmi +0 -0
  34. package/assets/compiler/libs/js_of_ocaml__Dom_svg.cmi +0 -0
  35. package/assets/compiler/libs/js_of_ocaml__Effect_js.cmi +0 -0
  36. package/assets/compiler/libs/js_of_ocaml__EventSource.cmi +0 -0
  37. package/assets/compiler/libs/js_of_ocaml__Fetch.cmi +0 -0
  38. package/assets/compiler/libs/js_of_ocaml__File.cmi +0 -0
  39. package/assets/compiler/libs/js_of_ocaml__Form.cmi +0 -0
  40. package/assets/compiler/libs/js_of_ocaml__Geolocation.cmi +0 -0
  41. package/assets/compiler/libs/js_of_ocaml__Import.cmi +0 -0
  42. package/assets/compiler/libs/js_of_ocaml__IntersectionObserver.cmi +0 -0
  43. package/assets/compiler/libs/js_of_ocaml__Intl.cmi +0 -0
  44. package/assets/compiler/libs/js_of_ocaml__Js.cmi +0 -0
  45. package/assets/compiler/libs/js_of_ocaml__Json.cmi +0 -0
  46. package/assets/compiler/libs/js_of_ocaml__Jstable.cmi +0 -0
  47. package/assets/compiler/libs/js_of_ocaml__Lib_version.cmi +0 -0
  48. package/assets/compiler/libs/js_of_ocaml__MutationObserver.cmi +0 -0
  49. package/assets/compiler/libs/js_of_ocaml__Performance.cmi +0 -0
  50. package/assets/compiler/libs/js_of_ocaml__PerformanceObserver.cmi +0 -0
  51. package/assets/compiler/libs/js_of_ocaml__Promise.cmi +0 -0
  52. package/assets/compiler/libs/js_of_ocaml__Regexp.cmi +0 -0
  53. package/assets/compiler/libs/js_of_ocaml__ResizeObserver.cmi +0 -0
  54. package/assets/compiler/libs/js_of_ocaml__Sys_js.cmi +0 -0
  55. package/assets/compiler/libs/js_of_ocaml__Typed_array.cmi +0 -0
  56. package/assets/compiler/libs/js_of_ocaml__Url.cmi +0 -0
  57. package/assets/compiler/libs/js_of_ocaml__WebGL.cmi +0 -0
  58. package/assets/compiler/libs/js_of_ocaml__WebSockets.cmi +0 -0
  59. package/assets/compiler/libs/js_of_ocaml__Worker.cmi +0 -0
  60. package/assets/compiler/libs/js_of_ocaml__XmlHttpRequest.cmi +0 -0
  61. package/assets/compiler/libs/js_of_ocaml_lwt.cma +0 -0
  62. package/assets/compiler/libs/js_of_ocaml_lwt.cmi +0 -0
  63. package/assets/compiler/libs/js_of_ocaml_lwt__.cmi +0 -0
  64. package/assets/compiler/libs/js_of_ocaml_lwt__Import.cmi +0 -0
  65. package/assets/compiler/libs/js_of_ocaml_lwt__Lwt_file.cmi +0 -0
  66. package/assets/compiler/libs/js_of_ocaml_lwt__Lwt_js.cmi +0 -0
  67. package/assets/compiler/libs/js_of_ocaml_lwt__Lwt_js_events.cmi +0 -0
  68. package/assets/compiler/libs/js_of_ocaml_lwt__Lwt_jsonp.cmi +0 -0
  69. package/assets/compiler/libs/js_of_ocaml_lwt__Lwt_promise.cmi +0 -0
  70. package/assets/compiler/libs/js_of_ocaml_lwt__Lwt_xmlHttpRequest.cmi +0 -0
  71. package/assets/compiler/libs/jsonm.cma +0 -0
  72. package/assets/compiler/libs/jsonm.cmi +0 -0
  73. package/assets/compiler/libs/jsoo_runtime.cma +0 -0
  74. package/assets/compiler/libs/jsoo_runtime.cmi +0 -0
  75. package/assets/compiler/libs/jsoo_runtime__.cmi +0 -0
  76. package/assets/compiler/libs/jsoo_runtime__Runtime_version.cmi +0 -0
  77. package/assets/compiler/libs/logs.cma +0 -0
  78. package/assets/compiler/libs/logs.cmi +0 -0
  79. package/assets/compiler/libs/lwt.cma +0 -0
  80. package/assets/compiler/libs/lwt.cmi +0 -0
  81. package/assets/compiler/libs/lwt_condition.cmi +0 -0
  82. package/assets/compiler/libs/lwt_list.cmi +0 -0
  83. package/assets/compiler/libs/lwt_mutex.cmi +0 -0
  84. package/assets/compiler/libs/lwt_mvar.cmi +0 -0
  85. package/assets/compiler/libs/lwt_pool.cmi +0 -0
  86. package/assets/compiler/libs/lwt_pqueue.cmi +0 -0
  87. package/assets/compiler/libs/lwt_result.cmi +0 -0
  88. package/assets/compiler/libs/lwt_rte.cmi +0 -0
  89. package/assets/compiler/libs/lwt_seq.cmi +0 -0
  90. package/assets/compiler/libs/lwt_sequence.cmi +0 -0
  91. package/assets/compiler/libs/lwt_stream.cmi +0 -0
  92. package/assets/compiler/libs/lwt_switch.cmi +0 -0
  93. package/assets/compiler/libs/manifest.txt +132 -0
  94. package/assets/compiler/libs/q.cmi +0 -0
  95. package/assets/compiler/libs/re.cma +0 -0
  96. package/assets/compiler/libs/re.cmi +0 -0
  97. package/assets/compiler/libs/re__.cmi +0 -0
  98. package/assets/compiler/libs/re__Ast.cmi +0 -0
  99. package/assets/compiler/libs/re__Automata.cmi +0 -0
  100. package/assets/compiler/libs/re__Bit_vector.cmi +0 -0
  101. package/assets/compiler/libs/re__Category.cmi +0 -0
  102. package/assets/compiler/libs/re__Color_map.cmi +0 -0
  103. package/assets/compiler/libs/re__Compile.cmi +0 -0
  104. package/assets/compiler/libs/re__Core.cmi +0 -0
  105. package/assets/compiler/libs/re__Cset.cmi +0 -0
  106. package/assets/compiler/libs/re__Dense_map.cmi +0 -0
  107. package/assets/compiler/libs/re__Dyn.cmi +0 -0
  108. package/assets/compiler/libs/re__Emacs.cmi +0 -0
  109. package/assets/compiler/libs/re__Fmt.cmi +0 -0
  110. package/assets/compiler/libs/re__Glob.cmi +0 -0
  111. package/assets/compiler/libs/re__Group.cmi +0 -0
  112. package/assets/compiler/libs/re__Hash_set.cmi +0 -0
  113. package/assets/compiler/libs/re__Import.cmi +0 -0
  114. package/assets/compiler/libs/re__Mark_infos.cmi +0 -0
  115. package/assets/compiler/libs/re__Parse_buffer.cmi +0 -0
  116. package/assets/compiler/libs/re__Pcre.cmi +0 -0
  117. package/assets/compiler/libs/re__Perl.cmi +0 -0
  118. package/assets/compiler/libs/re__Pmark.cmi +0 -0
  119. package/assets/compiler/libs/re__Posix.cmi +0 -0
  120. package/assets/compiler/libs/re__Posix_class.cmi +0 -0
  121. package/assets/compiler/libs/re__Replace.cmi +0 -0
  122. package/assets/compiler/libs/re__Search.cmi +0 -0
  123. package/assets/compiler/libs/re__Slice.cmi +0 -0
  124. package/assets/compiler/libs/re__Str.cmi +0 -0
  125. package/assets/compiler/libs/re__View.cmi +0 -0
  126. package/assets/compiler/libs/str.cma +0 -0
  127. package/assets/compiler/libs/str.cmi +0 -0
  128. package/assets/compiler/libs/stubs.txt +12 -0
  129. package/assets/compiler/libs/unix.cma +0 -0
  130. package/assets/compiler/libs/unix.cmi +0 -0
  131. package/assets/compiler/libs/unixLabels.cmi +0 -0
  132. package/assets/compiler/libs/uutf.cma +0 -0
  133. package/assets/compiler/libs/uutf.cmi +0 -0
  134. package/assets/compiler/libs/xmlm.cma +0 -0
  135. package/assets/compiler/libs/xmlm.cmi +0 -0
  136. package/assets/compiler/libs/z.cmi +0 -0
  137. package/assets/compiler/libs/zarith.cma +0 -0
  138. package/assets/compiler/libs/zarith_stubs_js.runtime.js +1246 -0
  139. package/assets/compiler/worker.js +38 -2
  140. package/assets/interpreter/libs/base64.cma +0 -0
  141. package/assets/interpreter/libs/base64.cmi +0 -0
  142. package/assets/interpreter/libs/big_int_Z.cmi +0 -0
  143. package/assets/interpreter/libs/fmt.cma +0 -0
  144. package/assets/interpreter/libs/fmt.cmi +0 -0
  145. package/assets/interpreter/libs/graphics.cma +0 -0
  146. package/assets/interpreter/libs/graphics.cmi +0 -0
  147. package/assets/interpreter/libs/graphicsX11.cmi +0 -0
  148. package/assets/interpreter/libs/js_of_ocaml.cma +0 -0
  149. package/assets/interpreter/libs/js_of_ocaml.cmi +0 -0
  150. package/assets/interpreter/libs/js_of_ocaml__.cmi +0 -0
  151. package/assets/interpreter/libs/js_of_ocaml__Abort.cmi +0 -0
  152. package/assets/interpreter/libs/js_of_ocaml__CSS.cmi +0 -0
  153. package/assets/interpreter/libs/js_of_ocaml__Console.cmi +0 -0
  154. package/assets/interpreter/libs/js_of_ocaml__Dom.cmi +0 -0
  155. package/assets/interpreter/libs/js_of_ocaml__Dom_events.cmi +0 -0
  156. package/assets/interpreter/libs/js_of_ocaml__Dom_html.cmi +0 -0
  157. package/assets/interpreter/libs/js_of_ocaml__Dom_svg.cmi +0 -0
  158. package/assets/interpreter/libs/js_of_ocaml__Effect_js.cmi +0 -0
  159. package/assets/interpreter/libs/js_of_ocaml__EventSource.cmi +0 -0
  160. package/assets/interpreter/libs/js_of_ocaml__Fetch.cmi +0 -0
  161. package/assets/interpreter/libs/js_of_ocaml__File.cmi +0 -0
  162. package/assets/interpreter/libs/js_of_ocaml__Form.cmi +0 -0
  163. package/assets/interpreter/libs/js_of_ocaml__Geolocation.cmi +0 -0
  164. package/assets/interpreter/libs/js_of_ocaml__Import.cmi +0 -0
  165. package/assets/interpreter/libs/js_of_ocaml__IntersectionObserver.cmi +0 -0
  166. package/assets/interpreter/libs/js_of_ocaml__Intl.cmi +0 -0
  167. package/assets/interpreter/libs/js_of_ocaml__Js.cmi +0 -0
  168. package/assets/interpreter/libs/js_of_ocaml__Json.cmi +0 -0
  169. package/assets/interpreter/libs/js_of_ocaml__Jstable.cmi +0 -0
  170. package/assets/interpreter/libs/js_of_ocaml__Lib_version.cmi +0 -0
  171. package/assets/interpreter/libs/js_of_ocaml__MutationObserver.cmi +0 -0
  172. package/assets/interpreter/libs/js_of_ocaml__Performance.cmi +0 -0
  173. package/assets/interpreter/libs/js_of_ocaml__PerformanceObserver.cmi +0 -0
  174. package/assets/interpreter/libs/js_of_ocaml__Promise.cmi +0 -0
  175. package/assets/interpreter/libs/js_of_ocaml__Regexp.cmi +0 -0
  176. package/assets/interpreter/libs/js_of_ocaml__ResizeObserver.cmi +0 -0
  177. package/assets/interpreter/libs/js_of_ocaml__Sys_js.cmi +0 -0
  178. package/assets/interpreter/libs/js_of_ocaml__Typed_array.cmi +0 -0
  179. package/assets/interpreter/libs/js_of_ocaml__Url.cmi +0 -0
  180. package/assets/interpreter/libs/js_of_ocaml__WebGL.cmi +0 -0
  181. package/assets/interpreter/libs/js_of_ocaml__WebSockets.cmi +0 -0
  182. package/assets/interpreter/libs/js_of_ocaml__Worker.cmi +0 -0
  183. package/assets/interpreter/libs/js_of_ocaml__XmlHttpRequest.cmi +0 -0
  184. package/assets/interpreter/libs/js_of_ocaml_lwt.cma +0 -0
  185. package/assets/interpreter/libs/js_of_ocaml_lwt.cmi +0 -0
  186. package/assets/interpreter/libs/js_of_ocaml_lwt__.cmi +0 -0
  187. package/assets/interpreter/libs/js_of_ocaml_lwt__Import.cmi +0 -0
  188. package/assets/interpreter/libs/js_of_ocaml_lwt__Lwt_file.cmi +0 -0
  189. package/assets/interpreter/libs/js_of_ocaml_lwt__Lwt_js.cmi +0 -0
  190. package/assets/interpreter/libs/js_of_ocaml_lwt__Lwt_js_events.cmi +0 -0
  191. package/assets/interpreter/libs/js_of_ocaml_lwt__Lwt_jsonp.cmi +0 -0
  192. package/assets/interpreter/libs/js_of_ocaml_lwt__Lwt_promise.cmi +0 -0
  193. package/assets/interpreter/libs/js_of_ocaml_lwt__Lwt_xmlHttpRequest.cmi +0 -0
  194. package/assets/interpreter/libs/jsonm.cma +0 -0
  195. package/assets/interpreter/libs/jsonm.cmi +0 -0
  196. package/assets/interpreter/libs/jsoo_runtime.cma +0 -0
  197. package/assets/interpreter/libs/logs.cma +0 -0
  198. package/assets/interpreter/libs/logs.cmi +0 -0
  199. package/assets/interpreter/libs/lwt.cma +0 -0
  200. package/assets/interpreter/libs/lwt.cmi +0 -0
  201. package/assets/interpreter/libs/lwt_condition.cmi +0 -0
  202. package/assets/interpreter/libs/lwt_list.cmi +0 -0
  203. package/assets/interpreter/libs/lwt_mutex.cmi +0 -0
  204. package/assets/interpreter/libs/lwt_mvar.cmi +0 -0
  205. package/assets/interpreter/libs/lwt_pool.cmi +0 -0
  206. package/assets/interpreter/libs/lwt_pqueue.cmi +0 -0
  207. package/assets/interpreter/libs/lwt_result.cmi +0 -0
  208. package/assets/interpreter/libs/lwt_rte.cmi +0 -0
  209. package/assets/interpreter/libs/lwt_seq.cmi +0 -0
  210. package/assets/interpreter/libs/lwt_sequence.cmi +0 -0
  211. package/assets/interpreter/libs/lwt_stream.cmi +0 -0
  212. package/assets/interpreter/libs/lwt_switch.cmi +0 -0
  213. package/assets/interpreter/libs/manifest.txt +16 -0
  214. package/assets/interpreter/libs/q.cmi +0 -0
  215. package/assets/interpreter/libs/re.cma +0 -0
  216. package/assets/interpreter/libs/re.cmi +0 -0
  217. package/assets/interpreter/libs/re__.cmi +0 -0
  218. package/assets/interpreter/libs/re__Ast.cmi +0 -0
  219. package/assets/interpreter/libs/re__Automata.cmi +0 -0
  220. package/assets/interpreter/libs/re__Bit_vector.cmi +0 -0
  221. package/assets/interpreter/libs/re__Category.cmi +0 -0
  222. package/assets/interpreter/libs/re__Color_map.cmi +0 -0
  223. package/assets/interpreter/libs/re__Compile.cmi +0 -0
  224. package/assets/interpreter/libs/re__Core.cmi +0 -0
  225. package/assets/interpreter/libs/re__Cset.cmi +0 -0
  226. package/assets/interpreter/libs/re__Dense_map.cmi +0 -0
  227. package/assets/interpreter/libs/re__Dyn.cmi +0 -0
  228. package/assets/interpreter/libs/re__Emacs.cmi +0 -0
  229. package/assets/interpreter/libs/re__Fmt.cmi +0 -0
  230. package/assets/interpreter/libs/re__Glob.cmi +0 -0
  231. package/assets/interpreter/libs/re__Group.cmi +0 -0
  232. package/assets/interpreter/libs/re__Hash_set.cmi +0 -0
  233. package/assets/interpreter/libs/re__Import.cmi +0 -0
  234. package/assets/interpreter/libs/re__Mark_infos.cmi +0 -0
  235. package/assets/interpreter/libs/re__Parse_buffer.cmi +0 -0
  236. package/assets/interpreter/libs/re__Pcre.cmi +0 -0
  237. package/assets/interpreter/libs/re__Perl.cmi +0 -0
  238. package/assets/interpreter/libs/re__Pmark.cmi +0 -0
  239. package/assets/interpreter/libs/re__Posix.cmi +0 -0
  240. package/assets/interpreter/libs/re__Posix_class.cmi +0 -0
  241. package/assets/interpreter/libs/re__Replace.cmi +0 -0
  242. package/assets/interpreter/libs/re__Search.cmi +0 -0
  243. package/assets/interpreter/libs/re__Slice.cmi +0 -0
  244. package/assets/interpreter/libs/re__Str.cmi +0 -0
  245. package/assets/interpreter/libs/re__View.cmi +0 -0
  246. package/assets/interpreter/libs/str.cma +0 -0
  247. package/assets/interpreter/libs/str.cmi +0 -0
  248. package/assets/interpreter/libs/unix.cma +0 -0
  249. package/assets/interpreter/libs/unix.cmi +0 -0
  250. package/assets/interpreter/libs/unixLabels.cmi +0 -0
  251. package/assets/interpreter/libs/uutf.cma +0 -0
  252. package/assets/interpreter/libs/uutf.cmi +0 -0
  253. package/assets/interpreter/libs/xmlm.cma +0 -0
  254. package/assets/interpreter/libs/xmlm.cmi +0 -0
  255. package/assets/interpreter/libs/z.cmi +0 -0
  256. package/assets/interpreter/libs/zarith.cma +0 -0
  257. package/assets/interpreter/libs/zarith_stubs_js.cma +0 -0
  258. package/assets/interpreter/libs/zarith_stubs_js.cmi +0 -0
  259. package/assets/interpreter/worker.js +20246 -20050
  260. package/package.json +1 -1
  261. package/src/compiler/compile.js +185 -14
  262. package/src/compiler/worker.js +38 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-codes/ocaml",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Run OCaml in the browser with no server: a compiler that emits JavaScript, and a toplevel that runs OCaml directly",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,11 +11,24 @@
11
11
 
12
12
  const lines = (text) => text.split('\n').filter((line) => line.trim() !== '');
13
13
 
14
- // js_of_ocaml is a command-line program and calls `exit` after writing its
15
- // output -- and in a browser-target build `exit` is not implemented, so it
16
- // raises. The output has already arrived by then, so this is noise rather than
17
- // a diagnostic. Filter it, since the CLI is not ours to change.
18
- const PHANTOM = /Function 'exit' not implemented/;
14
+ // Three kinds of upstream noise reach the worker's stderr, and none says anything
15
+ // about the program being compiled:
16
+ //
17
+ // - js_of_ocaml is a command-line program and calls `exit` after writing its
18
+ // output -- and in a browser-target build `exit` is not implemented, so it
19
+ // raises. The output has already arrived by then.
20
+ // - js_of_ocaml's runtime tries to `importScripts` its own chunk files, which
21
+ // cannot work once the worker is created from a data URL -- the trick that lets
22
+ // the compiler run from a CDN, since a worker script must otherwise be
23
+ // same-origin. It falls back to the files it was handed and the bundle is
24
+ // correct. A genuinely fatal load leaves no output at all, and that is reported
25
+ // separately by `compile`.
26
+ // - js_of_ocaml warns when it cannot represent an integer exactly, and does so
27
+ // about its own generated runtime rather than about the program. It is filtered
28
+ // on request; the cost is that a program relying on such an integer will not see
29
+ // it either.
30
+ const PHANTOM =
31
+ /Function 'exit' not implemented|Failed to execute 'importScripts'|^Uncaught NetworkError:|Warning \[integer-overflow\]/;
19
32
  const diagnosticsOf = (text) => lines(text).filter((line) => !PHANTOM.test(line));
20
33
 
21
34
  // Byte-for-byte, and deliberately not TextDecoder: in browsers "latin1" and
@@ -38,9 +51,25 @@ function hexToBytes(hex) {
38
51
  return bytes;
39
52
  }
40
53
 
41
- function runInWorker(baseUrl, script, files, env, argv, fs = []) {
54
+ // A worker script has to be same-origin with whatever creates it: `new Worker()`
55
+ // refuses a cross-origin URL, and CORS does not help -- it is the script fetch the
56
+ // browser will not make. So the worker is created from a small data URL whose only
57
+ // job is to `importScripts` the harness, which *is* allowed across origins and then
58
+ // loads everything else itself.
59
+ //
60
+ // A data URL rather than a blob because the compiler may itself be running inside a
61
+ // worker, where `URL.createObjectURL` is not dependable. The URL is interpolated
62
+ // into JavaScript, so it is quoted with `JSON.stringify` and base64-encoded: the
63
+ // base64 form needs no escaping of its own.
64
+ const toDataUrl = (content, type = 'text/javascript') =>
65
+ `data:${type};charset=UTF-8;base64,` + btoa(content);
66
+
67
+ const workerDataUrlFor = (script) =>
68
+ toDataUrl(`importScripts(${JSON.stringify(String(script))});`);
69
+
70
+ async function runInWorker(baseUrl, script, files, env, argv, fs = [], libs) {
71
+ const worker = new Worker(workerDataUrlFor(new URL('worker.js', baseUrl).href));
42
72
  return new Promise((resolve, reject) => {
43
- const worker = new Worker(new URL('worker.js', baseUrl));
44
73
  worker.onmessage = (event) => {
45
74
  worker.terminate();
46
75
  resolve(event.data);
@@ -49,7 +78,7 @@ function runInWorker(baseUrl, script, files, env, argv, fs = []) {
49
78
  worker.terminate();
50
79
  reject(new Error(event.message || 'the compiler worker failed to load'));
51
80
  };
52
- worker.postMessage({ script, files, env, argv, fs });
81
+ worker.postMessage({ script, files, env, argv, fs, libs });
53
82
  });
54
83
  }
55
84
 
@@ -94,8 +123,92 @@ async function loadRuntime(baseUrl) {
94
123
  const STDLIB = 'stdlib.js';
95
124
  const STDLIB_DIR = '/stdlib';
96
125
 
126
+ // The libraries a program names are fetched on demand rather than shipped up front.
127
+ // `libs/index.txt` says which files a module needs, and a compile that could not find
128
+ // a module is retried with those files added. Only what a program uses is fetched, and
129
+ // once fetched it stays, so the set converges as a session goes on.
130
+ const required = new Set();
131
+ let libraryIndex;
132
+
133
+ const loadLibraryIndex = async (baseUrl) => {
134
+ const byUnit = new Map();
135
+ try {
136
+ const text = await (await fetch(new URL('libs/index.txt', baseUrl))).text();
137
+ for (const line of lines(text)) {
138
+ const [unit, files] = line.split('\t');
139
+ if (unit && files) byUnit.set(unit.toLowerCase(), files.split(' ').filter(Boolean));
140
+ }
141
+ // The stub libraries are small and every link may need them, so they are always
142
+ // fetched rather than being part of a group.
143
+ const stubs = await (await fetch(new URL('libs/stubs.txt', baseUrl))).text();
144
+ for (const name of lines(stubs)) required.add(name.trim());
145
+ } catch {
146
+ // No index, so nothing can be fetched on demand: a program gets the standard
147
+ // library and whatever else it can link without one.
148
+ }
149
+ return byUnit;
150
+ };
151
+
152
+ // The two ways the compiler says a library is missing: a module it cannot find at all,
153
+ // and a unit with nothing to link against.
154
+ const missingUnits = (diagnostics) => {
155
+ const text = diagnostics.join('\n');
156
+ const names = new Set();
157
+ for (const [, name] of text.matchAll(/Unbound module ([A-Za-z0-9_']+)/g)) names.add(name);
158
+ for (const [, block] of text.matchAll(
159
+ /No implementation provided for the following modules:([\s\S]*?)(?:\n\s*\n|$)/g
160
+ )) {
161
+ for (const [, name] of block.matchAll(/([A-Za-z0-9_']+) referenced from/g)) names.add(name);
162
+ }
163
+ return [...names];
164
+ };
165
+
166
+ const filesFor = (byUnit, names) => {
167
+ const files = new Set();
168
+ const known = [...byUnit.keys()];
169
+ for (const name of names) {
170
+ const key = name.toLowerCase();
171
+ const exact = byUnit.get(key);
172
+ if (exact) {
173
+ for (const file of exact) files.add(file);
174
+ continue;
175
+ }
176
+ // A submodule is named without its package: Dom_html is Js_of_ocaml's.
177
+ for (const unit of known.filter((u) => u.endsWith('__' + key))) {
178
+ for (const file of byUnit.get(unit)) files.add(file);
179
+ }
180
+ }
181
+ return [...files];
182
+ };
183
+
184
+ // Byte by byte, not through TextDecoder: these are binary files, and decoding them as
185
+ // UTF-8 would corrupt every byte above 127 into something else entirely.
186
+ const toByteString = (bytes) => {
187
+ const chunks = [];
188
+ for (let i = 0; i < bytes.length; i += 0x8000) {
189
+ chunks.push(String.fromCharCode(...bytes.subarray(i, i + 0x8000)));
190
+ }
191
+ return chunks.join('');
192
+ };
193
+
194
+ // The library files are fetched here, in the page, rather than in the worker: a worker
195
+ // created from a data URL has an opaque origin, so a fetch from inside it to the server
196
+ // that served the page is refused. The page is same-origin, so it can.
197
+ const fetchLibraries = async (baseUrl, names) => {
198
+ const base = new URL('libs/', baseUrl);
199
+ return Promise.all(
200
+ names.map(async (name) => {
201
+ const bytes = new Uint8Array(await (await fetch(new URL(name, base))).arrayBuffer());
202
+ // The stub libraries are looked for at /static, from the stub directory of
203
+ // the build; everything else is read from the compiler's library directory.
204
+ const path = (name.startsWith('dll') ? '/static/' : '/stdlib/') + name;
205
+ return { path, content: toByteString(bytes) };
206
+ })
207
+ );
208
+ };
209
+
97
210
  /**
98
- * Compile OCaml source to JavaScript.
211
+ * Compile OCaml source to JavaScript, fetching whatever libraries it turns out to need.
99
212
  *
100
213
  * @param {string} source
101
214
  * @param {object} options
@@ -105,19 +218,58 @@ const STDLIB_DIR = '/stdlib';
105
218
  * @param {string} [options.stdlib] - the filesystem artifact carrying the standard library.
106
219
  * @returns {Promise<{js: string|null, diagnostics: string[], error?: string}>}
107
220
  */
108
- export async function compile(source, { baseUrl, driver = 'compile_stdout.browser.js', jsoo = 'jsoo.js', stdlib = STDLIB }) {
221
+ export async function compile(source, options) {
222
+ libraryIndex ??= await loadLibraryIndex(options.baseUrl);
223
+ let libs = [...required];
224
+ for (let attempt = 0; attempt < 6; attempt++) {
225
+ const result = await compileOnce(source, { ...options, libs });
226
+ const added = filesFor(libraryIndex, missingUnits(result.diagnostics)).filter(
227
+ (name) => !required.has(name)
228
+ );
229
+ if (added.length === 0) return result;
230
+ for (const name of added) required.add(name);
231
+ libs = [...required];
232
+ }
233
+ // Six rounds and still missing something: run once more, so the caller sees the
234
+ // compiler's own error rather than a guess about it.
235
+ return compileOnce(source, { ...options, libs });
236
+ }
237
+
238
+ // zarith's primitives are C stubs, and `zarith_stubs_js` implements them in JavaScript as a
239
+ // js_of_ocaml runtime chunk -- `//Provides:` and `//Requires:` lines that have to be a *link
240
+ // input* to js_of_ocaml, not text pasted into the output. Only as an input does js_of_ocaml
241
+ // honour them and put each definition where its dependencies already exist; concatenated,
242
+ // they land outside its primitive table and the program fails at load with
243
+ // "f.ml_z_init is not a function".
244
+ const RUNTIME_CHUNK = 'zarith_stubs_js.runtime.js';
245
+ const CHUNK_AS_INPUT = 'zarith_stubs_js.js';
246
+ let runtimeChunk;
247
+
248
+ const loadRuntimeChunk = async (baseUrl) => {
249
+ runtimeChunk ??= await (await fetch(new URL('libs/' + RUNTIME_CHUNK, baseUrl))).text();
250
+ return runtimeChunk;
251
+ };
252
+
253
+ async function compileOnce(
254
+ source,
255
+ { baseUrl, driver = 'compile_stdout.browser.js', jsoo = 'jsoo.js', stdlib = STDLIB, libs = [] }
256
+ ) {
109
257
  if (typeof source !== 'string') throw new TypeError('compile: source must be a string');
110
258
  const runtimeFiles = await loadRuntime(baseUrl);
111
259
  const files = [{ name: 'main.ml', content: source }, ...runtimeFiles];
260
+ const libraryFiles = await fetchLibraries(baseUrl, libs);
112
261
 
113
- // Source -> bytecode, as hex.
262
+ // Source -> bytecode, as hex. The library files go over with the source, one request
263
+ // each, from the browser's cache after the first compile. The bytecode ->
264
+ // JavaScript step needs none of them.
114
265
  const front = await runInWorker(
115
266
  baseUrl,
116
267
  new URL(driver, baseUrl).href,
117
268
  files,
118
269
  { OCAMLLIB: STDLIB_DIR },
119
270
  ['node', 'compile_stdout', 'main.ml'],
120
- [new URL(stdlib, baseUrl).href]
271
+ [new URL(stdlib, baseUrl).href],
272
+ libraryFiles
121
273
  );
122
274
  const diagnostics = diagnosticsOf(front.err.join('\n'));
123
275
  // The driver prints its hex with no newlines anywhere, so its chunks are
@@ -134,12 +286,31 @@ export async function compile(source, { baseUrl, driver = 'compile_stdout.browse
134
286
  // `--pretty` is for the reader -- the emitted JavaScript is what a Compiled
135
287
  // Code pane shows -- and it also keeps the output line-oriented, which the
136
288
  // joining below depends on.
289
+ const stubs = required.has('zarith.cma') ? await loadRuntimeChunk(baseUrl) : null;
137
290
  const back = await runInWorker(
138
291
  baseUrl,
139
292
  new URL(jsoo, baseUrl).href,
140
- [{ name: 'main.byte', content: bytesToString(hexToBytes(hex)) }, ...runtimeFiles],
293
+ [
294
+ { name: 'main.byte', content: bytesToString(hexToBytes(hex)) },
295
+ ...runtimeFiles,
296
+ // The chunk is mounted as a file and named on the command line, which is what
297
+ // makes js_of_ocaml treat it as a link input rather than something to paste in.
298
+ ...(stubs ? [{ name: CHUNK_AS_INPUT, content: stubs }] : [])
299
+ ],
141
300
  {},
142
- ['node', 'js_of_ocaml', 'main.byte', '-o', '-', '--target-env=browser', '--pretty']
301
+ [
302
+ 'node',
303
+ 'js_of_ocaml',
304
+ // The chunk comes before the bytecode: js_of_ocaml decides how to read its
305
+ // positional inputs from the first one, so a .js has to lead or the bytecode
306
+ // gets parsed as JavaScript.
307
+ ...(stubs ? [CHUNK_AS_INPUT] : []),
308
+ 'main.byte',
309
+ '-o',
310
+ '-',
311
+ '--target-env=browser',
312
+ '--pretty'
313
+ ]
143
314
  );
144
315
  const backDiagnostics = diagnosticsOf(back.err.join('\n'));
145
316
  // js_of_ocaml's stdout is line-buffered, so it hands over one console call
@@ -8,8 +8,42 @@
8
8
  // browser filesystem fd 1 and fd 2 are wired to console.log and console.error
9
9
  // (runtime/js/fs_fake.js). A worker has no other way in: the output never
10
10
  // reaches a file the page can read.
11
- self.onmessage = (event) => {
12
- const { script, files, env, argv, fs = [] } = event.data;
11
+ // Bytes to a bytestring, one character per byte, chunked so a multi-megabyte archive
12
+ // does not overflow the argument list. Deliberately not TextDecoder: in browsers
13
+ // "latin1" and "iso-8859-1" are both aliases for windows-1252, which remaps
14
+ // 0x80-0x9F, and that is enough to make a .cmi or a .cma unreadable.
15
+ const bytesToString = (bytes) => {
16
+ let s = '';
17
+ const CHUNK = 0x8000;
18
+ for (let i = 0; i < bytes.length; i += CHUNK) {
19
+ s += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
20
+ }
21
+ return s;
22
+ };
23
+
24
+ // The library files this compile needs. The *page* fetches them, not this worker: a
25
+ // worker created from a data URL has an opaque origin, so a fetch from here to the
26
+ // server that served the page is refused. They arrive with the path each belongs at,
27
+ // which is the same shape a filesystem artifact uses.
28
+ const mountLibraries = (files) => {
29
+ const archives = [];
30
+ for (const { path, content } of files) {
31
+ globalThis.jsoo_fs_tmp.push({ name: path, content });
32
+ if (path.endsWith('.cma')) archives.push(path.slice(path.lastIndexOf('/') + 1));
33
+ }
34
+ // What the linker links, and the order matters: a library has to come before the
35
+ // archive that references it. stdlib first, because that is where a program's
36
+ // runtime starts, then js_of_ocaml's runtime, then the rest.
37
+ const rank = (name) => (name === 'stdlib.cma' ? 0 : name === 'jsoo_runtime.cma' ? 1 : 2);
38
+ archives.sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : a > b ? 1 : 0));
39
+ globalThis.jsoo_fs_tmp.push({
40
+ name: '/stdlib/cmas.txt',
41
+ content: ['stdlib.cma', ...archives].join('\n') + '\n',
42
+ });
43
+ };
44
+
45
+ self.onmessage = async (event) => {
46
+ const { script, files, env, argv, fs = [], libs = [] } = event.data;
13
47
  const out = [];
14
48
  const err = [];
15
49
  const realLog = console.log;
@@ -30,6 +64,8 @@ self.onmessage = (event) => {
30
64
  // take the other branch and register the files directly, so the order is
31
65
  // not load-bearing, but this is the cheaper one.
32
66
  for (const url of fs) importScripts(url);
67
+ // Then the libraries, which have to be in place before the program starts.
68
+ if (libs.length > 0) mountLibraries(libs);
33
69
  importScripts(script);
34
70
  // The chunks go back as they arrived, not joined: the caller knows how to
35
71
  // put them together, and joining here would lose the newlines between