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,358 @@
1
+ /**
2
+ * @file BMWeb's home as an INPA script: the app's front door for any host
3
+ * that runs .IPO (the terminal UI first), written in INPA's own language and
4
+ * compiled by the app's own compiler. Pick a chassis, pick a module, and
5
+ * `scriptchange` hands the screen to that module's script, the way INPA's
6
+ * vehicle selection does.
7
+ *
8
+ * INPA's own start script (STARTUS.IPO) draws the info screen and reads the
9
+ * battery and ignition lamps, but the vehicle list itself lives in INPA.INI
10
+ * and the CFGDAT menus, read by INPA's program rather than by a script. Our
11
+ * script gets that list from the host through one builtin of the app's own,
12
+ * `bmweb_pick`, declared in home/bmweb.h beside the INPA builtins it uses.
13
+ *
14
+ * The source lives in app/renderer/home/ (the .ips and .h are the files a
15
+ * person reads and the CLI ships); the copies below are what the app
16
+ * compiles, and a test pins the two as identical.
17
+ */
18
+
19
+ /* exported IPO_HOME_SGBD, IPO_HOME_HOST, ipoHomeSource, ipoHomeExec,
20
+ ipoPickHome, ipoHomeEcuFor, showIpoHome */
21
+
22
+ /** The SGBD name the home is seeded under: no BMW file has it. */
23
+ const IPO_HOME_SGBD = 'bmweb_home';
24
+
25
+ /** home/bmweb.h, verbatim. */
26
+ const IPO_HOME_H = `// BMWeb's own include for scripts of its own: the INPA builtins those
27
+ // scripts call, declared the way inpa.h declares them (parameter modes are
28
+ // what the compiler needs to pass out-parameters by reference), plus the
29
+ // two builtins BMWeb adds so a script can ask the app for a pick.
30
+ //
31
+ // Written by the BMWeb project. Nothing here is copied from BMW's headers.
32
+
33
+ // INPA builtins used by the home script
34
+ extern setmenutitle( in: string title);
35
+ extern settitle( in: string title);
36
+ extern setmenu( in: menu name);
37
+ extern setscreen( in: screen name, in: bool flag);
38
+ extern scriptchange( in: string NewScriptFile);
39
+ extern printscreen();
40
+ extern exit();
41
+ extern messagebox( in: string Title, in: string Text);
42
+ extern ftextout( in: string text, in: int row, in: int col, in: int attr, in: int mode);
43
+
44
+ // BMWeb builtins (0xE0 and up; unused by INPA)
45
+ // bmweb_pick("chassis", "", chassis) the app lists its chassis, the user picks one
46
+ // bmweb_pick("module", chassis, sgbd) the modules of that chassis, picked one's SGBD
47
+ // bmweb_pick("vehicle", chassis, sgbd) the chassis's whole-vehicle script, "" if none
48
+ // A cancelled pick leaves the out-string empty.
49
+ extern bmweb_pick( in: string what, in: string arg, out: string choice);
50
+ // bmweb_status(text) one line from the app: cable state, version
51
+ extern bmweb_status( out: string text);
52
+ `;
53
+
54
+ /** home/bmweb-home.ips, verbatim. */
55
+ const IPO_HOME_IPS = `//**********************************************************************
56
+ //*
57
+ //* BMWeb home: the app's front door as an INPA script, so a terminal (or
58
+ //* any host that runs .IPO) starts where the app starts. Pick a chassis,
59
+ //* pick a module, and scriptchange hands the screen to that module's own
60
+ //* INPA script, exactly as INPA's vehicle selection does.
61
+ //*
62
+ //* Written by the BMWeb project.
63
+ //*
64
+ //**********************************************************************
65
+ #include "bmweb.h"
66
+
67
+ string chassis = "";
68
+ string module = "";
69
+ string status = "";
70
+
71
+ inpainit()
72
+ {
73
+ settitle("BMWeb");
74
+ setscreen(s_main, TRUE);
75
+ setmenu(m_main);
76
+ }
77
+
78
+ MENU m_main()
79
+ {
80
+ INIT {
81
+ setmenutitle("BMWeb");
82
+ }
83
+ ITEM( 1 ,"Vehicle") {
84
+ bmweb_pick("chassis", "", chassis);
85
+ if (chassis != "")
86
+ {
87
+ bmweb_pick("module", chassis, module);
88
+ if (module != "")
89
+ {
90
+ scriptchange(module);
91
+ }
92
+ }
93
+ }
94
+ ITEM( 2 ,"Error scan") {
95
+ bmweb_pick("chassis", "", chassis);
96
+ if (chassis != "")
97
+ {
98
+ bmweb_pick("vehicle", chassis, module);
99
+ if (module != "")
100
+ {
101
+ scriptchange(module);
102
+ }
103
+ else
104
+ {
105
+ messagebox("Error scan", "INPA ships no whole-vehicle script for this chassis.");
106
+ }
107
+ }
108
+ }
109
+ ITEM( 9 ,"Print") {
110
+ printscreen();
111
+ }
112
+ ITEM( 20 ,"Exit") {
113
+ exit();
114
+ }
115
+ }
116
+
117
+ SCREEN s_main()
118
+ {
119
+ bmweb_status(status);
120
+ ftextout("BMWeb", 1, 0, 1, 0);
121
+ ftextout("", 3, 0, 0, 0);
122
+ LINE("","")
123
+ {
124
+ ftextout("< F1 > Vehicle: pick a chassis, then a module", 4, 5, 0, 1);
125
+ ftextout("< F2 > Error scan: INPA's whole-vehicle script", 6, 5, 0, 1);
126
+ ftextout("< F9 > Print", 20, 5, 0, 1);
127
+ ftextout("<Shift> + < F10> Exit", 22, 45, 0, 1);
128
+ ftextout(status, 12, 5, 0, 1);
129
+ }
130
+ }
131
+ `;
132
+
133
+ /**
134
+ * The home script's source and include, as compiled.
135
+ * @returns {{ips: string, h: string}}
136
+ */
137
+ function ipoHomeSource() {
138
+ return { ips: IPO_HOME_IPS, h: IPO_HOME_H };
139
+ }
140
+
141
+ /** @type {object|null} The compiled home, once. */
142
+ let _ipoHomeExec = null;
143
+
144
+ /**
145
+ * Compile the home script (once) and seed it under IPO_HOME_SGBD so the
146
+ * runtime opens it like any shipped module.
147
+ * @returns {object} the exec
148
+ */
149
+ function ipoHomeExec() {
150
+ if (_ipoHomeExec) return _ipoHomeExec;
151
+ const src = ipoHomeSource();
152
+ const r = ipofCompileSource(src.ips, { files: { 'bmweb.h': src.h } });
153
+ if (!r.ok || !r.exec) {
154
+ const why = (r.errors || []).map((e) => e.message).join('; ');
155
+ throw new Error(`the home script did not compile: ${why}`);
156
+ }
157
+ _ipoHomeExec = r.exec;
158
+ if (typeof irSeedExec === 'function') irSeedExec(IPO_HOME_SGBD, r.exec);
159
+ return r.exec;
160
+ }
161
+
162
+ /**
163
+ * What the host offers the home script's picks. The browser's default reads
164
+ * the app's own chassis tree; a terminal host replaces it with its own.
165
+ * @typedef {object} IpoHomeHost
166
+ * @property {() => Promise<Array<{value: string, label: string, meta?: string}>>} chassis - every chassis, by id
167
+ * @property {(chassis: string) => Promise<Array<{value: string, label: string, meta?: string}>>} modules - the modules of one chassis; value is the SGBD
168
+ * @property {(chassis: string) => Promise<string>} vehicle - the whole-vehicle script's SGBD stem, '' when none ships
169
+ * @property {() => string} status - one line for the home screen
170
+ */
171
+
172
+ /** @type {IpoHomeHost} */
173
+ const IPO_HOME_HOST = {
174
+ chassis: async () => {
175
+ const ids = await api('/api/chassis');
176
+ return (ids || []).map((id) => ({
177
+ value: id,
178
+ label: typeof dispChassis === 'function' ? dispChassis(id) : id,
179
+ meta: (typeof CHASSIS_TAG !== 'undefined' && CHASSIS_TAG[id]) || '',
180
+ }));
181
+ },
182
+ modules: async (chassis) => {
183
+ const ch = await api(`/api/chassis/${encodeURIComponent(chassis)}`);
184
+ const out = [];
185
+ for (const sec of (ch && ch.sections) || [])
186
+ for (const ecu of sec.ecus || [])
187
+ if (ecu && ecu.sgbd)
188
+ out.push({
189
+ value: String(ecu.sgbd).toLowerCase(),
190
+ label: ecu.label || ecu.code || ecu.sgbd,
191
+ meta: `${sec.name || ''} · ${ecu.sgbd}`,
192
+ });
193
+ return out;
194
+ },
195
+ vehicle: async (chassis) =>
196
+ typeof vehicleScriptShipped === 'function' &&
197
+ (await vehicleScriptShipped(chassis))
198
+ ? String(chassis).toLowerCase()
199
+ : '',
200
+ status: () => {
201
+ const cable =
202
+ typeof webBus !== 'undefined' && webBus && webBus.connected
203
+ ? 'cable connected'
204
+ : 'no cable';
205
+ const ver =
206
+ typeof APP_VERSION !== 'undefined' ? `BMWeb ${APP_VERSION}` : 'BMWeb';
207
+ return `${ver} · ${cable}`;
208
+ },
209
+ };
210
+
211
+ /**
212
+ * The picker for a bmweb_pick suspension: the host's list for `step.what`
213
+ * in a dialog with a filter box; resolves to the chosen value, or null on
214
+ * cancel. A "vehicle" pick has one answer and needs no dialog.
215
+ * @param {object} p - the program
216
+ * @param {{what: string, arg: string}} step - the pick suspension
217
+ * @returns {Promise<string|null>}
218
+ */
219
+ async function ipoPickHome(p, step) {
220
+ const host = IPO_HOME_HOST;
221
+ if (step.what === 'vehicle') return host.vehicle(step.arg);
222
+ let options = [];
223
+ try {
224
+ options =
225
+ step.what === 'module'
226
+ ? await host.modules(step.arg)
227
+ : await host.chassis();
228
+ } catch (e) {
229
+ options = [];
230
+ }
231
+ const title = step.what === 'module' ? `Modules of ${step.arg}` : 'Vehicles';
232
+ return new Promise((resolve) => {
233
+ const rows = (list) =>
234
+ list
235
+ .map(
236
+ (o, i) =>
237
+ `<label class="ipo-pick-row" data-i="${i}"><input type="radio" name="ipo-home" value="${i}"/> ` +
238
+ `<span>${esc(o.label)}${o.meta ? ` <span class="ipo-pick-n">${esc(o.meta)}</span>` : ''}</span></label>`
239
+ )
240
+ .join('');
241
+ const { overlay, close } = openModal(
242
+ `<div class="modal" role="dialog" aria-modal="true">
243
+ <div class="modal-title">${esc(title)}</div>
244
+ <div class="modal-body ipo-pick">
245
+ <input class="modal-input ipo-home-filter" type="text" placeholder="Filter…" spellcheck="false" autocomplete="off"/>
246
+ <div class="ipo-home-rows">${rows(options)}</div>
247
+ </div>
248
+ <div class="modal-actions">
249
+ <button class="btn" data-x="cancel">Cancel</button>
250
+ <button class="btn primary" data-x="ok">Open</button>
251
+ </div></div>`,
252
+ { onClose: () => resolve(null) }
253
+ );
254
+ let settled = false;
255
+ const done = (v) => {
256
+ if (settled) return;
257
+ settled = true;
258
+ resolve(v);
259
+ close();
260
+ };
261
+ const filter = overlay.querySelector('.ipo-home-filter');
262
+ const rowsEl = overlay.querySelector('.ipo-home-rows');
263
+ filter.oninput = () => {
264
+ const q = filter.value.trim().toLowerCase();
265
+ rowsEl.querySelectorAll('.ipo-pick-row').forEach((row) => {
266
+ const o = options[Number(row.dataset.i)];
267
+ row.hidden =
268
+ !!q &&
269
+ !`${o.label} ${o.meta || ''} ${o.value}`.toLowerCase().includes(q);
270
+ });
271
+ };
272
+ rowsEl.ondblclick = (ev) => {
273
+ const row = ev.target.closest('.ipo-pick-row');
274
+ if (row) done(options[Number(row.dataset.i)].value);
275
+ };
276
+ overlay.querySelectorAll('[data-x]').forEach((b) => {
277
+ b.onclick = () => {
278
+ if (b.dataset.x === 'cancel') return done(null);
279
+ const picked = overlay.querySelector('input[name="ipo-home"]:checked');
280
+ if (picked) done(options[Number(picked.value)].value);
281
+ };
282
+ });
283
+ setTimeout(() => filter.focus(), 10);
284
+ });
285
+ }
286
+
287
+ /**
288
+ * The module record a home pick names, for the runtime's scriptchange: the
289
+ * car's own row from the chassis config (so the module identifies itself on
290
+ * entry the way opening it from the module list does), or the whole-vehicle
291
+ * script's synthetic record.
292
+ * @param {object} from - the home program's ecu (its chassis is the pick)
293
+ * @param {string} script - the SGBD the script named
294
+ * @returns {Promise<object|null>}
295
+ */
296
+ async function ipoHomeEcuFor(from, script) {
297
+ const chassis = from && from.chassis ? String(from.chassis) : '';
298
+ const want = String(script || '').toLowerCase();
299
+ if (!chassis || !want) return null;
300
+ if (want === chassis.toLowerCase()) {
301
+ return {
302
+ code: chassis,
303
+ sgbd: want,
304
+ label: `INPA ${typeof dispChassis === 'function' ? dispChassis(chassis) : chassis} script`,
305
+ group: null,
306
+ kind: 'vehicle',
307
+ chassis,
308
+ };
309
+ }
310
+ const ch = await api(`/api/chassis/${encodeURIComponent(chassis)}`);
311
+ for (const sec of (ch && ch.sections) || [])
312
+ for (const ecu of sec.ecus || [])
313
+ if (ecu && String(ecu.sgbd || '').toLowerCase() === want)
314
+ return Object.assign({}, ecu, { chassis, section: sec.name || '' });
315
+ return null;
316
+ }
317
+
318
+ /**
319
+ * The home script as a screen of the app (#inpa): the same runtime and the
320
+ * same picker a terminal host uses, so the script is exercised where it can
321
+ * be seen.
322
+ * @returns {Promise<void>}
323
+ */
324
+ async function showIpoHome() {
325
+ lastScreen = showIpoHome;
326
+ setCrumbs([{ label: 'Vehicles', fn: showChassis }, { label: 'INPA home' }]);
327
+ sbLeft.textContent = 'bmweb home';
328
+ view.innerHTML = head('INPA', 'BMWeb', 'The app as an INPA script.');
329
+ const host = document.createElement('div');
330
+ host.className = 'results-panel';
331
+ view.appendChild(host);
332
+ let exec;
333
+ try {
334
+ exec = ipoHomeExec();
335
+ } catch (e) {
336
+ host.innerHTML = errorBlock(String(e.message || e));
337
+ return;
338
+ }
339
+ const ecu = {
340
+ sgbd: IPO_HOME_SGBD,
341
+ code: 'BMWEB',
342
+ label: 'BMWeb',
343
+ _variant: IPO_HOME_SGBD.toUpperCase(),
344
+ _homeExec: exec,
345
+ };
346
+ if (typeof ipoProgramOpen === 'function')
347
+ await ipoProgramOpen(ecu, host, showChassis);
348
+ }
349
+
350
+ if (typeof module !== 'undefined' && module.exports) {
351
+ module.exports = {
352
+ IPO_HOME_SGBD,
353
+ IPO_HOME_HOST,
354
+ ipoHomeSource,
355
+ ipoHomeExec,
356
+ ipoHomeEcuFor,
357
+ };
358
+ }