import { ContextKeyExpr, ContextKeyExpression, IContext } from './contextkey/contextkey'; // We would like to support expressions with brackets but VSCode When Clauses // don't support this. To support this, we split the expressions with brackets // into sub-expressions, which can then be parsed and executed separately by the // When Clause library. interface AdvancedExpression { // (test1 && test2) || test3 original: string; // __sub_1 || test3 compiledText: string; // { __sub_1: "test1 && test2" } subExpressions: any; } function parseAdvancedExpression(advancedExpression: string): AdvancedExpression { let subExpressionIndex = -1; let subExpressions: string = ''; let currentSubExpressionKey = ''; const subContext: any = {}; let inBrackets = false; for (let i = 0; i < advancedExpression.length; i++) { const c = advancedExpression[i]; if (c === '(') { if (inBrackets) throw new Error('Nested brackets not supported'); inBrackets = true; subExpressionIndex++; currentSubExpressionKey = `__sub_${subExpressionIndex}`; subContext[currentSubExpressionKey] = ''; continue; } if (c === ')') { if (!inBrackets) throw new Error('Closing bracket without an opening one'); inBrackets = false; subExpressions += currentSubExpressionKey; currentSubExpressionKey = ''; continue; } if (inBrackets) { subContext[currentSubExpressionKey] += c; } else { subExpressions += c; } } return { compiledText: subExpressions, subExpressions: subContext, original: advancedExpression, }; } export default class WhenClause { private expression_: AdvancedExpression; private validate_: boolean; private ruleCache_: Record = {}; public constructor(expression: string, validate: boolean = true) { this.expression_ = parseAdvancedExpression(expression); this.validate_ = validate; } private createContext(ctx: any): IContext { return { getValue: (key: string) => { return ctx[key]; }, }; } private rules(exp: string): ContextKeyExpression { if (this.ruleCache_[exp]) return this.ruleCache_[exp]; this.ruleCache_[exp] = ContextKeyExpr.deserialize(exp); return this.ruleCache_[exp]; } public evaluate(context: any): boolean { if (this.validate_) this.validate(context); const subContext: any = {}; for (const k in this.expression_.subExpressions) { const subExp = this.expression_.subExpressions[k]; subContext[k] = this.rules(subExp).evaluate(this.createContext(context)); } const fullContext = { ...context, ...subContext }; return this.rules(this.expression_.compiledText).evaluate(this.createContext(fullContext)); } public validate(context: any) { const keys = this.rules(this.expression_.original.replace(/[()]/g, ' ')).keys(); for (const key of keys) { if (!(key in context)) throw new Error(`No such key: ${key}`); } } }