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.
- package/LICENSE +674 -0
- package/README.md +351 -0
- package/dist/bmweb.js +2790 -0
- package/package.json +53 -0
- package/runtime/core/bestvm/codec.js +285 -0
- package/runtime/core/bestvm/environment.js +116 -0
- package/runtime/core/bestvm/executor.js +1483 -0
- package/runtime/core/bestvm/index.js +52 -0
- package/runtime/core/bestvm/machine.js +491 -0
- package/runtime/core/bestvm/operands.js +356 -0
- package/runtime/core/bestvm/registers.js +152 -0
- package/runtime/core/bestvm/write-guard.js +111 -0
- package/runtime/core/ipofile/compile.js +364 -0
- package/runtime/core/ipofile/decls.js +187 -0
- package/runtime/core/ipofile/emit.js +708 -0
- package/runtime/core/ipofile/exec.js +164 -0
- package/runtime/core/ipofile/lex.js +243 -0
- package/runtime/core/ipofile/parse.js +550 -0
- package/runtime/core/ipofile/pool.js +404 -0
- package/runtime/core/ipofile/walk.js +431 -0
- package/runtime/core/ipovm/builtin-helpers.js +182 -0
- package/runtime/core/ipovm/builtins-api.js +610 -0
- package/runtime/core/ipovm/builtins-screen.js +493 -0
- package/runtime/core/ipovm/builtins-table.js +166 -0
- package/runtime/core/ipovm/builtins-text.js +166 -0
- package/runtime/core/ipovm/emissions.js +138 -0
- package/runtime/core/ipovm/hosts.js +191 -0
- package/runtime/core/ipovm/operators.js +229 -0
- package/runtime/core/ipovm/structures.js +250 -0
- package/runtime/core/ipovm/suspensions.js +241 -0
- package/runtime/core/ipovm/tape.js +206 -0
- package/runtime/core/ipovm/values.js +241 -0
- package/runtime/core/ipovm/vm.js +1166 -0
- package/runtime/core/translate.js +526 -0
- package/runtime/core/webshim/api-router.js +592 -0
- package/runtime/core/webshim/bus.js +95 -0
- package/runtime/core/webshim/coding.js +82 -0
- package/runtime/core/webshim/data-fetch.js +66 -0
- package/runtime/core/webshim/exchange.js +288 -0
- package/runtime/core/webshim/framing.js +331 -0
- package/runtime/core/webshim/install.js +30 -0
- package/runtime/core/webshim/job-runner.js +319 -0
- package/runtime/core/webshim/native-bus.js +108 -0
- package/runtime/core/webshim/timers.js +82 -0
- package/runtime/core/webshim/trace.js +205 -0
- package/runtime/core/webshim/transport-base.js +128 -0
- package/runtime/core/webshim/variant-resolver.js +249 -0
- package/runtime/core/webshim/web-serial-bus.js +734 -0
- package/runtime/home/bmweb-home.ips +76 -0
- package/runtime/home/bmweb.h +26 -0
- package/runtime/screens/activations.js +258 -0
- package/runtime/screens/garage/diff.js +331 -0
- package/runtime/screens/garage/share.js +276 -0
- package/runtime/screens/garage/store.js +547 -0
- package/runtime/screens/ipo-runtime/cells.js +176 -0
- package/runtime/screens/ipo-runtime/dialogs.js +254 -0
- package/runtime/screens/ipo-runtime/home.js +358 -0
- package/runtime/screens/ipo-runtime/open.js +393 -0
- package/runtime/screens/ipo-runtime/paint-grid.js +106 -0
- package/runtime/screens/ipo-runtime/paint-modern.js +424 -0
- package/runtime/screens/ipo-runtime/print.js +281 -0
- package/runtime/screens/ipo-runtime/program.js +1337 -0
- package/runtime/screens/ipo-runtime/protocol.js +464 -0
- package/runtime/screens/ipo-runtime/script-scan.js +225 -0
- package/runtime/screens/ipo-runtime/translate-sets.js +130 -0
- package/runtime/screens/ipo-runtime/ui.js +249 -0
- package/runtime/screens/ipo-runtime/wire-policy.js +113 -0
- package/runtime/screens/ir.js +324 -0
- package/runtime/screens/search/data.js +153 -0
- package/runtime/screens/search/match.js +285 -0
- package/runtime/screens/search/open.js +66 -0
- 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
|
+
}
|