Finished expr-eval testing
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Adsooi 2024-10-13 00:33:22 +02:00
parent edf4518494
commit 3a81441d0b
Signed by: Ad5001
GPG key ID: EF45F9C6AFE20160
5 changed files with 260 additions and 132 deletions

View file

@ -111,7 +111,7 @@ function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
* In the given instructions, replaces variable by expr. * In the given instructions, replaces variable by expr.
* @param {Instruction[]} tokens * @param {Instruction[]} tokens
* @param {string} variable * @param {string} variable
* @param {number} expr * @param {ExprEvalExpression} expr
* @return {Instruction[]} * @return {Instruction[]}
*/ */
function substitute(tokens, variable, expr) { function substitute(tokens, variable, expr) {
@ -487,18 +487,6 @@ export class ExprEvalExpression {
return evaluate(this.tokens, this, values) return evaluate(this.tokens, this, values)
} }
/**
* Returns a list of symbols (string of characters) in the expressions.
* Can be functions, constants, or variables.
* @returns {string[]}
*/
symbols(options) {
options = options || {}
const vars = []
getSymbols(this.tokens, vars, options)
return vars
}
toString() { toString() {
return expressionToString(this.tokens, false) return expressionToString(this.tokens, false)
} }

View file

@ -120,18 +120,13 @@ export class Parser {
min: Polyfill.min, min: Polyfill.min,
max: Polyfill.max, max: Polyfill.max,
hypot: Math.hypot || Polyfill.hypot, hypot: Math.hypot || Polyfill.hypot,
pyt: Math.hypot || Polyfill.hypot, // backward compat pyt: Math.hypot || Polyfill.hypot,
pow: Math.pow, pow: Math.pow,
atan2: Math.atan2, atan2: Math.atan2,
"if": Polyfill.condition, "if": Polyfill.condition,
gamma: Polyfill.gamma, gamma: Polyfill.gamma,
"Γ": Polyfill.gamma, "Γ": Polyfill.gamma,
roundTo: Polyfill.roundTo, roundTo: Polyfill.roundTo,
map: Polyfill.arrayMap,
fold: Polyfill.arrayFold,
filter: Polyfill.arrayFilter,
indexOf: Polyfill.stringOrArrayIndexOf,
join: Polyfill.arrayJoin
} }
// These constants will automatically be replaced the MOMENT they are parsed. // These constants will automatically be replaced the MOMENT they are parsed.
@ -156,10 +151,6 @@ export class Parser {
return new ExprEvalExpression(instr, this) return new ExprEvalExpression(instr, this)
} }
evaluate(expr, variables) {
return this.parse(expr).evaluate(variables)
}
isOperatorEnabled(op) { isOperatorEnabled(op) {
const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op
const operators = this.options.operators || {} const operators = this.options.operators || {}

View file

@ -210,9 +210,8 @@ export function gamma(n) {
} }
export function stringOrArrayLength(s) { export function stringOrArrayLength(s) {
if(Array.isArray(s)) { if(Array.isArray(s))
return s.length return s.length
}
return String(s).length return String(s).length
} }
@ -296,58 +295,6 @@ export function min(array) {
} }
} }
export function arrayMap(f, a) {
if(typeof f !== "function") {
throw new EvalError(qsTranslate("error", "First argument to map is not a function."))
}
if(!Array.isArray(a)) {
throw new EvalError(qsTranslate("error", "Second argument to map is not an array."))
}
return a.map(function(x, i) {
return f(x, i)
})
}
export function arrayFold(f, init, a) {
if(typeof f !== "function") {
throw new EvalError(qsTranslate("error", "First argument to fold is not a function."))
}
if(!Array.isArray(a)) {
throw new EvalError(qsTranslate("error", "Second argument to fold is not an array."))
}
return a.reduce(function(acc, x, i) {
return f(acc, x, i)
}, init)
}
export function arrayFilter(f, a) {
if(typeof f !== "function") {
throw new EvalError(qsTranslate("error", "First argument to filter is not a function."))
}
if(!Array.isArray(a)) {
throw new EvalError(qsTranslate("error", "Second argument to filter is not an array."))
}
return a.filter(function(x, i) {
return f(x, i)
})
}
export function stringOrArrayIndexOf(target, s) {
if(!(Array.isArray(s) || typeof s === "string")) {
throw new Error(qsTranslate("error", "Second argument to indexOf is not a string or array."))
}
return s.indexOf(target)
}
export function arrayJoin(sep, a) {
if(!Array.isArray(a)) {
throw new Error(qsTranslate("error", "Second argument to join is not an array."))
}
return a.join(sep)
}
export function sign(x) { export function sign(x) {
return ((x > 0) - (x < 0)) || +x return ((x > 0) - (x < 0)) || +x
} }

View file

@ -19,7 +19,7 @@
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import { Parser } from "../lib/expr-eval/parser.mjs" import { Parser } from "../lib/expr-eval/parser.mjs"
const evalVariables = { const EVAL_VARIABLES = {
// Variables not provided by expr-eval.js, needs to be provided manually // Variables not provided by expr-eval.js, needs to be provided manually
"pi": Math.PI, "pi": Math.PI,
"PI": Math.PI, "PI": Math.PI,
@ -42,7 +42,7 @@ class ExprParserAPI extends Module {
this.currentVars = {} this.currentVars = {}
this.#parser = new Parser() this.#parser = new Parser()
this.#parser.consts = Object.assign({}, this.#parser.consts, evalVariables) this.#parser.consts = Object.assign({}, this.#parser.consts, EVAL_VARIABLES)
this.#parser.functions.integral = this.integral.bind(this) this.#parser.functions.integral = this.integral.bind(this)
this.#parser.functions.derivative = this.derivative.bind(this) this.#parser.functions.derivative = this.derivative.bind(this)

View file

@ -25,72 +25,274 @@ import { expect } from "chai"
import ExprEval from "../../src/module/expreval.mjs" import ExprEval from "../../src/module/expreval.mjs"
describe("Module/ExprEval", function() { describe("Module/ExprEval", function() {
describe("#parse", function() { describe("#parse.evaluate", function() {
const evaluate = (expr, vals) => ExprEval.parse(expr).evaluate(vals) const evaluate = (expr, vals = {}) => ExprEval.parse(expr).evaluate(vals)
it("parses simple mathematical expressions", function() { it("parses simple mathematical expressions", function() {
expect(evaluate("1", {})).to.equal(1) expect(evaluate(`"\\'\\"\\\\\\/\\b\\f\\n\\r\\t\\u3509"`)).to.equal(`'"\\/\b\f\n\r\t\u3509`)
expect(evaluate("-1", {})).to.equal(-1) expect(evaluate("1")).to.equal(1)
expect(evaluate("-(-1)", {})).to.equal(1) expect(evaluate(" 1 ")).to.equal(1)
expect(evaluate("+(-1)", {})).to.equal(-1) expect(evaluate("0xFF")).to.equal(255)
expect(evaluate("3!", {})).to.equal(6) expect(evaluate("0b11")).to.equal(3)
expect(evaluate("1+1", {})).to.equal(2) expect(evaluate("-1")).to.equal(-1)
expect(evaluate("4*3", {})).to.equal(12) expect(evaluate("-(-1)")).to.equal(1)
expect(evaluate("64/4", {})).to.equal(16) expect(evaluate("+(-1)")).to.equal(-1)
expect(evaluate("2^10", {})).to.equal(1024) expect(evaluate("3!")).to.equal(6)
expect(evaluate("10%3", {})).to.equal(1) expect(evaluate("1+1")).to.equal(2)
expect(evaluate("10%3", {})).to.equal(1) expect(evaluate("4*3")).to.equal(12)
expect(evaluate("4•3")).to.equal(12)
expect(evaluate("64/4")).to.equal(16)
expect(evaluate("2^10")).to.equal(1024)
expect(evaluate("10%3")).to.equal(1)
expect(evaluate("10%3")).to.equal(1)
// Test priorities // Test priorities
expect(evaluate("10*10+10*10", {})).to.equal(200) expect(evaluate("10*10+10*10")).to.equal(200)
expect(evaluate("10/10+10/10", {})).to.equal(2) expect(evaluate("10/10+10/10")).to.equal(2)
expect(evaluate("10/10+10/10", {})).to.equal(2) expect(evaluate("10/10+10/10")).to.equal(2)
expect(evaluate("2^2-2^2", {})).to.equal(0) expect(evaluate("2^2-2^2")).to.equal(0)
expect(evaluate("(2^2-2)^2", {})).to.equal(4) expect(evaluate("(2^2-2)^2")).to.equal(4)
}) })
it("parses equality and test statements", function() { it("parses equality and test statements", function() {
expect(evaluate("10%3 == 1 ? 2 : 1", {})).to.equal(2) expect(evaluate("10%3 == 1 ? 2 : 1")).to.equal(2)
expect(evaluate("10%3 != 1 ? 2 : 1", {})).to.equal(1) expect(evaluate("not(10%3 == 1) ? 2 : 1")).to.equal(1)
expect(evaluate("10 < 3 ? 2 : 1", {})).to.equal(1) expect(evaluate("10%3 != 1 ? 2 : 1")).to.equal(1)
expect(evaluate("10 > 3 ? (2+1) : 1", {})).to.equal(3) expect(evaluate("10 < 3 ? 2 : 1")).to.equal(1)
expect(evaluate("10 <= 3 ? 4 : 1", {})).to.equal(1) expect(evaluate("10 > 3 ? (2+1) : 1")).to.equal(3)
expect(evaluate("10 >= 3 ? 4 : 1", {})).to.equal(4) expect(evaluate("10 <= 3 ? 4 : 1")).to.equal(1)
expect(evaluate("10 >= 3 ? 4 : 1")).to.equal(4)
// Check equality // Check equality
expect(evaluate("10 < 10 ? 2 : 1", {})).to.equal(1) expect(evaluate("10 < 10 ? 2 : 1")).to.equal(1)
expect(evaluate("10 > 10 ? 2 : 1", {})).to.equal(1) expect(evaluate("10 > 10 ? 2 : 1")).to.equal(1)
expect(evaluate("10 <= 10 ? 4 : 1", {})).to.equal(4) expect(evaluate("10 <= 10 ? 4 : 1")).to.equal(4)
expect(evaluate("10 >= 10 ? 4 : 1", {})).to.equal(4) expect(evaluate("10 >= 10 ? 4 : 1")).to.equal(4)
// Check 'and' and 'or' // Check 'and' and 'or'
expect(evaluate("10 <= 3 and 10 < 10 ? 4 : 1", {})).to.equal(1) expect(evaluate("10 <= 3 and 10 < 10 ? 4 : 1")).to.equal(1)
expect(evaluate("10 <= 10 and 10 < 10 ? 4 : 1", {})).to.equal(1) expect(evaluate("10 <= 10 and 10 < 10 ? 4 : 1")).to.equal(1)
expect(evaluate("10 <= 10 and 10 < 20 ? 4 : 1", {})).to.equal(4) expect(evaluate("10 <= 10 and 10 < 20 ? 4 : 1")).to.equal(4)
expect(evaluate("10 <= 3 or 10 < 10 ? 4 : 1", {})).to.equal(1) expect(evaluate("10 <= 3 or 10 < 10 ? 4 : 1")).to.equal(1)
expect(evaluate("10 <= 10 or 10 < 10 ? 4 : 1", {})).to.equal(4) expect(evaluate("10 <= 10 or 10 < 10 ? 4 : 1")).to.equal(4)
expect(evaluate("10 <= 10 or 10 < 20 ? 4 : 1", {})).to.equal(4) expect(evaluate("10 <= 10 or 10 < 20 ? 4 : 1")).to.equal(4)
}) })
it("parses singular function operators (functions with one arguments and no parenthesis)", function() { it("parses singular function operators (functions with one arguments and no parenthesis)", function() {
// Trigonometric functions // Trigonometric functions
expect(evaluate("sin π", { })).to.be.approximately(0, Number.EPSILON) expect(evaluate("sin 0")).to.be.approximately(0, Number.EPSILON)
expect(evaluate("cos π", { })).to.be.approximately(-1, Number.EPSILON) expect(evaluate("cos 0")).to.be.approximately(1, Number.EPSILON)
expect(evaluate("tan π", { })).to.be.approximately(0, Number.EPSILON) expect(evaluate("tan 0")).to.be.approximately(0, Number.EPSILON)
expect(evaluate("asin 1", { })).to.be.approximately(Math.PI/2, Number.EPSILON) expect(evaluate("asin 1")).to.be.approximately(Math.PI / 2, Number.EPSILON)
expect(evaluate("acos 1", { })).to.be.approximately(0, Number.EPSILON) expect(evaluate("acos 1")).to.be.approximately(0, Number.EPSILON)
expect(evaluate("atan 1", { })).to.be.approximately(Math.PI/4, Number.EPSILON) expect(evaluate("atan 1")).to.be.approximately(Math.PI / 4, Number.EPSILON)
expect(evaluate("sinh 1", { })).to.be.approximately(Math.sinh(1), Number.EPSILON) expect(evaluate("sinh 1")).to.be.approximately(Math.sinh(1), Number.EPSILON)
expect(evaluate("cosh 1", { })).to.be.approximately(Math.cosh(1), Number.EPSILON) expect(evaluate("cosh 1")).to.be.approximately(Math.cosh(1), Number.EPSILON)
expect(evaluate("tanh 1", { })).to.be.approximately(Math.tanh(1), Number.EPSILON) expect(evaluate("tanh 1")).to.be.approximately(Math.tanh(1), Number.EPSILON)
expect(evaluate("asinh 1", { })).to.be.approximately(Math.asinh(1), Number.EPSILON) expect(evaluate("asinh 1")).to.be.approximately(Math.asinh(1), Number.EPSILON)
expect(evaluate("acosh 1", { })).to.be.approximately(Math.acosh(1), Number.EPSILON) expect(evaluate("acosh 1")).to.be.approximately(Math.acosh(1), Number.EPSILON)
expect(evaluate("atanh 0.5", { })).to.be.approximately(Math.atanh(0.5), Number.EPSILON) expect(evaluate("atanh 0.5")).to.be.approximately(Math.atanh(0.5), Number.EPSILON)
// Reverse trigonometric // Reverse trigonometric
expect(evaluate("asin sin 1", { })).to.be.approximately(1, Number.EPSILON) expect(evaluate("asin sin 1")).to.be.approximately(1, Number.EPSILON)
expect(evaluate("acos cos 1", { })).to.be.approximately(1, Number.EPSILON) expect(evaluate("acos cos 1")).to.be.approximately(1, Number.EPSILON)
expect(evaluate("atan tan 1", { })).to.be.approximately(1, Number.EPSILON) expect(evaluate("atan tan 1")).to.be.approximately(1, Number.EPSILON)
expect(evaluate("asinh sinh 1", { })).to.be.approximately(1, Number.EPSILON) expect(evaluate("asinh sinh 1")).to.be.approximately(1, Number.EPSILON)
expect(evaluate("acosh cosh 1", { })).to.be.approximately(1, Number.EPSILON) expect(evaluate("acosh cosh 1")).to.be.approximately(1, Number.EPSILON)
expect(evaluate("atanh tanh 1", { })).to.be.approximately(1, Number.EPSILON) expect(evaluate("atanh tanh 1")).to.be.approximately(1, Number.EPSILON)
// Other functions // Other functions
expect(evaluate("sqrt 4")).to.be.approximately(2, Number.EPSILON)
expect(evaluate("sqrt 2")).to.be.approximately(Math.sqrt(2), Number.EPSILON)
expect(evaluate("cbrt 27")).to.be.approximately(3, Number.EPSILON)
expect(evaluate("cbrt 14")).to.be.approximately(Math.cbrt(14), Number.EPSILON)
expect(evaluate("log 1")).to.be.approximately(Math.log(1), Number.EPSILON)
expect(evaluate("ln 1")).to.be.approximately(Math.log(1), Number.EPSILON)
expect(evaluate("log2 8")).to.be.approximately(3, Number.EPSILON)
expect(evaluate("log10 100")).to.be.approximately(2, Number.EPSILON)
expect(evaluate("lg 100")).to.be.approximately(2, Number.EPSILON)
expect(evaluate("expm1 0")).to.be.approximately(0, Number.EPSILON)
expect(evaluate("expm1 10")).to.be.approximately(Math.expm1(10), Number.EPSILON)
expect(evaluate("log1p 0")).to.be.approximately(0, Number.EPSILON)
expect(evaluate("log1p 10")).to.be.approximately(Math.log1p(10), Number.EPSILON)
// Roundings/Sign transformations
expect(evaluate("abs -12.34")).to.equal(12.34)
expect(evaluate("abs 12.45")).to.equal(12.45)
expect(evaluate("ceil 12.45")).to.equal(13)
expect(evaluate("ceil 12.75")).to.equal(13)
expect(evaluate("ceil 12.0")).to.equal(12)
expect(evaluate("ceil -12.6")).to.equal(-12)
expect(evaluate("floor 12.45")).to.equal(12)
expect(evaluate("floor 12.75")).to.equal(12)
expect(evaluate("floor 12.0")).to.equal(12)
expect(evaluate("floor -12.2")).to.equal(-13)
expect(evaluate("round 12.45")).to.equal(12)
expect(evaluate("round 12.75")).to.equal(13)
expect(evaluate("round 12.0")).to.equal(12)
expect(evaluate("round -12.2")).to.equal(-12)
expect(evaluate("round -12.6")).to.equal(-13)
expect(evaluate("trunc 12.45")).to.equal(12)
expect(evaluate("trunc 12.75")).to.equal(12)
expect(evaluate("trunc 12.0")).to.equal(12)
expect(evaluate("trunc -12.2")).to.equal(-12)
expect(evaluate("exp 1")).to.be.approximately(Math.E, Number.EPSILON)
expect(evaluate("exp 10")).to.be.approximately(Math.pow(Math.E, 10), 1e-8)
expect(evaluate("length \"string\"")).to.equal(6)
expect(evaluate("sign 0")).to.equal(0)
expect(evaluate("sign -0")).to.equal(0)
expect(evaluate("sign -10")).to.equal(-1)
expect(evaluate("sign 80")).to.equal(1)
}) })
it("parses regular functions", function() {
for(let i = 0; i < 1000; i++) {
expect(evaluate("random()")).to.be.within(0, 1)
expect(evaluate("random(100)")).to.be.within(0, 100)
}
expect(evaluate("fac(3)")).to.equal(6)
expect(evaluate("fac(10)")).to.equal(3628800)
expect(evaluate("min(10, 20)")).to.equal(10)
expect(evaluate("min(-10, -20)")).to.equal(-20)
expect(evaluate("max(10, 20)")).to.equal(20)
expect(evaluate("max(-10, -20)")).to.equal(-10)
expect(evaluate("hypot(3, 4)")).to.equal(5)
expect(evaluate("pyt(30, 40)")).to.equal(50)
expect(evaluate("atan2(1, 1)")).to.be.approximately(Math.PI / 4, Number.EPSILON)
expect(evaluate("atan2(1, 0)")).to.be.approximately(Math.PI / 2, Number.EPSILON)
expect(evaluate("atan2(0, 1)")).to.be.approximately(0, Number.EPSILON)
expect(evaluate("if(10 == 10, 1, 0)")).to.be.approximately(1, Number.EPSILON)
expect(evaluate("if(10 != 10, 1, 0)")).to.be.approximately(0, Number.EPSILON)
expect(evaluate("gamma(10) == 9!")).to.be.true
expect(evaluate("Γ(30) == 29!")).to.be.true
expect(evaluate("Γ(25) == 23!")).to.be.false
expect(evaluate("roundTo(26.04)")).to.equal(26)
expect(evaluate("roundTo(26.04, 2)")).to.equal(26.04)
expect(evaluate("roundTo(26.04836432123, 5)")).to.equal(26.04836)
expect(evaluate("roundTo(26.04836432123, 5)")).to.equal(26.04836)
})
it("parses arrays and access their members", function() {
expect(evaluate("[6, 7, 9]")).to.have.lengthOf(3)
expect(evaluate("[6, 7, 9]")).to.deep.equal([6, 7, 9])
expect(evaluate("[6, \"8\", 9]")).to.have.lengthOf(3)
expect(evaluate("[6, 7%2]")).to.deep.equal([6, 1])
// Access array indices
expect(evaluate("[6, 7][1]")).to.equal(7)
expect(evaluate("[6, 7, 8, 9, 10][2*2-1]")).to.equal(9)
})
it("can apply functions to arrays", function() {
expect(evaluate("length [6, 7, 9]")).to.equal(3)
expect(evaluate("length [6, 7, 8, 9]")).to.equal(4)
expect(evaluate("[6, 7, 9]||[10,11,12]")).to.deep.equal([6, 7, 9, 10, 11, 12])
expect(evaluate("6 in [6, 7, 9]")).to.be.true
expect(evaluate("2 in [6, 7, 9]")).to.be.false
expect(evaluate("min([10, 6, 7, 8, 9])")).to.equal(6)
expect(evaluate("max([6, 7, 8, 9, 2])")).to.equal(9)
})
it("throws errors when invalid function parameters are provided", function() {
expect(() => evaluate("max()")).to.throw()
expect(() => evaluate("min()")).to.throw()
})
it("parses constants", function() {
expect(evaluate("pi")).to.equal(Math.PI)
expect(evaluate("PI")).to.equal(Math.PI)
expect(evaluate("π")).to.equal(Math.PI)
expect(evaluate("e")).to.equal(Math.E)
expect(evaluate("E")).to.equal(Math.E)
expect(evaluate("true")).to.be.true
expect(evaluate("false")).to.be.false
// expect(evaluate("∞")).to.equal(Math.Infinity)
// expect(evaluate("infinity")).to.equal(Math.Infinity)
// expect(evaluate("Infinity")).to.equal(Math.Infinity)
})
it("can be provided variables", function() {
const u = [1, 2, 3, 4]
const x = 10
const s_ = "string"
const f = (x) => x * 2
expect(evaluate("u", { u })).to.deep.equal([...u])
expect(evaluate("x", { x })).to.equal(x)
expect(evaluate("s_", { s_ })).to.equal(s_)
expect(evaluate("f", { f })).to.equal(f)
expect(evaluate("b", { b: true })).to.equal(true)
expect(evaluate("u[1]", { u })).to.equal(u[1])
expect(evaluate("x/2", { x })).to.equal(x / 2)
expect(evaluate("f(2)", { f })).to.equal(f(2))
expect(evaluate("if(x == f(2), u[0], s_)", { x, u, s_, f })).to.equal(s_)
})
it("can be provided objects", function() {
const obj = { execute: (x) => x * 3, x: 10, y: { cached: true, execute: () => 20 } }
expect(evaluate("O(3)+O(2)", { O: obj })).to.equal(9 + 6)
expect(evaluate("O.x+O.y", { O: obj })).to.equal(30)
})
it("throws errors when trying to use variables wrongly", function() {
const obj = { execute: (x) => x * 3 }
expect(() => evaluate("O()", { O: obj })).to.throw()
expect(() => evaluate("O.x", { O: obj })).to.throw()
expect(() => evaluate("x()", { x: 10 })).to.throw()
expect(() => evaluate("x")).to.throw()
expect(() => evaluate("n")).to.throw()
})
it("can do it all at once", function() {
const obj = { execute: (x) => x * 3, x: 20 }
const u = [1, 2, 3, 4]
const x = 10
const s = "string"
const expr = "random(e) <= e ? fac(x)+u[2]+O(pi) : O.x+length s"
expect(evaluate(expr, { x, u, s, O: obj })).to.equal(3628803 + obj.execute(Math.PI))
})
it("cannot parse invalid expressions", function() {
expect(() => evaluate("1+")).to.throw()
expect(() => evaluate("@")).to.throw()
expect(() => evaluate("]")).to.throw()
expect(() => evaluate("")).to.throw()
expect(() => evaluate(`"\\u35P2"`)).to.throw()
expect(() => evaluate(`"\\x"`)).to.throw()
})
})
describe("#parse.toString", function() {
it("can be converted back into a string without changes", function() {
const expressions = ["pi+2*(e+2)^4", "sin(1+2!+pi+cos -3)^2", "[2,3,4][(2-1)*2]", "true ? false : true"]
for(const ogString of expressions) {
const expr = ExprEval.parse(ogString)
const convertedString = expr.toString()
expect(ExprEval.parse(convertedString)).to.deep.equal(expr) // Can be reparsed just the same
}
})
})
describe("#parse.substitute", function() {
const parsed = ExprEval.parse("if(x == 0, 1, 2+x)")
it("can substitute a variable for a number", function() {
expect(parsed.substitute("x", 10).evaluate({})).to.equal(12)
expect(parsed.substitute("x", 0).evaluate({})).to.equal(1)
})
it("can substitute a variable for another", function() {
expect(parsed.substitute("x", "b").evaluate({ b: 10 })).to.equal(12)
expect(parsed.substitute("x", "b").evaluate({ b: 0 })).to.equal(1)
})
it("can substitute a variable for an expression", function() {
expect(parsed.substitute("x", "sin α").evaluate({ "α": Math.PI / 2 })).to.be.approximately(3, Number.EPSILON)
expect(parsed.substitute("x", "sin α").evaluate({ "α": 0 })).to.equal(1)
expect(parsed.substitute("x", "α == 1 ? 0 : 1").evaluate({ "α": 1 })).to.equal(1)
})
})
describe("#parse.variables", function() {
it("can list all parsed undefined variables", function() {
expect(ExprEval.parse("a+b+x+pi+sin(b)").variables()).to.deep.equal(["a", "b", "x"])
})
})
describe("#parse.toJSFunction", function() {
const func = ExprEval.parse("not(false) ? a+b+x+1/x : x!+random()+A.x+[][0]").toJSFunction("x", { a: "10", b: "0" })
expect(func(10)).to.equal(20.1)
expect(func(20)).to.equal(30.05)
}) })
@ -143,7 +345,7 @@ describe("Module/ExprEval", function() {
describe("#derivative", function() { describe("#derivative", function() {
const DELTA = 1e-5 const DELTA = 1e-5
it("returns the derivative value between two integers", function() { it("returns the derivative value of a function at a given number", function() {
expect(ExprEval.derivative("1", "t", 2)).to.be.approximately(0, DELTA) expect(ExprEval.derivative("1", "t", 2)).to.be.approximately(0, DELTA)
expect(ExprEval.derivative("t", "t", 2)).to.be.approximately(1, DELTA) expect(ExprEval.derivative("t", "t", 2)).to.be.approximately(1, DELTA)
expect(ExprEval.derivative("t^2", "t", 2)).to.be.approximately(4, DELTA) expect(ExprEval.derivative("t^2", "t", 2)).to.be.approximately(4, DELTA)