pip-services4-expressions-node
Version:
Tokenizers, parsers and expression calculators in Node.js / ES2017
455 lines (417 loc) • 16.4 kB
text/typescript
/** @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;
}
}
}