Skip to content

Commit

Permalink
Add support for labeled statements to the parser/AST
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
CountBleck committed Dec 16, 2024
1 parent 40850fe commit c9029f4
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 22 deletions.
30 changes: 24 additions & 6 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -675,9 +679,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(
Expand Down Expand Up @@ -753,9 +758,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. */
Expand Down Expand Up @@ -1788,6 +1794,8 @@ export class BlockStatement extends Statement {
constructor(
/** Contained statements. */
public statements: Statement[],
/** Label, if any. */
public label: IdentifierExpression | null,
/** Source range. */
range: Range
) {
Expand Down Expand Up @@ -1858,6 +1866,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
) {
Expand Down Expand Up @@ -2022,6 +2032,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
) {
Expand All @@ -2038,6 +2050,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
) {
Expand Down Expand Up @@ -2258,6 +2272,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
) {
Expand Down Expand Up @@ -2382,6 +2398,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
) {
Expand Down
1 change: 1 addition & 0 deletions src/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
68 changes: 52 additions & 16 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2899,7 +2899,35 @@ 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.OpenBrace:
case Token.Switch:
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);
Expand All @@ -2914,11 +2942,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: {
Expand All @@ -2934,7 +2962,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: {
Expand All @@ -2951,7 +2979,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: {
Expand All @@ -2967,7 +2995,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
Expand All @@ -2994,7 +3022,8 @@ export class Parser extends DiagnosticEmitter {

parseBlockStatement(
tn: Tokenizer,
topLevel: bool
topLevel: bool,
label: IdentifierExpression | null = null
): BlockStatement | null {

// at '{': Statement* '}' ';'?
Expand All @@ -3013,7 +3042,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;
}
Expand Down Expand Up @@ -3051,7 +3080,8 @@ export class Parser extends DiagnosticEmitter {
}

parseDoStatement(
tn: Tokenizer
tn: Tokenizer,
label: IdentifierExpression | null
): DoStatement | null {

// at 'do': Statement 'while' '(' Expression ')' ';'?
Expand All @@ -3067,7 +3097,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 {
Expand Down Expand Up @@ -3106,7 +3136,8 @@ export class Parser extends DiagnosticEmitter {
}

parseForStatement(
tn: Tokenizer
tn: Tokenizer,
label: IdentifierExpression | null
): Statement | null {

// at 'for': '(' Statement? Expression? ';' Expression? ')' Statement
Expand Down Expand Up @@ -3139,7 +3170,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 = (<VariableStatement>initializer).declarations;
Expand All @@ -3153,7 +3184,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,
Expand Down Expand Up @@ -3215,6 +3246,7 @@ export class Parser extends DiagnosticEmitter {
: null,
incrementor,
statement,
label,
tn.range(startPos, tn.pos)
);

Expand Down Expand Up @@ -3243,6 +3275,7 @@ export class Parser extends DiagnosticEmitter {
tn: Tokenizer,
startPos: i32,
variable: Statement,
label: IdentifierExpression | null
): ForOfStatement | null {

// at 'of': Expression ')' Statement
Expand All @@ -3265,6 +3298,7 @@ export class Parser extends DiagnosticEmitter {
variable,
iterable,
statement,
label,
tn.range(startPos, tn.pos)
);
}
Expand Down Expand Up @@ -3309,7 +3343,8 @@ export class Parser extends DiagnosticEmitter {
}

parseSwitchStatement(
tn: Tokenizer
tn: Tokenizer,
label: IdentifierExpression | null
): SwitchStatement | null {

// at 'switch': '(' Expression ')' '{' SwitchCase* '}' ';'?
Expand All @@ -3326,7 +3361,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 {
Expand Down Expand Up @@ -3609,7 +3644,8 @@ export class Parser extends DiagnosticEmitter {
}

parseWhileStatement(
tn: Tokenizer
tn: Tokenizer,
label: IdentifierExpression | null
): WhileStatement | null {

// at 'while': '(' Expression ')' Statement ';'?
Expand All @@ -3621,7 +3657,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 {
Expand Down

0 comments on commit c9029f4

Please sign in to comment.