Skip to content

Commit 43d7a99

Browse files
committed
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 AssemblyScript#2889.
1 parent c9b169f commit 43d7a99

File tree

3 files changed

+104
-22
lines changed

3 files changed

+104
-22
lines changed

src/compiler.ts

+59-20
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-
return module.unreachable();
2380+
let labelNode = statement.label;
2381+
let flow = this.currentFlow;
2382+
let continueLabel: string | null = null;
2383+
if (labelNode) {
2384+
const userLabel = 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

+2
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

+43-2
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)