Skip to content

Latest commit

 

History

History
228 lines (200 loc) · 5.44 KB

README.md

File metadata and controls

228 lines (200 loc) · 5.44 KB

expression-parser Build

An expression evaluator written in typescript with the goal to support SQL like WHERE clauses.

Installation

npm install --save @abdullah2993/expression-parser

Supported Operations and Functions

Arithmetics

  • +
  • -
  • *
  • /

Comparison

  • =
  • <>
  • >
  • >=
  • <=

Logical

  • and
  • or

SQL

  • IS NULL
  • IS NOT NULL
  • BETWEEN [NUMBER] AND [NUMBER]
  • CASE WHEN expression THEN expression [WHEN expression] [ELSE expression] END

Functions

Currently only function that is evaluated is the LENGTH(variable) function but supports function calls at the parser level so it is fairly easy to add more SQL functions.

Examples

/** Basic Math **/
evaluate('1+2'));
// 3
evaluate('10 + 2 * 6'));
// 22
evaluate('100 * 2 + 12'));
// 212
evaluate('100 * ( 2 + 12 )'));
// 1400
evaluate('100 * ( 2 + 12 ) / 14'));
// 100


/** Basic Math With Variable Substitution**/

const context = {
  a: 1, b: 2, c: 10, d: 6, e: 100, f: 12, g: 14,
};

evaluateObject('a+b', context));
// 3
evaluateObject('c + b * d', context));
// 22
evaluateObject('e * b + f', context));
// 212
evaluateObject('e * ( b + f )', context));
// 1400
evaluateObject('e * ( b + f ) / g', context));
// 100

/** Other Operations **/
const val: any = { a: null };
evaluateObject('a is not null and length(a) between 4 and 10', val);
// false
const val = { a: '123' };
evaluateObject('a is not null and length(a) between 4 and 10', val);
// false
const val = { a: '1234' };
evaluateObject('a is not null and length(a) between 4 and 10', val);
// true
const val = { a: '1234567890' };
evaluateObject('a is not null and length(a) between 4 and 10', val);
// true
const val = { a: '12345678901' };
evaluateObject('a is not null and length(a) between 4 and 10', val);
// false
const val: any = { a: 1, b: 1 };
evaluateObject('a is not null and b is not null', val);
// true
evaluateObject('a is not null and b is null', val);
// false
const val = { a: 1, b: null };
evaluateObject('a is not null and b is null', val);
// true
const val = { a: 1, b: 1 };
evaluateObject('a=b', val));
// true
evaluateObject('a>b', val));
// false
evaluateObject('a<b', val));
// false
const val = { a: 1, b: 2 };
evaluateObject('a=b', val);
// false
evaluateObject('a>b', val);
// false
evaluateObject('a<b', val);
// true

const val = { a: 1, b: 3 };
evaluateObject('case when a > 1 then true else false end', val);
// false
const val = { a: 2, b: 3 };
evaluateObject('case when a > 1 then true else false end', val);
// true
val = { a: 2, b: 3 };
evaluateObject('case when a = 1 then 1 when a = 2 then 1 else false end', val);
// 1
evaluateObject('case when a = 1 then 1 when a = 2 then 2 else 3 end', val);
// 2
evaluateObject('case when a = 1 then 1 when a = 3 then 3 else 2 end', val);
// 2
const rule = `case
                when light = 'Green' then 'Go'
                when light = 'Yellow' then 'Should Stop'
                when light = 'Red' then 'Stop'
                else 'Invalid State' end`;
val = { light: 'Yellow' };
evaluateObject(rule, val))
// 'Should Stop'
val = { light: 'Green' };
evaluateObject(rule, val))
// 'Go'
val = { light: 'Red' };
evaluateObject(rule, val))
// 'Stop'
val = { light: 'Blue' };
evaluateObject(rule, val))
// 'Invalid State'

Sample AST

//Expression: a is not null and length(a) between 4 and 10
{
  "operator": "and",
  "left": {
    "operator": "<>",
    "left": {
      "name": "a",
      "type": "IdentifierExpression"
    },
    "right": {
      "value": null,
      "type": "ValueExpression"
    },
    "type": "BinaryExpression"
  },
  "right": {
    "operator": "and",
    "left": {
      "operator": ">=",
      "left": {
        "name": "length",
        "args": [
          {
            "name": "a",
            "type": "IdentifierExpression"
          }
        ],
        "type": "FunctionCallExpression"
      },
      "right": {
        "value": 4,
        "type": "ValueExpression"
      },
      "type": "BinaryExpression"
    },
    "right": {
      "operator": "<=",
      "left": {
        "name": "length",
        "args": [
          {
            "name": "a",
            "type": "IdentifierExpression"
          }
        ],
        "type": "FunctionCallExpression"
      },
      "right": {
        "value": 10,
        "type": "ValueExpression"
      },
      "type": "BinaryExpression"
    },
    "type": "BinaryExpression"
  },
  "type": "BinaryExpression"
}

Test Coverage

13 specs, 0 failures
Finished in 0.069 seconds
Randomized with seed 62175 (jasmine --random=true --seed=62175)
--------------|---------|----------|---------|---------|-----------------------------------
File          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-----------------------------------
All files     |   93.24 |    83.33 |   92.73 |   93.19 |
 ast.ts       |   89.66 |      100 |      90 |      90 | 16-18
 evaluator.ts |      85 |    69.23 |     100 |   84.21 | 14,18,27,47-49,64
 lexer.ts     |   99.04 |       98 |     100 |   99.03 | 86
 parser.ts    |   89.89 |       70 |   89.47 |   89.41 | 83,91,103,121,133,137,144,181,184
 token.ts     |   97.06 |      100 |      75 |   96.97 | 44
--------------|---------|----------|---------|---------|-----------------------------------