Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/expression/operators.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export const properties = [
op: 'or',
associativity: 'left',
associativeWith: []
},
// logical nor
'OperatorNode:nor': {
op: 'nor',
associativity: 'left',
associativeWith: []
}
},
{ // logical xor
Expand All @@ -52,6 +58,12 @@ export const properties = [
op: 'and',
associativity: 'left',
associativeWith: []
},
// logical nand
'OperatorNode:nand': {
op: 'nand',
associativity: 'left',
associativeWith: []
}
},
{ // bitwise or
Expand Down
16 changes: 11 additions & 5 deletions src/expression/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({
and: true,
xor: true,
or: true,
not: true
nand: true,
not: true,
nor: true
}

const CONSTANTS = {
Expand Down Expand Up @@ -756,15 +758,17 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({

/**
* logical or, 'x or y'
* logical nor, 'x nor y'
* @return {Node} node
* @private
*/
function parseLogicalOr (state) {
let node = parseLogicalXor(state)

while (state.token === 'or') { // eslint-disable-line no-unmodified-loop-condition
while (state.token === 'or' || state.token === 'nor') { // eslint-disable-line no-unmodified-loop-condition
const op = state.token
getTokenSkipNewline(state)
node = new OperatorNode('or', 'or', [node, parseLogicalXor(state)])
node = new OperatorNode(op, op, [node, parseLogicalXor(state)])
}

return node
Expand All @@ -788,15 +792,17 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({

/**
* logical and, 'x and y'
* logical nand, 'x nand y'
* @return {Node} node
* @private
*/
function parseLogicalAnd (state) {
let node = parseBitwiseOr(state)

while (state.token === 'and') { // eslint-disable-line no-unmodified-loop-condition
while (state.token === 'and' || state.token === 'nand') { // eslint-disable-line no-unmodified-loop-condition
const op = state.token
getTokenSkipNewline(state)
node = new OperatorNode('and', 'and', [node, parseBitwiseOr(state)])
node = new OperatorNode(op, op, [node, parseBitwiseOr(state)])
}

return node
Expand Down
96 changes: 96 additions & 0 deletions test/typescript-tests/testTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3176,3 +3176,99 @@ Match types of exact positional arguments.
expectTypeOf(mixArray3).toMatchTypeOf<MathArray<MathScalarType>>()
expectTypeOf(unitArray3).toMatchTypeOf<MathArray<Unit>>()
}

/**
* NAND examples
*/
{
const math = create(all, {})

// number input
assert.strictEqual(math.nand(3, 2), false)
assert.strictEqual(math.nand(3, 0), true)
assert.strictEqual(math.nand(0, 2), true)
assert.strictEqual(math.nand(0, 0), true)

// bignumber input
assert.deepStrictEqual(math.nand(math.bignumber(4), math.bignumber(2)), false)
assert.deepStrictEqual(math.nand(math.bignumber(3), 0), true)
assert.deepStrictEqual(math.nand(math.bignumber(0), math.bignumber(0)), true)

// Complex input
const a = math.complex(3.24, -2.71)
const b = math.complex(0, 0)
assert.deepStrictEqual(math.nand(a, a), false)
assert.deepStrictEqual(math.nand(a, b), true)
assert.deepStrictEqual(math.nand(b, b), true)

// unit input
const u1 = math.unit(3.2, 'cm')
const u2 = math.unit('cm')
const u3 = math.unit(5.51, 'cm')
assert.deepStrictEqual(math.nand(u1, u2), true)
assert.deepStrictEqual(math.nand(u1, u3), false)

// array input
assert.deepStrictEqual(math.nand([3.2, 3.8, -4.7], 2), [false, false, false])
assert.deepStrictEqual(math.nand([3.2, 3.8, -4.7], 0), [true, true, true])
assert.deepStrictEqual(math.nand([3.2, 3.8, 0], 7), [false, false, true])
assert.deepStrictEqual(math.nand([3.21, 3.82, -4.71], math.bignumber(1)), [
false,
false,
false
])

// matrix of decimals
assert.deepStrictEqual(
math.nand(math.matrix([1, 4, 0, 0]), math.matrix([1, 0, 6, 0])).valueOf(),
[false, true, true, true]
)
}

/**
* NOR examples
*/
{
const math = create(all, {})

// number input
assert.strictEqual(math.nor(3, 2), false)
assert.strictEqual(math.nor(3, 0), false)
assert.strictEqual(math.nor(0, 2), false)
assert.strictEqual(math.nor(0, 0), true)

// bignumber input
assert.deepStrictEqual(math.nor(math.bignumber(4), math.bignumber(2)), false)
assert.deepStrictEqual(math.nor(math.bignumber(3), 0), false)
assert.deepStrictEqual(math.nor(math.bignumber(0), math.bignumber(0)), true)

// Complex input
const a = math.complex(3.24, -2.71)
const b = math.complex(0, 0)
assert.deepStrictEqual(math.nor(a, a), false)
assert.deepStrictEqual(math.nor(a, b), false)
assert.deepStrictEqual(math.nor(b, b), true)

// unit input
const u1 = math.unit(3.2, 'cm')
const u2 = math.unit('cm')
const u3 = math.unit(5.51, 'cm')
assert.deepStrictEqual(math.nor(u1, u2), false)
assert.deepStrictEqual(math.nor(u1, u3), false)

// array input
assert.deepStrictEqual(math.nor([3.2, 3.8, -4.7], 2), [false, false, false])
assert.deepStrictEqual(math.nor([3.2, 3.8, 0], 0), [false, false, true])
assert.deepStrictEqual(math.nor([3.2, 3.8, 0], 7), [false, false, false])
assert.deepStrictEqual(math.nor([3.21, 3.82, -4.71], math.bignumber(1)), [
false,
false,
false
])

// matrix of decimals
assert.deepStrictEqual(
math.nor(math.matrix([1, 4, 0, 0]), math.matrix([1, 0, 6, 0])).valueOf(),
[false, false, false, true]
)
}
67 changes: 66 additions & 1 deletion test/unit-tests/expression/parse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,7 @@ describe('parse', function () {
})

it('should create an object with unquoted keys that are keywords', function () {
assert.deepStrictEqual(parseAndEval('{ mod: 1, and: 1, not: 1, or: 1, xor: 1, to: 1, in: 1 }'), { mod: 1, and: 1, not: 1, or: 1, xor: 1, to: 1, in: 1 })
assert.deepStrictEqual(parseAndEval('{ mod: 1, and: 1, not: 1, or: 1, xor: 1, to: 1, in: 1, nor: 1, nand: 1 }'), { mod: 1, and: 1, not: 1, or: 1, xor: 1, to: 1, in: 1, nor: 1, nand: 1 })
})

it('should create an object with child object', function () {
Expand Down Expand Up @@ -1925,6 +1925,24 @@ describe('parse', function () {
assert.strictEqual(scope.a, false)
})

it('should parse logical nand', function () {
assert.strictEqual(parseAndEval('2 nand 6'), false)
assert.strictEqual(parseAndEval('2 nand 0'), true)
assert.strictEqual(parseAndEval('true nand true'), false)
assert.strictEqual(parseAndEval('true nand false'), true)
assert.strictEqual(parseAndEval('false nand true'), true)
assert.strictEqual(parseAndEval('false nand false'), true)
assert.throws(function () { parseAndEval('true nand undefined') }, TypeError)
})

it('should parse logical nand inside a function definition', function () {
const scope = {}
const f = parseAndEval('f(x) = x > 2 nand x < 4', scope)
assert.strictEqual(f(1), true)
assert.strictEqual(f(3), false)
assert.strictEqual(f(5), true)
})

it('should always pass a Map as scope to a rawArgs function', function () {
const myMath = math.create()
function myFunction (args, _math, _scope) {
Expand Down Expand Up @@ -1986,6 +2004,24 @@ describe('parse', function () {
assert.deepStrictEqual(scope, { a: true })
})

it('should parse logical nor', function () {
assert.strictEqual(parseAndEval('2 nor 6'), false)
assert.strictEqual(parseAndEval('2 nor 0'), false)
assert.strictEqual(parseAndEval('true nor true'), false)
assert.strictEqual(parseAndEval('true nor false'), false)
assert.strictEqual(parseAndEval('false nor true'), false)
assert.strictEqual(parseAndEval('false nor false'), true)
assert.throws(function () { parseAndEval('false nor undefined') }, TypeError)
})

it('should parse logical nor inside a function definition', function () {
const scope = {}
const f = parseAndEval('f(x) = x < 2 nor x > 4', scope)
assert.strictEqual(f(1), false)
assert.strictEqual(f(3), true)
assert.strictEqual(f(5), false)
})

it('should parse logical not', function () {
assert.strictEqual(parseAndEval('not 2'), false)
assert.strictEqual(parseAndEval('not not 2'), true)
Expand Down Expand Up @@ -2309,6 +2345,11 @@ describe('parse', function () {
assert.strictEqual(parseAndEval('4 and 2 | 2'), true)
})

it('should respect precedence between bitwise or | and logical nand', function () {
assert.strictEqual(parseAndEval('2 | 2 nand 4'), false)
assert.strictEqual(parseAndEval('4 nand 2 | 2'), false)
})

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)
Expand All @@ -2328,6 +2369,13 @@ describe('parse', function () {
assert.strictEqual(parseAndEval('(true or true) and false'), false)
})

it('should respect precedence between logical nand and or', function () {
assert.strictEqual(parseAndEval('false nand true or true'), true)
assert.strictEqual(parseAndEval('false nand (true or true)'), true)
assert.strictEqual(parseAndEval('true or true nand false'), true)
assert.strictEqual(parseAndEval('(true or true) nand false'), true)
})

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)
Expand All @@ -2337,6 +2385,22 @@ describe('parse', function () {
assert.strictEqual(node.compile().evaluate(), true)
})

it('should respect precedence between logical and and nor', function () {
assert.strictEqual(parseAndEval('false and true nor true'), false)
assert.strictEqual(parseAndEval('false and (true nor true)'), false)
assert.strictEqual(parseAndEval('true nor true and false'), false)
assert.strictEqual(parseAndEval('(true nor true) and false'), false)
})

it('should respect precedence of conditional operator and logical nor', function () {
const node = math.parse('1 nor 0 ? 2 nor 3 : 0 nor 0')
assert(node instanceof ConditionalNode)
assert.strictEqual(node.condition.toString(), '1 nor 0')
assert.strictEqual(node.trueExpr.toString(), '2 nor 3')
assert.strictEqual(node.falseExpr.toString(), '0 nor 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)
Expand Down Expand Up @@ -2679,6 +2743,7 @@ describe('parse', function () {
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('false nor true').toString(), 'false nor true')
assert.strictEqual(parse('not true').toString(), 'not true')
assert.strictEqual(parse('5!').toString(), '5!')
})
Expand Down