@hoox-sh/pynets 0.2.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 +39 -0
- package/README.md +17 -8
- package/dist/browser.js +89 -84
- package/dist/index.js +19482 -12937
- package/package.json +1 -1
- package/src/ast/builder.ts +235 -12
- package/src/ast/nodes.ts +114 -4
- package/src/ast/unparse.ts +192 -17
- package/src/cli/rich.ts +1 -1
- package/src/generated/PinescriptLexer.interp +5 -1
- package/src/generated/PinescriptLexer.tokens +97 -95
- package/src/generated/PinescriptLexer.ts +512 -502
- package/src/generated/PinescriptParser.interp +8 -1
- package/src/generated/PinescriptParser.tokens +97 -95
- package/src/generated/PinescriptParser.ts +1907 -1287
- package/src/generated/PinescriptParserVisitor.ts +36 -1
- package/src/index.ts +1 -0
- package/src/runtime/array.ts +238 -68
- package/src/runtime/compile/collections.ts +187 -34
- package/src/runtime/compile/emit.ts +266 -9
- package/src/runtime/compile/emit_call.ts +425 -35
- package/src/runtime/compile/runtime.ts +62 -2
- package/src/runtime/compile/strategy.ts +87 -6
- package/src/runtime/compile/types.ts +2 -0
- package/src/runtime/drawings.ts +405 -8
- package/src/runtime/interpret.ts +2201 -277
- 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
|
@@ -8,6 +8,7 @@ import { parse } from "../../ast/helper.ts";
|
|
|
8
8
|
import type {
|
|
9
9
|
Assign,
|
|
10
10
|
Attribute,
|
|
11
|
+
AugAssign,
|
|
11
12
|
BinOp,
|
|
12
13
|
BoolOp,
|
|
13
14
|
Call,
|
|
@@ -23,6 +24,7 @@ import type {
|
|
|
23
24
|
If,
|
|
24
25
|
Import,
|
|
25
26
|
Name,
|
|
27
|
+
Once,
|
|
26
28
|
ReAssign,
|
|
27
29
|
Script,
|
|
28
30
|
stmt,
|
|
@@ -33,7 +35,7 @@ import type {
|
|
|
33
35
|
UnaryOp,
|
|
34
36
|
While,
|
|
35
37
|
} from "../../ast/nodes.ts";
|
|
36
|
-
import { emitCall } from "./emit_call.ts";
|
|
38
|
+
import { emitCall, TA_BARE_ATTRS } from "./emit_call.ts";
|
|
37
39
|
import { emitFunctionDef } from "./emit_udf.ts";
|
|
38
40
|
import type { EmitCtx, VisitFn } from "./types.ts";
|
|
39
41
|
|
|
@@ -47,6 +49,7 @@ const CHART_LOCALS = new Set([
|
|
|
47
49
|
"close",
|
|
48
50
|
"volume",
|
|
49
51
|
"time",
|
|
52
|
+
"time_close",
|
|
50
53
|
"bar_index",
|
|
51
54
|
"last_bar_index",
|
|
52
55
|
"na",
|
|
@@ -138,6 +141,7 @@ export function newEmitCtx(): EmitCtx {
|
|
|
138
141
|
usesStrategy: false,
|
|
139
142
|
nextSite: 0,
|
|
140
143
|
errors: [],
|
|
144
|
+
onceFlags: [],
|
|
141
145
|
currentFunc: null,
|
|
142
146
|
currentParamNames: new Set(),
|
|
143
147
|
currentSeriesParams: new Set(),
|
|
@@ -210,6 +214,9 @@ function assemble(ctx: EmitCtx, body: string[]): string {
|
|
|
210
214
|
lines.push(` const ${arr} = new Array(n_bars).fill(null);`);
|
|
211
215
|
}
|
|
212
216
|
lines.push(" const __var_inited = Object.create(null);");
|
|
217
|
+
for (const flag of ctx.onceFlags) {
|
|
218
|
+
lines.push(` let ${flag} = false;`);
|
|
219
|
+
}
|
|
213
220
|
for (const fn of ctx.functions) {
|
|
214
221
|
if (fn) lines.push(indentBlock(fn, 2));
|
|
215
222
|
}
|
|
@@ -223,6 +230,7 @@ function assemble(ctx: EmitCtx, body: string[]): string {
|
|
|
223
230
|
lines.push(" const close = close_arr[__bar_idx];");
|
|
224
231
|
lines.push(" const volume = vol_arr[__bar_idx];");
|
|
225
232
|
lines.push(" const time = time_arr[__bar_idx];");
|
|
233
|
+
lines.push(" const time_close = __h.timeClose(__bar_idx, time_arr);");
|
|
226
234
|
lines.push(" const bar_index = __bar_idx;");
|
|
227
235
|
lines.push(" const last_bar_index = n_bars - 1;");
|
|
228
236
|
lines.push(" const na = null;");
|
|
@@ -267,6 +275,7 @@ function emitNode(state: State, node: expr | stmt | null | undefined): string {
|
|
|
267
275
|
switch (node.kind) {
|
|
268
276
|
case "Assign":
|
|
269
277
|
case "ReAssign":
|
|
278
|
+
case "AugAssign":
|
|
270
279
|
case "FunctionDef":
|
|
271
280
|
case "TypeDef":
|
|
272
281
|
case "EnumDef":
|
|
@@ -274,6 +283,7 @@ function emitNode(state: State, node: expr | stmt | null | undefined): string {
|
|
|
274
283
|
case "Break":
|
|
275
284
|
case "Continue":
|
|
276
285
|
case "Expr":
|
|
286
|
+
case "Once":
|
|
277
287
|
return emitStmt(state, node);
|
|
278
288
|
default:
|
|
279
289
|
return emitExpr(state, node);
|
|
@@ -288,6 +298,8 @@ function emitStmt(state: State, node: stmt | expr): string {
|
|
|
288
298
|
return emitAssign(state, node);
|
|
289
299
|
case "ReAssign":
|
|
290
300
|
return emitReAssign(state, node);
|
|
301
|
+
case "AugAssign":
|
|
302
|
+
return emitAugAssign(state, node);
|
|
291
303
|
case "FunctionDef":
|
|
292
304
|
emitFunctionDef(state.ctx, node as FunctionDef, (s) => emitStmt(state, s));
|
|
293
305
|
if (state.ctx.currentFunc != null) state.ctx.currentFunc = null;
|
|
@@ -312,6 +324,8 @@ function emitStmt(state: State, node: stmt | expr): string {
|
|
|
312
324
|
return emitWhile(state, node);
|
|
313
325
|
case "Switch":
|
|
314
326
|
return emitSwitch(state, node, false);
|
|
327
|
+
case "Once":
|
|
328
|
+
return emitOnce(state, node);
|
|
315
329
|
default:
|
|
316
330
|
return emitExprStmt(state, node);
|
|
317
331
|
}
|
|
@@ -330,6 +344,8 @@ function emitExprStmt(state: State, value: expr | null | undefined): string {
|
|
|
330
344
|
return emitWhile(state, value);
|
|
331
345
|
case "Switch":
|
|
332
346
|
return emitSwitch(state, value, false);
|
|
347
|
+
case "Once":
|
|
348
|
+
return emitOnce(state, value);
|
|
333
349
|
default: {
|
|
334
350
|
const e = emitExpr(state, value);
|
|
335
351
|
if (!e || e === "null") return "";
|
|
@@ -373,6 +389,12 @@ function emitExpr(state: State, node: expr | null | undefined): string {
|
|
|
373
389
|
return iife(emitForIn(state, node), "null");
|
|
374
390
|
case "While":
|
|
375
391
|
return iife(emitWhile(state, node), "null");
|
|
392
|
+
case "Qualify":
|
|
393
|
+
return emitExpr(state, node.value);
|
|
394
|
+
case "Specialize":
|
|
395
|
+
return emitExpr(state, node.value);
|
|
396
|
+
case "AugAssign":
|
|
397
|
+
return emitAugAssign(state, node);
|
|
376
398
|
default:
|
|
377
399
|
return "null";
|
|
378
400
|
}
|
|
@@ -423,11 +445,25 @@ function emitBinOp(state: State, node: BinOp): string {
|
|
|
423
445
|
return `__h.div(${left}, ${right})`;
|
|
424
446
|
case "Mod":
|
|
425
447
|
return `__h.mod(${left}, ${right})`;
|
|
448
|
+
case "BitAnd":
|
|
449
|
+
return emitBitOp(left, right, "&");
|
|
450
|
+
case "BitOr":
|
|
451
|
+
return emitBitOp(left, right, "|");
|
|
452
|
+
case "BitXor":
|
|
453
|
+
return emitBitOp(left, right, "^");
|
|
454
|
+
case "LShift":
|
|
455
|
+
return emitBitOp(left, right, "<<");
|
|
456
|
+
case "RShift":
|
|
457
|
+
return emitBitOp(left, right, ">>");
|
|
426
458
|
default:
|
|
427
459
|
return `__h.add(${left}, ${right})`;
|
|
428
460
|
}
|
|
429
461
|
}
|
|
430
462
|
|
|
463
|
+
function emitBitOp(left: string, right: string, op: "&" | "|" | "^" | "<<" | ">>"): string {
|
|
464
|
+
return `((__l, __r) => { if (__h.isNa(__l) || __h.isNa(__r)) return null; return (__h.nz(__l) | 0) ${op} (__h.nz(__r) | 0); })(${left}, ${right})`;
|
|
465
|
+
}
|
|
466
|
+
|
|
431
467
|
function emitUnaryOp(state: State, node: UnaryOp): string {
|
|
432
468
|
const operand = emitExpr(state, node.operand);
|
|
433
469
|
switch (opKind(node.op)) {
|
|
@@ -500,9 +536,13 @@ const STRATEGY_QUERY_ATTRS = new Set([
|
|
|
500
536
|
"openprofit",
|
|
501
537
|
"opentrades",
|
|
502
538
|
"closedtrades",
|
|
539
|
+
"leverage",
|
|
540
|
+
"margin_liquidation_price",
|
|
541
|
+
"position_avg_price",
|
|
542
|
+
"initial_capital",
|
|
503
543
|
]);
|
|
504
544
|
|
|
505
|
-
/** Namespaces used
|
|
545
|
+
/** Namespaces used as call receivers or dotted constants — not UDT field access. */
|
|
506
546
|
const ATTR_NAMESPACES = new Set([
|
|
507
547
|
"ta",
|
|
508
548
|
"math",
|
|
@@ -521,8 +561,150 @@ const ATTR_NAMESPACES = new Set([
|
|
|
521
561
|
"session",
|
|
522
562
|
"chart",
|
|
523
563
|
"log",
|
|
564
|
+
"order",
|
|
565
|
+
"format",
|
|
566
|
+
"text",
|
|
567
|
+
"size",
|
|
568
|
+
"barmerge",
|
|
569
|
+
"shape",
|
|
570
|
+
"location",
|
|
571
|
+
"xloc",
|
|
572
|
+
"yloc",
|
|
573
|
+
"extend",
|
|
574
|
+
"display",
|
|
575
|
+
"position",
|
|
576
|
+
"hline",
|
|
577
|
+
"dayofweek",
|
|
578
|
+
"month",
|
|
579
|
+
]);
|
|
580
|
+
|
|
581
|
+
/** Dotted keys from Python `_MATH_CONSTANTS` (base.py). Unknown member → na. */
|
|
582
|
+
const ATTR_CONSTANTS: Record<string, string | number | boolean> = {
|
|
583
|
+
// Math constants — Python SoT `_MATH_CONSTANTS` (evaluator/base.py).
|
|
584
|
+
"math.pi": Math.PI,
|
|
585
|
+
"math.e": Math.E,
|
|
586
|
+
"math.phi": (1 + Math.sqrt(5)) / 2,
|
|
587
|
+
"math.rphi": 2 / (1 + Math.sqrt(5)),
|
|
588
|
+
"format.mintick": "mintick",
|
|
589
|
+
"format.percent": "percent",
|
|
590
|
+
"format.volume": "volume",
|
|
591
|
+
"format.price": "price",
|
|
592
|
+
"text.formatting.none": "",
|
|
593
|
+
"text.formatting.bold": "bold",
|
|
594
|
+
"text.formatting.italic": "italic",
|
|
595
|
+
"text.formatting.bold_italic": "bold italic",
|
|
596
|
+
"size.auto": "auto",
|
|
597
|
+
"size.tiny": 8,
|
|
598
|
+
"size.small": 10,
|
|
599
|
+
"size.normal": 12,
|
|
600
|
+
"size.large": 16,
|
|
601
|
+
"size.huge": 20,
|
|
602
|
+
"order.ascending": 1,
|
|
603
|
+
"order.descending": -1,
|
|
604
|
+
"barmerge.gaps_on": true,
|
|
605
|
+
"barmerge.gaps_off": false,
|
|
606
|
+
"barmerge.lookahead_on": true,
|
|
607
|
+
"barmerge.lookahead_off": false,
|
|
608
|
+
"shape.arrowup": "arrowup",
|
|
609
|
+
"shape.arrowdown": "arrowdown",
|
|
610
|
+
"shape.circle": "circle",
|
|
611
|
+
"shape.cross": "cross",
|
|
612
|
+
"shape.diamond": "diamond",
|
|
613
|
+
"shape.flag": "flag",
|
|
614
|
+
"shape.labelup": "labelup",
|
|
615
|
+
"shape.labeldown": "labeldown",
|
|
616
|
+
"shape.square": "square",
|
|
617
|
+
"shape.triangledown": "triangledown",
|
|
618
|
+
"shape.triangleup": "triangleup",
|
|
619
|
+
"shape.xcross": "xcross",
|
|
620
|
+
"location.abovebar": "abovebar",
|
|
621
|
+
"location.belowbar": "belowbar",
|
|
622
|
+
"location.top": "top",
|
|
623
|
+
"location.bottom": "bottom",
|
|
624
|
+
"location.absolute": "absolute",
|
|
625
|
+
"xloc.bar_index": "bar_index",
|
|
626
|
+
"xloc.bar_time": "bar_time",
|
|
627
|
+
"yloc.price": "price",
|
|
628
|
+
"yloc.abovebar": "abovebar",
|
|
629
|
+
"yloc.belowbar": "belowbar",
|
|
630
|
+
"extend.none": "none",
|
|
631
|
+
"extend.left": "left",
|
|
632
|
+
"extend.right": "right",
|
|
633
|
+
"extend.both": "both",
|
|
634
|
+
// Pine display.* is a bitfield: none=0 pane=1 data_window=2 price_scale=4
|
|
635
|
+
// status_line=8 all=15. Integers so `display.pane + display.data_window` works
|
|
636
|
+
// (Python SoT base.py / compiler.py — matches interpret backend).
|
|
637
|
+
"display.none": 0,
|
|
638
|
+
"display.pane": 1,
|
|
639
|
+
"display.all": 15,
|
|
640
|
+
"display.data_window": 2,
|
|
641
|
+
"display.price_scale": 4,
|
|
642
|
+
"display.status_line": 8,
|
|
643
|
+
"position.top_left": "top_left",
|
|
644
|
+
"position.top_center": "top_center",
|
|
645
|
+
"position.top_right": "top_right",
|
|
646
|
+
"position.middle_left": "middle_left",
|
|
647
|
+
"position.middle_center": "middle_center",
|
|
648
|
+
"position.middle_right": "middle_right",
|
|
649
|
+
"position.bottom_left": "bottom_left",
|
|
650
|
+
"position.bottom_center": "bottom_center",
|
|
651
|
+
"position.bottom_right": "bottom_right",
|
|
652
|
+
"hline.style_solid": "solid",
|
|
653
|
+
"hline.style_dashed": "dashed",
|
|
654
|
+
"hline.style_dotted": "dotted",
|
|
655
|
+
"dayofweek.sunday": 1,
|
|
656
|
+
"dayofweek.monday": 2,
|
|
657
|
+
"dayofweek.tuesday": 3,
|
|
658
|
+
"dayofweek.wednesday": 4,
|
|
659
|
+
"dayofweek.thursday": 5,
|
|
660
|
+
"dayofweek.friday": 6,
|
|
661
|
+
"dayofweek.saturday": 7,
|
|
662
|
+
"month.january": 1,
|
|
663
|
+
"month.february": 2,
|
|
664
|
+
"month.march": 3,
|
|
665
|
+
"month.april": 4,
|
|
666
|
+
"month.may": 5,
|
|
667
|
+
"month.june": 6,
|
|
668
|
+
"month.july": 7,
|
|
669
|
+
"month.august": 8,
|
|
670
|
+
"month.september": 9,
|
|
671
|
+
"month.october": 10,
|
|
672
|
+
"month.november": 11,
|
|
673
|
+
"month.december": 12,
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const ATTR_CONST_NS = new Set([
|
|
677
|
+
"math",
|
|
678
|
+
"order",
|
|
679
|
+
"format",
|
|
680
|
+
"text",
|
|
681
|
+
"size",
|
|
682
|
+
"barmerge",
|
|
683
|
+
"shape",
|
|
684
|
+
"location",
|
|
685
|
+
"xloc",
|
|
686
|
+
"yloc",
|
|
687
|
+
"extend",
|
|
688
|
+
"display",
|
|
689
|
+
"position",
|
|
690
|
+
"hline",
|
|
691
|
+
"dayofweek",
|
|
692
|
+
"month",
|
|
524
693
|
]);
|
|
525
694
|
|
|
695
|
+
function attrQualifiedName(node: Attribute): string | null {
|
|
696
|
+
const parts: string[] = [node.attr];
|
|
697
|
+
let cur: expr = node.value;
|
|
698
|
+
while (cur.kind === "Attribute") {
|
|
699
|
+
parts.push(cur.attr);
|
|
700
|
+
cur = cur.value;
|
|
701
|
+
}
|
|
702
|
+
if (cur.kind !== "Name") return null;
|
|
703
|
+
parts.push(cur.id);
|
|
704
|
+
parts.reverse();
|
|
705
|
+
return parts.join(".");
|
|
706
|
+
}
|
|
707
|
+
|
|
526
708
|
function emitBarstate(attr: string): string {
|
|
527
709
|
switch (attr) {
|
|
528
710
|
case "isfirst":
|
|
@@ -584,19 +766,34 @@ function emitChart(attr: string): string {
|
|
|
584
766
|
}
|
|
585
767
|
}
|
|
586
768
|
|
|
769
|
+
// Static defaults mirror the Python SoT flat table in
|
|
770
|
+
// pynescript/ast/evaluator/base.py ("Chart timeframe defaults (daily)"),
|
|
771
|
+
// cross-checked with runtime/host.py Timeframe (same values). The compile host
|
|
772
|
+
// has no timeframe context (`__h.timeframe` is undefined), so these are the
|
|
773
|
+
// effective values; interpret derives them dynamically from env.timeframe and
|
|
774
|
+
// resolves to the same daily table when no timeframe is configured — every
|
|
775
|
+
// timeframe.* member is backend-parity (commit 6977a42; pinned in
|
|
776
|
+
// test/compile_timeframe_defaults.test.ts).
|
|
587
777
|
function emitTimeframe(attr: string): string {
|
|
588
778
|
switch (attr) {
|
|
589
779
|
case "period":
|
|
590
|
-
|
|
780
|
+
case "main_period":
|
|
781
|
+
return `(__h.timeframe || "D")`;
|
|
591
782
|
case "multiplier":
|
|
592
783
|
return "1";
|
|
593
784
|
case "isintraday":
|
|
594
|
-
return "true";
|
|
595
|
-
case "isdaily":
|
|
596
785
|
case "isweekly":
|
|
597
786
|
case "ismonthly":
|
|
787
|
+
case "isseconds":
|
|
788
|
+
case "isinseconds":
|
|
789
|
+
case "isminutes":
|
|
790
|
+
case "ishours":
|
|
598
791
|
return "false";
|
|
792
|
+
case "isdaily":
|
|
793
|
+
case "isdwm":
|
|
794
|
+
return "true";
|
|
599
795
|
default:
|
|
796
|
+
// Unknown member → na (documented behavior).
|
|
600
797
|
return "null";
|
|
601
798
|
}
|
|
602
799
|
}
|
|
@@ -607,6 +804,8 @@ function emitSyminfo(attr: string): string {
|
|
|
607
804
|
case "tickerid":
|
|
608
805
|
case "root":
|
|
609
806
|
return `"SYMBOL"`;
|
|
807
|
+
case "prefix":
|
|
808
|
+
return `__h.syminfoPrefix()`;
|
|
610
809
|
default:
|
|
611
810
|
return "null";
|
|
612
811
|
}
|
|
@@ -635,10 +834,22 @@ function emitAttribute(state: State, node: Attribute): string {
|
|
|
635
834
|
if (node.value.kind === "Name" && node.value.id === "color") {
|
|
636
835
|
return `__h.colorByName(${JSON.stringify(node.attr)})`;
|
|
637
836
|
}
|
|
837
|
+
if (node.value.kind === "Name" && node.value.id === "ta") {
|
|
838
|
+
// Bare `ta.<attr>` auto-calls only for the whitelist interpret's
|
|
839
|
+
// evalTaAttr supports; everything else is na on both backends.
|
|
840
|
+
if (!TA_BARE_ATTRS.has(node.attr)) return "null";
|
|
841
|
+
return emitCall(state.ctx, { kind: "Call", func: node, args: [] }, visitOf(state));
|
|
842
|
+
}
|
|
638
843
|
if (node.value.kind === "Name" && node.value.id === "strategy") {
|
|
639
844
|
const attr = node.attr;
|
|
640
845
|
if (attr === "long") return `"long"`;
|
|
641
846
|
if (attr === "short") return `"short"`;
|
|
847
|
+
if (attr === "avg_price_stock") return `"stock"`;
|
|
848
|
+
if (attr === "avg_price_futures") return `"futures"`;
|
|
849
|
+
if (attr === "avg_price_inverse") return `"inverse"`;
|
|
850
|
+
if (attr === "cash") return `"cash"`;
|
|
851
|
+
if (attr === "fixed") return `"fixed"`;
|
|
852
|
+
if (attr === "percent_of_equity") return `"percent_of_equity"`;
|
|
642
853
|
if (STRATEGY_QUERY_ATTRS.has(attr)) {
|
|
643
854
|
state.ctx.usesStrategy = true;
|
|
644
855
|
return `__h.strategy.${attr}()`;
|
|
@@ -650,6 +861,15 @@ function emitAttribute(state: State, node: Attribute): string {
|
|
|
650
861
|
return JSON.stringify(node.attr);
|
|
651
862
|
}
|
|
652
863
|
}
|
|
864
|
+
const qn = attrQualifiedName(node);
|
|
865
|
+
if (qn != null) {
|
|
866
|
+
const dot = qn.indexOf(".");
|
|
867
|
+
const root = dot < 0 ? qn : qn.slice(0, dot);
|
|
868
|
+
if (ATTR_CONST_NS.has(root)) {
|
|
869
|
+
const lit = ATTR_CONSTANTS[qn];
|
|
870
|
+
return lit === undefined ? "null" : JSON.stringify(lit);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
653
873
|
const obj = emitExpr(state, node.value);
|
|
654
874
|
if (node.value.kind === "Name" && state.ctx.udtTypes.has(node.value.id)) {
|
|
655
875
|
return `__h.udtGet(${obj}, ${JSON.stringify(node.attr)})`;
|
|
@@ -772,6 +992,21 @@ function emitReAssign(state: State, node: ReAssign): string {
|
|
|
772
992
|
return emitStore(state, node.target, emitExpr(state, node.value), false);
|
|
773
993
|
}
|
|
774
994
|
|
|
995
|
+
function emitAugAssign(state: State, node: AugAssign): string {
|
|
996
|
+
// Attribute targets store the raw rhs — the old field is discarded
|
|
997
|
+
// (quirk preserved from Python visit_AugAssign and interpret evalAugAssign).
|
|
998
|
+
if (node.target.kind === "Attribute") {
|
|
999
|
+
return emitStore(state, node.target, emitExpr(state, node.value), false);
|
|
1000
|
+
}
|
|
1001
|
+
const rhs = emitBinOp(state, {
|
|
1002
|
+
kind: "BinOp",
|
|
1003
|
+
left: node.target,
|
|
1004
|
+
op: node.op,
|
|
1005
|
+
right: node.value,
|
|
1006
|
+
});
|
|
1007
|
+
return emitStore(state, node.target, rhs, false);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
775
1010
|
function emitStore(state: State, target: expr, rhs: string, isVar: boolean): string {
|
|
776
1011
|
if (target.kind === "Name") {
|
|
777
1012
|
return storeName(state, target.id, rhs, isVar);
|
|
@@ -842,6 +1077,15 @@ function emitIfStmt(state: State, node: If, asExpr = false): string {
|
|
|
842
1077
|
return `if (${test}) {\n${indentBlock(thenBlock, 2)}\n} else {\n${indentBlock(elseBlock, 2)}\n}`;
|
|
843
1078
|
}
|
|
844
1079
|
|
|
1080
|
+
function emitOnce(state: State, node: Once): string {
|
|
1081
|
+
const flag = `__once_${state.ctx.onceFlags.length}`;
|
|
1082
|
+
state.ctx.onceFlags.push(flag);
|
|
1083
|
+
const test = node.test == null ? "true" : `__h.nz(${emitExpr(state, node.test)})`;
|
|
1084
|
+
const body = emitBranch(state, node.body, false);
|
|
1085
|
+
const inner = body ? `${indentBlock(body, 4)}\n ${flag} = true;` : ` ${flag} = true;`;
|
|
1086
|
+
return `if (!${flag}) {\n if (${test}) {\n${inner}\n }\n}`;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
845
1089
|
function emitBranch(state: State, stmts: stmt[], asExpr: boolean): string {
|
|
846
1090
|
const list = stmts ?? [];
|
|
847
1091
|
if (list.length === 0) return asExpr ? "return null;" : "";
|
|
@@ -900,6 +1144,7 @@ function isValueExpr(node: expr): boolean {
|
|
|
900
1144
|
case "ForIn":
|
|
901
1145
|
case "While":
|
|
902
1146
|
case "Switch":
|
|
1147
|
+
case "Once":
|
|
903
1148
|
return false;
|
|
904
1149
|
default:
|
|
905
1150
|
return true;
|
|
@@ -1021,10 +1266,7 @@ function emitSwitch(state: State, node: Switch, asExpr: boolean): string {
|
|
|
1021
1266
|
defaultCase = c;
|
|
1022
1267
|
continue;
|
|
1023
1268
|
}
|
|
1024
|
-
const cond =
|
|
1025
|
-
subjN != null
|
|
1026
|
-
? `__h.eq(${subjN}, ${emitExpr(state, c.pattern)})`
|
|
1027
|
-
: `__h.nz(${emitExpr(state, c.pattern)})`;
|
|
1269
|
+
const cond = emitSwitchCond(state, c.pattern, subjN);
|
|
1028
1270
|
const kw = first ? "if" : "else if";
|
|
1029
1271
|
first = false;
|
|
1030
1272
|
const body = emitBranch(state, c.body ?? [], asExpr);
|
|
@@ -1045,6 +1287,21 @@ function emitSwitch(state: State, node: Switch, asExpr: boolean): string {
|
|
|
1045
1287
|
return `{\n${indentBlock(block, 2)}\n}`;
|
|
1046
1288
|
}
|
|
1047
1289
|
|
|
1290
|
+
/** Python `_switch_case_matches`: a comma arm (`1, 2 =>`) matches any element. */
|
|
1291
|
+
function emitSwitchCond(state: State, pattern: expr, subjN: string | null): string {
|
|
1292
|
+
if (pattern.kind === "Tuple") {
|
|
1293
|
+
const parts = (pattern.elts ?? []).map((e) => emitSwitchLeaf(state, e, subjN));
|
|
1294
|
+
return parts.length ? `(${parts.join(" || ")})` : "false";
|
|
1295
|
+
}
|
|
1296
|
+
return emitSwitchLeaf(state, pattern, subjN);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function emitSwitchLeaf(state: State, pattern: expr, subjN: string | null): string {
|
|
1300
|
+
return subjN != null
|
|
1301
|
+
? `__h.eq(${subjN}, ${emitExpr(state, pattern)})`
|
|
1302
|
+
: `__h.nz(${emitExpr(state, pattern)})`;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1048
1305
|
function emitBlock(state: State, stmts: stmt[] | null | undefined): string {
|
|
1049
1306
|
const lines: string[] = [];
|
|
1050
1307
|
for (const s of stmts ?? []) {
|