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,364 @@
1
+ /**
2
+ * @file Compiling an INPA source script (.IPS / .SRC) to a runnable exec.
3
+ *
4
+ * The front door over lex / parse / emit: it resolves `#include`s from the
5
+ * files the user dropped alongside the script, builds the builtin-number table
6
+ * the emitter needs, and reports what it could not do with the line to look at.
7
+ *
8
+ * INCLUDES ARE THE USER'S TO SUPPLY. A header declares the builtins
9
+ * (`extern name(in: type a, out: type b)`) and the .SRC libraries carry
10
+ * function bodies the compiled file must contain, but those are BMW's files:
11
+ * they are not in this repo and never ship with it. A script that needs one it
12
+ * did not get is a clear error naming the missing include, not a silent
13
+ * half-compile.
14
+ */
15
+
16
+ /** Builtin name -> number, inverted from the walker's table. */
17
+ const IPOF_BUILTIN_NUMS = (() => {
18
+ const out = {};
19
+ for (const k of Object.keys(IPOF_BUILTINS)) out[IPOF_BUILTINS[k]] = Number(k);
20
+ return out;
21
+ })();
22
+
23
+ /**
24
+ * The aliases the decompiler prints for builtins the walker leaves numbered.
25
+ *
26
+ * A round trip has to accept its own output, so every name the decompiler can
27
+ * emit must compile back to the number it came from.
28
+ * @type {Object<string, number>}
29
+ */
30
+ const IPOF_ALIAS_NUMS = {
31
+ settimer: 0x09,
32
+ testtimer: 0x0a,
33
+ control: 0x12,
34
+ stop: 0x14,
35
+ getapistring: 0x15,
36
+ togglelist: 0x16,
37
+ setcolor: 0x1a,
38
+ stringtoint: 0x21,
39
+ hexconvert: 0x22,
40
+ strcat: 0x23,
41
+ input2int: 0x47,
42
+ blankscreen: 0x51,
43
+ userboxclear: 0x57,
44
+ userboxsetcolor: 0x58,
45
+ INP1apiResultReal: 0x74,
46
+ callstatemachine: 0x07,
47
+ returnstatemachine: 0x08,
48
+ setjobstatus: 0x0b,
49
+ delay: 0x1b,
50
+ inputnum: 0x39,
51
+ inputtext: 0x3a,
52
+ ftextclear: 0x4f,
53
+ clearrect: 0x50,
54
+ SPSInit: 0x92,
55
+ SPSLeseVonSPS: 0x94,
56
+ SPSSendeAnSPS: 0x95,
57
+ ApiJobFsLesenFAB: 0x97,
58
+ ApiResultFsLesenFAB: 0x98,
59
+ ELDIOpenStartDialog: 0x99,
60
+ setitemrepeat: 0xa1,
61
+ };
62
+
63
+ /**
64
+ * Every builtin name the compiler can place, name -> number.
65
+ * @returns {Object<string, number>} The table.
66
+ */
67
+ function ipofBuiltinTable() {
68
+ const out = Object.assign({}, IPOF_BUILTIN_NUMS, IPOF_ALIAS_NUMS);
69
+ // a `builtin_2a` spelling is what the decompiler prints for anything with no
70
+ // name at all, and it has to compile back to its own number
71
+ for (let n = 0; n < 256; n += 1) out[ipofBuiltinName(n)] = n;
72
+ return out;
73
+ }
74
+
75
+ /**
76
+ * The parameter modes of every prototype an include declares.
77
+ *
78
+ * A builtin's `out:` / `inout:` parameters are passed BY REFERENCE, and the
79
+ * bytecode says so with a different opcode: an out argument compiles to a
80
+ * procref carrying the slot, not to an ordinary push. Without the prototypes
81
+ * there is no way to know which argument that is, so a script whose headers
82
+ * were not supplied cannot be compiled faithfully -- which is the other reason
83
+ * the includes are required rather than optional.
84
+ *
85
+ * @param {Object<string, string>} files The dropped files, name -> text.
86
+ * @returns {Object<string, string[]>} Function name -> parameter modes.
87
+ */
88
+ function ipofScanPrototypes(files) {
89
+ const out = {};
90
+ for (const name of Object.keys(files || {})) {
91
+ const text = String(files[name])
92
+ .replace(/\/\*[\s\S]*?\*\//g, '')
93
+ .replace(/\/\/[^\n]*/g, '');
94
+ const re = /(?:extern|import32|import)\b[^(;]*?(\w+)\s*\(([^)]*)\)\s*;/g;
95
+ let m = re.exec(text);
96
+ while (m) {
97
+ const modes = [];
98
+ const pre = /\b(in|out|inout)\s*:/g;
99
+ let p = pre.exec(m[2]);
100
+ while (p) {
101
+ modes.push(p[1]);
102
+ p = pre.exec(m[2]);
103
+ }
104
+ if (modes.length && !out[m[1]]) out[m[1]] = modes;
105
+ m = re.exec(text);
106
+ }
107
+ }
108
+ return out;
109
+ }
110
+
111
+ /**
112
+ * The DLL functions the includes import, in declaration order.
113
+ *
114
+ * `import32 "C" lib "kernel32::GetPrivateProfileStringA" GetPrivateProfileString`
115
+ * binds a Windows entry point to a name the script may call. The compiled file
116
+ * numbers these per file and calls them by index, so the order they are
117
+ * declared in IS the numbering -- a name the script calls but no include
118
+ * imports has no number, and cannot be compiled.
119
+ *
120
+ * @param {Object<string, string>} files The dropped files, name -> text.
121
+ * @returns {string[]} The imported names by index, carrying an `alias` map of
122
+ * DLL entry-point names to the script-side name that holds each slot.
123
+ */
124
+ function ipofScanImports(files) {
125
+ const out = [];
126
+ // entry-point name -> the script-side name that holds the slot
127
+ out.alias = {};
128
+ for (const name of Object.keys(files || {})) {
129
+ const text = String(files[name])
130
+ .replace(/\/\*[\s\S]*?\*\//g, '')
131
+ .replace(/\/\/[^\n]*/g, '');
132
+ // `import32 "C" lib "kernel32::Entry" Name (...)`: the name is what follows
133
+ // the LIB string, so the calling convention's own quoted "C" has to be
134
+ // stepped over rather than matched non-greedily past.
135
+ const re = /import32\b[^\n]*?\blib\s+"([^"]*)"\s+(\w+)/g;
136
+ let m = re.exec(text);
137
+ while (m) {
138
+ // Each import takes ONE slot -- the index is the number the call carries.
139
+ // The DLL's own entry point is recorded as an alias of that slot, not as
140
+ // a second one: a source the user wrote calls the script-side name, while
141
+ // a source recovered from a compiled file names the entry point, because
142
+ // that is all the import table holds. Both must reach the same number.
143
+ const entry = m[1].indexOf('::') >= 0 ? m[1].split('::')[1] : '';
144
+ if (out.indexOf(m[2]) < 0) {
145
+ if (entry && entry !== m[2]) out.alias[entry] = m[2];
146
+ out.push(m[2]);
147
+ }
148
+ m = re.exec(text);
149
+ }
150
+ }
151
+ return out;
152
+ }
153
+
154
+ /**
155
+ * The include names a source text asks for, in order.
156
+ *
157
+ * @param {string} src The source text.
158
+ * @returns {string[]} The include names.
159
+ */
160
+ function ipofScanIncludes(src) {
161
+ const out = [];
162
+ const re = /^[ \t]*#\s*include\s*"([^"]+)"/gm;
163
+ let m = re.exec(src);
164
+ while (m) {
165
+ if (out.indexOf(m[1]) < 0) out.push(m[1]);
166
+ m = re.exec(src);
167
+ }
168
+ return out;
169
+ }
170
+
171
+ /**
172
+ * Whether an include only declares things (no bodies to compile in).
173
+ *
174
+ * A `.h` is prototypes and globals; a `.SRC` library carries function bodies
175
+ * the compiled file must contain, so it is concatenated instead.
176
+ *
177
+ * @param {string} name The include name.
178
+ * @returns {boolean} True for a declarations-only header.
179
+ */
180
+ function ipofIsDeclOnly(name) {
181
+ return /\.h$/i.test(name);
182
+ }
183
+
184
+ /**
185
+ * Resolve a script and its includes into one source text.
186
+ *
187
+ * `.h` headers contribute their globals (so slot numbering matches) and their
188
+ * prototypes; `.SRC` libraries contribute everything, bodies included, ahead of
189
+ * the main file -- which is the order the compiled artifacts show.
190
+ *
191
+ * @param {string} src The main script's text.
192
+ * @param {Object<string, string>} files The dropped files, name -> text; names
193
+ * are matched case-insensitively, as INPA's own tooling matches them.
194
+ * @returns {{text: string, used: string[], missing: string[]}} The combined
195
+ * source, the includes that resolved, and the ones that did not.
196
+ */
197
+ function ipofResolveIncludes(src, files) {
198
+ const lower = {};
199
+ for (const k of Object.keys(files || {})) lower[k.toLowerCase()] = files[k];
200
+ /**
201
+ * An include's text, matching on its base name.
202
+ *
203
+ * Scripts include their headers by relative path (`..\sgdat\inpa.h`), and a
204
+ * dropped file has no path at all -- only its name. Matching the base name
205
+ * is what lets the user drop the header next to the script, which is the
206
+ * only way they can supply it.
207
+ *
208
+ * @param {string} name The include name as written.
209
+ * @returns {string|undefined} The text, or undefined.
210
+ */
211
+ const find = (name) => {
212
+ const key = name.toLowerCase();
213
+ if (lower[key] !== undefined) return lower[key];
214
+ const base = key.split(/[\\/]/).pop();
215
+ return lower[base];
216
+ };
217
+ const used = [];
218
+ const missing = [];
219
+ const seen = {};
220
+ const parts = [];
221
+ /**
222
+ * Pull one include in, depth first, so a nested include's globals land first.
223
+ * @param {string} name The include name.
224
+ * @returns {void}
225
+ */
226
+ const pull = (name) => {
227
+ const key = name.toLowerCase().split(/[\\/]/).pop();
228
+ if (seen[key]) return;
229
+ seen[key] = true;
230
+ const text = find(name);
231
+ if (text === undefined) {
232
+ missing.push(name);
233
+ return;
234
+ }
235
+ used.push(name);
236
+ for (const nested of ipofScanIncludes(text)) pull(nested);
237
+ parts.push(ipofStripPrototypes(text, ipofIsDeclOnly(name)));
238
+ };
239
+ for (const name of ipofScanIncludes(src)) pull(name);
240
+ parts.push(src.replace(/^[ \t]*#\s*include\s*"[^"]+"[ \t]*$/gm, ''));
241
+ return { text: parts.join('\n'), used, missing };
242
+ }
243
+
244
+ /**
245
+ * Drop the `extern` prototype lines from an include.
246
+ *
247
+ * A prototype declares a builtin's signature for the real compiler; this one
248
+ * numbers builtins from its own tables, so the lines are noise the parser
249
+ * would reject. A declarations-only header keeps its globals, which do matter:
250
+ * they occupy the leading slots.
251
+ *
252
+ * @param {string} text The include's text.
253
+ * @param {boolean} declOnly Whether the include is a `.h`.
254
+ * @returns {string} The text the parser can take.
255
+ */
256
+ function ipofStripPrototypes(text, declOnly) {
257
+ // Newlines are preserved so a later error still names the line the user has
258
+ // in front of them. A prototype runs to its semicolon and may span lines --
259
+ // the import32 declarations put one parameter per line.
260
+ let out = text.replace(
261
+ /^[ \t]*(?:extern|import32|import)\b[\s\S]*?;/gm,
262
+ (m) => m.replace(/[^\n]/g, ' ')
263
+ );
264
+ out = out.replace(/^[ \t]*#\s*include\s*"[^"]+"[ \t]*$/gm, '');
265
+ void declOnly;
266
+ return out;
267
+ }
268
+
269
+ /**
270
+ * Compile an INPA source script into a runnable exec.
271
+ *
272
+ * @param {string} src The script's text.
273
+ * @param {Object} [opts] Options.
274
+ * @param {string} [opts.name] The stem to report as the exec's ecu.
275
+ * @param {Object<string, string>} [opts.files] Includes the user supplied,
276
+ * name -> text.
277
+ * @param {string[]} [opts.imports] DLL import names by index, when known.
278
+ * @returns {{ok: boolean, exec: Object|null, errors: Object[], includes:
279
+ * string[], missing: string[]}} The result; `errors` carries a `line` and a
280
+ * ready-to-show `text` for each problem.
281
+ */
282
+ function ipofCompileSource(src, opts) {
283
+ const o = opts || {};
284
+ const r = ipofResolveIncludes(src, o.files || {});
285
+ const errors = [];
286
+ for (const m of r.missing) {
287
+ errors.push({
288
+ line: 0,
289
+ message: `missing include "${m}" -- drop it alongside the script`,
290
+ text: `missing include "${m}" -- drop it alongside the script`,
291
+ });
292
+ }
293
+ if (errors.length) {
294
+ return {
295
+ ok: false,
296
+ exec: null,
297
+ errors,
298
+ includes: r.used,
299
+ missing: r.missing,
300
+ };
301
+ }
302
+ let ast;
303
+ try {
304
+ ast = ipofParse(r.text);
305
+ } catch (err) {
306
+ if (!(err instanceof IpofSyntaxError)) throw err;
307
+ errors.push({ line: err.line, message: err.message, text: err.message });
308
+ return {
309
+ ok: false,
310
+ exec: null,
311
+ errors,
312
+ includes: r.used,
313
+ missing: r.missing,
314
+ };
315
+ }
316
+ const c = new IpofCompiler(ast, {
317
+ builtins: ipofBuiltinTable(),
318
+ // the caller's table (a compiled file's own) wins; failing that, what the
319
+ // includes declare, which is the only source a bare .IPS has
320
+ imports:
321
+ o.imports && o.imports.length
322
+ ? o.imports
323
+ : ipofScanImports(o.files || {}),
324
+ protos: ipofScanPrototypes(o.files || {}),
325
+ });
326
+ c.ecu = o.name || 'script';
327
+ const exec = c.compile();
328
+ if (exec.errors.length) {
329
+ return {
330
+ ok: false,
331
+ exec: null,
332
+ errors: exec.errors,
333
+ includes: r.used,
334
+ missing: r.missing,
335
+ };
336
+ }
337
+ return {
338
+ ok: true,
339
+ exec: {
340
+ ecu: exec.ecu,
341
+ procs: exec.procs,
342
+ byid: exec.byid,
343
+ coding: false,
344
+ includes: ast.includes,
345
+ imports: {},
346
+ },
347
+ errors: [],
348
+ includes: r.used,
349
+ missing: [],
350
+ };
351
+ }
352
+
353
+ if (typeof module !== 'undefined' && module.exports) {
354
+ module.exports = {
355
+ ipofCompileSource,
356
+ ipofResolveIncludes,
357
+ ipofScanIncludes,
358
+ ipofBuiltinTable,
359
+ ipofScanPrototypes,
360
+ ipofScanImports,
361
+ IPOF_BUILTIN_NUMS,
362
+ IPOF_ALIAS_NUMS,
363
+ };
364
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * @file Procedure declarations and the Constant Data metadata of an .IPO.
3
+ *
4
+ * Port of tools/decompile/ipo_disasm.py's find_decls / body_start and of
5
+ * tools/decompile/ipo_source.py's _constant_data. A declaration is
6
+ *
7
+ * <type u8> <name> \n <u32 id> \n [version] \n \x00 <u16 dwords>
8
+ *
9
+ * and it is the trailing block header that makes it one -- anchoring on that
10
+ * is what separates real declarations from the tens of thousands of
11
+ * name-shaped byte runs a file contains.
12
+ */
13
+
14
+ /** Declaration type byte -> the table its id indexes. */
15
+ const IPOF_DECL_TYPES = {
16
+ 1: 'screen',
17
+ 2: 'menu',
18
+ 3: 'state',
19
+ 4: 'statemachine',
20
+ 5: 'func',
21
+ };
22
+
23
+ /**
24
+ * Whether a byte can start a declaration name.
25
+ *
26
+ * @param {number} c Byte value.
27
+ * @returns {boolean} True for A-Z, a-z or underscore.
28
+ */
29
+ function ipofNameStart(c) {
30
+ return (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || c === 0x5f;
31
+ }
32
+
33
+ /**
34
+ * Whether a byte can continue a declaration name.
35
+ *
36
+ * @param {number} c Byte value.
37
+ * @returns {boolean} True for A-Z, a-z, 0-9 or underscore.
38
+ */
39
+ function ipofNameChar(c) {
40
+ return ipofNameStart(c) || (c >= 0x30 && c <= 0x39);
41
+ }
42
+
43
+ /**
44
+ * Where a declaration's CODE begins.
45
+ *
46
+ * The version string between the id and the block is normally EMPTY (the two
47
+ * newlines sit side by side). One shipped file fills it in, and those extra
48
+ * bytes would put each of its procs three bytes into its own body, so the
49
+ * field's real length is read rather than assumed.
50
+ *
51
+ * @param {Uint8Array} data The file bytes.
52
+ * @param {number} off Offset of the declaration's type byte.
53
+ * @param {string} name The declared name.
54
+ * @returns {number} The offset of the first code byte.
55
+ */
56
+ function ipofBodyStart(data, off, name) {
57
+ const i = off + 1 + name.length + 1 + 4;
58
+ if (data[i] === 0x0a) {
59
+ // a non-empty version string: skip its content, leave its newline
60
+ for (let j = i + 1; j < Math.min(data.length, i + 64); j += 1) {
61
+ if (data[j] === 0x0a) return j > i + 1 ? j : i + 1;
62
+ }
63
+ }
64
+ return i + 1;
65
+ }
66
+
67
+ /**
68
+ * Match one declaration at `at`, or null.
69
+ *
70
+ * The name length is bounded only by INPA's own limit: a 30-character cap
71
+ * silently hides procs whose names are longer, and a minimum length hides the
72
+ * one- and two-character procs some ECU families declare, whose bodies then
73
+ * decode as garbage inside whichever proc preceded them.
74
+ *
75
+ * @param {Uint8Array} data The file bytes.
76
+ * @param {number} at Offset of the candidate type byte.
77
+ * @returns {{off: number, typ: string, name: string, id: number}|null} The
78
+ * declaration, or null.
79
+ */
80
+ function ipofMatchDecl(data, at) {
81
+ const typ = IPOF_DECL_TYPES[data[at]];
82
+ if (!typ) return null;
83
+ let i = at + 1;
84
+ if (!ipofNameStart(data[i])) return null;
85
+ const nameLo = i;
86
+ i += 1;
87
+ while (i < data.length && i - nameLo <= 60 && ipofNameChar(data[i])) i += 1;
88
+ if (data[i] !== 0x0a) return null;
89
+ const name = ipofLatin1(data, nameLo, i);
90
+ i += 1;
91
+ if (i + 4 > data.length) return null;
92
+ const id = ipofUint(data, i, 4);
93
+ i += 4;
94
+ if (data[i] !== 0x0a) return null;
95
+ i += 1;
96
+ // the optional version string, then the mandatory \x00 block header
97
+ let j = i;
98
+ const cap = Math.min(data.length, i + 33);
99
+ while (j < cap && data[j] !== 0x0a) {
100
+ if (data[j] < 0x20 || data[j] > 0x7e) return null;
101
+ j += 1;
102
+ }
103
+ if (j >= cap || data[j] !== 0x0a) return null;
104
+ if (data[j + 1] !== 0x00) return null;
105
+ return { off: at, typ, name, id };
106
+ }
107
+
108
+ /**
109
+ * Every procedure declaration in the code region.
110
+ *
111
+ * @param {Uint8Array} data The file bytes.
112
+ * @param {number} end End of the code region (the pool start or code end).
113
+ * @returns {Array<{off: number, typ: string, name: string, id: number}>} The
114
+ * declarations, in file order.
115
+ */
116
+ function ipofFindDecls(data, end) {
117
+ const out = [];
118
+ const cap = Math.min(end, data.length);
119
+ for (let i = 0; i < cap; i += 1) {
120
+ if (!IPOF_DECL_TYPES[data[i]]) continue;
121
+ const d = ipofMatchDecl(data, i);
122
+ // ids above this are not proc ids; the Python drops them the same way
123
+ if (d && d.id <= 2000) out.push(d);
124
+ }
125
+ return out;
126
+ }
127
+
128
+ /**
129
+ * The include list and the DLL import table from the Constant Data entries.
130
+ *
131
+ * The leading string entries are the `#include` names; any entry shaped like
132
+ * `lib::Function:signature%...` is an import32 signature, and `dllcall #n`
133
+ * names entry n.
134
+ *
135
+ * @param {Array<Array>} entries The pool entries.
136
+ * @returns {{includes: string[], imports: Object<number, string>}} The include
137
+ * names in order, and imports by pool index.
138
+ */
139
+ function ipofConstantData(entries) {
140
+ const includes = [];
141
+ for (const e of entries) {
142
+ if (e[0] !== 's') break;
143
+ includes.push(e[1]);
144
+ }
145
+ const imports = {};
146
+ entries.forEach((e, i) => {
147
+ if (e[0] === 's' && e[1].indexOf('::') >= 0 && e[1].indexOf('%') >= 0) {
148
+ imports[i] = e[1].split('::')[1].split(':')[0];
149
+ }
150
+ });
151
+ return { includes, imports };
152
+ }
153
+
154
+ /**
155
+ * The include names as the Constant Data region lists them, read straight from
156
+ * the bytes for the dialects whose pool did not decode.
157
+ *
158
+ * @param {Uint8Array} data The file bytes.
159
+ * @returns {string[]} The include names.
160
+ */
161
+ function ipofIncludesFromBytes(data) {
162
+ const marker = [0x12].concat(
163
+ Array.from('Constant Data').map((c) => c.charCodeAt(0)),
164
+ [0x0a]
165
+ );
166
+ const hits = ipofFindAll(data, marker, 0, data.length);
167
+ if (!hits.length) return [];
168
+ const out = [];
169
+ let i = hits[0] + marker.length;
170
+ while (i < data.length && data[i] === 6) {
171
+ const j = ipofFindNl(data, i + 1, data.length);
172
+ if (j < 0) break;
173
+ out.push(ipofLatin1(data, i + 1, j));
174
+ i = j + 1;
175
+ }
176
+ return out;
177
+ }
178
+
179
+ if (typeof module !== 'undefined' && module.exports) {
180
+ module.exports = {
181
+ ipofFindDecls,
182
+ ipofBodyStart,
183
+ ipofConstantData,
184
+ ipofIncludesFromBytes,
185
+ IPOF_DECL_TYPES,
186
+ };
187
+ }