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,229 @@
1
+ /**
2
+ * @file The .IPO VM's operators: truthiness and numeric coercion of boxed
3
+ * values, and the `binop` token's arithmetic, comparison and logic -- with
4
+ * the slot/key identity of an operand carried onto the result so a draw
5
+ * stays bound to the reading it was computed from.
6
+ */
7
+
8
+ /**
9
+ * Text that reads as a number: what `+` and unary minus treat as arithmetic
10
+ * when it arrives in a bound box (a job result is text with a key).
11
+ * @type {RegExp}
12
+ */
13
+ const IPO_NUMERIC_TEXT_RE = /^-?\d+(\.\d+)?$/;
14
+
15
+ /**
16
+ * The comparison operator names: their operands are recorded as predicate
17
+ * reads (a key a branch decides on) by the offline executor.
18
+ * @type {string[]}
19
+ */
20
+ const IPO_COMPARE_OPS = ['eq', 'ne', 'lt', 'gt', 'le', 'ge'];
21
+
22
+ /**
23
+ * INPA truthiness: '' and '0' are false, a boxed float is its number, a bound
24
+ * value is judged by its text.
25
+ * @param {IpoValue} v - any VM value
26
+ * @returns {boolean}
27
+ */
28
+ function truthy(v) {
29
+ if (isFloat(v)) return !!v.v;
30
+ if (isBound(v)) v = v.s;
31
+ if (typeof v === 'string') return v !== '' && v !== '0';
32
+ return !!v;
33
+ }
34
+
35
+ /**
36
+ * Numeric value of any VM value; unparseable text is 0.
37
+ * @param {IpoValue} v - any VM value
38
+ * @returns {number}
39
+ */
40
+ function num(v) {
41
+ if (isFloat(v)) return v.v;
42
+ if (isBound(v)) v = v.s;
43
+ if (typeof v === 'number') return v;
44
+ const f = parseFloat(String(v));
45
+ return Number.isNaN(f) ? 0 : f;
46
+ }
47
+
48
+ /**
49
+ * Does x read as a number for arithmetic purposes: an unboxed number, a
50
+ * boxed float, or a bound value whose text is numeric?
51
+ * @param {IpoValue} x - any VM value
52
+ * @returns {boolean}
53
+ */
54
+ function looksNumeric(x) {
55
+ return (
56
+ typeof x === 'number' ||
57
+ isFloat(x) ||
58
+ (isBound(x) && IPO_NUMERIC_TEXT_RE.test(String(x.s).trim()))
59
+ );
60
+ }
61
+
62
+ /**
63
+ * The (slot, key) an arithmetic result should keep from its operands, so a
64
+ * scaled reading (slot13 * scale) keeps STAT_..._WERT's key/slot.
65
+ * @param {IpoValue} a - left operand
66
+ * @param {IpoValue} b - right operand
67
+ * @returns {[IpoSlot|null, string|null]}
68
+ */
69
+ function carry(a, b) {
70
+ for (const x of [a, b]) {
71
+ if (isBound(x) && (x.key || x.slot)) return [x.slot, x.key];
72
+ if (isSlot(x)) return [x, null];
73
+ }
74
+ return [null, null];
75
+ }
76
+
77
+ /**
78
+ * A numeric result, wrapped to keep an operand's slot/key when one had it,
79
+ * and to keep FLOAT identity when the maths is real (either operand a float,
80
+ * or the result non-integral) -- so a later type filter still sees a float.
81
+ * @param {number} value - the computed number
82
+ * @param {IpoValue} a - left operand
83
+ * @param {IpoValue} b - right operand (null for unary minus)
84
+ * @returns {IpoValue}
85
+ */
86
+ function boundNum(value, a, b) {
87
+ const [slot, key] = carry(a, b);
88
+ const real = isFloat(a) || isFloat(b) || !Number.isInteger(value);
89
+ if (slot == null && key == null) return real ? mkFloat(value) : value;
90
+ return mkBound(slot, String(value), key);
91
+ }
92
+
93
+ /**
94
+ * INPA equality. Strings (plain or bound) compare as text; floats as numbers;
95
+ * and, like Python's `==`, a bool equals its int (True == 1) -- a forced
96
+ * keypress guard is the bool true compared against the const 1, so it must
97
+ * read equal (LSZ's "start" gates STEUERN_IO on `g52 == 1`). JS === would say
98
+ * true !== 1, closing the guard and skipping the job.
99
+ * @param {IpoValue} a - left operand
100
+ * @param {IpoValue} b - right operand
101
+ * @returns {boolean}
102
+ */
103
+ function cmpEq(a, b) {
104
+ if (isPlainStr(a) || isPlainStr(b) || isBound(a) || isBound(b)) {
105
+ return asStr(a) === asStr(b);
106
+ }
107
+ if (isFloat(a) || isFloat(b)) return num(a) === num(b);
108
+ if (typeof a === 'boolean' || typeof b === 'boolean') {
109
+ const na = typeof a === 'boolean' ? (a ? 1 : 0) : a;
110
+ const nb = typeof b === 'boolean' ? (b ? 1 : 0) : b;
111
+ return na === nb;
112
+ }
113
+ return a === b;
114
+ }
115
+
116
+ /**
117
+ * `+` on a slot or bound operand: concatenation that keeps the slot's
118
+ * identity and folds every other key into `extra`, so a row built as
119
+ * `<bound slot> + " Werte"` still draws bound to its reading.
120
+ * @param {IpoValue} a - left operand
121
+ * @param {IpoValue} b - right operand
122
+ * @param {IpoSlot} slot - the identity to keep
123
+ * @returns {IpoBound}
124
+ */
125
+ function concatBound(a, b, slot) {
126
+ const [, key] = carry(a, b);
127
+ const out = mkBound(slot, asStr(a) + asStr(b), key);
128
+ const folded = [];
129
+ for (const x of [a, b]) {
130
+ if (isBound(x)) {
131
+ if (x.key && x.key !== key) folded.push(x.key);
132
+ for (const k of x.extra) if (k !== key) folded.push(k);
133
+ }
134
+ }
135
+ if (folded.length) out.extra = [...new Set(folded)];
136
+ return out;
137
+ }
138
+
139
+ /**
140
+ * The `add` operator. BOTH SIDES NUMERIC -> ARITHMETIC, boxed or not: INPA's
141
+ * + on int variables is addition; the slot/bound boxes exist so the offline
142
+ * derivation can bind draws, and letting a box force concatenation made a
143
+ * live accumulator compute '10'+10='1010' (llerh's setpoint on the second
144
+ * keypress). Otherwise + is concatenation, and a slot concatenated must keep
145
+ * its identity or the draw sees only text.
146
+ * @param {IpoValue} a - left operand
147
+ * @param {IpoValue} b - right operand
148
+ * @returns {IpoValue}
149
+ */
150
+ function addOp(a, b) {
151
+ const numish = (x) => looksNumeric(x) || isSlot(x);
152
+ if (numish(a) && numish(b)) return boundNum(num(a) + num(b), a, b);
153
+ let slot = null;
154
+ for (const x of [a, b]) {
155
+ if (isSlot(x)) slot = x;
156
+ else if (isBound(x)) slot = x.slot;
157
+ if (slot != null) break;
158
+ }
159
+ if (slot != null) return concatBound(a, b, slot);
160
+ if (isPlainStr(a) || isPlainStr(b)) return asStr(a) + asStr(b);
161
+ return boundNum(num(a) + num(b), a, b);
162
+ }
163
+
164
+ /**
165
+ * The `neg` operator (0x6d) is UNARY MINUS (ipo_disasm ground truth): llerh's
166
+ * "-10" is `const 10; neg`, the clamp bounds are `const 128; neg`. Boolean-not
167
+ * only ever fit guard shapes; a numeric operand negates arithmetically.
168
+ * @param {IpoValue} a - the operand when passed first (null from the executor)
169
+ * @param {IpoValue} b - the operand as the executor passes it
170
+ * @returns {IpoValue}
171
+ */
172
+ function negOp(a, b) {
173
+ const x = a == null ? b : a;
174
+ if (looksNumeric(x)) return boundNum(-num(x), x, null);
175
+ return !truthy(x);
176
+ }
177
+
178
+ /**
179
+ * Apply a `binop` token. Unknown names yield null.
180
+ * @param {string} name - operator name (add, sub, mul, div, eq, ne, lt, gt,
181
+ * le, ge, and, or, neg)
182
+ * @param {IpoValue} a - left operand (null for neg)
183
+ * @param {IpoValue} b - right operand
184
+ * @returns {IpoValue}
185
+ */
186
+ function binop(name, a, b) {
187
+ switch (name) {
188
+ case 'add':
189
+ return addOp(a, b);
190
+ case 'sub':
191
+ return boundNum(num(a) - num(b), a, b);
192
+ case 'mul':
193
+ return boundNum(num(a) * num(b), a, b);
194
+ case 'div':
195
+ return boundNum(num(b) ? num(a) / num(b) : 0, a, b);
196
+ case 'eq':
197
+ return cmpEq(a, b);
198
+ case 'ne':
199
+ return !cmpEq(a, b);
200
+ case 'lt':
201
+ return num(a) < num(b);
202
+ case 'gt':
203
+ return num(a) > num(b);
204
+ case 'le':
205
+ return num(a) <= num(b);
206
+ case 'ge':
207
+ return num(a) >= num(b);
208
+ case 'and':
209
+ return truthy(a) && truthy(b);
210
+ case 'or':
211
+ return truthy(a) || truthy(b);
212
+ case 'neg':
213
+ return negOp(a, b);
214
+ default:
215
+ return null;
216
+ }
217
+ }
218
+
219
+ if (typeof module !== 'undefined' && module.exports) {
220
+ module.exports = {
221
+ IPO_COMPARE_OPS,
222
+ truthy,
223
+ num,
224
+ carry,
225
+ boundNum,
226
+ cmpEq,
227
+ binop,
228
+ };
229
+ }
@@ -0,0 +1,250 @@
1
+ /**
2
+ * @file INPA's binary-structure helpers (CreateStructure / SetStructureMode /
3
+ * Structure{Byte,Int,Long,String}) and the import32 sprintf the scripts'
4
+ * own hex formatters (chr, bytetohexstring, longtohexstring) build on.
5
+ * Offline these stay noops (the Python twin's behaviour, parity-diffed); a
6
+ * LIVE run needs them or every helper pops its own "Error: Handle" box and
7
+ * prints nothing.
8
+ */
9
+
10
+ /** The builtin names a live run answers here. */
11
+ const IPO_STRUCT_FNS = new Set([
12
+ 'CreateStructure',
13
+ 'SetStructureMode',
14
+ 'StructureByte',
15
+ 'StructureInt',
16
+ 'StructureLong',
17
+ 'StructureString',
18
+ ]);
19
+
20
+ /** Byte width of each fixed-width structure accessor. */
21
+ const IPO_STRUCT_WIDTH = {
22
+ StructureByte: 1,
23
+ StructureInt: 2,
24
+ StructureLong: 4,
25
+ };
26
+
27
+ /** SetStructureMode: 0 writes into the buffer, 1 reads out of it. */
28
+ const IPO_STRUCT_WRITE = 0;
29
+
30
+ /** CreateStructure's buffer size when the script names none. */
31
+ const IPO_STRUCT_DEFAULT_SIZE = 1024;
32
+
33
+ /**
34
+ * A structure the VM owns, behind a numeric handle.
35
+ * @typedef {object} IpoStructure
36
+ * @property {Uint8Array} buf - the bytes
37
+ * @property {number} mode - the mode it was created in (the effective mode is VM-wide)
38
+ */
39
+
40
+ /**
41
+ * The low bytes of a string, one per character (the structure buffers hold
42
+ * single-byte text).
43
+ * @param {string} s - the text
44
+ * @returns {number[]}
45
+ */
46
+ function ipoStringBytes(s) {
47
+ const out = [];
48
+ for (const ch of String(s || '')) out.push(ch.charCodeAt(0) & 0xff);
49
+ return out;
50
+ }
51
+
52
+ /**
53
+ * The number a structure argument holds: a bound value's text, a boxed
54
+ * float's value, else Number(); 0 when not finite.
55
+ * @param {IpoValue} v - the argument
56
+ * @returns {number}
57
+ */
58
+ function structNum(v) {
59
+ if (isBound(v)) v = v.s;
60
+ if (isFloat(v)) return v.v;
61
+ const n = Number(v);
62
+ return Number.isFinite(n) ? n : 0;
63
+ }
64
+
65
+ /**
66
+ * Answer one structure builtin on a live run. A structure is a byte buffer
67
+ * behind a handle (never 0: 0 is the failure code). Mode 0 writes, 1 reads.
68
+ * Structure{Byte,Int,Long,String}(handle, offset[, len], value | out ref):
69
+ * the handle is the first non-out argument (a slot read or a ref); a write
70
+ * takes the last non-ref argument as its value, a read stores through the
71
+ * last ref.
72
+ * @param {IpoVm} vm - the running VM (owns `structs` and `_structMode`)
73
+ * @param {string} name - the builtin's name (one of IPO_STRUCT_FNS)
74
+ * @param {IpoValue[]} stack - the call's arguments
75
+ * @returns {void}
76
+ */
77
+ /**
78
+ * The current value behind a reference (through chained refs).
79
+ * @param {IpoVm} vm - the running VM
80
+ * @param {IpoRef} r - the reference
81
+ * @returns {*}
82
+ */
83
+ function ipoRefValue(vm, r) {
84
+ const { n, map } = vm._refTarget(r, vm.frame);
85
+ return map.get(n);
86
+ }
87
+
88
+ function ipoStructureCall(vm, name, stack) {
89
+ if (!vm.structs) vm.structs = new Map();
90
+ const refs = stack.filter(isRef);
91
+ const nums = stack
92
+ .map((x) => (isPlainInt(x) ? x : isFloat(x) ? x.v : null))
93
+ .filter((x) => x != null);
94
+ if (name === 'CreateStructure') {
95
+ const size = nums.length ? Math.max(1, nums[0]) : IPO_STRUCT_DEFAULT_SIZE;
96
+ const h = vm.structs.size + 1;
97
+ vm.structs.set(h, { buf: new Uint8Array(size), mode: IPO_STRUCT_WRITE });
98
+ storeOut(vm, stack, h);
99
+ return;
100
+ }
101
+ if (name === 'SetStructureMode') {
102
+ vm._structMode = nums.length ? nums[0] : IPO_STRUCT_WRITE;
103
+ return;
104
+ }
105
+ const handle = structNum(
106
+ stack.find((x) => !isRef(x) && (isPlainInt(x) || isBound(x)))
107
+ );
108
+ const st = vm.structs.get(handle);
109
+ if (!st) return;
110
+ const off = nums.length > 1 ? nums[1] : nums.length ? nums[0] : 0;
111
+ const width = IPO_STRUCT_WIDTH[name] || 0;
112
+ const mode = vm._structMode || IPO_STRUCT_WRITE;
113
+ if (width) {
114
+ if (mode === IPO_STRUCT_WRITE) {
115
+ // the value comes by value or, as longtohexstring packs its number,
116
+ // by reference (StructureLong(handle, 0, &value))
117
+ const vals = stack.filter((x) => !isRef(x));
118
+ const v = structNum(
119
+ refs.length
120
+ ? ipoRefValue(vm, refs[refs.length - 1])
121
+ : vals[vals.length - 1]
122
+ );
123
+ for (let i = 0; i < width; i++) {
124
+ if (off + i < st.buf.length) st.buf[off + i] = (v >>> (8 * i)) & 0xff;
125
+ }
126
+ } else {
127
+ let v = 0;
128
+ for (let i = width - 1; i >= 0; i--) v = v * 256 + (st.buf[off + i] || 0);
129
+ storeOut(vm, refs.slice(-1), v);
130
+ }
131
+ return;
132
+ }
133
+ // StructureString(handle, offset, len, out string | string)
134
+ // StructureString(handle, offset, length, &text): length 0 means "to the
135
+ // NUL", which is how chr() reads the one byte it packed back as a string
136
+ const len = nums.length > 2 && nums[2] > 0 ? nums[2] : st.buf.length - off;
137
+ if (mode === IPO_STRUCT_WRITE) {
138
+ let sv = stack.find((x) => isPlainStr(x) || (isBound(x) && !isRef(x)));
139
+ if (sv == null && refs.length) sv = ipoRefValue(vm, refs[refs.length - 1]);
140
+ const bytes = ipoStringBytes(asStr(sv == null ? '' : sv));
141
+ for (let i = 0; i < Math.min(len, bytes.length); i++) {
142
+ if (off + i < st.buf.length) st.buf[off + i] = bytes[i];
143
+ }
144
+ } else {
145
+ let out = '';
146
+ for (let i = 0; i < len && off + i < st.buf.length; i++) {
147
+ const b = st.buf[off + i];
148
+ if (b === 0) break;
149
+ out += String.fromCharCode(b);
150
+ }
151
+ storeOut(vm, refs.slice(-1), out);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * sprintf through import32: (out string ref, format, handle ref, out count
157
+ * ref). The import table is per-file and carries no names, but the scripts
158
+ * use it for ONE thing the formatters must honour: a sprintf of a
159
+ * structure's bytes ("%08lX") into an out string. The format is the only
160
+ * string carrying '%'; the value is the structure's long at offset 0 (what
161
+ * the formatters just wrote), or the argument itself when it is not a
162
+ * handle.
163
+ * @param {IpoVm} vm - the running VM
164
+ * @param {IpoValue[]} stack - the call's arguments
165
+ * @returns {void}
166
+ */
167
+ function ipoDllCall(vm, stack) {
168
+ const refs = stack.filter(isRef);
169
+ // the format is usually computed ('%0' + width + 'lX'), so a bound string
170
+ const fmtArg = stack.find(
171
+ (x) => (isPlainStr(x) || isBound(x)) && asStr(x).includes('%')
172
+ );
173
+ if (!fmtArg) return ipoDllFileDialog(vm, stack, refs);
174
+ if (!refs.length) return;
175
+ const fmt = asStr(fmtArg);
176
+ let value = 0;
177
+ if (refs.length > 1) {
178
+ const raw = ipoRefValue(vm, refs[1]);
179
+ const n = Number(isBound(raw) ? raw.s : isFloat(raw) ? raw.v : raw);
180
+ const st = vm.structs && vm.structs.get(n);
181
+ if (st) {
182
+ for (let i = 3; i >= 0; i--) value = value * 256 + (st.buf[i] || 0);
183
+ } else if (Number.isFinite(n)) value = n;
184
+ }
185
+ const m = fmt.match(/%(0?)(\d*)(l?)([xXdus])/);
186
+ let text = fmt;
187
+ if (m) {
188
+ const width = m[2] ? Number(m[2]) : 0;
189
+ let body;
190
+ if (m[4] === 'x') body = (value >>> 0).toString(16);
191
+ else if (m[4] === 'X') body = (value >>> 0).toString(16).toUpperCase();
192
+ else if (m[4] === 'u') body = String(value >>> 0);
193
+ else body = String(value);
194
+ const pad = m[1] === '0' ? '0' : ' ';
195
+ while (body.length < width) body = pad + body;
196
+ text = fmt.replace(m[0], body);
197
+ }
198
+ storeOut(vm, [refs[0]], text);
199
+ if (refs.length > 2) storeOut(vm, [refs[refs.length - 1]], text.length);
200
+ }
201
+
202
+ /**
203
+ * The other import the scripts call: the "save as" dialog. The whole-vehicle
204
+ * scripts allocate a 256-byte buffer, call the dialog with (filter, &buffer,
205
+ * length, &rc), and take the buffer as the file name when rc > 0. Live, the
206
+ * runtime shows the platform's own picker: the call suspends, and the
207
+ * resume value (the chosen name, '' for cancel) fills the buffer and rc.
208
+ * The alloc/free calls around it carry no format and no such shape: no-ops.
209
+ * @param {IpoVm} vm - the running VM
210
+ * @param {IpoValue[]} stack - the call's arguments
211
+ * @param {IpoRef[]} refs - its references
212
+ * @returns {object|null} a 'file' suspension, or null
213
+ */
214
+ function ipoDllFileDialog(vm, stack, refs) {
215
+ const plain = stack.filter((x) => !isRef(x));
216
+ const isDialog =
217
+ refs.length === 2 &&
218
+ plain.length === 2 &&
219
+ (isPlainStr(plain[0]) || isBound(plain[0])) &&
220
+ isPlainInt(plain[1]);
221
+ if (!isDialog) return null;
222
+ return { kind: 'file', filter: asStr(plain[0]), stack, out: vm.out };
223
+ }
224
+
225
+ /**
226
+ * The picker's answer into the dialog's refs: the buffer takes the name,
227
+ * rc its length (0 = cancelled, the script then does nothing).
228
+ * @param {IpoVm} vm - the running VM
229
+ * @param {IpoValue[]} stack - the parked call's arguments
230
+ * @param {string} name - the chosen file name ('' when cancelled)
231
+ * @returns {void}
232
+ */
233
+ function ipoDllFileAnswer(vm, stack, name) {
234
+ const refs = stack.filter(isRef);
235
+ const text = name == null ? '' : String(name);
236
+ if (refs.length < 2) return;
237
+ storeOut(vm, [refs[0]], text);
238
+ storeOut(vm, [refs[1]], text.length);
239
+ }
240
+
241
+ if (typeof module !== 'undefined' && module.exports) {
242
+ module.exports = {
243
+ IPO_STRUCT_FNS,
244
+ ipoDllFileDialog,
245
+ ipoDllFileAnswer,
246
+ ipoStringBytes,
247
+ ipoStructureCall,
248
+ ipoDllCall,
249
+ };
250
+ }