@ball-lang/compiler 1.19.0 → 1.19.2
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/dist/compiler.d.ts +74 -0
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +322 -113
- package/dist/compiler.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler.ts +322 -92
package/dist/compiler.d.ts
CHANGED
|
@@ -61,6 +61,28 @@ export declare class BallCompiler {
|
|
|
61
61
|
* rather than silently emitting invalid or wrong-behaving TS.
|
|
62
62
|
*/
|
|
63
63
|
private activeGotoLabels;
|
|
64
|
+
/**
|
|
65
|
+
* Stack of enclosing *goto-switch* lowerings (see `emitGotoSwitchStmt`). A
|
|
66
|
+
* Ball `switch` with labelled cases (`case 0: continue one; one: case 1: …`)
|
|
67
|
+
* is Dart's goto-via-switch: `continue <caseLabel>` transfers control to that
|
|
68
|
+
* case's body with NO subject re-check. TS has no labelled switch cases, so
|
|
69
|
+
* such a switch lowers to a `<loopLabel>: while (…) switch (<stateVar>) { … }`
|
|
70
|
+
* state machine; a `continue <caseLabel>` inside a case body becomes
|
|
71
|
+
* `<stateVar> = <armIndex>; continue <loopLabel>;`. `labelToIndex` maps each
|
|
72
|
+
* case label to its arm index, `depth` records `loopNest` at the case-body
|
|
73
|
+
* level (so an unlabeled `break`/`continue` inside a *nested* loop is left
|
|
74
|
+
* verbatim for that loop rather than hijacked into the switch's own loop).
|
|
75
|
+
*/
|
|
76
|
+
private switchLabelStack;
|
|
77
|
+
/**
|
|
78
|
+
* Count of enclosing break/continue-capturing scopes (loops + switches)
|
|
79
|
+
* currently open during body emission. Consulted only when a
|
|
80
|
+
* `switchLabelStack` entry is active, to tell a case-body-level break from a
|
|
81
|
+
* nested-loop break (see `switchLabelStack`).
|
|
82
|
+
*/
|
|
83
|
+
private loopNest;
|
|
84
|
+
/** Monotonic id for uniquely naming goto-switch loop labels / state vars. */
|
|
85
|
+
private switchUid;
|
|
64
86
|
constructor(program: Program);
|
|
65
87
|
/** Compile to TS source. */
|
|
66
88
|
compile(options?: CompileOptions): string;
|
|
@@ -119,6 +141,58 @@ export declare class BallCompiler {
|
|
|
119
141
|
private emitStatement;
|
|
120
142
|
private isControlFlow;
|
|
121
143
|
private emitControlFlowStatement;
|
|
144
|
+
/**
|
|
145
|
+
* Parse a switch's `cases[]` into if/else-chain arms, merging empty-body
|
|
146
|
+
* fall-through cases (`case 'a': case 'b': body`) into the following arm via
|
|
147
|
+
* `||`. Also records `label → arm index` for labelled cases so a
|
|
148
|
+
* goto-via-switch (`emitGotoSwitchStmt`) can jump between arms; a label on an
|
|
149
|
+
* empty fall-through case maps to the arm that absorbs it (jumping there and
|
|
150
|
+
* immediately falling through are equivalent). Conditions reference the
|
|
151
|
+
* subject as `__sw`, so callers must bind `const __sw = <subject>`.
|
|
152
|
+
*/
|
|
153
|
+
private parseSwitchCases;
|
|
154
|
+
/**
|
|
155
|
+
* Lower a goto-via-switch (a Ball `switch` with labelled cases) to a
|
|
156
|
+
* state-machine loop. TS has no labelled switch cases, so
|
|
157
|
+
*
|
|
158
|
+
* switch (s) { case 0: …; continue one; one: case 1: …; break; default: … }
|
|
159
|
+
*
|
|
160
|
+
* becomes
|
|
161
|
+
*
|
|
162
|
+
* { const __sw = s; let __swst = -1;
|
|
163
|
+
* if (__sw === 0) __swst = 0; else if (__sw === 1) __swst = 1;
|
|
164
|
+
* if (__swst === -1) __swst = <default arm>;
|
|
165
|
+
* __swl: while (__swst >= 0) { switch (__swst) {
|
|
166
|
+
* case 0: { …; __swst = 1; continue __swl; } // continue one → arm 1
|
|
167
|
+
* case 1: { …; break __swl; }
|
|
168
|
+
* case <default>: { …; break __swl; }
|
|
169
|
+
* } break __swl; } }
|
|
170
|
+
*
|
|
171
|
+
* Phase 1 picks the entry arm by matching the subject (default only if
|
|
172
|
+
* nothing matched); phase 2 runs from that arm, where a `continue <label>`
|
|
173
|
+
* inside a body re-enters the loop at the labelled arm with NO subject
|
|
174
|
+
* re-check (see the `break`/`continue` cases in `emitControlFlowStatement`).
|
|
175
|
+
* A body that falls off its arm exits via the trailing `break __swl`.
|
|
176
|
+
*
|
|
177
|
+
* Known limitation: pattern bindings are recomputed from `__sw` on each arm
|
|
178
|
+
* (so a goto INTO a case with different bindings would rebind from the
|
|
179
|
+
* subject rather than leave them unbound as the reference engine does) — not
|
|
180
|
+
* reachable from valid Dart, where a `continue` target can't use a preceding
|
|
181
|
+
* case's binders.
|
|
182
|
+
*/
|
|
183
|
+
private emitGotoSwitchStmt;
|
|
184
|
+
/** A switch arm's pattern bindings (`case int n`, `case {'k': var v}`, …),
|
|
185
|
+
* read off the subject alias `__sw`. Shared by the if/else-chain and
|
|
186
|
+
* goto-switch lowerings. */
|
|
187
|
+
private switchArmBindings;
|
|
188
|
+
/**
|
|
189
|
+
* A switch arm's match condition over `__sw`, folding a `when` guard in. The
|
|
190
|
+
* guard runs inside an IIFE bound to the pattern variables, gated by the
|
|
191
|
+
* match condition via `&&` (so it only runs once the pattern matched and the
|
|
192
|
+
* temps are valid); its binders are passed positionally, the guard expression
|
|
193
|
+
* itself untouched. Shared by the if/else-chain and goto-switch lowerings.
|
|
194
|
+
*/
|
|
195
|
+
private buildSwitchArmCond;
|
|
122
196
|
private emitIfStmt;
|
|
123
197
|
private emitForStmt;
|
|
124
198
|
private emitForInStmt;
|
package/dist/compiler.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAMV,MAAM,EACN,OAAO,EAIR,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAQD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,+CAA+C;IAC/C,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,KAAK,CAAK;IAElB,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAE/C,sEAAsE;IACtE,OAAO,CAAC,YAAY,CAA0B;IAE9C,mEAAmE;IACnE,OAAO,CAAC,kBAAkB,CAA0B;IAEpD,mEAAmE;IACnE,OAAO,CAAC,mBAAmB,CAA0B;IAErD,0EAA0E;IAC1E,OAAO,CAAC,uBAAuB,CAA0B;IAEzD;0EACsE;IACtE,OAAO,CAAC,uBAAuB,CAA0B;IAEzD;;;;OAIG;IACH,OAAO,CAAC,uBAAuB,CAA0B;IAEzD,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,8EAA8E;IAC9E,OAAO,CAAC,sBAAsB,CAA0B;IAExD,yEAAyE;IACzE,OAAO,CAAC,gBAAgB,CAA0B;IAElD,4DAA4D;IAC5D,OAAO,CAAC,aAAa,CAA0C;IAE/D;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAA0B;IAEnD;;;;;OAKG;IACH,OAAO,CAAC,WAAW,CAA6B;IAEhD;;;;;;;;;;OAUG;IACH,OAAO,CAAC,gBAAgB,CAAgB;
|
|
1
|
+
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
|
package/dist/compiler.js
CHANGED
|
@@ -70,6 +70,28 @@ export class BallCompiler {
|
|
|
70
70
|
* rather than silently emitting invalid or wrong-behaving TS.
|
|
71
71
|
*/
|
|
72
72
|
activeGotoLabels = [];
|
|
73
|
+
/**
|
|
74
|
+
* Stack of enclosing *goto-switch* lowerings (see `emitGotoSwitchStmt`). A
|
|
75
|
+
* Ball `switch` with labelled cases (`case 0: continue one; one: case 1: …`)
|
|
76
|
+
* is Dart's goto-via-switch: `continue <caseLabel>` transfers control to that
|
|
77
|
+
* case's body with NO subject re-check. TS has no labelled switch cases, so
|
|
78
|
+
* such a switch lowers to a `<loopLabel>: while (…) switch (<stateVar>) { … }`
|
|
79
|
+
* state machine; a `continue <caseLabel>` inside a case body becomes
|
|
80
|
+
* `<stateVar> = <armIndex>; continue <loopLabel>;`. `labelToIndex` maps each
|
|
81
|
+
* case label to its arm index, `depth` records `loopNest` at the case-body
|
|
82
|
+
* level (so an unlabeled `break`/`continue` inside a *nested* loop is left
|
|
83
|
+
* verbatim for that loop rather than hijacked into the switch's own loop).
|
|
84
|
+
*/
|
|
85
|
+
switchLabelStack = [];
|
|
86
|
+
/**
|
|
87
|
+
* Count of enclosing break/continue-capturing scopes (loops + switches)
|
|
88
|
+
* currently open during body emission. Consulted only when a
|
|
89
|
+
* `switchLabelStack` entry is active, to tell a case-body-level break from a
|
|
90
|
+
* nested-loop break (see `switchLabelStack`).
|
|
91
|
+
*/
|
|
92
|
+
loopNest = 0;
|
|
93
|
+
/** Monotonic id for uniquely naming goto-switch loop labels / state vars. */
|
|
94
|
+
switchUid = 0;
|
|
73
95
|
constructor(program) {
|
|
74
96
|
this.program = program;
|
|
75
97
|
}
|
|
@@ -2752,12 +2774,43 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2752
2774
|
}
|
|
2753
2775
|
case "break": {
|
|
2754
2776
|
const label = stringField(call, "label");
|
|
2755
|
-
|
|
2777
|
+
if (label) {
|
|
2778
|
+
// A labelled break targets an enclosing labelled loop, never a
|
|
2779
|
+
// switch case — emit verbatim.
|
|
2780
|
+
this.writeln(`break ${label};`);
|
|
2781
|
+
}
|
|
2782
|
+
else {
|
|
2783
|
+
// An unlabeled break directly inside a goto-switch case body exits
|
|
2784
|
+
// the switch (its lowered state-machine loop). Inside a *nested* loop
|
|
2785
|
+
// it belongs to that loop, so leave it verbatim (see switchLabelStack).
|
|
2786
|
+
const ctx = this.switchLabelStack[this.switchLabelStack.length - 1];
|
|
2787
|
+
this.writeln(ctx && this.loopNest === ctx.depth ? `break ${ctx.loopLabel};` : "break;");
|
|
2788
|
+
}
|
|
2756
2789
|
break;
|
|
2757
2790
|
}
|
|
2758
2791
|
case "continue": {
|
|
2759
2792
|
const label = stringField(call, "label");
|
|
2760
|
-
|
|
2793
|
+
if (label) {
|
|
2794
|
+
// `continue <caseLabel>` naming a labelled case of an enclosing
|
|
2795
|
+
// goto-switch is a goto: jump to that case's arm with no subject
|
|
2796
|
+
// re-check (innermost matching switch wins). Any other label targets
|
|
2797
|
+
// an enclosing labelled loop and is emitted verbatim.
|
|
2798
|
+
let handled = false;
|
|
2799
|
+
for (let i = this.switchLabelStack.length - 1; i >= 0; i--) {
|
|
2800
|
+
const ctx = this.switchLabelStack[i];
|
|
2801
|
+
const idx = ctx.labelToIndex.get(label);
|
|
2802
|
+
if (idx !== undefined) {
|
|
2803
|
+
this.writeln(`${ctx.stateVar} = ${idx}; continue ${ctx.loopLabel};`);
|
|
2804
|
+
handled = true;
|
|
2805
|
+
break;
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
if (!handled)
|
|
2809
|
+
this.writeln(`continue ${label};`);
|
|
2810
|
+
}
|
|
2811
|
+
else {
|
|
2812
|
+
this.writeln("continue;");
|
|
2813
|
+
}
|
|
2761
2814
|
break;
|
|
2762
2815
|
}
|
|
2763
2816
|
case "labeled": {
|
|
@@ -2803,122 +2856,26 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2803
2856
|
break;
|
|
2804
2857
|
}
|
|
2805
2858
|
const subjectStr = this.expr(subjectExpr);
|
|
2859
|
+
const caseExprs = casesField.literal?.listValue?.elements ?? [];
|
|
2860
|
+
const parsed = this.parseSwitchCases(caseExprs);
|
|
2861
|
+
// A switch whose cases carry labels (`case 0: continue one; one: case
|
|
2862
|
+
// 1: …`) is Dart's goto-via-switch — lower it to a state machine so
|
|
2863
|
+
// `continue <caseLabel>` can jump between arms. Plain switches keep the
|
|
2864
|
+
// if/else-chain (zero change for the non-labelled common case).
|
|
2865
|
+
if (parsed.labelToArmIndex.size > 0) {
|
|
2866
|
+
this.emitGotoSwitchStmt(subjectStr, parsed);
|
|
2867
|
+
break;
|
|
2868
|
+
}
|
|
2869
|
+
const { parsedCases, defaultBody } = parsed;
|
|
2870
|
+
let first = true;
|
|
2806
2871
|
// Wrap in do-while(false) so case `break;` exits the switch,
|
|
2807
2872
|
// not an enclosing for loop (switch is compiled as if/else chain).
|
|
2808
2873
|
this.writeln(`do { const __sw = ${subjectStr};`);
|
|
2809
2874
|
this.depth++;
|
|
2810
|
-
|
|
2811
|
-
let defaultBody;
|
|
2812
|
-
let first = true;
|
|
2813
|
-
// Parse all cases, detecting fall-through (empty body = merge
|
|
2814
|
-
// with next case via ||).
|
|
2815
|
-
const parsedCases = [];
|
|
2816
|
-
const pendingConds = [];
|
|
2817
|
-
let lastPatText;
|
|
2818
|
-
for (const ce of caseExprs) {
|
|
2819
|
-
if (!ce.messageCreation)
|
|
2820
|
-
continue;
|
|
2821
|
-
let pattern;
|
|
2822
|
-
let body;
|
|
2823
|
-
let isDefaultFlag = false;
|
|
2824
|
-
let patternExprField;
|
|
2825
|
-
let guardField;
|
|
2826
|
-
for (const fd of ce.messageCreation.fields ?? []) {
|
|
2827
|
-
if (fd.name === "pattern")
|
|
2828
|
-
pattern = fd.value;
|
|
2829
|
-
if (fd.name === "body")
|
|
2830
|
-
body = fd.value;
|
|
2831
|
-
if (fd.name === "is_default" && fd.value?.literal?.boolValue === true)
|
|
2832
|
-
isDefaultFlag = true;
|
|
2833
|
-
if (fd.name === "pattern_expr")
|
|
2834
|
-
patternExprField = fd.value;
|
|
2835
|
-
if (fd.name === "guard")
|
|
2836
|
-
guardField = fd.value;
|
|
2837
|
-
}
|
|
2838
|
-
// Check for is_default flag
|
|
2839
|
-
if (isDefaultFlag) {
|
|
2840
|
-
defaultBody = body;
|
|
2841
|
-
continue;
|
|
2842
|
-
}
|
|
2843
|
-
// Handle structured pattern_expr (only for known pattern kinds)
|
|
2844
|
-
if (patternExprField) {
|
|
2845
|
-
const result = compileStructuredPattern(patternExprField, "__sw", (e) => this.expr(e));
|
|
2846
|
-
if (result) {
|
|
2847
|
-
const cond = result.condition;
|
|
2848
|
-
// A catch-all with no guard ends the chain; with a guard the
|
|
2849
|
-
// branch stays refutable (a false guard falls through).
|
|
2850
|
-
if (cond === "true" && !guardField) {
|
|
2851
|
-
defaultBody = body;
|
|
2852
|
-
break;
|
|
2853
|
-
}
|
|
2854
|
-
const isEmpty = body && body.block &&
|
|
2855
|
-
(body.block.statements ?? []).length === 0 &&
|
|
2856
|
-
body.block.result === undefined;
|
|
2857
|
-
if (!guardField && (!body || isEmpty)) {
|
|
2858
|
-
pendingConds.push(cond);
|
|
2859
|
-
continue;
|
|
2860
|
-
}
|
|
2861
|
-
pendingConds.push(cond);
|
|
2862
|
-
parsedCases.push({ conds: [...pendingConds], body, structuredBindings: result.bindings, guard: guardField });
|
|
2863
|
-
pendingConds.length = 0;
|
|
2864
|
-
continue;
|
|
2865
|
-
}
|
|
2866
|
-
// Unknown pattern kind -- fall through to text-based pattern handling
|
|
2867
|
-
}
|
|
2868
|
-
if (!pattern) {
|
|
2869
|
-
defaultBody = body;
|
|
2870
|
-
continue;
|
|
2871
|
-
}
|
|
2872
|
-
const patText = patternLiteralText(pattern);
|
|
2873
|
-
const cond = patText !== undefined
|
|
2874
|
-
? patternToTsCondition(patText, "__sw")
|
|
2875
|
-
: `((__sw) === ${this.expr(pattern)})`;
|
|
2876
|
-
if (cond === "true" && !guardField) {
|
|
2877
|
-
defaultBody = body;
|
|
2878
|
-
break;
|
|
2879
|
-
}
|
|
2880
|
-
// Empty body = fall-through: accumulate conditions.
|
|
2881
|
-
const isEmpty = body && body.block &&
|
|
2882
|
-
(body.block.statements ?? []).length === 0 &&
|
|
2883
|
-
body.block.result === undefined;
|
|
2884
|
-
if (!guardField && (!body || isEmpty)) {
|
|
2885
|
-
pendingConds.push(cond);
|
|
2886
|
-
lastPatText = patText;
|
|
2887
|
-
continue;
|
|
2888
|
-
}
|
|
2889
|
-
pendingConds.push(cond);
|
|
2890
|
-
parsedCases.push({ conds: [...pendingConds], body, patText: patText ?? lastPatText, guard: guardField });
|
|
2891
|
-
pendingConds.length = 0;
|
|
2892
|
-
lastPatText = undefined;
|
|
2893
|
-
}
|
|
2875
|
+
this.loopNest++;
|
|
2894
2876
|
for (const pc of parsedCases) {
|
|
2895
|
-
|
|
2896
|
-
const
|
|
2897
|
-
if (pc.structuredBindings)
|
|
2898
|
-
caseBindings.push(...pc.structuredBindings);
|
|
2899
|
-
if (pc.patText)
|
|
2900
|
-
caseBindings.push(...patternBindings(pc.patText, "__sw"));
|
|
2901
|
-
// When a `when` guard is present the bindings must be visible to the
|
|
2902
|
-
// guard, which is part of the `if` condition. Hoist them to temps
|
|
2903
|
-
// BEFORE the `if` (so the guard can read them) and reference the temps
|
|
2904
|
-
// inside. Without this, an entered `if` block swallows the arm and
|
|
2905
|
-
// later cases never get tested when the guard is false.
|
|
2906
|
-
let combinedCond = pc.conds.join(" || ");
|
|
2907
|
-
if (pc.guard) {
|
|
2908
|
-
// Evaluate the guard inside an IIFE bound to the pattern variables,
|
|
2909
|
-
// gated by the match condition via `&&` (so the IIFE only runs when
|
|
2910
|
-
// the pattern matched and the temps are valid). The guard's binders
|
|
2911
|
-
// are passed positionally; the guard expression itself is untouched.
|
|
2912
|
-
const matchCond = `(${pc.conds.join(" || ")})`;
|
|
2913
|
-
if (caseBindings.length > 0) {
|
|
2914
|
-
const params = caseBindings.map(b => b.varName).join(", ");
|
|
2915
|
-
const args = caseBindings.map(b => b.expr).join(", ");
|
|
2916
|
-
combinedCond = `${matchCond} && ((${params}) => (${this.expr(pc.guard)}))(${args})`;
|
|
2917
|
-
}
|
|
2918
|
-
else {
|
|
2919
|
-
combinedCond = `${matchCond} && (${this.expr(pc.guard)})`;
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2877
|
+
const caseBindings = this.switchArmBindings(pc);
|
|
2878
|
+
const combinedCond = this.buildSwitchArmCond(pc, caseBindings);
|
|
2922
2879
|
const kw = first ? "if" : "else if";
|
|
2923
2880
|
this.writeln(`${kw} (${combinedCond}) {`);
|
|
2924
2881
|
this.depth++;
|
|
@@ -2942,12 +2899,254 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2942
2899
|
this.writeln("}");
|
|
2943
2900
|
}
|
|
2944
2901
|
}
|
|
2902
|
+
this.loopNest--;
|
|
2945
2903
|
this.depth--;
|
|
2946
2904
|
this.writeln("} while (false);");
|
|
2947
2905
|
break;
|
|
2948
2906
|
}
|
|
2949
2907
|
}
|
|
2950
2908
|
}
|
|
2909
|
+
/**
|
|
2910
|
+
* Parse a switch's `cases[]` into if/else-chain arms, merging empty-body
|
|
2911
|
+
* fall-through cases (`case 'a': case 'b': body`) into the following arm via
|
|
2912
|
+
* `||`. Also records `label → arm index` for labelled cases so a
|
|
2913
|
+
* goto-via-switch (`emitGotoSwitchStmt`) can jump between arms; a label on an
|
|
2914
|
+
* empty fall-through case maps to the arm that absorbs it (jumping there and
|
|
2915
|
+
* immediately falling through are equivalent). Conditions reference the
|
|
2916
|
+
* subject as `__sw`, so callers must bind `const __sw = <subject>`.
|
|
2917
|
+
*/
|
|
2918
|
+
parseSwitchCases(caseExprs) {
|
|
2919
|
+
const parsedCases = [];
|
|
2920
|
+
const labelToArmIndex = new Map();
|
|
2921
|
+
let defaultBody;
|
|
2922
|
+
// Labels waiting to be bound to the arm that absorbs them (labels on empty
|
|
2923
|
+
// fall-through cases, and the label of a non-empty case, both attach when
|
|
2924
|
+
// that case's arm is pushed). Labels pending when `default` is reached bind
|
|
2925
|
+
// to the default arm.
|
|
2926
|
+
const pendingConds = [];
|
|
2927
|
+
const pendingLabels = [];
|
|
2928
|
+
const defaultLabels = [];
|
|
2929
|
+
let lastPatText;
|
|
2930
|
+
// Push the current arm, binding any labels accumulated for it.
|
|
2931
|
+
const pushArm = (arm) => {
|
|
2932
|
+
const armIndex = parsedCases.length;
|
|
2933
|
+
for (const l of pendingLabels)
|
|
2934
|
+
labelToArmIndex.set(l, armIndex);
|
|
2935
|
+
pendingLabels.length = 0;
|
|
2936
|
+
parsedCases.push(arm);
|
|
2937
|
+
};
|
|
2938
|
+
// Record the default arm's body and drain any labels accumulated up to it
|
|
2939
|
+
// (a `continue <label>` naming a labelled case that falls into `default`
|
|
2940
|
+
// jumps to the default arm).
|
|
2941
|
+
const markDefault = (b) => {
|
|
2942
|
+
defaultBody = b;
|
|
2943
|
+
defaultLabels.push(...pendingLabels);
|
|
2944
|
+
pendingLabels.length = 0;
|
|
2945
|
+
};
|
|
2946
|
+
for (const ce of caseExprs) {
|
|
2947
|
+
if (!ce.messageCreation)
|
|
2948
|
+
continue;
|
|
2949
|
+
let pattern;
|
|
2950
|
+
let body;
|
|
2951
|
+
let isDefaultFlag = false;
|
|
2952
|
+
let patternExprField;
|
|
2953
|
+
let guardField;
|
|
2954
|
+
let caseLabel;
|
|
2955
|
+
for (const fd of ce.messageCreation.fields ?? []) {
|
|
2956
|
+
if (fd.name === "pattern")
|
|
2957
|
+
pattern = fd.value;
|
|
2958
|
+
if (fd.name === "body")
|
|
2959
|
+
body = fd.value;
|
|
2960
|
+
if (fd.name === "is_default" && fd.value?.literal?.boolValue === true)
|
|
2961
|
+
isDefaultFlag = true;
|
|
2962
|
+
if (fd.name === "pattern_expr")
|
|
2963
|
+
patternExprField = fd.value;
|
|
2964
|
+
if (fd.name === "guard")
|
|
2965
|
+
guardField = fd.value;
|
|
2966
|
+
if (fd.name === "label") {
|
|
2967
|
+
const l = fd.value?.literal?.stringValue;
|
|
2968
|
+
if (l)
|
|
2969
|
+
caseLabel = l;
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
if (caseLabel)
|
|
2973
|
+
pendingLabels.push(caseLabel);
|
|
2974
|
+
// Check for is_default flag
|
|
2975
|
+
if (isDefaultFlag) {
|
|
2976
|
+
markDefault(body);
|
|
2977
|
+
continue;
|
|
2978
|
+
}
|
|
2979
|
+
// Handle structured pattern_expr (only for known pattern kinds)
|
|
2980
|
+
if (patternExprField) {
|
|
2981
|
+
const result = compileStructuredPattern(patternExprField, "__sw", (e) => this.expr(e));
|
|
2982
|
+
if (result) {
|
|
2983
|
+
const cond = result.condition;
|
|
2984
|
+
// A catch-all with no guard ends the chain; with a guard the
|
|
2985
|
+
// branch stays refutable (a false guard falls through).
|
|
2986
|
+
if (cond === "true" && !guardField) {
|
|
2987
|
+
markDefault(body);
|
|
2988
|
+
break;
|
|
2989
|
+
}
|
|
2990
|
+
const isEmpty = body && body.block &&
|
|
2991
|
+
(body.block.statements ?? []).length === 0 &&
|
|
2992
|
+
body.block.result === undefined;
|
|
2993
|
+
if (!guardField && (!body || isEmpty)) {
|
|
2994
|
+
pendingConds.push(cond);
|
|
2995
|
+
continue;
|
|
2996
|
+
}
|
|
2997
|
+
pendingConds.push(cond);
|
|
2998
|
+
pushArm({ conds: [...pendingConds], body, structuredBindings: result.bindings, guard: guardField });
|
|
2999
|
+
pendingConds.length = 0;
|
|
3000
|
+
continue;
|
|
3001
|
+
}
|
|
3002
|
+
// Unknown pattern kind -- fall through to text-based pattern handling
|
|
3003
|
+
}
|
|
3004
|
+
if (!pattern) {
|
|
3005
|
+
markDefault(body);
|
|
3006
|
+
continue;
|
|
3007
|
+
}
|
|
3008
|
+
const patText = patternLiteralText(pattern);
|
|
3009
|
+
const cond = patText !== undefined
|
|
3010
|
+
? patternToTsCondition(patText, "__sw")
|
|
3011
|
+
: `((__sw) === ${this.expr(pattern)})`;
|
|
3012
|
+
if (cond === "true" && !guardField) {
|
|
3013
|
+
markDefault(body);
|
|
3014
|
+
break;
|
|
3015
|
+
}
|
|
3016
|
+
// Empty body = fall-through: accumulate conditions.
|
|
3017
|
+
const isEmpty = body && body.block &&
|
|
3018
|
+
(body.block.statements ?? []).length === 0 &&
|
|
3019
|
+
body.block.result === undefined;
|
|
3020
|
+
if (!guardField && (!body || isEmpty)) {
|
|
3021
|
+
pendingConds.push(cond);
|
|
3022
|
+
lastPatText = patText;
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
pendingConds.push(cond);
|
|
3026
|
+
pushArm({ conds: [...pendingConds], body, patText: patText ?? lastPatText, guard: guardField });
|
|
3027
|
+
pendingConds.length = 0;
|
|
3028
|
+
lastPatText = undefined;
|
|
3029
|
+
}
|
|
3030
|
+
const defaultArmIndex = defaultBody !== undefined ? parsedCases.length : -1;
|
|
3031
|
+
if (defaultArmIndex >= 0) {
|
|
3032
|
+
for (const l of defaultLabels)
|
|
3033
|
+
labelToArmIndex.set(l, defaultArmIndex);
|
|
3034
|
+
}
|
|
3035
|
+
return { parsedCases, defaultBody, labelToArmIndex, defaultArmIndex };
|
|
3036
|
+
}
|
|
3037
|
+
/**
|
|
3038
|
+
* Lower a goto-via-switch (a Ball `switch` with labelled cases) to a
|
|
3039
|
+
* state-machine loop. TS has no labelled switch cases, so
|
|
3040
|
+
*
|
|
3041
|
+
* switch (s) { case 0: …; continue one; one: case 1: …; break; default: … }
|
|
3042
|
+
*
|
|
3043
|
+
* becomes
|
|
3044
|
+
*
|
|
3045
|
+
* { const __sw = s; let __swst = -1;
|
|
3046
|
+
* if (__sw === 0) __swst = 0; else if (__sw === 1) __swst = 1;
|
|
3047
|
+
* if (__swst === -1) __swst = <default arm>;
|
|
3048
|
+
* __swl: while (__swst >= 0) { switch (__swst) {
|
|
3049
|
+
* case 0: { …; __swst = 1; continue __swl; } // continue one → arm 1
|
|
3050
|
+
* case 1: { …; break __swl; }
|
|
3051
|
+
* case <default>: { …; break __swl; }
|
|
3052
|
+
* } break __swl; } }
|
|
3053
|
+
*
|
|
3054
|
+
* Phase 1 picks the entry arm by matching the subject (default only if
|
|
3055
|
+
* nothing matched); phase 2 runs from that arm, where a `continue <label>`
|
|
3056
|
+
* inside a body re-enters the loop at the labelled arm with NO subject
|
|
3057
|
+
* re-check (see the `break`/`continue` cases in `emitControlFlowStatement`).
|
|
3058
|
+
* A body that falls off its arm exits via the trailing `break __swl`.
|
|
3059
|
+
*
|
|
3060
|
+
* Known limitation: pattern bindings are recomputed from `__sw` on each arm
|
|
3061
|
+
* (so a goto INTO a case with different bindings would rebind from the
|
|
3062
|
+
* subject rather than leave them unbound as the reference engine does) — not
|
|
3063
|
+
* reachable from valid Dart, where a `continue` target can't use a preceding
|
|
3064
|
+
* case's binders.
|
|
3065
|
+
*/
|
|
3066
|
+
emitGotoSwitchStmt(subjectStr, parsed) {
|
|
3067
|
+
const { parsedCases, defaultBody, labelToArmIndex, defaultArmIndex } = parsed;
|
|
3068
|
+
const uid = this.switchUid++;
|
|
3069
|
+
const loopLabel = `__swl${uid}`;
|
|
3070
|
+
const stateVar = `__swst${uid}`;
|
|
3071
|
+
this.writeln("{");
|
|
3072
|
+
this.depth++;
|
|
3073
|
+
this.writeln(`const __sw = ${subjectStr};`);
|
|
3074
|
+
this.writeln(`let ${stateVar} = -1;`);
|
|
3075
|
+
// Phase 1 — entry arm selection (non-default arms, in order).
|
|
3076
|
+
let first = true;
|
|
3077
|
+
for (let i = 0; i < parsedCases.length; i++) {
|
|
3078
|
+
const pc = parsedCases[i];
|
|
3079
|
+
const combinedCond = this.buildSwitchArmCond(pc, this.switchArmBindings(pc));
|
|
3080
|
+
this.writeln(`${first ? "if" : "else if"} (${combinedCond}) { ${stateVar} = ${i}; }`);
|
|
3081
|
+
first = false;
|
|
3082
|
+
}
|
|
3083
|
+
if (defaultArmIndex >= 0) {
|
|
3084
|
+
this.writeln(`if (${stateVar} === -1) ${stateVar} = ${defaultArmIndex};`);
|
|
3085
|
+
}
|
|
3086
|
+
// Phase 2 — run from the entry arm, honoring goto/break.
|
|
3087
|
+
this.writeln(`${loopLabel}: while (${stateVar} >= 0) {`);
|
|
3088
|
+
this.depth++;
|
|
3089
|
+
this.writeln(`switch (${stateVar}) {`);
|
|
3090
|
+
this.depth++;
|
|
3091
|
+
this.switchLabelStack.push({ loopLabel, stateVar, labelToIndex: labelToArmIndex, depth: this.loopNest + 1 });
|
|
3092
|
+
this.loopNest++;
|
|
3093
|
+
const emitArm = (idx, body, bindings) => {
|
|
3094
|
+
this.writeln(`case ${idx}: {`);
|
|
3095
|
+
this.depth++;
|
|
3096
|
+
for (const b of bindings)
|
|
3097
|
+
this.writeln(`const ${b.varName} = ${b.expr};`);
|
|
3098
|
+
if (body)
|
|
3099
|
+
this.emitStatementOrExpression(body, false);
|
|
3100
|
+
// A body that falls off its arm (or an inner `break` that exited a nested
|
|
3101
|
+
// loop) ends the switch — arms never fall through to the next.
|
|
3102
|
+
this.writeln(`break ${loopLabel};`);
|
|
3103
|
+
this.depth--;
|
|
3104
|
+
this.writeln("}");
|
|
3105
|
+
};
|
|
3106
|
+
for (let i = 0; i < parsedCases.length; i++) {
|
|
3107
|
+
emitArm(i, parsedCases[i].body, this.switchArmBindings(parsedCases[i]));
|
|
3108
|
+
}
|
|
3109
|
+
if (defaultArmIndex >= 0)
|
|
3110
|
+
emitArm(defaultArmIndex, defaultBody, []);
|
|
3111
|
+
this.loopNest--;
|
|
3112
|
+
this.switchLabelStack.pop();
|
|
3113
|
+
this.depth--;
|
|
3114
|
+
this.writeln("}");
|
|
3115
|
+
this.writeln(`break ${loopLabel};`);
|
|
3116
|
+
this.depth--;
|
|
3117
|
+
this.writeln("}");
|
|
3118
|
+
this.depth--;
|
|
3119
|
+
this.writeln("}");
|
|
3120
|
+
}
|
|
3121
|
+
/** A switch arm's pattern bindings (`case int n`, `case {'k': var v}`, …),
|
|
3122
|
+
* read off the subject alias `__sw`. Shared by the if/else-chain and
|
|
3123
|
+
* goto-switch lowerings. */
|
|
3124
|
+
switchArmBindings(pc) {
|
|
3125
|
+
const b = [];
|
|
3126
|
+
if (pc.structuredBindings)
|
|
3127
|
+
b.push(...pc.structuredBindings);
|
|
3128
|
+
if (pc.patText)
|
|
3129
|
+
b.push(...patternBindings(pc.patText, "__sw"));
|
|
3130
|
+
return b;
|
|
3131
|
+
}
|
|
3132
|
+
/**
|
|
3133
|
+
* A switch arm's match condition over `__sw`, folding a `when` guard in. The
|
|
3134
|
+
* guard runs inside an IIFE bound to the pattern variables, gated by the
|
|
3135
|
+
* match condition via `&&` (so it only runs once the pattern matched and the
|
|
3136
|
+
* temps are valid); its binders are passed positionally, the guard expression
|
|
3137
|
+
* itself untouched. Shared by the if/else-chain and goto-switch lowerings.
|
|
3138
|
+
*/
|
|
3139
|
+
buildSwitchArmCond(pc, caseBindings) {
|
|
3140
|
+
if (!pc.guard)
|
|
3141
|
+
return pc.conds.join(" || ");
|
|
3142
|
+
const matchCond = `(${pc.conds.join(" || ")})`;
|
|
3143
|
+
if (caseBindings.length > 0) {
|
|
3144
|
+
const params = caseBindings.map(b => b.varName).join(", ");
|
|
3145
|
+
const args = caseBindings.map(b => b.expr).join(", ");
|
|
3146
|
+
return `${matchCond} && ((${params}) => (${this.expr(pc.guard)}))(${args})`;
|
|
3147
|
+
}
|
|
3148
|
+
return `${matchCond} && (${this.expr(pc.guard)})`;
|
|
3149
|
+
}
|
|
2951
3150
|
emitIfStmt(call) {
|
|
2952
3151
|
const cond = field(call, "condition");
|
|
2953
3152
|
const then_ = field(call, "then");
|
|
@@ -3014,8 +3213,10 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3014
3213
|
const updateStr = update ? this.expr(unwrapLambda(update)) : "";
|
|
3015
3214
|
this.writeln(`for (${initStr}; ${condStr}; ${updateStr}) {`);
|
|
3016
3215
|
this.depth++;
|
|
3216
|
+
this.loopNest++;
|
|
3017
3217
|
if (body)
|
|
3018
3218
|
this.emitStatementOrExpression(unwrapLambda(body), false);
|
|
3219
|
+
this.loopNest--;
|
|
3019
3220
|
this.depth--;
|
|
3020
3221
|
this.writeln(`}`);
|
|
3021
3222
|
}
|
|
@@ -3025,8 +3226,10 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3025
3226
|
const body = field(call, "body");
|
|
3026
3227
|
this.writeln(`for (const ${variable} of ${this.expr(iterable)}) {`);
|
|
3027
3228
|
this.depth++;
|
|
3229
|
+
this.loopNest++;
|
|
3028
3230
|
if (body)
|
|
3029
3231
|
this.emitStatementOrExpression(unwrapLambda(body), false);
|
|
3232
|
+
this.loopNest--;
|
|
3030
3233
|
this.depth--;
|
|
3031
3234
|
this.writeln(`}`);
|
|
3032
3235
|
}
|
|
@@ -3035,8 +3238,10 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3035
3238
|
const body = field(call, "body");
|
|
3036
3239
|
this.writeln(`while (${this.expr(unwrapLambda(cond))}) {`);
|
|
3037
3240
|
this.depth++;
|
|
3241
|
+
this.loopNest++;
|
|
3038
3242
|
if (body)
|
|
3039
3243
|
this.emitStatementOrExpression(unwrapLambda(body), false);
|
|
3244
|
+
this.loopNest--;
|
|
3040
3245
|
this.depth--;
|
|
3041
3246
|
this.writeln(`}`);
|
|
3042
3247
|
}
|
|
@@ -3059,7 +3264,9 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3059
3264
|
this.activeGotoLabels.push(name);
|
|
3060
3265
|
this.writeln(`${name}: while (true) {`);
|
|
3061
3266
|
this.depth++;
|
|
3267
|
+
this.loopNest++;
|
|
3062
3268
|
this.emitStatementOrExpression(body, false);
|
|
3269
|
+
this.loopNest--;
|
|
3063
3270
|
this.writeln(`break ${name};`);
|
|
3064
3271
|
this.depth--;
|
|
3065
3272
|
this.writeln(`}`);
|
|
@@ -3083,8 +3290,10 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3083
3290
|
const body = field(call, "body");
|
|
3084
3291
|
this.writeln(`do {`);
|
|
3085
3292
|
this.depth++;
|
|
3293
|
+
this.loopNest++;
|
|
3086
3294
|
if (body)
|
|
3087
3295
|
this.emitStatementOrExpression(unwrapLambda(body), false);
|
|
3296
|
+
this.loopNest--;
|
|
3088
3297
|
this.depth--;
|
|
3089
3298
|
this.writeln(`} while (${this.expr(unwrapLambda(cond))});`);
|
|
3090
3299
|
}
|