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,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builtins that talk to the diagnostic API and the user: INPAapiJob and
|
|
3
|
+
* the INPAapiResult* reads, string arrays, binary results, the input
|
|
4
|
+
* dialogs, the component picker, files, timers and state-machine entry.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** getinputstate's "confirmed" answer: the guard tests `== 0`. */
|
|
8
|
+
const IPO_INPUT_CONFIRMED = 0;
|
|
9
|
+
|
|
10
|
+
/** What an offline input stores: never sent live (see ipoDriveBuiltin). */
|
|
11
|
+
const IPO_INPUT_PLACEHOLDER = '0';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* INPAapiJob(sgbd, job, arg): record the job, attribute it to the item, mark
|
|
15
|
+
* the per-line arg boundary, and run it through the host. Python's _b_job
|
|
16
|
+
* tests isinstance(x, str), TRUE for _Bound: sgbd/job/arg are frequently
|
|
17
|
+
* concatenations (a bound value), so accept those and read their text --
|
|
18
|
+
* otherwise the job arg (LWS5's coding block index, LAR;0x;0) and an
|
|
19
|
+
* empty-string sgbd are lost.
|
|
20
|
+
* @type {IpoBuiltin}
|
|
21
|
+
*/
|
|
22
|
+
function bJob(vm, stack, item) {
|
|
23
|
+
const strv = (x) => (isPlainStr(x) ? x : isBound(x) ? x.s : null);
|
|
24
|
+
const sgbd = stack.length > 0 ? strv(stack[0]) : null;
|
|
25
|
+
const job = stack.length > 1 ? strv(stack[1]) : null;
|
|
26
|
+
const arg = stack.length > 2 ? strv(stack[2]) : null;
|
|
27
|
+
if (job == null) return;
|
|
28
|
+
/** @type {IpoJobRecord} */
|
|
29
|
+
const rec = { job, sgbd: sgbd == null ? null : sgbd };
|
|
30
|
+
if (arg && !vm.inputFed) rec.arg = arg;
|
|
31
|
+
vm.out.jobs.push(rec);
|
|
32
|
+
if (item && !('job' in item)) {
|
|
33
|
+
item.job = job;
|
|
34
|
+
if (rec.arg) item.jobArg = rec.arg;
|
|
35
|
+
}
|
|
36
|
+
// Per-line arg boundary (per-wheel loop draws one line per job arg).
|
|
37
|
+
if (rec.arg) {
|
|
38
|
+
let last = vm.out.lines.length
|
|
39
|
+
? vm.out.lines[vm.out.lines.length - 1]
|
|
40
|
+
: null;
|
|
41
|
+
const drawn = !!(last && last.elements && last.elements.length);
|
|
42
|
+
if (drawn && last.jobArg != null && last.jobArg !== rec.arg) {
|
|
43
|
+
vm.out.lines.push({ label: null, elements: [] });
|
|
44
|
+
last = vm.out.lines[vm.out.lines.length - 1];
|
|
45
|
+
}
|
|
46
|
+
if (last && !('jobArg' in last)) last.jobArg = rec.arg;
|
|
47
|
+
}
|
|
48
|
+
vm.globals.set('__last_job__', vm.host.job(sgbd, job, arg, null));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* INPAapiFsMode: the item switches the fault-read mode.
|
|
53
|
+
* @type {IpoBuiltin}
|
|
54
|
+
*/
|
|
55
|
+
function bFsmode(vm, stack, item) {
|
|
56
|
+
if (item) item.faultRead = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* INPAapiCheckJobStatus: the host's verdict on the last job.
|
|
61
|
+
* @type {IpoBuiltin}
|
|
62
|
+
*/
|
|
63
|
+
function bCheckStatus(vm) {
|
|
64
|
+
vm.globals.set('__status__', vm.host.status());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* INPAapiResult*(->dest, KEY, set[, format]): read a result key into the last
|
|
69
|
+
* out-ref, bound to the key; a two-ref form (INP1api...) gets rc = 1 in the
|
|
70
|
+
* first. The set number is the one numeric argument (a literal or a bound
|
|
71
|
+
* variable); the format is a string.
|
|
72
|
+
* @param {IpoVm} vm - the running VM
|
|
73
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
74
|
+
* @param {IpoItem|null} item - the item being executed
|
|
75
|
+
* @param {boolean} [integer] - read as a number (INPAapiResultInt)
|
|
76
|
+
* @returns {void}
|
|
77
|
+
*/
|
|
78
|
+
function bResult(vm, stack, item, integer) {
|
|
79
|
+
const key = stack.find(isPlainStr) || null;
|
|
80
|
+
const setArg = stack.find((x) => !isRef(x) && !isPlainStr(x));
|
|
81
|
+
const set = setArg != null ? Math.trunc(num(setArg)) : undefined;
|
|
82
|
+
let val = key ? vm.host.result(key, { integer, set }) : '';
|
|
83
|
+
const refs = stack.filter(isRef);
|
|
84
|
+
if (refs.length >= 2) storeOut(vm, [refs[0]], 1);
|
|
85
|
+
const dest = refs.length ? refs[refs.length - 1] : null;
|
|
86
|
+
if (dest != null) {
|
|
87
|
+
const { dsc: sc, n, map } = vm._refTarget(dest, vm.frame);
|
|
88
|
+
if (key) {
|
|
89
|
+
val = mkBound(mkSlot(sc, n), val == null ? '' : asStr(val), key);
|
|
90
|
+
vm.setBind(sc, n, key);
|
|
91
|
+
}
|
|
92
|
+
vm.globals.set(`ref:${n}`, val);
|
|
93
|
+
map.set(n, val);
|
|
94
|
+
}
|
|
95
|
+
if (key) {
|
|
96
|
+
vm.globals.set('__lastkey__', key);
|
|
97
|
+
vm.out.reads.push(key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* INPAapiResultInt: bResult reading a number.
|
|
103
|
+
* @type {IpoBuiltin}
|
|
104
|
+
*/
|
|
105
|
+
function bResultInt(vm, stack, item) {
|
|
106
|
+
bResult(vm, stack, item, true);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* INP1apiErrorCode: no error.
|
|
111
|
+
* @type {IpoBuiltin}
|
|
112
|
+
*/
|
|
113
|
+
function bErrorCode(vm, stack) {
|
|
114
|
+
storeOut(vm, stack, 0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* INP1apiErrorText: no error text.
|
|
119
|
+
* @type {IpoBuiltin}
|
|
120
|
+
*/
|
|
121
|
+
function bErrorText(vm, stack) {
|
|
122
|
+
storeOut(vm, stack, '');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* INPAapiResultSets(->n) / INP1apiResultSets(->rc, ->n): how many sets the
|
|
127
|
+
* job produced -- the wire's own count when a feed carries it (a fault list
|
|
128
|
+
* is one set per fault plus the JOB_STATUS set: INPA subtracts one and
|
|
129
|
+
* loops); 1 for the offline hosts.
|
|
130
|
+
* @type {IpoBuiltin}
|
|
131
|
+
*/
|
|
132
|
+
function bResultSets(vm, stack) {
|
|
133
|
+
const refs = stack.filter(isRef);
|
|
134
|
+
if (refs.length >= 2) storeOut(vm, [refs[0]], 1);
|
|
135
|
+
const n =
|
|
136
|
+
vm.host && typeof vm.host.count === 'function' ? vm.host.count() : 1;
|
|
137
|
+
storeOut(vm, refs.length ? [refs[refs.length - 1]] : [], n);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* StrArrayCreate(->rc, ->handle): a fresh string array.
|
|
142
|
+
* @type {IpoBuiltin}
|
|
143
|
+
*/
|
|
144
|
+
function bStrArrayCreate(vm, stack) {
|
|
145
|
+
const refs = stack.filter(isRef);
|
|
146
|
+
vm._arrN += 1;
|
|
147
|
+
vm.strArrays.set(vm._arrN, new Map());
|
|
148
|
+
if (refs.length >= 2) storeOut(vm, [refs[0]], 1);
|
|
149
|
+
storeOut(vm, refs.length ? [refs[refs.length - 1]] : [], vm._arrN);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* StrArrayWrite(handle, index, text).
|
|
154
|
+
* @type {IpoBuiltin}
|
|
155
|
+
*/
|
|
156
|
+
function bStrArrayWrite(vm, stack) {
|
|
157
|
+
// StrArrayWrite(array, index, text): the text is usually computed (a
|
|
158
|
+
// module's name + its fault count), not a literal
|
|
159
|
+
const args = stack.filter((x) => !isRef(x));
|
|
160
|
+
if (args.length < 3) return;
|
|
161
|
+
const id = Math.trunc(num(args[0]));
|
|
162
|
+
const idx = Math.trunc(num(args[1]));
|
|
163
|
+
const txt = asStr(args[2]);
|
|
164
|
+
if (vm.strArrays.has(id)) vm.strArrays.get(id).set(idx, txt);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* StrArrayRead(handle, index, ->text): the entry, bound to the index's key
|
|
169
|
+
* and carrying the whole array as a lookup map so a painter can caption the
|
|
170
|
+
* value.
|
|
171
|
+
* @type {IpoBuiltin}
|
|
172
|
+
*/
|
|
173
|
+
function bStrArrayRead(vm, stack) {
|
|
174
|
+
const vals = stack.filter((x) => !isRef(x));
|
|
175
|
+
const arr = vals.length
|
|
176
|
+
? vm.strArrays.get(Math.trunc(num(vals[0]))) || new Map()
|
|
177
|
+
: new Map();
|
|
178
|
+
const text = vals.length > 1 ? arr.get(Math.trunc(num(vals[1]))) || '' : '';
|
|
179
|
+
const out = mkBound(null, text, keyed(stack));
|
|
180
|
+
const amap = {};
|
|
181
|
+
for (const [k, v] of arr) amap[String(k)] = v;
|
|
182
|
+
out.amap = Object.keys(amap).length ? amap : null;
|
|
183
|
+
storeOut(vm, stack, out, out.key);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* INPAapiResultBinary(KEY): a coding image; the next store (or
|
|
188
|
+
* GetBinaryDataString) binds to the key.
|
|
189
|
+
* @type {IpoBuiltin}
|
|
190
|
+
*/
|
|
191
|
+
/**
|
|
192
|
+
* builtin_90(array, &n): how many entries a string array holds. The
|
|
193
|
+
* whole-vehicle scripts fill an array with the modules that answered with
|
|
194
|
+
* faults and walk it `for (i = 0; i < n; i++)` to write the short overview
|
|
195
|
+
* of the protocol, so n must be the real count, not a slot.
|
|
196
|
+
* @param {Best2Vm} vm
|
|
197
|
+
* @param {*[]} stack
|
|
198
|
+
*/
|
|
199
|
+
function bStrArraySize(vm, stack) {
|
|
200
|
+
const vals = stack.filter((x) => !isRef(x));
|
|
201
|
+
const arr = vals.length
|
|
202
|
+
? vm.strArrays.get(Math.trunc(num(vals[0]))) || new Map()
|
|
203
|
+
: new Map();
|
|
204
|
+
storeOut(vm, stack, arr.size);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function bResultBinary(vm, stack) {
|
|
208
|
+
// INPAapiResultBinary(&rc, key, set): the key is a literal or a variable
|
|
209
|
+
// holding one (protokoll_hexcode passes its parameter), the set a number
|
|
210
|
+
let key = null;
|
|
211
|
+
let set = null;
|
|
212
|
+
for (const a of stack) {
|
|
213
|
+
if (isRef(a)) continue;
|
|
214
|
+
const s = asStr(a);
|
|
215
|
+
if (key == null && /^[A-Za-z_]/.test(s)) key = s;
|
|
216
|
+
else if (set == null && s !== '' && Number.isFinite(Number(s)))
|
|
217
|
+
set = Math.trunc(Number(s));
|
|
218
|
+
}
|
|
219
|
+
if (key) {
|
|
220
|
+
vm.globals.set('__pending_binary__', key);
|
|
221
|
+
// the set it was asked of, for the live read behind GetBinaryDataString
|
|
222
|
+
vm.globals.set('__pending_binary_set__', set);
|
|
223
|
+
vm.out.reads.push(key);
|
|
224
|
+
}
|
|
225
|
+
// rc: the script prints "????" for a code it could not read; live that
|
|
226
|
+
// is a result the wire did not carry, offline every read succeeds
|
|
227
|
+
const refs = stack.filter(isRef);
|
|
228
|
+
if (refs.length) {
|
|
229
|
+
const have =
|
|
230
|
+
!vm.wireJobs ||
|
|
231
|
+
(key &&
|
|
232
|
+
vm.host &&
|
|
233
|
+
typeof vm.host.raw === 'function' &&
|
|
234
|
+
vm.host.raw(key, { set: set == null ? undefined : set }) != null);
|
|
235
|
+
storeOut(vm, [refs[0]], have ? 1 : 0);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* A binary result as the hex text INPA's GetBinaryDataString hands the
|
|
241
|
+
* script ("27C3"): bytes from a typed array, a plain array, or the object
|
|
242
|
+
* a typed array turns into through JSON; a string with its separators
|
|
243
|
+
* dropped ("27-C3", "0x27C3"); a number as four digits.
|
|
244
|
+
* @param {*} v - the result value
|
|
245
|
+
* @returns {string}
|
|
246
|
+
*/
|
|
247
|
+
function ipoBinaryHex(v) {
|
|
248
|
+
if (v == null || v === '') return '';
|
|
249
|
+
const byte = (b) =>
|
|
250
|
+
(Number(b) & 0xff).toString(16).toUpperCase().padStart(2, '0');
|
|
251
|
+
if (Array.isArray(v) || ArrayBuffer.isView(v))
|
|
252
|
+
return Array.from(v, byte).join('');
|
|
253
|
+
if (typeof v === 'number')
|
|
254
|
+
return Number.isFinite(v)
|
|
255
|
+
? (v >>> 0).toString(16).toUpperCase().padStart(4, '0')
|
|
256
|
+
: '';
|
|
257
|
+
if (typeof v === 'object') {
|
|
258
|
+
const keys = Object.keys(v).filter((k) => /^\d+$/.test(k));
|
|
259
|
+
if (keys.length)
|
|
260
|
+
return keys
|
|
261
|
+
.sort((a, b) => a - b)
|
|
262
|
+
.map((k) => byte(v[k]))
|
|
263
|
+
.join('');
|
|
264
|
+
}
|
|
265
|
+
return String(v)
|
|
266
|
+
.replace(/^0x/i, '')
|
|
267
|
+
.replace(/[^0-9a-fA-F]/g, '')
|
|
268
|
+
.toUpperCase();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* GetBinaryDataString(->dst, ->src, ...): the destination takes the source's
|
|
273
|
+
* binding (or the pending binary key) so a sliced display stays bound.
|
|
274
|
+
* @type {IpoBuiltin}
|
|
275
|
+
*/
|
|
276
|
+
function bGetBinaryDataString(vm, stack) {
|
|
277
|
+
const refs = stack.filter(isRef);
|
|
278
|
+
if (refs.length < 2) return;
|
|
279
|
+
const dst = refs[0],
|
|
280
|
+
src = refs[1];
|
|
281
|
+
// a live run: GetBinaryDataString(&text, &length) gives the script the
|
|
282
|
+
// bytes of the last INPAapiResultBinary as hex text, and their length in
|
|
283
|
+
// characters (E46.IPO's protokoll_hexcode walks it two characters a byte)
|
|
284
|
+
if (vm.wireJobs) {
|
|
285
|
+
const key = vm.globals.get('__pending_binary__');
|
|
286
|
+
const set = vm.globals.get('__pending_binary_set__');
|
|
287
|
+
if (key && vm.host && typeof vm.host.raw === 'function') {
|
|
288
|
+
const hex = ipoBinaryHex(
|
|
289
|
+
vm.host.raw(key, { set: set == null ? undefined : set })
|
|
290
|
+
);
|
|
291
|
+
vm.globals.delete('__pending_binary__');
|
|
292
|
+
vm.globals.delete('__pending_binary_set__');
|
|
293
|
+
storeOut(vm, [dst], mkBound(null, hex, key), key);
|
|
294
|
+
storeOut(vm, [src], hex.length);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const dsc = dst[1] === IPO_REF_LOCAL && vm.frame != null ? LOCAL : GLOBAL;
|
|
299
|
+
const ssc = src[1] === IPO_REF_LOCAL && vm.frame != null ? LOCAL : GLOBAL;
|
|
300
|
+
let key = vm.bindKey(ssc, src[2]);
|
|
301
|
+
if (!key) {
|
|
302
|
+
key = vm.globals.get('__pending_binary__');
|
|
303
|
+
vm.globals.delete('__pending_binary__');
|
|
304
|
+
}
|
|
305
|
+
if (!key) return;
|
|
306
|
+
const val = mkBound(mkSlot(dsc, dst[2]), '0', key);
|
|
307
|
+
vm.setBind(dsc, dst[2], key);
|
|
308
|
+
if (dsc === GLOBAL) vm.globals.set(dst[2], val);
|
|
309
|
+
else if (vm.frame) vm.frame.set(dst[2], val);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* getinputstate(->state): when the state machine is being DRIVEN (a resume
|
|
314
|
+
* fed the user's on/off), that answer is the input state; the guard tests
|
|
315
|
+
* `== 0`, so a valid pick is 0. Offline it falls to the host's placeholder.
|
|
316
|
+
* @type {IpoBuiltin}
|
|
317
|
+
*/
|
|
318
|
+
function bGetInputState(vm, stack) {
|
|
319
|
+
const iv = vm._driveInput;
|
|
320
|
+
storeOut(vm, stack, iv != null ? iv : vm.host.inputstate());
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* builtin_16 = togglelist. Offline a noop (the picked row is runtime-only,
|
|
325
|
+
* so the diff harness sees nothing). When DRIVEN, it writes the user's
|
|
326
|
+
* picked component (an ORT key from the BITS table) into its out-var,
|
|
327
|
+
* exactly as the widget does at runtime -- so the INPAapiJob that reads that
|
|
328
|
+
* slot sends the pick as its argument (STEUERN_DIGITAL <ORT>).
|
|
329
|
+
* @type {IpoBuiltin}
|
|
330
|
+
*/
|
|
331
|
+
function bToggleList(vm, stack) {
|
|
332
|
+
if (vm._pickInput == null) return;
|
|
333
|
+
storeOut(vm, stack, vm._pickInput);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* bmweb_pick(what, arg, ->choice): BMWeb's own picker builtin (home script).
|
|
338
|
+
* Offline a noop; driven, the renderer parks the script (suspensions.js
|
|
339
|
+
* 'pick'), asks the host for the list named by `what`, and resumes with the
|
|
340
|
+
* user's choice, which lands in the out-string; a cancelled pick leaves it
|
|
341
|
+
* as it was, and the script's own `if (choice != "")` is the guard.
|
|
342
|
+
* @type {IpoBuiltin}
|
|
343
|
+
*/
|
|
344
|
+
function bBmwebPick(vm, stack) {
|
|
345
|
+
if (vm._pickInput == null) return;
|
|
346
|
+
storeOut(vm, stack, asStr(vm._pickInput));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* bmweb_status(->text): one line from the host (cable state, version) for
|
|
351
|
+
* the home screen. The host hangs it on the VM as `homeStatus`; without one
|
|
352
|
+
* the line is empty, which the screen tolerates.
|
|
353
|
+
* @type {IpoBuiltin}
|
|
354
|
+
*/
|
|
355
|
+
function bBmwebStatus(vm, stack) {
|
|
356
|
+
const table = typeof IPO_HOME_HOST !== 'undefined' ? IPO_HOME_HOST : null;
|
|
357
|
+
const fn = (table && table.status) || (vm.host && vm.host.homeStatus);
|
|
358
|
+
let text = '';
|
|
359
|
+
try {
|
|
360
|
+
text = typeof fn === 'function' ? String(fn() || '') : '';
|
|
361
|
+
} catch (e) {
|
|
362
|
+
text = '';
|
|
363
|
+
}
|
|
364
|
+
storeOut(vm, stack, text);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* The input family (inputint, inputhex, input2int, ...): record the prompts
|
|
369
|
+
* on the item, store the placeholder through every out-ref, and taint the
|
|
370
|
+
* next job (mirrors Python _b_input: a job sent after an ask has no static
|
|
371
|
+
* arg).
|
|
372
|
+
* @type {IpoBuiltin}
|
|
373
|
+
*/
|
|
374
|
+
function bInput(vm, stack, item) {
|
|
375
|
+
const prompts = stack.filter((x) => isPlainStr(x) && x.trim());
|
|
376
|
+
if (item && prompts.length) {
|
|
377
|
+
const have = item.prompt || (item.prompt = []);
|
|
378
|
+
for (const p of prompts) if (!have.includes(p)) have.push(p);
|
|
379
|
+
}
|
|
380
|
+
for (const ref of stack.filter(isRef))
|
|
381
|
+
storeOut(vm, [ref], IPO_INPUT_PLACEHOLDER);
|
|
382
|
+
vm.inputFed = true;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* inputdigital: capture+taint via bInput, then the CONFIRMING choice as the
|
|
387
|
+
* placeholder -- the lift keeps the yes-branch's job (mirrors
|
|
388
|
+
* _b_inputdigital).
|
|
389
|
+
* @type {IpoBuiltin}
|
|
390
|
+
*/
|
|
391
|
+
function bInputDigital(vm, stack, item) {
|
|
392
|
+
bInput(vm, stack, item);
|
|
393
|
+
for (const ref of stack.filter(isRef)) storeOut(vm, [ref], 1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* fileopen(path..., mode): open an in-memory file; the path is every
|
|
398
|
+
* string/slot argument that is not the mode letter.
|
|
399
|
+
* @type {IpoBuiltin}
|
|
400
|
+
*/
|
|
401
|
+
function bFileopen(vm, stack) {
|
|
402
|
+
// fileopen(path, mode): the path is usually computed (folder + name +
|
|
403
|
+
// extension), so take whatever string value the expression produced
|
|
404
|
+
const args = stack.filter((x) => !isRef(x));
|
|
405
|
+
let mode = 'r';
|
|
406
|
+
const last = args.length > 1 ? asStr(args[args.length - 1]) : '';
|
|
407
|
+
if (['r', 'w', 'a'].includes(last)) {
|
|
408
|
+
mode = last;
|
|
409
|
+
args.pop();
|
|
410
|
+
}
|
|
411
|
+
const path = args.map(asStr).join('');
|
|
412
|
+
if (mode === 'w') vm.files.set(path, []);
|
|
413
|
+
else if (mode === 'a' && !vm.files.has(path)) vm.files.set(path, []);
|
|
414
|
+
if (mode !== 'r') vm.lastWritten = path;
|
|
415
|
+
vm.fh = { path, mode, line: 0 };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* fileclose.
|
|
420
|
+
* @type {IpoBuiltin}
|
|
421
|
+
*/
|
|
422
|
+
function bFileclose(vm) {
|
|
423
|
+
vm.fh = null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* viewopen(path): INPA opens the file the script just wrote in its viewer
|
|
428
|
+
* window (the whole-vehicle fault protocol). The runtime shows the same
|
|
429
|
+
* text as the screen and prints it as the sheet.
|
|
430
|
+
* @param {Best2Vm} vm
|
|
431
|
+
* @param {*[]} stack
|
|
432
|
+
*/
|
|
433
|
+
function bViewopen(vm, stack) {
|
|
434
|
+
// viewopen(path, title): the path is computed (folder + name + extension),
|
|
435
|
+
// the title is the window caption
|
|
436
|
+
const args = stack.filter((x) => !isRef(x)).map(asStr);
|
|
437
|
+
let path = args[0] || '';
|
|
438
|
+
if (!vm.files.has(path) && vm.lastWritten && vm.files.has(vm.lastWritten))
|
|
439
|
+
path = vm.lastWritten;
|
|
440
|
+
vm.out.view = {
|
|
441
|
+
path,
|
|
442
|
+
title: args.length > 1 ? args[args.length - 1] : '',
|
|
443
|
+
lines: [...(vm.files.get(path) || [])],
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* filewrite(text): append a line to the open file.
|
|
449
|
+
* @type {IpoBuiltin}
|
|
450
|
+
*/
|
|
451
|
+
function bFilewrite(vm, stack) {
|
|
452
|
+
if (!vm.fh || vm.fh.mode === 'r') return;
|
|
453
|
+
// the line is usually computed (caption + value), not a literal
|
|
454
|
+
const args = stack.filter((x) => !isRef(x));
|
|
455
|
+
const txt = args.length ? asStr(args[0]) : '';
|
|
456
|
+
if (!vm.files.has(vm.fh.path)) vm.files.set(vm.fh.path, []);
|
|
457
|
+
vm.files.get(vm.fh.path).push(txt);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* fileread(->line): the next line of the open file, '' at its end.
|
|
462
|
+
* @type {IpoBuiltin}
|
|
463
|
+
*/
|
|
464
|
+
function bFileread(vm, stack) {
|
|
465
|
+
// fileread(&line, &status): status 0 while a line came, else the end --
|
|
466
|
+
// the save keys copy the protocol with `while (status == 0)`
|
|
467
|
+
let line = '';
|
|
468
|
+
let status = 1;
|
|
469
|
+
if (vm.fh && vm.fh.mode === 'r') {
|
|
470
|
+
const lines = vm.files.get(vm.fh.path) || [];
|
|
471
|
+
if (vm.fh.line < lines.length) {
|
|
472
|
+
line = lines[vm.fh.line];
|
|
473
|
+
vm.fh.line += 1;
|
|
474
|
+
status = 0;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const refs = stack.filter(isRef);
|
|
478
|
+
if (refs.length) storeOut(vm, [refs[0]], line);
|
|
479
|
+
if (refs.length > 1) storeOut(vm, [refs[1]], status);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* settimer(n, ms): arm one of INPA's per-script timer slots. A screen INIT
|
|
484
|
+
* arms one and a LINE tests it every cycle; without them the test never
|
|
485
|
+
* fires and a timed refresh runs on every tick instead.
|
|
486
|
+
* @type {IpoBuiltin}
|
|
487
|
+
*/
|
|
488
|
+
function bSettimer(vm, stack) {
|
|
489
|
+
const ints = allInts(stack);
|
|
490
|
+
if (ints.length < 2) return;
|
|
491
|
+
if (!vm.timers) vm.timers = new Map();
|
|
492
|
+
const now = typeof vm.now === 'function' ? vm.now() : Date.now();
|
|
493
|
+
vm.timers.set(ints[0], now + Math.max(0, ints[1]));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* testtimer(n, ->expired): 1 once the timer's deadline has passed.
|
|
498
|
+
* @type {IpoBuiltin}
|
|
499
|
+
*/
|
|
500
|
+
function bTesttimer(vm, stack) {
|
|
501
|
+
const ints = allInts(stack);
|
|
502
|
+
const n = ints.length ? ints[0] : 0;
|
|
503
|
+
const now = typeof vm.now === 'function' ? vm.now() : Date.now();
|
|
504
|
+
const due = vm.timers && vm.timers.has(n) ? vm.timers.get(n) : null;
|
|
505
|
+
const expired = due != null && now >= due ? 1 : 0;
|
|
506
|
+
storeOut(vm, stack, expired);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* setstate/start(&sm): enter a state machine. LIVE the machine is a guided
|
|
511
|
+
* procedure (togglelist -> job -> parks): running it through the offline
|
|
512
|
+
* executor here stopped at its first %STATE and the driver never saw the
|
|
513
|
+
* picker, so it is emitted for the driver to run. Offline it runs to its
|
|
514
|
+
* first yield and the job it fired is attributed to the item (the simple
|
|
515
|
+
* case; the togglelist/fixed-job recovery of ipo_vm.py's _b_setstate is a
|
|
516
|
+
* refinement not needed here).
|
|
517
|
+
* @type {IpoBuiltin}
|
|
518
|
+
*/
|
|
519
|
+
/**
|
|
520
|
+
* callstatemachine(sm): run another machine and come back to this one
|
|
521
|
+
* when it calls returnstatemachine(). The runtime enters the callee like
|
|
522
|
+
* a setstatemachine; the caller's name waits on a stack for the return.
|
|
523
|
+
* @type {IpoBuiltin}
|
|
524
|
+
*/
|
|
525
|
+
function bCallStatemachine(vm, stack, item) {
|
|
526
|
+
if (!vm.machineReturn) vm.machineReturn = [];
|
|
527
|
+
vm.machineReturn.push(vm.currentMachine || null);
|
|
528
|
+
bSetstate(vm, stack, item);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* returnstatemachine(): back to the machine that called this one, when
|
|
533
|
+
* one did; otherwise the machine simply ends.
|
|
534
|
+
* @type {IpoBuiltin}
|
|
535
|
+
*/
|
|
536
|
+
function bReturnStatemachine(vm) {
|
|
537
|
+
const back = vm.machineReturn ? vm.machineReturn.pop() : null;
|
|
538
|
+
if (back && vm.procs[back] && vm.wireJobs) vm.out.stateEnter = back;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* An interface the browser has no counterpart for -- the factory line's
|
|
543
|
+
* PLC (SPS*), the order system (ELDI*), the test management (DTM*, PEM*):
|
|
544
|
+
* every out-parameter reads 0 / "", so the script takes the path it has
|
|
545
|
+
* for "not available" instead of running on with unset slots.
|
|
546
|
+
* @type {IpoBuiltin}
|
|
547
|
+
*/
|
|
548
|
+
function bUnavailable(vm, stack) {
|
|
549
|
+
for (const r of stack.filter(isRef)) storeOut(vm, [r], 0);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function bSetstate(vm, stack, item) {
|
|
553
|
+
const ref = stack.find(
|
|
554
|
+
(x) => isRef(x) && (x[1] === IPO_REF_STATE || x[1] === IPO_REF_STATE_ALT)
|
|
555
|
+
);
|
|
556
|
+
if (ref == null) return;
|
|
557
|
+
const name = vm.byid('state', ref[2]);
|
|
558
|
+
if (!name || !vm.procs[name]) return;
|
|
559
|
+
if (item) item.stateEnter = name;
|
|
560
|
+
if (vm.wireJobs) {
|
|
561
|
+
vm.out.stateEnter = name;
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (vm.entered.has(name)) return;
|
|
565
|
+
vm.entered.add(name);
|
|
566
|
+
const before = vm.out.jobs.length;
|
|
567
|
+
try {
|
|
568
|
+
vm._exec(vm.procs[name], new Map());
|
|
569
|
+
} catch (e) {
|
|
570
|
+
if (!(e instanceof Halt)) throw e;
|
|
571
|
+
}
|
|
572
|
+
if (item && !('job' in item) && vm.out.jobs.length > before) {
|
|
573
|
+
const j = vm.out.jobs[before];
|
|
574
|
+
item.job = j.job;
|
|
575
|
+
item.stateJob = true;
|
|
576
|
+
if (j.arg && !item.jobArg) item.jobArg = j.arg;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
581
|
+
module.exports = {
|
|
582
|
+
IPO_INPUT_CONFIRMED,
|
|
583
|
+
bBmwebPick,
|
|
584
|
+
bBmwebStatus,
|
|
585
|
+
bJob,
|
|
586
|
+
bFsmode,
|
|
587
|
+
bCheckStatus,
|
|
588
|
+
bResult,
|
|
589
|
+
bResultInt,
|
|
590
|
+
bErrorCode,
|
|
591
|
+
bErrorText,
|
|
592
|
+
bResultSets,
|
|
593
|
+
bStrArrayCreate,
|
|
594
|
+
bStrArrayWrite,
|
|
595
|
+
bStrArrayRead,
|
|
596
|
+
bResultBinary,
|
|
597
|
+
bGetBinaryDataString,
|
|
598
|
+
bGetInputState,
|
|
599
|
+
bToggleList,
|
|
600
|
+
bInput,
|
|
601
|
+
bInputDigital,
|
|
602
|
+
bFileopen,
|
|
603
|
+
bFileclose,
|
|
604
|
+
bFilewrite,
|
|
605
|
+
bFileread,
|
|
606
|
+
bSettimer,
|
|
607
|
+
bTesttimer,
|
|
608
|
+
bSetstate,
|
|
609
|
+
};
|
|
610
|
+
}
|