diff --git a/src/ast.ts b/src/ast.ts index da2271ef3d..34bc653eb3 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -440,9 +440,10 @@ export abstract class Node { static createBlockStatement( statements: Statement[], + label: IdentifierExpression | null, range: Range ): BlockStatement { - return new BlockStatement(statements, range); + return new BlockStatement(statements, label, range); } static createBreakStatement( @@ -475,9 +476,10 @@ export abstract class Node { static createDoStatement( body: Statement, condition: Expression, + label: IdentifierExpression | null, range: Range ): DoStatement { - return new DoStatement(body, condition, range); + return new DoStatement(body, condition, label, range); } static createEmptyStatement( @@ -548,9 +550,10 @@ export abstract class Node { condition: Expression, ifTrue: Statement, ifFalse: Statement | null, + label: IdentifierExpression | null, range: Range ): IfStatement { - return new IfStatement(condition, ifTrue, ifFalse, range); + return new IfStatement(condition, ifTrue, ifFalse, label, range); } static createImportStatement( @@ -607,18 +610,20 @@ export abstract class Node { condition: Expression | null, incrementor: Expression | null, body: Statement, + label: IdentifierExpression | null, range: Range ): ForStatement { - return new ForStatement(initializer, condition, incrementor, body, range); + return new ForStatement(initializer, condition, incrementor, body, label, range); } static createForOfStatement( variable: Statement, iterable: Expression, body: Statement, + label: IdentifierExpression | null, range: Range ): ForOfStatement { - return new ForOfStatement(variable, iterable, body, range); + return new ForOfStatement(variable, iterable, body, label, range); } static createFunctionDeclaration( @@ -675,9 +680,10 @@ export abstract class Node { static createSwitchStatement( condition: Expression, cases: SwitchCase[], + label: IdentifierExpression | null, range: Range ): SwitchStatement { - return new SwitchStatement(condition, cases, range); + return new SwitchStatement(condition, cases, label, range); } static createSwitchCase( @@ -700,9 +706,10 @@ export abstract class Node { catchVariable: IdentifierExpression | null, catchStatements: Statement[] | null, finallyStatements: Statement[] | null, + label: IdentifierExpression | null, range: Range ): TryStatement { - return new TryStatement(bodyStatements, catchVariable, catchStatements, finallyStatements, range); + return new TryStatement(bodyStatements, catchVariable, catchStatements, finallyStatements, label, range); } static createTypeDeclaration( @@ -753,9 +760,10 @@ export abstract class Node { static createWhileStatement( condition: Expression, statement: Statement, + label: IdentifierExpression | null, range: Range ): WhileStatement { - return new WhileStatement(condition, statement, range); + return new WhileStatement(condition, statement, label, range); } /** Tests if this node is a literal of the specified kind. */ @@ -1788,6 +1796,8 @@ export class BlockStatement extends Statement { constructor( /** Contained statements. */ public statements: Statement[], + /** Label, if any. */ + public label: IdentifierExpression | null, /** Source range. */ range: Range ) { @@ -1858,6 +1868,8 @@ export class DoStatement extends Statement { public body: Statement, /** Condition when to repeat. */ public condition: Expression, + /** Label, if any. */ + public label: IdentifierExpression | null, /** Source range. */ range: Range ) { @@ -2022,6 +2034,8 @@ export class ForStatement extends Statement { public incrementor: Expression | null, /** Body statement being looped over. */ public body: Statement, + /** Label, if any. */ + public label: IdentifierExpression | null, /** Source range. */ range: Range ) { @@ -2038,6 +2052,8 @@ export class ForOfStatement extends Statement { public iterable: Expression, /** Body statement being looped over. */ public body: Statement, + /** Label, if any. */ + public label: IdentifierExpression | null, /** Source range. */ range: Range ) { @@ -2108,6 +2124,8 @@ export class IfStatement extends Statement { public ifTrue: Statement, /** Statement executed when condition is `false`. */ public ifFalse: Statement | null, + /** Label, if any. */ + public label: IdentifierExpression | null, /** Source range. */ range: Range ) { @@ -2258,6 +2276,8 @@ export class SwitchStatement extends Statement { public condition: Expression, /** Contained cases. */ public cases: SwitchCase[], + /** Label, if any. */ + public label: IdentifierExpression | null, /** Source range. */ range: Range ) { @@ -2288,6 +2308,8 @@ export class TryStatement extends Statement { public catchStatements: Statement[] | null, /** Statements being executed afterwards, if a `finally` clause is present. */ public finallyStatements: Statement[] | null, + /** Label, if any. */ + public label: IdentifierExpression | null, /** Source range. */ range: Range ) { @@ -2382,6 +2404,8 @@ export class WhileStatement extends Statement { public condition: Expression, /** Body statement being looped over. */ public body: Statement, + /** Label, if any. */ + public label: IdentifierExpression | null, /** Source range. */ range: Range ) { diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 5b0249f9b2..30f493c86f 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -125,6 +125,7 @@ "A class may only extend another class.": 1311, "A parameter property cannot be declared using a rest parameter.": 1317, "A default export can only be used in a module.": 1319, + "A label is not allowed here.": 1344, "An expression of type '{0}' cannot be tested for truthiness.": 1345, "An identifier or keyword cannot immediately follow a numeric literal.": 1351, diff --git a/src/extra/ast.ts b/src/extra/ast.ts index ebe9217f90..5ca23bf79e 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -801,6 +801,7 @@ export class ASTBuilder { let sb = this.sb; let statements = node.statements; let numStatements = statements.length; + this.visitLabel(node.label); if (numStatements) { sb.push("{\n"); let indentLevel = ++this.indentLevel; @@ -815,6 +816,15 @@ export class ASTBuilder { } } + private visitLabel(label: IdentifierExpression | null) { + if (!label) return; + + let sb = this.sb; + this.visitIdentifierExpression(label); + sb.push(":\n"); + indent(sb, this.indentLevel); + } + visitBreakStatement(node: BreakStatement): void { let label = node.label; if (label) { @@ -908,6 +918,7 @@ export class ASTBuilder { visitDoStatement(node: DoStatement): void { let sb = this.sb; + this.visitLabel(node.label); sb.push("do "); this.visitNode(node.body); if (node.body.kind == NodeKind.Block) { @@ -1070,6 +1081,7 @@ export class ASTBuilder { visitForStatement(node: ForStatement): void { let sb = this.sb; + this.visitLabel(node.label); sb.push("for ("); let initializer = node.initializer; if (initializer) { @@ -1095,6 +1107,7 @@ export class ASTBuilder { visitForOfStatement(node: ForOfStatement): void { let sb = this.sb; + this.visitLabel(node.label); sb.push("for ("); this.visitNode(node.variable); sb.push(" of "); @@ -1205,6 +1218,7 @@ export class ASTBuilder { visitIfStatement(node: IfStatement): void { let sb = this.sb; + this.visitLabel(node.label); sb.push("if ("); this.visitNode(node.condition); sb.push(") "); @@ -1397,6 +1411,7 @@ export class ASTBuilder { visitSwitchStatement(node: SwitchStatement): void { let sb = this.sb; + this.visitLabel(node.label); sb.push("switch ("); this.visitNode(node.condition); sb.push(") {\n"); @@ -1418,6 +1433,7 @@ export class ASTBuilder { visitTryStatement(node: TryStatement): void { let sb = this.sb; + this.visitLabel(node.label); sb.push("try {\n"); let indentLevel = ++this.indentLevel; let bodyStatements = node.bodyStatements; @@ -1528,6 +1544,7 @@ export class ASTBuilder { visitWhileStatement(node: WhileStatement): void { let sb = this.sb; + this.visitLabel(node.label); sb.push("while ("); this.visitNode(node.condition); let body = node.body; diff --git a/src/parser.ts b/src/parser.ts index 7c69843973..640d913913 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -2899,7 +2899,37 @@ export class Parser extends DiagnosticEmitter { let state = tn.mark(); let token = tn.next(); + let label: IdentifierExpression | null = null; let statement: Statement | null = null; + + // Detect labeled statements + if (token == Token.Identifier) { + const preIdentifierState = tn.mark(); + const identifier = tn.readIdentifier(); + const range = tn.range(); + + if (tn.skip(Token.Colon)) { + label = Node.createIdentifierExpression(identifier, range); + token = tn.next(); + + switch (token) { + case Token.Do: + case Token.For: + case Token.If: + case Token.OpenBrace: + case Token.Switch: + case Token.Try: + case Token.While: + // Do nothing + break; + default: + this.error(DiagnosticCode.A_label_is_not_allowed_here, range); + } + } else { + tn.reset(preIdentifierState); + } + } + switch (token) { case Token.Break: { statement = this.parseBreak(tn); @@ -2914,15 +2944,15 @@ export class Parser extends DiagnosticEmitter { break; } case Token.Do: { - statement = this.parseDoStatement(tn); + statement = this.parseDoStatement(tn, label); break; } case Token.For: { - statement = this.parseForStatement(tn); + statement = this.parseForStatement(tn, label); break; } case Token.If: { - statement = this.parseIfStatement(tn); + statement = this.parseIfStatement(tn, label); break; } case Token.Let: { @@ -2934,7 +2964,7 @@ export class Parser extends DiagnosticEmitter { break; } case Token.OpenBrace: { - statement = this.parseBlockStatement(tn, topLevel); + statement = this.parseBlockStatement(tn, topLevel, label); break; } case Token.Return: { @@ -2951,7 +2981,7 @@ export class Parser extends DiagnosticEmitter { return Node.createEmptyStatement(tn.range(tn.tokenPos)); } case Token.Switch: { - statement = this.parseSwitchStatement(tn); + statement = this.parseSwitchStatement(tn, label); break; } case Token.Throw: { @@ -2959,7 +2989,7 @@ export class Parser extends DiagnosticEmitter { break; } case Token.Try: { - statement = this.parseTryStatement(tn); + statement = this.parseTryStatement(tn, label); break; } case Token.Void: { @@ -2967,7 +2997,7 @@ export class Parser extends DiagnosticEmitter { break; } case Token.While: { - statement = this.parseWhileStatement(tn); + statement = this.parseWhileStatement(tn, label); break; } case Token.Type: { // also identifier @@ -2994,7 +3024,8 @@ export class Parser extends DiagnosticEmitter { parseBlockStatement( tn: Tokenizer, - topLevel: bool + topLevel: bool, + label: IdentifierExpression | null = null ): BlockStatement | null { // at '{': Statement* '}' ';'? @@ -3013,7 +3044,7 @@ export class Parser extends DiagnosticEmitter { statements.push(statement); } } - let ret = Node.createBlockStatement(statements, tn.range(startPos, tn.pos)); + let ret = Node.createBlockStatement(statements, label, tn.range(startPos, tn.pos)); if (topLevel) tn.skip(Token.Semicolon); return ret; } @@ -3051,7 +3082,8 @@ export class Parser extends DiagnosticEmitter { } parseDoStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null ): DoStatement | null { // at 'do': Statement 'while' '(' Expression ')' ';'? @@ -3067,7 +3099,7 @@ export class Parser extends DiagnosticEmitter { if (!condition) return null; if (tn.skip(Token.CloseParen)) { - let ret = Node.createDoStatement(statement, condition, tn.range(startPos, tn.pos)); + let ret = Node.createDoStatement(statement, condition, label, tn.range(startPos, tn.pos)); tn.skip(Token.Semicolon); return ret; } else { @@ -3106,7 +3138,8 @@ export class Parser extends DiagnosticEmitter { } parseForStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null ): Statement | null { // at 'for': '(' Statement? Expression? ';' Expression? ')' Statement @@ -3139,7 +3172,7 @@ export class Parser extends DiagnosticEmitter { ); return null; } - return this.parseForOfStatement(tn, startPos, initializer); + return this.parseForOfStatement(tn, startPos, initializer, label); } if (initializer.kind == NodeKind.Variable) { let declarations = (initializer).declarations; @@ -3153,7 +3186,7 @@ export class Parser extends DiagnosticEmitter { ); // recoverable } } - return this.parseForOfStatement(tn, startPos, initializer); + return this.parseForOfStatement(tn, startPos, initializer, label); } this.error( DiagnosticCode.Identifier_expected, @@ -3215,6 +3248,7 @@ export class Parser extends DiagnosticEmitter { : null, incrementor, statement, + label, tn.range(startPos, tn.pos) ); @@ -3243,6 +3277,7 @@ export class Parser extends DiagnosticEmitter { tn: Tokenizer, startPos: i32, variable: Statement, + label: IdentifierExpression | null ): ForOfStatement | null { // at 'of': Expression ')' Statement @@ -3265,12 +3300,14 @@ export class Parser extends DiagnosticEmitter { variable, iterable, statement, + label, tn.range(startPos, tn.pos) ); } parseIfStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null ): IfStatement | null { // at 'if': '(' Expression ')' Statement ('else' Statement)? @@ -3291,6 +3328,7 @@ export class Parser extends DiagnosticEmitter { condition, statement, elseStatement, + label, tn.range(startPos, tn.pos) ); } else { @@ -3309,7 +3347,8 @@ export class Parser extends DiagnosticEmitter { } parseSwitchStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null ): SwitchStatement | null { // at 'switch': '(' Expression ')' '{' SwitchCase* '}' ';'? @@ -3326,7 +3365,7 @@ export class Parser extends DiagnosticEmitter { if (!switchCase) return null; switchCases.push(switchCase); } - let ret = Node.createSwitchStatement(condition, switchCases, tn.range(startPos, tn.pos)); + let ret = Node.createSwitchStatement(condition, switchCases, label, tn.range(startPos, tn.pos)); tn.skip(Token.Semicolon); return ret; } else { @@ -3427,7 +3466,8 @@ export class Parser extends DiagnosticEmitter { } parseTryStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null = null, ): TryStatement | null { // at 'try': @@ -3511,6 +3551,7 @@ export class Parser extends DiagnosticEmitter { catchVariable, catchStatements, finallyStatements, + label, tn.range(startPos, tn.pos) ); tn.skip(Token.Semicolon); @@ -3609,7 +3650,8 @@ export class Parser extends DiagnosticEmitter { } parseWhileStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null ): WhileStatement | null { // at 'while': '(' Expression ')' Statement ';'? @@ -3621,7 +3663,7 @@ export class Parser extends DiagnosticEmitter { if (tn.skip(Token.CloseParen)) { let statement = this.parseStatement(tn); if (!statement) return null; - let ret = Node.createWhileStatement(expression, statement, tn.range(startPos, tn.pos)); + let ret = Node.createWhileStatement(expression, statement, label, tn.range(startPos, tn.pos)); tn.skip(Token.Semicolon); return ret; } else {