@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.
- package/CHANGELOG.md +30 -0
- package/LICENSE +661 -0
- package/NOTICE +46 -0
- package/README.md +327 -0
- package/dist/browser.js +44 -0
- package/dist/index.d.ts +348 -0
- package/dist/index.js +41741 -0
- package/package.json +77 -0
- package/src/ast/builder.ts +1002 -0
- package/src/ast/dump.ts +43 -0
- package/src/ast/helper.ts +77 -0
- package/src/ast/nodes.ts +570 -0
- package/src/ast/unparse.ts +360 -0
- package/src/cli/rich.ts +378 -0
- package/src/cli.ts +627 -0
- package/src/generated/PinescriptLexer.interp +267 -0
- package/src/generated/PinescriptLexer.tokens +131 -0
- package/src/generated/PinescriptLexer.ts +673 -0
- package/src/generated/PinescriptLexerBase.ts +395 -0
- package/src/generated/PinescriptParser.interp +267 -0
- package/src/generated/PinescriptParser.tokens +131 -0
- package/src/generated/PinescriptParser.ts +8863 -0
- package/src/generated/PinescriptParserBase.ts +21 -0
- package/src/generated/PinescriptParserVisitor.ts +840 -0
- package/src/index.ts +55 -0
- package/src/parser/LexerBase.ts +395 -0
- package/src/parser/ParserBase.ts +21 -0
- package/src/parser/error.ts +33 -0
- package/src/runtime/array.ts +486 -0
- package/src/runtime/color.ts +164 -0
- package/src/runtime/drawings.ts +510 -0
- package/src/runtime/input.ts +102 -0
- package/src/runtime/interpret.ts +3897 -0
- package/src/runtime/library.ts +282 -0
- package/src/runtime/log.ts +69 -0
- package/src/runtime/map.ts +85 -0
- package/src/runtime/math.ts +231 -0
- package/src/runtime/matrix.ts +1154 -0
- package/src/runtime/provider.ts +232 -0
- package/src/runtime/request.ts +372 -0
- package/src/runtime/series.ts +44 -0
- package/src/runtime/str.ts +433 -0
- package/src/runtime/strategy.ts +1105 -0
- package/src/runtime/ta.ts +2093 -0
- package/src/runtime/ticker.ts +81 -0
- package/src/runtime/time.ts +439 -0
- package/src/runtime/timeframe.ts +85 -0
- package/src/runtime/udt.ts +119 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2024-2026 jango_blockchained
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*
|
|
5
|
+
* In-process Pine library registry for export/import resolution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type StubKwargs = Record<string, unknown>;
|
|
9
|
+
export type StubExportFn = (...args: unknown[]) => unknown;
|
|
10
|
+
|
|
11
|
+
export type LibraryLookup = {
|
|
12
|
+
namespace?: string | null;
|
|
13
|
+
name: string;
|
|
14
|
+
version?: number | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type LibraryModuleInit = {
|
|
18
|
+
namespace?: string | null;
|
|
19
|
+
version?: number | null;
|
|
20
|
+
exports?: Map<string, unknown> | Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function isPlainObject(value: unknown): value is StubKwargs {
|
|
24
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
25
|
+
const proto = Object.getPrototypeOf(value);
|
|
26
|
+
return proto === Object.prototype || proto === null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Best-effort int for stub polyfills; null / non-numeric → null. */
|
|
30
|
+
function coerceStubInt(value: unknown): number | null {
|
|
31
|
+
if (value == null) return null;
|
|
32
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
33
|
+
if (typeof value === "number") {
|
|
34
|
+
if (!Number.isFinite(value)) return null;
|
|
35
|
+
return Math.trunc(value);
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "bigint") {
|
|
38
|
+
const n = Number(value);
|
|
39
|
+
if (!Number.isFinite(n)) return null;
|
|
40
|
+
return Math.trunc(n);
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === "string") {
|
|
43
|
+
const t = value.trim();
|
|
44
|
+
if (!/^[+-]?\d+$/.test(t)) return null;
|
|
45
|
+
const n = Number(t);
|
|
46
|
+
if (!Number.isFinite(n)) return null;
|
|
47
|
+
return Math.trunc(n);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Merge positional + keyword args into a dense list using `order` names. */
|
|
53
|
+
function mergeStubArgs(args: unknown[], kwargs: StubKwargs, order: string[]): unknown[] {
|
|
54
|
+
const merged: unknown[] = args.slice();
|
|
55
|
+
for (const [key, val] of Object.entries(kwargs)) {
|
|
56
|
+
const idx = order.indexOf(key);
|
|
57
|
+
if (idx < 0) continue;
|
|
58
|
+
while (merged.length <= idx) merged.push(null);
|
|
59
|
+
if (idx < args.length && args[idx] != null) continue;
|
|
60
|
+
merged[idx] = val;
|
|
61
|
+
}
|
|
62
|
+
return merged;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseStubCall(
|
|
66
|
+
callArgs: unknown[],
|
|
67
|
+
kwargs?: StubKwargs,
|
|
68
|
+
): { args: unknown[]; kwargs: StubKwargs } {
|
|
69
|
+
if (kwargs !== undefined) return { args: callArgs, kwargs };
|
|
70
|
+
if (callArgs.length === 0) return { args: [], kwargs: {} };
|
|
71
|
+
if (callArgs.length === 1 && isPlainObject(callArgs[0])) {
|
|
72
|
+
return { args: [], kwargs: callArgs[0] };
|
|
73
|
+
}
|
|
74
|
+
if (
|
|
75
|
+
Array.isArray(callArgs[0]) &&
|
|
76
|
+
(callArgs.length === 1 || callArgs.length === 2) &&
|
|
77
|
+
(callArgs[1] === undefined || isPlainObject(callArgs[1]))
|
|
78
|
+
) {
|
|
79
|
+
return { args: callArgs[0], kwargs: callArgs[1] ?? {} };
|
|
80
|
+
}
|
|
81
|
+
const last = callArgs[callArgs.length - 1];
|
|
82
|
+
if (callArgs.length > 1 && isPlainObject(last)) {
|
|
83
|
+
return { args: callArgs.slice(0, -1), kwargs: last };
|
|
84
|
+
}
|
|
85
|
+
return { args: callArgs, kwargs: {} };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function stubIndex2dTo1d(args: unknown[], kwargs: StubKwargs): number | null {
|
|
89
|
+
const order = ["dimension_x", "dimension_y", "index_x", "index_y"];
|
|
90
|
+
const alias: Record<string, string> = {
|
|
91
|
+
dim_x: "dimension_x",
|
|
92
|
+
dim_y: "dimension_y",
|
|
93
|
+
ix: "index_x",
|
|
94
|
+
iy: "index_y",
|
|
95
|
+
};
|
|
96
|
+
const norm: StubKwargs = {};
|
|
97
|
+
for (const [k, v] of Object.entries(kwargs)) norm[alias[k] ?? k] = v;
|
|
98
|
+
const vals = mergeStubArgs(args, norm, order);
|
|
99
|
+
if (vals.length < 4) return null;
|
|
100
|
+
const dy = coerceStubInt(vals[1]);
|
|
101
|
+
const ix = coerceStubInt(vals[2]);
|
|
102
|
+
const iy = coerceStubInt(vals[3]);
|
|
103
|
+
if (dy == null || ix == null || iy == null) return null;
|
|
104
|
+
return ix * dy + iy;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function stubIndex1dTo2d(args: unknown[], kwargs: StubKwargs): [number, number] | null {
|
|
108
|
+
const order = ["dimension_x", "dimension_y", "index"];
|
|
109
|
+
const alias: Record<string, string> = {
|
|
110
|
+
dim_x: "dimension_x",
|
|
111
|
+
dim_y: "dimension_y",
|
|
112
|
+
i: "index",
|
|
113
|
+
};
|
|
114
|
+
const norm: StubKwargs = {};
|
|
115
|
+
for (const [k, v] of Object.entries(kwargs)) norm[alias[k] ?? k] = v;
|
|
116
|
+
const vals = mergeStubArgs(args, norm, order);
|
|
117
|
+
if (vals.length < 3) return null;
|
|
118
|
+
const dy = coerceStubInt(vals[1]);
|
|
119
|
+
const idx = coerceStubInt(vals[2]);
|
|
120
|
+
if (dy == null || idx == null || dy === 0) return null;
|
|
121
|
+
const ix = Math.floor(idx / dy);
|
|
122
|
+
const iy = ((idx % dy) + dy) % dy;
|
|
123
|
+
return [ix, iy];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Row-major flatten: `index_x * dimension_y + index_y`. */
|
|
127
|
+
export function index_2d_to_1d(...args: unknown[]): number | null {
|
|
128
|
+
const parsed = parseStubCall(args);
|
|
129
|
+
return stubIndex2dTo1d(parsed.args, parsed.kwargs);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Inverse of `index_2d_to_1d` → `[index_x, index_y]`. */
|
|
133
|
+
export function index_1d_to_2d(...args: unknown[]): [number, number] | null {
|
|
134
|
+
const parsed = parseStubCall(args);
|
|
135
|
+
return stubIndex1dTo2d(parsed.args, parsed.kwargs);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Known export polyfills for unresolved remote libraries (import stubs). */
|
|
139
|
+
export const STUB_KNOWN_EXPORTS: Record<string, StubExportFn> = {
|
|
140
|
+
index_2d_to_1d,
|
|
141
|
+
index_1d_to_2d,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** Call a known stub by export name with positional + named args. */
|
|
145
|
+
export function applyStubExport(
|
|
146
|
+
name: string,
|
|
147
|
+
args: unknown[] = [],
|
|
148
|
+
kwargs: StubKwargs = {},
|
|
149
|
+
): unknown {
|
|
150
|
+
if (name === "index_2d_to_1d") return stubIndex2dTo1d(args, kwargs);
|
|
151
|
+
if (name === "index_1d_to_2d") return stubIndex1dTo2d(args, kwargs);
|
|
152
|
+
const fn = STUB_KNOWN_EXPORTS[name];
|
|
153
|
+
if (fn == null) return undefined;
|
|
154
|
+
return fn(args, kwargs);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function toExportMap(exports?: Map<string, unknown> | Record<string, unknown>): Map<string, unknown> {
|
|
158
|
+
if (exports == null) return new Map();
|
|
159
|
+
if (exports instanceof Map) return exports;
|
|
160
|
+
return new Map(Object.entries(exports));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type LibraryExportKind = "const" | "fn" | "type" | "enum" | "method";
|
|
164
|
+
|
|
165
|
+
/** A loaded library: title/path identity plus exported callables and values. */
|
|
166
|
+
export class LibraryModule {
|
|
167
|
+
title: string;
|
|
168
|
+
namespace: string | null;
|
|
169
|
+
version: number | null;
|
|
170
|
+
exports: Map<string, unknown>;
|
|
171
|
+
kinds: Map<string, LibraryExportKind>;
|
|
172
|
+
private readonly resolveStubs: boolean;
|
|
173
|
+
|
|
174
|
+
constructor(title: string, init?: LibraryModuleInit & { resolveStubs?: boolean }) {
|
|
175
|
+
this.title = title;
|
|
176
|
+
this.namespace = init?.namespace ?? null;
|
|
177
|
+
this.version = init?.version ?? null;
|
|
178
|
+
this.exports = toExportMap(init?.exports);
|
|
179
|
+
this.kinds = new Map();
|
|
180
|
+
this.resolveStubs = init?.resolveStubs === true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
get(name: string): unknown {
|
|
184
|
+
if (this.exports.has(name)) return this.exports.get(name);
|
|
185
|
+
if (this.resolveStubs && Object.hasOwn(STUB_KNOWN_EXPORTS, name)) {
|
|
186
|
+
return STUB_KNOWN_EXPORTS[name];
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
has(name: string): boolean {
|
|
192
|
+
if (this.exports.has(name)) return true;
|
|
193
|
+
return this.resolveStubs && Object.hasOwn(STUB_KNOWN_EXPORTS, name);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Put `value` into `exports` and tag as a const. */
|
|
197
|
+
setExport(name: string, value: unknown): void {
|
|
198
|
+
this.putExport(name, value, "const");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
exportType(name: string, value: unknown): void {
|
|
202
|
+
this.putExport(name, value, "type");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
exportEnum(name: string, value: unknown): void {
|
|
206
|
+
this.putExport(name, value, "enum");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
exportFn(name: string, value: unknown): void {
|
|
210
|
+
this.putExport(name, value, "fn");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
kindOf(name: string): string | undefined {
|
|
214
|
+
const tagged = this.kinds.get(name);
|
|
215
|
+
if (tagged !== undefined) return tagged;
|
|
216
|
+
if (this.resolveStubs && Object.hasOwn(STUB_KNOWN_EXPORTS, name)) return "fn";
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Shallow copy of stored exports (does not include unresolved stub polyfills). */
|
|
221
|
+
allExports(): Record<string, unknown> {
|
|
222
|
+
const out: Record<string, unknown> = {};
|
|
223
|
+
for (const [key, value] of this.exports) out[key] = value;
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private putExport(name: string, value: unknown, kind: LibraryExportKind): void {
|
|
228
|
+
this.exports.set(name, value);
|
|
229
|
+
this.kinds.set(name, kind);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Stub module whose known ArrayExtension helpers resolve via `STUB_KNOWN_EXPORTS`. */
|
|
234
|
+
export function createStubModule(title: string): LibraryModule {
|
|
235
|
+
return new LibraryModule(title, { resolveStubs: true });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function pathKey(namespace: string, name: string, version: number): string {
|
|
239
|
+
return `${namespace}\0${name}\0${Math.trunc(version)}`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Maps library identity (path and/or title) → `LibraryModule`. */
|
|
243
|
+
export class LibraryRegistry {
|
|
244
|
+
private readonly byPath = new Map<string, LibraryModule>();
|
|
245
|
+
private readonly byTitle = new Map<string, LibraryModule>();
|
|
246
|
+
private readonly sources = new Map<string, string>();
|
|
247
|
+
|
|
248
|
+
/** Register or replace a loaded library (by title and path when known). */
|
|
249
|
+
register(module: LibraryModule): void {
|
|
250
|
+
this.byTitle.set(module.title, module);
|
|
251
|
+
if (module.namespace != null && module.version != null) {
|
|
252
|
+
this.byPath.set(pathKey(module.namespace, module.title, module.version), module);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Store Pine source for lazy load on `import namespace/name/version`. */
|
|
257
|
+
registerSource(namespace: string, name: string, version: number, source: string): void {
|
|
258
|
+
this.sources.set(pathKey(namespace, name, Math.trunc(version)), source);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Return registered source text, or `null` if unknown. */
|
|
262
|
+
getSource(namespace: string, name: string, version: number): string | null {
|
|
263
|
+
return this.sources.get(pathKey(namespace, name, Math.trunc(version))) ?? null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Resolve by `(namespace, name, version)` path first, then by title. */
|
|
267
|
+
lookup(query: LibraryLookup): LibraryModule | null {
|
|
268
|
+
const { namespace, name, version } = query;
|
|
269
|
+
if (namespace != null && version != null) {
|
|
270
|
+
const mod = this.byPath.get(pathKey(namespace, name, version));
|
|
271
|
+
if (mod != null) return mod;
|
|
272
|
+
}
|
|
273
|
+
return this.byTitle.get(name) ?? null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Drop all path, title, and source entries (tests). */
|
|
277
|
+
clear(): void {
|
|
278
|
+
this.byPath.clear();
|
|
279
|
+
this.byTitle.clear();
|
|
280
|
+
this.sources.clear();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2024-2026 jango_blockchained
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*
|
|
5
|
+
* Pine `log.*` buffer (Python `Logger` SoT).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type LogLevel = "INFO" | "WARNING" | "ERROR";
|
|
9
|
+
|
|
10
|
+
export interface LogRecord {
|
|
11
|
+
level: LogLevel;
|
|
12
|
+
bar: number;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class LogBook {
|
|
17
|
+
readonly records: LogRecord[] = [];
|
|
18
|
+
|
|
19
|
+
info(bar: number, message: string): void {
|
|
20
|
+
this.records.push({ level: "INFO", bar, message });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
warning(bar: number, message: string): void {
|
|
24
|
+
this.records.push({ level: "WARNING", bar, message });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
error(bar: number, message: string): void {
|
|
28
|
+
this.records.push({ level: "ERROR", bar, message });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Drop all records (Python `Logger.clear`). */
|
|
32
|
+
clear(): void {
|
|
33
|
+
this.records.length = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Copy of records (Python `Logger.get_logs`). */
|
|
37
|
+
snapshot(): LogRecord[] {
|
|
38
|
+
return this.records.slice();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Pine `runtime.error` abort (Python `RuntimeError`). */
|
|
43
|
+
export class RuntimeError extends Error {
|
|
44
|
+
override readonly name = "RuntimeError";
|
|
45
|
+
|
|
46
|
+
constructor(message: string) {
|
|
47
|
+
super(message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Halt with `RuntimeError` (Python `runtime_error`). */
|
|
52
|
+
export function runtimeError(message: string): never {
|
|
53
|
+
throw new RuntimeError(message);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function formatLogParts(parts: unknown[]): string {
|
|
57
|
+
if (parts.length === 0) return "";
|
|
58
|
+
const first = parts[0];
|
|
59
|
+
if (typeof first === "string" && parts.length > 1 && /\{[0-9]/.test(first)) {
|
|
60
|
+
return first.replace(/\{(\d+)\}/g, (_, i) => pineLogArg(parts[Number(i) + 1]));
|
|
61
|
+
}
|
|
62
|
+
return parts.map(pineLogArg).join(" ");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function pineLogArg(value: unknown): string {
|
|
66
|
+
if (value == null) return "na";
|
|
67
|
+
if (typeof value === "number" && !Number.isFinite(value)) return "na";
|
|
68
|
+
return String(value);
|
|
69
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2024-2026 jango_blockchained
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*
|
|
5
|
+
* Tiny Pine `map.*` value. Keys are string or finite number; `null` is `na`.
|
|
6
|
+
* Backed by `Map` (never a plain object) so user keys cannot pollute prototypes.
|
|
7
|
+
*/
|
|
8
|
+
export type Cell = number | null;
|
|
9
|
+
export type MapKey = string | number;
|
|
10
|
+
|
|
11
|
+
function keyOf(key: unknown): string | null {
|
|
12
|
+
if (typeof key === "string") return `s:${key}`;
|
|
13
|
+
if (typeof key === "number" && Number.isFinite(key)) {
|
|
14
|
+
const n = Object.is(key, -0) ? 0 : key;
|
|
15
|
+
return `n:${n}`;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function decodeKey(k: string): MapKey {
|
|
21
|
+
return k.startsWith("s:") ? k.slice(2) : Number(k.slice(2));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class PineMap {
|
|
25
|
+
private readonly data = new Map<string, Cell>();
|
|
26
|
+
private readonly order: string[] = [];
|
|
27
|
+
|
|
28
|
+
size(): number {
|
|
29
|
+
return this.data.size;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
put(key: unknown, value: Cell): void {
|
|
33
|
+
const k = keyOf(key);
|
|
34
|
+
if (k == null) return;
|
|
35
|
+
if (!this.data.has(k)) this.order.push(k);
|
|
36
|
+
this.data.set(k, value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get(key: unknown): Cell {
|
|
40
|
+
const k = keyOf(key);
|
|
41
|
+
if (k == null) return null;
|
|
42
|
+
return this.data.has(k) ? this.data.get(k)! : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
contains(key: unknown): boolean {
|
|
46
|
+
const k = keyOf(key);
|
|
47
|
+
return k != null && this.data.has(k);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
remove(key: unknown): Cell {
|
|
51
|
+
const k = keyOf(key);
|
|
52
|
+
if (k == null || !this.data.has(k)) return null;
|
|
53
|
+
const v = this.data.get(k)!;
|
|
54
|
+
this.data.delete(k);
|
|
55
|
+
const i = this.order.indexOf(k);
|
|
56
|
+
if (i >= 0) this.order.splice(i, 1);
|
|
57
|
+
return v;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clear(): void {
|
|
61
|
+
this.data.clear();
|
|
62
|
+
this.order.length = 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
keys(): MapKey[] {
|
|
66
|
+
return this.order.map(decodeKey);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
values(): Cell[] {
|
|
70
|
+
return this.order.map((k) => this.data.get(k)!);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
copy(): PineMap {
|
|
74
|
+
const out = new PineMap();
|
|
75
|
+
for (const k of this.order) out.put(decodeKey(k), this.data.get(k)!);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
putAll(other: PineMap): void {
|
|
80
|
+
if (!(other instanceof PineMap)) return;
|
|
81
|
+
const keys = other.keys();
|
|
82
|
+
const values = other.values();
|
|
83
|
+
for (let i = 0; i < keys.length; i++) this.put(keys[i]!, values[i]!);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2024-2026 jango_blockchained
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*
|
|
5
|
+
* Pine `math.*` scalars (Python `NumericBuiltinsMixin` SoT).
|
|
6
|
+
* `null` is `na`. Non-finite in/out is `na`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type Cell = number | null;
|
|
10
|
+
|
|
11
|
+
function finite(value: Cell): number | null {
|
|
12
|
+
return value !== null && Number.isFinite(value) ? value : null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function finiteOut(value: number): Cell {
|
|
16
|
+
return Number.isFinite(value) ? value : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Half away from 0 (Pine `math.round` ties). */
|
|
20
|
+
function roundHalfAwayFromZero(x: number): number {
|
|
21
|
+
return x >= 0 ? Math.round(x) : -Math.round(-x);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function mathAbs(x: Cell): Cell {
|
|
25
|
+
const n = finite(x);
|
|
26
|
+
return n === null ? null : Math.abs(n);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `-1`, `0`, or `1`. */
|
|
30
|
+
export function mathSign(x: Cell): Cell {
|
|
31
|
+
const n = finite(x);
|
|
32
|
+
if (n === null) return null;
|
|
33
|
+
if (n > 0) return 1;
|
|
34
|
+
if (n < 0) return -1;
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `na` if `x < 0`. */
|
|
39
|
+
export function mathSqrt(x: Cell): Cell {
|
|
40
|
+
const n = finite(x);
|
|
41
|
+
if (n === null || n < 0) return null;
|
|
42
|
+
return finiteOut(Math.sqrt(n));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Natural log, or log base `base` when given; `na` if `x <= 0` (or base <= 0). */
|
|
46
|
+
export function mathLog(x: Cell, base?: Cell): Cell {
|
|
47
|
+
const n = finite(x);
|
|
48
|
+
if (n === null || n <= 0) return null;
|
|
49
|
+
if (base === undefined) return finiteOut(Math.log(n));
|
|
50
|
+
const b = finite(base);
|
|
51
|
+
if (b === null || b <= 0 || b === 1) return null;
|
|
52
|
+
return finiteOut(Math.log(n) / Math.log(b));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `na` if `x <= 0`. */
|
|
56
|
+
export function mathLog10(x: Cell): Cell {
|
|
57
|
+
const n = finite(x);
|
|
58
|
+
if (n === null || n <= 0) return null;
|
|
59
|
+
return finiteOut(Math.log10(n));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function mathExp(x: Cell): Cell {
|
|
63
|
+
const n = finite(x);
|
|
64
|
+
return n === null ? null : finiteOut(Math.exp(n));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function mathPow(base: Cell, exp: Cell): Cell {
|
|
68
|
+
const b = finite(base);
|
|
69
|
+
const e = finite(exp);
|
|
70
|
+
if (b === null || e === null) return null;
|
|
71
|
+
// JS `Math.pow` yields NaN for e.g. (-1)**0.5; map that (and Inf) to na.
|
|
72
|
+
return finiteOut(Math.pow(b, e));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** `na` if any arg is `na` or the list is empty. */
|
|
76
|
+
export function mathMax(...xs: Cell[]): Cell {
|
|
77
|
+
if (xs.length === 0) return null;
|
|
78
|
+
let max = -Infinity;
|
|
79
|
+
for (const x of xs) {
|
|
80
|
+
const n = finite(x);
|
|
81
|
+
if (n === null) return null;
|
|
82
|
+
if (n > max) max = n;
|
|
83
|
+
}
|
|
84
|
+
return finiteOut(max);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** `na` if any arg is `na` or the list is empty. */
|
|
88
|
+
export function mathMin(...xs: Cell[]): Cell {
|
|
89
|
+
if (xs.length === 0) return null;
|
|
90
|
+
let min = Infinity;
|
|
91
|
+
for (const x of xs) {
|
|
92
|
+
const n = finite(x);
|
|
93
|
+
if (n === null) return null;
|
|
94
|
+
if (n < min) min = n;
|
|
95
|
+
}
|
|
96
|
+
return finiteOut(min);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function mathRound(x: Cell, precision?: Cell): Cell {
|
|
100
|
+
const n = finite(x);
|
|
101
|
+
if (n === null) return null;
|
|
102
|
+
if (precision === undefined) return roundHalfAwayFromZero(n);
|
|
103
|
+
const p = finite(precision);
|
|
104
|
+
if (p === null) return null;
|
|
105
|
+
const factor = 10 ** Math.trunc(p);
|
|
106
|
+
if (!Number.isFinite(factor)) return null;
|
|
107
|
+
return finiteOut(roundHalfAwayFromZero(n * factor) / factor);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function mathFloor(x: Cell): Cell {
|
|
111
|
+
const n = finite(x);
|
|
112
|
+
return n === null ? null : Math.floor(n);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function mathCeil(x: Cell): Cell {
|
|
116
|
+
const n = finite(x);
|
|
117
|
+
return n === null ? null : Math.ceil(n);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Arithmetic mean; `na` if any arg is `na` or the list is empty. */
|
|
121
|
+
export function mathAvg(...xs: Cell[]): Cell {
|
|
122
|
+
if (xs.length === 0) return null;
|
|
123
|
+
let sum = 0;
|
|
124
|
+
for (const x of xs) {
|
|
125
|
+
const n = finite(x);
|
|
126
|
+
if (n === null) return null;
|
|
127
|
+
sum += n;
|
|
128
|
+
}
|
|
129
|
+
return finiteOut(sum / xs.length);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function mathSum(...xs: Cell[]): Cell {
|
|
133
|
+
if (xs.length === 0) return null;
|
|
134
|
+
let sum = 0;
|
|
135
|
+
for (const x of xs) {
|
|
136
|
+
const n = finite(x);
|
|
137
|
+
if (n === null) return null;
|
|
138
|
+
sum += n;
|
|
139
|
+
}
|
|
140
|
+
return finiteOut(sum);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function trig(x: Cell, fn: (n: number) => number): Cell {
|
|
144
|
+
const n = finite(x);
|
|
145
|
+
return n === null ? null : finiteOut(fn(n));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function mathSin(x: Cell): Cell {
|
|
149
|
+
return trig(x, Math.sin);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function mathCos(x: Cell): Cell {
|
|
153
|
+
return trig(x, Math.cos);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function mathTan(x: Cell): Cell {
|
|
157
|
+
return trig(x, Math.tan);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function mathAsin(x: Cell): Cell {
|
|
161
|
+
const n = finite(x);
|
|
162
|
+
if (n === null || n < -1 || n > 1) return null;
|
|
163
|
+
return finiteOut(Math.asin(n));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function mathAcos(x: Cell): Cell {
|
|
167
|
+
const n = finite(x);
|
|
168
|
+
if (n === null || n < -1 || n > 1) return null;
|
|
169
|
+
return finiteOut(Math.acos(n));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function mathAtan(x: Cell): Cell {
|
|
173
|
+
return trig(x, Math.atan);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function mathToDegrees(x: Cell): Cell {
|
|
177
|
+
const n = finite(x);
|
|
178
|
+
return n === null ? null : finiteOut((n * 180) / Math.PI);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function mathToRadians(x: Cell): Cell {
|
|
182
|
+
const n = finite(x);
|
|
183
|
+
return n === null ? null : finiteOut((n * Math.PI) / 180);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** `na` in → `na`. Else 1 if finite, 0 otherwise. */
|
|
187
|
+
export function mathIsFinite(x: Cell): Cell {
|
|
188
|
+
if (x === null) return null;
|
|
189
|
+
return Number.isFinite(x) ? 1 : 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Default mintick 0.01 when the host does not supply one. */
|
|
193
|
+
export function mathRoundToMintick(x: Cell, mintick: Cell = 0.01): Cell {
|
|
194
|
+
const n = finite(x);
|
|
195
|
+
const tick = finite(mintick);
|
|
196
|
+
if (n === null || tick === null || tick <= 0) return n === null ? null : finiteOut(Number(n.toFixed(8)));
|
|
197
|
+
return finiteOut(Math.round(n / tick) * tick);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Pine v4 `iff(cond, then, else)`. `na` condition → `na`. */
|
|
201
|
+
export function mathIff(cond: Cell, thenV: Cell, elseV: Cell): Cell {
|
|
202
|
+
if (cond === null || !Number.isFinite(cond)) return null;
|
|
203
|
+
return cond ? thenV : elseV;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Python `_builtin_fixnan`: None / NaN → 0 (not prior-bar carry).
|
|
208
|
+
* Other non-finite (`Inf`) is treated as na → 0.
|
|
209
|
+
*/
|
|
210
|
+
export function mathFixnan(x: Cell): Cell {
|
|
211
|
+
if (x === null || !Number.isFinite(x)) return 0;
|
|
212
|
+
return x;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Uniform random. No args → [0, 1). One arg → [0, max]. Two args → [min, max]
|
|
217
|
+
* via `lo + (hi - lo) * u` (Python `random.uniform`). Any na / non-finite → na.
|
|
218
|
+
* `rng` is a [0, 1) source (default `Math.random`).
|
|
219
|
+
*/
|
|
220
|
+
export function mathRandom(a?: Cell, b?: Cell, rng: () => number = Math.random): Cell {
|
|
221
|
+
if (a === undefined && b === undefined) return finiteOut(rng());
|
|
222
|
+
if (b === undefined) {
|
|
223
|
+
const hi = finite(a as Cell);
|
|
224
|
+
if (hi === null) return null;
|
|
225
|
+
return finiteOut(hi * rng());
|
|
226
|
+
}
|
|
227
|
+
const lo = finite(a as Cell);
|
|
228
|
+
const hi = finite(b);
|
|
229
|
+
if (lo === null || hi === null) return null;
|
|
230
|
+
return finiteOut(lo + (hi - lo) * rng());
|
|
231
|
+
}
|