oakscriptjs 0.9.5 → 0.10.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/README.md +1 -1
- package/dist/cjs/array/index.d.ts +93 -50
- package/dist/cjs/array/index.d.ts.map +1 -1
- package/dist/cjs/array/index.js +239 -78
- package/dist/cjs/array/index.js.map +3 -3
- package/dist/cjs/callsite/index.d.ts +81 -2
- package/dist/cjs/callsite/index.d.ts.map +1 -1
- package/dist/cjs/callsite/index.js +62 -1
- package/dist/cjs/callsite/index.js.map +2 -2
- package/dist/cjs/color/index.d.ts +7 -3
- package/dist/cjs/color/index.d.ts.map +1 -1
- package/dist/cjs/color/index.js +6 -1
- package/dist/cjs/color/index.js.map +2 -2
- package/dist/cjs/drawing/bar-time.d.ts +24 -0
- package/dist/cjs/drawing/bar-time.d.ts.map +1 -0
- package/dist/cjs/drawing/bar-time.js +47 -0
- package/dist/cjs/drawing/bar-time.js.map +7 -0
- package/dist/cjs/drawing/output.d.ts +32 -0
- package/dist/cjs/drawing/output.d.ts.map +1 -0
- package/dist/cjs/drawing/output.js +184 -0
- package/dist/cjs/drawing/output.js.map +7 -0
- package/dist/cjs/drawing/registry.d.ts +5 -4
- package/dist/cjs/drawing/registry.d.ts.map +1 -1
- package/dist/cjs/drawing/registry.js +11 -2
- package/dist/cjs/drawing/registry.js.map +2 -2
- package/dist/cjs/index.d.ts +6 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +9 -2
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/label/index.d.ts.map +1 -1
- package/dist/cjs/label/index.js +2 -1
- package/dist/cjs/label/index.js.map +2 -2
- package/dist/cjs/linefill/index.d.ts.map +1 -1
- package/dist/cjs/linefill/index.js +1 -0
- package/dist/cjs/linefill/index.js.map +2 -2
- package/dist/cjs/math/index.d.ts +76 -21
- package/dist/cjs/math/index.d.ts.map +1 -1
- package/dist/cjs/math/index.js +155 -41
- package/dist/cjs/math/index.js.map +3 -3
- package/dist/cjs/matrix/index.d.ts.map +1 -1
- package/dist/cjs/matrix/index.js +42 -36
- package/dist/cjs/matrix/index.js.map +2 -2
- package/dist/cjs/polyline/index.d.ts +3 -5
- package/dist/cjs/polyline/index.d.ts.map +1 -1
- package/dist/cjs/polyline/index.js +0 -5
- package/dist/cjs/polyline/index.js.map +2 -2
- package/dist/cjs/runtime/series.d.ts +9 -0
- package/dist/cjs/runtime/series.d.ts.map +1 -1
- package/dist/cjs/runtime/series.js +16 -2
- package/dist/cjs/runtime/series.js.map +2 -2
- package/dist/cjs/runtime/types.d.ts +22 -0
- package/dist/cjs/runtime/types.d.ts.map +1 -1
- package/dist/cjs/runtime/types.js.map +1 -1
- package/dist/cjs/script/index.d.ts +97 -36
- package/dist/cjs/script/index.d.ts.map +1 -1
- package/dist/cjs/script/index.js +173 -26
- package/dist/cjs/script/index.js.map +3 -3
- package/dist/cjs/session/calendar.d.ts.map +1 -1
- package/dist/cjs/session/calendar.js +15 -2
- package/dist/cjs/session/calendar.js.map +2 -2
- package/dist/cjs/str/index.d.ts +50 -38
- package/dist/cjs/str/index.d.ts.map +1 -1
- package/dist/cjs/str/index.js +43 -15
- package/dist/cjs/str/index.js.map +2 -2
- package/dist/cjs/str/messageformat.d.ts +2 -1
- package/dist/cjs/str/messageformat.d.ts.map +1 -1
- package/dist/cjs/str/messageformat.js +4 -1
- package/dist/cjs/str/messageformat.js.map +2 -2
- package/dist/cjs/str/numberformat.d.ts +4 -0
- package/dist/cjs/str/numberformat.d.ts.map +1 -1
- package/dist/cjs/str/numberformat.js +26 -4
- package/dist/cjs/str/numberformat.js.map +3 -3
- package/dist/cjs/ta/index.d.ts +86 -43
- package/dist/cjs/ta/index.d.ts.map +1 -1
- package/dist/cjs/ta/index.js +173 -267
- package/dist/cjs/ta/index.js.map +3 -3
- package/dist/cjs/ta/running-sum.d.ts +54 -2
- package/dist/cjs/ta/running-sum.d.ts.map +1 -1
- package/dist/cjs/ta/running-sum.js +132 -52
- package/dist/cjs/ta/running-sum.js.map +2 -2
- package/dist/cjs/ta-series.d.ts +4 -4
- package/dist/cjs/ta-series.d.ts.map +1 -1
- package/dist/cjs/ta-series.js +4 -3
- package/dist/cjs/ta-series.js.map +2 -2
- package/dist/cjs/table/index.d.ts +87 -0
- package/dist/cjs/table/index.d.ts.map +1 -0
- package/dist/cjs/table/index.js +150 -0
- package/dist/cjs/table/index.js.map +7 -0
- package/dist/cjs/text/index.d.ts +17 -2
- package/dist/cjs/text/index.d.ts.map +1 -1
- package/dist/cjs/text/index.js +15 -1
- package/dist/cjs/text/index.js.map +2 -2
- package/dist/cjs/time/datestring.d.ts +1 -0
- package/dist/cjs/time/datestring.d.ts.map +1 -1
- package/dist/cjs/time/datestring.js +7 -0
- package/dist/cjs/time/datestring.js.map +2 -2
- package/dist/cjs/time/index.d.ts.map +1 -1
- package/dist/cjs/time/index.js +2 -1
- package/dist/cjs/time/index.js.map +2 -2
- package/dist/cjs/time/session.d.ts +28 -9
- package/dist/cjs/time/session.d.ts.map +1 -1
- package/dist/cjs/time/session.js +74 -28
- package/dist/cjs/time/session.js.map +2 -2
- package/dist/cjs/time/timezone.d.ts +5 -0
- package/dist/cjs/time/timezone.d.ts.map +1 -1
- package/dist/cjs/time/timezone.js +15 -1
- package/dist/cjs/time/timezone.js.map +2 -2
- package/dist/cjs/types/index.d.ts +30 -1
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +12 -0
- package/dist/cjs/types/index.js.map +2 -2
- package/dist/cjs/types/metadata.d.ts +258 -26
- package/dist/cjs/types/metadata.d.ts.map +1 -1
- package/dist/cjs/types/metadata.js.map +1 -1
- package/dist/esm/array/index.d.ts +93 -50
- package/dist/esm/array/index.d.ts.map +1 -1
- package/dist/esm/array/index.js +383 -133
- package/dist/esm/array/index.js.map +1 -1
- package/dist/esm/callsite/index.d.ts +81 -2
- package/dist/esm/callsite/index.d.ts.map +1 -1
- package/dist/esm/callsite/index.js +145 -3
- package/dist/esm/callsite/index.js.map +1 -1
- package/dist/esm/color/index.d.ts +7 -3
- package/dist/esm/color/index.d.ts.map +1 -1
- package/dist/esm/color/index.js +15 -3
- package/dist/esm/color/index.js.map +1 -1
- package/dist/esm/drawing/bar-time.d.ts +24 -0
- package/dist/esm/drawing/bar-time.d.ts.map +1 -0
- package/dist/esm/drawing/bar-time.js +44 -0
- package/dist/esm/drawing/bar-time.js.map +1 -0
- package/dist/esm/drawing/output.d.ts +32 -0
- package/dist/esm/drawing/output.d.ts.map +1 -0
- package/dist/esm/drawing/output.js +190 -0
- package/dist/esm/drawing/output.js.map +1 -0
- package/dist/esm/drawing/registry.d.ts +5 -4
- package/dist/esm/drawing/registry.d.ts.map +1 -1
- package/dist/esm/drawing/registry.js +12 -2
- package/dist/esm/drawing/registry.js.map +1 -1
- package/dist/esm/index.d.ts +6 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +7 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/label/index.d.ts.map +1 -1
- package/dist/esm/label/index.js +2 -1
- package/dist/esm/label/index.js.map +1 -1
- package/dist/esm/linefill/index.d.ts.map +1 -1
- package/dist/esm/linefill/index.js +3 -0
- package/dist/esm/linefill/index.js.map +1 -1
- package/dist/esm/math/index.d.ts +76 -21
- package/dist/esm/math/index.d.ts.map +1 -1
- package/dist/esm/math/index.js +227 -61
- package/dist/esm/math/index.js.map +1 -1
- package/dist/esm/matrix/index.d.ts.map +1 -1
- package/dist/esm/matrix/index.js +57 -43
- package/dist/esm/matrix/index.js.map +1 -1
- package/dist/esm/polyline/index.d.ts +3 -5
- package/dist/esm/polyline/index.d.ts.map +1 -1
- package/dist/esm/polyline/index.js +3 -9
- package/dist/esm/polyline/index.js.map +1 -1
- package/dist/esm/runtime/series.d.ts +9 -0
- package/dist/esm/runtime/series.d.ts.map +1 -1
- package/dist/esm/runtime/series.js +18 -1
- package/dist/esm/runtime/series.js.map +1 -1
- package/dist/esm/runtime/types.d.ts +22 -0
- package/dist/esm/runtime/types.d.ts.map +1 -1
- package/dist/esm/script/index.d.ts +97 -36
- package/dist/esm/script/index.d.ts.map +1 -1
- package/dist/esm/script/index.js +211 -22
- package/dist/esm/script/index.js.map +1 -1
- package/dist/esm/session/calendar.d.ts.map +1 -1
- package/dist/esm/session/calendar.js +22 -2
- package/dist/esm/session/calendar.js.map +1 -1
- package/dist/esm/str/index.d.ts +50 -38
- package/dist/esm/str/index.d.ts.map +1 -1
- package/dist/esm/str/index.js +103 -52
- package/dist/esm/str/index.js.map +1 -1
- package/dist/esm/str/messageformat.d.ts +2 -1
- package/dist/esm/str/messageformat.d.ts.map +1 -1
- package/dist/esm/str/messageformat.js +7 -2
- package/dist/esm/str/messageformat.js.map +1 -1
- package/dist/esm/str/numberformat.d.ts +4 -0
- package/dist/esm/str/numberformat.d.ts.map +1 -1
- package/dist/esm/str/numberformat.js +38 -4
- package/dist/esm/str/numberformat.js.map +1 -1
- package/dist/esm/ta/index.d.ts +86 -43
- package/dist/esm/ta/index.d.ts.map +1 -1
- package/dist/esm/ta/index.js +296 -348
- package/dist/esm/ta/index.js.map +1 -1
- package/dist/esm/ta/running-sum.d.ts +54 -2
- package/dist/esm/ta/running-sum.d.ts.map +1 -1
- package/dist/esm/ta/running-sum.js +166 -71
- package/dist/esm/ta/running-sum.js.map +1 -1
- package/dist/esm/ta-series.d.ts +4 -4
- package/dist/esm/ta-series.d.ts.map +1 -1
- package/dist/esm/ta-series.js +5 -4
- package/dist/esm/ta-series.js.map +1 -1
- package/dist/esm/table/index.d.ts +87 -0
- package/dist/esm/table/index.d.ts.map +1 -0
- package/dist/esm/table/index.js +169 -0
- package/dist/esm/table/index.js.map +1 -0
- package/dist/esm/text/index.d.ts +17 -2
- package/dist/esm/text/index.d.ts.map +1 -1
- package/dist/esm/text/index.js +17 -2
- package/dist/esm/text/index.js.map +1 -1
- package/dist/esm/time/datestring.d.ts +1 -0
- package/dist/esm/time/datestring.d.ts.map +1 -1
- package/dist/esm/time/datestring.js +12 -0
- package/dist/esm/time/datestring.js.map +1 -1
- package/dist/esm/time/index.d.ts.map +1 -1
- package/dist/esm/time/index.js +3 -1
- package/dist/esm/time/index.js.map +1 -1
- package/dist/esm/time/session.d.ts +28 -9
- package/dist/esm/time/session.d.ts.map +1 -1
- package/dist/esm/time/session.js +113 -42
- package/dist/esm/time/session.js.map +1 -1
- package/dist/esm/time/timezone.d.ts +5 -0
- package/dist/esm/time/timezone.d.ts.map +1 -1
- package/dist/esm/time/timezone.js +21 -1
- package/dist/esm/time/timezone.js.map +1 -1
- package/dist/esm/types/index.d.ts +30 -1
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/types/index.js +14 -0
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm/types/metadata.d.ts +258 -26
- package/dist/esm/types/metadata.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/array/index.ts +364 -150
- package/src/callsite/index.ts +147 -3
- package/src/color/index.ts +15 -5
- package/src/drawing/bar-time.ts +42 -0
- package/src/drawing/output.ts +214 -0
- package/src/drawing/registry.ts +16 -6
- package/src/index.ts +9 -3
- package/src/label/index.ts +2 -1
- package/src/linefill/index.ts +2 -0
- package/src/math/index.ts +256 -74
- package/src/matrix/index.ts +52 -52
- package/src/polyline/index.ts +3 -12
- package/src/runtime/series.ts +22 -1
- package/src/runtime/types.ts +23 -0
- package/src/script/index.ts +307 -25
- package/src/session/calendar.ts +22 -2
- package/src/str/index.ts +95 -51
- package/src/str/messageformat.ts +7 -2
- package/src/str/numberformat.ts +39 -4
- package/src/ta/index.ts +306 -386
- package/src/ta/running-sum.ts +187 -74
- package/src/ta-series.ts +8 -7
- package/src/table/index.ts +221 -0
- package/src/text/index.ts +18 -2
- package/src/time/datestring.ts +10 -0
- package/src/time/index.ts +3 -1
- package/src/time/session.ts +97 -47
- package/src/time/timezone.ts +21 -1
- package/src/types/index.ts +32 -1
- package/src/types/metadata.ts +325 -26
package/src/str/index.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { formatNumber } from './numberformat.js';
|
|
13
13
|
import { formatMessage } from './messageformat.js';
|
|
14
14
|
import { formatDate } from './dateformat.js';
|
|
15
|
+
import { round_to_mintick } from '../math/index.js';
|
|
15
16
|
import type { int, bool, float, simple_int, simple_string } from '../types/index.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -34,20 +35,28 @@ export function length(str: simple_string): int {
|
|
|
34
35
|
* Converts a value to its string representation (PineScript `str.tostring`).
|
|
35
36
|
*
|
|
36
37
|
* @param value - The value to convert (number, boolean, string, etc.)
|
|
37
|
-
* @param format - Optional number format: a pattern ("#.##", "#,###.00", "0.00%", "0.00E0") or
|
|
38
|
-
* `format.percent` ("percent"), `format.volume` ("volume"), `format.
|
|
38
|
+
* @param format - Optional number format: a pattern ("#.##", "#,###.00", "0.00%", "0.00E0", "#.##;(#.##)") or
|
|
39
|
+
* `format.percent` ("percent"), `format.volume` ("volume"), `format.mintick` ("mintick"),
|
|
40
|
+
* `format.price` / `format.inherit`
|
|
41
|
+
* @param mintick - Tick size for `format.mintick` (PineScript `syminfo.mintick`, implicit there). Not part of the
|
|
42
|
+
* PineScript signature: the core function cannot see the chart, so pass it, as for `math.round_to_mintick`
|
|
39
43
|
* @returns String representation of the value
|
|
40
44
|
*
|
|
41
45
|
* @remarks
|
|
42
46
|
* As in PineScript:
|
|
43
47
|
* - without format, numbers show up to 10 decimals ("0.3333333333"); 1e21 and more use "1E21"
|
|
44
|
-
* - patterns round half away from zero on the shortest decimal form: 1.005 with "#.##" gives "1.01"
|
|
48
|
+
* - patterns round half away from zero on the shortest decimal form: 1.005 with "#.##" gives "1.01";
|
|
49
|
+
* at most 16 decimals from 1e-3 up (0.0012345678901234567 with 20 '#' decimals gives "0.0012345678901235")
|
|
50
|
+
* - "pos;neg" patterns: a negative number takes the text of the negative subpattern ("#.##;(#.##)": "(3.5)")
|
|
45
51
|
* - "#" is an optional digit ("1.5" with "#.##"), "0" a required digit ("1.50" with "0.00"),
|
|
46
52
|
* "," groups thousands, "%" multiplies by 100, 'text' is literal text
|
|
47
53
|
* - `format.percent` shows 2 decimals and "%" without multiplying; `format.volume` uses K, M, B, T
|
|
48
54
|
* - `format.price` and `format.inherit` give "price1" / "inherit1" in PineScript: they are patterns
|
|
49
55
|
* without digit characters, and oakscriptjs does the same
|
|
50
|
-
* - `format.mintick`
|
|
56
|
+
* - `format.mintick` rounds to the tick (`math.round_to_mintick`) and shows the tick's decimals ("#.##" for 0.01);
|
|
57
|
+
* it throws without `mintick`
|
|
58
|
+
* - na gives "NaN" ("NaN%" with `format.percent`); an infinity gives "NaN" too ("NaN%" for `format.percent`,
|
|
59
|
+
* "NaNT" for `format.volume`), except `format.mintick`, which gives "Infinity" / "-Infinity"
|
|
51
60
|
*
|
|
52
61
|
* @example
|
|
53
62
|
* ```typescript
|
|
@@ -57,9 +66,14 @@ export function length(str: simple_string): int {
|
|
|
57
66
|
* str.tostring(12345.678, format.volume) // "12.346K"
|
|
58
67
|
* ```
|
|
59
68
|
*/
|
|
60
|
-
export function tostring(value: any, format?: simple_string): string {
|
|
69
|
+
export function tostring(value: any, format?: simple_string, mintick?: float): string {
|
|
61
70
|
if (typeof value !== 'number') return String(value);
|
|
62
|
-
if (
|
|
71
|
+
if (Number.isNaN(value)) return format === 'percent' ? 'NaN%' : 'NaN';
|
|
72
|
+
if (!Number.isFinite(value)) {
|
|
73
|
+
// the chart formatter has no infinity: NaN with the format's suffix
|
|
74
|
+
if (format === 'mintick') return String(value);
|
|
75
|
+
return format === 'percent' ? 'NaN%' : format === 'volume' ? 'NaNT' : 'NaN';
|
|
76
|
+
}
|
|
63
77
|
if (format === undefined || format === '') {
|
|
64
78
|
if (Math.abs(value) >= 1e21 && Number.isFinite(value)) {
|
|
65
79
|
return String(value).replace('e+', 'E').replace(/\.0+E/, 'E');
|
|
@@ -76,8 +90,16 @@ export function tostring(value: any, format?: simple_string): string {
|
|
|
76
90
|
}
|
|
77
91
|
return formatNumber(value, '#', 'halfUpShortest');
|
|
78
92
|
}
|
|
79
|
-
case 'mintick':
|
|
80
|
-
|
|
93
|
+
case 'mintick': {
|
|
94
|
+
if (mintick === undefined) {
|
|
95
|
+
throw new Error('str.tostring(x, format.mintick) needs the tick size: pass it as str.tostring(x, format.mintick, syminfo.mintick)');
|
|
96
|
+
}
|
|
97
|
+
// mask: one '#' per decimal of the tick ("#.##" for 0.01, "#" for 1)
|
|
98
|
+
const m = /^\d+(?:\.(\d+))?(?:e-(\d+))?$/.exec(String(mintick));
|
|
99
|
+
const decimals = m ? (m[1]?.length ?? 0) + Number(m[2] ?? 0) : 0;
|
|
100
|
+
const mask = decimals ? '#.' + '#'.repeat(decimals) : '#';
|
|
101
|
+
return formatNumber(round_to_mintick(value, mintick), mask, 'halfUpShortest');
|
|
102
|
+
}
|
|
81
103
|
default:
|
|
82
104
|
return formatNumber(value, format, 'halfUpShortest');
|
|
83
105
|
}
|
|
@@ -87,26 +109,33 @@ export function tostring(value: any, format?: simple_string): string {
|
|
|
87
109
|
* Converts a string to a number (float).
|
|
88
110
|
*
|
|
89
111
|
* @param str - The string to convert
|
|
90
|
-
* @returns The numeric value, or null if conversion fails
|
|
112
|
+
* @returns The numeric value, or null (na) if conversion fails
|
|
91
113
|
*
|
|
92
114
|
* @remarks
|
|
93
|
-
*
|
|
94
|
-
* -
|
|
95
|
-
* -
|
|
96
|
-
* -
|
|
97
|
-
*
|
|
115
|
+
* As in PineScript:
|
|
116
|
+
* - The whole text must be a number: a sign, digits and one optional "." ("0.5", ".5", "5.", "-12"); "12abc" is na
|
|
117
|
+
* - Scientific notation ("1e3"), "Infinity", "NaN", hexadecimal, "1,000" give na
|
|
118
|
+
* - The characters U+0000 to U+0020 (space, tab, line feed...) around the number are ignored; other spaces
|
|
119
|
+
* (no-break space U+00A0, U+3000) give na
|
|
98
120
|
*
|
|
99
121
|
* @example
|
|
100
122
|
* ```typescript
|
|
101
123
|
* str.tonumber("123") // Returns: 123
|
|
102
124
|
* str.tonumber("45.67") // Returns: 45.67
|
|
103
125
|
* str.tonumber("abc") // Returns: null
|
|
104
|
-
* str.tonumber("
|
|
126
|
+
* str.tonumber("12abc") // Returns: null
|
|
127
|
+
* str.tonumber("1e3") // Returns: null
|
|
105
128
|
* ```
|
|
106
129
|
*/
|
|
107
130
|
export function tonumber(str: simple_string): float | null {
|
|
108
|
-
|
|
109
|
-
|
|
131
|
+
// remove the characters U+0000..U+0020 at both ends
|
|
132
|
+
let start = 0;
|
|
133
|
+
let end = str.length;
|
|
134
|
+
while (start < end && str.charCodeAt(start) <= 0x20) start++;
|
|
135
|
+
while (end > start && str.charCodeAt(end - 1) <= 0x20) end--;
|
|
136
|
+
const text = str.slice(start, end);
|
|
137
|
+
if (!/^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(text)) return null;
|
|
138
|
+
return parseFloat(text);
|
|
110
139
|
}
|
|
111
140
|
|
|
112
141
|
/**
|
|
@@ -118,6 +147,7 @@ export function tonumber(str: simple_string): float | null {
|
|
|
118
147
|
* @returns The extracted substring
|
|
119
148
|
*
|
|
120
149
|
* @remarks
|
|
150
|
+
* - An na `end` (NaN / null) is a runtime error in PineScript: it throws
|
|
121
151
|
* - If begin > end, indices are swapped (JavaScript behavior)
|
|
122
152
|
* - Negative indices are treated as 0
|
|
123
153
|
* - Indices beyond string length return empty string
|
|
@@ -130,6 +160,7 @@ export function tonumber(str: simple_string): float | null {
|
|
|
130
160
|
* ```
|
|
131
161
|
*/
|
|
132
162
|
export function substring(str: simple_string, begin: simple_int, end?: simple_int): string {
|
|
163
|
+
if (end === null || Number.isNaN(end)) throw new Error('str.substring: the end position is na');
|
|
133
164
|
return str.substring(begin, end);
|
|
134
165
|
}
|
|
135
166
|
|
|
@@ -194,51 +225,63 @@ export function contains(source: simple_string, str: simple_string): bool {
|
|
|
194
225
|
*
|
|
195
226
|
* @param source - The string to search in
|
|
196
227
|
* @param str - The substring to search for
|
|
197
|
-
* @returns The 0-based index of the first occurrence, or
|
|
228
|
+
* @returns The 0-based index of the first occurrence, or na (NaN) if not found
|
|
198
229
|
*
|
|
199
230
|
* @remarks
|
|
200
231
|
* - Search is case-sensitive
|
|
201
232
|
* - Returns 0 for empty search string
|
|
202
|
-
* - Returns
|
|
233
|
+
* - Returns na (NaN) if substring is not found, as in PineScript
|
|
203
234
|
*
|
|
204
235
|
* @example
|
|
205
236
|
* ```typescript
|
|
206
237
|
* str.pos("hello world", "world") // Returns: 6
|
|
207
238
|
* str.pos("hello world", "o") // Returns: 4 (first occurrence)
|
|
208
|
-
* str.pos("hello", "xyz") // Returns:
|
|
239
|
+
* str.pos("hello", "xyz") // Returns: NaN (na)
|
|
209
240
|
* ```
|
|
210
241
|
*/
|
|
211
242
|
export function pos(source: simple_string, str: simple_string): int {
|
|
212
|
-
|
|
243
|
+
const index = source.indexOf(str);
|
|
244
|
+
return index === -1 ? NaN : index;
|
|
213
245
|
}
|
|
214
246
|
|
|
215
247
|
/**
|
|
216
|
-
* Replaces
|
|
248
|
+
* Replaces the Nth occurrence of a substring (PineScript `str.replace`).
|
|
217
249
|
*
|
|
218
250
|
* @param source - The source string
|
|
219
251
|
* @param target - The substring to replace
|
|
220
252
|
* @param replacement - The string to replace with
|
|
221
|
-
* @param occurrence -
|
|
222
|
-
* @returns The string with
|
|
253
|
+
* @param occurrence - 0-based occurrence to replace (default 0, the first one)
|
|
254
|
+
* @returns The string with one occurrence replaced
|
|
223
255
|
*
|
|
224
256
|
* @remarks
|
|
225
|
-
*
|
|
226
|
-
* -
|
|
227
|
-
* -
|
|
257
|
+
* PineScript rules:
|
|
258
|
+
* - only the Nth occurrence is replaced; an na occurrence is 0 (`str.replace("aaa", "a", "-", na)` is "-aa")
|
|
259
|
+
* - occurrences overlap: "aa" occurs at 0 and 1 in "aaa" (`str.replace("aaa", "aa", "-", 1)` is "a-")
|
|
260
|
+
* - an empty target inserts the replacement at character N, at most at the end
|
|
261
|
+
* (`str.replace("abc", "", "-", 2)` is "ab-c")
|
|
262
|
+
* - fewer than N + 1 occurrences (or a negative N) leave the source unchanged
|
|
228
263
|
*
|
|
229
264
|
* @example
|
|
230
265
|
* ```typescript
|
|
231
|
-
* str.replace("hello world hello", "hello", "hi"
|
|
232
|
-
* str.replace("hello world hello", "hello", "hi") // Returns: "
|
|
266
|
+
* str.replace("hello world hello", "hello", "hi") // Returns: "hi world hello"
|
|
267
|
+
* str.replace("hello world hello", "hello", "hi", 1) // Returns: "hello world hi"
|
|
233
268
|
* str.replace("test", "xyz", "abc") // Returns: "test"
|
|
234
269
|
* ```
|
|
235
270
|
*/
|
|
236
|
-
export function replace(source: simple_string, target: simple_string, replacement: simple_string, occurrence
|
|
237
|
-
|
|
238
|
-
|
|
271
|
+
export function replace(source: simple_string, target: simple_string, replacement: simple_string, occurrence: simple_int = 0): string {
|
|
272
|
+
const n = occurrence === null || Number.isNaN(occurrence) ? 0 : Math.trunc(occurrence);
|
|
273
|
+
if (n < 0) return source;
|
|
274
|
+
let index: number;
|
|
275
|
+
if (target === '') {
|
|
276
|
+
index = Math.min(n, source.length);
|
|
239
277
|
} else {
|
|
240
|
-
|
|
278
|
+
index = -1;
|
|
279
|
+
for (let k = 0; k <= n; k++) {
|
|
280
|
+
index = source.indexOf(target, index + 1);
|
|
281
|
+
if (index === -1) return source;
|
|
282
|
+
}
|
|
241
283
|
}
|
|
284
|
+
return source.slice(0, index) + replacement + source.slice(index + target.length);
|
|
242
285
|
}
|
|
243
286
|
|
|
244
287
|
/**
|
|
@@ -274,7 +317,7 @@ export function replace_all(source: simple_string, target: simple_string, replac
|
|
|
274
317
|
* @returns Array of substrings
|
|
275
318
|
*
|
|
276
319
|
* @remarks
|
|
277
|
-
* - Empty separator splits into individual characters
|
|
320
|
+
* - Empty separator splits into individual characters; an empty string gives [""] (also with "")
|
|
278
321
|
* - If separator is not found, returns array with original string
|
|
279
322
|
* - Consecutive separators create empty strings in result
|
|
280
323
|
*
|
|
@@ -286,7 +329,8 @@ export function replace_all(source: simple_string, target: simple_string, replac
|
|
|
286
329
|
* ```
|
|
287
330
|
*/
|
|
288
331
|
export function split(str: simple_string, separator: simple_string): string[] {
|
|
289
|
-
|
|
332
|
+
// JavaScript gives [] for "".split(""); PineScript gives [""]
|
|
333
|
+
return str === '' ? [''] : str.split(separator);
|
|
290
334
|
}
|
|
291
335
|
|
|
292
336
|
/**
|
|
@@ -316,7 +360,8 @@ export function concat(...strings: simple_string[]): string {
|
|
|
316
360
|
* @remarks
|
|
317
361
|
* As in PineScript: numbers in `{n}` use
|
|
318
362
|
* "#,##0.###" (1234.5678 gives "1,234.568"); number patterns round half to even on the exact
|
|
319
|
-
* value; `'...'` is literal text and `''` a quote;
|
|
363
|
+
* value (16 decimals at most from 1e-3 up); an array gives "[a, b]"; `'...'` is literal text and `''` a quote;
|
|
364
|
+
* a missing argument leaves `{n}` in the text.
|
|
320
365
|
* Dates are formatted in UTC here; PineScript uses the exchange time zone.
|
|
321
366
|
*
|
|
322
367
|
* @example
|
|
@@ -459,26 +504,25 @@ export function trimRight(str: simple_string): string {
|
|
|
459
504
|
}
|
|
460
505
|
|
|
461
506
|
/**
|
|
462
|
-
*
|
|
507
|
+
* Returns the first substring of `source` that matches the `regex` (PineScript `str.match`).
|
|
463
508
|
*
|
|
464
|
-
* @param source -
|
|
465
|
-
* @param regex -
|
|
466
|
-
* @returns
|
|
509
|
+
* @param source - Source string
|
|
510
|
+
* @param regex - Regular expression
|
|
511
|
+
* @returns The first match (searched anywhere in the string), or na (null) when nothing matches or the match is empty
|
|
467
512
|
*
|
|
468
513
|
* @remarks
|
|
514
|
+
* - As PineScript: "Hello World!" with "o" gives "o", with "[a-z]+" gives "ello"; no match gives na
|
|
469
515
|
* - Uses JavaScript regex syntax
|
|
470
|
-
* - Match is case-sensitive by default
|
|
471
|
-
* - Pattern is tested against entire string
|
|
472
516
|
*
|
|
473
517
|
* @example
|
|
474
518
|
* ```typescript
|
|
475
|
-
* str.match("
|
|
476
|
-
* str.match("
|
|
477
|
-
* str.match("abc", "[A-Z]+") // Returns: false
|
|
519
|
+
* str.match("abc123def", "[0-9]+") // Returns: "123"
|
|
520
|
+
* str.match("abc", "[0-9]+") // Returns: null (na)
|
|
478
521
|
* ```
|
|
479
522
|
*/
|
|
480
|
-
export function match(source: simple_string, regex: simple_string):
|
|
481
|
-
|
|
523
|
+
export function match(source: simple_string, regex: simple_string): simple_string | null {
|
|
524
|
+
const found = new RegExp(regex).exec(source);
|
|
525
|
+
return found && found[0] !== '' ? found[0] : null;
|
|
482
526
|
}
|
|
483
527
|
|
|
484
528
|
/**
|
|
@@ -521,21 +565,21 @@ export function match(source: simple_string, regex: simple_string): bool {
|
|
|
521
565
|
* Constructs a new string containing the source string repeated N times with an optional separator.
|
|
522
566
|
*
|
|
523
567
|
* @param source - The string to repeat
|
|
524
|
-
* @param count - Number of times to repeat
|
|
568
|
+
* @param count - Number of times to repeat
|
|
525
569
|
* @param separator - String to inject between repeated instances. Optional, defaults to empty string.
|
|
526
|
-
* @returns The repeated string, or null if source is
|
|
570
|
+
* @returns The repeated string, or null (na) if source is na or count is 0 / negative / na, as in PineScript
|
|
527
571
|
*
|
|
528
572
|
* @example
|
|
529
573
|
* ```typescript
|
|
530
574
|
* str.repeat("ab", 3) // Returns: "ababab"
|
|
531
575
|
* str.repeat("?", 3, ",") // Returns: "?,?,?"
|
|
532
|
-
* str.repeat("hello", 0) // Returns:
|
|
576
|
+
* str.repeat("hello", 0) // Returns: null (na)
|
|
533
577
|
* str.repeat("x", 1) // Returns: "x"
|
|
534
578
|
* ```
|
|
535
579
|
*/
|
|
536
580
|
export function repeat(source: simple_string, count: simple_int, separator: simple_string = ''): string | null {
|
|
537
581
|
if (source == null) return null;
|
|
538
|
-
if (count
|
|
582
|
+
if (!(count > 0)) return null; // 0, negative or na: na
|
|
539
583
|
return Array(count).fill(source).join(separator);
|
|
540
584
|
}
|
|
541
585
|
|
package/src/str/messageformat.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `str.format` patterns as PineScript formats them (Java MessageFormat):
|
|
3
3
|
*
|
|
4
|
-
* - `{n}` inserts argument n; numbers use "#,##0.###" (1234.5678 gives "1,234.568")
|
|
4
|
+
* - `{n}` inserts argument n; numbers use "#,##0.###" (1234.5678 gives "1,234.568"); an array gives
|
|
5
|
+
* "[Hello, World!]"
|
|
5
6
|
* - `{n,number}`, `{n,number,integer}` ("#,##0"), `{n,number,percent}` ("#,##0%"),
|
|
6
7
|
* `{n,number,currency}` ("$1,234.57", "-$7.00"), `{n,number,<pattern>}`
|
|
7
8
|
* - `{n,date}`, `{n,time}`, `{n,date,<pattern>}`, `{n,time,<pattern>}` (styles short, medium, long, full)
|
|
@@ -44,7 +45,11 @@ function formatArgument(
|
|
|
44
45
|
timezone: string | (() => string)
|
|
45
46
|
): string {
|
|
46
47
|
if (value === undefined) return '';
|
|
47
|
-
if (!type)
|
|
48
|
+
if (!type) {
|
|
49
|
+
// an array gives "[a, b]" (PineScript); the element format of numbers is not measured: plain JS text
|
|
50
|
+
if (Array.isArray(value)) return '[' + value.map((v) => (v == null ? 'NaN' : String(v))).join(', ') + ']';
|
|
51
|
+
return typeof value === 'number' ? number(value, '#,##0.###') : String(value);
|
|
52
|
+
}
|
|
48
53
|
switch (type.trim()) {
|
|
49
54
|
case 'number': {
|
|
50
55
|
const s = style?.trim();
|
package/src/str/numberformat.ts
CHANGED
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* - with an exponent, the value is first rounded to the fraction digits ("0.00E0": 0.125 gives "1.30E-1")
|
|
13
13
|
* - a pattern without digit characters (`format.price` is "price") formats like "#" after its text,
|
|
14
14
|
* and keeps the sign: -0.004 gives "-price0"
|
|
15
|
+
* - "pos;neg": a negative number uses the text around the digits of the negative subpattern instead of "-"
|
|
16
|
+
* ("#.##;(#.##)" gives "(3.5)" for -3.5); the digits follow the positive subpattern, as in Java
|
|
17
|
+
* - at most 16 decimals when |x| >= 1e-3 (the shortest digits of a double never have more there):
|
|
18
|
+
* 0.1 with 20 '#' decimals gives "0.1" in both rounding modes
|
|
15
19
|
*/
|
|
16
20
|
|
|
17
21
|
export type Rounding = 'halfUpShortest' | 'halfEvenExact';
|
|
@@ -19,6 +23,9 @@ export type Rounding = 'halfUpShortest' | 'halfEvenExact';
|
|
|
19
23
|
interface Pattern {
|
|
20
24
|
prefix: string;
|
|
21
25
|
suffix: string;
|
|
26
|
+
/** Text around the digits of the negative subpattern ("pos;neg"), or null without one */
|
|
27
|
+
negPrefix: string | null;
|
|
28
|
+
negSuffix: string | null;
|
|
22
29
|
minInt: number;
|
|
23
30
|
minFrac: number;
|
|
24
31
|
maxFrac: number;
|
|
@@ -32,9 +39,27 @@ interface Pattern {
|
|
|
32
39
|
|
|
33
40
|
const patterns = new Map<string, Pattern>();
|
|
34
41
|
|
|
42
|
+
/** Index of the first ';' outside quotes, or -1. */
|
|
43
|
+
function subpatternSplit(pattern: string): number {
|
|
44
|
+
let quoted = false;
|
|
45
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
46
|
+
if (pattern[i] === "'") quoted = !quoted;
|
|
47
|
+
else if (pattern[i] === ';' && !quoted) return i;
|
|
48
|
+
}
|
|
49
|
+
return -1;
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
function parsePattern(pattern: string): Pattern {
|
|
36
53
|
const cached = patterns.get(pattern);
|
|
37
54
|
if (cached) return cached;
|
|
55
|
+
const semi = subpatternSplit(pattern);
|
|
56
|
+
if (semi !== -1) {
|
|
57
|
+
// only the text around the digits of the negative subpattern is used
|
|
58
|
+
const neg = parsePattern(pattern.slice(semi + 1));
|
|
59
|
+
const parsed = { ...parsePattern(pattern.slice(0, semi)), negPrefix: neg.prefix, negSuffix: neg.suffix };
|
|
60
|
+
patterns.set(pattern, parsed);
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
38
63
|
|
|
39
64
|
let prefix = '';
|
|
40
65
|
let suffix = '';
|
|
@@ -72,6 +97,8 @@ function parsePattern(pattern: string): Pattern {
|
|
|
72
97
|
const parsed: Pattern = {
|
|
73
98
|
prefix,
|
|
74
99
|
suffix,
|
|
100
|
+
negPrefix: null,
|
|
101
|
+
negSuffix: null,
|
|
75
102
|
// as in Java, a pattern with a decimal point and no '0' at all ("#.##") shows one integer digit
|
|
76
103
|
minInt: !mantissa.includes('0') && mantissa.includes('.') ? 1 : [...intPart].filter((c) => c === '0').length,
|
|
77
104
|
minFrac: [...fracPart].filter((c) => c === '0').length,
|
|
@@ -169,7 +196,7 @@ function layout(d: Digits, p: Pattern): string {
|
|
|
169
196
|
export function formatNumber(value: number, pattern: string, mode: Rounding): string {
|
|
170
197
|
if (Number.isNaN(value)) return 'NaN';
|
|
171
198
|
const p = parsePattern(pattern);
|
|
172
|
-
if (!Number.isFinite(value)) return (value < 0
|
|
199
|
+
if (!Number.isFinite(value)) return affixes(p, value < 0, '∞');
|
|
173
200
|
const x = p.percent ? value * 100 : value;
|
|
174
201
|
const toDigits = mode === 'halfUpShortest' ? shortestDigits : exactDigits;
|
|
175
202
|
|
|
@@ -188,10 +215,18 @@ export function formatNumber(value: number, pattern: string, mode: Rounding): st
|
|
|
188
215
|
const e = String(Math.abs(exponent)).padStart(p.exponent, '0');
|
|
189
216
|
body = layout(mant, p) + 'E' + (exponent < 0 ? '-' : '') + e;
|
|
190
217
|
} else {
|
|
191
|
-
|
|
218
|
+
// 16 decimals at most from 1e-3 up (the shortest digits of a double have no more there)
|
|
219
|
+
const frac = !p.hasDigits ? 0 : Math.abs(x) >= 1e-3 ? Math.min(p.maxFrac, 16) : p.maxFrac;
|
|
220
|
+
const rounded = roundDigits(toDigits(x), frac, mode);
|
|
192
221
|
isZero = rounded.digits === '';
|
|
193
222
|
body = p.hasDigits ? layout(rounded, p) : layout(rounded, { ...p, minInt: 0, maxFrac: 0, minFrac: 0 });
|
|
194
223
|
}
|
|
195
|
-
|
|
196
|
-
|
|
224
|
+
return affixes(p, x < 0 && (!isZero || !p.hasDigits), body);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** `body` with the text of the positive pattern, or of the negative one ("-" + prefix without "pos;neg"). */
|
|
228
|
+
function affixes(p: Pattern, negative: boolean, body: string): string {
|
|
229
|
+
if (!negative) return p.prefix + body + p.suffix;
|
|
230
|
+
if (p.negPrefix === null) return '-' + p.prefix + body + p.suffix;
|
|
231
|
+
return p.negPrefix + body + p.negSuffix!;
|
|
197
232
|
}
|