@supersolutions/super-chart-superscript 0.0.0 → 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/LICENSE +674 -0
- package/README.md +11 -1
- package/dist/chunk-PAWYIS77.js +1214 -0
- package/dist/index.d.ts +354 -0
- package/dist/index.js +429 -0
- package/dist/scripts.d.ts +119 -0
- package/dist/scripts.js +443 -0
- package/package.json +43 -6
|
@@ -0,0 +1,1214 @@
|
|
|
1
|
+
// packages/superscript/src/runtime/builtins.ts
|
|
2
|
+
import { ta } from "@supersolutions/super-chart-core";
|
|
3
|
+
function asSeries(v, n) {
|
|
4
|
+
if (typeof v !== "number")
|
|
5
|
+
return v;
|
|
6
|
+
return new Array(n).fill(Number.isFinite(v) ? v : null);
|
|
7
|
+
}
|
|
8
|
+
function finiteOrNa(r) {
|
|
9
|
+
return Number.isFinite(r) ? r : NaN;
|
|
10
|
+
}
|
|
11
|
+
function mapValue(v, f) {
|
|
12
|
+
if (typeof v === "number")
|
|
13
|
+
return Number.isNaN(v) ? NaN : finiteOrNa(f(v));
|
|
14
|
+
return v.map((x) => {
|
|
15
|
+
if (x == null)
|
|
16
|
+
return null;
|
|
17
|
+
const r = f(x);
|
|
18
|
+
return Number.isFinite(r) ? r : null;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function zipValue(a, b, f, n) {
|
|
22
|
+
if (typeof a === "number" && typeof b === "number")
|
|
23
|
+
return Number.isNaN(a) || Number.isNaN(b) ? NaN : finiteOrNa(f(a, b));
|
|
24
|
+
const out = new Array(n).fill(null);
|
|
25
|
+
for (let i = 0; i < n; i++) {
|
|
26
|
+
const x = typeof a === "number" ? a : a[i];
|
|
27
|
+
const y = typeof b === "number" ? b : b[i];
|
|
28
|
+
if (x == null || y == null || Number.isNaN(x) || Number.isNaN(y))
|
|
29
|
+
continue;
|
|
30
|
+
const r = f(x, y);
|
|
31
|
+
out[i] = Number.isFinite(r) ? r : null;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
var S = (v, ctx) => asSeries(v, ctx.n);
|
|
36
|
+
var N = (v) => v;
|
|
37
|
+
var src = (name = "src") => ({ name, kind: "series" });
|
|
38
|
+
var len = (min = 1, name = "len") => ({ name, kind: "length", min });
|
|
39
|
+
function def(name, params, groups, impl, note) {
|
|
40
|
+
const b = { name, params, groups, impl };
|
|
41
|
+
if (note !== void 0)
|
|
42
|
+
b.note = note;
|
|
43
|
+
return b;
|
|
44
|
+
}
|
|
45
|
+
var unary = (name, f) => def(name, [src("x")], ["math"], (a) => mapValue(a[0], f));
|
|
46
|
+
var LIST = [
|
|
47
|
+
def("sma", [src(), len()], ["ma"], (a, c) => ta.sma(S(a[0], c), N(a[1]))),
|
|
48
|
+
def("ema", [src(), len()], ["ma"], (a, c) => ta.ema(S(a[0], c), N(a[1]))),
|
|
49
|
+
def("wma", [src(), len()], ["ma"], (a, c) => ta.wma(S(a[0], c), N(a[1]))),
|
|
50
|
+
def("rma", [src(), len()], ["ma"], (a, c) => ta.rma(S(a[0], c), N(a[1]))),
|
|
51
|
+
def("hma", [src(), len(2)], ["ma"], (a, c) => ta.hma(S(a[0], c), N(a[1]))),
|
|
52
|
+
def("vwma", [src(), len()], ["ma", "volume"], (a, c) => ta.vwma(S(a[0], c), c.series("volume"), N(a[1]))),
|
|
53
|
+
def("rsi", [src(), len()], ["osc"], (a, c) => ta.rsi(S(a[0], c), N(a[1]))),
|
|
54
|
+
def("stoch", [len()], ["osc"], (a, c) => ta.stochK(c.series("high"), c.series("low"), c.series("close"), N(a[0])), "%K over close/high/low"),
|
|
55
|
+
def("cci", [len()], ["osc"], (a, c) => ta.cci(c.series("high"), c.series("low"), c.series("close"), N(a[0])), "over hlc3"),
|
|
56
|
+
def("mfi", [len()], ["osc", "volume"], (a, c) => ta.mfi(c.series("high"), c.series("low"), c.series("close"), c.series("volume"), N(a[0])), "over hlc3"),
|
|
57
|
+
def("roc", [src(), len()], ["osc"], (a, c) => ta.roc(S(a[0], c), N(a[1]))),
|
|
58
|
+
def("mom", [src(), len()], ["osc"], (a, c) => ta.mom(S(a[0], c), N(a[1]))),
|
|
59
|
+
def("stdev", [src(), len()], ["vol"], (a, c) => ta.stdev(S(a[0], c), N(a[1]))),
|
|
60
|
+
def("dev", [src(), len()], ["vol"], (a, c) => ta.dev(S(a[0], c), N(a[1]))),
|
|
61
|
+
def("atr", [len()], ["vol"], (a, c) => ta.atr(c.series("high"), c.series("low"), c.series("close"), N(a[0]))),
|
|
62
|
+
def("tr", [], ["vol"], (_a, c) => ta.trueRange(c.series("high"), c.series("low"), c.series("close"))),
|
|
63
|
+
def("obv", [], ["volume"], (_a, c) => ta.obv(c.series("close"), c.series("volume"))),
|
|
64
|
+
def("vwap", [], ["volume"], (_a, c) => ta.vwap(c.series("high"), c.series("low"), c.series("close"), c.series("volume")), "cumulative from the first bar, no session reset"),
|
|
65
|
+
def(
|
|
66
|
+
"sar",
|
|
67
|
+
[{ name: "start", kind: "number", default: 0.02 }, { name: "inc", kind: "number", default: 0.02 }, { name: "max", kind: "number", default: 0.2 }],
|
|
68
|
+
["trend"],
|
|
69
|
+
(a, c) => ta.sar(c.series("high"), c.series("low"), N(a[0]), N(a[1]), N(a[2]))
|
|
70
|
+
),
|
|
71
|
+
def("linreg", [src(), len(), { name: "offset", kind: "count", default: 0 }], ["trend"], (a, c) => ta.linreg(S(a[0], c), N(a[1]), N(a[2]))),
|
|
72
|
+
def("correlation", [src("a"), src("b"), len()], ["trend"], (a, c) => ta.correlation(S(a[0], c), S(a[1], c), N(a[2]))),
|
|
73
|
+
def("highest", [src(), len()], ["range"], (a, c) => ta.highest(S(a[0], c), N(a[1]))),
|
|
74
|
+
def("lowest", [src(), len()], ["range"], (a, c) => ta.lowest(S(a[0], c), N(a[1]))),
|
|
75
|
+
def("sum", [src(), len()], ["range"], (a, c) => ta.sum(S(a[0], c), N(a[1]))),
|
|
76
|
+
def("cum", [src()], ["range"], (a, c) => ta.cum(S(a[0], c))),
|
|
77
|
+
def("change", [src(), { name: "len", kind: "length", min: 1, default: 1 }], ["range"], (a, c) => ta.change(S(a[0], c), N(a[1]))),
|
|
78
|
+
def("crossover", [src("a"), src("b")], ["signal"], (a, c) => ta.crossover(S(a[0], c), S(a[1], c))),
|
|
79
|
+
def("crossunder", [src("a"), src("b")], ["signal"], (a, c) => ta.crossunder(S(a[0], c), S(a[1], c))),
|
|
80
|
+
def("cross", [src("a"), src("b")], ["signal"], (a, c) => ta.cross(S(a[0], c), S(a[1], c))),
|
|
81
|
+
def("barssince", [src("cond")], ["signal"], (a, c) => ta.barssince(S(a[0], c))),
|
|
82
|
+
def("valuewhen", [src("cond"), src(), { name: "occurrence", kind: "count", default: 0 }], ["signal"], (a, c) => ta.valuewhen(S(a[0], c), S(a[1], c), N(a[2]))),
|
|
83
|
+
def("pivothigh", [src(), { name: "left", kind: "count" }, { name: "right", kind: "count" }], ["pivot"], (a, c) => ta.pivot(S(a[0], c), N(a[1]), N(a[2]), "high")),
|
|
84
|
+
def("pivotlow", [src(), { name: "left", kind: "count" }, { name: "right", kind: "count" }], ["pivot"], (a, c) => ta.pivot(S(a[0], c), N(a[1]), N(a[2]), "low")),
|
|
85
|
+
unary("abs", Math.abs),
|
|
86
|
+
unary("sign", Math.sign),
|
|
87
|
+
unary("round", Math.round),
|
|
88
|
+
unary("floor", Math.floor),
|
|
89
|
+
unary("ceil", Math.ceil),
|
|
90
|
+
unary("sqrt", Math.sqrt),
|
|
91
|
+
unary("log", Math.log),
|
|
92
|
+
unary("log10", Math.log10),
|
|
93
|
+
unary("exp", Math.exp),
|
|
94
|
+
def("pow", [src("a"), src("b")], ["math"], (a, c) => zipValue(a[0], a[1], Math.pow, c.n)),
|
|
95
|
+
def("max", [src("a"), src("b")], ["math"], (a, c) => zipValue(a[0], a[1], Math.max, c.n)),
|
|
96
|
+
def("min", [src("a"), src("b")], ["math"], (a, c) => zipValue(a[0], a[1], Math.min, c.n)),
|
|
97
|
+
def("nz", [src("x"), { name: "replacement", kind: "series", default: 0 }], ["math"], (a, c) => {
|
|
98
|
+
const x = a[0];
|
|
99
|
+
const r = a[1];
|
|
100
|
+
if (typeof x === "number")
|
|
101
|
+
return Number.isFinite(x) ? x : r;
|
|
102
|
+
return x.map((v, i) => v != null ? v : typeof r === "number" ? Number.isFinite(r) ? r : null : r[i]);
|
|
103
|
+
}),
|
|
104
|
+
def("na", [src("x")], ["math"], (a, c) => {
|
|
105
|
+
const x = a[0];
|
|
106
|
+
if (typeof x === "number")
|
|
107
|
+
return Number.isFinite(x) ? 0 : 1;
|
|
108
|
+
return x.map((v) => v == null ? 1 : 0);
|
|
109
|
+
})
|
|
110
|
+
];
|
|
111
|
+
var BUILTINS = new Map(LIST.map((b) => [b.name, b]));
|
|
112
|
+
var PINE_ALIASES = new Map([
|
|
113
|
+
...[
|
|
114
|
+
"sma",
|
|
115
|
+
"ema",
|
|
116
|
+
"wma",
|
|
117
|
+
"rma",
|
|
118
|
+
"hma",
|
|
119
|
+
"vwma",
|
|
120
|
+
"rsi",
|
|
121
|
+
"roc",
|
|
122
|
+
"mom",
|
|
123
|
+
"stdev",
|
|
124
|
+
"dev",
|
|
125
|
+
"atr",
|
|
126
|
+
"tr",
|
|
127
|
+
"sar",
|
|
128
|
+
"linreg",
|
|
129
|
+
"correlation",
|
|
130
|
+
"highest",
|
|
131
|
+
"lowest",
|
|
132
|
+
"cum",
|
|
133
|
+
"change",
|
|
134
|
+
"crossover",
|
|
135
|
+
"crossunder",
|
|
136
|
+
"cross",
|
|
137
|
+
"barssince",
|
|
138
|
+
"valuewhen",
|
|
139
|
+
"pivothigh",
|
|
140
|
+
"pivotlow"
|
|
141
|
+
].map((n) => [`ta.${n}`, n]),
|
|
142
|
+
...["abs", "sign", "round", "floor", "ceil", "sqrt", "log", "log10", "exp", "pow", "max", "min", "sum"].map((n) => [`math.${n}`, n])
|
|
143
|
+
]);
|
|
144
|
+
var PINE_ARG_NAMES = /* @__PURE__ */ new Map([["source", "src"], ["length", "len"]]);
|
|
145
|
+
var PINE_COLORS = new Map([
|
|
146
|
+
["aqua", "#00BCD4"],
|
|
147
|
+
["black", "#363A45"],
|
|
148
|
+
["blue", "#2196F3"],
|
|
149
|
+
["fuchsia", "#E040FB"],
|
|
150
|
+
["gray", "#787B86"],
|
|
151
|
+
["green", "#4CAF50"],
|
|
152
|
+
["lime", "#00E676"],
|
|
153
|
+
["maroon", "#880E4F"],
|
|
154
|
+
["navy", "#311B92"],
|
|
155
|
+
["olive", "#808000"],
|
|
156
|
+
["orange", "#FF9800"],
|
|
157
|
+
["purple", "#9C27B0"],
|
|
158
|
+
["red", "#FF5252"],
|
|
159
|
+
["silver", "#B2B5BE"],
|
|
160
|
+
["teal", "#00897B"],
|
|
161
|
+
["white", "#FFFFFF"],
|
|
162
|
+
["yellow", "#FFEB3B"]
|
|
163
|
+
].map(([k, v]) => [`color.${k}`, v]));
|
|
164
|
+
var SERIES_NAMES = ["open", "high", "low", "close", "volume", "hl2", "hlc3", "ohlc4", "bar_index", "time"];
|
|
165
|
+
function signature(b) {
|
|
166
|
+
const ps = b.params.map((p) => p.default !== void 0 && b.name !== "nz" ? `${p.name}=${p.default}` : p.name);
|
|
167
|
+
return `${b.name}(${ps.join(",")})${b.note !== void 0 ? ` (${b.note})` : ""}`;
|
|
168
|
+
}
|
|
169
|
+
var GROUPS = [
|
|
170
|
+
["ma", "Moving averages"],
|
|
171
|
+
["osc", "Oscillators"],
|
|
172
|
+
["vol", "Volatility", "Bollinger via basis=sma(close,len) and stdev(close,len)."],
|
|
173
|
+
["volume", "Volume"],
|
|
174
|
+
["trend", "Trend"],
|
|
175
|
+
["range", "Ranges/aggregates"],
|
|
176
|
+
["signal", "Signals"],
|
|
177
|
+
["pivot", "Pivots"],
|
|
178
|
+
["math", "Math"]
|
|
179
|
+
];
|
|
180
|
+
function builtinReferenceLines() {
|
|
181
|
+
return GROUPS.map(([g, label, extra]) => {
|
|
182
|
+
const fns = LIST.filter((b) => b.groups.includes(g)).map(signature).join(", ");
|
|
183
|
+
return `${label}: ${fns}.${extra !== void 0 ? ` ${extra}` : ""}`;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// packages/superscript/src/runtime/diagnostics.ts
|
|
188
|
+
var BRAND = Symbol.for("zzyzx.superscript.error");
|
|
189
|
+
var SuperScriptError = class extends Error {
|
|
190
|
+
code;
|
|
191
|
+
line;
|
|
192
|
+
col;
|
|
193
|
+
endCol;
|
|
194
|
+
hint;
|
|
195
|
+
diagnostics;
|
|
196
|
+
constructor(diagnostic, diagnostics = [diagnostic]) {
|
|
197
|
+
super(`${diagnostic.message} (line ${diagnostic.line}, col ${diagnostic.col})${diagnostic.hint !== void 0 ? `; ${diagnostic.hint}` : ""}`);
|
|
198
|
+
this.name = "SuperScriptError";
|
|
199
|
+
this.code = diagnostic.code;
|
|
200
|
+
this.line = diagnostic.line;
|
|
201
|
+
this.col = diagnostic.col;
|
|
202
|
+
this.endCol = diagnostic.endCol;
|
|
203
|
+
if (diagnostic.hint !== void 0)
|
|
204
|
+
this.hint = diagnostic.hint;
|
|
205
|
+
this.diagnostics = diagnostics;
|
|
206
|
+
Object.defineProperty(this, BRAND, { value: true });
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
function isSuperScriptError(e) {
|
|
210
|
+
return typeof e === "object" && e !== null && e[BRAND] === true;
|
|
211
|
+
}
|
|
212
|
+
var DiagnosticThrow = class extends Error {
|
|
213
|
+
constructor(diagnostic) {
|
|
214
|
+
super(diagnostic.message);
|
|
215
|
+
this.diagnostic = diagnostic;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
function diag(severity, code, message, at, hint) {
|
|
219
|
+
const d = { severity, code, message, line: at.line, col: at.col, endCol: at.endCol };
|
|
220
|
+
if (hint !== void 0)
|
|
221
|
+
d.hint = hint;
|
|
222
|
+
return d;
|
|
223
|
+
}
|
|
224
|
+
function fail(code, message, at, hint) {
|
|
225
|
+
throw new DiagnosticThrow(diag("error", code, message, at, hint));
|
|
226
|
+
}
|
|
227
|
+
function formatDiagnostics(diagnostics, source) {
|
|
228
|
+
const lines = source?.split("\n");
|
|
229
|
+
return diagnostics.map((d) => {
|
|
230
|
+
let s = `line ${d.line}, col ${d.col}: ${d.severity} ${d.code}: ${d.message}`;
|
|
231
|
+
if (d.hint !== void 0)
|
|
232
|
+
s += ` (${d.hint})`;
|
|
233
|
+
const text = lines?.[d.line - 1];
|
|
234
|
+
if (text !== void 0) {
|
|
235
|
+
s += `
|
|
236
|
+
${text.replace(/\r$/, "")}
|
|
237
|
+
${" ".repeat(Math.max(d.col - 1, 0))}${"^".repeat(Math.max(d.endCol - d.col, 1))}`;
|
|
238
|
+
}
|
|
239
|
+
return s;
|
|
240
|
+
}).join("\n");
|
|
241
|
+
}
|
|
242
|
+
function closest(word, candidates) {
|
|
243
|
+
let best;
|
|
244
|
+
let bestD = 3;
|
|
245
|
+
const w = word.toLowerCase();
|
|
246
|
+
for (const c of candidates) {
|
|
247
|
+
const d = distance(w, c.toLowerCase(), bestD);
|
|
248
|
+
if (d < bestD) {
|
|
249
|
+
bestD = d;
|
|
250
|
+
best = c;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return best;
|
|
254
|
+
}
|
|
255
|
+
function distance(a, b, cap) {
|
|
256
|
+
if (Math.abs(a.length - b.length) >= cap)
|
|
257
|
+
return cap;
|
|
258
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
259
|
+
for (let i = 1; i <= a.length; i++) {
|
|
260
|
+
const cur = [i];
|
|
261
|
+
for (let j = 1; j <= b.length; j++) {
|
|
262
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
263
|
+
}
|
|
264
|
+
prev = cur;
|
|
265
|
+
}
|
|
266
|
+
return prev[b.length];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// packages/superscript/src/runtime/language.ts
|
|
270
|
+
var TOKEN = /([ \t\r\f\v]+)|(\/\/[^\n]*)|(\n)|((?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)|("[^"\n]*"|'[^'\n]*')|(["'])|([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)|(#[0-9A-Fa-f]{3,8}(?![0-9A-Za-z_]))|(:=|\+=|-=|\*=|\/=|=>|==|!=|<=|>=|&&|\|\||[()[\],=+\-*/%<>!?:])/y;
|
|
271
|
+
var DIRECTIVE_LINE = /[ \t]*@[^\n]*/y;
|
|
272
|
+
function lex(source, diags) {
|
|
273
|
+
const tokens = [];
|
|
274
|
+
let line = 1;
|
|
275
|
+
let lineStart = 0;
|
|
276
|
+
let pos = 0;
|
|
277
|
+
let atLineStart = true;
|
|
278
|
+
while (pos < source.length) {
|
|
279
|
+
if (atLineStart) {
|
|
280
|
+
atLineStart = false;
|
|
281
|
+
DIRECTIVE_LINE.lastIndex = pos;
|
|
282
|
+
if (DIRECTIVE_LINE.exec(source) !== null) {
|
|
283
|
+
pos = DIRECTIVE_LINE.lastIndex;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const col = pos - lineStart + 1;
|
|
288
|
+
TOKEN.lastIndex = pos;
|
|
289
|
+
const m = TOKEN.exec(source);
|
|
290
|
+
if (m === null) {
|
|
291
|
+
const ch = String.fromCodePoint(source.codePointAt(pos) ?? 0);
|
|
292
|
+
diags.push(diag(
|
|
293
|
+
"error",
|
|
294
|
+
"UNEXPECTED_CHAR",
|
|
295
|
+
`unexpected character '${ch}'`,
|
|
296
|
+
{ line, col, endCol: col + 1 },
|
|
297
|
+
ch === "^" ? "use pow(a, b) for powers" : ch === ";" ? "put one statement per line" : void 0
|
|
298
|
+
));
|
|
299
|
+
pos += ch.length;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const text = m[0];
|
|
303
|
+
pos = TOKEN.lastIndex;
|
|
304
|
+
const at = { line, col, endCol: col + text.length };
|
|
305
|
+
if (m[1] !== void 0 || m[2] !== void 0)
|
|
306
|
+
continue;
|
|
307
|
+
if (m[3] !== void 0) {
|
|
308
|
+
tokens.push({ k: "nl", v: "\n", ...at });
|
|
309
|
+
line++;
|
|
310
|
+
lineStart = pos;
|
|
311
|
+
atLineStart = true;
|
|
312
|
+
} else if (m[4] !== void 0) {
|
|
313
|
+
tokens.push({ k: "num", v: text, ...at });
|
|
314
|
+
} else if (m[5] !== void 0) {
|
|
315
|
+
tokens.push({ k: "str", v: text.slice(1, -1), ...at });
|
|
316
|
+
} else if (m[6] !== void 0) {
|
|
317
|
+
diags.push(diag("error", "UNTERMINATED_STRING", "this string is never closed", at));
|
|
318
|
+
const end = source.indexOf("\n", pos);
|
|
319
|
+
pos = end < 0 ? source.length : end;
|
|
320
|
+
} else if (m[7] !== void 0) {
|
|
321
|
+
tokens.push({ k: "id", v: text, ...at });
|
|
322
|
+
} else if (m[8] !== void 0) {
|
|
323
|
+
tokens.push({ k: "color", v: text, ...at });
|
|
324
|
+
} else {
|
|
325
|
+
tokens.push({ k: "op", v: text, ...at });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return tokens;
|
|
329
|
+
}
|
|
330
|
+
var CONTINUES = /* @__PURE__ */ new Set(["+", "-", "*", "/", "%", "<", ">", "<=", ">=", "==", "!=", "&&", "||", "?", ":", "=", ","]);
|
|
331
|
+
function statements(tokens, diags) {
|
|
332
|
+
const out = [];
|
|
333
|
+
let cur = [];
|
|
334
|
+
const open = [];
|
|
335
|
+
for (const t of tokens) {
|
|
336
|
+
if (t.k === "nl") {
|
|
337
|
+
const prev = cur[cur.length - 1];
|
|
338
|
+
const cont = prev !== void 0 && (prev.k === "op" && CONTINUES.has(prev.v) || prev.k === "id" && (prev.v === "and" || prev.v === "or" || prev.v === "not"));
|
|
339
|
+
if (open.length > 0 || cont)
|
|
340
|
+
continue;
|
|
341
|
+
if (cur.length > 0)
|
|
342
|
+
out.push(cur);
|
|
343
|
+
cur = [];
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (t.k === "op" && (t.v === "(" || t.v === "["))
|
|
347
|
+
open.push(t);
|
|
348
|
+
else if (t.k === "op" && (t.v === ")" || t.v === "]") && open.length > 0)
|
|
349
|
+
open.pop();
|
|
350
|
+
cur.push(t);
|
|
351
|
+
}
|
|
352
|
+
if (open.length > 0) {
|
|
353
|
+
const o = open[0];
|
|
354
|
+
diags.push(diag("error", "UNBALANCED", `'${o.v}' is never closed`, o));
|
|
355
|
+
} else if (cur.length > 0) {
|
|
356
|
+
out.push(cur);
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
var CONTROL = /* @__PURE__ */ new Set(["if", "else", "for", "while", "switch", "return", "break", "continue"]);
|
|
361
|
+
var TYPE_WORDS = /* @__PURE__ */ new Set(["float", "int", "bool", "string", "color", "series", "simple", "const"]);
|
|
362
|
+
var REASSIGN = /* @__PURE__ */ new Set([":=", "+=", "-=", "*=", "/="]);
|
|
363
|
+
var DECLARATIONS = /* @__PURE__ */ new Set(["indicator", "study", "strategy"]);
|
|
364
|
+
var IGNORED_CALLS = /* @__PURE__ */ new Set(["plotshape", "plotchar", "plotarrow", "plotcandle", "plotbar", "bgcolor", "barcolor", "fill", "alertcondition", "alert"]);
|
|
365
|
+
var IGNORED_PREFIXES = ["strategy.", "label.", "line.", "box.", "table.", "linefill.", "polyline.", "log.", "runtime."];
|
|
366
|
+
var INPUTS = /* @__PURE__ */ new Set(["input", "input.int", "input.float"]);
|
|
367
|
+
var COSMETIC_KW = /* @__PURE__ */ new Set(["editable", "display", "trackprice", "show_last", "join", "transp", "force_overlay", "format", "precision"]);
|
|
368
|
+
var INPUT_COSMETIC_KW = /* @__PURE__ */ new Set(["tooltip", "group", "inline", "confirm", "display", "options"]);
|
|
369
|
+
var PLOT_STYLES = /* @__PURE__ */ new Map([
|
|
370
|
+
["plot.style_line", "line"],
|
|
371
|
+
["plot.style_linebr", "line"],
|
|
372
|
+
["plot.style_stepline", "line"],
|
|
373
|
+
["plot.style_area", "line"],
|
|
374
|
+
["plot.style_histogram", "histogram"],
|
|
375
|
+
["plot.style_columns", "histogram"],
|
|
376
|
+
["plot.style_circles", "circle"],
|
|
377
|
+
["plot.style_cross", "circle"]
|
|
378
|
+
]);
|
|
379
|
+
var HLINE_STYLES = /* @__PURE__ */ new Map([
|
|
380
|
+
["hline.style_dashed", "dashed"],
|
|
381
|
+
["hline.style_dotted", "dotted"],
|
|
382
|
+
["hline.style_solid", "solid"]
|
|
383
|
+
]);
|
|
384
|
+
var PREC = /* @__PURE__ */ new Map([["or", 1], ["||", 1], ["and", 2], ["&&", 2], ["==", 3], ["!=", 3], ["<", 4], ["<=", 4], [">", 4], [">=", 4], ["+", 5], ["-", 5], ["*", 6], ["/", 6], ["%", 6]]);
|
|
385
|
+
var span = (a, b, fallback) => a.line === b.line && b.endCol >= a.col ? { line: a.line, col: a.col, endCol: b.endCol } : fallback;
|
|
386
|
+
var MAX_NESTING = 200;
|
|
387
|
+
var Parser = class {
|
|
388
|
+
constructor(toks, inputs) {
|
|
389
|
+
this.toks = toks;
|
|
390
|
+
this.inputs = inputs;
|
|
391
|
+
}
|
|
392
|
+
pos = 0;
|
|
393
|
+
depth = 0;
|
|
394
|
+
peek(o = 0) {
|
|
395
|
+
return this.toks[this.pos + o];
|
|
396
|
+
}
|
|
397
|
+
atEnd() {
|
|
398
|
+
return this.pos >= this.toks.length;
|
|
399
|
+
}
|
|
400
|
+
isOp(v, o = 0) {
|
|
401
|
+
const t = this.peek(o);
|
|
402
|
+
return t !== void 0 && t.k === "op" && t.v === v;
|
|
403
|
+
}
|
|
404
|
+
isId(v, o = 0) {
|
|
405
|
+
const t = this.peek(o);
|
|
406
|
+
return t !== void 0 && t.k === "id" && t.v === v;
|
|
407
|
+
}
|
|
408
|
+
next() {
|
|
409
|
+
const t = this.toks[this.pos];
|
|
410
|
+
if (t === void 0) {
|
|
411
|
+
const last = this.toks[this.toks.length - 1];
|
|
412
|
+
fail("UNEXPECTED_TOKEN", "unexpected end of the statement", { line: last.line, col: last.endCol, endCol: last.endCol + 1 });
|
|
413
|
+
}
|
|
414
|
+
this.pos++;
|
|
415
|
+
return t;
|
|
416
|
+
}
|
|
417
|
+
expect(v) {
|
|
418
|
+
const t = this.peek();
|
|
419
|
+
if (t === void 0 || t.k !== "op" || t.v !== v) {
|
|
420
|
+
if (t === void 0) {
|
|
421
|
+
const last = this.toks[this.toks.length - 1];
|
|
422
|
+
fail("UNEXPECTED_TOKEN", `expected '${v}' at the end of the statement`, { line: last.line, col: last.endCol, endCol: last.endCol + 1 });
|
|
423
|
+
}
|
|
424
|
+
fail("UNEXPECTED_TOKEN", `expected '${v}', found '${t.v}'`, t);
|
|
425
|
+
}
|
|
426
|
+
this.pos++;
|
|
427
|
+
return t;
|
|
428
|
+
}
|
|
429
|
+
/** Nothing may follow a complete statement. */
|
|
430
|
+
end() {
|
|
431
|
+
const t = this.peek();
|
|
432
|
+
if (t !== void 0) {
|
|
433
|
+
fail(
|
|
434
|
+
"TRAILING_TOKENS",
|
|
435
|
+
`unexpected '${t.v === "\n" ? "\\n" : t.v}' after the end of the statement`,
|
|
436
|
+
t,
|
|
437
|
+
t.k === "op" && t.v === "^" ? "use pow(a, b) for powers" : "put one statement per line; is an operator or a comma missing?"
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/** Count one level of recursion; a script nested past MAX_NESTING is a diagnostic, not a stack overflow. */
|
|
442
|
+
enter(at) {
|
|
443
|
+
if (++this.depth > MAX_NESTING) {
|
|
444
|
+
const where = at ?? this.toks[this.toks.length - 1];
|
|
445
|
+
fail("UNSUPPORTED", `this expression is nested more than ${MAX_NESTING} levels deep`, where, "split it into several named steps");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
expr() {
|
|
449
|
+
this.enter(this.peek());
|
|
450
|
+
const c = this.binary(1);
|
|
451
|
+
if (this.isOp("?")) {
|
|
452
|
+
const q = this.next();
|
|
453
|
+
const a = this.expr();
|
|
454
|
+
this.expect(":");
|
|
455
|
+
const b = this.expr();
|
|
456
|
+
this.depth--;
|
|
457
|
+
return { k: "tern", c, a, b, at: span(c.at, b.at, q) };
|
|
458
|
+
}
|
|
459
|
+
this.depth--;
|
|
460
|
+
return c;
|
|
461
|
+
}
|
|
462
|
+
binary(min) {
|
|
463
|
+
let l = this.unary();
|
|
464
|
+
for (; ; ) {
|
|
465
|
+
const t = this.peek();
|
|
466
|
+
if (t === void 0 || t.k !== "op" && t.k !== "id")
|
|
467
|
+
return l;
|
|
468
|
+
const p = PREC.get(t.v);
|
|
469
|
+
if (p === void 0 || p < min)
|
|
470
|
+
return l;
|
|
471
|
+
this.pos++;
|
|
472
|
+
const r = this.binary(p + 1);
|
|
473
|
+
l = { k: "bin", op: t.v === "and" ? "&&" : t.v === "or" ? "||" : t.v, l, r, at: span(l.at, r.at, t) };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
unary() {
|
|
477
|
+
const t = this.peek();
|
|
478
|
+
if (t !== void 0 && (t.k === "op" && (t.v === "-" || t.v === "+" || t.v === "!") || t.k === "id" && t.v === "not")) {
|
|
479
|
+
this.pos++;
|
|
480
|
+
this.enter(t);
|
|
481
|
+
const arg = this.unary();
|
|
482
|
+
this.depth--;
|
|
483
|
+
return { k: "unary", op: t.v === "!" ? "not" : t.v, arg, at: span(t, arg.at, t) };
|
|
484
|
+
}
|
|
485
|
+
return this.postfix();
|
|
486
|
+
}
|
|
487
|
+
postfix() {
|
|
488
|
+
let e = this.primary();
|
|
489
|
+
while (this.isOp("[")) {
|
|
490
|
+
const open = this.next();
|
|
491
|
+
const offset = this.expr();
|
|
492
|
+
const close = this.expect("]");
|
|
493
|
+
e = { k: "index", target: e, offset, at: span(e.at, close, open) };
|
|
494
|
+
}
|
|
495
|
+
return e;
|
|
496
|
+
}
|
|
497
|
+
primary() {
|
|
498
|
+
const t = this.next();
|
|
499
|
+
switch (t.k) {
|
|
500
|
+
case "num":
|
|
501
|
+
return { k: "num", v: parseFloat(t.v), text: t.v, at: t };
|
|
502
|
+
case "str":
|
|
503
|
+
return { k: "str", v: t.v, at: t };
|
|
504
|
+
case "color":
|
|
505
|
+
return { k: "color", v: t.v, at: t };
|
|
506
|
+
case "op":
|
|
507
|
+
if (t.v === "(") {
|
|
508
|
+
const e = this.expr();
|
|
509
|
+
this.expect(")");
|
|
510
|
+
return e;
|
|
511
|
+
}
|
|
512
|
+
if (REASSIGN.has(t.v))
|
|
513
|
+
fail("REASSIGNMENT", `'${t.v}' is not supported`, t, reassignHint(t.v));
|
|
514
|
+
return fail("UNEXPECTED_TOKEN", `unexpected '${t.v}'`, t);
|
|
515
|
+
case "id": {
|
|
516
|
+
if (t.v === "true" || t.v === "false")
|
|
517
|
+
return { k: "num", v: t.v === "true" ? 1 : 0, text: t.v, at: t };
|
|
518
|
+
if (this.isOp("("))
|
|
519
|
+
return this.call(t);
|
|
520
|
+
if (t.v === "na")
|
|
521
|
+
return { k: "na", at: t };
|
|
522
|
+
const color = PINE_COLORS.get(t.v);
|
|
523
|
+
if (color !== void 0)
|
|
524
|
+
return { k: "color", v: color, at: t };
|
|
525
|
+
return { k: "id", name: t.v, at: t };
|
|
526
|
+
}
|
|
527
|
+
default:
|
|
528
|
+
return fail("UNEXPECTED_TOKEN", "unexpected end of line", t);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
call(name) {
|
|
532
|
+
const node = { k: "call", name: name.v, args: [], kw: [], at: name };
|
|
533
|
+
if (INPUTS.has(name.v))
|
|
534
|
+
node.input = this.inputs.count++;
|
|
535
|
+
this.expect("(");
|
|
536
|
+
if (this.isOp(")")) {
|
|
537
|
+
this.next();
|
|
538
|
+
return node;
|
|
539
|
+
}
|
|
540
|
+
for (; ; ) {
|
|
541
|
+
const t = this.peek();
|
|
542
|
+
if (t !== void 0 && t.k === "id" && this.isOp("=", 1)) {
|
|
543
|
+
this.pos += 2;
|
|
544
|
+
node.kw.push({ name: t.v, value: this.expr(), at: t });
|
|
545
|
+
} else {
|
|
546
|
+
const e = this.expr();
|
|
547
|
+
if (node.kw.length > 0)
|
|
548
|
+
fail("UNEXPECTED_TOKEN", "a positional argument cannot follow a keyword argument", e.at);
|
|
549
|
+
node.args.push(e);
|
|
550
|
+
}
|
|
551
|
+
if (this.isOp(",")) {
|
|
552
|
+
this.next();
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
this.expect(")");
|
|
556
|
+
return node;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
function reassignHint(op) {
|
|
561
|
+
return op === ":=" ? "SuperScript bindings are single-assignment series, so Pine's recursive `x := f(x[1])` cannot be expressed; bind a new name instead" : `write it out: x2 = x ${op[0]} \u2026`;
|
|
562
|
+
}
|
|
563
|
+
function directives(source) {
|
|
564
|
+
const title = /@title[ \t]+([^\n]+)/.exec(source);
|
|
565
|
+
const overlay = /(^|\n)[ \t]*(\/\/[ \t]*)?@overlay\b/.test(source);
|
|
566
|
+
const d = { overlay };
|
|
567
|
+
if (title !== null)
|
|
568
|
+
d.title = title[1].replace(/\r$/, "").trim();
|
|
569
|
+
return d;
|
|
570
|
+
}
|
|
571
|
+
function parse(source) {
|
|
572
|
+
const diagnostics = [];
|
|
573
|
+
const dir = directives(source);
|
|
574
|
+
const program = { statements: [], title: dir.title ?? "SuperScript", overlay: dir.overlay, inputIds: [], poisoned: /* @__PURE__ */ new Set(), diagnostics };
|
|
575
|
+
const inputs = { count: 0 };
|
|
576
|
+
const assignedInputs = /* @__PURE__ */ new Map();
|
|
577
|
+
const tokens = lex(source, diagnostics);
|
|
578
|
+
const badLines = new Set(diagnostics.map((d) => d.line));
|
|
579
|
+
for (const toks of statements(tokens, diagnostics)) {
|
|
580
|
+
const first = inputs.count;
|
|
581
|
+
if (toks.some((t) => badLines.has(t.line))) {
|
|
582
|
+
const target = toks.find((t, i) => t.k === "id" && toks[i + 1]?.k === "op" && toks[i + 1].v === "=");
|
|
583
|
+
if (target !== void 0)
|
|
584
|
+
program.poisoned.add(target.v);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
const st = statement(toks, new Parser(toks, inputs), program, dir, diagnostics);
|
|
589
|
+
if (st === null)
|
|
590
|
+
continue;
|
|
591
|
+
program.statements.push(st);
|
|
592
|
+
if (st.k === "assign" && st.value.k === "call" && st.value.input !== void 0)
|
|
593
|
+
assignedInputs.set(st.value.input, st.name);
|
|
594
|
+
} catch (e) {
|
|
595
|
+
if (!(e instanceof DiagnosticThrow))
|
|
596
|
+
throw e;
|
|
597
|
+
diagnostics.push(e.diagnostic);
|
|
598
|
+
inputs.count = first;
|
|
599
|
+
const target = toks.find((t, i) => t.k === "id" && toks[i + 1]?.k === "op" && (toks[i + 1].v === "=" || REASSIGN.has(toks[i + 1].v)));
|
|
600
|
+
if (target !== void 0)
|
|
601
|
+
program.poisoned.add(target.v);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const used = /* @__PURE__ */ new Set();
|
|
605
|
+
for (let i = 0; i < inputs.count; i++) {
|
|
606
|
+
let id = assignedInputs.get(i) ?? `in${i}`;
|
|
607
|
+
if (used.has(id))
|
|
608
|
+
id = `${id}_${i}`;
|
|
609
|
+
used.add(id);
|
|
610
|
+
program.inputIds.push(id);
|
|
611
|
+
}
|
|
612
|
+
return program;
|
|
613
|
+
}
|
|
614
|
+
function statement(toks, p, program, dir, diags) {
|
|
615
|
+
const arrow = toks.find((t) => t.k === "op" && t.v === "=>");
|
|
616
|
+
if (arrow !== void 0)
|
|
617
|
+
fail("FUNCTION_DEF", "function definitions are not supported", arrow, "inline the expression, or bind it to a name");
|
|
618
|
+
let t0 = toks[0];
|
|
619
|
+
if (t0.k === "id" && CONTROL.has(t0.v)) {
|
|
620
|
+
fail("CONTROL_FLOW", `'${t0.v}' is not supported`, t0, "SuperScript works on whole series: use cond ? a : b instead of if/else, and sum()/highest()/\u2026 instead of loops");
|
|
621
|
+
}
|
|
622
|
+
while (t0.k === "id" && (t0.v === "var" || t0.v === "varip" || TYPE_WORDS.has(t0.v)) && p.peek(1)?.k === "id") {
|
|
623
|
+
diags.push(diag("warning", "IGNORED_KEYWORD", `'${t0.v}' is ignored`, t0, t0.v.startsWith("var") ? "SuperScript bindings are series; var has no effect" : void 0));
|
|
624
|
+
p.next();
|
|
625
|
+
t0 = p.peek();
|
|
626
|
+
}
|
|
627
|
+
const t1 = p.peek(1);
|
|
628
|
+
if (t0.k === "id" && t1 !== void 0 && t1.k === "op" && REASSIGN.has(t1.v)) {
|
|
629
|
+
fail("REASSIGNMENT", `'${t1.v}' is not supported`, t1, reassignHint(t1.v));
|
|
630
|
+
}
|
|
631
|
+
if (t0.k === "id" && t1 !== void 0 && t1.k === "op" && t1.v === "=") {
|
|
632
|
+
if (SERIES_NAMES.includes(t0.v))
|
|
633
|
+
fail("BUILTIN_REASSIGNED", `'${t0.v}' is a built-in series and cannot be assigned`, t0, "bind a new name");
|
|
634
|
+
if (t0.v.includes(".") || t0.v === "na" || t0.v === "true" || t0.v === "false")
|
|
635
|
+
fail("UNEXPECTED_TOKEN", `'${t0.v}' cannot be assigned`, t0);
|
|
636
|
+
p.next();
|
|
637
|
+
p.next();
|
|
638
|
+
const value2 = p.expr();
|
|
639
|
+
p.end();
|
|
640
|
+
return { k: "assign", name: t0.v, value: value2, at: t0 };
|
|
641
|
+
}
|
|
642
|
+
if (t0.k === "id" && p.isOp("(", 1)) {
|
|
643
|
+
if (t0.v === "plot")
|
|
644
|
+
return plotStatement(p, diags);
|
|
645
|
+
if (t0.v === "hline")
|
|
646
|
+
return hlineStatement(p, diags);
|
|
647
|
+
if (DECLARATIONS.has(t0.v)) {
|
|
648
|
+
const call = p.primary();
|
|
649
|
+
p.end();
|
|
650
|
+
if (call.k === "call")
|
|
651
|
+
declaration(call, program, dir, diags);
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
if (IGNORED_CALLS.has(t0.v) || IGNORED_PREFIXES.some((x) => t0.v.startsWith(x))) {
|
|
655
|
+
diags.push(diag("warning", "IGNORED_STATEMENT", `${t0.v}() is not supported and is ignored`, t0));
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
const value = p.expr();
|
|
660
|
+
p.end();
|
|
661
|
+
diags.push(diag("warning", "UNUSED_EXPRESSION", "this expression is computed but not used", value.at, "bind it (x = \u2026) or draw it (plot(\u2026))"));
|
|
662
|
+
return { k: "expr", value, at: value.at };
|
|
663
|
+
}
|
|
664
|
+
function declaration(call, program, dir, diags) {
|
|
665
|
+
if (call.name === "strategy") {
|
|
666
|
+
diags.push(diag("warning", "IGNORED_STATEMENT", "strategy() is not supported; orders are ignored and the script runs as an indicator", call.at));
|
|
667
|
+
}
|
|
668
|
+
let title;
|
|
669
|
+
const a0 = call.args[0];
|
|
670
|
+
if (a0 !== void 0 && a0.k === "str")
|
|
671
|
+
title = a0.v;
|
|
672
|
+
const kw = call.kw.find((k) => k.name === "title")?.value;
|
|
673
|
+
if (kw !== void 0 && kw.k === "str")
|
|
674
|
+
title = kw.v;
|
|
675
|
+
if (dir.title === void 0 && title !== void 0)
|
|
676
|
+
program.title = title;
|
|
677
|
+
const ov = call.kw.find((k) => k.name === "overlay")?.value;
|
|
678
|
+
if (ov !== void 0 && ov.k === "num" && ov.v === 1)
|
|
679
|
+
program.overlay = true;
|
|
680
|
+
}
|
|
681
|
+
function colorOf(e, diags) {
|
|
682
|
+
if (e.k === "str" || e.k === "color")
|
|
683
|
+
return e.v;
|
|
684
|
+
if (e.k === "call" && e.name === "color.new") {
|
|
685
|
+
fail("UNSUPPORTED", "color.new() is not supported", e.at, 'write the colour with its alpha as "#rrggbbaa"');
|
|
686
|
+
}
|
|
687
|
+
diags.push(diag("warning", "IGNORED_KEYWORD", 'a colour must be a constant such as "#ff6d00" or color.red; the default colour is used', e.at));
|
|
688
|
+
return void 0;
|
|
689
|
+
}
|
|
690
|
+
function stringOf(e, what, diags) {
|
|
691
|
+
if (e.k === "str")
|
|
692
|
+
return e.v;
|
|
693
|
+
diags.push(diag("warning", "IGNORED_KEYWORD", `${what} must be a string; ignored`, e.at));
|
|
694
|
+
return void 0;
|
|
695
|
+
}
|
|
696
|
+
function plotStatement(p, diags) {
|
|
697
|
+
const call = p.primary();
|
|
698
|
+
p.end();
|
|
699
|
+
if (call.k !== "call")
|
|
700
|
+
return fail("UNEXPECTED_TOKEN", "expected plot(\u2026)", call.at);
|
|
701
|
+
const [value, title, color, lineWidth, style] = call.args;
|
|
702
|
+
if (value === void 0)
|
|
703
|
+
fail("ARITY", "plot() needs a value to draw", call.at);
|
|
704
|
+
if (call.args.length > 5)
|
|
705
|
+
fail("ARITY", "plot() takes at most 5 positional arguments (value, title, color, linewidth, style)", call.args[5].at);
|
|
706
|
+
const st = { k: "plot", value, style: {}, at: call.at };
|
|
707
|
+
const setType = (e) => {
|
|
708
|
+
const s = e.k === "str" ? e.v : e.k === "id" ? e.name : "";
|
|
709
|
+
const mapped = s === "line" || s === "histogram" || s === "circle" ? s : PLOT_STYLES.get(s);
|
|
710
|
+
if (mapped === void 0)
|
|
711
|
+
diags.push(diag("warning", "UNKNOWN_KEYWORD", `unknown plot type '${s}'; drawn as a line`, e.at, 'type="line", "histogram" or "circle"'));
|
|
712
|
+
else
|
|
713
|
+
st.style.type = mapped;
|
|
714
|
+
};
|
|
715
|
+
if (title !== void 0)
|
|
716
|
+
st.title = stringOf(title, "the title", diags);
|
|
717
|
+
if (color !== void 0)
|
|
718
|
+
st.style.color = colorOf(color, diags);
|
|
719
|
+
if (lineWidth !== void 0)
|
|
720
|
+
st.lineWidth = lineWidth;
|
|
721
|
+
if (style !== void 0)
|
|
722
|
+
setType(style);
|
|
723
|
+
const seen = /* @__PURE__ */ new Set();
|
|
724
|
+
for (const kw of call.kw) {
|
|
725
|
+
if (seen.has(kw.name))
|
|
726
|
+
fail("DUPLICATE_ARGUMENT", `'${kw.name}' is given twice`, kw.at);
|
|
727
|
+
seen.add(kw.name);
|
|
728
|
+
switch (kw.name) {
|
|
729
|
+
case "title":
|
|
730
|
+
st.title = stringOf(kw.value, "the title", diags);
|
|
731
|
+
break;
|
|
732
|
+
case "color":
|
|
733
|
+
st.style.color = colorOf(kw.value, diags);
|
|
734
|
+
break;
|
|
735
|
+
case "type":
|
|
736
|
+
case "style":
|
|
737
|
+
setType(kw.value);
|
|
738
|
+
break;
|
|
739
|
+
case "lineWidth":
|
|
740
|
+
case "linewidth":
|
|
741
|
+
st.lineWidth = kw.value;
|
|
742
|
+
break;
|
|
743
|
+
case "histbase":
|
|
744
|
+
st.histbase = kw.value;
|
|
745
|
+
break;
|
|
746
|
+
default:
|
|
747
|
+
if (!COSMETIC_KW.has(kw.name))
|
|
748
|
+
diags.push(diag("warning", "UNKNOWN_KEYWORD", `unknown plot() keyword '${kw.name}' is ignored`, kw.at, "plot(value, title=, color=, type=, lineWidth=, histbase=)"));
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (st.title === void 0)
|
|
752
|
+
delete st.title;
|
|
753
|
+
if (st.style.color === void 0)
|
|
754
|
+
delete st.style.color;
|
|
755
|
+
return st;
|
|
756
|
+
}
|
|
757
|
+
function hlineStatement(p, diags) {
|
|
758
|
+
const call = p.primary();
|
|
759
|
+
p.end();
|
|
760
|
+
if (call.k !== "call")
|
|
761
|
+
return fail("UNEXPECTED_TOKEN", "expected hline(\u2026)", call.at);
|
|
762
|
+
const [value, title, color] = call.args;
|
|
763
|
+
if (value === void 0)
|
|
764
|
+
fail("ARITY", "hline() needs a price", call.at);
|
|
765
|
+
if (call.args.length > 3)
|
|
766
|
+
fail("ARITY", "hline() takes at most 3 positional arguments (price, title, color)", call.args[3].at);
|
|
767
|
+
const st = { k: "hline", value, style: { lineStyle: "dashed" }, at: call.at };
|
|
768
|
+
if (title !== void 0)
|
|
769
|
+
st.title = stringOf(title, "the title", diags);
|
|
770
|
+
if (color !== void 0)
|
|
771
|
+
st.style.color = colorOf(color, diags);
|
|
772
|
+
for (const kw of call.kw) {
|
|
773
|
+
switch (kw.name) {
|
|
774
|
+
case "title":
|
|
775
|
+
st.title = stringOf(kw.value, "the title", diags);
|
|
776
|
+
break;
|
|
777
|
+
case "color":
|
|
778
|
+
st.style.color = colorOf(kw.value, diags);
|
|
779
|
+
break;
|
|
780
|
+
case "linestyle":
|
|
781
|
+
case "lineStyle": {
|
|
782
|
+
const v = kw.value.k === "id" ? HLINE_STYLES.get(kw.value.name) : kw.value.k === "str" ? kw.value.v : void 0;
|
|
783
|
+
if (v === "solid" || v === "dashed" || v === "dotted")
|
|
784
|
+
st.style.lineStyle = v;
|
|
785
|
+
else
|
|
786
|
+
diags.push(diag("warning", "UNKNOWN_KEYWORD", "unknown line style; dashed is used", kw.value.at));
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
case "linewidth":
|
|
790
|
+
case "lineWidth":
|
|
791
|
+
st.lineWidth = kw.value;
|
|
792
|
+
break;
|
|
793
|
+
default:
|
|
794
|
+
if (!COSMETIC_KW.has(kw.name))
|
|
795
|
+
diags.push(diag("warning", "UNKNOWN_KEYWORD", `unknown hline() keyword '${kw.name}' is ignored`, kw.at));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (st.title === void 0)
|
|
799
|
+
delete st.title;
|
|
800
|
+
if (st.style.color === void 0)
|
|
801
|
+
delete st.style.color;
|
|
802
|
+
return st;
|
|
803
|
+
}
|
|
804
|
+
var Poisoned = class extends Error {
|
|
805
|
+
};
|
|
806
|
+
var hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
807
|
+
var Evaluator = class {
|
|
808
|
+
constructor(program, bars, params, check2) {
|
|
809
|
+
this.program = program;
|
|
810
|
+
this.bars = bars;
|
|
811
|
+
this.params = params;
|
|
812
|
+
this.check = check2;
|
|
813
|
+
this.n = bars.length;
|
|
814
|
+
this.poisoned = new Set(program.poisoned);
|
|
815
|
+
}
|
|
816
|
+
n;
|
|
817
|
+
cache = /* @__PURE__ */ new Map();
|
|
818
|
+
vars = /* @__PURE__ */ new Map();
|
|
819
|
+
poisoned;
|
|
820
|
+
inputs = [];
|
|
821
|
+
diagnostics = [];
|
|
822
|
+
series(name) {
|
|
823
|
+
return this.base(name);
|
|
824
|
+
}
|
|
825
|
+
base(name) {
|
|
826
|
+
const hit = this.cache.get(name);
|
|
827
|
+
if (hit !== void 0)
|
|
828
|
+
return hit;
|
|
829
|
+
const f = (v) => v == null || !Number.isFinite(v) ? null : v;
|
|
830
|
+
const b = this.bars;
|
|
831
|
+
let s;
|
|
832
|
+
switch (name) {
|
|
833
|
+
case "open":
|
|
834
|
+
s = b.map((x) => f(x.open));
|
|
835
|
+
break;
|
|
836
|
+
case "high":
|
|
837
|
+
s = b.map((x) => f(x.high));
|
|
838
|
+
break;
|
|
839
|
+
case "low":
|
|
840
|
+
s = b.map((x) => f(x.low));
|
|
841
|
+
break;
|
|
842
|
+
case "close":
|
|
843
|
+
s = b.map((x) => f(x.close));
|
|
844
|
+
break;
|
|
845
|
+
case "volume":
|
|
846
|
+
s = b.map((x) => f(x.volume));
|
|
847
|
+
break;
|
|
848
|
+
case "hl2":
|
|
849
|
+
s = b.map((x) => f((x.high + x.low) / 2));
|
|
850
|
+
break;
|
|
851
|
+
case "hlc3":
|
|
852
|
+
s = b.map((x) => f((x.high + x.low + x.close) / 3));
|
|
853
|
+
break;
|
|
854
|
+
case "ohlc4":
|
|
855
|
+
s = b.map((x) => f((x.open + x.high + x.low + x.close) / 4));
|
|
856
|
+
break;
|
|
857
|
+
case "bar_index":
|
|
858
|
+
s = b.map((_x, i) => i);
|
|
859
|
+
break;
|
|
860
|
+
default:
|
|
861
|
+
s = b.map((x) => f(x.timestamp));
|
|
862
|
+
}
|
|
863
|
+
this.cache.set(name, s);
|
|
864
|
+
return s;
|
|
865
|
+
}
|
|
866
|
+
run() {
|
|
867
|
+
const plots = [];
|
|
868
|
+
for (const st of this.program.statements) {
|
|
869
|
+
try {
|
|
870
|
+
switch (st.k) {
|
|
871
|
+
case "assign":
|
|
872
|
+
this.vars.set(st.name, this.eval(st.value));
|
|
873
|
+
this.poisoned.delete(st.name);
|
|
874
|
+
break;
|
|
875
|
+
case "plot": {
|
|
876
|
+
const values = asSeries(this.eval(st.value), this.n);
|
|
877
|
+
const style = { ...st.style };
|
|
878
|
+
if (st.lineWidth !== void 0)
|
|
879
|
+
style.lineWidth = this.constant(st.lineWidth, "lineWidth");
|
|
880
|
+
if (st.histbase !== void 0)
|
|
881
|
+
style.histbase = this.constant(st.histbase, "histbase");
|
|
882
|
+
plots.push({ id: `p${plots.length}`, title: st.title ?? `plot ${plots.length + 1}`, values, style });
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
case "hline": {
|
|
886
|
+
const price = this.eval(st.value);
|
|
887
|
+
if (typeof price !== "number")
|
|
888
|
+
fail("EXPECTED_CONSTANT", "hline() draws a constant price; use plot() for a series", st.value.at);
|
|
889
|
+
const style = { ...st.style };
|
|
890
|
+
if (st.lineWidth !== void 0)
|
|
891
|
+
style.lineWidth = this.constant(st.lineWidth, "linewidth");
|
|
892
|
+
plots.push({ id: `p${plots.length}`, title: st.title ?? "", values: asSeries(price, this.n), style, kind: "hline" });
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
default:
|
|
896
|
+
this.eval(st.value);
|
|
897
|
+
}
|
|
898
|
+
} catch (e) {
|
|
899
|
+
if (st.k === "assign")
|
|
900
|
+
this.poisoned.add(st.name);
|
|
901
|
+
if (e instanceof Poisoned)
|
|
902
|
+
continue;
|
|
903
|
+
if (!(e instanceof DiagnosticThrow))
|
|
904
|
+
throw e;
|
|
905
|
+
if (!this.check)
|
|
906
|
+
throw new SuperScriptError(e.diagnostic);
|
|
907
|
+
this.diagnostics.push(e.diagnostic);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return { plots, inputs: this.inputs, diagnostics: this.diagnostics };
|
|
911
|
+
}
|
|
912
|
+
constant(e, what) {
|
|
913
|
+
const v = this.eval(e);
|
|
914
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
915
|
+
fail("EXPECTED_CONSTANT", `${what} must be a constant number`, e.at);
|
|
916
|
+
return v;
|
|
917
|
+
}
|
|
918
|
+
eval(e) {
|
|
919
|
+
switch (e.k) {
|
|
920
|
+
case "num":
|
|
921
|
+
return Number.isFinite(e.v) ? e.v : NaN;
|
|
922
|
+
case "str":
|
|
923
|
+
return fail("TYPE", "a string is only allowed as a title or a colour", e.at);
|
|
924
|
+
case "color":
|
|
925
|
+
return fail("TYPE", "a colour is only allowed as a plot()/hline() colour", e.at);
|
|
926
|
+
case "na":
|
|
927
|
+
return asSeries(NaN, this.n);
|
|
928
|
+
case "id":
|
|
929
|
+
return this.identifier(e.name, e.at);
|
|
930
|
+
case "index": {
|
|
931
|
+
const target = this.eval(e.target);
|
|
932
|
+
const off = this.eval(e.offset);
|
|
933
|
+
if (typeof off !== "number") {
|
|
934
|
+
fail("BAD_OFFSET", "a history offset must be a constant (a number, an input, or arithmetic on them)", e.offset.at, "offsets that vary bar by bar are not supported");
|
|
935
|
+
}
|
|
936
|
+
if (!Number.isInteger(off) || off < 0) {
|
|
937
|
+
fail("BAD_OFFSET", `a history offset must be a whole number >= 0, got ${off}`, e.offset.at, off < 0 ? "a negative offset would read future bars" : void 0);
|
|
938
|
+
}
|
|
939
|
+
return shift(asSeries(target, this.n), off);
|
|
940
|
+
}
|
|
941
|
+
case "unary": {
|
|
942
|
+
const v = this.eval(e.arg);
|
|
943
|
+
if (e.op === "-")
|
|
944
|
+
return typeof v === "number" ? -v : v.map((x) => x == null ? null : -x);
|
|
945
|
+
if (e.op === "+")
|
|
946
|
+
return v;
|
|
947
|
+
return typeof v === "number" ? Number.isNaN(v) ? NaN : v === 0 ? 1 : 0 : v.map((x) => x == null ? null : x === 0 ? 1 : 0);
|
|
948
|
+
}
|
|
949
|
+
case "bin": {
|
|
950
|
+
const l = this.eval(e.l);
|
|
951
|
+
const r = this.eval(e.r);
|
|
952
|
+
return zipValue(l, r, BINARY[e.op], this.n);
|
|
953
|
+
}
|
|
954
|
+
case "tern": {
|
|
955
|
+
const c = this.eval(e.c);
|
|
956
|
+
const a = this.eval(e.a);
|
|
957
|
+
const b = this.eval(e.b);
|
|
958
|
+
if (typeof c === "number") {
|
|
959
|
+
if (Number.isNaN(c))
|
|
960
|
+
return typeof a === "number" && typeof b === "number" ? NaN : asSeries(NaN, this.n);
|
|
961
|
+
return c !== 0 ? a : b;
|
|
962
|
+
}
|
|
963
|
+
const as = asSeries(a, this.n);
|
|
964
|
+
const bs = asSeries(b, this.n);
|
|
965
|
+
return c.map((cv, i) => cv == null ? null : cv !== 0 ? as[i] : bs[i]);
|
|
966
|
+
}
|
|
967
|
+
case "call":
|
|
968
|
+
return this.call(e);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
identifier(name, at) {
|
|
972
|
+
const bound = this.vars.get(name);
|
|
973
|
+
if (bound !== void 0)
|
|
974
|
+
return bound;
|
|
975
|
+
if (this.poisoned.has(name))
|
|
976
|
+
throw new Poisoned();
|
|
977
|
+
if (SERIES_NAMES.includes(name))
|
|
978
|
+
return this.base(name);
|
|
979
|
+
if (name === "ta.tr")
|
|
980
|
+
return BUILTINS.get("tr").impl([], this);
|
|
981
|
+
if (name === "ta.obv")
|
|
982
|
+
return BUILTINS.get("obv").impl([], this);
|
|
983
|
+
if (name === "ta.vwap")
|
|
984
|
+
return fail("PINE_MISMATCH", "Pine's ta.vwap resets every session; SuperScript's vwap() is cumulative from the first bar", at, "write vwap() if that is what you want");
|
|
985
|
+
if (BUILTINS.has(name) || PINE_ALIASES.has(name))
|
|
986
|
+
return fail("TYPE", `'${name}' is a function: call it as ${name}(\u2026)`, at);
|
|
987
|
+
if (name.startsWith("color."))
|
|
988
|
+
return fail("TYPE", "a colour is only allowed as a plot()/hline() colour", at);
|
|
989
|
+
const guess = closest(name, [...SERIES_NAMES, ...this.vars.keys(), "na"]);
|
|
990
|
+
return fail("UNKNOWN_IDENTIFIER", `unknown identifier '${name}'`, at, guess !== void 0 ? `did you mean '${guess}'?` : void 0);
|
|
991
|
+
}
|
|
992
|
+
call(e) {
|
|
993
|
+
const name = e.name;
|
|
994
|
+
if (e.input !== void 0)
|
|
995
|
+
return this.input(e, e.input);
|
|
996
|
+
if (name === "plot" || name === "hline")
|
|
997
|
+
return fail("STATEMENT", `${name}() is a statement: put it on its own line`, e.at);
|
|
998
|
+
if (name.startsWith("input."))
|
|
999
|
+
return fail("UNSUPPORTED", `${name}() is not supported`, e.at, "only numeric inputs: input(), input.int(), input.float()");
|
|
1000
|
+
if (name.startsWith("request."))
|
|
1001
|
+
return fail("UNSUPPORTED", `${name}() is not supported`, e.at, "a script sees only the chart's own symbol and timeframe");
|
|
1002
|
+
if (name === "color.new" || name.startsWith("color."))
|
|
1003
|
+
return fail("TYPE", "a colour is only allowed as a plot()/hline() colour", e.at);
|
|
1004
|
+
const pine = this.pineAdapter(e);
|
|
1005
|
+
if (pine !== void 0)
|
|
1006
|
+
return pine;
|
|
1007
|
+
const b = BUILTINS.get(PINE_ALIASES.get(name) ?? name);
|
|
1008
|
+
if (b === void 0) {
|
|
1009
|
+
const guess = closest(name, [...BUILTINS.keys(), ...PINE_ALIASES.keys()]);
|
|
1010
|
+
return fail("UNKNOWN_FUNCTION", `unknown function '${name}'`, e.at, guess !== void 0 ? `did you mean '${guess}'?` : void 0);
|
|
1011
|
+
}
|
|
1012
|
+
return this.callBuiltin(b, this.bind(b, e));
|
|
1013
|
+
}
|
|
1014
|
+
/** Pine calls whose SuperScript form takes different arguments: accepted only when the meaning is the same. */
|
|
1015
|
+
pineAdapter(e) {
|
|
1016
|
+
const isId = (x, id) => x !== void 0 && x.k === "id" && x.name === id;
|
|
1017
|
+
const [a0, a1, a2, a3] = e.args;
|
|
1018
|
+
switch (e.name) {
|
|
1019
|
+
case "ta.cci":
|
|
1020
|
+
case "ta.mfi": {
|
|
1021
|
+
const target = e.name.slice(3);
|
|
1022
|
+
if (e.args.length === 2 && e.kw.length === 0 && isId(a0, "hlc3"))
|
|
1023
|
+
return this.callBuiltin(BUILTINS.get(target), [this.argument(BUILTINS.get(target), 0, a1)]);
|
|
1024
|
+
return fail("PINE_MISMATCH", `SuperScript's ${target}(len) always uses hlc3`, e.at, target === "cci" ? "write cci(len) for hlc3, or (src - sma(src,len)) / (0.015 * dev(src,len)) for another source" : `write ${target}(len)`);
|
|
1025
|
+
}
|
|
1026
|
+
case "ta.stoch":
|
|
1027
|
+
if (e.args.length === 4 && e.kw.length === 0 && isId(a0, "close") && isId(a1, "high") && isId(a2, "low"))
|
|
1028
|
+
return this.callBuiltin(BUILTINS.get("stoch"), [this.argument(BUILTINS.get("stoch"), 0, a3)]);
|
|
1029
|
+
return fail("PINE_MISMATCH", "SuperScript's stoch(len) is %K of close within high/low", e.at, "write stoch(len)");
|
|
1030
|
+
case "ta.vwap":
|
|
1031
|
+
return fail("PINE_MISMATCH", "Pine's ta.vwap resets every session; SuperScript's vwap() is cumulative from the first bar", e.at, "write vwap() if that is what you want");
|
|
1032
|
+
default:
|
|
1033
|
+
return void 0;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
/** Positional and keyword arguments to one value per parameter (defaults filled). */
|
|
1037
|
+
bind(b, e) {
|
|
1038
|
+
const shown = `${b.name}(${b.params.map((p) => p.name).join(", ")})`;
|
|
1039
|
+
if (e.args.length > b.params.length) {
|
|
1040
|
+
fail("ARITY", `${b.name}() takes ${b.params.length === 0 ? "no arguments" : `at most ${b.params.length} (${b.params.map((p) => p.name).join(", ")})`}, got ${e.args.length}`, e.args[b.params.length].at, `write ${shown}`);
|
|
1041
|
+
}
|
|
1042
|
+
const given = b.params.map((_p, i) => e.args[i]);
|
|
1043
|
+
for (const kw of e.kw) {
|
|
1044
|
+
const pname = PINE_ARG_NAMES.get(kw.name) ?? kw.name;
|
|
1045
|
+
const i = b.params.findIndex((p) => p.name === pname);
|
|
1046
|
+
if (i < 0)
|
|
1047
|
+
fail("UNKNOWN_ARGUMENT", `${b.name}() has no argument '${kw.name}'`, kw.at, `write ${shown}`);
|
|
1048
|
+
if (given[i] !== void 0)
|
|
1049
|
+
fail("DUPLICATE_ARGUMENT", `'${b.params[i].name}' is given twice`, kw.at);
|
|
1050
|
+
given[i] = kw.value;
|
|
1051
|
+
}
|
|
1052
|
+
return b.params.map((p, i) => {
|
|
1053
|
+
const g = given[i];
|
|
1054
|
+
if (g === void 0) {
|
|
1055
|
+
if (p.default !== void 0)
|
|
1056
|
+
return p.default;
|
|
1057
|
+
return fail("ARITY", `${b.name}() needs ${p.name}`, e.at, `write ${shown}`);
|
|
1058
|
+
}
|
|
1059
|
+
return this.argument(b, i, g);
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
argument(b, i, e) {
|
|
1063
|
+
const p = b.params[i];
|
|
1064
|
+
const v = this.eval(e);
|
|
1065
|
+
if (p.kind === "series")
|
|
1066
|
+
return v;
|
|
1067
|
+
if (typeof v !== "number") {
|
|
1068
|
+
const lengthOnly = b.params.length === 1 && (b.name === "cci" || b.name === "mfi" || b.name === "stoch" || b.name === "atr");
|
|
1069
|
+
return fail(
|
|
1070
|
+
"EXPECTED_CONSTANT",
|
|
1071
|
+
`${b.name}()'s ${p.name} must be a constant (a number, an input, or arithmetic on them), not a series`,
|
|
1072
|
+
e.at,
|
|
1073
|
+
lengthOnly ? `SuperScript's ${b.name}() takes only the length: ${b.name}(len)` : void 0
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
if (p.kind === "number") {
|
|
1077
|
+
if (!Number.isFinite(v))
|
|
1078
|
+
fail("EXPECTED_CONSTANT", `${b.name}()'s ${p.name} must be a finite number`, e.at);
|
|
1079
|
+
return v;
|
|
1080
|
+
}
|
|
1081
|
+
const min = p.kind === "count" ? 0 : p.min ?? 1;
|
|
1082
|
+
if (!Number.isInteger(v) || v < min) {
|
|
1083
|
+
fail("BAD_LENGTH", `${b.name}()'s ${p.name} must be a whole number >= ${min}, got ${Number.isNaN(v) ? "na" : v}`, e.at);
|
|
1084
|
+
}
|
|
1085
|
+
return v;
|
|
1086
|
+
}
|
|
1087
|
+
callBuiltin(b, args) {
|
|
1088
|
+
return b.impl(args, this);
|
|
1089
|
+
}
|
|
1090
|
+
input(e, index) {
|
|
1091
|
+
const at = e.at;
|
|
1092
|
+
const [d, t] = e.args;
|
|
1093
|
+
if (d === void 0)
|
|
1094
|
+
fail("ARITY", `${e.name}() needs a default value`, at, `write ${e.name}(14, title="Length")`);
|
|
1095
|
+
if (e.args.length > 2)
|
|
1096
|
+
fail("ARITY", `${e.name}() takes at most 2 positional arguments (default, title)`, e.args[2].at, "pass minval=, maxval=, step= by keyword");
|
|
1097
|
+
const num = (x, what) => {
|
|
1098
|
+
const v = this.eval(x);
|
|
1099
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
1100
|
+
fail("EXPECTED_CONSTANT", `${e.name}()'s ${what} must be a constant number`, x.at);
|
|
1101
|
+
return v;
|
|
1102
|
+
};
|
|
1103
|
+
const defval = num(d, "default");
|
|
1104
|
+
let title;
|
|
1105
|
+
if (t !== void 0) {
|
|
1106
|
+
if (t.k !== "str")
|
|
1107
|
+
fail("TYPE", `${e.name}()'s second argument is its title and must be a string`, t.at);
|
|
1108
|
+
title = t.v;
|
|
1109
|
+
}
|
|
1110
|
+
const input = {
|
|
1111
|
+
id: this.program.inputIds[index] ?? `in${index}`,
|
|
1112
|
+
title: "",
|
|
1113
|
+
defval,
|
|
1114
|
+
type: e.name === "input.float" ? "float" : e.name === "input.int" ? "int" : d.k === "num" && /[.eE]/.test(d.text) ? "float" : Number.isInteger(defval) ? "int" : "float"
|
|
1115
|
+
};
|
|
1116
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1117
|
+
for (const kw of e.kw) {
|
|
1118
|
+
if (seen.has(kw.name))
|
|
1119
|
+
fail("DUPLICATE_ARGUMENT", `'${kw.name}' is given twice`, kw.at);
|
|
1120
|
+
seen.add(kw.name);
|
|
1121
|
+
switch (kw.name) {
|
|
1122
|
+
case "title":
|
|
1123
|
+
if (title !== void 0)
|
|
1124
|
+
fail("DUPLICATE_ARGUMENT", "the title is given twice", kw.at);
|
|
1125
|
+
if (kw.value.k !== "str")
|
|
1126
|
+
fail("TYPE", "title must be a string", kw.value.at);
|
|
1127
|
+
title = kw.value.v;
|
|
1128
|
+
break;
|
|
1129
|
+
case "defval":
|
|
1130
|
+
fail("DUPLICATE_ARGUMENT", "the default is given twice", kw.at);
|
|
1131
|
+
break;
|
|
1132
|
+
case "minval":
|
|
1133
|
+
input.min = num(kw.value, "minval");
|
|
1134
|
+
break;
|
|
1135
|
+
case "maxval":
|
|
1136
|
+
input.max = num(kw.value, "maxval");
|
|
1137
|
+
break;
|
|
1138
|
+
case "step":
|
|
1139
|
+
input.step = num(kw.value, "step");
|
|
1140
|
+
break;
|
|
1141
|
+
default:
|
|
1142
|
+
if (!INPUT_COSMETIC_KW.has(kw.name))
|
|
1143
|
+
fail("UNKNOWN_ARGUMENT", `${e.name}() has no argument '${kw.name}'`, kw.at, `write ${e.name}(14, title="Length", minval=1, maxval=100, step=1)`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
input.title = title ?? input.id;
|
|
1147
|
+
if (input.type === "int" && !Number.isInteger(input.defval))
|
|
1148
|
+
input.type = "float";
|
|
1149
|
+
this.inputs[index] = input;
|
|
1150
|
+
return resolveParam(input, index, this.params);
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
var BINARY = {
|
|
1154
|
+
"+": (x, y) => x + y,
|
|
1155
|
+
"-": (x, y) => x - y,
|
|
1156
|
+
"*": (x, y) => x * y,
|
|
1157
|
+
"/": (x, y) => x / y,
|
|
1158
|
+
"%": (x, y) => x % y,
|
|
1159
|
+
"<": (x, y) => x < y ? 1 : 0,
|
|
1160
|
+
"<=": (x, y) => x <= y ? 1 : 0,
|
|
1161
|
+
">": (x, y) => x > y ? 1 : 0,
|
|
1162
|
+
">=": (x, y) => x >= y ? 1 : 0,
|
|
1163
|
+
"==": (x, y) => x === y ? 1 : 0,
|
|
1164
|
+
"!=": (x, y) => x !== y ? 1 : 0,
|
|
1165
|
+
"&&": (x, y) => x !== 0 && y !== 0 ? 1 : 0,
|
|
1166
|
+
"||": (x, y) => x !== 0 || y !== 0 ? 1 : 0
|
|
1167
|
+
};
|
|
1168
|
+
function shift(s, n) {
|
|
1169
|
+
if (n <= 0)
|
|
1170
|
+
return s;
|
|
1171
|
+
return s.map((_v, i) => i - n >= 0 ? s[i - n] : null);
|
|
1172
|
+
}
|
|
1173
|
+
function resolveParam(input, index, params) {
|
|
1174
|
+
let v;
|
|
1175
|
+
if (Array.isArray(params))
|
|
1176
|
+
v = params[index];
|
|
1177
|
+
else if (params !== void 0 && hasOwn(params, input.id))
|
|
1178
|
+
v = params[input.id];
|
|
1179
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
1180
|
+
return input.defval;
|
|
1181
|
+
if (input.type === "int")
|
|
1182
|
+
v = Math.round(v);
|
|
1183
|
+
if (input.min !== void 0)
|
|
1184
|
+
v = Math.max(v, input.min);
|
|
1185
|
+
if (input.max !== void 0)
|
|
1186
|
+
v = Math.min(v, input.max);
|
|
1187
|
+
return v;
|
|
1188
|
+
}
|
|
1189
|
+
function check(program) {
|
|
1190
|
+
const ev = new Evaluator(program, [], void 0, true).run();
|
|
1191
|
+
const diagnostics = [...program.diagnostics, ...ev.diagnostics];
|
|
1192
|
+
if (diagnostics.every((d) => d.severity !== "error") && ev.plots.length === 0) {
|
|
1193
|
+
diagnostics.push(diag("warning", "NO_PLOTS", "the script draws nothing", { line: 1, col: 1, endCol: 1 }, "add plot(\u2026)"));
|
|
1194
|
+
}
|
|
1195
|
+
diagnostics.sort((a, b) => a.line !== b.line ? a.line - b.line : a.col - b.col);
|
|
1196
|
+
return { plots: ev.plots, inputs: ev.inputs, diagnostics };
|
|
1197
|
+
}
|
|
1198
|
+
function evaluate(program, bars, params) {
|
|
1199
|
+
return new Evaluator(program, bars, params, false).run();
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
export {
|
|
1203
|
+
BUILTINS,
|
|
1204
|
+
PINE_ALIASES,
|
|
1205
|
+
PINE_ARG_NAMES,
|
|
1206
|
+
builtinReferenceLines,
|
|
1207
|
+
SuperScriptError,
|
|
1208
|
+
isSuperScriptError,
|
|
1209
|
+
diag,
|
|
1210
|
+
formatDiagnostics,
|
|
1211
|
+
parse,
|
|
1212
|
+
check,
|
|
1213
|
+
evaluate
|
|
1214
|
+
};
|