bmweb-cli 0.1.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 (72) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +351 -0
  3. package/dist/bmweb.js +2790 -0
  4. package/package.json +53 -0
  5. package/runtime/core/bestvm/codec.js +285 -0
  6. package/runtime/core/bestvm/environment.js +116 -0
  7. package/runtime/core/bestvm/executor.js +1483 -0
  8. package/runtime/core/bestvm/index.js +52 -0
  9. package/runtime/core/bestvm/machine.js +491 -0
  10. package/runtime/core/bestvm/operands.js +356 -0
  11. package/runtime/core/bestvm/registers.js +152 -0
  12. package/runtime/core/bestvm/write-guard.js +111 -0
  13. package/runtime/core/ipofile/compile.js +364 -0
  14. package/runtime/core/ipofile/decls.js +187 -0
  15. package/runtime/core/ipofile/emit.js +708 -0
  16. package/runtime/core/ipofile/exec.js +164 -0
  17. package/runtime/core/ipofile/lex.js +243 -0
  18. package/runtime/core/ipofile/parse.js +550 -0
  19. package/runtime/core/ipofile/pool.js +404 -0
  20. package/runtime/core/ipofile/walk.js +431 -0
  21. package/runtime/core/ipovm/builtin-helpers.js +182 -0
  22. package/runtime/core/ipovm/builtins-api.js +610 -0
  23. package/runtime/core/ipovm/builtins-screen.js +493 -0
  24. package/runtime/core/ipovm/builtins-table.js +166 -0
  25. package/runtime/core/ipovm/builtins-text.js +166 -0
  26. package/runtime/core/ipovm/emissions.js +138 -0
  27. package/runtime/core/ipovm/hosts.js +191 -0
  28. package/runtime/core/ipovm/operators.js +229 -0
  29. package/runtime/core/ipovm/structures.js +250 -0
  30. package/runtime/core/ipovm/suspensions.js +241 -0
  31. package/runtime/core/ipovm/tape.js +206 -0
  32. package/runtime/core/ipovm/values.js +241 -0
  33. package/runtime/core/ipovm/vm.js +1166 -0
  34. package/runtime/core/translate.js +526 -0
  35. package/runtime/core/webshim/api-router.js +592 -0
  36. package/runtime/core/webshim/bus.js +95 -0
  37. package/runtime/core/webshim/coding.js +82 -0
  38. package/runtime/core/webshim/data-fetch.js +66 -0
  39. package/runtime/core/webshim/exchange.js +288 -0
  40. package/runtime/core/webshim/framing.js +331 -0
  41. package/runtime/core/webshim/install.js +30 -0
  42. package/runtime/core/webshim/job-runner.js +319 -0
  43. package/runtime/core/webshim/native-bus.js +108 -0
  44. package/runtime/core/webshim/timers.js +82 -0
  45. package/runtime/core/webshim/trace.js +205 -0
  46. package/runtime/core/webshim/transport-base.js +128 -0
  47. package/runtime/core/webshim/variant-resolver.js +249 -0
  48. package/runtime/core/webshim/web-serial-bus.js +734 -0
  49. package/runtime/home/bmweb-home.ips +76 -0
  50. package/runtime/home/bmweb.h +26 -0
  51. package/runtime/screens/activations.js +258 -0
  52. package/runtime/screens/garage/diff.js +331 -0
  53. package/runtime/screens/garage/share.js +276 -0
  54. package/runtime/screens/garage/store.js +547 -0
  55. package/runtime/screens/ipo-runtime/cells.js +176 -0
  56. package/runtime/screens/ipo-runtime/dialogs.js +254 -0
  57. package/runtime/screens/ipo-runtime/home.js +358 -0
  58. package/runtime/screens/ipo-runtime/open.js +393 -0
  59. package/runtime/screens/ipo-runtime/paint-grid.js +106 -0
  60. package/runtime/screens/ipo-runtime/paint-modern.js +424 -0
  61. package/runtime/screens/ipo-runtime/print.js +281 -0
  62. package/runtime/screens/ipo-runtime/program.js +1337 -0
  63. package/runtime/screens/ipo-runtime/protocol.js +464 -0
  64. package/runtime/screens/ipo-runtime/script-scan.js +225 -0
  65. package/runtime/screens/ipo-runtime/translate-sets.js +130 -0
  66. package/runtime/screens/ipo-runtime/ui.js +249 -0
  67. package/runtime/screens/ipo-runtime/wire-policy.js +113 -0
  68. package/runtime/screens/ir.js +324 -0
  69. package/runtime/screens/search/data.js +153 -0
  70. package/runtime/screens/search/match.js +285 -0
  71. package/runtime/screens/search/open.js +66 -0
  72. package/runtime/vendor/fflate.min.js +1 -0
