@hoox-sh/pynets 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 (48) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +661 -0
  3. package/NOTICE +46 -0
  4. package/README.md +327 -0
  5. package/dist/browser.js +44 -0
  6. package/dist/index.d.ts +348 -0
  7. package/dist/index.js +41741 -0
  8. package/package.json +77 -0
  9. package/src/ast/builder.ts +1002 -0
  10. package/src/ast/dump.ts +43 -0
  11. package/src/ast/helper.ts +77 -0
  12. package/src/ast/nodes.ts +570 -0
  13. package/src/ast/unparse.ts +360 -0
  14. package/src/cli/rich.ts +378 -0
  15. package/src/cli.ts +627 -0
  16. package/src/generated/PinescriptLexer.interp +267 -0
  17. package/src/generated/PinescriptLexer.tokens +131 -0
  18. package/src/generated/PinescriptLexer.ts +673 -0
  19. package/src/generated/PinescriptLexerBase.ts +395 -0
  20. package/src/generated/PinescriptParser.interp +267 -0
  21. package/src/generated/PinescriptParser.tokens +131 -0
  22. package/src/generated/PinescriptParser.ts +8863 -0
  23. package/src/generated/PinescriptParserBase.ts +21 -0
  24. package/src/generated/PinescriptParserVisitor.ts +840 -0
  25. package/src/index.ts +55 -0
  26. package/src/parser/LexerBase.ts +395 -0
  27. package/src/parser/ParserBase.ts +21 -0
  28. package/src/parser/error.ts +33 -0
  29. package/src/runtime/array.ts +486 -0
  30. package/src/runtime/color.ts +164 -0
  31. package/src/runtime/drawings.ts +510 -0
  32. package/src/runtime/input.ts +102 -0
  33. package/src/runtime/interpret.ts +3897 -0
  34. package/src/runtime/library.ts +282 -0
  35. package/src/runtime/log.ts +69 -0
  36. package/src/runtime/map.ts +85 -0
  37. package/src/runtime/math.ts +231 -0
  38. package/src/runtime/matrix.ts +1154 -0
  39. package/src/runtime/provider.ts +232 -0
  40. package/src/runtime/request.ts +372 -0
  41. package/src/runtime/series.ts +44 -0
  42. package/src/runtime/str.ts +433 -0
  43. package/src/runtime/strategy.ts +1105 -0
  44. package/src/runtime/ta.ts +2093 -0
  45. package/src/runtime/ticker.ts +81 -0
  46. package/src/runtime/time.ts +439 -0
  47. package/src/runtime/timeframe.ts +85 -0
  48. package/src/runtime/udt.ts +119 -0
