@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.
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
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
- this.writeln(label ? `break ${label};` : "break;");
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
- this.writeln(label ? `continue ${label};` : "continue;");
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
- const caseExprs = casesField.literal?.listValue?.elements ?? [];
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
- // Gather this case's pattern bindings.
2896
- const caseBindings = [];
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
  }