@@ -306,14 +306,34 @@ value ::=
306
306
307
307
class ParseError extends Error { }
308
308
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
+ */
311
326
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 ( ) ;
312
332
333
+ // lifetime note: `_tokens`, `_current`, and `_parsingErrors` must be reset between calls to `parse`
313
334
private _tokens : Token [ ] = [ ] ;
314
335
private _current = 0 ; // invariant: 0 <= this._current < this._tokens.length ; any incrementation of this value must first call `_isAtEnd`
315
336
private _parsingErrors : string [ ] = [ ] ;
316
- private _scanner = new Scanner ( ) ;
317
337
318
338
get lexingErrors ( ) : Readonly < Token [ ] > {
319
339
return this . _scanner . errorTokens ;
@@ -327,7 +347,7 @@ export class Parser {
327
347
* Parse a context key expression.
328
348
*
329
349
* @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
331
351
*/
332
352
parse ( input : string ) : ContextKeyExpression | undefined {
333
353
@@ -366,7 +386,7 @@ export class Parser {
366
386
private _or ( ) : ContextKeyExpression | undefined {
367
387
const expr = [ this . _and ( ) ] ;
368
388
369
- while ( this . _match ( TokenType . Or ) ) {
389
+ while ( this . _matchOne ( TokenType . Or ) ) {
370
390
const right = this . _and ( ) ;
371
391
expr . push ( right ) ;
372
392
}
@@ -377,7 +397,7 @@ export class Parser {
377
397
private _and ( ) : ContextKeyExpression | undefined {
378
398
const expr = [ this . _term ( ) ] ;
379
399
380
- while ( this . _match ( TokenType . And ) ) {
400
+ while ( this . _matchOne ( TokenType . And ) ) {
381
401
const right = this . _term ( ) ;
382
402
expr . push ( right ) ;
383
403
}
@@ -386,8 +406,8 @@ export class Parser {
386
406
}
387
407
388
408
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 ) ) {
391
411
const expr = this . _previous ( ) ;
392
412
switch ( expr . type ) {
393
413
case TokenType . Str :
@@ -407,30 +427,30 @@ export class Parser {
407
427
408
428
private _primary ( ) : ContextKeyExpression | undefined {
409
429
410
- if ( this . _match ( TokenType . True ) ) {
430
+ if ( this . _matchOne ( TokenType . True ) ) {
411
431
return ContextKeyExpr . true ( ) ;
412
432
413
- } else if ( this . _match ( TokenType . False ) ) {
433
+ } else if ( this . _matchOne ( TokenType . False ) ) {
414
434
return ContextKeyExpr . false ( ) ;
415
435
416
- } else if ( this . _match ( TokenType . LParen ) ) {
436
+ } else if ( this . _matchOne ( TokenType . LParen ) ) {
417
437
const expr = this . _expr ( ) ;
418
438
this . _consume ( TokenType . RParen , `')'` ) ;
419
439
return expr ;
420
440
421
- } else if ( this . _match ( TokenType . Str ) ) {
441
+ } else if ( this . _matchOne ( TokenType . Str ) ) {
422
442
// KEY
423
443
const key = this . _previous ( ) . lexeme ! ;
424
444
425
445
// =~ regex
426
- if ( this . _match ( TokenType . RegexOp ) ) {
446
+ if ( this . _matchOne ( TokenType . RegexOp ) ) {
427
447
428
- if ( this . _match ( TokenType . RegexStr ) ) { // expected tokens
448
+ if ( this . _matchOne ( TokenType . RegexStr ) ) { // expected tokens
429
449
const regexLexeme = this . _previous ( ) . lexeme ! ; // /REGEX/ or /REGEX/FLAGS
430
450
const closingSlashIndex = regexLexeme . lastIndexOf ( '/' ) ;
431
451
const flags = closingSlashIndex === regexLexeme . length - 1 ? undefined : regexLexeme . substring ( closingSlashIndex + 1 ) ;
432
452
return ContextKeyExpr . regex ( key , new RegExp ( regexLexeme . substring ( 1 , closingSlashIndex ) , flags ) ) ;
433
- } if ( this . _match ( TokenType . QuotedStr ) ) {
453
+ } if ( this . _matchOne ( TokenType . QuotedStr ) ) {
434
454
// replicate old regex parsing behavior
435
455
436
456
const serializedValue = this . _previous ( ) . lexeme ! ;
@@ -463,14 +483,14 @@ export class Parser {
463
483
}
464
484
465
485
// [ 'not' 'in' value ]
466
- if ( this . _match ( TokenType . Not ) ) {
486
+ if ( this . _matchOne ( TokenType . Not ) ) {
467
487
this . _consume ( TokenType . In , `'in' after 'not'` ) ;
468
488
const right = this . _value ( ) ;
469
489
return ContextKeyExpr . notIn ( key , right ) ;
470
490
}
471
491
472
492
// [ ('==' | '!=' | '<' | '<=' | '>' | '>=' | '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 ) ) {
474
494
const op = this . _previous ( ) . type ;
475
495
const right = this . _value ( ) ;
476
496
switch ( op ) {
@@ -516,13 +536,13 @@ export class Parser {
516
536
}
517
537
518
538
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 ) ) {
520
540
return this . _previous ( ) . lexeme ! ;
521
- } if ( this . _match ( TokenType . True ) ) {
541
+ } if ( this . _matchOne ( TokenType . True ) ) {
522
542
return 'true' ;
523
- } if ( this . _match ( TokenType . False ) ) {
543
+ } if ( this . _matchOne ( TokenType . False ) ) {
524
544
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
526
546
return 'in' ;
527
547
} else {
528
548
return '' ; // this allows "when": "foo == " which's used by existing extensions
@@ -534,7 +554,16 @@ export class Parser {
534
554
return this . _tokens [ this . _current - 1 ] ;
535
555
}
536
556
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 [ ] ) {
538
567
for ( const token of tokens ) {
539
568
if ( this . _check ( token ) ) {
540
569
this . _advance ( ) ;
0 commit comments