context keys: scanner: support triple equal and triple not equal as double equal and double not equal respectively

This commit is contained in:
Ulugbek Abdullaev 2023-03-01 19:33:28 +01:00
parent aa8367e132
commit 1a25bd5f0f
2 changed files with 33 additions and 23 deletions

View file

@ -35,8 +35,8 @@ export type Token =
| { type: TokenType.LParen; offset: number }
| { type: TokenType.RParen; offset: number }
| { type: TokenType.Neg; offset: number }
| { type: TokenType.Eq; offset: number }
| { type: TokenType.NotEq; offset: number }
| { type: TokenType.Eq; offset: number; isTripleEq: boolean }
| { type: TokenType.NotEq; offset: number; isTripleEq: boolean }
| { type: TokenType.Lt; offset: number }
| { type: TokenType.LtEq; offset: number }
| { type: TokenType.Gt; offset: number }
@ -59,8 +59,6 @@ type TokenTypeWithoutLexeme =
TokenType.LParen |
TokenType.RParen |
TokenType.Neg |
TokenType.Eq |
TokenType.NotEq |
TokenType.Lt |
TokenType.LtEq |
TokenType.Gt |
@ -98,7 +96,6 @@ function hintDidYouMean(...meant: string[]) {
}
}
const hintDontSupportTripleEq = localize('contextkey.scanner.hint.dontSupportTripleEq', "The '===' operator is not supported. Use '==' instead.");
const hintDidYouForgetToOpenOrCloseQuote = localize('contextkey.scanner.hint.didYouForgetToOpenOrCloseQuote', "Did you forget to open or close the quote?");
const hintDidYouForgetToEscapeSlash = localize('contextkey.scanner.hint.didYouForgetToEscapeSlash', "Did you forget to escape the '/' (slash) character? Put two backslashes before it to escape, e.g., '\\\\/\'.");
@ -128,9 +125,9 @@ export class Scanner {
case TokenType.Neg:
return '!';
case TokenType.Eq:
return '==';
return token.isTripleEq ? '===' : '==';
case TokenType.NotEq:
return '!=';
return token.isTripleEq ? '!==' : '!=';
case TokenType.Lt:
return '<';
case TokenType.LtEq:
@ -209,7 +206,12 @@ export class Scanner {
case CharCode.CloseParen: this._addToken(TokenType.RParen); break;
case CharCode.ExclamationMark:
this._addToken(this._match(CharCode.Equals) ? TokenType.NotEq : TokenType.Neg);
if (this._match(CharCode.Equals)) {
const isTripleEq = this._match(CharCode.Equals); // eat last `=` if `!==`
this._tokens.push({ type: TokenType.NotEq, offset: this._start, isTripleEq });
} else {
this._addToken(TokenType.Neg);
}
break;
case CharCode.SingleQuote: this._quotedString(); break;
@ -217,15 +219,12 @@ export class Scanner {
case CharCode.Equals:
if (this._match(CharCode.Equals)) { // support `==`
this._addToken(TokenType.Eq);
const isTripleEq = this._match(CharCode.Equals); // eat last `=` if `===`
this._tokens.push({ type: TokenType.Eq, offset: this._start, isTripleEq });
} else if (this._match(CharCode.Tilde)) {
this._addToken(TokenType.RegexOp);
} else {
if (this._tokens.length === 0 || this._tokens[this._tokens.length - 1].type !== TokenType.Eq) {
this._error(hintDidYouMean('==', '=~'));
} else {
this._error(hintDontSupportTripleEq);
}
this._error(hintDidYouMean('==', '=~'));
}
break;
@ -294,7 +293,7 @@ export class Scanner {
private _error(additional?: string) {
const offset = this._start;
const lexeme = this._input.substring(this._start, this._current);
const errToken = { type: TokenType.Error, offset: this._start, lexeme };
const errToken: Token = { type: TokenType.Error, offset: this._start, lexeme };
this._errors.push({ offset, lexeme, additionalInfo: additional });
this._tokens.push(errToken);
}

View file

@ -15,9 +15,9 @@ suite('Context Key Scanner', () => {
case TokenType.Neg:
return '!';
case TokenType.Eq:
return '==';
return token.isTripleEq ? '===' : '==';
case TokenType.NotEq:
return '!=';
return token.isTripleEq ? '!==' : '!=';
case TokenType.Lt:
return '<';
case TokenType.LtEq:
@ -53,6 +53,7 @@ suite('Context Key Scanner', () => {
}
}
function scan(input: string) {
return (new Scanner()).reset(input).scan().map((token: Token) => {
return 'lexeme' in token
@ -79,6 +80,16 @@ suite('Context Key Scanner', () => {
assert.deepStrictEqual(scan(input), ([{ type: "!", offset: 0 }, { type: "Str", lexeme: "foo", offset: 1 }, { type: "EOF", offset: 4 }]));
});
test('foo === bar', () => {
const input = 'foo === bar';
assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "===", offset: 4 }, { type: "Str", offset: 8, lexeme: "bar" }, { type: "EOF", offset: 11 }]));
});
test('foo !== bar', () => {
const input = 'foo !== bar';
assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "!==", offset: 5 }, { type: "Str", offset: 9, lexeme: "bar" }, { type: "EOF", offset: 12 }]));
});
test('!(foo && bar)', () => {
const input = '!(foo && bar)';
assert.deepStrictEqual(scan(input), ([{ type: "!", offset: 0 }, { type: "(", offset: 1 }, { type: "Str", lexeme: "foo", offset: 2 }, { type: "&&", offset: 6 }, { type: "Str", lexeme: "bar", offset: 9 }, { type: ")", offset: 12 }, { type: "EOF", offset: 13 }]));
@ -181,11 +192,16 @@ suite('Context Key Scanner', () => {
});
});
test(`foo === bar'`, () => {
const input = `foo === bar'`;
assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "===", offset: 4 }, { type: "Str", offset: 8, lexeme: "bar" }, { type: "ErrorToken", offset: 11, lexeme: "'" }, { type: "EOF", offset: 12 }]));
});
suite('handling lexical errors', () => {
test(`foo === '`, () => {
const input = `foo === '`;
assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "==", offset: 4 }, { type: "ErrorToken", offset: 6, lexeme: "=" }, { type: "ErrorToken", offset: 8, lexeme: "'" }, { type: "EOF", offset: 9 }]));
assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "===", offset: 4 }, { type: "ErrorToken", offset: 8, lexeme: "'" }, { type: "EOF", offset: 9 }]));
});
test(`foo && 'bar - unterminated single quote`, () => {
@ -193,11 +209,6 @@ suite('Context Key Scanner', () => {
assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "foo", offset: 0 }, { type: "&&", offset: 4 }, { type: "ErrorToken", offset: 7, lexeme: "'bar" }, { type: "EOF", offset: 11 }]));
});
test(`foo === bar'`, () => {
const input = `foo === bar'`;
assert.deepStrictEqual(scan(input), ([{ type: "Str", offset: 0, lexeme: "foo" }, { type: "==", offset: 4 }, { type: "ErrorToken", offset: 6, lexeme: "=" }, { type: "Str", offset: 8, lexeme: "bar" }, { type: "ErrorToken", offset: 11, lexeme: "'" }, { type: "EOF", offset: 12 }]));
});
test('vim<c-r> == 1 && vim<2 <= 3', () => {
const input = 'vim<c-r> == 1 && vim<2 <= 3';
assert.deepStrictEqual(scan(input), ([{ type: "Str", lexeme: "vim<c-r>", offset: 0 }, { type: "==", offset: 9 }, { type: "Str", lexeme: "1", offset: 12 }, { type: "&&", offset: 14 }, { type: "Str", lexeme: "vim<2", offset: 17 }, { type: "<=", offset: 23 }, { type: "Str", lexeme: "3", offset: 26 }, { type: "EOF", offset: 27 }]));