@hoox-sh/pynets 0.2.0 → 0.4.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 +56 -0
- package/README.md +21 -12
- package/dist/browser.js +89 -84
- package/dist/index.js +21011 -12972
- package/package.json +1 -1
- package/src/ast/builder.ts +235 -12
- package/src/ast/nodes.ts +114 -4
- package/src/ast/unparse.ts +192 -17
- package/src/cli/rich.ts +1 -1
- package/src/generated/PinescriptLexer.interp +5 -1
- package/src/generated/PinescriptLexer.tokens +97 -95
- package/src/generated/PinescriptLexer.ts +512 -502
- package/src/generated/PinescriptParser.interp +8 -1
- package/src/generated/PinescriptParser.tokens +97 -95
- package/src/generated/PinescriptParser.ts +1907 -1287
- package/src/generated/PinescriptParserVisitor.ts +36 -1
- package/src/index.ts +1 -0
- package/src/runtime/array.ts +238 -68
- package/src/runtime/compile/collections.ts +191 -34
- package/src/runtime/compile/draw.ts +609 -19
- package/src/runtime/compile/emit.ts +346 -9
- package/src/runtime/compile/emit_call.ts +737 -44
- package/src/runtime/compile/runtime.ts +82 -2
- package/src/runtime/compile/strategy.ts +339 -6
- package/src/runtime/compile/types.ts +2 -0
- package/src/runtime/drawings.ts +405 -8
- package/src/runtime/interpret.ts +2352 -277
- package/src/runtime/matrix.ts +371 -42
- package/src/runtime/strategy.ts +695 -33
- package/src/runtime/ta.ts +1046 -20
- package/src/runtime/timeframe.ts +199 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Generated from /
|
|
1
|
+
// Generated from /home/jango/Git/pynescript/src/pynescript/ast/grammar/antlr4/resource/PinescriptParser.g4 by ANTLR 4.13.2
|
|
2
2
|
|
|
3
3
|
import {ParseTreeVisitor} from 'antlr4';
|
|
4
4
|
|
|
@@ -31,6 +31,7 @@ import { Field_definitionContext } from "./PinescriptParser.ts";
|
|
|
31
31
|
import { Enum_declarationContext } from "./PinescriptParser.ts";
|
|
32
32
|
import { Enum_definitionsContext } from "./PinescriptParser.ts";
|
|
33
33
|
import { Enum_definitionContext } from "./PinescriptParser.ts";
|
|
34
|
+
import { Value_structureContext } from "./PinescriptParser.ts";
|
|
34
35
|
import { StructureContext } from "./PinescriptParser.ts";
|
|
35
36
|
import { Structure_statementContext } from "./PinescriptParser.ts";
|
|
36
37
|
import { Structure_expressionContext } from "./PinescriptParser.ts";
|
|
@@ -44,8 +45,11 @@ import { For_structure_inContext } from "./PinescriptParser.ts";
|
|
|
44
45
|
import { For_iteratorContext } from "./PinescriptParser.ts";
|
|
45
46
|
import { While_structureContext } from "./PinescriptParser.ts";
|
|
46
47
|
import { Switch_structureContext } from "./PinescriptParser.ts";
|
|
48
|
+
import { Once_structureContext } from "./PinescriptParser.ts";
|
|
49
|
+
import { Switch_caseContext } from "./PinescriptParser.ts";
|
|
47
50
|
import { Switch_casesContext } from "./PinescriptParser.ts";
|
|
48
51
|
import { Switch_pattern_caseContext } from "./PinescriptParser.ts";
|
|
52
|
+
import { Switch_patternsContext } from "./PinescriptParser.ts";
|
|
49
53
|
import { Switch_default_caseContext } from "./PinescriptParser.ts";
|
|
50
54
|
import { Local_blockContext } from "./PinescriptParser.ts";
|
|
51
55
|
import { Indented_local_blockContext } from "./PinescriptParser.ts";
|
|
@@ -113,6 +117,7 @@ import { Type_specificationContext } from "./PinescriptParser.ts";
|
|
|
113
117
|
import { Type_qualifierContext } from "./PinescriptParser.ts";
|
|
114
118
|
import { Attributed_type_nameContext } from "./PinescriptParser.ts";
|
|
115
119
|
import { Template_spec_suffixContext } from "./PinescriptParser.ts";
|
|
120
|
+
import { Rshift_closed_type_argsContext } from "./PinescriptParser.ts";
|
|
116
121
|
import { Array_type_suffixContext } from "./PinescriptParser.ts";
|
|
117
122
|
import { Type_argument_listContext } from "./PinescriptParser.ts";
|
|
118
123
|
import { NameContext } from "./PinescriptParser.ts";
|
|
@@ -298,6 +303,12 @@ export default class PinescriptParserVisitor<Result> extends ParseTreeVisitor<Re
|
|
|
298
303
|
* @return the visitor result
|
|
299
304
|
*/
|
|
300
305
|
visitEnum_definition?: (ctx: Enum_definitionContext) => Result;
|
|
306
|
+
/**
|
|
307
|
+
* Visit a parse tree produced by `PinescriptParser.value_structure`.
|
|
308
|
+
* @param ctx the parse tree
|
|
309
|
+
* @return the visitor result
|
|
310
|
+
*/
|
|
311
|
+
visitValue_structure?: (ctx: Value_structureContext) => Result;
|
|
301
312
|
/**
|
|
302
313
|
* Visit a parse tree produced by `PinescriptParser.structure`.
|
|
303
314
|
* @param ctx the parse tree
|
|
@@ -376,6 +387,18 @@ export default class PinescriptParserVisitor<Result> extends ParseTreeVisitor<Re
|
|
|
376
387
|
* @return the visitor result
|
|
377
388
|
*/
|
|
378
389
|
visitSwitch_structure?: (ctx: Switch_structureContext) => Result;
|
|
390
|
+
/**
|
|
391
|
+
* Visit a parse tree produced by `PinescriptParser.once_structure`.
|
|
392
|
+
* @param ctx the parse tree
|
|
393
|
+
* @return the visitor result
|
|
394
|
+
*/
|
|
395
|
+
visitOnce_structure?: (ctx: Once_structureContext) => Result;
|
|
396
|
+
/**
|
|
397
|
+
* Visit a parse tree produced by `PinescriptParser.switch_case`.
|
|
398
|
+
* @param ctx the parse tree
|
|
399
|
+
* @return the visitor result
|
|
400
|
+
*/
|
|
401
|
+
visitSwitch_case?: (ctx: Switch_caseContext) => Result;
|
|
379
402
|
/**
|
|
380
403
|
* Visit a parse tree produced by `PinescriptParser.switch_cases`.
|
|
381
404
|
* @param ctx the parse tree
|
|
@@ -388,6 +411,12 @@ export default class PinescriptParserVisitor<Result> extends ParseTreeVisitor<Re
|
|
|
388
411
|
* @return the visitor result
|
|
389
412
|
*/
|
|
390
413
|
visitSwitch_pattern_case?: (ctx: Switch_pattern_caseContext) => Result;
|
|
414
|
+
/**
|
|
415
|
+
* Visit a parse tree produced by `PinescriptParser.switch_patterns`.
|
|
416
|
+
* @param ctx the parse tree
|
|
417
|
+
* @return the visitor result
|
|
418
|
+
*/
|
|
419
|
+
visitSwitch_patterns?: (ctx: Switch_patternsContext) => Result;
|
|
391
420
|
/**
|
|
392
421
|
* Visit a parse tree produced by `PinescriptParser.switch_default_case`.
|
|
393
422
|
* @param ctx the parse tree
|
|
@@ -794,6 +823,12 @@ export default class PinescriptParserVisitor<Result> extends ParseTreeVisitor<Re
|
|
|
794
823
|
* @return the visitor result
|
|
795
824
|
*/
|
|
796
825
|
visitTemplate_spec_suffix?: (ctx: Template_spec_suffixContext) => Result;
|
|
826
|
+
/**
|
|
827
|
+
* Visit a parse tree produced by `PinescriptParser.rshift_closed_type_args`.
|
|
828
|
+
* @param ctx the parse tree
|
|
829
|
+
* @return the visitor result
|
|
830
|
+
*/
|
|
831
|
+
visitRshift_closed_type_args?: (ctx: Rshift_closed_type_argsContext) => Result;
|
|
797
832
|
/**
|
|
798
833
|
* Visit a parse tree produced by `PinescriptParser.array_type_suffix`.
|
|
799
834
|
* @param ctx the parse tree
|
package/src/index.ts
CHANGED
package/src/runtime/array.ts
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
4
|
*
|
|
5
5
|
* Tiny Pine `array.*` value. `null` is `na`. Get is na-safe (OOB / non-finite → na).
|
|
6
|
-
* Set OOB is a no-op.
|
|
6
|
+
* Set OOB is a no-op. Slots may hold numbers, na, UDT instances, strings, or bools.
|
|
7
|
+
* Python `ArrayBuiltinsMixin` remains the source of truth.
|
|
7
8
|
*/
|
|
8
9
|
|
|
10
|
+
import { UdtInstance } from "./udt.ts";
|
|
11
|
+
|
|
12
|
+
/** Numeric/na cell. Arrays may also store UDT instances, strings, and bools. */
|
|
9
13
|
export type Cell = number | null;
|
|
10
14
|
|
|
11
15
|
/** Python `array.set` grows only while index < 1_000_000; same bound here. */
|
|
@@ -19,28 +23,118 @@ function resolveIndex(index: number, length: number): number | null {
|
|
|
19
23
|
return i;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
function isFiniteCell(v:
|
|
23
|
-
return v
|
|
26
|
+
function isFiniteCell(v: unknown): v is number {
|
|
27
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
/** Python `bool` on array cells: only `na` and `0` are false (`NaN` is true). */
|
|
27
|
-
function pineTruthy(v:
|
|
31
|
+
function pineTruthy(v: unknown): boolean {
|
|
28
32
|
return v !== null && v !== 0;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
function isSortNa(v: unknown): boolean {
|
|
36
|
+
if (v == null) return true;
|
|
37
|
+
if (typeof v === "number") return !Number.isFinite(v);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Python `_looks_like_udt` — ObjectInstance or compile-path `{__type__}` dict. */
|
|
42
|
+
function looksLikeUdt(item: unknown): boolean {
|
|
43
|
+
if (item == null || typeof item !== "object") return false;
|
|
44
|
+
if (item instanceof UdtInstance) return true;
|
|
45
|
+
return !Array.isArray(item) && Object.prototype.hasOwnProperty.call(item, "__type__");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function udtFieldNames(item: object): string[] | null {
|
|
49
|
+
if (item instanceof UdtInstance) return item.type.fields.map((f) => f.name);
|
|
50
|
+
const rec = item as Record<string, unknown>;
|
|
51
|
+
if (Object.prototype.hasOwnProperty.call(rec, "__type__")) {
|
|
52
|
+
return Object.keys(rec).filter((k) => k !== "__type__");
|
|
53
|
+
}
|
|
54
|
+
const udt = (item as { udt?: { fields?: Record<string, unknown> | Array<{ name: string }> } }).udt;
|
|
55
|
+
const fields = udt?.fields;
|
|
56
|
+
if (fields == null) return null;
|
|
57
|
+
if (Array.isArray(fields)) return fields.map((f) => f.name);
|
|
58
|
+
return Object.keys(fields);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readUdtField(item: object, name: string): unknown {
|
|
62
|
+
if (item instanceof UdtInstance) return item.get(name);
|
|
63
|
+
const getField = (item as { get_field?: (n: string) => unknown }).get_field;
|
|
64
|
+
if (typeof getField === "function") {
|
|
65
|
+
try {
|
|
66
|
+
return getField.call(item, name);
|
|
67
|
+
} catch {
|
|
68
|
+
return item;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return (item as Record<string, unknown>)[name];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Python `_udt_field_key` — field name or int index; missing field → item. */
|
|
75
|
+
function udtFieldKey(item: unknown, sortField: unknown): unknown {
|
|
76
|
+
if (item == null || sortField == null) return item;
|
|
77
|
+
if (typeof item !== "object") return item;
|
|
78
|
+
if (typeof sortField === "string") {
|
|
79
|
+
const names = udtFieldNames(item);
|
|
80
|
+
if (names != null && !names.includes(sortField)) return item;
|
|
81
|
+
return readUdtField(item, sortField);
|
|
82
|
+
}
|
|
83
|
+
if (typeof sortField === "number" && Number.isFinite(sortField)) {
|
|
84
|
+
const names = udtFieldNames(item);
|
|
85
|
+
if (names == null) return item;
|
|
86
|
+
const idx = Math.trunc(sortField);
|
|
87
|
+
if (idx < 0 || idx >= names.length) return item;
|
|
88
|
+
return readUdtField(item, names[idx]!);
|
|
89
|
+
}
|
|
90
|
+
return item;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Python `_resolve_search_field` — default `0` (first field) on UDT arrays. */
|
|
94
|
+
function resolveSearchField(seq: readonly unknown[], sortField: unknown): unknown {
|
|
95
|
+
if (sortField != null) return sortField;
|
|
96
|
+
for (const item of seq) {
|
|
97
|
+
if (item == null) continue;
|
|
98
|
+
if (looksLikeUdt(item)) return 0;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function searchElemKey(item: unknown, sortField: unknown): unknown {
|
|
105
|
+
if (sortField == null) return item;
|
|
106
|
+
return udtFieldKey(item, sortField);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function searchTargetKey(value: unknown, sortField: unknown): unknown {
|
|
110
|
+
if (sortField == null) return value;
|
|
111
|
+
if (looksLikeUdt(value)) return udtFieldKey(value, sortField);
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Python `_key_lt`: `na` sorts last (never less). */
|
|
116
|
+
function keyLt(left: unknown, right: unknown): boolean {
|
|
117
|
+
if (left == null) return false;
|
|
118
|
+
if (right == null) return true;
|
|
119
|
+
try {
|
|
120
|
+
return (left as never) < (right as never);
|
|
121
|
+
} catch {
|
|
122
|
+
return `${String(Object.prototype.toString.call(left))}${String(left)}` <
|
|
123
|
+
`${String(Object.prototype.toString.call(right))}${String(right)}`;
|
|
124
|
+
}
|
|
36
125
|
}
|
|
37
126
|
|
|
38
|
-
function keyEq(left:
|
|
39
|
-
if (left
|
|
40
|
-
if (left
|
|
127
|
+
function keyEq(left: unknown, right: unknown): boolean {
|
|
128
|
+
if (left == null && right == null) return true;
|
|
129
|
+
if (left == null || right == null) return false;
|
|
41
130
|
return left === right;
|
|
42
131
|
}
|
|
43
132
|
|
|
133
|
+
function cmpKeys(a: unknown, b: unknown): number {
|
|
134
|
+
if (keyEq(a, b)) return 0;
|
|
135
|
+
return keyLt(a, b) ? -1 : 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
44
138
|
function meanOf(nums: number[]): number {
|
|
45
139
|
let acc = 0;
|
|
46
140
|
for (const v of nums) acc += v;
|
|
@@ -48,13 +142,13 @@ function meanOf(nums: number[]): number {
|
|
|
48
142
|
}
|
|
49
143
|
|
|
50
144
|
export class PineArray {
|
|
51
|
-
private readonly cells:
|
|
145
|
+
private readonly cells: unknown[] = [];
|
|
52
146
|
|
|
53
|
-
constructor(size?: number, initial?:
|
|
147
|
+
constructor(size?: number, initial?: unknown) {
|
|
54
148
|
if (size === undefined) return;
|
|
55
149
|
if (!Number.isFinite(size) || size < 0) return;
|
|
56
150
|
const n = Math.min(Math.trunc(size), MAX_ARRAY_SIZE);
|
|
57
|
-
const fill
|
|
151
|
+
const fill = initial === undefined ? null : initial;
|
|
58
152
|
for (let i = 0; i < n; i++) this.cells.push(fill);
|
|
59
153
|
}
|
|
60
154
|
|
|
@@ -65,17 +159,17 @@ export class PineArray {
|
|
|
65
159
|
/** `array.get` — OOB / non-finite index → `na`. Negative counts from the end. */
|
|
66
160
|
get(index: number): Cell {
|
|
67
161
|
const i = resolveIndex(index, this.cells.length);
|
|
68
|
-
return i === null ? null : this.cells[i]
|
|
162
|
+
return (i === null ? null : this.cells[i]) as Cell;
|
|
69
163
|
}
|
|
70
164
|
|
|
71
165
|
/** `array.set` — OOB / non-finite index is a no-op. */
|
|
72
|
-
set(index: number, value:
|
|
166
|
+
set(index: number, value: unknown): void {
|
|
73
167
|
const i = resolveIndex(index, this.cells.length);
|
|
74
168
|
if (i === null) return;
|
|
75
169
|
this.cells[i] = value;
|
|
76
170
|
}
|
|
77
171
|
|
|
78
|
-
push(value:
|
|
172
|
+
push(value: unknown): void {
|
|
79
173
|
if (this.cells.length >= MAX_ARRAY_SIZE) return;
|
|
80
174
|
this.cells.push(value);
|
|
81
175
|
}
|
|
@@ -83,10 +177,10 @@ export class PineArray {
|
|
|
83
177
|
/** Last element, or `na` if empty. */
|
|
84
178
|
pop(): Cell {
|
|
85
179
|
if (this.cells.length === 0) return null;
|
|
86
|
-
return this.cells.pop()
|
|
180
|
+
return this.cells.pop() as Cell;
|
|
87
181
|
}
|
|
88
182
|
|
|
89
|
-
unshift(value:
|
|
183
|
+
unshift(value: unknown): void {
|
|
90
184
|
if (this.cells.length >= MAX_ARRAY_SIZE) return;
|
|
91
185
|
this.cells.unshift(value);
|
|
92
186
|
}
|
|
@@ -94,26 +188,26 @@ export class PineArray {
|
|
|
94
188
|
/** First element, or `na` if empty. */
|
|
95
189
|
shift(): Cell {
|
|
96
190
|
if (this.cells.length === 0) return null;
|
|
97
|
-
return this.cells.shift()
|
|
191
|
+
return this.cells.shift() as Cell;
|
|
98
192
|
}
|
|
99
193
|
|
|
100
194
|
clear(): void {
|
|
101
195
|
this.cells.length = 0;
|
|
102
196
|
}
|
|
103
197
|
|
|
104
|
-
includes(value:
|
|
198
|
+
includes(value: unknown): boolean {
|
|
105
199
|
return this.cells.includes(value);
|
|
106
200
|
}
|
|
107
201
|
|
|
108
202
|
first(): Cell {
|
|
109
|
-
return this.cells.length === 0 ? null : this.cells[0]
|
|
203
|
+
return (this.cells.length === 0 ? null : this.cells[0]) as Cell;
|
|
110
204
|
}
|
|
111
205
|
|
|
112
206
|
last(): Cell {
|
|
113
|
-
return this.cells.length === 0 ? null : this.cells[this.cells.length - 1]
|
|
207
|
+
return (this.cells.length === 0 ? null : this.cells[this.cells.length - 1]) as Cell;
|
|
114
208
|
}
|
|
115
209
|
|
|
116
|
-
insert(index: number, value:
|
|
210
|
+
insert(index: number, value: unknown): void {
|
|
117
211
|
if (this.cells.length >= MAX_ARRAY_SIZE) return;
|
|
118
212
|
if (!Number.isFinite(index)) return;
|
|
119
213
|
let i = Math.trunc(index);
|
|
@@ -126,10 +220,10 @@ export class PineArray {
|
|
|
126
220
|
remove(index: number): Cell {
|
|
127
221
|
const i = resolveIndex(index, this.cells.length);
|
|
128
222
|
if (i === null) return null;
|
|
129
|
-
return this.cells.splice(i, 1)[0]
|
|
223
|
+
return this.cells.splice(i, 1)[0] as Cell;
|
|
130
224
|
}
|
|
131
225
|
|
|
132
|
-
fill(value:
|
|
226
|
+
fill(value: unknown): void {
|
|
133
227
|
for (let i = 0; i < this.cells.length; i++) this.cells[i] = value;
|
|
134
228
|
}
|
|
135
229
|
|
|
@@ -155,17 +249,14 @@ export class PineArray {
|
|
|
155
249
|
this.cells.reverse();
|
|
156
250
|
}
|
|
157
251
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (b === null) return -1;
|
|
163
|
-
return order === "desc" ? b - a : a - b;
|
|
164
|
-
});
|
|
252
|
+
/** In-place; `na` last. Optional *sortField* keys UDT cells (name or index). */
|
|
253
|
+
sort(order: "asc" | "desc" = "asc", sortField?: unknown): void {
|
|
254
|
+
const reverse = order === "desc";
|
|
255
|
+
this.cells.splice(0, this.cells.length, ...sortWithNaLast(this.cells, reverse, sortField));
|
|
165
256
|
}
|
|
166
257
|
|
|
167
258
|
/** `array.indexof` — miss is `-1` (Python `ArrayBuiltinsMixin`). */
|
|
168
|
-
indexof(value:
|
|
259
|
+
indexof(value: unknown): Cell {
|
|
169
260
|
for (let i = 0; i < this.cells.length; i++) {
|
|
170
261
|
if (Object.is(this.cells[i], value)) return i;
|
|
171
262
|
}
|
|
@@ -210,7 +301,7 @@ export class PineArray {
|
|
|
210
301
|
}
|
|
211
302
|
|
|
212
303
|
/** `array.from(...)` — collect args; capped at `MAX_ARRAY_SIZE`. */
|
|
213
|
-
static from(...values:
|
|
304
|
+
static from(...values: unknown[]): PineArray {
|
|
214
305
|
const out = new PineArray();
|
|
215
306
|
const n = Math.min(values.length, MAX_ARRAY_SIZE);
|
|
216
307
|
for (let i = 0; i < n; i++) out.push(values[i]!);
|
|
@@ -218,7 +309,7 @@ export class PineArray {
|
|
|
218
309
|
}
|
|
219
310
|
|
|
220
311
|
/** `array.lastindexof` — miss is `-1`. */
|
|
221
|
-
lastIndexOf(value:
|
|
312
|
+
lastIndexOf(value: unknown): Cell {
|
|
222
313
|
for (let i = this.cells.length - 1; i >= 0; i--) {
|
|
223
314
|
if (Object.is(this.cells[i], value)) return i;
|
|
224
315
|
}
|
|
@@ -234,10 +325,14 @@ export class PineArray {
|
|
|
234
325
|
return this;
|
|
235
326
|
}
|
|
236
327
|
|
|
237
|
-
/** Element-wise `abs`; `na` stays `na`. */
|
|
328
|
+
/** Element-wise `abs`; `na` stays `na`. Non-numeric slots become `na`. */
|
|
238
329
|
abs(): PineArray | null {
|
|
239
330
|
const out = new PineArray();
|
|
240
|
-
for (const v of this.cells)
|
|
331
|
+
for (const v of this.cells) {
|
|
332
|
+
if (v == null) out.push(null);
|
|
333
|
+
else if (typeof v === "number") out.push(Math.abs(v));
|
|
334
|
+
else out.push(null);
|
|
335
|
+
}
|
|
241
336
|
return out;
|
|
242
337
|
}
|
|
243
338
|
|
|
@@ -293,24 +388,32 @@ export class PineArray {
|
|
|
293
388
|
}
|
|
294
389
|
|
|
295
390
|
/** Index of `value` in an ascending array (`na` last), or `-1`. */
|
|
296
|
-
binarySearch(value:
|
|
297
|
-
return this.binarySearchBy(value, "any");
|
|
391
|
+
binarySearch(value: unknown, sortField?: unknown): number {
|
|
392
|
+
return this.binarySearchBy(value, "any", sortField);
|
|
298
393
|
}
|
|
299
394
|
|
|
300
|
-
binarySearchLeftmost(value:
|
|
301
|
-
if (
|
|
395
|
+
binarySearchLeftmost(value: unknown, sortField?: unknown): number {
|
|
396
|
+
if (
|
|
397
|
+
sortField == null &&
|
|
398
|
+
!this.cells.some((x) => x != null && looksLikeUdt(x)) &&
|
|
399
|
+
(value == null || this.cells.some((x) => x == null))
|
|
400
|
+
) {
|
|
302
401
|
const i = this.indexof(value);
|
|
303
402
|
return typeof i === "number" ? i : -1;
|
|
304
403
|
}
|
|
305
|
-
return this.binarySearchBy(value, "left");
|
|
404
|
+
return this.binarySearchBy(value, "left", sortField);
|
|
306
405
|
}
|
|
307
406
|
|
|
308
|
-
binarySearchRightmost(value:
|
|
309
|
-
if (
|
|
407
|
+
binarySearchRightmost(value: unknown, sortField?: unknown): number {
|
|
408
|
+
if (
|
|
409
|
+
sortField == null &&
|
|
410
|
+
!this.cells.some((x) => x != null && looksLikeUdt(x)) &&
|
|
411
|
+
(value == null || this.cells.some((x) => x == null))
|
|
412
|
+
) {
|
|
310
413
|
const i = this.lastIndexOf(value);
|
|
311
414
|
return typeof i === "number" ? i : -1;
|
|
312
415
|
}
|
|
313
|
-
return this.binarySearchBy(value, "right");
|
|
416
|
+
return this.binarySearchBy(value, "right", sortField);
|
|
314
417
|
}
|
|
315
418
|
|
|
316
419
|
/** Default `biased=true` is population (`n`); `false` is sample (`n-1`). Skips `na`. */
|
|
@@ -377,7 +480,7 @@ export class PineArray {
|
|
|
377
480
|
return sorted[Math.min(rank, n) - 1]!;
|
|
378
481
|
}
|
|
379
482
|
|
|
380
|
-
percentrank(value:
|
|
483
|
+
percentrank(value: unknown): Cell {
|
|
381
484
|
if (!isFiniteCell(value)) return null;
|
|
382
485
|
const nums = this.finiteSkip();
|
|
383
486
|
if (nums.length === 0) return null;
|
|
@@ -404,38 +507,52 @@ export class PineArray {
|
|
|
404
507
|
}
|
|
405
508
|
|
|
406
509
|
/** Indices that would sort this array; `na` indices last. */
|
|
407
|
-
sortIndices(order: "asc" | "desc" = "asc"): number[] | null {
|
|
408
|
-
const
|
|
510
|
+
sortIndices(order: "asc" | "desc" = "asc", sortField?: unknown): number[] | null {
|
|
511
|
+
const reverse = order === "desc";
|
|
512
|
+
const nonNa: { key: unknown; idx: number }[] = [];
|
|
409
513
|
const naIdx: number[] = [];
|
|
410
514
|
for (let i = 0; i < this.cells.length; i++) {
|
|
411
|
-
const v = this.cells[i]
|
|
412
|
-
if (
|
|
515
|
+
const v = this.cells[i];
|
|
516
|
+
if (isSortNa(v)) {
|
|
413
517
|
naIdx.push(i);
|
|
414
518
|
continue;
|
|
415
519
|
}
|
|
416
|
-
|
|
520
|
+
const key = sortField != null ? udtFieldKey(v, sortField) : v;
|
|
521
|
+
if (isSortNa(key)) naIdx.push(i);
|
|
522
|
+
else nonNa.push({ key, idx: i });
|
|
523
|
+
}
|
|
524
|
+
try {
|
|
525
|
+
nonNa.sort((a, b) => {
|
|
526
|
+
const cmp = cmpKeys(a.key, b.key);
|
|
527
|
+
if (cmp !== 0) return reverse ? -cmp : cmp;
|
|
528
|
+
return a.idx - b.idx;
|
|
529
|
+
});
|
|
530
|
+
} catch {
|
|
531
|
+
nonNa.sort((a, b) => {
|
|
532
|
+
const ka = `${String(Object.prototype.toString.call(a.key))}${String(a.key)}`;
|
|
533
|
+
const kb = `${String(Object.prototype.toString.call(b.key))}${String(b.key)}`;
|
|
534
|
+
const cmp = ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
535
|
+
if (cmp !== 0) return reverse ? -cmp : cmp;
|
|
536
|
+
return a.idx - b.idx;
|
|
537
|
+
});
|
|
417
538
|
}
|
|
418
|
-
const reverse = order === "desc";
|
|
419
|
-
nonNa.sort((a, b) => {
|
|
420
|
-
const cmp = a.key - b.key;
|
|
421
|
-
if (cmp !== 0) return reverse ? -cmp : cmp;
|
|
422
|
-
return a.idx - b.idx;
|
|
423
|
-
});
|
|
424
539
|
return nonNa.map((x) => x.idx).concat(naIdx);
|
|
425
540
|
}
|
|
426
541
|
|
|
427
|
-
private binarySearchBy(value:
|
|
542
|
+
private binarySearchBy(value: unknown, side: "any" | "left" | "right", sortField?: unknown): number {
|
|
428
543
|
const seq = this.cells;
|
|
544
|
+
const field = resolveSearchField(seq, sortField ?? null);
|
|
545
|
+
const target = searchTargetKey(value, field);
|
|
429
546
|
const n = seq.length;
|
|
430
547
|
if (side === "left") {
|
|
431
548
|
let left = 0;
|
|
432
549
|
let right = n;
|
|
433
550
|
while (left < right) {
|
|
434
551
|
const mid = Math.trunc((left + right) / 2);
|
|
435
|
-
if (keyLt(seq[mid]
|
|
552
|
+
if (keyLt(searchElemKey(seq[mid], field), target)) left = mid + 1;
|
|
436
553
|
else right = mid;
|
|
437
554
|
}
|
|
438
|
-
if (left < n && keyEq(seq[left]
|
|
555
|
+
if (left < n && keyEq(searchElemKey(seq[left], field), target)) return left;
|
|
439
556
|
return -1;
|
|
440
557
|
}
|
|
441
558
|
if (side === "right") {
|
|
@@ -443,19 +560,19 @@ export class PineArray {
|
|
|
443
560
|
let right = n;
|
|
444
561
|
while (left < right) {
|
|
445
562
|
const mid = Math.trunc((left + right) / 2);
|
|
446
|
-
if (keyLt(
|
|
563
|
+
if (keyLt(target, searchElemKey(seq[mid], field))) right = mid;
|
|
447
564
|
else left = mid + 1;
|
|
448
565
|
}
|
|
449
|
-
if (left > 0 && keyEq(seq[left - 1]
|
|
566
|
+
if (left > 0 && keyEq(searchElemKey(seq[left - 1], field), target)) return left - 1;
|
|
450
567
|
return -1;
|
|
451
568
|
}
|
|
452
569
|
let lo = 0;
|
|
453
570
|
let hi = n - 1;
|
|
454
571
|
while (lo <= hi) {
|
|
455
572
|
const mid = Math.trunc((lo + hi) / 2);
|
|
456
|
-
const midK = seq[mid]
|
|
457
|
-
if (keyEq(midK,
|
|
458
|
-
if (keyLt(midK,
|
|
573
|
+
const midK = searchElemKey(seq[mid], field);
|
|
574
|
+
if (keyEq(midK, target)) return mid;
|
|
575
|
+
if (keyLt(midK, target)) lo = mid + 1;
|
|
459
576
|
else hi = mid - 1;
|
|
460
577
|
}
|
|
461
578
|
return -1;
|
|
@@ -481,6 +598,59 @@ export class PineArray {
|
|
|
481
598
|
}
|
|
482
599
|
|
|
483
600
|
toValues(): Cell[] {
|
|
484
|
-
return this.cells.slice();
|
|
601
|
+
return this.cells.slice() as Cell[];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Python `_sort_with_na_last`. */
|
|
606
|
+
function sortWithNaLast(sequence: readonly unknown[], reverse: boolean, sortField?: unknown): unknown[] {
|
|
607
|
+
if (sortField == null) {
|
|
608
|
+
const nonNa: unknown[] = [];
|
|
609
|
+
let naCount = 0;
|
|
610
|
+
for (const x of sequence) {
|
|
611
|
+
if (isSortNa(x)) naCount += 1;
|
|
612
|
+
else nonNa.push(x);
|
|
613
|
+
}
|
|
614
|
+
try {
|
|
615
|
+
nonNa.sort((a, b) => {
|
|
616
|
+
const cmp = cmpKeys(a, b);
|
|
617
|
+
return reverse ? -cmp : cmp;
|
|
618
|
+
});
|
|
619
|
+
} catch {
|
|
620
|
+
nonNa.sort((a, b) => {
|
|
621
|
+
const ka = `${String(Object.prototype.toString.call(a))}${String(a)}`;
|
|
622
|
+
const kb = `${String(Object.prototype.toString.call(b))}${String(b)}`;
|
|
623
|
+
const cmp = ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
624
|
+
return reverse ? -cmp : cmp;
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
const out = nonNa.slice();
|
|
628
|
+
for (let i = 0; i < naCount; i++) out.push(null);
|
|
629
|
+
return out;
|
|
630
|
+
}
|
|
631
|
+
const keyed: { key: unknown; item: unknown }[] = [];
|
|
632
|
+
const naItems: unknown[] = [];
|
|
633
|
+
for (const item of sequence) {
|
|
634
|
+
if (isSortNa(item)) {
|
|
635
|
+
naItems.push(item);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const key = udtFieldKey(item, sortField);
|
|
639
|
+
if (isSortNa(key)) naItems.push(item);
|
|
640
|
+
else keyed.push({ key, item });
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
keyed.sort((a, b) => {
|
|
644
|
+
const cmp = cmpKeys(a.key, b.key);
|
|
645
|
+
return reverse ? -cmp : cmp;
|
|
646
|
+
});
|
|
647
|
+
} catch {
|
|
648
|
+
keyed.sort((a, b) => {
|
|
649
|
+
const ka = `${String(Object.prototype.toString.call(a.key))}${String(a.key)}`;
|
|
650
|
+
const kb = `${String(Object.prototype.toString.call(b.key))}${String(b.key)}`;
|
|
651
|
+
const cmp = ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
652
|
+
return reverse ? -cmp : cmp;
|
|
653
|
+
});
|
|
485
654
|
}
|
|
655
|
+
return keyed.map((x) => x.item).concat(naItems);
|
|
486
656
|
}
|