@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +20 -9
  3. package/dist/browser.js +95 -34
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +23002 -12427
  6. package/package.json +1 -1
  7. package/src/ast/builder.ts +235 -12
  8. package/src/ast/nodes.ts +114 -4
  9. package/src/ast/unparse.ts +192 -17
  10. package/src/cli/rich.ts +4 -3
  11. package/src/cli.ts +29 -7
  12. package/src/generated/PinescriptLexer.interp +5 -1
  13. package/src/generated/PinescriptLexer.tokens +97 -95
  14. package/src/generated/PinescriptLexer.ts +512 -502
  15. package/src/generated/PinescriptParser.interp +8 -1
  16. package/src/generated/PinescriptParser.tokens +97 -95
  17. package/src/generated/PinescriptParser.ts +1907 -1287
  18. package/src/generated/PinescriptParserVisitor.ts +36 -1
  19. package/src/index.ts +22 -0
  20. package/src/runtime/array.ts +238 -68
  21. package/src/runtime/compile/collections.ts +492 -0
  22. package/src/runtime/compile/draw.ts +181 -0
  23. package/src/runtime/compile/emit.ts +1421 -0
  24. package/src/runtime/compile/emit_call.ts +1796 -0
  25. package/src/runtime/compile/emit_udf.ts +326 -0
  26. package/src/runtime/compile/engine.ts +265 -0
  27. package/src/runtime/compile/helpers.ts +140 -0
  28. package/src/runtime/compile/index.ts +33 -0
  29. package/src/runtime/compile/runtime.ts +357 -0
  30. package/src/runtime/compile/strategy.ts +250 -0
  31. package/src/runtime/compile/types.ts +127 -0
  32. package/src/runtime/compile/udt_runtime.ts +50 -0
  33. package/src/runtime/drawings.ts +405 -8
  34. package/src/runtime/interpret.ts +2387 -281
  35. package/src/runtime/matrix.ts +371 -42
  36. package/src/runtime/strategy.ts +695 -33
  37. package/src/runtime/ta.ts +654 -20
  38. package/src/runtime/timeframe.ts +199 -0
@@ -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 `${emitExpr(node.value)}.${node.attr}`;
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 `${emitExpr(node.left)} ${OP[node.op.kind] ?? "?"} ${emitExpr(node.right)}`;
202
+ return emitBinOp(node);
94
203
  case "UnaryOp":
95
- return `${UOP[node.op.kind] ?? ""}${emitExpr(node.operand)}`;
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
- let out = emitExpr(node.left);
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] ?? "??"} ${emitExpr(node.comparators[i]!)}`;
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
- return `${emitExpr(node.test)} ? ${emitExpr(node.body)} : ${emitExpr(node.orelse)}`;
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
- return node.values.map(emitExpr).join(` ${op} `);
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
- return `${emitExpr(node.func)}(${asArgs(node.args).map(emitArg).join(", ")})`;
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 `${emitExpr(a.value)}.${a.attr}`;
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
- const b = node as BinOp;
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.1.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", "Interpret on synthetic OHLCV"],
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}`);