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 29c460b
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 18 deletions.
25 changes: 20 additions & 5 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: string | 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: string | 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: string | 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: string | 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 @@ -753,9 +757,10 @@ export abstract class Node {
static createWhileStatement(
condition: Expression,
statement: Statement,
label: string | 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 +1793,8 @@ export class BlockStatement extends Statement {
constructor(
/** Contained statements. */
public statements: Statement[],
/** Label, if any. */
public label: string | null,
/** Source range. */
range: Range
) {
Expand Down Expand Up @@ -1858,6 +1865,8 @@ export class DoStatement extends Statement {
public body: Statement,
/** Condition when to repeat. */
public condition: Expression,
/** Label, if any. */
public label: string | null,
/** Source range. */
range: Range
) {
Expand Down Expand Up @@ -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: string | null,
/** Source range. */
range: Range
) {
Expand All @@ -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: string | null,
/** Source range. */
range: Range
) {
Expand Down Expand Up @@ -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: string | 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
60 changes: 47 additions & 13 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2899,7 +2899,34 @@ export class Parser extends DiagnosticEmitter {

let state = tn.mark();
let token = tn.next();
let label: string | 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 = identifier;
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);
Expand All @@ -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: {
Expand All @@ -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: {
Expand Down Expand Up @@ -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
Expand All @@ -2994,7 +3021,8 @@ export class Parser extends DiagnosticEmitter {

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

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

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

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

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

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

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

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

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

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

0 comments on commit 29c460b

Please sign in to comment.