@@ -0,0 +1,510 @@
1
+ /**
2
+ * Copyright (C) 2024-2026 jango_blockchained
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * Stub drawing / alert book. Records line/label/box/alert/table/polyline/
6
+ * linefill events; new() helpers return incrementing ids. Caps match Python
7
+ * DrawingRegistry (default 50; lines/labels/boxes hard 500, polylines 100).
8
+ * Alerts / tables / linefills are uncapped. Interpret wiring is out of scope.
9
+ */
10
+
11
+ export type DrawingKind =
12
+ | "line"
13
+ | "label"
14
+ | "box"
15
+ | "alert"
16
+ | "table"
17
+ | "polyline"
18
+ | "linefill";
19
+
20
+ export interface DrawingEvent {
21
+ kind: DrawingKind;
22
+ bar: number;
23
+ text?: string;
24
+ extra?: Record<string, unknown>;
25
+ deleted?: boolean;
26
+ }
27
+
28
+ export interface DrawingLimits {
29
+ max_lines_count?: number;
30
+ max_labels_count?: number;
31
+ max_boxes_count?: number;
32
+ max_polylines_count?: number;
33
+ }
34
+
35
+ const DEFAULT_DRAWING_LIMIT = 50;
36
+ const HARD_CAP_LINES_LABELS_BOXES = 500;
37
+ const HARD_CAP_POLYLINES = 100;
38
+
39
+ function isFiniteNumber(v: unknown): v is number {
40
+ return typeof v === "number" && Number.isFinite(v);
41
+ }
42
+
43
+ function clampLimit(value: unknown, fallback: number, hardCap: number): number {
44
+ if (value == null || typeof value !== "number" || !Number.isFinite(value)) {
45
+ return fallback;
46
+ }
47
+ return Math.max(1, Math.min(hardCap, Math.trunc(value)));
48
+ }
49
+
50
+ function safeBar(bar: unknown): number {
51
+ return isFiniteNumber(bar) ? bar : 0;
52
+ }
53
+
54
+ function asExtra(extra: unknown): Record<string, unknown> | undefined {
55
+ if (extra == null || typeof extra !== "object" || Array.isArray(extra)) return undefined;
56
+ return { ...(extra as Record<string, unknown>) };
57
+ }
58
+
59
+ function setNum(extra: Record<string, unknown>, key: string, value: unknown): void {
60
+ if (isFiniteNumber(value)) extra[key] = value;
61
+ }
62
+
63
+ function setAny(extra: Record<string, unknown>, key: string, value: unknown): void {
64
+ extra[key] = value;
65
+ }
66
+
67
+ function cellKey(column: unknown, row: unknown): string | undefined {
68
+ if (!isFiniteNumber(column) || !isFiniteNumber(row)) return undefined;
69
+ return `${column},${row}`;
70
+ }
71
+
72
+ export class DrawingBook {
73
+ readonly items: DrawingEvent[] = [];
74
+ private readonly byId = new Map<number, DrawingEvent>();
75
+ private nextId = 0;
76
+ private maxLines = DEFAULT_DRAWING_LIMIT;
77
+ private maxLabels = DEFAULT_DRAWING_LIMIT;
78
+ private maxBoxes = DEFAULT_DRAWING_LIMIT;
79
+ private maxPolylines = DEFAULT_DRAWING_LIMIT;
80
+
81
+ constructor(limits?: DrawingLimits) {
82
+ if (limits != null) this.configure(limits);
83
+ }
84
+
85
+ configure(limits: DrawingLimits): void {
86
+ if (limits == null || typeof limits !== "object") return;
87
+ if (limits.max_lines_count !== undefined) {
88
+ this.maxLines = clampLimit(limits.max_lines_count, this.maxLines, HARD_CAP_LINES_LABELS_BOXES);
89
+ }
90
+ if (limits.max_labels_count !== undefined) {
91
+ this.maxLabels = clampLimit(limits.max_labels_count, this.maxLabels, HARD_CAP_LINES_LABELS_BOXES);
92
+ }
93
+ if (limits.max_boxes_count !== undefined) {
94
+ this.maxBoxes = clampLimit(limits.max_boxes_count, this.maxBoxes, HARD_CAP_LINES_LABELS_BOXES);
95
+ }
96
+ if (limits.max_polylines_count !== undefined) {
97
+ this.maxPolylines = clampLimit(limits.max_polylines_count, this.maxPolylines, HARD_CAP_POLYLINES);
98
+ }
99
+ this.gc("line", this.maxLines);
100
+ this.gc("label", this.maxLabels);
101
+ this.gc("box", this.maxBoxes);
102
+ this.gc("polyline", this.maxPolylines);
103
+ }
104
+
105
+ get(id: number): DrawingEvent | undefined {
106
+ return this.byId.get(id);
107
+ }
108
+
109
+ all(kind: DrawingKind): DrawingEvent[] {
110
+ return this.items.filter((e) => e.kind === kind && !e.deleted);
111
+ }
112
+
113
+ lineNew(bar: number, extra?: Record<string, unknown>): number {
114
+ return this.push("line", bar, { extra, cap: this.maxLines });
115
+ }
116
+
117
+ labelNew(bar: number, text?: string): number {
118
+ return this.push("label", bar, { text, cap: this.maxLabels });
119
+ }
120
+
121
+ boxNew(bar: number): number {
122
+ return this.push("box", bar, { cap: this.maxBoxes });
123
+ }
124
+
125
+ tableNew(bar: number): number {
126
+ return this.push("table", bar);
127
+ }
128
+
129
+ polylineNew(bar: number): number {
130
+ return this.push("polyline", bar, { cap: this.maxPolylines });
131
+ }
132
+
133
+ linefillNew(bar: number, extraOrId1?: Record<string, unknown> | number, id2?: number): number {
134
+ const extra: Record<string, unknown> = {};
135
+ const patch = asExtra(extraOrId1);
136
+ if (patch != null) {
137
+ Object.assign(extra, patch);
138
+ } else {
139
+ if (isFiniteNumber(extraOrId1)) extra.id1 = extraOrId1;
140
+ if (isFiniteNumber(id2)) extra.id2 = id2;
141
+ }
142
+ return this.push("linefill", bar, { extra: Object.keys(extra).length ? extra : undefined });
143
+ }
144
+
145
+ alert(bar: number, message: string): void {
146
+ this.items.push({
147
+ kind: "alert",
148
+ bar: safeBar(bar),
149
+ text: message == null ? "" : String(message),
150
+ });
151
+ }
152
+
153
+ lineSetXy(id: number, x1: number, y1: number, x2: number, y2: number): void {
154
+ const extra = this.extraOf(id);
155
+ if (!extra) return;
156
+ setNum(extra, "x1", x1);
157
+ setNum(extra, "y1", y1);
158
+ setNum(extra, "x2", x2);
159
+ setNum(extra, "y2", y2);
160
+ }
161
+
162
+ lineSetXy1(id: number, x1: number, y1: number): void {
163
+ const extra = this.extraOf(id);
164
+ if (!extra) return;
165
+ setNum(extra, "x1", x1);
166
+ setNum(extra, "y1", y1);
167
+ }
168
+
169
+ lineSetXy2(id: number, x2: number, y2: number): void {
170
+ const extra = this.extraOf(id);
171
+ if (!extra) return;
172
+ setNum(extra, "x2", x2);
173
+ setNum(extra, "y2", y2);
174
+ }
175
+
176
+ labelSetText(id: number, text: string): void {
177
+ const ev = this.byId.get(id);
178
+ if (!ev) return;
179
+ ev.text = text == null ? "" : String(text);
180
+ }
181
+
182
+ labelSetXy(id: number, x: number, y: number): void {
183
+ const extra = this.extraOf(id);
184
+ if (!extra) return;
185
+ setNum(extra, "x", x);
186
+ setNum(extra, "y", y);
187
+ }
188
+
189
+ boxSet(id: number, extra?: Record<string, unknown>): void {
190
+ const ev = this.byId.get(id);
191
+ const patch = asExtra(extra);
192
+ if (!ev || patch == null) return;
193
+ ev.extra = { ...(ev.extra ?? {}), ...patch };
194
+ }
195
+
196
+ boxSetCorners(id: number, left: number, top: number, right: number, bottom: number): void {
197
+ const extra = this.extraOf(id, "box");
198
+ if (!extra) return;
199
+ setNum(extra, "left", left);
200
+ setNum(extra, "top", top);
201
+ setNum(extra, "right", right);
202
+ setNum(extra, "bottom", bottom);
203
+ }
204
+
205
+ lineSetColor(id: number, color: unknown): void {
206
+ const extra = this.extraOf(id, "line");
207
+ if (!extra) return;
208
+ setAny(extra, "color", color);
209
+ }
210
+
211
+ lineSetWidth(id: number, width: unknown): void {
212
+ const extra = this.extraOf(id, "line");
213
+ if (!extra) return;
214
+ setNum(extra, "width", width);
215
+ }
216
+
217
+ lineSetStyle(id: number, style: unknown): void {
218
+ const extra = this.extraOf(id, "line");
219
+ if (!extra) return;
220
+ setAny(extra, "style", style);
221
+ }
222
+
223
+ lineSetExtend(id: number, extend: unknown): void {
224
+ const extra = this.extraOf(id, "line");
225
+ if (!extra) return;
226
+ setAny(extra, "extend", extend);
227
+ }
228
+
229
+ lineGetX1(id: number): number | null {
230
+ return this.lineGetNum(id, "x1");
231
+ }
232
+
233
+ lineGetY1(id: number): number | null {
234
+ return this.lineGetNum(id, "y1");
235
+ }
236
+
237
+ lineGetX2(id: number): number | null {
238
+ return this.lineGetNum(id, "x2");
239
+ }
240
+
241
+ lineGetY2(id: number): number | null {
242
+ return this.lineGetNum(id, "y2");
243
+ }
244
+
245
+ getX1(id: number): number | null {
246
+ return this.extraNum(id, "x1");
247
+ }
248
+
249
+ getY1(id: number): number | null {
250
+ return this.extraNum(id, "y1");
251
+ }
252
+
253
+ getX2(id: number): number | null {
254
+ return this.extraNum(id, "x2");
255
+ }
256
+
257
+ getY2(id: number): number | null {
258
+ return this.extraNum(id, "y2");
259
+ }
260
+
261
+ labelGetX(id: number): number | null {
262
+ const ev = this.byId.get(id);
263
+ if (!ev || ev.kind !== "label") return null;
264
+ return this.extraNum(id, "x");
265
+ }
266
+
267
+ labelGetY(id: number): number | null {
268
+ const ev = this.byId.get(id);
269
+ if (!ev || ev.kind !== "label") return null;
270
+ return this.extraNum(id, "y");
271
+ }
272
+
273
+ labelGetText(id: number): string | null {
274
+ const ev = this.byId.get(id);
275
+ if (!ev || ev.kind !== "label") return null;
276
+ if (ev.text !== undefined) return ev.text;
277
+ const t = ev.extra?.text;
278
+ return t == null ? null : String(t);
279
+ }
280
+
281
+ lineGetPrice(id: number, x: unknown): number | null {
282
+ if (!isFiniteNumber(x)) return null;
283
+ const x1 = this.lineGetNum(id, "x1");
284
+ const y1 = this.lineGetNum(id, "y1");
285
+ const x2 = this.lineGetNum(id, "x2");
286
+ const y2 = this.lineGetNum(id, "y2");
287
+ if (x1 == null || y1 == null || x2 == null || y2 == null) return null;
288
+ if (x1 === x2) return y1;
289
+ const t = (x - x1) / (x2 - x1);
290
+ return y1 + t * (y2 - y1);
291
+ }
292
+
293
+ labelSetColor(id: number, color: unknown): void {
294
+ const extra = this.extraOf(id, "label");
295
+ if (!extra) return;
296
+ setAny(extra, "color", color);
297
+ }
298
+
299
+ labelSetStyle(id: number, style: unknown): void {
300
+ const extra = this.extraOf(id, "label");
301
+ if (!extra) return;
302
+ setAny(extra, "style", style);
303
+ }
304
+
305
+ labelSetSize(id: number, size: unknown): void {
306
+ const extra = this.extraOf(id, "label");
307
+ if (!extra) return;
308
+ setAny(extra, "size", size);
309
+ }
310
+
311
+ labelSetTextalign(id: number, textalign: unknown): void {
312
+ const extra = this.extraOf(id, "label");
313
+ if (!extra) return;
314
+ setAny(extra, "textalign", textalign);
315
+ }
316
+
317
+ labelSetTooltip(id: number, tooltip: unknown): void {
318
+ const extra = this.extraOf(id, "label");
319
+ if (!extra) return;
320
+ setAny(extra, "tooltip", tooltip);
321
+ }
322
+
323
+ boxSetBgcolor(id: number, bgcolor: unknown): void {
324
+ const extra = this.extraOf(id, "box");
325
+ if (!extra) return;
326
+ setAny(extra, "bgcolor", bgcolor);
327
+ }
328
+
329
+ boxSetBorderColor(id: number, color: unknown): void {
330
+ const extra = this.extraOf(id, "box");
331
+ if (!extra) return;
332
+ setAny(extra, "border_color", color);
333
+ }
334
+
335
+ boxSetText(id: number, text: unknown): void {
336
+ const extra = this.extraOf(id, "box");
337
+ if (!extra) return;
338
+ setAny(extra, "text", text == null ? "" : String(text));
339
+ }
340
+
341
+ boxSetExtend(id: number, extend: unknown): void {
342
+ const extra = this.extraOf(id, "box");
343
+ if (!extra) return;
344
+ setAny(extra, "extend", extend);
345
+ }
346
+
347
+ tableCell(id: number, column: unknown, row: unknown, text?: unknown): void {
348
+ this.upsertCell(id, column, row, text, text !== undefined);
349
+ }
350
+
351
+ tableCellSetText(id: number, column: unknown, row: unknown, text: unknown): void {
352
+ this.upsertCell(id, column, row, text, true);
353
+ }
354
+
355
+ tableSetPosition(id: number, position: unknown): void {
356
+ const extra = this.extraOf(id, "table");
357
+ if (!extra) return;
358
+ setAny(extra, "position", position);
359
+ }
360
+
361
+ linefillSetColor(id: number, color: unknown): void {
362
+ const extra = this.extraOf(id, "linefill");
363
+ if (!extra) return;
364
+ setAny(extra, "color", color);
365
+ }
366
+
367
+ /** Compact non-deleted items for RuntimeResult.drawings. */
368
+ exportForApi(): Array<{
369
+ kind: DrawingKind;
370
+ bar: number;
371
+ text?: string;
372
+ extra?: Record<string, unknown>;
373
+ }> {
374
+ const out: Array<{
375
+ kind: DrawingKind;
376
+ bar: number;
377
+ text?: string;
378
+ extra?: Record<string, unknown>;
379
+ }> = [];
380
+ for (const ev of this.items) {
381
+ if (ev.deleted) continue;
382
+ const rec: {
383
+ kind: DrawingKind;
384
+ bar: number;
385
+ text?: string;
386
+ extra?: Record<string, unknown>;
387
+ } = { kind: ev.kind, bar: ev.bar };
388
+ if (ev.text !== undefined) rec.text = ev.text;
389
+ if (ev.extra !== undefined) rec.extra = { ...ev.extra };
390
+ out.push(rec);
391
+ }
392
+ return out;
393
+ }
394
+
395
+ lineDelete(id: number): void {
396
+ this.markDeleted(id);
397
+ }
398
+
399
+ labelDelete(id: number): void {
400
+ this.markDeleted(id);
401
+ }
402
+
403
+ boxDelete(id: number): void {
404
+ this.markDeleted(id);
405
+ }
406
+
407
+ private push(
408
+ kind: DrawingKind,
409
+ bar: number,
410
+ opts?: { text?: string; extra?: Record<string, unknown>; cap?: number },
411
+ ): number {
412
+ const id = this.nextId++;
413
+ const ev: DrawingEvent = { kind, bar: safeBar(bar) };
414
+ if (opts?.text !== undefined && opts.text !== null) ev.text = String(opts.text);
415
+ const extra = asExtra(opts?.extra);
416
+ if (extra !== undefined) ev.extra = extra;
417
+ this.items.push(ev);
418
+ this.byId.set(id, ev);
419
+ if (opts?.cap != null) this.gc(kind, opts.cap);
420
+ return id;
421
+ }
422
+
423
+ private extraOf(id: number, kind?: DrawingKind): Record<string, unknown> | undefined {
424
+ const ev = this.byId.get(id);
425
+ if (!ev) return undefined;
426
+ if (kind != null && ev.kind !== kind) return undefined;
427
+ if (ev.extra == null) ev.extra = {};
428
+ return ev.extra;
429
+ }
430
+
431
+ private lineGetNum(id: number, key: "x1" | "y1" | "x2" | "y2"): number | null {
432
+ const ev = this.byId.get(id);
433
+ if (!ev || ev.kind !== "line") return null;
434
+ const v = ev.extra?.[key];
435
+ return isFiniteNumber(v) ? v : null;
436
+ }
437
+
438
+ private extraNum(id: number, key: string): number | null {
439
+ const ev = this.byId.get(id);
440
+ if (!ev) return null;
441
+ const v = ev.extra?.[key];
442
+ return isFiniteNumber(v) ? v : null;
443
+ }
444
+
445
+ private upsertCell(
446
+ id: number,
447
+ column: unknown,
448
+ row: unknown,
449
+ text: unknown,
450
+ setText: boolean,
451
+ ): void {
452
+ const extra = this.extraOf(id, "table");
453
+ const key = cellKey(column, row);
454
+ if (!extra || key == null) return;
455
+ const prev = extra.cells;
456
+ const cells =
457
+ prev != null && typeof prev === "object" && !Array.isArray(prev)
458
+ ? (prev as Record<string, unknown>)
459
+ : {};
460
+ if (cells !== prev) extra.cells = cells;
461
+ const cur = cells[key];
462
+ const rec =
463
+ cur != null && typeof cur === "object" && !Array.isArray(cur)
464
+ ? (cur as Record<string, unknown>)
465
+ : {};
466
+ if (setText) rec.text = text == null ? "" : String(text);
467
+ else if (rec.text === undefined) rec.text = "";
468
+ cells[key] = rec;
469
+ }
470
+
471
+ private markDeleted(id: number): void {
472
+ const ev = this.byId.get(id);
473
+ if (!ev) return;
474
+ ev.deleted = true;
475
+ }
476
+
477
+ /**
478
+ * Drop oldest active objects of *kind* from `items` when over *cap*
479
+ * so RuntimeResult.drawings stays bounded (Python marks deleted).
480
+ */
481
+ private gc(kind: DrawingKind, cap: number): void {
482
+ const limit = cap > 0 ? cap : DEFAULT_DRAWING_LIMIT;
483
+ let n = 0;
484
+ for (const ev of this.items) {
485
+ if (ev.kind === kind && !ev.deleted) n++;
486
+ }
487
+ let excess = n - limit;
488
+ if (excess <= 0) return;
489
+ for (let i = 0; i < this.items.length && excess > 0; ) {
490
+ const ev = this.items[i]!;
491
+ if (ev.kind === kind && !ev.deleted) {
492
+ ev.deleted = true;
493
+ this.items.splice(i, 1);
494
+ this.forget(ev);
495
+ excess--;
496
+ } else {
497
+ i++;
498
+ }
499
+ }
500
+ }
501
+
502
+ private forget(ev: DrawingEvent): void {
503
+ for (const [id, item] of this.byId) {
504
+ if (item === ev) {
505
+ this.byId.delete(id);
506
+ return;
507
+ }
508
+ }
509
+ }
510
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Copyright (C) 2024-2026 jango_blockchained
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * Resolve `input` / `input.*` defaults (Python `InputBuiltinsMixin` SoT).
6
+ * Interpret should call this from `evalCall`; this module is host-agnostic.
7
+ */
8
+
9
+ export type InputValue = number | boolean | string | null;
10
+
11
+ const INPUT_FNS = new Set([
12
+ "input",
13
+ "input.bool",
14
+ "input.int",
15
+ "input.float",
16
+ "input.price",
17
+ "input.string",
18
+ "input.symbol",
19
+ "input.session",
20
+ "input.source",
21
+ "input.time",
22
+ "input.timeframe",
23
+ "input.color",
24
+ "input.enum",
25
+ "input.text_area",
26
+ ]);
27
+
28
+ export function isInputBuiltin(fname: string | null | undefined): boolean {
29
+ return fname != null && INPUT_FNS.has(fname);
30
+ }
31
+
32
+ /** Pick `defval` from positional/named args (Pine: first positional or `defval=`). */
33
+ export function inputDefval(
34
+ positional: readonly unknown[],
35
+ named: Record<string, unknown> = {},
36
+ ): unknown {
37
+ if (named.defval !== undefined) return named.defval;
38
+ return positional[0];
39
+ }
40
+
41
+ /**
42
+ * Coerce a Pine `input*` default into a scalar the interpret host can bind.
43
+ * Bools become 1/0; unknown / string (except numeric-looking) become `null`.
44
+ */
45
+ export function resolveInputDefault(
46
+ fname: string,
47
+ positional: readonly unknown[],
48
+ named: Record<string, unknown> = {},
49
+ ): InputValue {
50
+ const raw = inputDefval(positional, named);
51
+ switch (fname) {
52
+ case "input.bool":
53
+ return raw == null ? false : Boolean(raw);
54
+ case "input.int":
55
+ case "input.time":
56
+ return toInt(raw, 0);
57
+ case "input.float":
58
+ case "input.price":
59
+ return toNum(raw, 0);
60
+ case "input.string":
61
+ case "input.symbol":
62
+ case "input.session":
63
+ case "input.source":
64
+ case "input.timeframe":
65
+ case "input.color":
66
+ case "input.enum":
67
+ case "input.text_area":
68
+ return raw == null ? "" : String(raw);
69
+ default:
70
+ return coerceGeneric(raw);
71
+ }
72
+ }
73
+
74
+ /** Cell-shaped value for the interpret host (`number | null`). */
75
+ export function inputAsCell(value: InputValue): number | null {
76
+ if (value == null) return null;
77
+ if (typeof value === "boolean") return value ? 1 : 0;
78
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
79
+ const n = Number(value);
80
+ return Number.isFinite(n) ? n : null;
81
+ }
82
+
83
+ function coerceGeneric(raw: unknown): InputValue {
84
+ if (raw == null) return null;
85
+ if (typeof raw === "boolean" || typeof raw === "string") return raw;
86
+ if (typeof raw === "number") return Number.isFinite(raw) ? raw : null;
87
+ return null;
88
+ }
89
+
90
+ function toNum(raw: unknown, fallback: number): number {
91
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
92
+ if (typeof raw === "boolean") return raw ? 1 : 0;
93
+ if (typeof raw === "string" && raw !== "") {
94
+ const n = Number(raw);
95
+ if (Number.isFinite(n)) return n;
96
+ }
97
+ return fallback;
98
+ }
99
+
100
+ function toInt(raw: unknown, fallback: number): number {
101
+ return Math.trunc(toNum(raw, fallback));
102
+ }