private _primary()

in patched-vscode/src/vs/platform/contextkey/common/contextkey.ts [286:499]


	private _primary(): ContextKeyExpression | undefined {

		const peek = this._peek();
		switch (peek.type) {
			case TokenType.True:
				this._advance();
				return ContextKeyExpr.true();

			case TokenType.False:
				this._advance();
				return ContextKeyExpr.false();

			case TokenType.LParen: {
				this._advance();
				const expr = this._expr();
				this._consume(TokenType.RParen, errorClosingParenthesis);
				return expr;
			}

			case TokenType.Str: {
				// KEY
				const key = peek.lexeme;
				this._advance();

				// =~ regex
				if (this._matchOne(TokenType.RegexOp)) {

					// @ulugbekna: we need to reconstruct the regex from the tokens because some extensions use unescaped slashes in regexes
					const expr = this._peek();

					if (!this._config.regexParsingWithErrorRecovery) {
						this._advance();
						if (expr.type !== TokenType.RegexStr) {
							throw this._errExpectedButGot(`REGEX`, expr);
						}
						const regexLexeme = expr.lexeme;
						const closingSlashIndex = regexLexeme.lastIndexOf('/');
						const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : this._removeFlagsGY(regexLexeme.substring(closingSlashIndex + 1));
						let regexp: RegExp | null;
						try {
							regexp = new RegExp(regexLexeme.substring(1, closingSlashIndex), flags);
						} catch (e) {
							throw this._errExpectedButGot(`REGEX`, expr);
						}
						return ContextKeyRegexExpr.create(key, regexp);
					}

					switch (expr.type) {
						case TokenType.RegexStr:
						case TokenType.Error: { // also handle an ErrorToken in case of smth such as /(/file)/
							const lexemeReconstruction = [expr.lexeme]; // /REGEX/ or /REGEX/FLAGS
							this._advance();

							let followingToken = this._peek();
							let parenBalance = 0;
							for (let i = 0; i < expr.lexeme.length; i++) {
								if (expr.lexeme.charCodeAt(i) === CharCode.OpenParen) {
									parenBalance++;
								} else if (expr.lexeme.charCodeAt(i) === CharCode.CloseParen) {
									parenBalance--;
								}
							}

							while (!this._isAtEnd() && followingToken.type !== TokenType.And && followingToken.type !== TokenType.Or) {
								switch (followingToken.type) {
									case TokenType.LParen:
										parenBalance++;
										break;
									case TokenType.RParen:
										parenBalance--;
										break;
									case TokenType.RegexStr:
									case TokenType.QuotedStr:
										for (let i = 0; i < followingToken.lexeme.length; i++) {
											if (followingToken.lexeme.charCodeAt(i) === CharCode.OpenParen) {
												parenBalance++;
											} else if (expr.lexeme.charCodeAt(i) === CharCode.CloseParen) {
												parenBalance--;
											}
										}
								}
								if (parenBalance < 0) {
									break;
								}
								lexemeReconstruction.push(Scanner.getLexeme(followingToken));
								this._advance();
								followingToken = this._peek();
							}

							const regexLexeme = lexemeReconstruction.join('');
							const closingSlashIndex = regexLexeme.lastIndexOf('/');
							const flags = closingSlashIndex === regexLexeme.length - 1 ? undefined : this._removeFlagsGY(regexLexeme.substring(closingSlashIndex + 1));
							let regexp: RegExp | null;
							try {
								regexp = new RegExp(regexLexeme.substring(1, closingSlashIndex), flags);
							} catch (e) {
								throw this._errExpectedButGot(`REGEX`, expr);
							}
							return ContextKeyExpr.regex(key, regexp);
						}

						case TokenType.QuotedStr: {
							const serializedValue = expr.lexeme;
							this._advance();
							// replicate old regex parsing behavior

							let regex: RegExp | null = null;

							if (!isFalsyOrWhitespace(serializedValue)) {
								const start = serializedValue.indexOf('/');
								const end = serializedValue.lastIndexOf('/');
								if (start !== end && start >= 0) {

									const value = serializedValue.slice(start + 1, end);
									const caseIgnoreFlag = serializedValue[end + 1] === 'i' ? 'i' : '';
									try {
										regex = new RegExp(value, caseIgnoreFlag);
									} catch (_e) {
										throw this._errExpectedButGot(`REGEX`, expr);
									}
								}
							}

							if (regex === null) {
								throw this._errExpectedButGot('REGEX', expr);
							}

							return ContextKeyRegexExpr.create(key, regex);
						}

						default:
							throw this._errExpectedButGot('REGEX', this._peek());
					}
				}

				// [ 'not' 'in' value ]
				if (this._matchOne(TokenType.Not)) {
					this._consume(TokenType.In, errorNoInAfterNot);
					const right = this._value();
					return ContextKeyExpr.notIn(key, right);
				}

				// [ ('==' | '!=' | '<' | '<=' | '>' | '>=' | 'in') value ]
				const maybeOp = this._peek().type;
				switch (maybeOp) {
					case TokenType.Eq: {
						this._advance();

						const right = this._value();
						if (this._previous().type === TokenType.QuotedStr) { // to preserve old parser behavior: "foo == 'true'" is preserved as "foo == 'true'", but "foo == true" is optimized as "foo"
							return ContextKeyExpr.equals(key, right);
						}
						switch (right) {
							case 'true':
								return ContextKeyExpr.has(key);
							case 'false':
								return ContextKeyExpr.not(key);
							default:
								return ContextKeyExpr.equals(key, right);
						}
					}

					case TokenType.NotEq: {
						this._advance();

						const right = this._value();
						if (this._previous().type === TokenType.QuotedStr) { // same as above with "foo != 'true'"
							return ContextKeyExpr.notEquals(key, right);
						}
						switch (right) {
							case 'true':
								return ContextKeyExpr.not(key);
							case 'false':
								return ContextKeyExpr.has(key);
							default:
								return ContextKeyExpr.notEquals(key, right);
						}
					}
					// TODO: ContextKeyExpr.smaller(key, right) accepts only `number` as `right` AND during eval of this node, we just eval to `false` if `right` is not a number
					// consequently, package.json linter should _warn_ the user if they're passing undesired things to ops
					case TokenType.Lt:
						this._advance();
						return ContextKeySmallerExpr.create(key, this._value());

					case TokenType.LtEq:
						this._advance();
						return ContextKeySmallerEqualsExpr.create(key, this._value());

					case TokenType.Gt:
						this._advance();
						return ContextKeyGreaterExpr.create(key, this._value());

					case TokenType.GtEq:
						this._advance();
						return ContextKeyGreaterEqualsExpr.create(key, this._value());

					case TokenType.In:
						this._advance();
						return ContextKeyExpr.in(key, this._value());

					default:
						return ContextKeyExpr.has(key);
				}
			}

			case TokenType.EOF:
				this._parsingErrors.push({ message: errorUnexpectedEOF, offset: peek.offset, lexeme: '', additionalInfo: hintUnexpectedEOF });
				throw Parser._parseError;

			default:
				throw this._errExpectedButGot(`true | false | KEY \n\t| KEY '=~' REGEX \n\t| KEY ('==' | '!=' | '<' | '<=' | '>' | '>=' | 'in' | 'not' 'in') value`, this._peek());

		}
	}