2019-08-26 23:39:06 -06:00

2122 lines
93 KiB
JavaScript

// test parse
import assert from 'assert'
import approx from '../../../tools/approx'
import math from '../../../src/bundleAny'
const parse = math.parse
const ConditionalNode = math.ConditionalNode
const ConstantNode = math.ConstantNode
const OperatorNode = math.OperatorNode
const RangeNode = math.RangeNode
const Complex = math.Complex
const Matrix = math.Matrix
const Range = math.Range
const unit = math.unit
const ResultSet = math.ResultSet
/**
* Helper function to parse an expression and immediately evaluate its results
* @param {String} expr
* @param {Object} [scope]
* @return {*} result
*/
function parseAndEval (expr, scope) {
return parse(expr).evaluate(scope)
}
function parseAndStringifyWithParens (expr) {
return parse(expr).toString({ parenthesis: 'all' })
}
describe('parse', function () {
it('should parse a single expression', function () {
approx.equal(parse('2 + 6 / 3').compile().evaluate(), 4)
})
it('should parse an empty expression', function () {
assert.strictEqual(parse('').compile().evaluate(), undefined)
assert.strictEqual(parse('\n').compile().evaluate(), undefined)
assert.strictEqual(parse('\n\n').compile().evaluate(), undefined)
assert.strictEqual(parse('\n \n').compile().evaluate(), undefined)
assert.strictEqual(parse('#foo\n').compile().evaluate(), undefined)
assert.strictEqual(parse('#foo\n#bar\n').compile().evaluate(), undefined)
})
it('should parse an array with expressions', function () {
let scope = {}
assert.deepStrictEqual(parse(['a=3', 'b=4', 'a*b']).map(function (node) {
return node.compile().evaluate(scope)
}), [3, 4, 12])
})
it('should parse a matrix with expressions', function () {
let scope = {}
assert.deepStrictEqual(parse(math.matrix(['a=3', 'b=4', 'a*b'])).map(function (node) {
return node.compile().evaluate(scope)
}), math.matrix([3, 4, 12]))
})
it('should parse an array with an empty expression', function () {
assert.deepStrictEqual(parse(['']).map(function (node) {
return node.compile().evaluate()
}), [undefined])
})
it('should parse an array with an empty expression', function () {
assert.deepStrictEqual(parse(math.matrix([''])).map(function (node) {
return node.compile().evaluate()
}), math.matrix([undefined]))
})
it('should parse unicode and other special characters', function () {
// https://unicode-table.com/en
let scope = {}
math.evaluate('$ab$c = 2', scope) // dollar sign
assert.strictEqual(scope['$ab$c'], 2)
math.evaluate('\u00E9 = 2', scope) // Latin Small Letter E with Acute
assert.strictEqual(scope['\u00E9'], 2)
math.evaluate('\u03A6 = 3', scope) // Greek Capital Letter Phi
assert.strictEqual(scope['\u03A6'], 3)
math.evaluate('\u03A9 = 4', scope) // Greek Capital Letter Omega
assert.strictEqual(scope['\u03A9'], 4)
math.evaluate('\u2126 = 4', scope) // Letter-like character Ohm
assert.strictEqual(scope['\u2126'], 4)
math.evaluate('k\u00F6ln = 5', scope) // Combination of latin and unicode
assert.strictEqual(scope['k\u00F6ln'], 5)
// test unicode characters in the astral plane (surrogate pairs
math.evaluate('\uD835\uDD38 = 1', scope) // double struck capital A
assert.strictEqual(scope['\uD835\uDD38'], 1)
// should not allow the "holes"
assert.throws(function () {
math.evaluate('\uD835\uDCA3 = 1', scope)
})
})
describe('multiline', function () {
it('should parse multiline expressions', function () {
assert.deepStrictEqual(parse('a=3\nb=4\na*b').compile().evaluate(), new ResultSet([3, 4, 12]))
assert.deepStrictEqual(parse('b = 43; b * 4').compile().evaluate(), new ResultSet([172]))
})
it('should skip empty lines in multiline expressions', function () {
assert.deepStrictEqual(parse('\n;\n2 * 4\n').compile().evaluate(), new ResultSet([8]))
})
it('should spread operators over multiple lines', function () {
assert.deepStrictEqual(parse('2+\n3').compile().evaluate(), 5)
assert.deepStrictEqual(parse('2+\n\n3').compile().evaluate(), 5)
assert.deepStrictEqual(parse('2*\n3').compile().evaluate(), 6)
assert.deepStrictEqual(parse('2^\n3').compile().evaluate(), 8)
assert.deepStrictEqual(parse('2==\n3').compile().evaluate(), false)
assert.deepStrictEqual(parse('2*-\n3').compile().evaluate(), -6)
})
it('should parse multiple function assignments', function () {
let scope = {}
parse('f(x)=x*2;g(x)=x*3').compile().evaluate(scope)
assert.strictEqual(scope.f(2), 4)
assert.strictEqual(scope.g(2), 6)
let scope2 = {}
parse('a=2;f(x)=x^a;').compile().evaluate(scope2)
assert.strictEqual(scope2.a, 2)
assert.strictEqual(scope2.f(3), 9)
})
it('should correctly scope a function variable if also used outside the function', function () {
let scope = {}
const res = parse('x=2;f(x)=x^2;x').compile().evaluate(scope) // x should be x=2, not x of the function
assert.deepStrictEqual(res.entries, [2])
assert.strictEqual(scope.x, 2)
assert.strictEqual(scope.f(3), 9)
})
it('should spread a function over multiple lines', function () {
assert.deepStrictEqual(parse('add(\n4\n,\n2\n)').compile().evaluate(), 6)
})
it('should spread contents of parameters over multiple lines', function () {
assert.deepStrictEqual(parse('(\n4\n+\n2\n)').compile().evaluate(), 6)
})
it('should spread a function assignment over multiple lines', function () {
assert.deepStrictEqual(typeof parse('f(\nx\n,\ny\n)=\nx+\ny').compile().evaluate(), 'function')
})
it('should spread a variable assignment over multiple lines', function () {
assert.deepStrictEqual(parse('x=\n2').compile().evaluate(), 2)
})
it('should spread a matrix over multiple lines', function () {
assert.deepStrictEqual(parse('[\n1\n,\n2\n]').compile().evaluate(), math.matrix([1, 2]))
})
it('should spread a range over multiple lines', function () {
assert.deepStrictEqual(parse('2:\n4').compile().evaluate(), math.matrix([2, 3, 4]))
assert.deepStrictEqual(parse('2:\n2:\n6').compile().evaluate(), math.matrix([2, 4, 6]))
})
it('should spread an index over multiple lines', function () {
assert.deepStrictEqual(parse('a[\n1\n,\n1\n]').compile().evaluate({ a: [[1, 2], [3, 4]] }), 1)
let scope = { a: [[1, 2], [3, 4]] }
assert.deepStrictEqual(parse('a[\n1\n,\n1\n]=\n100').compile().evaluate(scope), 100)
assert.deepStrictEqual(scope, { a: [[100, 2], [3, 4]] })
})
})
it('should throw an error when scope contains a reserved keyword', function () {
let scope = {
end: 2
}
assert.throws(function () {
parse('2+3').compile().evaluate(scope)
}, /Scope contains an illegal symbol/)
})
it('should give informative syntax errors', function () {
assert.throws(function () { parse('2 +') }, /Unexpected end of expression \(char 4\)/)
assert.throws(function () { parse('2 + 3 + *') }, /Value expected \(char 9\)/)
})
it('should throw an error if called with wrong number of arguments', function () {
assert.throws(function () { parse() }, /Too few arguments/)
assert.throws(function () { parse('2 + 3', {}, 3) }, /Too many arguments/)
})
it('should throw an error if called with a wrong type of argument', function () {
assert.throws(function () { parse(math.unit('5cm')) }, TypeError)
assert.throws(function () { parse(new Complex(2, 3)) }, TypeError)
assert.throws(function () { parse(new Date()) }, TypeError)
})
it('should throw an error in case of unsupported characters', function () {
assert.throws(function () { parse('2\u00A1') }, /Syntax error in part "\u00A1"/)
})
describe('comments', function () {
it('should skip comments', function () {
assert.strictEqual(parseAndEval('2 + 3 # - 4'), 5)
})
it('should skip comments in a ResultSet', function () {
assert.deepStrictEqual(parseAndEval('2 + 3 # - 4\n6-2'), new ResultSet([5, 4]))
})
it('should fill in the property comment of a Node', function () {
assert.strictEqual(parse('2 + 3').comment, '')
assert.strictEqual(parse('2 + 3 # hello').comment, '# hello')
assert.strictEqual(parse(' # hi').comment, '# hi')
const blockNode = parse('2 # foo\n3 # bar')
assert.strictEqual(blockNode.blocks.length, 2)
assert.strictEqual(blockNode.blocks[0].node.comment, '# foo')
assert.strictEqual(blockNode.blocks[1].node.comment, '# bar')
})
})
describe('number', function () {
it('should parse valid numbers', function () {
assert.strictEqual(parseAndEval('0'), 0)
assert.strictEqual(parseAndEval('3'), 3)
assert.strictEqual(parseAndEval('3.2'), 3.2)
assert.strictEqual(parseAndEval('3.'), 3)
assert.strictEqual(parseAndEval('3. '), 3)
assert.strictEqual(parseAndEval('3.\t'), 3)
assert.strictEqual(parseAndEval('003.2'), 3.2)
assert.strictEqual(parseAndEval('003.200'), 3.2)
assert.strictEqual(parseAndEval('.2'), 0.2)
assert.strictEqual(parseAndEval('3e2'), 300)
assert.strictEqual(parseAndEval('300e2'), 30000)
assert.strictEqual(parseAndEval('300e+2'), 30000)
assert.strictEqual(parseAndEval('300e-2'), 3)
assert.strictEqual(parseAndEval('300E-2'), 3)
assert.strictEqual(parseAndEval('3.2e2'), 320)
})
it('should parse a number followed by e', function () {
approx.equal(parseAndEval('2e'), 2 * Math.E)
})
it('should throw an error with invalid numbers', function () {
assert.throws(function () { parseAndEval('.') }, /Value expected/)
assert.throws(function () { parseAndEval('3.2.2') }, SyntaxError)
assert.throws(function () { parseAndEval('3.2e2.2') }, SyntaxError)
assert.throws(function () { parseAndEval('3e0.5') }, /Digit expected, got "."/)
assert.throws(function () { parseAndEval('3e.5') }, /Digit expected, got "."/)
assert.throws(function () { parseAndEval('-3e0.5') }, /Digit expected, got "."/)
assert.throws(function () { parseAndEval('-3e.5') }, /Digit expected, got "."/)
assert.throws(function () { parseAndEval('3e-0.5') }, /Digit expected, got "."/)
assert.throws(function () { parseAndEval('3e-.5') }, /Digit expected, got "."/)
assert.throws(function () { parseAndEval('-3e-0.5') }, /Digit expected, got "."/)
assert.throws(function () { parseAndEval('-3e-.5') }, /Digit expected, got "."/)
assert.throws(function () { parseAndEval('2e+a') }, /Digit expected, got "a"/)
})
})
describe('bignumber', function () {
it('should parse bignumbers', function () {
assert.deepStrictEqual(parseAndEval('bignumber(0.1)'), math.bignumber(0.1))
assert.deepStrictEqual(parseAndEval('bignumber("1.2e500")'), math.bignumber('1.2e500'))
})
it('should output bignumbers if default number type is bignumber', function () {
const bigmath = math.create({
number: 'BigNumber'
})
assert.deepStrictEqual(bigmath.parse('0.1').compile().evaluate(), bigmath.bignumber(0.1))
assert.deepStrictEqual(bigmath.parse('1.2e5000').compile().evaluate(), bigmath.bignumber('1.2e5000'))
})
})
describe('fraction', function () {
it('should output fractions if default number type is fraction', function () {
const fmath = math.create({
number: 'Fraction'
})
assert(fmath.parse('0.1').compile().evaluate() instanceof math.Fraction)
assert.strictEqual(fmath.parse('1/3').compile().evaluate().toString(), '0.(3)')
assert.strictEqual(fmath.parse('0.1+0.2').compile().evaluate().toString(), '0.3')
})
})
describe('string (double quotes)', function () {
it('should parse a string', function () {
assert.deepStrictEqual(parseAndEval('"hello"'), 'hello')
assert.deepStrictEqual(parseAndEval(' "hi" '), 'hi')
})
it('should parse a with escaped characters', function () {
assert.deepStrictEqual(parseAndEval('"line end\\nnext"'), 'line end\nnext')
assert.deepStrictEqual(parseAndEval('"line end\\n"'), 'line end\n')
assert.deepStrictEqual(parseAndEval('"tab\\tnext"'), 'tab\tnext')
assert.deepStrictEqual(parseAndEval('"tab\\t"'), 'tab\t')
assert.deepStrictEqual(parseAndEval('"escaped backslash\\\\next"'), 'escaped backslash\\next')
assert.deepStrictEqual(parseAndEval('"escaped backslash\\\\"'), 'escaped backslash\\')
})
it('should throw an error with invalid strings', function () {
assert.throws(function () { parseAndEval('"hi') }, SyntaxError)
assert.throws(function () { parseAndEval(' hi" ') }, Error)
})
it('should get a string subset', function () {
let scope = {}
assert.deepStrictEqual(parseAndEval('c="hello"', scope), 'hello')
assert.deepStrictEqual(parseAndEval('c[2:4]', scope), 'ell')
assert.deepStrictEqual(parseAndEval('c[5:-1:1]', scope), 'olleh')
assert.deepStrictEqual(parseAndEval('c[end-2:-1:1]', scope), 'leh')
assert.deepStrictEqual(parseAndEval('"hello"[2:4]', scope), 'ell')
})
it('should set a string subset', function () {
let scope = {}
assert.deepStrictEqual(parseAndEval('c="hello"', scope), 'hello')
assert.deepStrictEqual(parseAndEval('c[1] = "H"', scope), 'H')
assert.deepStrictEqual(scope.c, 'Hello')
assert.deepStrictEqual(parseAndEval('c', scope), 'Hello')
assert.deepStrictEqual(parseAndEval('c[6:11] = " world"', scope), ' world')
assert.deepStrictEqual(scope.c, 'Hello world')
assert.deepStrictEqual(parseAndEval('c[end] = "D"', scope), 'D')
assert.deepStrictEqual(scope.c, 'Hello worlD')
})
it('should set a string subset on an object', function () {
let scope = { a: {} }
assert.deepStrictEqual(parseAndEval('a.c="hello"', scope), 'hello')
assert.deepStrictEqual(parseAndEval('a.c[1] = "H"', scope), 'H')
assert.deepStrictEqual(scope.a, { c: 'Hello' })
assert.deepStrictEqual(parseAndEval('a.c', scope), 'Hello')
assert.deepStrictEqual(parseAndEval('a.c[6:11] = " world"', scope), ' world')
assert.deepStrictEqual(scope.a, { c: 'Hello world' })
assert.deepStrictEqual(parseAndEval('a.c', scope), 'Hello world')
assert.deepStrictEqual(scope.a, { c: 'Hello world' })
assert.deepStrictEqual(parseAndEval('a.c[end] = "D"', scope), 'D')
assert.deepStrictEqual(scope.a, { c: 'Hello worlD' })
})
})
describe('string (single quotes)', function () {
it('should parse a string', function () {
assert.deepStrictEqual(parseAndEval('\'hello\''), 'hello')
assert.deepStrictEqual(parseAndEval(' \'hi\' '), 'hi')
})
it('should parse a with escaped characters', function () {
assert.deepStrictEqual(parseAndEval('\'line end\\nnext\''), 'line end\nnext')
assert.deepStrictEqual(parseAndEval('\'line end\\n\''), 'line end\n')
assert.deepStrictEqual(parseAndEval('\'tab\\tnext\''), 'tab\tnext')
assert.deepStrictEqual(parseAndEval('\'tab\\t\''), 'tab\t')
assert.deepStrictEqual(parseAndEval('\'escaped backslash\\\\next\''), 'escaped backslash\\next')
assert.deepStrictEqual(parseAndEval('\'escaped backslash\\\\\''), 'escaped backslash\\')
})
it('should throw an error with invalid strings', function () {
assert.throws(function () { parseAndEval('\'hi') }, SyntaxError)
assert.throws(function () { parseAndEval(' hi\' ') }, Error)
})
it('should get a string subset', function () {
let scope = {}
assert.deepStrictEqual(parseAndEval('c=\'hello\'', scope), 'hello')
assert.deepStrictEqual(parseAndEval('c[2:4]', scope), 'ell')
assert.deepStrictEqual(parseAndEval('c[5:-1:1]', scope), 'olleh')
assert.deepStrictEqual(parseAndEval('c[end-2:-1:1]', scope), 'leh')
assert.deepStrictEqual(parseAndEval('\'hello\'[2:4]', scope), 'ell')
})
it('should set a string subset', function () {
let scope = {}
assert.deepStrictEqual(parseAndEval('c=\'hello\'', scope), 'hello')
assert.deepStrictEqual(parseAndEval('c[1] = \'H\'', scope), 'H')
assert.deepStrictEqual(scope.c, 'Hello')
assert.deepStrictEqual(parseAndEval('c', scope), 'Hello')
assert.deepStrictEqual(parseAndEval('c[6:11] = \' world\'', scope), ' world')
assert.deepStrictEqual(scope.c, 'Hello world')
assert.deepStrictEqual(parseAndEval('c[end] = \'D\'', scope), 'D')
assert.deepStrictEqual(scope.c, 'Hello worlD')
})
it('should set a string subset on an object', function () {
let scope = { a: {} }
assert.deepStrictEqual(parseAndEval('a.c=\'hello\'', scope), 'hello')
assert.deepStrictEqual(parseAndEval('a.c[1] = \'H\'', scope), 'H')
assert.deepStrictEqual(scope.a, { c: 'Hello' })
assert.deepStrictEqual(parseAndEval('a.c', scope), 'Hello')
assert.deepStrictEqual(parseAndEval('a.c[6:11] = \' world\'', scope), ' world')
assert.deepStrictEqual(scope.a, { c: 'Hello world' })
assert.deepStrictEqual(parseAndEval('a.c', scope), 'Hello world')
assert.deepStrictEqual(scope.a, { c: 'Hello world' })
assert.deepStrictEqual(parseAndEval('a.c[end] = \'D\'', scope), 'D')
assert.deepStrictEqual(scope.a, { c: 'Hello worlD' })
})
})
describe('unit', function () {
it('should parse units', function () {
assert.deepStrictEqual(parseAndEval('5cm'), unit(5, 'cm'))
assert.ok(parseAndEval('5cm').isUnit)
})
it('should parse physical constants', function () {
const expected = unit(299792458, 'm/s').to()
assert.deepStrictEqual(parseAndEval('speedOfLight'), expected)
})
it('should correctly parse negative temperatures', function () {
approx.deepEqual(parseAndEval('-6 celsius'), unit(-6, 'celsius'))
approx.deepEqual(parseAndEval('--6 celsius'), unit(6, 'celsius'))
approx.deepEqual(parseAndEval('-6 celsius to fahrenheit'),
unit(21.2, 'fahrenheit').to('fahrenheit'))
})
it('should convert units', function () {
let scope = {}
approx.deepEqual(parseAndEval('(5.08 cm * 1000) to inch', scope),
math.unit(2000, 'inch').to('inch'))
approx.deepEqual(parseAndEval('a = (5.08 cm * 1000) to mm', scope),
math.unit(50800, 'mm').to('mm'))
approx.deepEqual(parseAndEval('a to inch', scope),
math.unit(2000, 'inch').to('inch'))
approx.deepEqual(parseAndEval('10 celsius to fahrenheit'),
math.unit(50, 'fahrenheit').to('fahrenheit'))
approx.deepEqual(parseAndEval('20 celsius to fahrenheit'),
math.unit(68, 'fahrenheit').to('fahrenheit'))
approx.deepEqual(parseAndEval('50 fahrenheit to celsius'),
math.unit(10, 'celsius').to('celsius'))
})
it('should create units and aliases', function () {
const myMath = math.create()
myMath.evaluate('createUnit("knot", {definition: "0.514444444 m/s", aliases: ["knots", "kt", "kts"]}, {override: true})') // override required because kt also means kilotonne
assert.strictEqual(myMath.evaluate('5 knot').toString(), '5 knot')
assert.strictEqual(myMath.evaluate('5 knots').toString(), '5 knots')
assert.strictEqual(myMath.evaluate('5 kt').toString(), '5 kt')
})
it('should evaluate operator "to" with correct precedence ', function () {
approx.deepEqual(parseAndEval('5.08 cm * 1000 to inch'),
unit(2000, 'inch').to('inch'))
})
it('should evaluate operator "in" (alias of "to") ', function () {
approx.deepEqual(parseAndEval('5.08 cm in inch'),
unit(2, 'inch').to('inch'))
})
it('should evaluate unit "in" (should not conflict with operator "in")', function () {
approx.deepEqual(parseAndEval('2 in'), unit(2, 'in'))
approx.deepEqual(parseAndEval('5.08 cm in in'), unit(2, 'in').to('in'))
approx.deepEqual(parseAndEval('5 in in in'), unit(5, 'in').to('in'))
approx.deepEqual(parseAndEval('2 in to meter'), unit(2, 'inch').to('meter'))
approx.deepEqual(parseAndEval('2 in in meter'), unit(2, 'inch').to('meter'))
approx.deepEqual(parseAndEval('a in inch', { a: unit(5.08, 'cm') }), unit(2, 'inch').to('inch'))
approx.deepEqual(parseAndEval('(2+3) in'), unit(5, 'in'))
approx.deepEqual(parseAndEval('a in', { a: 5 }), unit(5, 'in'))
approx.deepEqual(parseAndEval('0.5in + 1.5in to cm'), unit(5.08, 'cm').to('cm'))
})
})
describe('complex', function () {
it('should parse complex values', function () {
assert.deepStrictEqual(parseAndEval('i'), new Complex(0, 1))
assert.deepStrictEqual(parseAndEval('2+3i'), new Complex(2, 3))
assert.deepStrictEqual(parseAndEval('2+3*i'), new Complex(2, 3))
assert.deepStrictEqual(parseAndEval('1/2i'), new Complex(0, 0.5))
})
})
describe('matrix', function () {
it('should parse a matrix', function () {
assert.ok(parseAndEval('[1,2;3,4]') instanceof Matrix)
const m = parseAndEval('[1,2,3;4,5,6]')
assert.deepStrictEqual(m.size(), [2, 3])
assert.deepStrictEqual(m, math.matrix([[1, 2, 3], [4, 5, 6]]))
const b = parseAndEval('[5, 6; 1, 1]')
assert.deepStrictEqual(b.size(), [2, 2])
assert.deepStrictEqual(b, math.matrix([[5, 6], [1, 1]]))
// from 1 to n dimensions
assert.deepStrictEqual(parseAndEval('[ ]'), math.matrix([]))
assert.deepStrictEqual(parseAndEval('[1,2,3]'), math.matrix([1, 2, 3]))
assert.deepStrictEqual(parseAndEval('[1;2;3]'), math.matrix([[1], [2], [3]]))
assert.deepStrictEqual(parseAndEval('[[1,2],[3,4]]'), math.matrix([[1, 2], [3, 4]]))
assert.deepStrictEqual(parseAndEval('[[[1],[2]],[[3],[4]]]'), math.matrix([[[1], [2]], [[3], [4]]]))
})
it('should parse an empty matrix', function () {
assert.deepStrictEqual(parseAndEval('[]'), math.matrix([]))
})
it('should get a matrix subset', function () {
let scope = {
a: math.matrix([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
}
assert.deepStrictEqual(parseAndEval('a[2, :]', scope), math.matrix([[4, 5, 6]]))
assert.deepStrictEqual(parseAndEval('a[2, :2]', scope), math.matrix([[4, 5]]))
assert.deepStrictEqual(parseAndEval('a[2, :end-1]', scope), math.matrix([[4, 5]]))
assert.deepStrictEqual(parseAndEval('a[2, 2:]', scope), math.matrix([[5, 6]]))
assert.deepStrictEqual(parseAndEval('a[2, 2:3]', scope), math.matrix([[5, 6]]))
assert.deepStrictEqual(parseAndEval('a[2, 1:2:3]', scope), math.matrix([[4, 6]]))
assert.deepStrictEqual(parseAndEval('a[:, 2]', scope), math.matrix([[2], [5], [8]]))
assert.deepStrictEqual(parseAndEval('a[:2, 2]', scope), math.matrix([[2], [5]]))
assert.deepStrictEqual(parseAndEval('a[:end-1, 2]', scope), math.matrix([[2], [5]]))
assert.deepStrictEqual(parseAndEval('a[2:, 2]', scope), math.matrix([[5], [8]]))
assert.deepStrictEqual(parseAndEval('a[2:3, 2]', scope), math.matrix([[5], [8]]))
assert.deepStrictEqual(parseAndEval('a[1:2:3, 2]', scope), math.matrix([[2], [8]]))
})
it('should get a matrix subset of a matrix subset', function () {
let scope = {
a: math.matrix([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
}
assert.deepStrictEqual(parseAndEval('a[2, :][1,1]', scope), 4)
})
it('should get BigNumber value from an array', function () {
const res = parseAndEval('arr[1]', { arr: [math.bignumber(2)] })
assert.deepStrictEqual(res, math.bignumber(2))
})
it('should parse matrix resizings', function () {
let scope = {}
assert.deepStrictEqual(parseAndEval('a = []', scope), math.matrix([]))
assert.deepStrictEqual(parseAndEval('a[1:3,1] = [1;2;3]', scope), math.matrix([[1], [2], [3]]))
assert.deepStrictEqual(parseAndEval('a[:,2] = [4;5;6]', scope), math.matrix([[4], [5], [6]]))
assert.deepStrictEqual(scope.a, math.matrix([[1, 4], [2, 5], [3, 6]]))
assert.deepStrictEqual(parseAndEval('a = []', scope), math.matrix([]))
assert.strictEqual(parseAndEval('a[1,3] = 3', scope), 3)
assert.deepStrictEqual(scope.a, math.matrix([[0, 0, 3]]))
assert.deepStrictEqual(parseAndEval('a[2,:] = [[4,5,6]]', scope), math.matrix([[4, 5, 6]]))
assert.deepStrictEqual(scope.a, math.matrix([[0, 0, 3], [4, 5, 6]]))
assert.deepStrictEqual(parseAndEval('a = []', scope), math.matrix([]))
assert.strictEqual(parseAndEval('a[3,1] = 3', scope), 3)
assert.deepStrictEqual(scope.a, math.matrix([[0], [0], [3]]))
assert.deepStrictEqual(parseAndEval('a[:,2] = [4;5;6]', scope), math.matrix([[4], [5], [6]]))
assert.deepStrictEqual(scope.a, math.matrix([[0, 4], [0, 5], [3, 6]]))
assert.deepStrictEqual(parseAndEval('a = []', scope), math.matrix([]))
assert.deepStrictEqual(parseAndEval('a[1,1:3] = [[1,2,3]]', scope), math.matrix([[1, 2, 3]]))
assert.deepStrictEqual(scope.a, math.matrix([[1, 2, 3]]))
assert.deepStrictEqual(parseAndEval('a[2,:] = [[4,5,6]]', scope), math.matrix([[4, 5, 6]]))
assert.deepStrictEqual(scope.a, math.matrix([[1, 2, 3], [4, 5, 6]]))
})
it('should get/set the matrix correctly', function () {
let scope = {}
parseAndEval('a=[1,2;3,4]', scope)
parseAndEval('a[1,1] = 100', scope)
assert.deepStrictEqual(scope.a.size(), [2, 2])
assert.deepStrictEqual(scope.a, math.matrix([[100, 2], [3, 4]]))
parseAndEval('a[2:3,2:3] = [10,11;12,13]', scope)
assert.deepStrictEqual(scope.a.size(), [3, 3])
assert.deepStrictEqual(scope.a, math.matrix([[100, 2, 0], [3, 10, 11], [0, 12, 13]]))
const a = scope.a
// note: after getting subset, uninitialized elements are replaced by elements with an undefined value
assert.deepStrictEqual(a.subset(math.index(new Range(0, 3), new Range(0, 2))), math.matrix([[100, 2], [3, 10], [0, 12]]))
assert.deepStrictEqual(parseAndEval('a[1:3,1:2]', scope), math.matrix([[100, 2], [3, 10], [0, 12]]))
scope.b = [[1, 2], [3, 4]]
assert.deepStrictEqual(parseAndEval('b[1,:]', scope), [[1, 2]])
})
it('should get/set the matrix correctly for 3d matrices', function () {
let scope = {}
assert.deepStrictEqual(parseAndEval('f=[1,2;3,4]', scope), math.matrix([[1, 2], [3, 4]]))
assert.deepStrictEqual(parseAndEval('size(f)', scope), math.matrix([2, 2]))
parseAndEval('f[:,:,2]=[5,6;7,8]', scope)
assert.deepStrictEqual(scope.f, math.matrix([
[
[1, 5],
[2, 6]
],
[
[3, 7],
[4, 8]
]
]))
assert.deepStrictEqual(parseAndEval('size(f)', scope), math.matrix([2, 2, 2]))
assert.deepStrictEqual(parseAndEval('f[:,:,1]', scope), math.matrix([[[1], [2]], [[3], [4]]]))
assert.deepStrictEqual(parseAndEval('f[:,:,2]', scope), math.matrix([[[5], [6]], [[7], [8]]]))
assert.deepStrictEqual(parseAndEval('f[:,2,:]', scope), math.matrix([[[2, 6]], [[4, 8]]]))
assert.deepStrictEqual(parseAndEval('f[2,:,:]', scope), math.matrix([[[3, 7], [4, 8]]]))
parseAndEval('a=diag([1,2,3,4])', scope)
assert.deepStrictEqual(parseAndEval('a[3:end, 3:end]', scope), math.matrix([[3, 0], [0, 4]]))
parseAndEval('a[3:end, 2:end]=9*ones(2,3)', scope)
assert.deepStrictEqual(scope.a, math.matrix([
[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 9, 9, 9],
[0, 9, 9, 9]
]))
assert.deepStrictEqual(parseAndEval('a[2:end-1, 2:end-1]', scope), math.matrix([[2, 0], [9, 9]]))
})
it('should merge nested matrices', function () {
let scope = {}
parseAndEval('a=[1,2;3,4]', scope)
})
it('should parse matrix concatenations', function () {
let scope = {}
parseAndEval('a=[1,2;3,4]', scope)
parseAndEval('b=[5,6;7,8]', scope)
assert.deepStrictEqual(parseAndEval('c=concat(a,b)', scope), math.matrix([[1, 2, 5, 6], [3, 4, 7, 8]]))
assert.deepStrictEqual(parseAndEval('c=concat(a,b,1)', scope), math.matrix([[1, 2], [3, 4], [5, 6], [7, 8]]))
assert.deepStrictEqual(parseAndEval('c=concat(concat(a,b), concat(b,a), 1)', scope), math.matrix([[1, 2, 5, 6], [3, 4, 7, 8], [5, 6, 1, 2], [7, 8, 3, 4]]))
assert.deepStrictEqual(parseAndEval('c=concat([[1,2]], [[3,4]], 1)', scope), math.matrix([[1, 2], [3, 4]]))
assert.deepStrictEqual(parseAndEval('c=concat([[1,2]], [[3,4]], 2)', scope), math.matrix([[1, 2, 3, 4]]))
assert.deepStrictEqual(parseAndEval('c=concat([[1]], [2;3], 1)', scope), math.matrix([[1], [2], [3]]))
assert.deepStrictEqual(parseAndEval('d=1:3', scope), math.matrix([1, 2, 3]))
assert.deepStrictEqual(parseAndEval('concat(d,d)', scope), math.matrix([1, 2, 3, 1, 2, 3]))
assert.deepStrictEqual(parseAndEval('e=1+d', scope), math.matrix([2, 3, 4]))
assert.deepStrictEqual(parseAndEval('size(e)', scope), math.matrix([3]))
assert.deepStrictEqual(parseAndEval('concat(e,e)', scope), math.matrix([2, 3, 4, 2, 3, 4]))
assert.deepStrictEqual(parseAndEval('[[],[]]', scope), math.matrix([[], []]))
assert.deepStrictEqual(parseAndEval('[[],[]]', scope).size(), [2, 0])
assert.deepStrictEqual(parseAndEval('size([[],[]])', scope), math.matrix([2, 0]))
})
it('should disable arrays as range in a matrix index', function () {
let scope = {
a: [[1, 2, 3], [4, 5, 6]]
}
assert.throws(function () {
parseAndEval('a[2, 2+3i]', scope)
}, /TypeError: Dimension must be an Array, Matrix, number, string, or Range/)
})
it('should throw an error for invalid matrix', function () {
assert.throws(function () { parseAndEval('[1, 2') }, /End of matrix ] expected/)
assert.throws(function () { parseAndEval('[1; 2') }, /End of matrix ] expected/)
})
it('should throw an error when matrix rows mismatch', function () {
assert.throws(function () { parseAndEval('[1, 2; 1, 2, 3]') }, /Column dimensions mismatch/)
})
it('should throw an error for invalid matrix subsets', function () {
let scope = { a: [1, 2, 3] }
assert.throws(function () { parseAndEval('a[1', scope) }, /Parenthesis ] expected/)
})
it('should throw an error for invalid matrix concatenations', function () {
let scope = {}
assert.throws(function () { parseAndEval('c=concat(a, [1,2,3])', scope) })
})
})
describe('objects', function () {
it('should get an object property', function () {
assert.deepStrictEqual(parseAndEval('obj["foo"]', { obj: { foo: 2 } }), 2)
})
it('should get a nested object property', function () {
assert.deepStrictEqual(parseAndEval('obj["foo"]["bar"]', { obj: { foo: { bar: 2 } } }), 2)
})
it('should get a nested matrix subset from an object property', function () {
assert.deepStrictEqual(parseAndEval('obj.foo[2]', { obj: { foo: [1, 2, 3] } }), 2)
assert.deepStrictEqual(parseAndEval('obj.foo[end]', { obj: { foo: [1, 2, 3] } }), 3)
assert.deepStrictEqual(parseAndEval('obj.foo[2][3]', { obj: { foo: ['hello', 'world'] } }), 'r')
assert.deepStrictEqual(parseAndEval('obj.foo[2][end]', { obj: { foo: ['hello', 'world'] } }), 'd')
assert.deepStrictEqual(parseAndEval('obj.foo[1].bar', { obj: { foo: [{ bar: 4 }] } }), 4)
})
it('should set an object property', function () {
let scope = { obj: { a: 3 } }
const res = parseAndEval('obj["b"] = 2', scope)
assert.strictEqual(res, 2)
assert.deepStrictEqual(scope, { obj: { a: 3, b: 2 } })
})
it('should set a nested object property', function () {
let scope = { obj: { foo: {} } }
const res = parseAndEval('obj["foo"]["bar"] = 2', scope)
assert.strictEqual(res, 2)
assert.deepStrictEqual(scope, { obj: { foo: { bar: 2 } } })
})
it('should throw an error when trying to apply a matrix index as object property', function () {
let scope = { a: {} }
assert.throws(function () {
parseAndEval('a[2] = 6', scope)
}, /Cannot apply a numeric index as object property/)
})
it('should set a nested matrix subset from an object property (1)', function () {
let scope = { obj: { foo: [1, 2, 3] } }
assert.deepStrictEqual(parseAndEval('obj.foo[2] = 6', scope), 6)
assert.deepStrictEqual(scope, { obj: { foo: [1, 6, 3] } })
assert.deepStrictEqual(parseAndEval('obj.foo[end] = 8', scope), 8)
assert.deepStrictEqual(scope, { obj: { foo: [1, 6, 8] } })
})
it('should set a nested matrix subset from an object property (2)', function () {
let scope = { obj: { foo: [{ bar: 4 }] } }
assert.deepStrictEqual(parseAndEval('obj.foo[1].bar = 6', scope), 6)
assert.deepStrictEqual(scope, { obj: { foo: [{ bar: 6 }] } })
})
it('should set a nested matrix subset from an object property (3)', function () {
let scope = { obj: { foo: [{ bar: {} }] } }
assert.deepStrictEqual(parseAndEval('obj.foo[1].bar.baz = 6', scope), 6)
assert.deepStrictEqual(scope, { obj: { foo: [{ bar: { baz: 6 } }] } })
})
it('should set a nested matrix subset from an object property (4)', function () {
let scope = { obj: { foo: ['hello', 'world'] } }
assert.deepStrictEqual(parseAndEval('obj.foo[1][end] = "a"', scope), 'a')
assert.deepStrictEqual(scope, { obj: { foo: ['hella', 'world'] } })
assert.deepStrictEqual(parseAndEval('obj.foo[end][end] = "!"', scope), '!')
assert.deepStrictEqual(scope, { obj: { foo: ['hella', 'worl!'] } })
})
// TODO: test whether 1-based IndexErrors are thrown
it('should get an object property with dot notation', function () {
assert.deepStrictEqual(parseAndEval('obj.foo', { obj: { foo: 2 } }), 2)
})
it('should get an object property from an object inside parentheses', function () {
assert.deepStrictEqual(parseAndEval('(obj).foo', { obj: { foo: 2 } }), 2)
})
it('should get a nested object property with dot notation', function () {
assert.deepStrictEqual(parseAndEval('obj.foo.bar', { obj: { foo: { bar: 2 } } }), 2)
})
it('should invoke a function in an object', function () {
let scope = {
obj: {
fn: function (x) {
return x * x
}
}
}
assert.deepStrictEqual(parseAndEval('obj.fn(2)', scope), 4)
assert.deepStrictEqual(parseAndEval('obj["fn"](2)', scope), 4)
})
it('should apply implicit multiplication after a function call', function () {
assert.deepStrictEqual(parseAndEval('sqrt(4)(1+2)'), 6)
assert.deepStrictEqual(parseAndEval('sqrt(4)(1+2)(2)'), 12)
})
it('should invoke a function on an object with the right context', function () {
assert.deepStrictEqual(parseAndEval('bignumber(2).plus(3)'), math.bignumber(5))
assert.deepStrictEqual(parseAndEval('bignumber(2)["plus"](3)'), math.bignumber(5))
})
it('should invoke native methods on a number', function () {
assert.strictEqual(parseAndEval('(3).toString()'), '3')
assert.strictEqual(parseAndEval('(3.2).toFixed()'), '3')
})
it('should get nested object property with mixed dot- and index-notation', function () {
assert.deepStrictEqual(parseAndEval('obj.foo["bar"].baz', { obj: { foo: { bar: { baz: 2 } } } }), 2)
assert.deepStrictEqual(parseAndEval('obj["foo"].bar["baz"]', { obj: { foo: { bar: { baz: 2 } } } }), 2)
})
it('should set an object property with dot notation', function () {
let scope = { obj: {} }
parseAndEval('obj.foo = 2', scope)
assert.deepStrictEqual(scope, { obj: { foo: 2 } })
})
it('should set a nested object property with dot notation', function () {
let scope = { obj: { foo: {} } }
parseAndEval('obj.foo.bar = 2', scope)
assert.deepStrictEqual(scope, { obj: { foo: { bar: 2 } } })
})
it('should throw an error in case of invalid property with dot notation', function () {
assert.throws(function () { parseAndEval('obj. +foo') }, /SyntaxError: Property name expected after dot \(char 6\)/)
assert.throws(function () { parseAndEval('obj.["foo"]') }, /SyntaxError: Property name expected after dot \(char 5\)/)
})
it('should create an empty object', function () {
assert.deepStrictEqual(parseAndEval('{}'), {})
})
it('should spread a object over multiple lines', function () {
assert.deepStrictEqual(parseAndEval('{\na:2+3,\nb:"foo"\n}'), { a: 5, b: 'foo' })
})
it('should create an object with quoted keys', function () {
assert.deepStrictEqual(parseAndEval('{"a":2+3,"b":"foo"}'), { a: 5, b: 'foo' })
})
it('should create an object with unquoted keys', function () {
assert.deepStrictEqual(parseAndEval('{a:2+3,b:"foo"}'), { a: 5, b: 'foo' })
})
it('should create an object with child object', function () {
assert.deepStrictEqual(parseAndEval('{a:{b:2}}'), { a: { b: 2 } })
})
it('should get a property from a just created object', function () {
assert.deepStrictEqual(parseAndEval('{foo:2}["foo"]'), 2)
})
it('should parse an object containing a function assignment', function () {
const obj = parseAndEval('{f: f(x)=x^2}')
assert.deepStrictEqual(Object.keys(obj), ['f'])
assert.strictEqual(obj.f(2), 4)
})
it('should not parse a function assignment in an accessor node', function () {
assert.throws(function () {
let scope = {}
parseAndEval('a["b"](x)=x^2', scope)
}, /SyntaxError: Invalid left hand side of assignment operator =/)
})
it('should parse an object containing a variable assignment', function () {
let scope = {}
assert.deepStrictEqual(parseAndEval('{f: a=42}', scope), { f: 42 })
assert.strictEqual(scope.a, 42)
})
it('should throw an exception in case of invalid object key', function () {
assert.throws(function () { parseAndEval('{a b: 2}') }, /SyntaxError: Colon : expected after object key \(char 4\)/)
assert.throws(function () { parseAndEval('{a: }') }, /SyntaxError: Value expected \(char 5\)/)
})
})
describe('boolean', function () {
it('should parse boolean values', function () {
assert.strictEqual(parseAndEval('true'), true)
assert.strictEqual(parseAndEval('false'), false)
})
})
describe('constants', function () {
it('should parse symbolic constants', function () {
assert.strictEqual(parse('i').type, 'SymbolNode')
assert.deepStrictEqual(parseAndEval('i'), new Complex(0, 1))
approx.equal(parseAndEval('pi'), Math.PI)
approx.equal(parseAndEval('e'), Math.E)
})
it('should parse constants', function () {
assert.strictEqual(parse('true').type, 'ConstantNode')
assert.deepStrictEqual(parse('true'), createConstantNode(true))
assert.deepStrictEqual(parse('false'), createConstantNode(false))
assert.deepStrictEqual(parse('null'), createConstantNode(null))
assert.deepStrictEqual(parse('undefined'), createConstantNode(undefined))
})
it('should parse numeric constants', function () {
const nanConstantNode = parse('NaN')
assert.deepStrictEqual(nanConstantNode.type, 'ConstantNode')
assert.ok(isNaN(nanConstantNode.value))
assert.deepStrictEqual(parse('Infinity'), createConstantNode(Infinity))
})
it('should evaluate constants', function () {
// Do these tests really belong in constants.test.js ?
approx.equal(math.sin(math.pi / 2), 1)
assert.deepStrictEqual(math.round(math.add(1, math.pow(math.e, math.multiply(math.pi, math.i))), 5), math.complex(0))
assert.deepStrictEqual(math.round(math.evaluate('1+e^(pi*i)'), 5), math.complex(0))
assert.deepStrictEqual(math.sqrt(-1), math.i)
assert.deepStrictEqual(math.evaluate('i'), math.complex(0, 1))
assert.strictEqual(math.evaluate('true'), true)
assert.strictEqual(math.evaluate('false'), false)
})
// helper function to create a ConstantNode with empty comment
function createConstantNode (value) {
const c = new ConstantNode(value)
c.comment = ''
return c
}
})
describe('variables', function () {
it('should parse valid variable assignments', function () {
let scope = {}
assert.strictEqual(parseAndEval('a = 0.75', scope), 0.75)
assert.strictEqual(parseAndEval('a + 2', scope), 2.75)
assert.strictEqual(parseAndEval('a = 2', scope), 2)
assert.strictEqual(parseAndEval('a + 2', scope), 4)
approx.equal(parseAndEval('pi * 2', scope), 6.283185307179586)
})
it('should throw an error on undefined symbol', function () {
assert.throws(function () { parseAndEval('qqq + 2') })
})
it('should throw an error on invalid assignments', function () {
// assert.throws(function () {parseAndEval('sin(2) = 0.75')}, SyntaxError); // TODO: should this throw an exception?
assert.throws(function () { parseAndEval('sin + 2 = 3') }, SyntaxError)
})
it('should parse nested assignments', function () {
let scope = {}
assert.strictEqual(parseAndEval('c = d = (e = 4.5)', scope), 4.5)
assert.strictEqual(scope.c, 4.5)
assert.strictEqual(scope.d, 4.5)
assert.strictEqual(scope.e, 4.5)
assert.deepStrictEqual(parseAndEval('a = [1,2,f=3]', scope), math.matrix([1, 2, 3]))
assert.strictEqual(scope.f, 3)
assert.strictEqual(parseAndEval('2 + (g = 3 + 4)', scope), 9)
assert.strictEqual(scope.g, 7)
})
it('should parse variable assignment inside a function call', function () {
let scope = {}
assert.deepStrictEqual(parseAndEval('sqrt(x=4)', scope), 2)
assert.deepStrictEqual(scope, { x: 4 })
})
it('should parse variable assignment inside an accessor', function () {
let scope = { A: [10, 20, 30] }
assert.deepStrictEqual(parseAndEval('A[x=2]', scope), 20)
assert.deepStrictEqual(scope, { A: [10, 20, 30], x: 2 })
})
})
describe('functions', function () {
it('should parse functions', function () {
assert.strictEqual(parseAndEval('sqrt(4)'), 2)
assert.strictEqual(parseAndEval('sqrt(6+3)'), 3)
assert.strictEqual(parseAndEval('atan2(2,2)'), 0.7853981633974483)
assert.deepStrictEqual(parseAndEval('sqrt(-4)'), new Complex(0, 2))
assert.strictEqual(parseAndEval('abs(-4.2)'), 4.2)
assert.strictEqual(parseAndEval('add(2, 3)'), 5)
approx.deepEqual(parseAndEval('1+exp(pi*i)'), new Complex(0, 0))
assert.strictEqual(parseAndEval('unequal(2, 3)'), true)
})
it('should get a subset of a matrix returned by a function', function () {
let scope = {
test: function () {
return [1, 2, 3, 4]
}
}
assert.strictEqual(parseAndEval('test()[2]', scope), 2)
})
it('should parse column function', function () {
const a = [
[0, 2, 0, 0, 0],
[0, 1, 0, 2, 4],
[0, 0, 0, 0, 0],
[8, 4, 0, 3, 0],
[0, 0, 0, 6, 0]
]
const m = math.matrix(a)
const c = math.matrix([[2], [1], [0], [4], [0]])
let scope = {
test: function () {
return m
}
}
assert.deepStrictEqual(parseAndEval('column(test(),2)', scope), c)
})
it('should parse row function', function () {
const a = [
[0, 2, 0, 0, 0],
[0, 1, 0, 2, 4],
[0, 0, 0, 0, 0],
[8, 4, 0, 3, 0],
[0, 0, 0, 6, 0]
]
const m = math.matrix(a)
const r = math.matrix([[0, 1, 0, 2, 4]])
let scope = {
test: function () {
return m
}
}
assert.deepStrictEqual(parseAndEval('row(test(),2)', scope), r)
})
it('should parse functions without parameters', function () {
assert.strictEqual(parseAndEval('r()', { r: function () { return 2 } }), 2)
})
it('should parse function assignments', function () {
let scope = {}
parseAndEval('x=100', scope) // for testing scoping of the function variables
assert.strictEqual(parseAndEval('f(x) = x^2', scope).syntax, 'f(x)')
assert.strictEqual(parseAndEval('f(3)', scope), 9)
assert.strictEqual(scope.f(3), 9)
assert.strictEqual(scope.x, 100)
assert.strictEqual(parseAndEval('g(x, y) = x^y', scope).syntax, 'g(x, y)')
assert.strictEqual(parseAndEval('g(4,5)', scope), 1024)
assert.strictEqual(scope.g(4, 5), 1024)
})
it('should correctly evaluate variables in assigned functions', function () {
let scope = {}
assert.strictEqual(parseAndEval('a = 3', scope), 3)
assert.strictEqual(parseAndEval('f(x) = a * x', scope).syntax, 'f(x)')
assert.strictEqual(parseAndEval('f(2)', scope), 6)
assert.strictEqual(parseAndEval('a = 5', scope), 5)
assert.strictEqual(parseAndEval('f(2)', scope), 10)
assert.strictEqual(parseAndEval('g(x) = x^q', scope).syntax, 'g(x)')
assert.strictEqual(parseAndEval('q = 4/2', scope), 2)
assert.strictEqual(parseAndEval('g(3)', scope), 9)
})
it('should throw an error for undefined variables in an assigned function', function () {
let scope = {}
assert.strictEqual(parseAndEval('g(x) = x^q', scope).syntax, 'g(x)')
assert.throws(function () {
parseAndEval('g(3)', scope)
}, function (err) {
return (err instanceof Error) && (err.toString() === 'Error: Undefined symbol q')
})
})
it('should throw an error on invalid left hand side of a function assignment', function () {
assert.throws(function () {
let scope = {}
parseAndEval('g(x, 2) = x^2', scope)
}, SyntaxError)
assert.throws(function () {
let scope = {}
parseAndEval('2(x, 2) = x^2', scope)
}, SyntaxError)
})
})
describe('parentheses', function () {
it('should parse parentheses overriding the default precedence', function () {
approx.equal(parseAndEval('2 - (2 - 2)'), 2)
approx.equal(parseAndEval('2 - ((2 - 2) - 2)'), 4)
approx.equal(parseAndEval('3 * (2 + 3)'), 15)
approx.equal(parseAndEval('(2 + 3) * 3'), 15)
})
it('should throw an error in case of unclosed parentheses', function () {
assert.throws(function () { parseAndEval('3 * (1 + 2') }, /Parenthesis \) expected/)
})
})
describe('operators', function () {
it('should parse operations', function () {
approx.equal(parseAndEval('(2+3)/4'), 1.25)
approx.equal(parseAndEval('2+3/4'), 2.75)
assert.strictEqual(parse('0 + 2').toString(), '0 + 2')
})
it('should parse add +', function () {
assert.strictEqual(parseAndEval('2 + 3'), 5)
assert.strictEqual(parseAndEval('2 + 3 + 4'), 9)
assert.strictEqual(parseAndEval('2.+3'), 5) // test whether the decimal mark isn't confused
})
it('should parse divide /', function () {
assert.strictEqual(parseAndEval('4 / 2'), 2)
assert.strictEqual(parseAndEval('8 / 2 / 2'), 2)
})
it('should parse dotDivide ./', function () {
assert.strictEqual(parseAndEval('4./2'), 2)
assert.deepStrictEqual(parseAndEval('4./[2,4]'), math.matrix([2, 1]))
assert.strictEqual(parseAndEval('4 ./ 2'), 2)
assert.strictEqual(parseAndEval('8 ./ 4 / 2'), 1)
assert.strictEqual(parseAndEval('8 ./ 2 / 2'), 2)
assert.deepStrictEqual(parseAndEval('[1,2,3] ./ [1,2,3]'), math.matrix([1, 1, 1]))
})
it('should parse dotMultiply .*', function () {
approx.deepEqual(parseAndEval('2.*3'), 6)
approx.deepEqual(parseAndEval('2e3.*3'), 6e3)
approx.deepEqual(parseAndEval('2 .* 3'), 6)
approx.deepEqual(parseAndEval('4 .* 2'), 8)
approx.deepEqual(parseAndEval('8 .* 2 .* 2'), 32)
assert.deepStrictEqual(parseAndEval('a=3; a.*4'), new ResultSet([12]))
assert.deepStrictEqual(parseAndEval('[1,2,3] .* [1,2,3]'), math.matrix([1, 4, 9]))
})
it('should parse dotPower .^', function () {
approx.deepEqual(parseAndEval('2.^3'), 8)
approx.deepEqual(parseAndEval('2 .^ 3'), 8)
approx.deepEqual(parseAndEval('-2.^2'), -4) // -(2^2)
approx.deepEqual(parseAndEval('2.^3.^4'), 2.41785163922926e+24) // 2^(3^4)
assert.deepStrictEqual(parseAndEval('[2,3] .^ [2,3]'), math.matrix([4, 27]))
})
it('should parse equal ==', function () {
assert.strictEqual(parseAndEval('2 == 3'), false)
assert.strictEqual(parseAndEval('2 == 2'), true)
assert.deepStrictEqual(parseAndEval('[2,3] == [2,4]'), math.matrix([true, false]))
})
it('should parse larger >', function () {
assert.strictEqual(parseAndEval('2 > 3'), false)
assert.strictEqual(parseAndEval('2 > 2'), false)
assert.strictEqual(parseAndEval('2 > 1'), true)
})
it('should parse largerEq >=', function () {
assert.strictEqual(parseAndEval('2 >= 3'), false)
assert.strictEqual(parseAndEval('2 >= 2'), true)
assert.strictEqual(parseAndEval('2 >= 1'), true)
})
it('should parse chained conditionals', function () {
assert.strictEqual(parseAndEval('2 < 3 + 2 < 5 * 10'), true)
assert.strictEqual(parseAndEval('2 < 3 < 4'), true)
assert.strictEqual(parseAndEval('2 > 3 > 4'), false)
assert.strictEqual(parseAndEval('2 < 3 > 4'), false)
assert.strictEqual(parseAndEval('2 > 3 < 4'), false)
assert.strictEqual(parseAndEval('2 < 4 > 3 <= 5 >= 5'), true)
})
it('should parse mod %', function () {
approx.equal(parseAndEval('8 % 3'), 2)
})
it('should parse operator mod', function () {
approx.equal(parseAndEval('8 mod 3'), 2)
})
it('should parse multiply *', function () {
approx.equal(parseAndEval('4 * 2'), 8)
approx.equal(parseAndEval('8 * 2 * 2'), 32)
})
it('should parse implicit multiplication', function () {
assert.strictEqual(parseAndStringifyWithParens('4a'), '4 a')
assert.strictEqual(parseAndStringifyWithParens('4 a'), '4 a')
assert.strictEqual(parseAndStringifyWithParens('a b'), 'a b')
assert.strictEqual(parseAndStringifyWithParens('2a b'), '(2 a) b')
assert.strictEqual(parseAndStringifyWithParens('2a * b'), '(2 a) * b')
assert.strictEqual(parseAndStringifyWithParens('2a / b'), '(2 a) / b')
assert.strictEqual(parseAndStringifyWithParens('a b c'), '(a b) c')
assert.strictEqual(parseAndStringifyWithParens('a b*c'), '(a b) * c')
assert.strictEqual(parseAndStringifyWithParens('a*b c'), 'a * (b c)')
assert.strictEqual(parseAndStringifyWithParens('a/b c'), 'a / (b c)')
assert.strictEqual(parseAndStringifyWithParens('1/2a'), '(1 / 2) a')
assert.strictEqual(parseAndStringifyWithParens('8/2a/2'), '((8 / 2) a) / 2')
assert.strictEqual(parseAndStringifyWithParens('8/2a*2'), '((8 / 2) a) * 2')
assert.strictEqual(parseAndStringifyWithParens('4*2a'), '4 * (2 a)')
assert.strictEqual(parseAndStringifyWithParens('3!10'), '(3!) 10')
assert.strictEqual(parseAndStringifyWithParens('(2+3)a'), '(2 + 3) a')
assert.strictEqual(parseAndStringifyWithParens('(2+3)2'), '(2 + 3) 2')
assert.strictEqual(parseAndStringifyWithParens('(2)(3)+4'), '(2 3) + 4')
assert.strictEqual(parseAndStringifyWithParens('2(3+4)'), '2 (3 + 4)')
assert.strictEqual(parseAndStringifyWithParens('(2+3)-2'), '(2 + 3) - 2') // no implicit multiplication, just a unary minus
assert.strictEqual(parseAndStringifyWithParens('a(2+3)'), 'a(2 + 3)') // function call
assert.strictEqual(parseAndStringifyWithParens('a.b(2+3)'), 'a.b(2 + 3)') // function call
assert.strictEqual(parseAndStringifyWithParens('(2+3)(4+5)'), '(2 + 3) (4 + 5)') // implicit multiplication
assert.strictEqual(parseAndStringifyWithParens('(2+3)(4+5)(3-1)'), '((2 + 3) (4 + 5)) (3 - 1)') // implicit multiplication
assert.strictEqual(parseAndStringifyWithParens('(2a)^3'), '(2 a) ^ 3')
assert.strictEqual(parseAndStringifyWithParens('2a^3'), '2 (a ^ 3)')
assert.strictEqual(parseAndStringifyWithParens('2(a)^3'), '2 (a ^ 3)')
assert.strictEqual(parseAndStringifyWithParens('(2)a^3'), '2 (a ^ 3)')
assert.strictEqual(parseAndStringifyWithParens('2^3a'), '(2 ^ 3) a')
assert.strictEqual(parseAndStringifyWithParens('2^3(a)'), '(2 ^ 3) a')
assert.strictEqual(parseAndStringifyWithParens('2^(3)(a)'), '(2 ^ 3) a')
assert.strictEqual(parseAndStringifyWithParens('sqrt(2a)'), 'sqrt(2 a)')
assert.deepStrictEqual(parseAndEval('[2, 3] 2'), math.matrix([4, 6]))
assert.deepStrictEqual(parseAndEval('[2, 3] a', { a: 2 }), math.matrix([4, 6]))
assert.deepStrictEqual(parseAndEval('A [2,2]', { A: [[1, 2], [3, 4]] }), 4) // index
assert.deepStrictEqual(parseAndEval('(A) [2,2]', { A: [[1, 2], [3, 4]] }), 4) // index
assert.deepStrictEqual(parseAndEval('[1,2;3,4] [2,2]'), 4) // index
assert.deepStrictEqual(parseAndEval('([1,2;3,4])[2,2]'), 4) // index
assert.throws(function () { parseAndEval('2[1,2,3]') }, /Unexpected operator/)// index
})
it('should tell the OperatorNode about implicit multiplications', function () {
assert.strictEqual(parse('2 + 3').implicit, false)
assert.strictEqual(parse('4 * a').implicit, false)
assert.strictEqual(parse('4a').implicit, true)
assert.strictEqual(parse('4 a').implicit, true)
assert.strictEqual(parse('a b').implicit, true)
assert.strictEqual(parse('2a b').implicit, true)
assert.strictEqual(parse('a b c').implicit, true)
assert.strictEqual(parse('(2+3)a').implicit, true)
assert.strictEqual(parse('(2+3)2').implicit, true)
assert.strictEqual(parse('2(3+4)').implicit, true)
})
it('should correctly order consecutive multiplications and implicit multiplications', function () {
assert.strictEqual(parseAndStringifyWithParens('9km*3km'), '(9 km) * (3 km)')
})
it('should follow precedence rules for implicit multiplication and division', function () {
assert.strictEqual(parseAndStringifyWithParens('2 / 3 x'), '(2 / 3) x')
assert.strictEqual(parseAndStringifyWithParens('2.5 / 5 kg'), '(2.5 / 5) kg')
assert.strictEqual(parseAndStringifyWithParens('2.5 / 5 x y'), '((2.5 / 5) x) y')
assert.strictEqual(parseAndStringifyWithParens('2 x / 5 y'), '(2 x) / (5 y)')
assert.strictEqual(parseAndStringifyWithParens('17 h / 1 h'), '(17 h) / (1 h)')
assert.strictEqual(parseAndStringifyWithParens('1 / 2 x'), '(1 / 2) x')
assert.strictEqual(parseAndStringifyWithParens('1 / 2 * x'), '(1 / 2) * x')
assert.strictEqual(parseAndStringifyWithParens('1 / 2 x y'), '((1 / 2) x) y')
assert.strictEqual(parseAndStringifyWithParens('1 / 2 (x y)'), '(1 / 2) (x y)')
assert.strictEqual(parseAndStringifyWithParens('1 / 2x * y'), '((1 / 2) x) * y')
assert.strictEqual(parseAndStringifyWithParens('y / 2 x'), 'y / (2 x)')
assert.strictEqual(parseAndStringifyWithParens('y / 2 * x'), '(y / 2) * x')
assert.strictEqual(parseAndStringifyWithParens('y / 2 x w'), 'y / ((2 x) w)')
assert.strictEqual(parseAndStringifyWithParens('y / v x w'), 'y / ((v x) w)')
assert.strictEqual(parseAndStringifyWithParens('1 h / (1+1) h'), '(1 h) / ((1 + 1) h)')
assert.strictEqual(parseAndStringifyWithParens('4 lb + 1/2 lb'), '(4 lb) + ((1 / 2) lb)')
assert.strictEqual(parseAndStringifyWithParens('4 lb + 1 lb^2 / 2 lb'), '(4 lb) + ((1 (lb ^ 2)) / (2 lb))')
assert.strictEqual(parseAndStringifyWithParens('1 m/s^2 + 1 m / 2 s^2'), '((1 m) / (s ^ 2)) + ((1 m) / (2 (s ^ 2)))')
assert.strictEqual(parseAndStringifyWithParens('8.314 J/mol K'), '(8.314 J) / (mol K)')
})
it('should throw an error when having an implicit multiplication between two numbers', function () {
assert.throws(function () { math.parse('2 3') }, /Unexpected part "3"/)
assert.throws(function () { math.parse('2 * 3 4') }, /Unexpected part "4"/)
assert.throws(function () { math.parse('2 * 3 4 * 5') }, /Unexpected part "4"/)
assert.throws(function () { math.parse('2 / 3 4 5') }, /Unexpected part "4"/)
assert.throws(function () { math.parse('2 + 3 4') }, /Unexpected part "4"/)
assert.throws(function () { math.parse('-2 2') }, /Unexpected part "2"/)
assert.throws(function () { math.parse('+3 3') }, /Unexpected part "3"/)
assert.throws(function () { math.parse('2^3 4') }, /Unexpected part "4"/)
})
it('should parse pow ^', function () {
approx.equal(parseAndEval('2^3'), 8)
approx.equal(parseAndEval('-2^2'), -4) // -(2^2)
approx.equal(parseAndEval('2^3^4'), 2.41785163922926e+24) // 2^(3^4)
})
it('should parse smaller <', function () {
assert.strictEqual(parseAndEval('2 < 3'), true)
assert.strictEqual(parseAndEval('2 < 2'), false)
assert.strictEqual(parseAndEval('2 < 1'), false)
})
it('should parse smallerEq <=', function () {
assert.strictEqual(parseAndEval('2 <= 3'), true)
assert.strictEqual(parseAndEval('2 <= 2'), true)
assert.strictEqual(parseAndEval('2 <= 1'), false)
})
it('should parse bitwise and &', function () {
assert.strictEqual(parseAndEval('2 & 6'), 2)
assert.strictEqual(parseAndEval('5 & 3'), 1)
assert.strictEqual(parseAndEval('true & true'), 1)
assert.strictEqual(parseAndEval('true & false'), 0)
assert.strictEqual(parseAndEval('false & true'), 0)
assert.strictEqual(parseAndEval('false & false'), 0)
})
it('should parse bitwise xor ^|', function () {
assert.strictEqual(parseAndEval('2 ^| 6'), 4)
assert.strictEqual(parseAndEval('5 ^| 3'), 6)
assert.strictEqual(parseAndEval('true ^| true'), 0)
assert.strictEqual(parseAndEval('true ^| false'), 1)
assert.strictEqual(parseAndEval('false ^| true'), 1)
assert.strictEqual(parseAndEval('false ^| false'), 0)
})
it('should parse bitwise or |', function () {
assert.strictEqual(parseAndEval('2 | 6'), 6)
assert.strictEqual(parseAndEval('5 | 3'), 7)
assert.strictEqual(parseAndEval('true | true'), 1)
assert.strictEqual(parseAndEval('true | false'), 1)
assert.strictEqual(parseAndEval('false | true'), 1)
assert.strictEqual(parseAndEval('false | false'), 0)
})
it('should parse bitwise left shift <<', function () {
assert.strictEqual(parseAndEval('23 << 1'), 46)
})
it('should parse bitwise right arithmetic shift >>', function () {
assert.strictEqual(parseAndEval('32 >> 4'), 2)
assert.strictEqual(parseAndEval('-12 >> 2'), -3)
})
it('should parse bitwise right logical shift >>>', function () {
assert.strictEqual(parseAndEval('32 >>> 4'), 2)
assert.strictEqual(parseAndEval('-12 >>> 2'), 1073741821)
})
it('should parse logical and', function () {
assert.strictEqual(parseAndEval('2 and 6'), true)
assert.strictEqual(parseAndEval('2 and 0'), false)
assert.strictEqual(parseAndEval('true and true'), true)
assert.strictEqual(parseAndEval('true and false'), false)
assert.strictEqual(parseAndEval('false and true'), false)
assert.strictEqual(parseAndEval('false and false'), false)
})
it('should parse logical xor', function () {
assert.strictEqual(parseAndEval('2 xor 6'), false)
assert.strictEqual(parseAndEval('2 xor 0'), true)
assert.strictEqual(parseAndEval('true xor true'), false)
assert.strictEqual(parseAndEval('true xor false'), true)
assert.strictEqual(parseAndEval('false xor true'), true)
assert.strictEqual(parseAndEval('false xor false'), false)
})
it('should parse logical or', function () {
assert.strictEqual(parseAndEval('2 or 6'), true)
assert.strictEqual(parseAndEval('2 or 0'), true)
assert.strictEqual(parseAndEval('true or true'), true)
assert.strictEqual(parseAndEval('true or false'), true)
assert.strictEqual(parseAndEval('false or true'), true)
assert.strictEqual(parseAndEval('false or false'), false)
})
it('should parse logical not', function () {
assert.strictEqual(parseAndEval('not 2'), false)
assert.strictEqual(parseAndEval('not not 2'), true)
assert.strictEqual(parseAndEval('not not not 2'), false)
assert.strictEqual(parseAndEval('not true'), false)
assert.strictEqual(parseAndEval('4*not 2'), 0)
assert.strictEqual(parseAndEval('4 * not 2'), 0)
assert.strictEqual(parseAndEval('4-not 2'), 4)
assert.strictEqual(parseAndEval('4 - not 2'), 4)
assert.strictEqual(parseAndEval('4+not 2'), 4)
assert.strictEqual(parseAndEval('4 + not 2'), 4)
assert.strictEqual(parseAndEval('10+not not 3'), 11)
})
it('should parse minus -', function () {
assert.strictEqual(parseAndEval('4 - 2'), 2)
assert.strictEqual(parseAndEval('8 - 2 - 2'), 4)
})
it('should parse unary minus -', function () {
assert.strictEqual(parseAndEval('-2'), -2)
assert.strictEqual(parseAndEval('--2'), 2)
assert.strictEqual(parseAndEval('---2'), -2)
assert.strictEqual(parseAndEval('4*-2'), -8)
assert.strictEqual(parseAndEval('4 * -2'), -8)
assert.strictEqual(parseAndEval('4+-2'), 2)
assert.strictEqual(parseAndEval('4 + -2'), 2)
assert.strictEqual(parseAndEval('4--2'), 6)
assert.strictEqual(parseAndEval('4 - -2'), 6)
assert.strictEqual(parseAndEval('5-3'), 2)
assert.strictEqual(parseAndEval('5--3'), 8)
assert.strictEqual(parseAndEval('5---3'), 2)
assert.strictEqual(parseAndEval('5+---3'), 2)
assert.strictEqual(parseAndEval('5----3'), 8)
assert.strictEqual(parseAndEval('5+--(2+1)'), 8)
})
it('should parse unary +', function () {
assert.strictEqual(parseAndEval('+2'), 2)
assert.strictEqual(parseAndEval('++2'), 2)
assert.strictEqual(parseAndEval('+++2'), 2)
assert.strictEqual(parseAndEval('+true'), 1)
assert.strictEqual(parseAndEval('4*+2'), 8)
assert.strictEqual(parseAndEval('4 * +2'), 8)
assert.strictEqual(parseAndEval('4-+2'), 2)
assert.strictEqual(parseAndEval('4 - +2'), 2)
assert.strictEqual(parseAndEval('4++2'), 6)
assert.strictEqual(parseAndEval('4 + +2'), 6)
assert.strictEqual(parseAndEval('5+3'), 8)
assert.strictEqual(parseAndEval('5++3'), 8)
})
it('should parse unary ~', function () {
assert.strictEqual(parseAndEval('~2'), -3)
assert.strictEqual(parseAndEval('~~2'), 2)
assert.strictEqual(parseAndEval('~~~2'), -3)
assert.strictEqual(parseAndEval('~true'), -2)
assert.strictEqual(parseAndEval('4*~2'), -12)
assert.strictEqual(parseAndEval('4 * ~2'), -12)
assert.strictEqual(parseAndEval('4-~2'), 7)
assert.strictEqual(parseAndEval('4 - ~2'), 7)
assert.strictEqual(parseAndEval('4+~2'), 1)
assert.strictEqual(parseAndEval('4 + ~2'), 1)
assert.strictEqual(parseAndEval('10+~~3'), 13)
})
it('should parse unary plus and minus +, -', function () {
assert.strictEqual(parseAndEval('-+2'), -2)
assert.strictEqual(parseAndEval('-+-2'), 2)
assert.strictEqual(parseAndEval('+-+-2'), 2)
assert.strictEqual(parseAndEval('+-2'), -2)
assert.strictEqual(parseAndEval('+-+2'), -2)
assert.strictEqual(parseAndEval('-+-+2'), 2)
})
it('should parse unary plus and bitwise not +, ~', function () {
assert.strictEqual(parseAndEval('~+2'), -3)
assert.strictEqual(parseAndEval('~+~2'), 2)
assert.strictEqual(parseAndEval('+~+~2'), 2)
assert.strictEqual(parseAndEval('+~2'), -3)
assert.strictEqual(parseAndEval('+~+2'), -3)
assert.strictEqual(parseAndEval('~+~+2'), 2)
})
it('should parse unary minus and bitwise not -, ~', function () {
assert.strictEqual(parseAndEval('~-2'), 1)
assert.strictEqual(parseAndEval('~-~2'), -4)
assert.strictEqual(parseAndEval('-~-~2'), 4)
assert.strictEqual(parseAndEval('-~2'), 3)
assert.strictEqual(parseAndEval('-~-2'), -1)
assert.strictEqual(parseAndEval('~-~-2'), 0)
})
it('should parse unary plus + and logical not', function () {
assert.strictEqual(parseAndEval('not+2'), false)
assert.strictEqual(parseAndEval('not + not 2'), true)
assert.strictEqual(parseAndEval('+not+not 2'), 1)
assert.strictEqual(parseAndEval('+ not 2'), 0)
assert.strictEqual(parseAndEval('+ not +2'), 0)
assert.strictEqual(parseAndEval('not + not +2'), true)
})
it('should parse bitwise not ~ and logical not', function () {
assert.strictEqual(parseAndEval('~not 2'), -1)
assert.strictEqual(parseAndEval('~not~2'), -1)
assert.strictEqual(parseAndEval('not~not~2'), false)
assert.strictEqual(parseAndEval('not~2'), false)
assert.strictEqual(parseAndEval('not~not 2'), false)
assert.strictEqual(parseAndEval('~not~not 2'), -1)
})
it('should parse unary minus and logical not', function () {
assert.strictEqual(parseAndEval('not-2'), false)
assert.strictEqual(parseAndEval('not-not 2'), true)
assert.strictEqual(parseAndEval('-not-not 2'), -1)
assert.strictEqual(parseAndEval('-not 2'), -0)
assert.strictEqual(parseAndEval('-not-2'), -0)
assert.strictEqual(parseAndEval('not-not-2'), true)
})
it('should parse unequal !=', function () {
assert.strictEqual(parseAndEval('2 != 3'), true)
assert.strictEqual(parseAndEval('2 != 2'), false)
assert.deepStrictEqual(parseAndEval('[2,3] != [2,4]'), math.matrix([false, true]))
})
it('should parse conditional expression a ? b : c', function () {
assert.strictEqual(parseAndEval('2 ? true : false'), true)
assert.strictEqual(parseAndEval('0 ? true : false'), false)
assert.strictEqual(parseAndEval('false ? true : false'), false)
assert.strictEqual(parseAndEval('2 > 0 ? 1 : 2 < 0 ? -1 : 0'), 1)
assert.strictEqual(parseAndEval('(2 > 0 ? 1 : 2 < 0) ? -1 : 0'), -1)
assert.strictEqual(parseAndEval('-2 > 0 ? 1 : -2 < 0 ? -1 : 0'), -1)
assert.strictEqual(parseAndEval('0 > 0 ? 1 : 0 < 0 ? -1 : 0'), 0)
})
it('should lazily evaluate conditional expression a ? b : c', function () {
let scope = {}
math.parse('true ? (a = 2) : (b = 2)').compile().evaluate(scope)
assert.deepStrictEqual(scope, { a: 2 })
})
it('should throw an error when false part of conditional expression is missing', function () {
assert.throws(function () { parseAndEval('2 ? true') }, /False part of conditional expression expected/)
})
it('should parse : (range)', function () {
assert.ok(parseAndEval('2:5') instanceof Matrix)
assert.deepStrictEqual(parseAndEval('2:5'), math.matrix([2, 3, 4, 5]))
assert.deepStrictEqual(parseAndEval('10:-2:0'), math.matrix([10, 8, 6, 4, 2, 0]))
assert.deepStrictEqual(parseAndEval('2:4.0'), math.matrix([2, 3, 4]))
assert.deepStrictEqual(parseAndEval('2:4.5'), math.matrix([2, 3, 4]))
assert.deepStrictEqual(parseAndEval('2:4.1'), math.matrix([2, 3, 4]))
assert.deepStrictEqual(parseAndEval('2:3.9'), math.matrix([2, 3]))
assert.deepStrictEqual(parseAndEval('2:3.5'), math.matrix([2, 3]))
assert.deepStrictEqual(parseAndEval('3:-1:0.5'), math.matrix([3, 2, 1]))
assert.deepStrictEqual(parseAndEval('3:-1:0.5'), math.matrix([3, 2, 1]))
assert.deepStrictEqual(parseAndEval('3:-1:0.1'), math.matrix([3, 2, 1]))
assert.deepStrictEqual(parseAndEval('3:-1:-0.1'), math.matrix([3, 2, 1, 0]))
})
it('should parse to', function () {
approx.deepEqual(parseAndEval('2.54 cm to inch'), math.unit(1, 'inch').to('inch'))
approx.deepEqual(parseAndEval('2.54 cm + 2 inch to foot'), math.unit(0.25, 'foot').to('foot'))
})
it('should parse in', function () {
approx.deepEqual(parseAndEval('2.54 cm in inch'), math.unit(1, 'inch').to('inch'))
})
it('should parse factorial !', function () {
assert.deepStrictEqual(parseAndEval('5!'), 120)
assert.deepStrictEqual(parseAndEval('[1,2,3,4]!'), math.matrix([1, 2, 6, 24]))
assert.deepStrictEqual(parseAndEval('4!+2'), 26)
assert.deepStrictEqual(parseAndEval('4!-2'), 22)
assert.deepStrictEqual(parseAndEval('4!*2'), 48)
assert.deepStrictEqual(parseAndEval('3!!'), 720)
assert.deepStrictEqual(parseAndEval('[1,2;3,1]!\'!'), math.matrix([[1, 720], [2, 1]]))
assert.deepStrictEqual(parseAndEval('[4,5]![2]'), 120) // index [2]
})
it('should parse transpose \'', function () {
assert.deepStrictEqual(parseAndEval('23\''), 23)
assert.deepStrictEqual(parseAndEval('[1,2,3;4,5,6]\''), math.matrix([[1, 4], [2, 5], [3, 6]]))
assert.ok(parseAndEval('[1,2,3;4,5,6]\'') instanceof Matrix)
assert.deepStrictEqual(parseAndEval('[1:5]'), math.matrix([[1, 2, 3, 4, 5]]))
assert.deepStrictEqual(parseAndEval('[1:5]\''), math.matrix([[1], [2], [3], [4], [5]]))
assert.deepStrictEqual(parseAndEval('size([1:5])'), math.matrix([1, 5]))
assert.deepStrictEqual(parseAndEval('[1,2;3,4]\''), math.matrix([[1, 3], [2, 4]]))
})
describe('operator precedence', function () {
it('should respect precedence of plus and minus', function () {
assert.strictEqual(parseAndEval('4-2+3'), 5)
assert.strictEqual(parseAndEval('4-(2+3)'), -1)
assert.strictEqual(parseAndEval('4-2-3'), -1)
assert.strictEqual(parseAndEval('4-(2-3)'), 5)
})
it('should respect precedence of plus/minus and multiply/divide', function () {
assert.strictEqual(parseAndEval('2+3*4'), 14)
assert.strictEqual(parseAndEval('2*3+4'), 10)
})
it('should respect precedence of plus/minus and pow', function () {
assert.strictEqual(parseAndEval('2+3^2'), 11)
assert.strictEqual(parseAndEval('3^2+2'), 11)
assert.strictEqual(parseAndEval('8-2^2'), 4)
assert.strictEqual(parseAndEval('4^2-2'), 14)
})
it('should respect precedence of multiply/divide and pow', function () {
assert.strictEqual(parseAndEval('2*3^2'), 18)
assert.strictEqual(parseAndEval('3^2*2'), 18)
assert.strictEqual(parseAndEval('8/2^2'), 2)
assert.strictEqual(parseAndEval('4^2/2'), 8)
})
it('should respect precedence of pow', function () {
assert.strictEqual(parseAndEval('2^3'), 8)
assert.strictEqual(parseAndEval('2^3^4'), Math.pow(2, Math.pow(3, 4)))
assert.strictEqual(parseAndEval('1.5^1.5^1.5'), parseAndEval('1.5^(1.5^1.5)'))
assert.strictEqual(parseAndEval('1.5^1.5^1.5^1.5'), parseAndEval('1.5^(1.5^(1.5^1.5))'))
})
it('should respect precedence of unary operations and pow', function () {
assert.strictEqual(parseAndEval('-3^2'), -9)
assert.strictEqual(parseAndEval('(-3)^2'), 9)
assert.strictEqual(parseAndEval('2^-2'), 0.25)
assert.strictEqual(parseAndEval('2^(-2)'), 0.25)
assert.strictEqual(parseAndEval('+3^2'), 9)
assert.strictEqual(parseAndEval('(+3)^2'), 9)
assert.strictEqual(parseAndEval('2^(+2)'), 4)
assert.strictEqual(parseAndEval('~3^2'), -10)
assert.strictEqual(parseAndEval('(~3)^2'), 16)
assert.strictEqual(parseAndEval('2^~2'), 0.125)
assert.strictEqual(parseAndEval('2^(~2)'), 0.125)
assert.strictEqual(parseAndEval('not 3^2'), false)
assert.strictEqual(parseAndEval('(not 3)^2'), 0)
assert.strictEqual(parseAndEval('2^not 2'), 1)
assert.strictEqual(parseAndEval('2^(not 2)'), 1)
})
it('should respect precedence of factorial and pow', function () {
assert.strictEqual(parseAndEval('2^3!'), 64)
assert.strictEqual(parseAndEval('2^(3!)'), 64)
assert.strictEqual(parseAndEval('3!^2'), 36)
})
it('should respect precedence of factorial and unary operations', function () {
assert.strictEqual(parseAndEval('-4!'), -24)
assert.strictEqual(parseAndEval('-(4!)'), -24)
assert.strictEqual(parseAndEval('3!+2'), 8)
assert.strictEqual(parseAndEval('(3!)+2'), 8)
assert.strictEqual(parseAndEval('+4!'), 24)
assert.strictEqual(parseAndEval('~4!+1'), -24)
assert.strictEqual(parseAndEval('~(4!)+1'), -24)
assert.strictEqual(parseAndEval('not 4!'), false)
assert.strictEqual(parseAndEval('not not 4!'), true)
assert.strictEqual(parseAndEval('not(4!)'), false)
assert.strictEqual(parseAndEval('(not 4!)'), false)
assert.strictEqual(parseAndEval('(not 4)!'), 1)
})
it('should respect precedence of transpose', function () {
const node = math.parse('a + b\'')
assert(node instanceof OperatorNode)
assert.strictEqual(node.op, '+')
assert.strictEqual(node.args[0].toString(), 'a')
assert.strictEqual(node.args[1].toString(), 'b\'')
})
it('should respect precedence of transpose (2)', function () {
const node = math.parse('a ^ b\'')
assert(node instanceof OperatorNode)
assert.strictEqual(node.op, '^')
assert.strictEqual(node.args[0].toString(), 'a')
assert.strictEqual(node.args[1].toString(), 'b\'')
})
it('should respect precedence of conditional operator and other operators', function () {
assert.strictEqual(parseAndEval('2 > 3 ? true : false'), false)
assert.strictEqual(parseAndEval('2 == 3 ? true : false'), false)
assert.strictEqual(parseAndEval('3 ? 2 + 4 : 2 - 1'), 6)
assert.deepStrictEqual(parseAndEval('3 ? true : false; 22'), new ResultSet([22]))
assert.deepStrictEqual(parseAndEval('3 ? 5cm to m : 5cm in mm'), unit(5, 'cm').to('m'))
assert.deepStrictEqual(parseAndEval('2 == 4-2 ? [1,2] : false'), math.matrix([1, 2]))
assert.deepStrictEqual(parseAndEval('false ? 1:2:6'), math.matrix([2, 3, 4, 5, 6]))
})
it('should respect precedence between left/right shift and relational operators', function () {
assert.strictEqual(parseAndEval('32 >> 4 == 2'), true)
assert.strictEqual(parseAndEval('2 == 32 >> 4'), true)
assert.strictEqual(parseAndEval('2 << 2 == 8'), true)
assert.strictEqual(parseAndEval('8 == 2 << 2'), true)
})
it('should respect precedence between relational operators and bitwise and', function () {
assert.strictEqual(parseAndEval('2 == 3 & 1'), 0)
assert.strictEqual(parseAndEval('3 & 1 == 2'), 0)
assert.strictEqual(parseAndEval('2 == (3 & 1)'), false)
})
it('should respect precedence between bitwise or | and logical and', function () {
assert.strictEqual(parseAndEval('2 | 2 and 4'), true)
assert.strictEqual(parseAndEval('4 and 2 | 2'), true)
})
it('should respect precedence between bitwise xor ^| and bitwise or |', function () {
assert.strictEqual(parseAndEval('4 ^| 6 | 2'), 2)
assert.strictEqual(parseAndEval('2 | 4 ^| 6'), 2)
assert.strictEqual(parseAndEval('(2 | 4) ^| 6'), 0)
})
it('should respect precedence between bitwise and & and bitwise or |', function () {
assert.strictEqual(parseAndEval('4 & 3 | 12'), 12)
assert.strictEqual(parseAndEval('12 | 4 & 3'), 12)
assert.strictEqual(parseAndEval('(12 | 4) & 3'), 0)
})
it('should respect precedence between logical and and or', function () {
assert.strictEqual(parseAndEval('false and true or true'), true)
assert.strictEqual(parseAndEval('false and (true or true)'), false)
assert.strictEqual(parseAndEval('true or true and false'), true)
assert.strictEqual(parseAndEval('(true or true) and false'), false)
})
it('should respect precedence of conditional operator and logical or', function () {
const node = math.parse('1 or 0 ? 2 or 3 : 0 or 0')
assert(node instanceof ConditionalNode)
assert.strictEqual(node.condition.toString(), '1 or 0')
assert.strictEqual(node.trueExpr.toString(), '2 or 3')
assert.strictEqual(node.falseExpr.toString(), '0 or 0')
assert.strictEqual(node.compile().evaluate(), true)
})
it('should respect precedence of conditional operator and relational operators', function () {
const node = math.parse('a == b ? a > b : a < b')
assert(node instanceof ConditionalNode)
assert.strictEqual(node.condition.toString(), 'a == b')
assert.strictEqual(node.trueExpr.toString(), 'a > b')
assert.strictEqual(node.falseExpr.toString(), 'a < b')
})
it('should respect precedence of conditional operator and range operator', function () {
const node = math.parse('a ? b : c : d')
assert(node instanceof ConditionalNode)
assert.strictEqual(node.condition.toString(), 'a')
assert.strictEqual(node.trueExpr.toString(), 'b')
assert.strictEqual(node.falseExpr.toString(), 'c:d')
})
it('should respect precedence of conditional operator and range operator (2)', function () {
const node = math.parse('a ? (b : c) : (d : e)')
assert(node instanceof ConditionalNode)
assert.strictEqual(node.condition.toString(), 'a')
assert.strictEqual(node.trueExpr.toString(), '(b:c)')
assert.strictEqual(node.falseExpr.toString(), '(d:e)')
})
it('should respect precedence of conditional operator and range operator (2)', function () {
const node = math.parse('a ? (b ? c : d) : (e ? f : g)')
assert(node instanceof ConditionalNode)
assert.strictEqual(node.condition.toString(), 'a')
assert.strictEqual(node.trueExpr.toString(), '(b ? c : d)')
assert.strictEqual(node.falseExpr.toString(), '(e ? f : g)')
})
it('should respect precedence of range operator and relational operators', function () {
const node = math.parse('a:b == c:d')
assert(node instanceof OperatorNode)
assert.strictEqual(node.args[0].toString(), 'a:b')
assert.strictEqual(node.args[1].toString(), 'c:d')
})
it('should respect precedence of range operator and operator plus and minus', function () {
const node = math.parse('a + b : c - d')
assert(node instanceof RangeNode)
assert.strictEqual(node.start.toString(), 'a + b')
assert.strictEqual(node.end.toString(), 'c - d')
})
it('should respect precedence of "to" operator and relational operators', function () {
const node = math.parse('a == b to c')
assert(node instanceof OperatorNode)
assert.strictEqual(node.args[0].toString(), 'a')
assert.strictEqual(node.args[1].toString(), 'b to c')
})
it('should respect precedence of "to" operator and relational operators (2)', function () {
const node = math.parse('a to b == c')
assert(node instanceof OperatorNode)
assert.strictEqual(node.args[0].toString(), 'a to b')
assert.strictEqual(node.args[1].toString(), 'c')
})
// TODO: extensively test operator precedence
})
})
describe('functions', function () {
it('should evaluate function "mod"', function () {
approx.equal(parseAndEval('mod(8, 3)'), 2)
})
it('should evaluate function "to" ', function () {
approx.deepEqual(parseAndEval('to(5.08 cm * 1000, inch)'),
math.unit(2000, 'inch').to('inch'))
})
it('should evaluate function "sort" with a custom sort function', function () {
let scope = {}
parseAndEval('sortByLength(a, b) = size(a)[1] - size(b)[1]', scope)
assert.deepStrictEqual(parseAndEval('sort(["Langdon", "Tom", "Sara"], sortByLength)', scope),
math.matrix(['Tom', 'Sara', 'Langdon']))
})
})
describe('bignumber', function () {
const bigmath = math.create({
number: 'BigNumber'
})
const BigNumber = bigmath.BigNumber
it('should parse numbers as bignumber', function () {
assert.deepStrictEqual(bigmath.bignumber('2.3'), new BigNumber('2.3'))
assert.deepStrictEqual(bigmath.evaluate('2.3'), new BigNumber('2.3'))
assert.deepStrictEqual(bigmath.evaluate('2.3e+500'), new BigNumber('2.3e+500'))
})
it('should evaluate functions supporting bignumbers', function () {
assert.deepStrictEqual(bigmath.evaluate('0.1 + 0.2'), new BigNumber('0.3'))
})
it('should evaluate functions supporting bignumbers', function () {
assert.deepStrictEqual(bigmath.evaluate('add(0.1, 0.2)'), new BigNumber('0.3'))
})
it('should work with mixed numbers and bignumbers', function () {
approx.equal(bigmath.evaluate('pi + 1'), 4.141592653589793)
})
it('should evaluate functions not supporting bignumbers', function () {
approx.equal(bigmath.evaluate('sin(0.1)'), 0.09983341664682815)
})
it('should create a range from bignumbers', function () {
assert.deepStrictEqual(bigmath.evaluate('4:6'),
bigmath.matrix([new BigNumber(4), new BigNumber(5), new BigNumber(6)]))
assert.deepStrictEqual(bigmath.evaluate('0:2:4'),
bigmath.matrix([new BigNumber(0), new BigNumber(2), new BigNumber(4)]))
})
it('should create a matrix with bignumbers', function () {
assert.deepStrictEqual(bigmath.evaluate('[0.1, 0.2]'),
bigmath.matrix([new BigNumber(0.1), new BigNumber(0.2)]))
})
it('should get an element from a matrix with bignumbers', function () {
let scope = {}
assert.deepStrictEqual(bigmath.evaluate('a=[0.1, 0.2]', scope),
bigmath.matrix([new BigNumber(0.1), new BigNumber(0.2)]))
assert.deepStrictEqual(bigmath.evaluate('a[1]', scope), new BigNumber(0.1))
assert.deepStrictEqual(bigmath.evaluate('a[:]', scope),
bigmath.matrix([new BigNumber(0.1), new BigNumber(0.2)]))
assert.deepStrictEqual(bigmath.evaluate('a[1:2]', scope),
bigmath.matrix([new BigNumber(0.1), new BigNumber(0.2)]))
})
it('should replace elements in a matrix with bignumbers', function () {
let scope = {}
assert.deepStrictEqual(bigmath.evaluate('a=[0.1, 0.2]', scope),
bigmath.matrix([new BigNumber(0.1), new BigNumber(0.2)]))
bigmath.evaluate('a[1] = 0.3', scope)
assert.deepStrictEqual(scope.a, bigmath.matrix([new BigNumber(0.3), new BigNumber(0.2)]))
bigmath.evaluate('a[:] = [0.5, 0.6]', scope)
assert.deepStrictEqual(scope.a, bigmath.matrix([new BigNumber(0.5), new BigNumber(0.6)]))
bigmath.evaluate('a[1:2] = [0.7, 0.8]', scope)
assert.deepStrictEqual(scope.a, bigmath.matrix([new BigNumber(0.7), new BigNumber(0.8)]))
})
it('should work with complex numbers (downgrades bignumbers to number)', function () {
assert.deepStrictEqual(bigmath.evaluate('3i'), new Complex(0, 3))
assert.deepStrictEqual(bigmath.evaluate('2 + 3i'), new Complex(2, 3))
assert.deepStrictEqual(bigmath.evaluate('2 * i'), new Complex(0, 2))
})
it('should work with units', function () {
assert.deepStrictEqual(bigmath.evaluate('2 cm'), bigmath.unit(new bigmath.BigNumber(2), 'cm'))
})
})
describe('scope', function () {
it('should use a given scope for assignments', function () {
let scope = {
a: 3,
b: 4
}
assert.deepStrictEqual(parse('a*b').compile().evaluate(scope), 12)
assert.deepStrictEqual(parse('c=5').compile().evaluate(scope), 5)
assert.deepStrictEqual(parse('f(x) = x^a').compile().evaluate(scope).syntax, 'f(x)')
assert.deepStrictEqual(Object.keys(scope).length, 4)
assert.deepStrictEqual(scope.a, 3)
assert.deepStrictEqual(scope.b, 4)
assert.deepStrictEqual(scope.c, 5)
assert.deepStrictEqual(typeof scope.f, 'function')
assert.strictEqual(scope.f(3), 27)
scope.a = 2
assert.strictEqual(scope.f(3), 9)
scope.hello = function (name) {
return 'hello, ' + name + '!'
}
assert.deepStrictEqual(parse('hello("jos")').compile().evaluate(scope), 'hello, jos!')
})
it('should parse undefined symbols, defining symbols, and removing symbols', function () {
let scope = {}
let n = parse('q')
assert.throws(function () { n.compile().evaluate(scope) })
parse('q=33').compile().evaluate(scope)
assert.strictEqual(n.compile().evaluate(scope), 33)
delete scope.q
assert.throws(function () { n.compile().evaluate(scope) })
n = parse('qq[1,1]=33')
assert.throws(function () { n.compile().evaluate(scope) })
parse('qq=[1,2;3,4]').compile().evaluate(scope)
n.compile().evaluate(scope)
assert.deepStrictEqual(scope.qq, math.matrix([[33, 2], [3, 4]]))
parse('qq=[4]').compile().evaluate(scope)
n.compile().evaluate(scope)
assert.deepStrictEqual(scope.qq, math.matrix([[33]]))
delete scope.qq
assert.throws(function () { n.compile().evaluate(scope) })
})
it('should evaluate a symbol with value null or undefined', function () {
assert.strictEqual(parse('a').compile().evaluate({ a: null }), null)
assert.strictEqual(parse('a').compile().evaluate({ a: undefined }), undefined)
})
})
describe('errors', function () {
it('should return IndexErrors with one based indices', function () {
// functions throw a zero-based error
assert.throws(function () { math.subset([1, 2, 3], math.index(4)) }, /Index out of range \(4 > 2\)/)
assert.throws(function () { math.subset([1, 2, 3], math.index(-2)) }, /Index out of range \(-2 < 0\)/)
// evaluation via parser throws one-based error
assert.throws(function () { math.evaluate('A[4]', { A: [1, 2, 3] }) }, /Index out of range \(4 > 3\)/)
assert.throws(function () { math.evaluate('A[-2]', { A: [1, 2, 3] }) }, /IndexError: Index out of range \(-2 < 1\)/)
})
it('should return DimensionErrors with one based indices (subset)', function () {
// TODO: it would be more clear when all errors where DimensionErrors
// functions throw a zero-based error
assert.throws(function () { math.subset([1, 2, 3], math.index(1, 1)) }, /DimensionError: Dimension mismatch \(2 != 1\)/)
// evaluation via parser throws one-based error
assert.throws(function () { math.evaluate('A[1,1]', { A: [1, 2, 3] }) }, /DimensionError: Dimension mismatch \(2 != 1\)/)
})
it('should return DimensionErrors with one based indices (concat)', function () {
// TODO: it would be more clear when all errors where DimensionErrors
// functions throw a zero-based error
assert.throws(function () { math.concat([1, 2], [[3, 4]]) }, /DimensionError: Dimension mismatch \(1 != 2\)/)
assert.throws(function () { math.concat([[1, 2]], [[3, 4]], 2) }, /IndexError: Index out of range \(2 > 1\)/)
assert.throws(function () { math.concat([[1, 2]], [[3, 4]], -1) }, /IndexError: Index out of range \(-1 < 0\)/)
// evaluation via parser throws one-based error
assert.throws(function () { math.evaluate('concat([1,2], [[3,4]])') }, /DimensionError: Dimension mismatch \(1 != 2\)/)
assert.throws(function () { math.evaluate('concat([[1,2]], [[3,4]], 3)') }, /IndexError: Index out of range \(3 > 2\)/)
assert.throws(function () { math.evaluate('concat([[1,2]], [[3,4]], 0)') }, /IndexError: Index out of range \(0 < 1\)/)
})
it('should return DimensionErrors with one based indices (max)', function () {
// TODO: it would be more clear when all errors where DimensionErrors
// functions throw a zero-based error
// TODO
// evaluation via parser throws one-based error
assert.deepStrictEqual(math.evaluate('max([[1,2], [3,4]])'), 4)
assert.deepStrictEqual(math.evaluate('max([[1,2], [3,4]], 1)'), math.matrix([3, 4]))
assert.deepStrictEqual(math.evaluate('max([[1,2], [3,4]], 2)'), math.matrix([2, 4]))
assert.throws(function () { math.evaluate('max([[1,2], [3,4]], 3)') }, /IndexError: Index out of range \(3 > 2\)/)
assert.throws(function () { math.evaluate('max([[1,2], [3,4]], 0)') }, /IndexError: Index out of range \(0 < 1\)/)
})
it('should return DimensionErrors with one based indices (min)', function () {
// TODO: it would be more clear when all errors where DimensionErrors
// functions throw a zero-based error
// TODO
// evaluation via parser throws one-based error
assert.deepStrictEqual(math.evaluate('min([[1,2], [3,4]])'), 1)
assert.deepStrictEqual(math.evaluate('min([[1,2], [3,4]], 1)'), math.matrix([1, 2]))
assert.deepStrictEqual(math.evaluate('min([[1,2], [3,4]], 2)'), math.matrix([1, 3]))
assert.throws(function () { math.evaluate('min([[1,2], [3,4]], 3)') }, /IndexError: Index out of range \(3 > 2\)/)
assert.throws(function () { math.evaluate('min([[1,2], [3,4]], 0)') }, /IndexError: Index out of range \(0 < 1\)/)
})
it('should return DimensionErrors with one based indices (mean)', function () {
// TODO: it would be more clear when all errors where DimensionErrors
// functions throw a zero-based error
// TODO
// evaluation via parser throws one-based error
assert.deepStrictEqual(math.evaluate('mean([[1,2], [3,4]])'), 2.5)
assert.deepStrictEqual(math.evaluate('mean([[1,2], [3,4]], 1)'), math.matrix([2, 3]))
assert.deepStrictEqual(math.evaluate('mean([[1,2], [3,4]], 2)'), math.matrix([1.5, 3.5]))
assert.throws(function () { math.evaluate('mean([[1,2], [3,4]], 3)') }, /IndexError: Index out of range \(3 > 2\)/)
assert.throws(function () { math.evaluate('mean([[1,2], [3,4]], 0)') }, /IndexError: Index out of range \(0 < 1\)/)
})
})
describe('node tree', function () {
// TODO: test parsing into a node tree
it('should correctly stringify a node tree', function () {
assert.strictEqual(parse('0').toString(), '0')
assert.strictEqual(parse('"hello"').toString(), '"hello"')
assert.strictEqual(parse('[1, 2 + 3i, 4]').toString(), '[1, 2 + 3 i, 4]')
assert.strictEqual(parse('1/2a').toString(), '1 / 2 a')
})
it('should correctly stringify named operators', function () {
assert.strictEqual(parse('7 mod 3').toString(), '7 mod 3')
assert.strictEqual(parse('5 inch to cm').toString(), '5 inch to cm')
assert.strictEqual(parse('5 inch in cm').toString(), '5 inch in cm')
assert.strictEqual(parse('false and true').toString(), 'false and true')
assert.strictEqual(parse('false xor true').toString(), 'false xor true')
assert.strictEqual(parse('false or true').toString(), 'false or true')
assert.strictEqual(parse('not true').toString(), 'not true')
assert.strictEqual(parse('5!').toString(), '5!')
})
it('should correctly stringify an index with dot notation', function () {
assert.strictEqual(parse('A[2]').toString(), 'A[2]')
assert.strictEqual(parse('a["b"]').toString(), 'a["b"]')
assert.strictEqual(parse('a.b').toString(), 'a.b')
})
describe('custom nodes', function () {
// define a custom node
function CustomNode (args) {
this.args = args
}
CustomNode.prototype = new math.Node()
CustomNode.prototype.toString = function () {
return 'CustomNode'
}
CustomNode.prototype._compile = function (math, argNames) {
const strArgs = []
this.args.forEach(function (arg) {
strArgs.push(arg.toString())
})
return function (scope, args, context) {
return 'CustomNode(' + strArgs.join(', ') + ')'
}
}
CustomNode.prototype.forEach = function (callback) {
// we don't have childs
}
const options = {
nodes: {
custom: CustomNode
}
}
it('should parse custom nodes', function () {
const node = parse('custom(x, (2+x), sin(x))', options)
assert.strictEqual(node.compile().evaluate(), 'CustomNode(x, (2 + x), sin(x))')
})
it('should parse custom nodes without parameters', function () {
const node = parse('custom()', options)
assert.strictEqual(node.compile().evaluate(), 'CustomNode()')
assert.strictEqual(node.filter(function (node) { return node instanceof CustomNode }).length, 1)
const node2 = parse('custom', options)
assert.strictEqual(node2.compile().evaluate(), 'CustomNode()')
assert.strictEqual(node2.filter(function (node) { return node instanceof CustomNode }).length, 1)
})
it('should throw an error on syntax errors in using custom nodes', function () {
assert.throws(function () { parse('custom(x', options) }, /Parenthesis \) expected/)
assert.throws(function () { parse('custom(x, ', options) }, /Unexpected end of expression/)
})
})
})
describe('expose test functions', function () {
it('should expose isAlpha', function () {
assert.ok('should expose isAlpha', typeof math.parse.isAlpha === 'function')
})
it('should expose isValidLatinOrGreek', function () {
assert.ok('should expose isAlpha', typeof math.parse.isValidLatinOrGreek === 'function')
})
it('should expose isValidMathSymbol', function () {
assert.ok('should expose isAlpha', typeof math.parse.isValidMathSymbol === 'function')
})
it('should expose isWhitespace', function () {
assert.ok('should expose isAlpha', typeof math.parse.isWhitespace === 'function')
})
it('should expose isDecimalMark', function () {
assert.ok('should expose isAlpha', typeof math.parse.isDecimalMark === 'function')
})
it('should expose isDigitDot', function () {
assert.ok('should expose isAlpha', typeof math.parse.isDigitDot === 'function')
})
it('should expose isDigit', function () {
assert.ok('should expose isAlpha', typeof math.parse.isDigit === 'function')
})
it('should allow overriding isAlpha', function () {
const originalIsAlpha = math.parse.isAlpha
// override isAlpha with one accepting $ characters too
math.parse.isAlpha = function (c, cPrev, cNext) {
return /^[a-zA-Z_$]$/.test(c)
}
const node = math.parse('$foo')
const result = node.evaluate({ $foo: 42 })
assert.strictEqual(result, 42)
// restore original isAlpha
math.parse.isAlpha = originalIsAlpha
})
})
it('Should not allow crashing math by placing a clone function in the config', function () {
const mathClone = math.create()
try {
mathClone.evaluate('f(x)=1;config({clone:f})')
} catch (err) {}
assert.strictEqual(mathClone.evaluate('2'), 2)
})
})