Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 60e57da

Browse files
Add support for labeled break/continue
This requires an additional field to Flow that maps user-defined statement labels to the internal Binaryen labels passed to module.br(). Thanks to the existing logic to handle unlabeled break/continue, adding support for labeled break/continue is a breeze. Fixes #2889.
1 parent c9b169f commit 60e57da

File tree

3 files changed

+104
-22
lines changed

3 files changed

+104
-22
lines changed

‎src/compiler.ts

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2290,6 +2290,8 @@ export class Compiler extends DiagnosticEmitter {
22902290
private compileBlockStatement(
22912291
statement: BlockStatement
22922292
): ExpressionRef {
2293+
if (statement.label) return this.compileLabeledBlockStatement(statement);
2294+
22932295
let statements = statement.statements;
22942296
let outerFlow = this.currentFlow;
22952297
let innerFlow = outerFlow.fork();
@@ -2301,6 +2303,30 @@ export class Compiler extends DiagnosticEmitter {
23012303
return this.module.flatten(stmts);
23022304
}
23032305

2306+
private compileLabeledBlockStatement(
2307+
statement: BlockStatement
2308+
): ExpressionRef {
2309+
let statements = statement.statements;
2310+
let outerFlow = this.currentFlow;
2311+
let innerFlow = outerFlow.fork();
2312+
2313+
let labelNode = assert(statement.label);
2314+
let label = innerFlow.pushControlFlowLabel();
2315+
let breakLabel = `block-break|${label}`;
2316+
innerFlow.addUserLabel(labelNode.text, breakLabel, null, labelNode);
2317+
this.currentFlow = innerFlow;
2318+
2319+
let stmts = this.compileStatements(statements);
2320+
innerFlow.popControlFlowLabel(label);
2321+
innerFlow.removeUserLabel(labelNode.text);
2322+
2323+
outerFlow.inherit(innerFlow);
2324+
this.currentFlow = outerFlow;
2325+
return innerFlow.isAny(FlowFlags.Breaks | FlowFlags.ConditionallyBreaks)
2326+
? this.module.block(breakLabel, stmts)
2327+
: this.module.flatten(stmts);
2328+
}
2329+
23042330
private compileTypeDeclaration(statement: TypeDeclaration): ExpressionRef {
23052331
let flow = this.currentFlow;
23062332
let name = statement.name.text;
@@ -2324,23 +2350,25 @@ export class Compiler extends DiagnosticEmitter {
23242350
): ExpressionRef {
23252351
let module = this.module;
23262352
let labelNode = statement.label;
2353+
let flow = this.currentFlow;
2354+
let breakLabel: string | null = null;
23272355
if (labelNode) {
2328-
this.error(
2329-
DiagnosticCode.Not_implemented_0,
2330-
labelNode.range,
2331-
"Break label"
2332-
);
2333-
return module.unreachable();
2356+
const userLabel = flow.getUserLabel(labelNode.text);
2357+
if (userLabel) breakLabel = userLabel.breakLabel;
2358+
} else {
2359+
breakLabel = flow.breakLabel;
23342360
}
2335-
let flow = this.currentFlow;
2336-
let breakLabel = flow.breakLabel;
2361+
23372362
if (breakLabel == null) {
23382363
this.error(
2339-
DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement,
2364+
labelNode
2365+
? DiagnosticCode.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement
2366+
: DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement,
23402367
statement.range
23412368
);
23422369
return module.unreachable();
23432370
}
2371+
23442372
flow.set(FlowFlags.Breaks);
23452373
return module.br(breakLabel);
23462374
}
@@ -2349,25 +2377,27 @@ export class Compiler extends DiagnosticEmitter {
23492377
statement: ContinueStatement
23502378
): ExpressionRef {
23512379
let module = this.module;
2352-
let label = statement.label;
2353-
if(label){
2354-
this.error(
2355-
DiagnosticCode.Not_implemented_0,
2356-
label.range,
2357-
"Continue label"
2358-
);
2359-
returnmodule.unreachable();
2380+
let labelNode = statement.label;
2381+
letflow=this.currentFlow;
2382+
letcontinueLabel: string|null=null;
2383+
if(labelNode){
2384+
constuserLabel=flow.getUserLabel(labelNode.text);
2385+
if(userLabel)continueLabel=userLabel.continueLabel;
2386+
}else{
2387+
continueLabel=flow.continueLabel;
23602388
}
2389+
23612390
// Check if 'continue' is allowed here
2362-
let flow = this.currentFlow;
2363-
let continueLabel = flow.continueLabel;
23642391
if (continueLabel == null) {
23652392
this.error(
2366-
DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement,
2393+
labelNode
2394+
? DiagnosticCode.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement
2395+
: DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement,
23672396
statement.range
23682397
);
23692398
return module.unreachable();
23702399
}
2400+
23712401
flow.set(FlowFlags.Continues | FlowFlags.Terminates);
23722402
return module.br(continueLabel);
23732403
}
@@ -2409,6 +2439,8 @@ export class Compiler extends DiagnosticEmitter {
24092439
let continueLabel = `do-continue|${label}`;
24102440
flow.continueLabel = continueLabel;
24112441
let loopLabel = `do-loop|${label}`;
2442+
let labelNode = statement.label;
2443+
if (labelNode) flow.addUserLabel(labelNode.text, breakLabel, continueLabel, labelNode);
24122444
this.currentFlow = flow;
24132445
let bodyStmts = new Array<ExpressionRef>();
24142446
let body = statement.body;
@@ -2418,6 +2450,7 @@ export class Compiler extends DiagnosticEmitter {
24182450
bodyStmts.push(this.compileStatement(body));
24192451
}
24202452
flow.popControlFlowLabel(label);
2453+
if (labelNode) flow.removeUserLabel(labelNode.text);
24212454

24222455
let possiblyContinues = flow.isAny(FlowFlags.Continues | FlowFlags.ConditionallyContinues);
24232456
let possiblyBreaks = flow.isAny(FlowFlags.Breaks | FlowFlags.ConditionallyBreaks);
@@ -2573,6 +2606,8 @@ export class Compiler extends DiagnosticEmitter {
25732606
bodyFlow.breakLabel = breakLabel;
25742607
let continueLabel = `for-continue|${label}`;
25752608
bodyFlow.continueLabel = continueLabel;
2609+
let labelNode = statement.label;
2610+
if (labelNode) bodyFlow.addUserLabel(labelNode.text, breakLabel, continueLabel, labelNode);
25762611
let loopLabel = `for-loop|${label}`;
25772612
this.currentFlow = bodyFlow;
25782613
let bodyStmts = new Array<ExpressionRef>();
@@ -2583,6 +2618,7 @@ export class Compiler extends DiagnosticEmitter {
25832618
bodyStmts.push(this.compileStatement(body));
25842619
}
25852620
bodyFlow.popControlFlowLabel(label);
2621+
if (labelNode) bodyFlow.removeUserLabel(labelNode.text);
25862622
bodyFlow.breakLabel = null;
25872623
bodyFlow.continueLabel = null;
25882624

@@ -3208,6 +3244,8 @@ export class Compiler extends DiagnosticEmitter {
32083244
thenFlow.breakLabel = breakLabel;
32093245
let continueLabel = `while-continue|${label}`;
32103246
thenFlow.continueLabel = continueLabel;
3247+
let labelNode = statement.label;
3248+
if (labelNode) thenFlow.addUserLabel(labelNode.text, breakLabel, continueLabel, labelNode);
32113249
this.currentFlow = thenFlow;
32123250
let bodyStmts = new Array<ExpressionRef>();
32133251
let body = statement.body;
@@ -3220,6 +3258,7 @@ export class Compiler extends DiagnosticEmitter {
32203258
module.br(continueLabel)
32213259
);
32223260
thenFlow.popControlFlowLabel(label);
3261+
if (labelNode) thenFlow.removeUserLabel(labelNode.text);
32233262

32243263
let possiblyContinues = thenFlow.isAny(FlowFlags.Continues | FlowFlags.ConditionallyContinues);
32253264
let possiblyBreaks = thenFlow.isAny(FlowFlags.Breaks | FlowFlags.ConditionallyBreaks);

‎src/diagnosticMessages.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@
9494
"Type expected.": 1110,
9595
"A 'default' clause cannot appear more than once in a 'switch' statement.": 1113,
9696
"Duplicate label '{0}'.": 1114,
97+
"A 'continue' statement can only jump to a label of an enclosing iteration statement.": 1115,
98+
"A 'break' statement can only jump to a label of an enclosing statement": 1116,
9799
"An export assignment cannot have modifiers.": 1120,
98100
"Octal literals are not allowed in strict mode.": 1121,
99101
"Digit expected.": 1124,

‎src/flow.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,15 @@ export const enum ConditionKind {
199199
False
200200
}
201201

202+
class UserLabels {
203+
constructor(
204+
/** The label we break to when encountering a break statement. */
205+
readonly breakLabel: string,
206+
/** The label we break to when encountering a continue statement. */
207+
readonly continueLabel: string | null
208+
) {}
209+
}
210+
202211
/** A control flow evaluator. */
203212
export class Flow {
204213

@@ -245,10 +254,12 @@ export class Flow {
245254
outer: Flow | null = null;
246255
/** Flow flags indicating specific conditions. */
247256
flags: FlowFlags = FlowFlags.None;
248-
/** The label we break to when encountering a continue statement. */
257+
/** The label we break to when encountering an unlabeled continue statement. */
249258
continueLabel: string | null = null;
250-
/** The label we break to when encountering a break statement. */
259+
/** The label we break to when encountering an unlabeled break statement. */
251260
breakLabel: string | null = null;
261+
/** Map of user-declared statement label names to internal label names */
262+
userLabelMap: Map<string,UserLabels> | null = null;
252263
/** Scoped local variables. */
253264
scopedLocals: Map<string,Local> | null = null;
254265
/** Scoped type alias. */
@@ -351,6 +362,9 @@ export class Flow {
351362
} else {
352363
branch.continueLabel = this.continueLabel;
353364
}
365+
let userLabelMap = this.userLabelMap;
366+
if (userLabelMap) userLabelMap = cloneMap(userLabelMap);
367+
branch.userLabelMap = userLabelMap;
354368
branch.localFlags = this.localFlags.slice();
355369
if (this.sourceFunction.is(CommonFlags.Constructor)) {
356370
let thisFieldFlags = assert(this.thisFieldFlags);
@@ -447,6 +461,33 @@ export class Flow {
447461
return local;
448462
}
449463

464+
465+
/** Gets the internal labels associated with a user-declared label name. */
466+
getUserLabel(name: string): UserLabels | null {
467+
const userLabelMap = this.userLabelMap;
468+
if (userLabelMap && userLabelMap.has(name)) return assert(userLabelMap.get(name));
469+
return null;
470+
}
471+
472+
/** Associates a user-declared label name with internal labels. */
473+
addUserLabel(name: string, breakLabel: string, continueLabel: string | null, declarationNode: Node): void {
474+
let userLabelMap = this.userLabelMap;
475+
if (!userLabelMap) {
476+
this.userLabelMap = userLabelMap = new Map();
477+
} else if (userLabelMap.has(name)) {
478+
this.program.error(DiagnosticCode.Duplicate_label_0, declarationNode.range, name);
479+
}
480+
481+
userLabelMap.set(name, new UserLabels(breakLabel, continueLabel));
482+
}
483+
484+
/** Remove a user-declared label name. */
485+
removeUserLabel(name: string): void {
486+
let userLabelMap = assert(this.userLabelMap);
487+
assert(userLabelMap.has(name));
488+
userLabelMap.delete(name);
489+
}
490+
450491
/** Gets the scoped local of the specified name. */
451492
getScopedLocal(name: string): Local | null {
452493
let scopedLocals = this.scopedLocals;

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /