UNPKG

expressionparser

Version:

Parse simple expressions, in a language of your own description

622 lines (516 loc) 15.6 kB
import "mocha"; import { expect } from "chai"; import { init, formula } from "../index"; import { ExpressionValue } from "../ExpressionParser"; const termVals: { [key: string]: number | ((...args: any) => any) } = { a: 12, b: 9, c: -3, _TEST: 42, xadd: (a, b) => a + b, xneg: (x) => -x, isEven: (x) => x % 2 == 0, }; const termTypes: { [key: string]: "number" | "function" } = { xadd: "function", xneg: "function", isEven: "function", }; const parser = init( formula, (term: string) => { if (term in termVals) { return termVals[term]; } else { throw new Error(`Invalid term: ${term}`); } }, (term: string) => { if (term in termTypes) { return termTypes[term]; } else { return "number"; } } ); const calc = (expression: string, terms?: Record<string, ExpressionValue>) => { return parser.expressionToValue(expression, terms); }; describe("Infix Simple Arithmetic", () => { it("should result in 0", () => { const result = calc("1^1 - ((1 + 1) * 2) / 4"); expect(result).to.equal(0); }); }); describe("Infix Modular Arithmetic", () => { it("should result in 3", () => { const result = calc("15 % 12"); expect(result).to.equal(3); }); it("should result in 4", () => { const result = calc("MOD(16, 12)"); expect(result).to.equal(4); }); }); describe("Quadratic Formula", () => { it("should result in -1", () => { const result = calc("(-b - sqrt(b^2 - 4 * a * c))/(2 * a)"); expect(result).to.equal(-1); }); }); describe("External Function", () => { it("should result in 2", () => { const result = calc("xadd(1,1)"); expect(result).to.equal(2); }); it("should result in 2", () => { const result = calc("xneg(-2)"); expect(result).to.equal(2); }); it("should result in [1, 2]", () => { const result = calc("map(xneg, [-1, -2])"); expect(result).to.eql([1, 2]); }); }); describe("Additional Terms", () => { it("should result in 3", () => { const result = calc("x + y", { x: 1, y: 2 }); expect(result).to.equal(3); }); it("should result in true", () => { const result = calc("x = UNDEFINED", { x: undefined }); expect(result).to.equal(true); }); }); describe("Simple Boolean Expression", () => { it("should result in true", () => { const result = calc("1 + 1 = 2"); expect(result).to.equal(true); }); it("should result in false", () => { const result = calc("!(1 + 1 = 2)"); expect(result).to.equal(false); }); }); describe("Boolean Expression", () => { it("should result in true", () => { const result = calc( "(1 = 1) AND (1 != 2) AND (1 <> 2) AND (1 < 2) AND (2 > 1) AND (1 <= 1) AND ((1 >= 1) OR FALSE) AND (PI = PI)" ); expect(result).to.equal(true); }); it("should result in true", () => { const result = calc("(1 = 1) AND ISPRIME(5)"); expect(result).to.equal(true); }); }); describe("Case Insensitive Expression", () => { it("should result in true", () => { const result = calc( "(E = E) and (LN2 != LN10) and (LOG2E <> LOG10E) and (ROUND(SQRTHALF) = ROUND(1/SQRT2)) and (TRUE != FALSE) and (LENGTH(EMPTY) = 0)" ); expect(result).to.equal(true); }); }); describe("Terminal", () => { it("should result in 42", () => { const result = calc("_TEST + 1 - 1"); expect(result).to.equal(42); }); it("should raise error", () => { expect(() => { calc("_TEST + _INVALID"); }).to.throw("Invalid term"); }); }); describe("Grouping", () => { it("should raise error", () => { expect(() => { calc("(TRUE AND FALSE"); }).to.throw('Mismatched Grouping (unexpected "(")'); }); it("should raise error", () => { expect(() => { calc(")TRUE AND FALSE"); }).to.throw('Mismatched Grouping (unexpected closing ")")'); }); it("should raise error", () => { expect(() => { calc("((TRUE) AND (FALSE)"); }).to.throw('Mismatched Grouping (unexpected "(")'); }); it("should result in false", () => { expect(calc("((TRUE) AND (FALSE))")).to.equal(false); }); }); describe("Calls and Arrays", () => { it("should result in 4", () => { const result = calc("GCD(8, 12)"); expect(result).to.equal(4); }); it("should result in 5", () => { const result = calc("AVERAGE([4,5,6])"); expect(result).to.equal(5); }); it("should result in 5", () => { const result = calc("SUM(SORT(REVERSE([1,2,2])))"); expect(result).to.equal(5); }); it('should result in ABCDEFG"', () => { const result = calc('STRING(MAP("UPPER", CHARARRAY("abcdefg\\"")))'); expect(result).to.equal('ABCDEFG"'); }); it("should result in [true, false, true]", () => { const result = calc('MAP("NOT", [FALSE, TRUE, FALSE])'); expect(result).to.eql([true, false, true]); }); it("should result in 6", () => { const result = calc('REDUCE("ADD", 0, [1, 2, 3])'); expect(result).to.equal(6); }); it("should result in -6", () => { const result = calc('REDUCE("SUB", 0, [3, 2, 1])'); expect(result).to.equal(-6); }); it("should result in 25", () => { const result = calc('REDUCE("DIV", 100, [2, 2, 1])'); expect(result).to.equal(25); }); it("should result in 4", () => { const result = calc('REDUCE("MUL", 1, [2, 2, 1])'); expect(result).to.equal(4); }); it("should result in [ 97, 98, 99, 100, 101, 102, 103 ]", () => { const result = calc('MAP("CODE", CHARARRAY("abcdefg"))'); expect(result).to.eql([97, 98, 99, 100, 101, 102, 103]); }); it("should result in 700", () => { const result = calc('REDUCE("+", 0, MAP("CODE", CHARARRAY("abcdefg")))'); expect(result).to.equal(700); }); it("should throw error", () => { expect(() => { calc('REDUCE("_TEST_", 0, [1, 2, 3])'); }).to.throw("Unknown function: _TEST_"); }); }); describe("More Functions", () => { it("should result in 10", () => { const result = calc("IF(7 < 5, 8, 10)"); expect(result).to.equal(10); }); it("should result in 8", () => { const result = calc("IF(7 > 5, 8, 10)"); expect(result).to.equal(8); }); it("should result in 8", () => { expect(() => { calc("IF(7 > 5, 8)"); }).to.throw("Incorrect number of arguments. Expected 3"); }); it("should result in 10", () => { const result = calc("LENGTH(RANGE(0, 10))"); expect(result).to.equal(10); }); // optional arguments it("should result in 10", () => { const result = calc("LENGTH(RANGE(10))"); expect(result).to.equal(10); }); // different typed arguments it("should result in 3", () => { const result = calc("RANGE([1, 2, 3, 4])"); expect(result).to.equal(3); }); it("should result in 'A'", () => { const result = calc("CHAR(65)"); expect(result).to.equal("A"); }); it("should result in 5", () => { const result = calc("MIN([5, 6, 7, 8])"); expect(result).to.equal(5); }); it("should result in 5", () => { const result = calc("MAX([5, 4, 3, 2])"); expect(result).to.equal(5); }); it("should result in 5", () => { const result = calc("INDEX([5, 4, 3, 2], 0)"); expect(result).to.equal(5); }); it('should result in "a,b"', () => { const result = calc('JOIN(",", ["a", "b"])'); expect(result).to.equal("a,b"); }); it("should result in ['a', 'b']", () => { const result = calc('SPLIT(",", "a,b")'); expect(result).to.eql(["a", "b"]); }); }); describe("Array Functions", () => { it("should result in [[1, 2], [3, 4]]", () => { const result = calc("ZIP([1, 3], [2, 4])"); expect(result).to.eql([ [1, 2], [3, 4], ]); }); it("should result in [1, 3], [2, 4]", () => { const result = calc("UNZIP([[1, 2], [3, 4]])"); expect(result).to.eql([ [1, 3], [2, 4], ]); }); it("should result in [42, 69]", () => { const result = calc("TAKE(2, [42, 69, 54])"); expect(result).to.eql([42, 69]); }); it("should result in [69, 54]", () => { const result = calc("DROP(2, [1, 42, 69, 54])"); expect(result).to.eql([69, 54]); }); it("should result in [42, 69]", () => { const result = calc("SLICE(1, 3, [1, 42, 69, 54])"); expect(result).to.eql([42, 69]); }); it("should result in [42, 69, 54]", () => { const result = calc("CONCAT([42, 69], [54])"); expect(result).to.eql([42, 69, 54]); }); it("should result in 42", () => { const result = calc("HEAD([42, 69, 54])"); expect(result).to.equal(42); }); it("should result in [69, 54]", () => { const result = calc("TAIL([42, 69, 54])"); expect(result).to.eql([69, 54]); }); it("should result in 54", () => { const result = calc("LAST([42, 69, 54])"); expect(result).to.equal(54); }); it("should result in [2,3,4]", () => { const result = calc("CONS(2, [3, 4])"); expect(result).to.eql([2, 3, 4]); }); it("should result in [2,4,6]", () => { const result = calc("FILTER(isEven, [1,2,3,4,5,6])"); expect(result).to.eql([2, 4, 6]); }); it("should result in [0,2,4]", () => { const result = calc("TAKEWHILE(isEven, [0,2,4,5,6,7,8])"); expect(result).to.eql([0, 2, 4]); }); it("should result in [5,6,7,8]", () => { const result = calc("DROPWHILE(isEven, [0,2,4,5,6,7,8])"); expect(result).to.eql([5, 6, 7, 8]); }); }); describe("Dictionaries", () => { it("should result in 5", () => { const result = calc('GET("b", DICT(["a", "b"], [1, 5]))'); expect(result).to.equal(5); }); it("should result in 5", () => { const result = calc('GET("b", PUT("b", 5, DICT(["a", "b"], [1, 4])))'); expect(result).to.equal(5); }); it("should result in 5", () => { const result = calc('GET("b", UNZIPDICT([["a", 1], ["b", 5]]))'); expect(result).to.equal(5); }); it('should result in ["a", "b"]', () => { const result = calc('KEYS(UNZIPDICT([["b", 1], ["a", 5]]))'); expect(result).to.eql(["a", "b"]); }); it("should result in [5, 1]", () => { const result = calc('VALUES(UNZIPDICT([["b", 1], ["a", 5]]))'); expect(result).to.eql([5, 1]); }); }); describe("Maths", () => { it("should be false", () => { const result = calc("ISNAN(1/0)"); expect(result).to.equal(false); }); it("should be true", () => { const result = calc("(1/0) = INFINITY"); expect(result).to.equal(true); }); it("should be false", () => { const result = calc("ISNAN(0)"); expect(result).to.equal(false); }); it("should be -1", () => { const result = calc("(-1)"); expect(result).to.equal(-1); }); it("should be true", () => { const result = calc("isNaN(sqrt(-1))"); expect(result).to.equal(true); }); it("should be false", () => { const result = calc("isNaN(sqrt(abs(-1)))"); expect(result).to.equal(false); }); it("should be true", () => { const result = calc("TAN(ATAN(1)) ~= 1"); expect(result).to.equal(true); }); it("should be true", () => { const result = calc("COS(ACOS(1)) ~= 1"); expect(result).to.equal(true); }); it("should be true", () => { const result = calc("SIN(ASIN(1)) ~= 1"); expect(result).to.equal(true); }); it("should be true", () => { const result = calc("TANH(ATANH(1)) ~= 1"); expect(result).to.equal(true); }); it("should be true", () => { const result = calc("COSH(ACOSH(1)) ~= 1"); expect(result).to.equal(true); }); it("should be true", () => { const result = calc("SINH(ASINH(1)) ~= 1"); expect(result).to.equal(true); }); it("should result in PI/4", () => { const result = calc("ATAN2(2,2)"); expect(result).to.equal(Math.PI / 4); }); it("should result in 1", () => { const result = calc("LN(E)"); expect(result).to.equal(1); }); it("should result in 1", () => { const result = calc("LOG(10)"); expect(result).to.equal(1); }); it("should result in 1", () => { const result = calc("LOG2(2)"); expect(result).to.equal(1); }); it("should result in 1", () => { const result = calc("FLOOR(1.9)"); expect(result).to.equal(1); }); it("should result in 1", () => { const result = calc("CEIL(0.1)"); expect(result).to.equal(1); }); it("should result in 1", () => { const result = calc("ROUND(0.6)"); expect(result).to.equal(1); }); it("should result in 1", () => { const result = calc("ROUND(1.1)"); expect(result).to.equal(1); }); it("should result in 1", () => { const result = calc("TRUNC(1.9)"); expect(result).to.equal(1); }); it("should result in 1", () => { const result = calc("SIGN(5)"); expect(result).to.equal(1); }); it("should result in -1", () => { const result = calc("FLOOR(-0.1)"); expect(result).to.equal(-1); }); it("should result in -1", () => { const result = calc("CEIL(-1.1)"); expect(result).to.equal(-1); }); it("should result in -1", () => { const result = calc("ROUND(-0.6)"); expect(result).to.equal(-1); }); it("should result in -1", () => { const result = calc("ROUND(-1.1)"); expect(result).to.equal(-1); }); it("should result in -1", () => { const result = calc("TRUNC(-1.9)"); expect(result).to.equal(-1); }); it("should result in -1", () => { const result = calc("SIGN(-5)"); expect(result).to.equal(-1); }); it("should be true", () => { const result = calc("EXP(1) = E"); expect(result).to.equal(true); }); it("should be true", () => { const result = calc("CUBEROOT(27) = 3"); expect(result).to.equal(true); }); it("should result in 90", () => { const result = calc("DEGREES(RADIANS(90))"); expect(result).to.equal(90); }); it("should result in '100'", () => { const result = calc("DEC2BIN(4)"); expect(result).to.equal("100"); }); it("should result in 4", () => { const result = calc('BIN2DEC("100")'); expect(result).to.equal(4); }); it("should result in 'f'", () => { const result = calc("LOWER(UPPER(DEC2HEX(15)))"); expect(result).to.equal("f"); }); it("should result in 16", () => { const result = calc('HEX2DEC("F")'); expect(result).to.equal(15); }); it("should be true", () => { const result = calc("0.99999999999999999 + EPSILON > 1"); expect(result).to.equal(true); }); }); describe("Exceptions", () => { it("should throw 'Too few arguments'", () => { expect(() => { calc("GCD(2)"); }).to.throw("Too few arguments. Expected 2, found 1 (2)"); }); it("should throw 'Expected number'", () => { expect(() => { calc('add("A", "B")'); }).to.throw("Expected number, found: string"); }); it("should throw 'Expected array'", () => { expect(() => { calc('sort("ABC")'); }).to.throw("Expected array, found: string"); }); it("should throw 'Expected array or string'", () => { expect(() => { calc("index(1, 1)"); }).to.throw("Expected array or string, found: number"); }); it("should throw 'Expected string'", () => { expect(() => { calc("BIN2DEC(10)"); }).to.throw("Expected string, found: number"); }); it("should throw 'Expected char'", () => { expect(() => { calc('CODE("FOO")'); }).to.throw("Expected char, found: string"); }); }); describe("Tokenization of adjacent symbols", () => { it("should be the same", () => { const a = parser.tokenize("RANGE(-5,-1)"); const b = parser.tokenize("RANGE(-5, -1)"); expect(a).to.eql(b); }); });