From c9b169fad1d56fc24fa0f67fcc258e367d5459d3 Mon Sep 17 00:00:00 2001 From: CountBleck Date: Sun, 15 Dec 2024 21:56:47 -0800 Subject: [PATCH] Add support for labeled statements to the parser/AST This is a prerequisite for supporting labeled breaks/continues. Clearly unusable labels, such as `x: let foo = 1;` report an error by default, similar to TS's behavior. --- src/ast.ts | 25 ++++++++++++---- src/diagnosticMessages.json | 1 + src/parser.ts | 60 +++++++++++++++++++++++++++++-------- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index da2271ef3d..b1daf3040e 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( @@ -607,18 +609,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( @@ -753,9 +757,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 +1793,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 +1865,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 +2031,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 +2049,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 ) { @@ -2382,6 +2395,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/parser.ts b/src/parser.ts index 7c69843973..5e1d49dc96 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -2899,7 +2899,34 @@ 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.For: + case Token.While: + case Token.Do: + case Token.OpenBrace: + // 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,11 +2941,11 @@ 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: { @@ -2934,7 +2961,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: { @@ -2967,7 +2994,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 +3021,8 @@ export class Parser extends DiagnosticEmitter { parseBlockStatement( tn: Tokenizer, - topLevel: bool + topLevel: bool, + label: IdentifierExpression | null = null ): BlockStatement | null { // at '{': Statement* '}' ';'? @@ -3013,7 +3041,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 +3079,8 @@ export class Parser extends DiagnosticEmitter { } parseDoStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null ): DoStatement | null { // at 'do': Statement 'while' '(' Expression ')' ';'? @@ -3067,7 +3096,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 +3135,8 @@ export class Parser extends DiagnosticEmitter { } parseForStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null ): Statement | null { // at 'for': '(' Statement? Expression? ';' Expression? ')' Statement @@ -3139,7 +3169,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 +3183,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 +3245,7 @@ export class Parser extends DiagnosticEmitter { : null, incrementor, statement, + label, tn.range(startPos, tn.pos) ); @@ -3243,6 +3274,7 @@ export class Parser extends DiagnosticEmitter { tn: Tokenizer, startPos: i32, variable: Statement, + label: IdentifierExpression | null ): ForOfStatement | null { // at 'of': Expression ')' Statement @@ -3265,6 +3297,7 @@ export class Parser extends DiagnosticEmitter { variable, iterable, statement, + label, tn.range(startPos, tn.pos) ); } @@ -3609,7 +3642,8 @@ export class Parser extends DiagnosticEmitter { } parseWhileStatement( - tn: Tokenizer + tn: Tokenizer, + label: IdentifierExpression | null ): WhileStatement | null { // at 'while': '(' Expression ')' Statement ';'? @@ -3621,7 +3655,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 {