UNPKG

pip-services4-expressions-node

Version:

Tokenizers, parsers and expression calculators in Node.js / ES2017

455 lines (417 loc) 16.4 kB
/** @module calculator */ import { IVariableCollection } from "./variables/IVariableCollection"; import { VariableCollection } from "./variables/VariableCollection"; import { Variable } from "./variables/Variable"; import { IFunctionCollection } from "./functions/IFunctionCollection"; import { DefaultFunctionCollection } from "./functions/DefaultFunctionCollection"; import { ExpressionParser } from "./parsers/ExpressionParser"; import { ExpressionToken } from "./parsers/ExpressionToken"; import { ExpressionTokenType } from "./parsers/ExpressionTokenType"; import { IVariantOperations } from "../variants/IVariantOperations"; import { TypeUnsafeVariantOperations } from "../variants/TypeUnsafeVariantOperations"; import { Token } from "../tokenizers/Token"; import { Variant } from "../variants/Variant"; import { CalculationStack } from "./CalculationStack"; import { ExpressionException } from "./ExpressionException"; /** * Implements an expression calculator class. */ export class ExpressionCalculator { private _defaultVariables: IVariableCollection = new VariableCollection(); private _defaultFunctions: IFunctionCollection = new DefaultFunctionCollection(); private _variantOperations: IVariantOperations = new TypeUnsafeVariantOperations(); private _parser: ExpressionParser = new ExpressionParser(); private _autoVariables = true; /** * Constructs this class and assigns expression string. * @param expression The expression string. */ public constructor(expression?: string) { if (expression != null) { this.expression = expression; } } /** * The expression string. */ public get expression(): string { return this._parser.expression; } /** * The expression string. */ public set expression(value: string) { this._parser.expression = value; if (this._autoVariables) { this.createVariables(this._defaultVariables); } } public get originalTokens(): Token[] { return this._parser.originalTokens; } public set originalTokens(value: Token[]) { this._parser.originalTokens = value; if (this._autoVariables) { this.createVariables(this._defaultVariables); } } /** * Gets the flag to turn on auto creation of variables for specified expression. */ public get autoVariables(): boolean { return this._autoVariables; } /** * Sets the flag to turn on auto creation of variables for specified expression. */ public set autoVariables(value: boolean) { this._autoVariables = value; } /** * Gets the manager for operations on variant values. */ public get variantOperations(): IVariantOperations { return this._variantOperations; } /** * Sets the manager for operations on variant values. */ public set variantOperations(value: IVariantOperations) { this._variantOperations = value; } /** * The list with default variables. */ public get defaultVariables(): IVariableCollection { return this._defaultVariables; } /** * The list with default functions. */ public get defaultFunctions(): IFunctionCollection { return this._defaultFunctions; } /** * The list of original expression tokens. */ public get initialTokens(): ExpressionToken[] { return this._parser.initialTokens; } /** * The list of processed expression tokens. */ public get resultTokens(): ExpressionToken[] { return this._parser.resultTokens; } /** * Populates the specified variables list with variables from parsed expression. * @param variables The list of variables to be populated. */ public createVariables(variables: IVariableCollection): void { for (const variableName of this._parser.variableNames) { if (variables.findByName(variableName) == null) { variables.add(new Variable(variableName)); } } } /** * Cleans up this calculator from all data. */ public clear(): void { this._parser.clear(); this._defaultVariables.clear(); } /** * Evaluates this expression using default variables and functions. * @returns the evaluation result. */ public async evaluate(): Promise<Variant> { return await this.evaluateWithVariablesAndFunctions(null, null); } /** * Evaluates this expression using specified variables. * @param variables The list of variables * @returns the evaluation result */ public async evaluateWithVariables(variables: IVariableCollection): Promise<Variant> { return await this.evaluateWithVariablesAndFunctions(variables, null); } /** * Evaluates this expression using specified variables and functions. * @param variables The list of variables * @param functions The list of functions * @returns the evaluation result */ public async evaluateWithVariablesAndFunctions(variables: IVariableCollection, functions: IFunctionCollection): Promise<Variant> { const stack = new CalculationStack(); variables = variables || this._defaultVariables; functions = functions || this._defaultFunctions; for (const token of this.resultTokens) { if (await this.evaluateConstant(token, stack)) { continue; } if (await this.evaluateVariable(token, stack, variables)) { continue; } if (await this.evaluateFunction(token, stack, functions)) { continue; } if (await this.evaluateLogical(token, stack)) { continue; } if (await this.evaluateArithmetical(token, stack)) { continue; } if (await this.evaluateBoolean(token, stack)) { continue; } if (await this.evaluateOther(token, stack)) { continue; } throw new ExpressionException(null, "INTERNAL", "Internal error", token.line, token.column); } if (stack.length != 1) { throw new ExpressionException(null, "INTERNAL", "Internal error", 0, 0); } return stack.pop(); } private async evaluateConstant(token: ExpressionToken, stack: CalculationStack): Promise<boolean> { if (token.type != ExpressionTokenType.Constant) { return false; } stack.push(token.value); return true; } private async evaluateVariable(token: ExpressionToken, stack: CalculationStack, variables: IVariableCollection): Promise<boolean> { if (token.type != ExpressionTokenType.Variable) { return false; } const variable = variables.findByName(token.value.asString); if (variable == null) { throw new ExpressionException( null, "VAR_NOT_FOUND", "Variable " + token.value.asString + " was not found", token.line, token.column ); } stack.push(variable.value); return true; } private async evaluateFunction(token: ExpressionToken, stack: CalculationStack, functions: IFunctionCollection): Promise<boolean> { if (token.type != ExpressionTokenType.Function) { return false; } const func = functions.findByName(token.value.asString); if (func == null) { throw new ExpressionException( null, "FUNC_NOT_FOUND", "Function " + token.value.asString + " was not found", token.line, token.column ); } // Retrieve function parameters const params: Variant[] = []; let paramCount = stack.pop().asInteger; while (paramCount > 0) { params.splice(0, 0, stack.pop()); paramCount--; } const functionResult = await func.calculate(params, this._variantOperations); stack.push(functionResult); return true; } private async evaluateLogical(token: ExpressionToken, stack: CalculationStack): Promise<boolean> { switch (token.type) { case ExpressionTokenType.And: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.and(value1, value2)); return true; } case ExpressionTokenType.Or: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.or(value1, value2)); return true; } case ExpressionTokenType.Xor: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.xor(value1, value2)); return true; } case ExpressionTokenType.Not: { stack.push(this._variantOperations.not(stack.pop())); return true; } default: return false; } } private async evaluateArithmetical(token: ExpressionToken, stack: CalculationStack): Promise<boolean> { switch (token.type) { case ExpressionTokenType.Plus: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.add(value1, value2)); return true; } case ExpressionTokenType.Minus: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.sub(value1, value2)); return true; } case ExpressionTokenType.Star: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.mul(value1, value2)); return true; } case ExpressionTokenType.Slash: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.div(value1, value2)); return true; } case ExpressionTokenType.Procent: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.mod(value1, value2)); return true; } case ExpressionTokenType.Power: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.pow(value1, value2)); return true; } case ExpressionTokenType.Unary: { stack.push(this._variantOperations.negative(stack.pop())); return true; } case ExpressionTokenType.ShiftLeft: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.lsh(value1, value2)); return true; } case ExpressionTokenType.ShiftRight: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.rsh(value1, value2)); return true; } default: return false; } } private async evaluateBoolean(token: ExpressionToken, stack: CalculationStack): Promise<boolean> { switch (token.type) { case ExpressionTokenType.Equal: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.equal(value1, value2)); return true; } case ExpressionTokenType.NotEqual: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.notEqual(value1, value2)); return true; } case ExpressionTokenType.More: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.more(value1, value2)); return true; } case ExpressionTokenType.Less: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.less(value1, value2)); return true; } case ExpressionTokenType.EqualMore: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.moreEqual(value1, value2)); return true; } case ExpressionTokenType.EqualLess: { const value2 = stack.pop(); const value1 = stack.pop(); stack.push(this._variantOperations.lessEqual(value1, value2)); return true; } default: return false; } } private async evaluateOther(token: ExpressionToken, stack: CalculationStack): Promise<boolean> { switch (token.type) { case ExpressionTokenType.In: { const value2 = stack.pop(); const value1 = stack.pop(); const rvalue = this._variantOperations.in(value2, value1); stack.push(rvalue); return true; } case ExpressionTokenType.NotIn: { const value2 = stack.pop(); const value1 = stack.pop(); let rvalue = this._variantOperations.in(value2, value1) rvalue = Variant.fromBoolean(!rvalue.asBoolean); stack.push(rvalue); return true; } case ExpressionTokenType.Element: { const value2 = stack.pop(); const value1 = stack.pop(); const rvalue = this._variantOperations.getElement(value1, value2); stack.push(rvalue); return true; } case ExpressionTokenType.IsNull: { const rvalue = new Variant(stack.pop().isNull()); stack.push(rvalue); return true; } case ExpressionTokenType.IsNotNull: { const rvalue = new Variant(!stack.pop().isNull()); stack.push(rvalue); return true; } default: return false; } } }