@@ -0,0 +1,404 @@
1
+ /**
2
+ * @file Constant pool of an .IPO, decoded in the browser.
3
+ *
4
+ * Port of tools/decompile/ipo_disasm.py's find_pool / _tok_pool /
5
+ * _find_pool_suffix. The pool is the body of a declaration literally named
6
+ * "Constant Data": a typed literal stream, one entry per source literal, in
7
+ * emission order. The code stream refers to entries by index, so getting the
8
+ * pool's START byte right is what makes every `const` token correct -- a start
9
+ * one entry early shifts every index in the file.
10
+ *
11
+ * Kept byte-for-byte faithful to the Python because the shipped
12
+ * data/inpa-ir/<stem>.ipoexec.json dumps are produced by that code: a dropped
13
+ * script has to decode to the same tokens the shipped ones did, or the same
14
+ * runtime would run two different programs depending on where the file came
15
+ * from.
16
+ */
17
+
18
+ /** Bytes a pool string may contain: everything printable-ish except newline. */
19
+ const IPOF_PRINTABLE_LO = 0x09;
20
+
21
+ /**
22
+ * Whether a byte may appear inside a pool string.
23
+ *
24
+ * @param {number} c Byte value.
25
+ * @returns {boolean} True when the byte is allowed in a string entry.
26
+ */
27
+ function ipofPrintable(c) {
28
+ return c >= IPOF_PRINTABLE_LO && c !== 0x0a;
29
+ }
30
+
31
+ /**
32
+ * Read a little-endian unsigned integer from a byte array.
33
+ *
34
+ * @param {Uint8Array} data The file bytes.
35
+ * @param {number} at Offset of the first byte.
36
+ * @param {number} n Byte count (1..4).
37
+ * @returns {number} The value.
38
+ */
39
+ function ipofUint(data, at, n) {
40
+ let v = 0;
41
+ for (let k = n - 1; k >= 0; k -= 1) v = v * 256 + data[at + k];
42
+ return v;
43
+ }
44
+
45
+ /**
46
+ * Decode `n` bytes as latin-1 text, the encoding INPA's own tooling uses.
47
+ *
48
+ * @param {Uint8Array} data The file bytes.
49
+ * @param {number} lo First byte offset.
50
+ * @param {number} hi End offset, exclusive.
51
+ * @returns {string} The decoded text.
52
+ */
53
+ function ipofLatin1(data, lo, hi) {
54
+ let s = '';
55
+ for (let i = lo; i < hi; i += 1) s += String.fromCharCode(data[i]);
56
+ return s;
57
+ }
58
+
59
+ /**
60
+ * Index of the next newline in `data` within a window, or -1.
61
+ *
62
+ * @param {Uint8Array} data The file bytes.
63
+ * @param {number} from First offset to look at.
64
+ * @param {number} to End offset, exclusive.
65
+ * @returns {number} The offset of the newline, or -1.
66
+ */
67
+ function ipofFindNl(data, from, to) {
68
+ const end = Math.min(to, data.length);
69
+ for (let i = Math.max(0, from); i < end; i += 1)
70
+ if (data[i] === 0x0a) return i;
71
+ return -1;
72
+ }
73
+
74
+ /**
75
+ * Read a 64-bit float, the pool's `real` entry.
76
+ *
77
+ * @param {Uint8Array} data The file bytes.
78
+ * @param {number} at Offset of the first byte.
79
+ * @returns {number} The value.
80
+ */
81
+ function ipofFloat64(data, at) {
82
+ const dv = new DataView(data.buffer, data.byteOffset + at, 8);
83
+ return dv.getFloat64(0, true);
84
+ }
85
+
86
+ /**
87
+ * Tokenize `data[start:end]` as a pure literal stream.
88
+ *
89
+ * Entry types match the Python: 06 string, 03 int, 05 real, 01 bool, 02 byte,
90
+ * 04 long. Each entry becomes `[typeLetter, value]` exactly as the Python
91
+ * tuples do, because the walker inlines both fields into every const token.
92
+ *
93
+ * @param {Uint8Array} data The file bytes.
94
+ * @param {number} start First offset.
95
+ * @param {number} end End offset, exclusive.
96
+ * @returns {Array<Array>|null} The entries, or null when the region is not a
97
+ * clean literal stream.
98
+ */
99
+ function ipofTokPool(data, start, end) {
100
+ const entries = [];
101
+ let i = start;
102
+ while (i < end) {
103
+ const t = data[i];
104
+ if (t === 0x06) {
105
+ // No length cap: one corpus pool holds a 377-byte string, and the
106
+ // declaration's entry count validates the walk end to end anyway.
107
+ const j = ipofFindNl(data, i + 1, data.length);
108
+ if (j < 0) return null;
109
+ for (let k = i + 1; k < j; k += 1)
110
+ if (!ipofPrintable(data[k])) return null;
111
+ entries.push(['s', ipofLatin1(data, i + 1, j)]);
112
+ i = j + 1;
113
+ } else if (t === 0x03) {
114
+ if (i + 3 > end) return null;
115
+ entries.push(['i', ipofUint(data, i + 1, 2)]);
116
+ i += 3;
117
+ } else if (t === 0x05) {
118
+ if (i + 9 > end) return null;
119
+ entries.push(['d', ipofFloat64(data, i + 1)]);
120
+ i += 9;
121
+ } else if (t === 0x01) {
122
+ if (i + 2 > end) return null;
123
+ entries.push(['b', data[i + 1]]);
124
+ i += 2;
125
+ } else if (t === 0x02) {
126
+ if (i + 2 > end) return null;
127
+ entries.push(['y', data[i + 1]]);
128
+ i += 2;
129
+ } else if (t === 0x04) {
130
+ if (i + 5 > end) return null;
131
+ entries.push(['l', ipofUint(data, i + 1, 4)]);
132
+ i += 5;
133
+ } else {
134
+ return null;
135
+ }
136
+ }
137
+ return entries;
138
+ }
139
+
140
+ /**
141
+ * Every offset at which the byte pattern occurs.
142
+ *
143
+ * @param {Uint8Array} data The file bytes.
144
+ * @param {number[]} pat The bytes to find.
145
+ * @param {number} from First offset to search from.
146
+ * @param {number} to End offset, exclusive.
147
+ * @returns {number[]} The matching offsets.
148
+ */
149
+ function ipofFindAll(data, pat, from, to) {
150
+ const out = [];
151
+ const end = Math.min(to, data.length) - pat.length;
152
+ for (let i = from; i <= end; i += 1) {
153
+ let ok = true;
154
+ for (let k = 0; k < pat.length; k += 1) {
155
+ if (data[i + k] !== pat[k]) {
156
+ ok = false;
157
+ break;
158
+ }
159
+ }
160
+ if (ok) out.push(i);
161
+ }
162
+ return out;
163
+ }
164
+
165
+ /** The literal bytes of the pool declaration's name, ".Constant Data\n". */
166
+ const IPOF_POOL_NAME = Array.from('Constant Data').map((c) => c.charCodeAt(0));
167
+
168
+ /**
169
+ * Match the pool declaration header at a candidate name offset.
170
+ *
171
+ * The declaration has the same grammar as every proc:
172
+ * `<type> <name> \n <u32 id> \n [version] \n \x00 <u16 count>`; the u16 is the
173
+ * ENTRY COUNT, which is what validates the walk.
174
+ *
175
+ * @param {Uint8Array} data The file bytes.
176
+ * @param {number} nameAt Offset of the "C" of "Constant Data".
177
+ * @returns {{start: number, declared: number}|null} Body start and declared
178
+ * entry count, or null when the bytes are not a declaration.
179
+ */
180
+ function ipofPoolHeader(data, nameAt) {
181
+ let i = nameAt + IPOF_POOL_NAME.length;
182
+ if (data[i] !== 0x0a) return null;
183
+ i += 1;
184
+ if (i + 4 > data.length) return null;
185
+ i += 4; // the u32 id
186
+ if (data[i] !== 0x0a) return null;
187
+ i += 1;
188
+ // the optional version string: up to 32 printable bytes, then a newline
189
+ let j = i;
190
+ const cap = Math.min(data.length, i + 33);
191
+ while (j < cap && data[j] !== 0x0a) {
192
+ if (data[j] < 0x20 || data[j] > 0x7e) return null;
193
+ j += 1;
194
+ }
195
+ if (j >= cap || data[j] !== 0x0a) return null;
196
+ i = j + 1;
197
+ if (data[i] !== 0x00 || i + 3 > data.length) return null;
198
+ return { start: i + 3, declared: ipofUint(data, i + 1, 2) };
199
+ }
200
+
201
+ /**
202
+ * The constant pool: its start offset and its entries.
203
+ *
204
+ * The LAST "Constant Data" declaration wins, as in the Python -- earlier
205
+ * matches can be a string inside the code region.
206
+ *
207
+ * @param {Uint8Array} data The file bytes.
208
+ * @returns {{start: number|null, entries: Array<Array>}} Pool start (null when
209
+ * the file has none) and the decoded entries.
210
+ */
211
+ function ipofFindPool(data) {
212
+ const hits = ipofFindAll(data, IPOF_POOL_NAME, 0, data.length);
213
+ for (let k = hits.length - 1; k >= 0; k -= 1) {
214
+ const hdr = ipofPoolHeader(data, hits[k]);
215
+ if (!hdr) continue;
216
+ const entries = ipofTokPool(data, hdr.start, data.length);
217
+ if (entries && (entries.length & 0xffff) === hdr.declared) {
218
+ return { start: hdr.start, entries };
219
+ }
220
+ break; // the last declaration is the pool
221
+ }
222
+ return ipofFindPoolSuffix(data);
223
+ }
224
+
225
+ /**
226
+ * Fallback: the longest EOF-suffix that tokenizes as literals AND whose
227
+ * indexing agrees with the code's own references.
228
+ *
229
+ * A too-early start prepends fake entries and shifts every index, so
230
+ * candidates are scored on a semantic anchor: at a sampled
231
+ * `text(row, col, str)` call site, pool[r..r+2] must type-match (int,int,str).
232
+ *
233
+ * @param {Uint8Array} data The file bytes.
234
+ * @returns {{start: number|null, entries: Array<Array>}} Pool start and entries.
235
+ */
236
+ function ipofFindPoolSuffix(data) {
237
+ const n = data.length;
238
+ // ok[i]: data[i:] tokenizes cleanly to EOF. Computed descending, O(n).
239
+ const ok = new Uint8Array(n + 1);
240
+ ok[n] = 1;
241
+ for (let i = n - 1; i >= 0; i -= 1) {
242
+ const t = data[i];
243
+ if (t === 0x06) {
244
+ const j = ipofFindNl(data, i + 1, i + 221);
245
+ if (j >= 0) {
246
+ let clean = true;
247
+ for (let k = i + 1; k < j; k += 1)
248
+ if (!ipofPrintable(data[k])) {
249
+ clean = false;
250
+ break;
251
+ }
252
+ if (clean) ok[i] = ok[j + 1];
253
+ }
254
+ } else if (t === 0x03 && i + 3 <= n) ok[i] = ok[i + 3];
255
+ else if (t === 0x05 && i + 9 <= n) ok[i] = ok[i + 9];
256
+ else if ((t === 0x01 || t === 0x02) && i + 2 <= n) ok[i] = ok[i + 2];
257
+ else if (t === 0x04 && i + 5 <= n) ok[i] = ok[i + 5];
258
+ }
259
+ let good = null;
260
+ let run = 0;
261
+ for (let i = n - 1; i >= 0; i -= 1) {
262
+ if (ok[i]) {
263
+ good = i;
264
+ run = 0;
265
+ } else {
266
+ run += 1;
267
+ if (good !== null && run > 6000) break;
268
+ }
269
+ }
270
+ if (good === null) return { start: null, entries: [] };
271
+ // candidate boundaries: the offset of every entry from `good` forward
272
+ const offs = [];
273
+ let i = good;
274
+ while (i < n) {
275
+ offs.push(i);
276
+ const t = data[i];
277
+ if (t === 0x06) i = ipofFindNl(data, i + 1, n) + 1;
278
+ else if (t === 0x03) i += 3;
279
+ else if (t === 0x05) i += 9;
280
+ else if (t === 0x01 || t === 0x02) i += 2;
281
+ else if (t === 0x04) i += 5;
282
+ else break;
283
+ }
284
+ // sample text-call anchors in the code region [0, good)
285
+ const anchors = [];
286
+ for (let p = 0; p + 15 <= good; p += 1) {
287
+ if (data[p] !== 0x01 || data[p + 1] !== 0x01) continue;
288
+ if (data[p + 4] !== 0x01 || data[p + 5] !== 0x01) continue;
289
+ if (data[p + 8] !== 0x01 || data[p + 9] !== 0x01) continue;
290
+ if (data[p + 12] !== 0x0c || data[p + 13] !== 0x81) continue;
291
+ if (data[p + 14] !== 0x48 || data[p + 15] !== 0x00) continue;
292
+ const r0 = ipofUint(data, p + 2, 2);
293
+ const r1 = ipofUint(data, p + 6, 2);
294
+ const r2 = ipofUint(data, p + 10, 2);
295
+ if (r1 === r0 + 1 && r2 === r0 + 2) anchors.push(r0);
296
+ }
297
+ const entries = ipofTokPool(data, good, n);
298
+ if (!entries) return { start: null, entries: [] };
299
+ const sample = anchors.slice(0, 40);
300
+ let best = null;
301
+ for (let k = 0; k < entries.length; k += 1) {
302
+ let score = 0;
303
+ for (const r of sample) {
304
+ const j = k + r;
305
+ if (
306
+ j + 2 < entries.length &&
307
+ entries[j][0] === 'i' &&
308
+ entries[j + 1][0] === 'i' &&
309
+ entries[j + 2][0] === 's'
310
+ )
311
+ score += 1;
312
+ }
313
+ if (best === null || score > best.score) best = { score, k };
314
+ if (best.score === sample.length && best.score > 0) break;
315
+ }
316
+ const k = best.k;
317
+ return { start: k < offs.length ? offs[k] : good, entries: entries.slice(k) };
318
+ }
319
+
320
+ /**
321
+ * Where PROC CODE ends: at the trailing metadata region, not at the pool.
322
+ *
323
+ * Nearly every file closes its last proc then carries `11 "Global Data"` (the
324
+ * global-slot type table) and `12 "Constant Data"` before the pool proper.
325
+ * Counting that region as the last proc's body buries a file under "unknown
326
+ * code" that was never code.
327
+ *
328
+ * @param {Uint8Array} data The file bytes.
329
+ * @param {number|null} ps The pool start, or null.
330
+ * @returns {number} The end of the code region.
331
+ */
332
+ function ipofCodeEnd(data, ps) {
333
+ const end = ps || data.length;
334
+ const pat = [0x11].concat(
335
+ Array.from('Global Data').map((c) => c.charCodeAt(0)),
336
+ [0x0a]
337
+ );
338
+ const hits = ipofFindAll(data, pat, 0, end);
339
+ return hits.length ? hits[0] : end;
340
+ }
341
+
342
+ /**
343
+ * The flat const-slot list of a coding dispatcher (the `A_<cabd>` dialect), or
344
+ * null when the file is not one.
345
+ *
346
+ * These share the bytecode with the screen scripts but have no screen-style
347
+ * pool, and their entries are tagged differently (04 string, 02 int, 01 bool).
348
+ * Every record from the header on is a slot INCLUDING the include names and
349
+ * the import signatures: `const n` indexes the whole stream, so skipping them
350
+ * shifts every high index.
351
+ *
352
+ * @param {Uint8Array} data The file bytes.
353
+ * @returns {Array<Array>|null} The entries, or null.
354
+ */
355
+ function ipofAPool(data) {
356
+ const marker = [0x12].concat(
357
+ Array.from('Constant Data').map((c) => c.charCodeAt(0)),
358
+ [0x0a]
359
+ );
360
+ const hits = ipofFindAll(data, marker, 0, data.length);
361
+ if (!hits.length) return null;
362
+ let p = hits[0] + marker.length;
363
+ if (p + 9 > data.length) return null;
364
+ p += 4; // the u32 id
365
+ while (p < data.length && data[p] === 0x0a) p += 1;
366
+ if (p < data.length && data[p] === 0x00) p += 1;
367
+ p += 2; // the declared count, not relied on
368
+ const pool = [];
369
+ while (p < data.length) {
370
+ const t = data[p];
371
+ if (t === 0x0a) {
372
+ p += 1;
373
+ continue;
374
+ } // a separator, not a slot
375
+ if (t === 0x04) {
376
+ const e = ipofFindNl(data, p + 1, data.length);
377
+ if (e < 0) break;
378
+ pool.push(['s', ipofLatin1(data, p + 1, e)]);
379
+ p = e + 1;
380
+ } else if (t === 0x02) {
381
+ pool.push(['i', ipofUint(data, p + 1, 2)]);
382
+ p += 3;
383
+ } else if (t === 0x01) {
384
+ pool.push(['b', data[p + 1]]);
385
+ p += 2;
386
+ } else {
387
+ break; // the trailing DLL signature table
388
+ }
389
+ }
390
+ return pool;
391
+ }
392
+
393
+ if (typeof module !== 'undefined' && module.exports) {
394
+ module.exports = {
395
+ ipofFindPool,
396
+ ipofCodeEnd,
397
+ ipofTokPool,
398
+ ipofUint,
399
+ ipofLatin1,
400
+ ipofFindNl,
401
+ ipofFindAll,
402
+ ipofAPool,
403
+ };
404
+ }