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,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builtins that shape what the user sees: titles, menu items, menu and
|
|
3
|
+
* screen switches, printed text, the lamp and bar instruments, message
|
|
4
|
+
* boxes, and the key actions (select, deselect, exit, print, scriptchange).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** A unit key: the *_EINH/*_EINHEIT result drawn beside a value. */
|
|
8
|
+
const IPO_UNIT_KEY_RE = /(_EINH|_EINHEIT)$/;
|
|
9
|
+
|
|
10
|
+
/** Format-string builtin argument of analogout: "<width>.<decimals>". */
|
|
11
|
+
const IPO_ANALOG_FMT_RE = /^(\d+)\.(\d+)$/;
|
|
12
|
+
|
|
13
|
+
/** toFixed()'s upper bound on decimals; a stray format never asks for more. */
|
|
14
|
+
const IPO_MAX_DECIMALS = 20;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* settitle / setmenutitle: the first argument is the title.
|
|
18
|
+
* @type {IpoBuiltin}
|
|
19
|
+
*/
|
|
20
|
+
function bSetTitle(vm, stack) {
|
|
21
|
+
if (stack.length) vm.out.title = asStr(stack[0]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* setitem(nr, caption): a menu item declared by call rather than ITEM token.
|
|
26
|
+
* @type {IpoBuiltin}
|
|
27
|
+
*/
|
|
28
|
+
function bSetitem(vm, stack) {
|
|
29
|
+
// setitem(nr, caption[, shown]): the third argument shows (1) or hides
|
|
30
|
+
// (0) the key -- E46.IPO's read enables "FS drucken" on F9 once there is
|
|
31
|
+
// a protocol to print, and the save keys hide it again
|
|
32
|
+
const ints = allInts(stack);
|
|
33
|
+
const nr = ints.length ? ints[0] : null;
|
|
34
|
+
const cap = stack.find(isPlainStr);
|
|
35
|
+
const on = ints.length > 1 ? ints[ints.length - 1] : null;
|
|
36
|
+
if (nr != null) vm.out.items.push({ nr, label: cap, on, fromSetitem: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* setmenu(&menu): hand control to a menu; the item that did it remembers.
|
|
41
|
+
* @type {IpoBuiltin}
|
|
42
|
+
*/
|
|
43
|
+
function bSetmenu(vm, stack, item) {
|
|
44
|
+
const ref = stack.find(isRef);
|
|
45
|
+
const tgt = vm.target(ref, 'menu');
|
|
46
|
+
if (tgt) {
|
|
47
|
+
vm.out.menu = tgt;
|
|
48
|
+
if (item) item.menu = tgt;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* setscreen(&screen, frequent): set the backdrop screen; the bool rides as a
|
|
54
|
+
* 0/1 const after the ref and says whether the screen re-runs on a timer.
|
|
55
|
+
* @type {IpoBuiltin}
|
|
56
|
+
*/
|
|
57
|
+
function bSetscreen(vm, stack, item) {
|
|
58
|
+
const ref = stack.find(isRef);
|
|
59
|
+
const tgt = vm.target(ref, 'screen');
|
|
60
|
+
if (tgt) {
|
|
61
|
+
vm.out.screen = tgt;
|
|
62
|
+
if (item) item.screen = tgt;
|
|
63
|
+
const flags = stack.filter((x) => isPlainInt(x) || typeof x === 'boolean');
|
|
64
|
+
vm.out.screenFrequent = flags.length ? !!flags[flags.length - 1] : false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The line the next element lands on: the last one, opened if none exists.
|
|
70
|
+
* @param {IpoVm} vm - the running VM
|
|
71
|
+
* @returns {IpoLine}
|
|
72
|
+
*/
|
|
73
|
+
function currentLine(vm) {
|
|
74
|
+
if (!vm.out.lines.length) vm.out.lines.push({ label: null, elements: [] });
|
|
75
|
+
return vm.out.lines[vm.out.lines.length - 1];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* ftextout(text/slot, row, col, ...): a printed literal, or a printed VALUE
|
|
80
|
+
* whose key comes from the binding. A bound value wins over a literal. A
|
|
81
|
+
* drawn unit folds onto the value element sharing its base key instead of
|
|
82
|
+
* becoming an element of its own.
|
|
83
|
+
* @type {IpoBuiltin}
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* userboxopen(x, y, w, title, ...): INPA's progress window. The
|
|
87
|
+
* whole-vehicle scripts open one over a long read and write the module
|
|
88
|
+
* being asked into it (userboxftextout); a live run shows it as it fills.
|
|
89
|
+
* @type {IpoBuiltin}
|
|
90
|
+
*/
|
|
91
|
+
function bUserboxOpen(vm, stack) {
|
|
92
|
+
const strs = stack.filter((x) => isPlainStr(x) || isBound(x)).map(asStr);
|
|
93
|
+
vm.userbox = { title: strs.find((s) => s.trim()) || '', lines: [] };
|
|
94
|
+
if (vm.onUserbox) vm.onUserbox(vm.userbox);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* userboxclose(): the progress window goes away.
|
|
99
|
+
* @type {IpoBuiltin}
|
|
100
|
+
*/
|
|
101
|
+
function bUserboxClose(vm) {
|
|
102
|
+
vm.userbox = null;
|
|
103
|
+
if (vm.onUserbox) vm.onUserbox(null);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* userboxclear(): the window stays, its lines go.
|
|
108
|
+
* @type {IpoBuiltin}
|
|
109
|
+
*/
|
|
110
|
+
function bUserboxClear(vm) {
|
|
111
|
+
if (!vm.userbox) return;
|
|
112
|
+
vm.userbox.lines = [];
|
|
113
|
+
if (vm.onUserbox) vm.onUserbox(vm.userbox);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* userboxftextout(text, row, col ...): a line of the progress window. It is
|
|
118
|
+
* also a textout, so a body without a box paints it with the screen.
|
|
119
|
+
* @type {IpoBuiltin}
|
|
120
|
+
*/
|
|
121
|
+
function bUserboxTextout(vm, stack) {
|
|
122
|
+
if (vm.userbox) {
|
|
123
|
+
const t = firstText(stack);
|
|
124
|
+
if (t != null && String(asStr(t)).trim()) {
|
|
125
|
+
vm.userbox.lines.push(asStr(t));
|
|
126
|
+
if (vm.onUserbox) vm.onUserbox(vm.userbox);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
bTextout(vm, stack);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A screen area the body blanks: recorded for the runtime, and every
|
|
134
|
+
* element this cycle already printed inside it goes.
|
|
135
|
+
* @param {Best2Vm} vm
|
|
136
|
+
* @param {{row: number, col: number, h: number, w: number}} rect
|
|
137
|
+
*/
|
|
138
|
+
function ipoClearRect(vm, rect) {
|
|
139
|
+
vm.out.clears.push(rect);
|
|
140
|
+
const inside = (el) =>
|
|
141
|
+
el.row != null &&
|
|
142
|
+
el.col != null &&
|
|
143
|
+
el.row >= rect.row &&
|
|
144
|
+
el.row < rect.row + rect.h &&
|
|
145
|
+
el.col >= rect.col &&
|
|
146
|
+
el.col < rect.col + rect.w;
|
|
147
|
+
for (const ln of vm.out.lines) {
|
|
148
|
+
if (ln.elements) ln.elements = ln.elements.filter((el) => !inside(el));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* clearrect(row, col, height, width): blank a rectangle of the screen
|
|
154
|
+
* (the activation screens clear a gauge area before redrawing it).
|
|
155
|
+
* @type {IpoBuiltin}
|
|
156
|
+
*/
|
|
157
|
+
function bClearRect(vm, stack) {
|
|
158
|
+
const n = allInts(stack);
|
|
159
|
+
if (n.length < 4) return;
|
|
160
|
+
ipoClearRect(vm, {
|
|
161
|
+
row: n[0],
|
|
162
|
+
col: n[1],
|
|
163
|
+
h: Math.max(0, n[2]),
|
|
164
|
+
w: Math.max(0, n[3]),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* ftextclear(text, row, col, size, attr): erase a text printed with
|
|
170
|
+
* ftextout at the same place ("switch the ignition on" banners come and
|
|
171
|
+
* go this way).
|
|
172
|
+
* @type {IpoBuiltin}
|
|
173
|
+
*/
|
|
174
|
+
function bFtextClear(vm, stack) {
|
|
175
|
+
const t = firstText(stack);
|
|
176
|
+
const n = allInts(stack);
|
|
177
|
+
if (n.length < 2) return;
|
|
178
|
+
const len = t == null ? 1 : Math.max(1, asStr(t).length);
|
|
179
|
+
ipoClearRect(vm, { row: n[0], col: n[1], h: 1, w: len });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function bTextout(vm, stack) {
|
|
183
|
+
if (vm.onText) {
|
|
184
|
+
const t = firstText(stack);
|
|
185
|
+
if (t != null && String(t).trim()) vm.onText(asStr(t));
|
|
186
|
+
}
|
|
187
|
+
let key = null,
|
|
188
|
+
also = [],
|
|
189
|
+
liveText = null;
|
|
190
|
+
for (const x of stack) {
|
|
191
|
+
if (isBound(x) && x.key) {
|
|
192
|
+
key = x.key;
|
|
193
|
+
also = (x.extra || []).filter((k) => k !== key);
|
|
194
|
+
liveText = x.s;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
const sl = isBound(x) ? x.slot : isSlot(x) ? x : null;
|
|
198
|
+
if (sl != null) {
|
|
199
|
+
key = vm.bindKey(sl.sc, sl.n);
|
|
200
|
+
if (key) break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const lit = firstStr(stack);
|
|
204
|
+
if (key == null && lit == null) return;
|
|
205
|
+
const ints = allInts(stack);
|
|
206
|
+
const line = currentLine(vm);
|
|
207
|
+
if (key && IPO_UNIT_KEY_RE.test(key.toUpperCase())) {
|
|
208
|
+
for (let e = line.elements.length - 1; e >= 0; e--) {
|
|
209
|
+
const el = line.elements[e];
|
|
210
|
+
if (
|
|
211
|
+
el.key &&
|
|
212
|
+
baseKey(el.key) === baseKey(key) &&
|
|
213
|
+
!IPO_UNIT_KEY_RE.test(el.key.toUpperCase())
|
|
214
|
+
) {
|
|
215
|
+
el.unit = key;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** @type {IpoElement} */
|
|
221
|
+
let el;
|
|
222
|
+
if (key) {
|
|
223
|
+
el = { t: 'value', key };
|
|
224
|
+
// the text the value HAD when drawn: a live run fed real results, and
|
|
225
|
+
// the painter shows this rather than polling the key again
|
|
226
|
+
if (vm.wireJobs && liveText != null && liveText !== '') el.s = liveText;
|
|
227
|
+
if (also.length) el.also = also;
|
|
228
|
+
const amap = stack.map((x) => (isBound(x) ? x.amap : null)).find((m) => m);
|
|
229
|
+
if (amap) el.map = amap;
|
|
230
|
+
} else {
|
|
231
|
+
el = { t: 'text', s: lit };
|
|
232
|
+
}
|
|
233
|
+
if (ints.length >= 2) {
|
|
234
|
+
el.row = ints[0];
|
|
235
|
+
el.col = ints[1];
|
|
236
|
+
}
|
|
237
|
+
line.elements.push(el);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The element a lamp or bar builtin declares, placed on the current line.
|
|
242
|
+
* Offline the row/col come from the int scan the Python twin uses (for
|
|
243
|
+
* byte-identical IR); LIVE the value comes first and may itself be an int
|
|
244
|
+
* (digitalout's bool arrives as 1/0 from a result compare), so per Inpa.h's
|
|
245
|
+
* (val, row, col, ...) the two ints after the value are taken by position.
|
|
246
|
+
* Wire mode also fills the cell's text: digitalout shows one of its two words
|
|
247
|
+
* for the value it was handed, analogout shows the number; the offline twins
|
|
248
|
+
* emit the declaration, never a value.
|
|
249
|
+
* @param {IpoVm} vm - the running VM
|
|
250
|
+
* @param {IpoValue[]} stack - the call's arguments
|
|
251
|
+
* @param {'analog'|'digital'} kind - which instrument
|
|
252
|
+
* @returns {IpoElement} the element pushed
|
|
253
|
+
*/
|
|
254
|
+
function drawField(vm, stack, kind) {
|
|
255
|
+
const ints = allInts(stack);
|
|
256
|
+
const strs = allStrs(stack);
|
|
257
|
+
/** @type {IpoElement} */
|
|
258
|
+
const el = { t: kind === 'analog' ? 'gauge' : 'lamp' };
|
|
259
|
+
if (ints.length >= 2) {
|
|
260
|
+
el.row = ints[0];
|
|
261
|
+
el.col = ints[1];
|
|
262
|
+
}
|
|
263
|
+
if (vm.wireJobs && stack.length >= 3) {
|
|
264
|
+
const isInt = (x) => isPlainInt(x) && typeof x !== 'boolean';
|
|
265
|
+
if (isInt(stack[1]) && isInt(stack[2])) {
|
|
266
|
+
el.row = stack[1];
|
|
267
|
+
el.col = stack[2];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
let key = keyed(stack);
|
|
271
|
+
if (key == null) {
|
|
272
|
+
let sl = null;
|
|
273
|
+
for (const x of stack)
|
|
274
|
+
if (isBound(x) && x.slot) {
|
|
275
|
+
sl = x.slot;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
if (sl == null) sl = stack.find(isSlot) || null;
|
|
279
|
+
if (sl != null) key = vm.bindKey(sl.sc, sl.n);
|
|
280
|
+
}
|
|
281
|
+
if (key) el.key = key;
|
|
282
|
+
if (kind === 'digital' && strs.length >= 2) {
|
|
283
|
+
el.on = strs[strs.length - 2].trim();
|
|
284
|
+
el.off = strs[strs.length - 1].trim();
|
|
285
|
+
}
|
|
286
|
+
if (vm.wireJobs && stack.length) {
|
|
287
|
+
const v = stack[0];
|
|
288
|
+
const n = isBound(v)
|
|
289
|
+
? parseFloat(v.s)
|
|
290
|
+
: isFloat(v)
|
|
291
|
+
? v.v
|
|
292
|
+
: typeof v === 'number' || typeof v === 'boolean'
|
|
293
|
+
? Number(v)
|
|
294
|
+
: NaN;
|
|
295
|
+
if (kind === 'digital') {
|
|
296
|
+
const on = !Number.isNaN(n) ? n !== 0 : isBound(v) && !!v.s.trim();
|
|
297
|
+
el.s = on ? (el.on != null ? el.on : '1') : el.off != null ? el.off : '0';
|
|
298
|
+
} else {
|
|
299
|
+
el.s = Number.isNaN(n) ? (isBound(v) ? v.s : '') : String(n);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
currentLine(vm).elements.push(el);
|
|
303
|
+
return el;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* analogout(val, row, col, min, max, minvalid, maxvalid, fmt): a bar. The
|
|
308
|
+
* bounds are the numbers after the first three arguments (ints and floats
|
|
309
|
+
* both count, as in Python: `[x for x in stack[3:] if isinstance(x,
|
|
310
|
+
* (int,float)) and not bool]`); the format is the first non-blank string-like
|
|
311
|
+
* argument (a fmt built by concatenation is a bound value).
|
|
312
|
+
* @type {IpoBuiltin}
|
|
313
|
+
*/
|
|
314
|
+
function bAnalogout(vm, stack) {
|
|
315
|
+
const el = drawField(vm, stack, 'analog');
|
|
316
|
+
const nums = stack
|
|
317
|
+
.slice(3)
|
|
318
|
+
.filter(
|
|
319
|
+
(x) => isFloat(x) || (typeof x === 'number' && typeof x !== 'boolean')
|
|
320
|
+
)
|
|
321
|
+
.map(num);
|
|
322
|
+
if (nums.length >= 2) {
|
|
323
|
+
el.min = nums[0];
|
|
324
|
+
el.max = nums[1];
|
|
325
|
+
}
|
|
326
|
+
if (nums.length >= 4) {
|
|
327
|
+
el.warnLo = nums[2];
|
|
328
|
+
el.warnHi = nums[3];
|
|
329
|
+
}
|
|
330
|
+
const fmt = stack.find(
|
|
331
|
+
(x) => (isPlainStr(x) || isBound(x)) && asStr(x).trim()
|
|
332
|
+
);
|
|
333
|
+
if (fmt) el.fmt = asStr(fmt).trim();
|
|
334
|
+
// live text in the declared format ("6.2" = width 6, 2 decimals). The
|
|
335
|
+
// format is the LAST plain string argument; the first string-like value on
|
|
336
|
+
// the stack can be the reading itself (a bound real with a long tail),
|
|
337
|
+
// which is not a format and once asked toFixed for 100+ digits.
|
|
338
|
+
if (vm.wireJobs && el.s != null) {
|
|
339
|
+
let fmtStr = null;
|
|
340
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
341
|
+
if (isPlainStr(stack[k]) && stack[k].trim()) {
|
|
342
|
+
fmtStr = stack[k].trim();
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const m = fmtStr ? IPO_ANALOG_FMT_RE.exec(fmtStr) : null;
|
|
347
|
+
const n = Number(el.s);
|
|
348
|
+
if (m && !Number.isNaN(n)) {
|
|
349
|
+
const digits = Math.max(0, Math.min(IPO_MAX_DECIMALS, Number(m[2])));
|
|
350
|
+
el.s = n.toFixed(digits);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* multianalogout: several analogout groups in one call, each ended by its
|
|
357
|
+
* non-blank format string; a call with no format is one plain analogout.
|
|
358
|
+
* @type {IpoBuiltin}
|
|
359
|
+
*/
|
|
360
|
+
function bMultiAnalogout(vm, stack) {
|
|
361
|
+
let group = [],
|
|
362
|
+
drawn = 0;
|
|
363
|
+
for (const x of stack) {
|
|
364
|
+
group.push(x);
|
|
365
|
+
if (isPlainStr(x) && x.trim()) {
|
|
366
|
+
bAnalogout(vm, group);
|
|
367
|
+
drawn += 1;
|
|
368
|
+
group = [];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (!drawn) bAnalogout(vm, stack);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* digitalout(val, row, col, TrueText, FalseText): a lamp.
|
|
376
|
+
* @type {IpoBuiltin}
|
|
377
|
+
*/
|
|
378
|
+
function bDigitalout(vm, stack) {
|
|
379
|
+
drawField(vm, stack, 'digital');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* messagebox(title, body): recorded, and tapped to onText. The body is often
|
|
384
|
+
* a concatenation, so a bound value counts as text (Python's isinstance str).
|
|
385
|
+
* @type {IpoBuiltin}
|
|
386
|
+
*/
|
|
387
|
+
function bMessage(vm, stack, item) {
|
|
388
|
+
const strs = stack.filter((x) => isPlainStr(x) || isBound(x)).map(asStr);
|
|
389
|
+
if (vm.onText && strs.length) vm.onText(strs.join(' — '));
|
|
390
|
+
if (strs.length) {
|
|
391
|
+
vm.out.messages.push({
|
|
392
|
+
title: strs[0],
|
|
393
|
+
body: strs.length > 1 ? strs[1] : null,
|
|
394
|
+
});
|
|
395
|
+
if (item) (item.messages = item.messages || []).push(strs[0]);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* select(): the item is INPA's Select key (a line filter picker).
|
|
401
|
+
* @type {IpoBuiltin}
|
|
402
|
+
*/
|
|
403
|
+
function bSelect(vm, stack, item) {
|
|
404
|
+
if (item && !item.action) item.action = 'select';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* deselect(): the item is INPA's Deselect key; LIVE, every logical line shows
|
|
409
|
+
* again.
|
|
410
|
+
* @type {IpoBuiltin}
|
|
411
|
+
*/
|
|
412
|
+
function bDeselect(vm, stack, item) {
|
|
413
|
+
if (item && !item.action) item.action = 'deselect';
|
|
414
|
+
if (vm.wireJobs) vm.out.deselect = true;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* exit(): the script ends itself.
|
|
419
|
+
* @type {IpoBuiltin}
|
|
420
|
+
*/
|
|
421
|
+
function bExit(vm, stack, item) {
|
|
422
|
+
if (item) item.action = 'exit';
|
|
423
|
+
vm.out.exit = true;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* blankscreen: the next paint starts from an empty grid.
|
|
428
|
+
* @type {IpoBuiltin}
|
|
429
|
+
*/
|
|
430
|
+
function bBlankscreen(vm) {
|
|
431
|
+
vm.out.blank = true;
|
|
432
|
+
vm.out.lines = [];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* printscreen: the item is INPA's Print key.
|
|
437
|
+
* @type {IpoBuiltin}
|
|
438
|
+
*/
|
|
439
|
+
function bPrint(vm, stack, item) {
|
|
440
|
+
if (item) item.action = 'printscreen';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* scriptchange("IHKA46") hands the WHOLE UI to another .IPO: INPA unloads
|
|
445
|
+
* this script and runs the named one, inpainit and all. KLIMA_5B does it from
|
|
446
|
+
* its variant check (IHKA46_3 -> IHKA46, IHKA85 -> IHKX85), so a script that
|
|
447
|
+
* never names a variant in its own menus is still correct -- it left before
|
|
448
|
+
* the menu drew. Record the target for the entry gate to follow; the key
|
|
449
|
+
* itself stays an app-side tool.
|
|
450
|
+
* @type {IpoBuiltin}
|
|
451
|
+
*/
|
|
452
|
+
function bScriptchange(vm, stack, item) {
|
|
453
|
+
const name = firstText(stack);
|
|
454
|
+
if (name != null && asStr(name)) vm.out.scriptChange = asStr(name);
|
|
455
|
+
if (item) item.appTool = true;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* callwin: an external program; the item is an app-side tool.
|
|
460
|
+
* @type {IpoBuiltin}
|
|
461
|
+
*/
|
|
462
|
+
function bCallwin(vm, stack, item) {
|
|
463
|
+
if (item) item.appTool = true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* A builtin with no effect on the model (window chrome, colours, stop).
|
|
468
|
+
* @type {IpoBuiltin}
|
|
469
|
+
*/
|
|
470
|
+
function bNoop() {}
|
|
471
|
+
|
|
472
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
473
|
+
module.exports = {
|
|
474
|
+
bSetTitle,
|
|
475
|
+
bSetitem,
|
|
476
|
+
bSetmenu,
|
|
477
|
+
bSetscreen,
|
|
478
|
+
bTextout,
|
|
479
|
+
drawField,
|
|
480
|
+
bAnalogout,
|
|
481
|
+
bMultiAnalogout,
|
|
482
|
+
bDigitalout,
|
|
483
|
+
bMessage,
|
|
484
|
+
bSelect,
|
|
485
|
+
bDeselect,
|
|
486
|
+
bExit,
|
|
487
|
+
bBlankscreen,
|
|
488
|
+
bPrint,
|
|
489
|
+
bScriptchange,
|
|
490
|
+
bCallwin,
|
|
491
|
+
bNoop,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The builtin dispatch table: every `call` token name the VM answers,
|
|
3
|
+
* mapped to its implementation. Names the decoder could not resolve are
|
|
4
|
+
* keyed by number (`builtin_<hex>`); each of those carries the name the
|
|
5
|
+
* corpus proved it to be.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Builtin name -> implementation. A name absent here is a silent noop in
|
|
10
|
+
* `_builtin` (still recorded in emissions.calls).
|
|
11
|
+
* @type {Record<string, IpoBuiltin>}
|
|
12
|
+
*/
|
|
13
|
+
const BUILTINS = {
|
|
14
|
+
setmenutitle: bSetTitle,
|
|
15
|
+
settitle: bSetTitle,
|
|
16
|
+
setitem: bSetitem,
|
|
17
|
+
setmenu: bSetmenu,
|
|
18
|
+
setscreen: bSetscreen,
|
|
19
|
+
INPAapiJob: bJob,
|
|
20
|
+
INP1apiJob: bJob,
|
|
21
|
+
INPAapiFsMode: bFsmode,
|
|
22
|
+
INPAapiCheckJobStatus: bCheckStatus,
|
|
23
|
+
INPAapiResultText: bResult,
|
|
24
|
+
INPAapiResultAnalog: bResult,
|
|
25
|
+
INPAapiResultDigital: bResult,
|
|
26
|
+
INPAapiResultInt: bResultInt,
|
|
27
|
+
INP1apiResultText: bResult,
|
|
28
|
+
INP1apiResultInt: bResultInt,
|
|
29
|
+
ftextout: bTextout,
|
|
30
|
+
textout: bTextout,
|
|
31
|
+
text: bTextout,
|
|
32
|
+
userboxftextout: bUserboxTextout,
|
|
33
|
+
messagebox: bMessage,
|
|
34
|
+
builtin_53: bMessage,
|
|
35
|
+
exit: bExit,
|
|
36
|
+
printscreen: bPrint,
|
|
37
|
+
scriptchange: bScriptchange,
|
|
38
|
+
callwin: bCallwin,
|
|
39
|
+
analogout: bAnalogout,
|
|
40
|
+
digitalout: bDigitalout,
|
|
41
|
+
multianalogout: bMultiAnalogout,
|
|
42
|
+
strlen: bStrlen,
|
|
43
|
+
midstr: bMidstr,
|
|
44
|
+
inttostring: bInttostring,
|
|
45
|
+
inttolong: bIntwiden,
|
|
46
|
+
bytetoint: bIntwiden,
|
|
47
|
+
realtostring: bInttostring,
|
|
48
|
+
// the binary-structure helpers are noops OFFLINE (the Python twin's
|
|
49
|
+
// behaviour, parity-diffed); a live run answers them in ipoDriveBuiltin
|
|
50
|
+
SetStructureMode: bNoop,
|
|
51
|
+
CreateStructure: bNoop,
|
|
52
|
+
StructureByte: bNoop,
|
|
53
|
+
StructureString: bNoop,
|
|
54
|
+
StructureInt: bNoop,
|
|
55
|
+
StructureLong: bNoop,
|
|
56
|
+
userboxopen: bUserboxOpen,
|
|
57
|
+
userboxclose: bUserboxClose,
|
|
58
|
+
viewopen: bViewopen,
|
|
59
|
+
viewclose: bNoop,
|
|
60
|
+
setstate: bSetstate,
|
|
61
|
+
start: bSetstate,
|
|
62
|
+
select: bSelect,
|
|
63
|
+
deselect: bDeselect,
|
|
64
|
+
INPAapiInit: bNoop,
|
|
65
|
+
INPAapiEnd: bNoop,
|
|
66
|
+
INPAapiFsLesen: bNoop,
|
|
67
|
+
INP1apiErrorText: bErrorText,
|
|
68
|
+
INP1apiErrorCode: bErrorCode,
|
|
69
|
+
INP1apiResultSets: bResultSets,
|
|
70
|
+
getinputstate: bGetInputState,
|
|
71
|
+
inputhex: bInput,
|
|
72
|
+
inputdigital: bInputDigital,
|
|
73
|
+
input2hex: bInput,
|
|
74
|
+
builtin_47: bInput, // input2int (ACC: Kalenderwoche/Jahr)
|
|
75
|
+
builtin_3f: bInput,
|
|
76
|
+
builtin_40: bInput,
|
|
77
|
+
input2text: bInput,
|
|
78
|
+
input2hexnum: bInput,
|
|
79
|
+
inputint: bInput,
|
|
80
|
+
fileopen: bFileopen,
|
|
81
|
+
fileclose: bFileclose,
|
|
82
|
+
filewrite: bFilewrite,
|
|
83
|
+
fileread: bFileread,
|
|
84
|
+
hexdump: bNoop,
|
|
85
|
+
printfile: bNoop,
|
|
86
|
+
setstatemachine: bNoop,
|
|
87
|
+
StrArrayCreate: bStrArrayCreate,
|
|
88
|
+
StrArrayDestroy: bNoop,
|
|
89
|
+
StrArrayWrite: bStrArrayWrite,
|
|
90
|
+
StrArrayRead: bStrArrayRead,
|
|
91
|
+
StrArrayDelete: bNoop,
|
|
92
|
+
INPAapiResultBinary: bResultBinary,
|
|
93
|
+
GetBinaryDataString: bGetBinaryDataString,
|
|
94
|
+
// builtin_16 = togglelist: writes the picked row into an out variable.
|
|
95
|
+
// Offline a noop (the pick is runtime-only); when driven it stores the
|
|
96
|
+
// user's pick.
|
|
97
|
+
builtin_16: bToggleList,
|
|
98
|
+
builtin_12: bNoop, // control (0x12)
|
|
99
|
+
// --- coverage sweep 2026-08-27: shapes proven against the corpus ---
|
|
100
|
+
stringtoreal: bStringtoreal,
|
|
101
|
+
builtin_21: bStringtoint, // stringtoint
|
|
102
|
+
builtin_22: bHexconvert, // hexconvert
|
|
103
|
+
builtin_23: bStrcat, // strcat (dest ref FIRST)
|
|
104
|
+
builtin_26: bNumconvert, // inttoreal/realtoint family
|
|
105
|
+
// longtoreal(in long, out real): Inpa.h's extern after inttolong, and the
|
|
106
|
+
// fault printers use it that way (F_ORT_NR -> inttolong -> longtoreal ->
|
|
107
|
+
// realtostring). Unnamed, it was a no-op and every fault read "Nr: 5".
|
|
108
|
+
builtin_2a: bNumconvert,
|
|
109
|
+
longtoreal: bNumconvert,
|
|
110
|
+
formatnum: bInttostring, // (src, dst): number -> display
|
|
111
|
+
getdate: bGetdate,
|
|
112
|
+
gettime: bGettime,
|
|
113
|
+
builtin_15: bGetapistring, // getapistring(out s)
|
|
114
|
+
INPAapiResultSets: bResultSets, // single-ref INPA form
|
|
115
|
+
INP1apiResultBinary: bResultBinary,
|
|
116
|
+
builtin_74: bResult, // INP1apiResultReal(rc, val, KEY, set)
|
|
117
|
+
builtin_14: bNoop, // stop
|
|
118
|
+
// named from their call shapes (builtin-helpers.js IPO_BUILTIN_CANON)
|
|
119
|
+
callstatemachine: bCallStatemachine,
|
|
120
|
+
returnstatemachine: bReturnStatemachine,
|
|
121
|
+
setjobstatus: bNoop, // the exit status for a calling program
|
|
122
|
+
delay: bNoop, // a live run waits (suspensions.js IPO_WAIT_BUILTIN)
|
|
123
|
+
inputnum: bInput, // (out real, title, text, min, max)
|
|
124
|
+
inputtext: bInput, // (out string, title, text)
|
|
125
|
+
ftextclear: bFtextClear,
|
|
126
|
+
clearrect: bClearRect,
|
|
127
|
+
setitemrepeat: bNoop, // key auto-repeat
|
|
128
|
+
// the factory line's interfaces: PLC, order files, test management
|
|
129
|
+
SPSInit: bUnavailable,
|
|
130
|
+
SPSLeseVonSPS: bUnavailable,
|
|
131
|
+
SPSSendeAnSPS: bUnavailable,
|
|
132
|
+
ApiJobFsLesenFAB: bUnavailable,
|
|
133
|
+
ApiResultFsLesenFAB: bUnavailable,
|
|
134
|
+
ELDIOpenStartDialog: bUnavailable,
|
|
135
|
+
// DTM / PEM calls (an out-reference and a key): which of the family
|
|
136
|
+
// each number is cannot be told from the corpus; all are unavailable here
|
|
137
|
+
builtin_0e: bUnavailable,
|
|
138
|
+
builtin_2d: bUnavailable,
|
|
139
|
+
builtin_36: bUnavailable,
|
|
140
|
+
builtin_3c: bUnavailable,
|
|
141
|
+
builtin_3d: bUnavailable,
|
|
142
|
+
builtin_70: bUnavailable,
|
|
143
|
+
builtin_7d: bUnavailable,
|
|
144
|
+
builtin_7e: bUnavailable,
|
|
145
|
+
builtin_80: bUnavailable,
|
|
146
|
+
builtin_81: bUnavailable,
|
|
147
|
+
builtin_87: bUnavailable,
|
|
148
|
+
builtin_93: bUnavailable,
|
|
149
|
+
builtin_90: bStrArraySize, // string array length, out-param
|
|
150
|
+
builtin_1a: bNoop, // setcolor
|
|
151
|
+
builtin_51: bBlankscreen, // blankscreen
|
|
152
|
+
blankscreen: bBlankscreen,
|
|
153
|
+
settimer: bSettimer,
|
|
154
|
+
testtimer: bTesttimer,
|
|
155
|
+
builtin_09: bSettimer,
|
|
156
|
+
builtin_0a: bTesttimer,
|
|
157
|
+
// BMWeb's own (home/bmweb.h): a pick from the host, a status line
|
|
158
|
+
bmweb_pick: bBmwebPick,
|
|
159
|
+
bmweb_status: bBmwebStatus,
|
|
160
|
+
builtin_57: bUserboxClear, // userboxclear
|
|
161
|
+
builtin_58: bNoop, // userboxsetcolor
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
165
|
+
module.exports = { BUILTINS };
|
|
166
|
+
}
|