Skip to content

Commit b6605d3

Browse files
committed
context keys: parser: rewrite _match into _matchOne and _matchAny
1 parent 85e0160 commit b6605d3

File tree

1 file changed

+51
-22
lines changed

1 file changed

+51
-22
lines changed

src/vs/platform/contextkey/common/contextkey.ts

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -306,14 +306,34 @@ value ::=
306306

307307
class ParseError extends Error { }
308308

309-
// Note: this doesn't produce an exact syntax tree but a normalized one
310-
// ContextKeyExpression's that we use as AST nodes do not expose constructors that do not normalize
309+
/**
310+
* A parser for context key expressions.
311+
*
312+
* Example:
313+
* ```ts
314+
* const parser = new Parser();
315+
* const expr = parser.parse('foo == "bar" && baz == true');
316+
*
317+
* if (expr === undefined) {
318+
* // there were lexing or parsing errors
319+
* // process lexing errors with `parser.lexingErrors`
320+
* // process parsing errors with `parser.parsingErrors`
321+
* } else {
322+
* // expr is a valid expression
323+
* }
324+
* ```
325+
*/
311326
export class Parser {
327+
// Note: this doesn't produce an exact syntax tree but a normalized one
328+
// ContextKeyExpression's that we use as AST nodes do not expose constructors that do not normalize
329+
330+
// lifetime note: `_scanner` lives as long as the parser does, i.e., is not reset between calls to `parse`
331+
private _scanner = new Scanner();
312332

333+
// lifetime note: `_tokens`, `_current`, and `_parsingErrors` must be reset between calls to `parse`
313334
private _tokens: Token[] = [];
314335
private _current = 0; // invariant: 0 <= this._current < this._tokens.length ; any incrementation of this value must first call `_isAtEnd`
315336
private _parsingErrors: string[] = [];
316-
private _scanner = new Scanner();
317337

318338
get lexingErrors(): Readonly<Token[]> {
319339
return this._scanner.errorTokens;
@@ -327,7 +347,7 @@ export class Parser {
327347
* Parse a context key expression.
328348
*
329349
* @param input the expression to parse
330-
* @returns the parsed expression or `undefined` if the input is empty
350+
* @returns the parsed expression or `undefined` if there's an error - call `lexingErrors` and `parsingErrors` to see the errors
331351
*/
332352
parse(input: string): ContextKeyExpression | undefined {
333353

@@ -366,7 +386,7 @@ export class Parser {
366386
private _or(): ContextKeyExpression | undefined {
367387
const expr = [this._and()];
368388

369-
while (this._match(TokenType.Or)) {
389+
while (this._matchOne(TokenType.Or)) {
370390
const right = this._and();
371391
expr.push(right);
372392
}
@@ -377,7 +397,7 @@ export class Parser {
377397
private _and(): ContextKeyExpression | undefined {
378398
const expr = [this._term()];
379399

380-
while (this._match(TokenType.And)) {
400+
while (this._matchOne(TokenType.And)) {
381401
const right = this._term();
382402
expr.push(right);
383403
}
@@ -386,8 +406,8 @@ export class Parser {
386406
}
387407

388408
private _term(): ContextKeyExpression | undefined {
389-
if (this._match(TokenType.Neg)) {
390-
if (this._match(TokenType.Str, TokenType.True, TokenType.False)) {
409+
if (this._matchOne(TokenType.Neg)) {
410+
if (this._matchAny(TokenType.Str, TokenType.True, TokenType.False)) {
391411
const expr = this._previous();
392412
switch (expr.type) {
393413
case TokenType.Str:
@@ -407,30 +427,30 @@ export class Parser {
407427

408428
private _primary(): ContextKeyExpression | undefined {
409429

410-
if (this._match(TokenType.True)) {
430+
if (this._matchOne(TokenType.True)) {
411431
return ContextKeyExpr.true();
412432

413-
} else if (this._match(TokenType.False)) {
433+
} else if (this._matchOne(TokenType.False)) {
414434
return ContextKeyExpr.false();
415435

416-
} else if (this._match(TokenType.LParen)) {
436+
} else if (this._matchOne(TokenType.LParen)) {
417437
const expr = this._expr();
418438
this._consume(TokenType.RParen, `')'`);
419439
return expr;
420440

421-
} else if (this._match(TokenType.Str)) {
441+
} else if (this._matchOne(TokenType.Str)) {
422442
// KEY
423443
const key = this._previous().lexeme!;
424444

425445
// =~ regex
426-
if (this._match(TokenType.RegexOp)) {
446+
if (this._matchOne(TokenType.RegexOp)) {
427447

428-
if (this._match(TokenType.RegexStr)) { // expected tokens
448+
if (this._matchOne(TokenType.RegexStr)) { // expected tokens
429449
const regexLexeme = this._previous().lexeme!; // /REGEX/ or /REGEX/FLAGS
430450
const closingSlashIndex = regexLexeme.lastIndexOf('/');
431451
const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : regexLexeme.substring(closingSlashIndex + 1);
432452
return ContextKeyExpr.regex(key, new RegExp(regexLexeme.substring(1, closingSlashIndex), flags));
433-
} if (this._match(TokenType.QuotedStr)) {
453+
} if (this._matchOne(TokenType.QuotedStr)) {
434454
// replicate old regex parsing behavior
435455

436456
const serializedValue = this._previous().lexeme!;
@@ -463,14 +483,14 @@ export class Parser {
463483
}
464484

465485
// [ 'not' 'in' value ]
466-
if (this._match(TokenType.Not)) {
486+
if (this._matchOne(TokenType.Not)) {
467487
this._consume(TokenType.In, `'in' after 'not'`);
468488
const right = this._value();
469489
return ContextKeyExpr.notIn(key, right);
470490
}
471491

472492
// [ ('==' | '!=' | '<' | '<=' | '>' | '>=' | 'in') value ]
473-
if (this._match(TokenType.Eq, TokenType.NotEq, TokenType.Lt, TokenType.LtEq, TokenType.Gt, TokenType.GtEq, TokenType.In)) {
493+
if (this._matchAny(TokenType.Eq, TokenType.NotEq, TokenType.Lt, TokenType.LtEq, TokenType.Gt, TokenType.GtEq, TokenType.In)) {
474494
const op = this._previous().type;
475495
const right = this._value();
476496
switch (op) {
@@ -516,13 +536,13 @@ export class Parser {
516536
}
517537

518538
private _value(): string { // TODO@ulugbekna: match all at once and then `switch` on the type
519-
if (this._match(TokenType.Str, TokenType.QuotedStr)) {
539+
if (this._matchAny(TokenType.Str, TokenType.QuotedStr)) {
520540
return this._previous().lexeme!;
521-
} if (this._match(TokenType.True)) {
541+
} if (this._matchOne(TokenType.True)) {
522542
return 'true';
523-
} if (this._match(TokenType.False)) {
543+
} if (this._matchOne(TokenType.False)) {
524544
return 'false';
525-
} if (this._match(TokenType.In)) { // we support `in` as a value, e.g., "when": "languageId == in" - exists in existing extensions
545+
} if (this._matchOne(TokenType.In)) { // we support `in` as a value, e.g., "when": "languageId == in" - exists in existing extensions
526546
return 'in';
527547
} else {
528548
return ''; // this allows "when": "foo == " which's used by existing extensions
@@ -534,7 +554,16 @@ export class Parser {
534554
return this._tokens[this._current - 1];
535555
}
536556

537-
private _match(...tokens: TokenType[]) {
557+
private _matchOne(token: TokenType) {
558+
if (this._check(token)) {
559+
this._advance();
560+
return true;
561+
}
562+
563+
return false;
564+
}
565+
566+
private _matchAny(...tokens: TokenType[]) {
538567
for (const token of tokens) {
539568
if (this._check(token)) {
540569
this._advance();

0 commit comments

Comments
 (0)