@hoox-sh/pynets 0.1.0 → 0.3.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 +59 -0
- package/README.md +20 -9
- package/dist/browser.js +95 -34
- package/dist/index.d.ts +1 -1
- package/dist/index.js +23002 -12427
- 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 +4 -3
- package/src/cli.ts +29 -7
- 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 +22 -0
- package/src/runtime/array.ts +238 -68
- package/src/runtime/compile/collections.ts +492 -0
- package/src/runtime/compile/draw.ts +181 -0
- package/src/runtime/compile/emit.ts +1421 -0
- package/src/runtime/compile/emit_call.ts +1796 -0
- package/src/runtime/compile/emit_udf.ts +326 -0
- package/src/runtime/compile/engine.ts +265 -0
- package/src/runtime/compile/helpers.ts +140 -0
- package/src/runtime/compile/index.ts +33 -0
- package/src/runtime/compile/runtime.ts +357 -0
- package/src/runtime/compile/strategy.ts +250 -0
- package/src/runtime/compile/types.ts +127 -0
- package/src/runtime/compile/udt_runtime.ts +50 -0
- package/src/runtime/drawings.ts +405 -8
- package/src/runtime/interpret.ts +2387 -281
- package/src/runtime/matrix.ts +371 -42
- package/src/runtime/strategy.ts +695 -33
- package/src/runtime/ta.ts +654 -20
- package/src/runtime/timeframe.ts +199 -0
package/src/ast/unparse.ts
CHANGED
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
* Slice-1 unparser: expressions plus Assign / ReAssign / var / varip,
|
|
6
6
|
* Tuple / Conditional / ForTo / ForIn / While / Switch / Case /
|
|
7
7
|
* BoolOp / Break / Continue / TypeDef / EnumDef.
|
|
8
|
+
*
|
|
9
|
+
* Parenthesization is precedence-driven, ported from PYNE's
|
|
10
|
+
* pynescript.ast.unparser (Precedence ladder + per-child assignment): a child
|
|
11
|
+
* is wrapped only when the precedence its parent assigns exceeds the child's
|
|
12
|
+
* own binding level, so unparse output always reparses to the same tree.
|
|
8
13
|
*/
|
|
9
14
|
import type {
|
|
10
15
|
AST,
|
|
11
16
|
Arg,
|
|
12
17
|
Assign,
|
|
13
18
|
Attribute,
|
|
19
|
+
AugAssign,
|
|
14
20
|
BinOp,
|
|
15
21
|
BoolOp,
|
|
16
22
|
Call,
|
|
@@ -27,9 +33,12 @@ import type {
|
|
|
27
33
|
If,
|
|
28
34
|
Import,
|
|
29
35
|
Name,
|
|
36
|
+
Once,
|
|
30
37
|
Param,
|
|
38
|
+
Qualify,
|
|
31
39
|
ReAssign,
|
|
32
40
|
Script,
|
|
41
|
+
Specialize,
|
|
33
42
|
Subscript,
|
|
34
43
|
Switch,
|
|
35
44
|
Tuple,
|
|
@@ -38,6 +47,7 @@ import type {
|
|
|
38
47
|
While,
|
|
39
48
|
expr,
|
|
40
49
|
stmt,
|
|
50
|
+
type_qual,
|
|
41
51
|
} from "./nodes.ts";
|
|
42
52
|
|
|
43
53
|
const OP: Record<string, string> = {
|
|
@@ -46,6 +56,18 @@ const OP: Record<string, string> = {
|
|
|
46
56
|
Mult: "*",
|
|
47
57
|
Div: "/",
|
|
48
58
|
Mod: "%",
|
|
59
|
+
BitAnd: "&",
|
|
60
|
+
BitOr: "|",
|
|
61
|
+
BitXor: "^",
|
|
62
|
+
LShift: "<<",
|
|
63
|
+
RShift: ">>",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const TYPE_QUAL: Record<string, string> = {
|
|
67
|
+
Const: "const",
|
|
68
|
+
Input: "input",
|
|
69
|
+
Simple: "simple",
|
|
70
|
+
Series: "series",
|
|
49
71
|
};
|
|
50
72
|
|
|
51
73
|
const UOP: Record<string, string> = {
|
|
@@ -69,6 +91,93 @@ const BOOLOP: Record<string, string> = {
|
|
|
69
91
|
Or: "or",
|
|
70
92
|
};
|
|
71
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Operator binding levels for parenthesization — port of PYNE's
|
|
96
|
+
* `pynescript.ast.unparser.Precedence` ladder (higher binds tighter).
|
|
97
|
+
* A child is wrapped in parens when the precedence its parent assigns to it
|
|
98
|
+
* is strictly greater than the child's own binding level.
|
|
99
|
+
*/
|
|
100
|
+
const PREC = {
|
|
101
|
+
TEST: 1, // ternary '?', ':' — loosest
|
|
102
|
+
OR: 2,
|
|
103
|
+
AND: 3,
|
|
104
|
+
BITOR: 4,
|
|
105
|
+
BITXOR: 5,
|
|
106
|
+
BITAND: 6,
|
|
107
|
+
EQ: 7,
|
|
108
|
+
INEQ: 8, // Python Precedence.CMP alias
|
|
109
|
+
SHIFT: 9,
|
|
110
|
+
EXPR: 10,
|
|
111
|
+
ARITH: 11,
|
|
112
|
+
TERM: 12,
|
|
113
|
+
FACTOR: 13, // unary +,-,~,not (Python Precedence.NOT alias)
|
|
114
|
+
ATOM: 14, // names, literals, attr, calls — tightest
|
|
115
|
+
} as const;
|
|
116
|
+
|
|
117
|
+
function nextPrec(prec: number): number {
|
|
118
|
+
return Math.min(prec + 1, PREC.ATOM);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const BINOP_PREC: Record<string, number> = {
|
|
122
|
+
Add: PREC.ARITH,
|
|
123
|
+
Sub: PREC.ARITH,
|
|
124
|
+
Mult: PREC.TERM,
|
|
125
|
+
Div: PREC.TERM,
|
|
126
|
+
Mod: PREC.TERM,
|
|
127
|
+
BitAnd: PREC.BITAND,
|
|
128
|
+
BitOr: PREC.BITOR,
|
|
129
|
+
BitXor: PREC.BITXOR,
|
|
130
|
+
LShift: PREC.SHIFT,
|
|
131
|
+
RShift: PREC.SHIFT,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Python assigns NOT = FACTOR (same level) for unary ops.
|
|
135
|
+
const UNOP_PREC = PREC.FACTOR;
|
|
136
|
+
|
|
137
|
+
const BOOLOP_PREC: Record<string, number> = {
|
|
138
|
+
And: PREC.AND,
|
|
139
|
+
Or: PREC.OR,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Python Precedence.CMP — Compare children are checked against INEQ.
|
|
143
|
+
const COMPARE_PREC = PREC.INEQ;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Natural binding level of an expression node — the precedence the Python
|
|
147
|
+
* unparser's visitor checks against its parent-assigned level. Node kinds
|
|
148
|
+
* without an operator (atoms, Call, Attribute, …) never self-parenthesize in
|
|
149
|
+
* the SoT unparser, hence `undefined`.
|
|
150
|
+
*/
|
|
151
|
+
function ownPrec(node: expr): number | undefined {
|
|
152
|
+
switch (node.kind) {
|
|
153
|
+
case "BinOp":
|
|
154
|
+
return BINOP_PREC[node.op.kind];
|
|
155
|
+
case "UnaryOp":
|
|
156
|
+
return UNOP_PREC;
|
|
157
|
+
case "BoolOp":
|
|
158
|
+
return BOOLOP_PREC[node.op.kind];
|
|
159
|
+
case "Compare":
|
|
160
|
+
return COMPARE_PREC;
|
|
161
|
+
case "Conditional":
|
|
162
|
+
return PREC.TEST;
|
|
163
|
+
default:
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Emit a child expression, parenthesizing it when the parent-assigned
|
|
170
|
+
* precedence exceeds the child's own binding level. Mirrors Python's
|
|
171
|
+
* `NodeUnparser.require_parens` / `_needs_parens` rule: compare is strictly
|
|
172
|
+
* greater-than, so equal levels stay unparenthesized (left-associative
|
|
173
|
+
* chaining is encoded by the parent's per-slot precedence assignment).
|
|
174
|
+
*/
|
|
175
|
+
function emitChild(node: expr, required: number): string {
|
|
176
|
+
const src = emitExpr(node);
|
|
177
|
+
const prec = ownPrec(node);
|
|
178
|
+
return prec !== undefined && required > prec ? `(${src})` : src;
|
|
179
|
+
}
|
|
180
|
+
|
|
72
181
|
function quoteString(value: string): string {
|
|
73
182
|
if (value.includes("\n") || value.includes("\r")) {
|
|
74
183
|
if (!value.includes('"""')) return `"""${value}"""`;
|
|
@@ -86,13 +195,13 @@ function emitExpr(node: expr): string {
|
|
|
86
195
|
case "Call":
|
|
87
196
|
return emitCall(node);
|
|
88
197
|
case "Attribute":
|
|
89
|
-
return `${
|
|
198
|
+
return `${emitChild(node.value, PREC.ATOM)}.${node.attr}`;
|
|
90
199
|
case "Subscript":
|
|
91
200
|
return `${emitExpr(node.value)}[${emitSlice(node.slice)}]`;
|
|
92
201
|
case "BinOp":
|
|
93
|
-
return
|
|
202
|
+
return emitBinOp(node);
|
|
94
203
|
case "UnaryOp":
|
|
95
|
-
return
|
|
204
|
+
return emitUnaryOp(node);
|
|
96
205
|
case "Compare":
|
|
97
206
|
return emitCompare(node);
|
|
98
207
|
case "If":
|
|
@@ -109,19 +218,61 @@ function emitExpr(node: expr): string {
|
|
|
109
218
|
return emitWhile(node);
|
|
110
219
|
case "Switch":
|
|
111
220
|
return emitSwitch(node);
|
|
221
|
+
case "Once":
|
|
222
|
+
return emitOnce(node);
|
|
112
223
|
case "BoolOp":
|
|
113
224
|
return emitBoolOp(node);
|
|
225
|
+
case "Qualify":
|
|
226
|
+
return emitQualify(node);
|
|
227
|
+
case "Specialize":
|
|
228
|
+
return emitSpecialize(node);
|
|
229
|
+
case "AugAssign":
|
|
230
|
+
return emitAugAssign(node);
|
|
114
231
|
}
|
|
115
232
|
}
|
|
116
233
|
|
|
234
|
+
function emitTypeQual(node: type_qual): string {
|
|
235
|
+
return TYPE_QUAL[node.kind] ?? node.kind.toLowerCase();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function emitQualify(node: Qualify): string {
|
|
239
|
+
return `${emitTypeQual(node.qualifier)} ${emitExpr(node.value)}`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function emitSpecializeArgs(args: expr | null): string {
|
|
243
|
+
if (args == null) return "";
|
|
244
|
+
if (args.kind === "Tuple") return args.elts.map(emitExpr).join(", ");
|
|
245
|
+
return emitExpr(args);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function emitSpecialize(node: Specialize): string {
|
|
249
|
+
return `${emitExpr(node.value)}<${emitSpecializeArgs(node.args)}>`;
|
|
250
|
+
}
|
|
251
|
+
|
|
117
252
|
function emitCompare(node: Compare): string {
|
|
118
|
-
|
|
253
|
+
// Python: all Compare children are checked against CMP.next() (= SHIFT) so
|
|
254
|
+
// chained comparisons and tighter-binding operands stay unparenthesized.
|
|
255
|
+
const required = nextPrec(COMPARE_PREC);
|
|
256
|
+
let out = emitChild(node.left, required);
|
|
119
257
|
for (let i = 0; i < node.ops.length; i++) {
|
|
120
|
-
out += ` ${CMP[node.ops[i]!.kind] ?? "??"} ${
|
|
258
|
+
out += ` ${CMP[node.ops[i]!.kind] ?? "??"} ${emitChild(node.comparators[i]!, required)}`;
|
|
121
259
|
}
|
|
122
260
|
return out;
|
|
123
261
|
}
|
|
124
262
|
|
|
263
|
+
function emitBinOp(node: BinOp): string {
|
|
264
|
+
// Python: left child keeps the operator's level, right child uses next()
|
|
265
|
+
// so `a - b - c` prints bare while `a - (b - c)` re-parenthesizes.
|
|
266
|
+
const prec = BINOP_PREC[node.op.kind] ?? PREC.TEST;
|
|
267
|
+
return `${emitChild(node.left, prec)} ${OP[node.op.kind] ?? "?"} ${emitChild(node.right, nextPrec(prec))}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function emitUnaryOp(node: UnaryOp): string {
|
|
271
|
+
// Python: the operand is checked at the unary operator's own level (FACTOR),
|
|
272
|
+
// so -(a + b) and not (a and b) wrap while -a * b and not not a do not.
|
|
273
|
+
return `${UOP[node.op.kind] ?? ""}${emitChild(node.operand, UNOP_PREC)}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
125
276
|
function emitIf(node: If): string {
|
|
126
277
|
const body = node.body.map(emitStmt).join("; ");
|
|
127
278
|
if (node.orelse.length === 0) return `if ${emitExpr(node.test)}\n ${body}`;
|
|
@@ -140,7 +291,9 @@ function emitSlice(slice: expr | null | undefined): string {
|
|
|
140
291
|
}
|
|
141
292
|
|
|
142
293
|
function emitConditional(node: Conditional): string {
|
|
143
|
-
|
|
294
|
+
// Python: test/body at TEST.next() (nested ternaries there wrap); orelse at
|
|
295
|
+
// TEST so `a ? b : c ? d : e` stays right-associative without parens.
|
|
296
|
+
return `${emitChild(node.test, PREC.OR)} ? ${emitChild(node.body, PREC.OR)} : ${emitChild(node.orelse, PREC.TEST)}`;
|
|
144
297
|
}
|
|
145
298
|
|
|
146
299
|
function emitForBody(body: stmt[]): string {
|
|
@@ -169,6 +322,11 @@ function emitWhile(node: While): string {
|
|
|
169
322
|
return `while ${emitExpr(node.test)}${emitIndentedBody(node.body)}`;
|
|
170
323
|
}
|
|
171
324
|
|
|
325
|
+
function emitOnce(node: Once): string {
|
|
326
|
+
const head = node.test ? `once ${emitExpr(node.test)}` : "once";
|
|
327
|
+
return head + emitIndentedBody(node.body);
|
|
328
|
+
}
|
|
329
|
+
|
|
172
330
|
function emitCaseBody(body: stmt[]): string {
|
|
173
331
|
if (!body.length) return "";
|
|
174
332
|
if (body.length === 1 && body[0]!.kind === "Expr") {
|
|
@@ -192,7 +350,16 @@ function emitSwitch(node: Switch): string {
|
|
|
192
350
|
|
|
193
351
|
function emitBoolOp(node: BoolOp): string {
|
|
194
352
|
const op = BOOLOP[node.op.kind] ?? "and";
|
|
195
|
-
|
|
353
|
+
const base = BOOLOP_PREC[node.op.kind] ?? PREC.AND;
|
|
354
|
+
// Python raises the required level per value (op.next(), then tighter) so
|
|
355
|
+
// mixed nesting wraps the way the SoT unparser does.
|
|
356
|
+
let required = base;
|
|
357
|
+
return node.values
|
|
358
|
+
.map((v) => {
|
|
359
|
+
required = nextPrec(required);
|
|
360
|
+
return emitChild(v, required);
|
|
361
|
+
})
|
|
362
|
+
.join(` ${op} `);
|
|
196
363
|
}
|
|
197
364
|
|
|
198
365
|
function emitConstant(node: Constant): string {
|
|
@@ -216,7 +383,8 @@ function asArgs(args: Arg | Arg[] | null | undefined): Arg[] {
|
|
|
216
383
|
}
|
|
217
384
|
|
|
218
385
|
function emitCall(node: Call): string {
|
|
219
|
-
|
|
386
|
+
// Python forces the callee to ATOM so e.g. a computed callee parenthesizes.
|
|
387
|
+
return `${emitChild(node.func, PREC.ATOM)}(${asArgs(node.args).map(emitArg).join(", ")})`;
|
|
220
388
|
}
|
|
221
389
|
|
|
222
390
|
function emitImport(node: Import): string {
|
|
@@ -229,6 +397,7 @@ function emitStmt(node: stmt): string {
|
|
|
229
397
|
if (node.kind === "Expr") return emitExpr(node.value);
|
|
230
398
|
if (node.kind === "Assign") return emitAssign(node);
|
|
231
399
|
if (node.kind === "ReAssign") return emitReAssign(node);
|
|
400
|
+
if (node.kind === "AugAssign") return emitAugAssign(node);
|
|
232
401
|
if (node.kind === "FunctionDef") return emitFunctionDef(node);
|
|
233
402
|
if (node.kind === "TypeDef") return emitTypeDef(node);
|
|
234
403
|
if (node.kind === "EnumDef") return emitEnumDef(node);
|
|
@@ -274,6 +443,7 @@ function emitFunctionDef(node: FunctionDef): string {
|
|
|
274
443
|
const chunks: string[] = [];
|
|
275
444
|
if (node.export) chunks.push("export");
|
|
276
445
|
if (node.method) chunks.push("method");
|
|
446
|
+
if (node.returns) chunks.push(emitExpr(node.returns));
|
|
277
447
|
chunks.push(`${node.name}(${asParams(node.args).map(emitParam).join(", ")}) =>`);
|
|
278
448
|
const head = chunks.join(" ");
|
|
279
449
|
const body = node.body;
|
|
@@ -305,6 +475,10 @@ function emitReAssign(node: ReAssign): string {
|
|
|
305
475
|
return `${emitExpr(node.target)} := ${emitExpr(node.value)}`;
|
|
306
476
|
}
|
|
307
477
|
|
|
478
|
+
function emitAugAssign(node: AugAssign): string {
|
|
479
|
+
return `${emitExpr(node.target)} ${OP[node.op.kind] ?? "?"}= ${emitExpr(node.value)}`;
|
|
480
|
+
}
|
|
481
|
+
|
|
308
482
|
export function unparse(node: AST): string {
|
|
309
483
|
if (node.kind === "Script") {
|
|
310
484
|
const s = node as Script;
|
|
@@ -317,32 +491,33 @@ export function unparse(node: AST): string {
|
|
|
317
491
|
if (node.kind === "Expr") return emitExpr((node as Expr).value);
|
|
318
492
|
if (node.kind === "Assign") return emitAssign(node as Assign);
|
|
319
493
|
if (node.kind === "ReAssign") return emitReAssign(node as ReAssign);
|
|
494
|
+
if (node.kind === "AugAssign") return emitAugAssign(node as AugAssign);
|
|
320
495
|
if (node.kind === "FunctionDef") return emitFunctionDef(node as FunctionDef);
|
|
321
496
|
if (node.kind === "TypeDef") return emitTypeDef(node as TypeDef);
|
|
322
497
|
if (node.kind === "EnumDef") return emitEnumDef(node as EnumDef);
|
|
323
498
|
if (node.kind === "Param") return emitParam(node as Param);
|
|
324
499
|
if (node.kind === "Var") return "var";
|
|
325
500
|
if (node.kind === "VarIp") return "varip";
|
|
501
|
+
if (node.kind === "Const") return "const";
|
|
502
|
+
if (node.kind === "Input") return "input";
|
|
503
|
+
if (node.kind === "Simple") return "simple";
|
|
504
|
+
if (node.kind === "Series") return "series";
|
|
505
|
+
if (node.kind === "Qualify") return emitQualify(node as Qualify);
|
|
506
|
+
if (node.kind === "Specialize") return emitSpecialize(node as Specialize);
|
|
326
507
|
if (node.kind === "Call") return emitCall(node as Call);
|
|
327
508
|
if (node.kind === "Name") return (node as Name).id;
|
|
328
509
|
if (node.kind === "Constant") return emitConstant(node as Constant);
|
|
329
510
|
if (node.kind === "Arg") return emitArg(node as Arg);
|
|
330
511
|
if (node.kind === "Attribute") {
|
|
331
512
|
const a = node as Attribute;
|
|
332
|
-
return `${
|
|
513
|
+
return `${emitChild(a.value, PREC.ATOM)}.${a.attr}`;
|
|
333
514
|
}
|
|
334
515
|
if (node.kind === "Subscript") {
|
|
335
516
|
const s = node as Subscript;
|
|
336
517
|
return `${emitExpr(s.value)}[${emitSlice(s.slice)}]`;
|
|
337
518
|
}
|
|
338
|
-
if (node.kind === "BinOp")
|
|
339
|
-
|
|
340
|
-
return `${emitExpr(b.left)} ${OP[b.op.kind] ?? "?"} ${emitExpr(b.right)}`;
|
|
341
|
-
}
|
|
342
|
-
if (node.kind === "UnaryOp") {
|
|
343
|
-
const u = node as UnaryOp;
|
|
344
|
-
return `${UOP[u.op.kind] ?? ""}${emitExpr(u.operand)}`;
|
|
345
|
-
}
|
|
519
|
+
if (node.kind === "BinOp") return emitBinOp(node as BinOp);
|
|
520
|
+
if (node.kind === "UnaryOp") return emitUnaryOp(node as UnaryOp);
|
|
346
521
|
if (node.kind === "Compare") return emitCompare(node as Compare);
|
|
347
522
|
if (node.kind === "If") return emitIf(node as If);
|
|
348
523
|
if (node.kind === "Tuple") return emitTuple(node as Tuple);
|
package/src/cli/rich.ts
CHANGED
|
@@ -12,7 +12,7 @@ const MUTED = [203, 204, 189] as const;
|
|
|
12
12
|
const FAIL = [231, 0, 11] as const;
|
|
13
13
|
const WARN = [235, 169, 65] as const;
|
|
14
14
|
|
|
15
|
-
export const VERSION = "0.
|
|
15
|
+
export const VERSION = "0.3.0";
|
|
16
16
|
|
|
17
17
|
type Rgb = readonly [number, number, number];
|
|
18
18
|
|
|
@@ -122,7 +122,7 @@ export class Rich {
|
|
|
122
122
|
this.write(this.paint("accent", "COMMANDS"));
|
|
123
123
|
} else {
|
|
124
124
|
this.write(
|
|
125
|
-
"usage: pynets <check|format|run|dump|info> [file] [--bars N] [--commission N] [--slippage N] [--pyramiding N] [--json] [--plain] [--rich] [--full]",
|
|
125
|
+
"usage: pynets <check|format|run|dump|info> [file] [--bars N] [--mode interpret|compile|auto] [--commission N] [--slippage N] [--pyramiding N] [--json] [--plain] [--rich] [--full]",
|
|
126
126
|
);
|
|
127
127
|
this.write("");
|
|
128
128
|
this.write("commands:");
|
|
@@ -130,7 +130,7 @@ export class Rich {
|
|
|
130
130
|
const cmds: Array<[string, string]> = [
|
|
131
131
|
["check", "Parse-only validation (CI-friendly exit codes)"],
|
|
132
132
|
["format", "Parse → unparse (pretty-print on TTY)"],
|
|
133
|
-
["run", "
|
|
133
|
+
["run", "Run on synthetic OHLCV (interpret or compile JS emit)"],
|
|
134
134
|
["dump", "Dump the ASDL-shaped AST"],
|
|
135
135
|
["info", "Version and runtime extras"],
|
|
136
136
|
];
|
|
@@ -146,6 +146,7 @@ export class Rich {
|
|
|
146
146
|
else this.write("options:");
|
|
147
147
|
const opts: Array<[string, string]> = [
|
|
148
148
|
["--bars N", "Synthetic bar count for run (default 20, max 100000)"],
|
|
149
|
+
["--mode MODE", "run engine: interpret | compile | auto (JS emit; default interpret)"],
|
|
149
150
|
["--commission N", "Broker commission fraction for run (e.g. 0.001)"],
|
|
150
151
|
["--slippage N", "Broker slippage in price units for run"],
|
|
151
152
|
["--pyramiding N", "Broker max same-direction adds for run"],
|
package/src/cli.ts
CHANGED
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
useRich,
|
|
21
21
|
} from "./cli/rich.ts";
|
|
22
22
|
|
|
23
|
+
export type RunMode = "interpret" | "compile" | "auto";
|
|
24
|
+
|
|
23
25
|
export const MAX_BARS = 100_000;
|
|
24
26
|
|
|
25
27
|
export class UsageError extends Error {
|
|
@@ -155,6 +157,11 @@ function takeValue(args: string[], i: number, flag: string): { raw: string | und
|
|
|
155
157
|
return { raw: a.slice(flag.length + 1), next: i };
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
function requireMode(raw: string | undefined): RunMode {
|
|
161
|
+
if (raw === "interpret" || raw === "compile" || raw === "auto") return raw;
|
|
162
|
+
throw new UsageError("error: --mode must be interpret|compile|auto");
|
|
163
|
+
}
|
|
164
|
+
|
|
158
165
|
export function parseArgs(argv: string[]): {
|
|
159
166
|
cmd: string;
|
|
160
167
|
file: string | undefined;
|
|
@@ -162,6 +169,7 @@ export function parseArgs(argv: string[]): {
|
|
|
162
169
|
json: boolean;
|
|
163
170
|
indent: number;
|
|
164
171
|
full: boolean;
|
|
172
|
+
mode: RunMode;
|
|
165
173
|
commission: number | undefined;
|
|
166
174
|
slippage: number | undefined;
|
|
167
175
|
pyramiding: number | undefined;
|
|
@@ -174,13 +182,14 @@ export function parseArgs(argv: string[]): {
|
|
|
174
182
|
let json = false;
|
|
175
183
|
let indent = 2;
|
|
176
184
|
let full = false;
|
|
185
|
+
let mode: RunMode = "interpret";
|
|
177
186
|
let commission: number | undefined;
|
|
178
187
|
let slippage: number | undefined;
|
|
179
188
|
let pyramiding: number | undefined;
|
|
180
189
|
for (let i = 1; i < args.length; i++) {
|
|
181
190
|
const a = args[i]!;
|
|
182
191
|
if (a === "--help" || a === "-h") {
|
|
183
|
-
return { cmd: "help", file: undefined, bars, json, indent, full, commission, slippage, pyramiding };
|
|
192
|
+
return { cmd: "help", file: undefined, bars, json, indent, full, mode, commission, slippage, pyramiding };
|
|
184
193
|
}
|
|
185
194
|
if (a === "--json") {
|
|
186
195
|
json = true;
|
|
@@ -190,6 +199,12 @@ export function parseArgs(argv: string[]): {
|
|
|
190
199
|
full = true;
|
|
191
200
|
continue;
|
|
192
201
|
}
|
|
202
|
+
if (a === "--mode" || a.startsWith("--mode=")) {
|
|
203
|
+
const got = takeValue(args, i, "--mode");
|
|
204
|
+
mode = requireMode(got.raw);
|
|
205
|
+
i = got.next;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
193
208
|
if (a === "--bars" || a.startsWith("--bars=")) {
|
|
194
209
|
const got = takeValue(args, i, "--bars");
|
|
195
210
|
bars = clampBars(requireUint(got.raw, "--bars"));
|
|
@@ -230,7 +245,7 @@ export function parseArgs(argv: string[]): {
|
|
|
230
245
|
if (file != null) throw new UsageError(`error: unexpected argument: ${a}`);
|
|
231
246
|
file = a;
|
|
232
247
|
}
|
|
233
|
-
return { cmd, file, bars, json, indent, full, commission, slippage, pyramiding };
|
|
248
|
+
return { cmd, file, bars, json, indent, full, mode, commission, slippage, pyramiding };
|
|
234
249
|
}
|
|
235
250
|
|
|
236
251
|
function fmtNum(v: number | null | undefined): string {
|
|
@@ -346,21 +361,27 @@ function runJson(out: RuntimeOut): void {
|
|
|
346
361
|
series: typeof out.series;
|
|
347
362
|
count: number;
|
|
348
363
|
script_name: string | null;
|
|
364
|
+
mode: RuntimeOut["mode"];
|
|
349
365
|
events?: RuntimeEvent[];
|
|
350
366
|
drawings?: DrawingLike[];
|
|
351
367
|
fills?: RuntimeFill[];
|
|
352
368
|
alerts?: AlertLike[];
|
|
369
|
+
compile_fallback_reason?: string;
|
|
370
|
+
auto_backend?: RuntimeOut["auto_backend"];
|
|
353
371
|
error?: string;
|
|
354
372
|
} = {
|
|
355
373
|
plots: out.plots,
|
|
356
374
|
series: out.series,
|
|
357
375
|
count: out.count,
|
|
358
376
|
script_name: out.script_name,
|
|
377
|
+
mode: out.mode,
|
|
359
378
|
};
|
|
360
379
|
if (out.events != null) payload.events = out.events;
|
|
361
380
|
if (out.drawings != null) payload.drawings = out.drawings;
|
|
362
381
|
if (out.fills != null) payload.fills = out.fills;
|
|
363
382
|
if (out.alerts != null) payload.alerts = out.alerts;
|
|
383
|
+
if (out.compile_fallback_reason != null) payload.compile_fallback_reason = out.compile_fallback_reason;
|
|
384
|
+
if (out.auto_backend != null) payload.auto_backend = out.auto_backend;
|
|
364
385
|
if (out.error != null) payload.error = out.error;
|
|
365
386
|
writeJson(payload);
|
|
366
387
|
if (out.error != null) process.exit(1);
|
|
@@ -506,11 +527,12 @@ function run(
|
|
|
506
527
|
bars: number,
|
|
507
528
|
asJson: boolean,
|
|
508
529
|
broker: BrokerSettings,
|
|
530
|
+
mode: RunMode,
|
|
509
531
|
): void {
|
|
510
532
|
const t0 = performance.now();
|
|
511
533
|
let out: RuntimeOut;
|
|
512
534
|
try {
|
|
513
|
-
out = new Runtime("AAPL", { broker }).run(source, syntheticBars(bars));
|
|
535
|
+
out = new Runtime("AAPL", { broker, mode }).run(source, syntheticBars(bars));
|
|
514
536
|
} catch (err) {
|
|
515
537
|
fail(ui, err instanceof Error ? err.message : String(err), 1);
|
|
516
538
|
}
|
|
@@ -538,7 +560,7 @@ function infoPayload(ui: Rich): {
|
|
|
538
560
|
version: VERSION,
|
|
539
561
|
runtime: "bun",
|
|
540
562
|
bun: typeof Bun !== "undefined" ? Bun.version : null,
|
|
541
|
-
mode: "interpret",
|
|
563
|
+
mode: "interpret+compile",
|
|
542
564
|
rich: ui.enabled,
|
|
543
565
|
docs: "https://hoox.sh/pyne",
|
|
544
566
|
};
|
|
@@ -558,7 +580,7 @@ function info(ui: Rich, asJson: boolean): void {
|
|
|
558
580
|
["package", "@hoox-sh/pynets"],
|
|
559
581
|
["version", VERSION],
|
|
560
582
|
["runtime", `bun ${payload.bun ?? "?"}`],
|
|
561
|
-
["engine", "interpret"],
|
|
583
|
+
["engine", "interpret + compile (JS emit)"],
|
|
562
584
|
["rich", "yes (TTY)"],
|
|
563
585
|
["docs", payload.docs],
|
|
564
586
|
],
|
|
@@ -577,7 +599,7 @@ function main(): void {
|
|
|
577
599
|
failUsage(ui, msg);
|
|
578
600
|
}
|
|
579
601
|
|
|
580
|
-
const { cmd, file, bars, json, indent, full } = parsed;
|
|
602
|
+
const { cmd, file, bars, json, indent, full, mode } = parsed;
|
|
581
603
|
const broker = brokerFromFlags(parsed);
|
|
582
604
|
if (cmd === "help" || cmd === "-h" || cmd === "--help") {
|
|
583
605
|
ui.help();
|
|
@@ -610,7 +632,7 @@ function main(): void {
|
|
|
610
632
|
return;
|
|
611
633
|
}
|
|
612
634
|
if (cmd === "run") {
|
|
613
|
-
run(ui, readSource(ui, file), file, bars, json, broker);
|
|
635
|
+
run(ui, readSource(ui, file), file, bars, json, broker, mode);
|
|
614
636
|
return;
|
|
615
637
|
}
|
|
616
638
|
failUsage(ui, `error: unknown command: ${cmd}`);
|