substrate-lang 0.9.4 → 0.10.1

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 (199) hide show
  1. package/README.md +1 -1
  2. package/build/compiler/{binary.cdd2ffb408dd@0+0.wasm → binary.2d43b43d1600@0+0.wasm} +0 -0
  3. package/build/compiler/checker.4dd4556dd421@0+0.wasm +0 -0
  4. package/build/compiler/{compile.da85f9bc4478@0+0.wasm → compile.5d8f6407af2a@0+0.wasm} +0 -0
  5. package/build/compiler/{core.0410ffbdc488@0+0.wasm → core.f2defb75e00b@0+0.wasm} +0 -0
  6. package/build/compiler/expander.967ce443d379@0+0.wasm +0 -0
  7. package/build/compiler/fs.01b8aab4a9a8@0+0.wasm +0 -0
  8. package/build/compiler/{leb128.ba77430f659f@0+0.wasm → leb128.15d814b7b4f2@0+0.wasm} +0 -0
  9. package/build/compiler/{link.be6e641afa93@0+0.wasm → link.b826ea7c8f17@0+0.wasm} +0 -0
  10. package/build/compiler/{macros.b695763c0ca3@0+0.wasm → macros.b269e0c42784@0+0.wasm} +0 -0
  11. package/build/compiler/{main.cbbdf42e34c2@0+0.wasm → main.4fc52f204d54@0+0.wasm} +0 -0
  12. package/build/compiler/{ops.324f0a665fab@0+0.wasm → ops.515b5e05f646@0+0.wasm} +0 -0
  13. package/build/compiler/{path.1b5fd988b6f3@0+0.wasm → path.5a6286472cd4@0+0.wasm} +0 -0
  14. package/build/compiler/program +21 -21
  15. package/build/compiler/{prune.29dde307bdee@0+0.wasm → prune.406215c3e4f8@0+0.wasm} +0 -0
  16. package/build/compiler/reader.694b03770bf0@0+0.wasm +0 -0
  17. package/build/compiler/roots.cefd10e93684@0+0.wasm +0 -0
  18. package/build/compiler/{running.b99787c2d180@0+0.wasm → running.0d25dbaec018@0+0.wasm} +0 -0
  19. package/build/compiler/serialize.b0d82f3d63d2@0+0.wasm +0 -0
  20. package/build/compiler/{sha256.f6c742932dde@0+0.wasm → sha256.a6c32b04d799@0+0.wasm} +0 -0
  21. package/build/compiler/substrate.5ee5d0b4d3e7@0+0.wasm +0 -0
  22. package/build/compiler/{syntax.fadc832db29f@0+0.wasm → syntax.32f3e40b61c7@0+0.wasm} +0 -0
  23. package/build/compiler/{writer.550227768eb9@0+0.wasm → writer.00b7802cbe39@0+0.wasm} +0 -0
  24. package/build/sub/{base64.454b045156d2@0+0.wasm → base64.a26bdcf60a2d@0+0.wasm} +0 -0
  25. package/build/sub/build.459c5b9412bd@0+0.wasm +0 -0
  26. package/build/sub/chromium.3d5818f2707d@0+0.wasm +0 -0
  27. package/build/sub/common.1edc613607a0@0+0.wasm +0 -0
  28. package/build/sub/deflate.23f7c4679260@0+0.wasm +0 -0
  29. package/build/sub/doc.0775df5de6fe@0+0.wasm +0 -0
  30. package/build/sub/{doc.f18ad14060e9@0+0.wasm → doc.ef9965d5d402@0+0.wasm} +0 -0
  31. package/build/sub/env.e5451da6e747@0+0.wasm +0 -0
  32. package/build/sub/events.bf5473ebf220@0+0.wasm +0 -0
  33. package/build/sub/fetch.7790d4943758@0+0.wasm +0 -0
  34. package/build/sub/fetch.96b2d10f025e@0+0.wasm +0 -0
  35. package/build/sub/files.ad0469fdf6fa@0+0.wasm +0 -0
  36. package/build/sub/fs.01b8aab4a9a8@0+0.wasm +0 -0
  37. package/build/sub/git.dc4c0e757ebe@0+0.wasm +0 -0
  38. package/build/sub/grants.c54776c0d7e3@0+0.wasm +0 -0
  39. package/build/sub/gzip.16e372eda1cf@0+0.wasm +0 -0
  40. package/build/sub/hash.660682e4f491@0+0.wasm +0 -0
  41. package/build/sub/{html.0b0362932a75@0+0.wasm → html.a23126eb949a@0+0.wasm} +0 -0
  42. package/build/sub/{html.3b3c8d7d9971@0+0.wasm → html.e9ed37f34977@0+0.wasm} +0 -0
  43. package/build/sub/{inflate.3cfa17586072@0+0.wasm → inflate.59bb61f14001@0+0.wasm} +0 -0
  44. package/build/sub/json.55590d2f518a@0+0.wasm +0 -0
  45. package/build/sub/manifest.ef317480deb0@0+0.wasm +0 -0
  46. package/build/sub/{map.e582eae03ec2@0+0.wasm → map.f455cca6a42a@0+0.wasm} +0 -0
  47. package/build/sub/pack.e02836264f6a@0+0.wasm +0 -0
  48. package/build/sub/{path.1b5fd988b6f3@0+0.wasm → path.5a6286472cd4@0+0.wasm} +0 -0
  49. package/build/sub/path.6891ffea73ca@0+0.wasm +0 -0
  50. package/build/sub/process.7106571fa723@0+0.wasm +0 -0
  51. package/build/sub/program +48 -48
  52. package/build/sub/read.0b93933d62a2@0+0.wasm +0 -0
  53. package/build/sub/{reader.d256e69e09e2@0+0.wasm → reader.694b03770bf0@0+0.wasm} +0 -0
  54. package/build/sub/{root.02141d04d568@0+0.wasm → root.f10a4906f460@0+0.wasm} +0 -0
  55. package/build/sub/{roots.e366210c01d6@0+0.wasm → roots.cefd10e93684@0+0.wasm} +0 -0
  56. package/build/sub/{sha256.f6c742932dde@0+0.wasm → sha256.a6c32b04d799@0+0.wasm} +0 -0
  57. package/build/sub/{sha512.9fee24e95460@0+0.wasm → sha512.c24a792d7891@0+0.wasm} +0 -0
  58. package/build/sub/site.1732b58ec0e8@0+0.wasm +0 -0
  59. package/build/sub/site.ae3913b0b0f4@0+0.wasm +0 -0
  60. package/build/sub/store.a24d7a25daa6@0+0.wasm +0 -0
  61. package/build/sub/stream.126e3bd5d626@0+0.wasm +0 -0
  62. package/build/sub/sub.2e3092140706@0+0.wasm +0 -0
  63. package/build/sub/substrate.5ee5d0b4d3e7@0+0.wasm +0 -0
  64. package/build/sub/{syntax.fadc832db29f@0+0.wasm → syntax.32f3e40b61c7@0+0.wasm} +0 -0
  65. package/build/sub/tar.2a633861e654@0+0.wasm +0 -0
  66. package/build/sub/test.9bd59c73f65e@0+0.wasm +0 -0
  67. package/build/sub/{text.8ff142d72fd7@0+0.wasm → text.1bc90482bd43@0+0.wasm} +0 -0
  68. package/build/sub/{timer.d3454726b3bf@0+0.wasm → timer.f35eb62ae446@0+0.wasm} +0 -0
  69. package/build/sub/upgrade.39ee449b00d5@0+0.wasm +0 -0
  70. package/build/sub/{utf8.e82afeb9e953@0+0.wasm → utf8.e7216bd054e1@0+0.wasm} +0 -0
  71. package/build/sub/worker.762a9d642af0@0+0.wasm +0 -0
  72. package/build/sub/writer.00b7802cbe39@0+0.wasm +0 -0
  73. package/package.json +1 -1
  74. package/std/archive/tar.sub +30 -23
  75. package/std/browser/chromium.sub +5 -4
  76. package/std/browser/devtools.sub +24 -19
  77. package/std/compress/gzip.sub +18 -16
  78. package/std/compress/inflate.sub +7 -4
  79. package/std/crypto/password.sub +3 -3
  80. package/std/doc/doc.sub +2 -2
  81. package/std/encoding/json.sub +20 -8
  82. package/std/encoding/wire.sub +9 -7
  83. package/std/http/form.sub +2 -2
  84. package/std/http/multipart.sub +8 -10
  85. package/std/http/route.sub +9 -4
  86. package/std/http/server.sub +14 -11
  87. package/std/http/session.sub +2 -2
  88. package/std/http/static.sub +5 -5
  89. package/std/meta/derive.sub +0 -2
  90. package/std/meta/method.sub +41 -0
  91. package/std/meta/type-info.sub +30 -37
  92. package/std/package/files.sub +24 -25
  93. package/std/package/git.sub +26 -33
  94. package/std/package/hash.sub +8 -8
  95. package/std/package/manifest.sub +67 -42
  96. package/std/package/map.sub +1 -1
  97. package/std/package/pack.sub +13 -13
  98. package/std/package/read.sub +7 -6
  99. package/std/package/site.sub +7 -7
  100. package/std/package/store.sub +16 -12
  101. package/std/package.sub +7 -6
  102. package/std/test/browser.sub +9 -8
  103. package/std/test/build.sub +2 -2
  104. package/std/test/page.sub +31 -26
  105. package/std/test/run.sub +8 -7
  106. package/std/test/test.sub +12 -13
  107. package/std/vector/vector.sub +75 -0
  108. package/std/web/canvas.sub +3 -3
  109. package/std/web/dom.sub +45 -11
  110. package/std/web/gpu-commands.sub +439 -0
  111. package/std/web/gpu.sub +17 -424
  112. package/std/web/patches.sub +45 -10
  113. package/substrate/VERSION +1 -1
  114. package/substrate/compiler/checker.sub +40 -212
  115. package/substrate/compiler/compile.sub +3 -3
  116. package/substrate/compiler/expander.sub +32 -50
  117. package/substrate/compiler/main.sub +9 -9
  118. package/substrate/compiler/reader.sub +4 -4
  119. package/substrate/compiler/roots.sub +8 -8
  120. package/substrate/compiler/running.sub +1 -1
  121. package/substrate/compiler/serialize.sub +56 -49
  122. package/substrate/compiler/substrate.sub +53 -11
  123. package/substrate/host/errors.mjs +3 -2
  124. package/substrate/host/events.sub +3 -16
  125. package/substrate/host/fs.mjs +5 -4
  126. package/substrate/host/fs.sub +135 -102
  127. package/substrate/host/grants.mjs +1 -1
  128. package/substrate/host/grants.sub +8 -7
  129. package/substrate/host/modules/fake.mjs +1 -1
  130. package/substrate/host/modules/websocket.mjs +4 -4
  131. package/substrate/host/modules/websocket.sub +8 -6
  132. package/substrate/host/node/env.mjs +12 -6
  133. package/substrate/host/node/env.sub +21 -16
  134. package/substrate/host/node/fs.mjs +1 -1
  135. package/substrate/host/node/net.mjs +2 -2
  136. package/substrate/host/node/net.sub +13 -6
  137. package/substrate/host/node/process.mjs +4 -4
  138. package/substrate/host/node/process.sub +25 -24
  139. package/substrate/host/node/signal.mjs +3 -2
  140. package/substrate/host/node/signal.sub +9 -6
  141. package/substrate/host/node/watch.mjs +1 -1
  142. package/substrate/host/node/watch.sub +11 -9
  143. package/substrate/host/stream.sub +2 -0
  144. package/substrate/host/subhost-core.mjs +9 -1
  145. package/substrate/host/web/audio.mjs +17 -9
  146. package/substrate/host/web/audio.sub +9 -3
  147. package/substrate/host/web/clipboard.mjs +2 -2
  148. package/substrate/host/web/clipboard.sub +10 -3
  149. package/substrate/host/web/dom.mjs +165 -64
  150. package/substrate/host/web/dom.sub +9 -27
  151. package/substrate/host/web/files.mjs +2 -2
  152. package/substrate/host/web/files.sub +6 -2
  153. package/substrate/host/web/fs.mjs +3 -3
  154. package/substrate/host/web/gpu.mjs +63 -25
  155. package/substrate/host/web/gpu.sub +18 -4
  156. package/substrate/host/web/location.mjs +18 -2
  157. package/substrate/host/web/location.sub +19 -7
  158. package/substrate/host/worker-side.mjs +3 -1
  159. package/substrate/host/worker.mjs +6 -5
  160. package/substrate/host/worker.sub +10 -10
  161. package/build/compiler/checker.2b0ea87ed311@0+0.wasm +0 -0
  162. package/build/compiler/expander.80707e1666d6@0+0.wasm +0 -0
  163. package/build/compiler/fs.5017b627cbd2@0+0.wasm +0 -0
  164. package/build/compiler/reader.d256e69e09e2@0+0.wasm +0 -0
  165. package/build/compiler/roots.e366210c01d6@0+0.wasm +0 -0
  166. package/build/compiler/serialize.a12536bbbe9a@0+0.wasm +0 -0
  167. package/build/compiler/substrate.728c690187f2@0+0.wasm +0 -0
  168. package/build/sub/build.115aeefc15e8@0+0.wasm +0 -0
  169. package/build/sub/chromium.fc0bfea0fbe6@0+0.wasm +0 -0
  170. package/build/sub/common.997c21b0b8cf@0+0.wasm +0 -0
  171. package/build/sub/deflate.3138a38325a1@0+0.wasm +0 -0
  172. package/build/sub/doc.e3f9a998cd91@0+0.wasm +0 -0
  173. package/build/sub/env.861d55fff974@0+0.wasm +0 -0
  174. package/build/sub/events.2c981f6f1ec7@0+0.wasm +0 -0
  175. package/build/sub/fetch.b0ce8f8b3da8@0+0.wasm +0 -0
  176. package/build/sub/fetch.d62aa5fc9623@0+0.wasm +0 -0
  177. package/build/sub/files.1706cc0941c0@0+0.wasm +0 -0
  178. package/build/sub/fs.5017b627cbd2@0+0.wasm +0 -0
  179. package/build/sub/git.1c5791236e35@0+0.wasm +0 -0
  180. package/build/sub/grants.cd99dff2854e@0+0.wasm +0 -0
  181. package/build/sub/gzip.9ebea150f218@0+0.wasm +0 -0
  182. package/build/sub/hash.ba8330dad9ff@0+0.wasm +0 -0
  183. package/build/sub/json.af2e0ae20084@0+0.wasm +0 -0
  184. package/build/sub/manifest.6e014ac19e5d@0+0.wasm +0 -0
  185. package/build/sub/pack.4295e4f5bdbd@0+0.wasm +0 -0
  186. package/build/sub/path.de22f6659534@0+0.wasm +0 -0
  187. package/build/sub/process.ca10ac0b8a62@0+0.wasm +0 -0
  188. package/build/sub/read.279fafcda508@0+0.wasm +0 -0
  189. package/build/sub/site.76aa8286c257@0+0.wasm +0 -0
  190. package/build/sub/site.e6e415bc3170@0+0.wasm +0 -0
  191. package/build/sub/store.c48db3345968@0+0.wasm +0 -0
  192. package/build/sub/stream.27fd2fe858e1@0+0.wasm +0 -0
  193. package/build/sub/sub.c0fb7310de12@0+0.wasm +0 -0
  194. package/build/sub/substrate.728c690187f2@0+0.wasm +0 -0
  195. package/build/sub/tar.692c0e8827cd@0+0.wasm +0 -0
  196. package/build/sub/test.1fd2ff8123bf@0+0.wasm +0 -0
  197. package/build/sub/upgrade.2a9219e6a45d@0+0.wasm +0 -0
  198. package/build/sub/worker.ea1e11f8f5e2@0+0.wasm +0 -0
  199. package/build/sub/writer.550227768eb9@0+0.wasm +0 -0
@@ -10,6 +10,9 @@ const HEARD = 1;
10
10
  const IMAGE_LOADED = 2;
11
11
  const IMAGE_FAILED = 3;
12
12
 
13
+ // The document's node number (std/web/patches.sub).
14
+ const DOCUMENT = 1;
15
+
13
16
  const imageTypes = {
14
17
  png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp',
15
18
  avif: 'image/avif', bmp: 'image/bmp', svg: 'image/svg+xml',
@@ -17,9 +20,14 @@ const imageTypes = {
17
20
 
18
21
  export function dom(root) {
19
22
  const templates = new Map();
20
- const nodes = new Map([[0, root]]);
23
+ const nodes = new Map([[0, root], [DOCUMENT, root.ownerDocument]]);
21
24
  const images = new Map();
22
- let loading = 0;
25
+ const loading = new Map();
26
+ const counted = (tag, n) => {
27
+ const k = (loading.get(tag) || 0) + n;
28
+ if (k === 0) loading.delete(tag);
29
+ else loading.set(tag, k);
30
+ };
23
31
  let app = null;
24
32
  let caller = null;
25
33
 
@@ -30,6 +38,73 @@ export function dom(root) {
30
38
  return e && e.source === SOURCE ? e.data : null;
31
39
  };
32
40
 
41
+ const kinds = ['Node', 'Event', 'NodeList', 'HTMLCollection', 'DOMRectReadOnly', 'DOMTokenList',
42
+ 'CSSStyleDeclaration', 'DOMStringMap', 'ValidityState', 'FileList', 'Blob', 'DataTransfer', 'TouchList', 'Touch',
43
+ 'IntersectionObserverEntry'].map((name) => globalThis[name]).filter((kind) => kind !== undefined);
44
+ const reachable = (x) => kinds.some((kind) => x instanceof kind);
45
+
46
+ function guarded(path, f) {
47
+ try {
48
+ return f();
49
+ } catch (e) {
50
+ caller.trap(`dom: ${path}: ${e.message}`);
51
+ }
52
+ }
53
+
54
+ function step(x, name, path) {
55
+ if (!(name in Object(x))) caller.trap(`dom: ${path}: there is no ${name}`);
56
+ return guarded(path, () => x[name]);
57
+ }
58
+
59
+ function owner(root, path) {
60
+ const parts = path.split('.');
61
+ const last = parts.pop();
62
+ let x = root;
63
+ for (let k = 0; k < parts.length; k++) {
64
+ x = step(x, parts[k], path);
65
+ if (x === null || x === undefined) return [null, last];
66
+ if (!reachable(x)) {
67
+ caller.trap(`dom: ${path}: ${parts.slice(0, k + 1).join('.')} is not one of the page's objects`);
68
+ }
69
+ }
70
+ return [x, last];
71
+ }
72
+
73
+ function walk(root, path) {
74
+ const [x, last] = owner(root, path);
75
+ return x === null ? null : step(x, last, path);
76
+ }
77
+
78
+ function read(root, path) {
79
+ const v = walk(root, path);
80
+ const plain = v === null || v === undefined || ['number', 'string', 'boolean'].includes(typeof v);
81
+ if (!plain) caller.trap(`dom: ${path} is not a number, text, or true or false`);
82
+ return v;
83
+ }
84
+
85
+ function write(root, path, value) {
86
+ const [x, last] = owner(root, path);
87
+ if (x === null || !(last in x)) caller.trap(`dom: ${path}: there is no ${last} to set`);
88
+ guarded(path, () => { x[last] = value; });
89
+ }
90
+
91
+ function call(root, path, args) {
92
+ const [x, last] = owner(root, path);
93
+ if (x === null || typeof x[last] !== 'function') caller.trap(`dom: ${path} is not a method`);
94
+ const result = guarded(path, () => x[last](...args));
95
+ if (result && typeof result.catch === 'function') result.catch(() => {});
96
+ }
97
+
98
+ const numberOf = (v) => (v === null || v === undefined ? 0 : Number(v));
99
+ const textOf = (v) => (v === null || v === undefined ? '' : String(v));
100
+
101
+ const bits = new DataView(new ArrayBuffer(8));
102
+ const float64 = (low, high) => {
103
+ bits.setUint32(0, low, true);
104
+ bits.setUint32(4, high, true);
105
+ return bits.getFloat64(0, true);
106
+ };
107
+
33
108
  const listener = (e) => {
34
109
  const messages = e.currentTarget.subListeners;
35
110
  if (messages && e.type in messages) give(e, messages[e.type]);
@@ -66,12 +141,27 @@ export function dom(root) {
66
141
 
67
142
  const windowTarget = (type) => (type === 'pointerlockchange' || type === 'pointerlockerror' ? document : window);
68
143
 
144
+ const watched = new Map();
145
+ let observer = null;
146
+
147
+ function watch(node, message) {
148
+ observer ||= new IntersectionObserver((entries) => {
149
+ for (const entry of entries) if (watched.has(entry.target)) give(entry, watched.get(entry.target));
150
+ });
151
+ watched.set(node, message);
152
+ observer.observe(node);
153
+ }
154
+
69
155
  const unlisten = (id) => {
70
156
  const n = nodes.get(id);
71
157
  if (n instanceof Node && n.subListeners) {
72
158
  for (const message of Object.values(n.subListeners)) caller.forget(message);
73
159
  n.subListeners = null;
74
160
  }
161
+ if (watched.has(n)) {
162
+ observer.unobserve(n);
163
+ watched.delete(n);
164
+ }
75
165
  for (const { type, message, handler } of windowListeners.get(id) || []) {
76
166
  windowTarget(type).removeEventListener(type, handler);
77
167
  caller.forget(message);
@@ -260,68 +350,75 @@ export function dom(root) {
260
350
  const node = nodes.get(w[i + 1]);
261
351
  const type = string(i + 2);
262
352
  (node.subListeners ||= {})[type] = w[i + 4];
263
- node.addEventListener(type, listener);
353
+ if (type === 'visibility') watch(node, w[i + 4]);
354
+ else node.addEventListener(type, listener);
264
355
  i += 5;
265
356
  break;
266
357
  }
267
- case 10: {
268
- const node = nodes.get(w[i + 1]);
269
- after.push(() => node.focus());
270
- i += 2;
271
- break;
272
- }
273
- case 11: {
274
- const node = nodes.get(w[i + 1]);
275
- after.push(() => { node.scrollTop = node.scrollHeight; });
276
- i += 2;
277
- break;
278
- }
279
- case 12:
358
+ case 10:
280
359
  listenWindow(w[i + 1], string(i + 2), w[i + 4]);
281
360
  i += 5;
282
361
  break;
283
- case 15: {
284
- const node = nodes.get(w[i + 1]);
285
- if (w[i + 2] !== 0) {
286
- after.push(() => { const p = node.requestPointerLock(); if (p && p.catch) p.catch(() => {}); });
287
- } else {
288
- after.push(() => document.exitPointerLock());
289
- }
290
- i += 3;
362
+ case 11: {
363
+ const canvas = nodes.get(w[i + 1]);
364
+ const count = w[i + 2];
365
+ const f = new Float32Array(w.buffer, w.byteOffset, w.length);
366
+ draw(canvas.getContext('2d'), w, f, i + 3, i + 3 + count, string);
367
+ i += 3 + count;
291
368
  break;
292
369
  }
293
- case 14: {
370
+ case 12: {
294
371
  const name = string(i + 2);
295
372
  const image = new Image();
296
373
  const bytes = app.files.get(resolvePath(app.cwd, name));
297
374
  const type = imageTypes[name.slice(name.lastIndexOf('.') + 1).toLowerCase()] || '';
298
375
  const url = bytes ? URL.createObjectURL(new Blob([bytes], { type })) : name;
299
376
  const id = w[i + 1];
377
+ const tag = w[i + 4];
300
378
  const finished = () => {
301
- loading--;
379
+ counted(tag, -1);
302
380
  if (bytes) URL.revokeObjectURL(url);
303
381
  };
304
- loading++;
382
+ counted(tag, 1);
305
383
  image.onload = () => {
306
384
  finished();
307
- caller.event(IMAGE_LOADED, id, 0, 0);
385
+ caller.event(IMAGE_LOADED, id, tag, 0);
308
386
  };
309
387
  image.onerror = () => {
310
388
  finished();
311
389
  console.error(`dom: cannot load the image ${name}`);
312
- caller.event(IMAGE_FAILED, id, 0, errors.missing, utf8.encode(`cannot load the image ${name}`));
390
+ caller.event(IMAGE_FAILED, id, tag, errors.missing, utf8.encode(`cannot load the image ${name}`));
313
391
  };
314
392
  image.src = url;
315
393
  images.set(w[i + 1], image);
316
- i += 4;
394
+ i += 5;
317
395
  break;
318
396
  }
319
397
  case 13: {
320
- const canvas = nodes.get(w[i + 1]);
321
- const count = w[i + 2];
322
- const f = new Float32Array(w.buffer, w.byteOffset, w.length);
323
- draw(canvas.getContext('2d'), w, f, i + 3, i + 3 + count, string);
324
- i += 3 + count;
398
+ const style = document.createElement('style');
399
+ style.textContent = string(i + 1);
400
+ root.ownerDocument.head.appendChild(style);
401
+ i += 3;
402
+ break;
403
+ }
404
+ case 14: {
405
+ const node = nodeOf(w[i + 1]);
406
+ const path = string(i + 2);
407
+ const value = w[i + 4] === 0 ? float64(w[i + 5], w[i + 6]) : string(i + 5);
408
+ after.push(() => write(node, path, value));
409
+ i += 7;
410
+ break;
411
+ }
412
+ case 15: {
413
+ const node = nodeOf(w[i + 1]);
414
+ const path = string(i + 2);
415
+ const args = [];
416
+ for (let k = 0; k < w[i + 4]; k++) {
417
+ const at = i + 5 + 3 * k;
418
+ args.push(w[at] === 0 ? float64(w[at + 1], w[at + 2]) : string(at + 1));
419
+ }
420
+ after.push(() => call(node, path, args));
421
+ i += 5 + 3 * w[i + 4];
325
422
  break;
326
423
  }
327
424
  default:
@@ -340,45 +437,49 @@ export function dom(root) {
340
437
  caller = theCaller;
341
438
  app = theApp;
342
439
  const given = (s, out) => caller.give(utf8.encode(s), out);
440
+ const current = () => {
441
+ const e = event();
442
+ if (!e) caller.trap('dom: there is no current event');
443
+ return e;
444
+ };
445
+ const known = (id) => {
446
+ if (!nodes.has(id)) caller.trap(`dom: there is no node ${id}`);
447
+ return nodeOf(id);
448
+ };
343
449
  return {
344
450
  source: SOURCE,
345
451
  apply(ptr, length) {
346
452
  apply(caller.memory(), caller.u(ptr), length);
347
453
  },
348
- event_value(out) {
349
- const e = event();
350
- const target = e && e.target;
351
- return given(target && typeof target.value === 'string' ? target.value : '', out);
454
+ event_number(ptr, length) {
455
+ return numberOf(read(current(), caller.string(ptr, length)));
352
456
  },
353
- event_key(out) {
354
- const e = event();
355
- return given(e && typeof e.key === 'string' ? e.key : '', out);
457
+ event_text(ptr, length, out) {
458
+ return given(textOf(read(current(), caller.string(ptr, length))), out);
356
459
  },
357
- event_flags() {
358
- const e = event();
359
- if (!e) return 0;
360
- return (e.shiftKey ? 1 : 0) | (e.ctrlKey ? 2 : 0) | (e.altKey ? 4 : 0) | (e.metaKey ? 8 : 0) |
361
- (e.target && e.target.checked === true ? 16 : 0) | (document.pointerLockElement ? 32 : 0);
460
+ event_call(ptr, length) {
461
+ call(current(), caller.string(ptr, length), []);
362
462
  },
363
- event_number(which) {
364
- const e = event();
365
- if (!e) return 0;
366
- switch (which) {
367
- case 0: return e.timeStamp || 0;
368
- case 1: return e.offsetX || 0;
369
- case 2: return e.offsetY || 0;
370
- case 3: return e.movementX || 0;
371
- case 4: return e.movementY || 0;
372
- case 5: return e.button || 0;
373
- default: return 0;
374
- }
463
+ event_is(node, ptr, length) {
464
+ return walk(current(), caller.string(ptr, length)) === known(node) ? 1 : 0;
465
+ },
466
+ read_number(node, ptr, length) {
467
+ return numberOf(read(known(node), caller.string(ptr, length)));
468
+ },
469
+ read_text(node, ptr, length, out) {
470
+ return given(textOf(read(known(node), caller.string(ptr, length))), out);
471
+ },
472
+ is(node, from, ptr, length) {
473
+ return walk(known(from), caller.string(ptr, length)) === known(node) ? 1 : 0;
375
474
  },
376
- images_loading() {
377
- return loading;
475
+ images_loading(tag) {
476
+ return loading.get(tag) || 0;
378
477
  },
379
- prevent_default() {
380
- const e = event();
381
- if (e && e.preventDefault) e.preventDefault();
478
+ element(ptr, length, node) {
479
+ const found = root.ownerDocument.getElementById(caller.string(ptr, length));
480
+ if (!found) return 0;
481
+ nodes.set(node, found);
482
+ return 1;
382
483
  },
383
484
  };
384
485
  };
@@ -1,34 +1,16 @@
1
- ;; The page: the host module "dom". A heard event carries the id and tag its listener was given. dom-event-flags gives
2
- ;; the bits dom-shift to dom-locked. dom-event-number takes one of dom-time, dom-x, dom-y, dom-movement-x,
3
- ;; dom-movement-y and dom-button, and gives that number for the current event.
4
- (provide dom-source dom-heard dom-image-loaded dom-image-failed
5
- dom-shift dom-control dom-alt dom-meta dom-checked dom-locked
6
- dom-time dom-x dom-y dom-movement-x dom-movement-y dom-button
7
- dom-apply dom-event-value dom-event-key dom-event-flags dom-event-number dom-prevent-default dom-images-loading)
1
+ ;; The page: the host module "dom". A heard event carries the id and tag its listener was given. A visibility listener
2
+ ;; hears when its element comes into the viewport or leaves it. dom-bind-element gives the element with an id a node
3
+ ;; number, for dom-element (std/web/dom.sub). The reads of the page are in std/web/dom.sub too. dom-images-loading
4
+ ;; counts the images loading for a tag.
5
+ (provide dom-source dom-heard dom-image-loaded dom-image-failed dom-apply dom-bind-element dom-images-loading)
8
6
 
9
7
  (const dom-source u32 1)
10
8
  (const dom-heard u32 1)
11
9
  (const dom-image-loaded u32 2)
12
10
  (const dom-image-failed u32 3)
13
11
 
14
- (const dom-shift u32 1)
15
- (const dom-control u32 2)
16
- (const dom-alt u32 4)
17
- (const dom-meta u32 8)
18
- (const dom-checked u32 16)
19
- (const dom-locked u32 32)
20
-
21
- (const dom-time i32 0)
22
- (const dom-x i32 1)
23
- (const dom-y i32 2)
24
- (const dom-movement-x i32 3)
25
- (const dom-movement-y i32 4)
26
- (const dom-button i32 5)
27
-
28
12
  (import dom-apply "dom" "apply" (i32 i32 ->))
29
- (import dom-event-value "dom" "event_value" (i32 -> i32))
30
- (import dom-event-key "dom" "event_key" (i32 -> i32))
31
- (import dom-event-flags "dom" "event_flags" (-> i32))
32
- (import dom-event-number "dom" "event_number" (i32 -> f64))
33
- (import dom-prevent-default "dom" "prevent_default" (->))
34
- (import dom-images-loading "dom" "images_loading" (-> i32))
13
+ (import images-loading "dom" "images_loading" (i32 -> i32))
14
+ (import dom-bind-element "dom" "element" (i32 i32 i32 -> i32))
15
+
16
+ (define (dom-images-loading [tag (ptr void)]) -> u32 (cast u32 (images-loading (cast i32 tag))))
@@ -1,5 +1,5 @@
1
1
  // The host module "files" (files.sub).
2
- import { errors } from '../errors.mjs';
2
+ import { errors, errorOf } from '../errors.mjs';
3
3
  import { readerSource } from '../streams.mjs';
4
4
 
5
5
  const utf8 = new TextEncoder();
@@ -43,7 +43,7 @@ export function files() {
43
43
  input.showPicker();
44
44
  } catch (e) {
45
45
  input.remove();
46
- caller.fail(err, e.message);
46
+ caller.fail(err, e.message, errorOf(e));
47
47
  return 0;
48
48
  }
49
49
  return id;
@@ -10,8 +10,12 @@
10
10
  (const files-picked u32 2)
11
11
  (const files-failed u32 3)
12
12
 
13
- (define (files-pick [multiple bool] [accept string] [tag (ptr void)] [err (ptr string)]) -> u32
14
- (cast u32 (pick multiple (cast i32 (-> accept bytes)) (cast i32 (-> accept length)) (cast i32 tag) (cast i32 err))))
13
+ (define (files-pick [multiple bool] [accept string] [tag (ptr void)] [id (ptr u32)] [err (ptr error)]) -> bool
14
+ (let ([why string] [n u32 0])
15
+ (set! n (cast u32 (pick multiple (cast i32 (-> accept bytes)) (cast i32 (-> accept length)) (cast i32 tag)
16
+ (cast i32 (& why)))))
17
+ (unless (== id 0) (set! (@ id) n))
18
+ (or (!= n 0) (host-failed! (& why) err))))
15
19
 
16
20
  (define (files-save [name string] [b (slice u8)])
17
21
  (save (cast i32 (-> name bytes)) (cast i32 (-> name length)) (cast i32 (-> b items)) (cast i32 (-> b length))))
@@ -13,7 +13,7 @@ export function resolvePath(cwd, path) {
13
13
  return `/${out.join('/')}`;
14
14
  }
15
15
 
16
- const missing = () => new Error('No such file or directory');
16
+ const missing = () => Object.assign(new Error('No such file or directory'), { code: 'ENOENT' });
17
17
  const unsupported = () => new Error('Operation not supported');
18
18
  const under = (dir) => (dir === '/' ? '/' : `${dir}/`);
19
19
 
@@ -142,7 +142,7 @@ export function pageFiles(files, cwd, grants) {
142
142
  },
143
143
  readLink(p) {
144
144
  info(at('read', p));
145
- throw new Error('Invalid argument');
145
+ throw Object.assign(new Error('Invalid argument'), { code: 'EINVAL' });
146
146
  },
147
147
  realPath(p) {
148
148
  const key = at('read', p);
@@ -180,7 +180,7 @@ export function pageFiles(files, cwd, grants) {
180
180
  const key = at(changes ? 'write' : 'read', p);
181
181
  if (how.read && changes) at('read', p);
182
182
  if (files.has(key)) {
183
- if (how.createNew) throw new Error('File already exists');
183
+ if (how.createNew) throw Object.assign(new Error('File already exists'), { code: 'EEXIST' });
184
184
  } else if (inside(key).length > 0) {
185
185
  throw new Error('Illegal operation on a directory');
186
186
  } else if (how.create || how.createNew) {
@@ -39,6 +39,8 @@ function unavailable(reason) {
39
39
  source: SOURCE,
40
40
  apply() { caller.trap(`gpu: WebGPU is not available: ${reason}`); },
41
41
  loading() { return 0; },
42
+ listen() { return 0; },
43
+ unlisten() {},
42
44
  });
43
45
  module.available = false;
44
46
  module.adapter = {};
@@ -71,17 +73,31 @@ export async function gpu(options = {}) {
71
73
  const layered = new Map();
72
74
  const bindGroups = new Map();
73
75
  const tables = { 1: surfaces, 2: shaders, 3: pipelines, 4: buffers, 5: textures, 6: samplers, 7: layouts, 8: bindGroups };
74
- let loading = 0;
76
+ const loading = new Map();
77
+ const counted = (t, n) => {
78
+ const k = (loading.get(t) || 0) + n;
79
+ if (k === 0) loading.delete(t);
80
+ else loading.set(t, k);
81
+ };
75
82
 
76
83
  let app = null;
77
84
  let caller = null;
78
- function event(kind, id, data) {
79
- if (caller) caller.event(kind, id, 0, kind === ERROR ? errors.invalid : 0, data);
85
+ function event(kind, id, data, to = 0) {
86
+ if (caller) caller.event(kind, id, to, kind === ERROR ? errors.invalid : 0, data);
87
+ }
88
+ const error = (id, message, to = 0) => event(ERROR, id, utf8.encode(message), to);
89
+
90
+ let tag = 0;
91
+ const listeners = new Map();
92
+ let nextListener = 1;
93
+
94
+ function deviceError(message) {
95
+ if (listeners.size === 0) error(0, message);
96
+ for (const t of listeners.values()) error(0, message, t);
80
97
  }
81
- const error = (id, message) => event(ERROR, id, utf8.encode(message));
82
98
 
83
- device.addEventListener('uncapturederror', (e) => error(0, e.error.message));
84
- device.lost.then((info) => error(0, `the device was lost (${info.reason}): ${info.message}`));
99
+ device.addEventListener('uncapturederror', (e) => deviceError(e.error.message));
100
+ device.lost.then((info) => deviceError(`the device was lost (${info.reason}): ${info.message}`));
85
101
 
86
102
  function get(kind, id) {
87
103
  const r = tables[kind].get(id);
@@ -100,7 +116,18 @@ export async function gpu(options = {}) {
100
116
  return v;
101
117
  };
102
118
 
103
- function run(memory, w, f, string) {
119
+ function run(memory, w, f, string, batchTag) {
120
+ tag = batchTag;
121
+ device.pushErrorScope('validation');
122
+ try {
123
+ commands(memory, w, f, string);
124
+ } finally {
125
+ tag = 0;
126
+ device.popErrorScope().then((e) => { if (e) error(0, e.message, batchTag); });
127
+ }
128
+ }
129
+
130
+ function commands(memory, w, f, string) {
104
131
  const encoder = device.createCommandEncoder();
105
132
  let pass = null;
106
133
  let computing = false;
@@ -137,6 +164,7 @@ export async function gpu(options = {}) {
137
164
  }
138
165
  case 2: {
139
166
  const id = w[i + 1];
167
+ const to = tag;
140
168
  device.pushErrorScope('validation');
141
169
  const module = device.createShaderModule({ label: `shader ${id}`, code: string(i + 2) });
142
170
  shaders.set(id, module);
@@ -144,7 +172,7 @@ export async function gpu(options = {}) {
144
172
  if (!e) return;
145
173
  const errors = (await module.getCompilationInfo()).messages.filter((m) => m.type === 'error');
146
174
  error(id, errors.length === 0 ? e.message : `shader ${id} does not compile:\n` +
147
- errors.map((m) => ` line ${m.lineNum}, column ${m.linePos}: ${m.message}`).join('\n'));
175
+ errors.map((m) => ` line ${m.lineNum}, column ${m.linePos}: ${m.message}`).join('\n'), to);
148
176
  });
149
177
  i += 4;
150
178
  break;
@@ -246,7 +274,7 @@ export async function gpu(options = {}) {
246
274
  usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
247
275
  });
248
276
  textures.set(id, { texture: placeholder, format: 'rgba8unorm', width: 1, height: 1, layers: 1 });
249
- loadTexture(id, name);
277
+ loadTexture(id, name, tag);
250
278
  i += 4;
251
279
  break;
252
280
  }
@@ -311,7 +339,7 @@ export async function gpu(options = {}) {
311
339
  entries.push({ binding: w[i], kind, id: w[i + 2], offset: w[i + 3], size: w[i + 4] });
312
340
  i += 5;
313
341
  }
314
- const description = { id, layout, entries, layers: layoutId !== 0 ? layered.get(layoutId) : new Set() };
342
+ const description = { id, layout, entries, layers: layoutId !== 0 ? layered.get(layoutId) : new Set(), tag };
315
343
  description.group = makeBindGroup(description);
316
344
  bindGroups.set(id, description);
317
345
  break;
@@ -347,17 +375,19 @@ export async function gpu(options = {}) {
347
375
  }
348
376
  case 14: {
349
377
  const id = w[i + 1];
378
+ const to = tag;
350
379
  const read = readTexture(encoder, id, w[i + 2], w[i + 3], w[i + 4], w[i + 5], w[i + 6], w[i + 7]);
351
- reads.push(() => read().then((bytes) => event(PIXELS, id, bytes), (e) => error(id, e.message)));
380
+ reads.push(() => read().then((bytes) => event(PIXELS, id, bytes, to), (e) => error(id, e.message, to)));
352
381
  i += 8;
353
382
  break;
354
383
  }
355
384
  case 15: {
356
385
  const id = w[i + 1];
386
+ const to = tag;
357
387
  const len = w[i + 3];
358
388
  const staging = device.createBuffer({ size: (len + 3) & ~3, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ });
359
389
  encoder.copyBufferToBuffer(get(4, id), w[i + 2], staging, 0, (len + 3) & ~3);
360
- reads.push(() => readStaging(staging, len).then((bytes) => event(BYTES, id, bytes), (e) => error(id, e.message)));
390
+ reads.push(() => readStaging(staging, len).then((bytes) => event(BYTES, id, bytes, to), (e) => error(id, e.message, to)));
361
391
  i += 4;
362
392
  break;
363
393
  }
@@ -499,10 +529,10 @@ export async function gpu(options = {}) {
499
529
  for (const read of reads) read();
500
530
  }
501
531
 
502
- function checked(what, id, make) {
532
+ function checked(what, id, make, to = tag) {
503
533
  device.pushErrorScope('validation');
504
534
  const resource = make();
505
- device.popErrorScope().then((e) => { if (e) error(id, `${what} is invalid: ${e.message}`); });
535
+ device.popErrorScope().then((e) => { if (e) error(id, `${what} is invalid: ${e.message}`, to); });
506
536
  return resource;
507
537
  }
508
538
 
@@ -529,8 +559,8 @@ export async function gpu(options = {}) {
529
559
  });
530
560
  }
531
561
 
532
- async function loadTexture(id, name) {
533
- loading++;
562
+ async function loadTexture(id, name, to) {
563
+ counted(to, 1);
534
564
  try {
535
565
  const bytes = app && app.files.get(resolvePath(app.cwd, name));
536
566
  const blob = bytes ? new Blob([bytes]) : await fetch(name).then((r) => {
@@ -564,15 +594,15 @@ export async function gpu(options = {}) {
564
594
  if (description.entries.some((e) => e.kind === 3 && e.id === id)) description.group = makeBindGroup(description);
565
595
  }
566
596
  t.texture.destroy();
567
- event(LOADED, id, new Uint8Array(0));
597
+ event(LOADED, id, new Uint8Array(0), to);
568
598
  } catch (e) {
569
- error(id, `cannot load the texture ${name}: ${e.message}`);
599
+ error(id, `cannot load the texture ${name}: ${e.message}`, to);
570
600
  } finally {
571
- loading--;
601
+ counted(to, -1);
572
602
  }
573
603
  }
574
604
 
575
- function makeBindGroup({ id, layout, entries, layers }) {
605
+ function makeBindGroup({ id, layout, entries, layers, tag: to }) {
576
606
  return checked(`bind group ${id}`, id, () => device.createBindGroup({
577
607
  label: `bind group ${id}`,
578
608
  layout,
@@ -590,7 +620,7 @@ export async function gpu(options = {}) {
590
620
  }
591
621
  return { binding: e.binding, resource };
592
622
  }),
593
- }));
623
+ }), to);
594
624
  }
595
625
 
596
626
  function readTexture(encoder, id, mip, layer, x, y, width, height) {
@@ -624,17 +654,25 @@ export async function gpu(options = {}) {
624
654
  app = theApp;
625
655
  return {
626
656
  source: SOURCE,
627
- apply(ptr, len) {
657
+ apply(ptr, len, t) {
628
658
  const memory = caller.memory();
629
659
  const w = ptr % 4 === 0
630
660
  ? new Uint32Array(memory.buffer, memory.byteOffset + ptr, len >>> 2)
631
661
  : new Uint32Array(memory.slice(ptr, ptr + len).buffer);
632
662
  const f = new Float32Array(w.buffer, w.byteOffset, w.length);
633
663
  const string = (at) => text.decode(memory.subarray(w[at], w[at] + w[at + 1]));
634
- run(memory, w, f, string);
664
+ run(memory, w, f, string, t);
665
+ },
666
+ loading(t) {
667
+ return loading.get(t) || 0;
668
+ },
669
+ listen(t) {
670
+ const id = nextListener++;
671
+ listeners.set(id, t);
672
+ return id;
635
673
  },
636
- loading() {
637
- return loading;
674
+ unlisten(id) {
675
+ listeners.delete(id);
638
676
  },
639
677
  };
640
678
  };
@@ -1,5 +1,8 @@
1
- ;; WebGPU: the host module "gpu".
2
- (provide gpu-source gpu-error gpu-loaded gpu-pixels gpu-bytes gpu-apply gpu-loading)
1
+ ;; WebGPU: the host module "gpu". gpu-apply runs a batch of commands, and the events it causes go to its tag: errors,
2
+ ;; loaded texture files and finished reads. gpu-loading counts the texture files loading for a tag. Each listener's tag
3
+ ;; hears the errors about the whole device, such as its loss. gpu-listen gives the listener's number, and gpu-unlisten
4
+ ;; takes it.
5
+ (provide gpu-source gpu-error gpu-loaded gpu-pixels gpu-bytes gpu-apply gpu-loading gpu-listen gpu-unlisten)
3
6
 
4
7
  (const gpu-source u32 2)
5
8
  (const gpu-error u32 1)
@@ -7,5 +10,16 @@
7
10
  (const gpu-pixels u32 3)
8
11
  (const gpu-bytes u32 4)
9
12
 
10
- (import gpu-apply "gpu" "apply" (i32 i32 ->))
11
- (import gpu-loading "gpu" "loading" (-> i32))
13
+ (import apply "gpu" "apply" (i32 i32 i32 ->))
14
+ (import loading "gpu" "loading" (i32 -> i32))
15
+ (import listen "gpu" "listen" (i32 -> i32))
16
+ (import unlisten "gpu" "unlisten" (i32 ->))
17
+
18
+ (define (gpu-apply [commands (slice u8)] [tag (ptr void)])
19
+ (apply (cast i32 (-> commands items)) (cast i32 (-> commands length)) (cast i32 tag)))
20
+
21
+ (define (gpu-loading [tag (ptr void)]) -> u32 (cast u32 (loading (cast i32 tag))))
22
+
23
+ (define (gpu-listen [tag (ptr void)]) -> u32 (cast u32 (listen (cast i32 tag))))
24
+
25
+ (define (gpu-unlisten [id u32]) (unlisten (cast i32 id)))