Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy evaluation of and, or, &, | #3090

Closed
wants to merge 10 commits into from
10 changes: 9 additions & 1 deletion src/expression/node/OperatorNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getSafeProperty, isSafeMethod } from '../../utils/customs.js'
import { getAssociativity, getPrecedence, isAssociativeWith, properties } from '../operators.js'
import { latexOperators } from '../../utils/latex.js'
import { factory } from '../../utils/factory.js'
import { createSubScope } from '../../utils/scope.js'

const name = 'OperatorNode'
const dependencies = [
Expand Down Expand Up @@ -304,7 +305,14 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({
return arg._compile(math, argNames)
})

if (evalArgs.length === 1) {
if (typeof fn === 'function' && fn.rawArgs === true) {
// pass unevaluated parameters (nodes) to the function
// "raw" evaluation
const rawArgs = this.args
return function evalOperatorNode (scope, args, context) {
return fn(rawArgs, math, createSubScope(scope, args), scope)
}
} else if (evalArgs.length === 1) {
const evalArg0 = evalArgs[0]
return function evalOperatorNode (scope, args, context) {
return fn(evalArg0(scope, args, context))
Expand Down
26 changes: 26 additions & 0 deletions src/expression/transform/and.transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createAnd } from '../../function/logical/and.js'
import { factory } from '../../utils/factory.js'
import { testCondition } from './utils/testCondition.js'
import { isBigNumber, isBoolean, isComplex, isNumber, isUnit } from '../../utils/is.js'

const name = 'and'
const dependencies = ['typed', 'matrix', 'zeros', 'add', 'equalScalar', 'not', 'concat']

export const createAndTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, not, concat }) => {
const and = createAnd({ typed, matrix, equalScalar, zeros, not, concat })

function andTransform (args, math, scope) {
const condition1 = args[0].compile().evaluate(scope)
if (isNumber(condition1) || isBoolean(condition1) || isBigNumber(condition1) || isComplex(condition1) || isUnit(condition1)) {
smith120bh marked this conversation as resolved.
Show resolved Hide resolved
if (!testCondition(condition1)) {
return false
}
}
const condition2 = args[1].compile().evaluate(scope)
return and(condition1, condition2)
}

andTransform.rawArgs = true

return andTransform
}, { isTransformFunction: true })
28 changes: 28 additions & 0 deletions src/expression/transform/bitAnd.transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createBitAnd } from '../../function/bitwise/bitAnd.js'
import { factory } from '../../utils/factory.js'
import { isBigNumber, isBoolean, isComplex, isNumber, isUnit } from '../../utils/is.js'

const name = 'bitAnd'
const dependencies = ['typed', 'matrix', 'zeros', 'add', 'equalScalar', 'not', 'concat']

export const createBitAndTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, zeros, not, concat }) => {
const and = createBitAnd({ typed, matrix, equalScalar, zeros, not, concat })

function bitAndTransform (args, math, scope) {
const condition1 = args[0].compile().evaluate(scope)
if (isNumber(condition1) || isBoolean(condition1) || isBigNumber(condition1) || isComplex(condition1) || isUnit(condition1)) {
if (isNaN(condition1)) {
return NaN
}
if (condition1 === 0 || condition1 === false) {
return 0
}
}
const condition2 = args[1].compile().evaluate(scope)
return and(condition1, condition2)
}

bitAndTransform.rawArgs = true

return bitAndTransform
}, { isTransformFunction: true })
31 changes: 31 additions & 0 deletions src/expression/transform/bitOr.transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createBitOr } from '../../function/bitwise/bitOr.js'
import { factory } from '../../utils/factory.js'
import { isBigNumber, isBoolean, isComplex, isNumber, isUnit } from '../../utils/is.js'

const name = 'bitOr'
const dependencies = ['typed', 'matrix', 'equalScalar', 'DenseMatrix', 'concat']

export const createBitOrTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix, concat }) => {
const or = createBitOr({ typed, matrix, equalScalar, DenseMatrix, concat })

function bitOrTransform (args, math, scope) {
const condition1 = args[0].compile().evaluate(scope)
if (isNumber(condition1) || isBoolean(condition1) || isBigNumber(condition1) || isComplex(condition1) || isUnit(condition1)) {
if (isNaN(condition1)) {
return NaN
}
if (condition1 === (-1)) {
return -1
}
if (condition1 === true) {
return 1
}
}
const condition2 = args[1].compile().evaluate(scope)
return or(condition1, condition2)
}

bitOrTransform.rawArgs = true

return bitOrTransform
}, { isTransformFunction: true })
26 changes: 26 additions & 0 deletions src/expression/transform/or.transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createOr } from '../../function/logical/or.js'
import { factory } from '../../utils/factory.js'
import { testCondition } from './utils/testCondition.js'
import { isBigNumber, isBoolean, isComplex, isNumber, isUnit } from '../../utils/is.js'

const name = 'or'
const dependencies = ['typed', 'matrix', 'equalScalar', 'DenseMatrix', 'concat']

export const createOrTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, equalScalar, DenseMatrix, concat }) => {
const or = createOr({ typed, matrix, equalScalar, DenseMatrix, concat })

function orTransform (args, math, scope) {
const condition1 = args[0].compile().evaluate(scope)
if (isNumber(condition1) || isBoolean(condition1) || isBigNumber(condition1) || isComplex(condition1) || isUnit(condition1)) {
if (testCondition(condition1)) {
return true
}
}
const condition2 = args[1].compile().evaluate(scope)
return or(condition1, condition2)
}

orTransform.rawArgs = true

return orTransform
}, { isTransformFunction: true })
31 changes: 31 additions & 0 deletions src/expression/transform/utils/testCondition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { isBigNumber, isComplex, isUnit } from '../../../utils/is.js'

/**
* Test the truthiness of a scalar condition
* @param {number, boolean, BigNumber, Complex, Unit} condition
* @returns {boolean} Returns the truthiness of the condition
*/
export function testCondition (condition) {
if (typeof condition === 'number' ||
typeof condition === 'boolean') {
return !!condition
}
if (condition === null || condition === undefined) {
return false
}
if (isBigNumber(condition)) {
return !condition.isZero()
}

if (isComplex(condition)) {
return !!((condition.re || condition.im))
}

if (isUnit(condition)) {
return !!condition.value
}

if (condition === null || condition === undefined) {
return false
}
}
4 changes: 4 additions & 0 deletions src/factoriesAny.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,7 @@ export { createQuantileSeqTransform } from './expression/transform/quantileSeq.t
export { createCumSumTransform } from './expression/transform/cumsum.transform.js'
export { createVarianceTransform } from './expression/transform/variance.transform.js'
export { createPrintTransform } from './expression/transform/print.transform.js'
export { createAndTransform } from './expression/transform/and.transform.js'
smith120bh marked this conversation as resolved.
Show resolved Hide resolved
export { createOrTransform } from './expression/transform/or.transform.js'
export { createBitAndTransform } from './expression/transform/bitAnd.transform.js'
export { createBitOrTransform } from './expression/transform/bitOr.transform.js'
1 change: 1 addition & 0 deletions src/function/bitwise/bitAnd.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const createBitAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed
/**
* Bitwise AND two values, `x & y`.
* For matrices, the function is evaluated element wise.
* For non-matrices, the function is evaluated lazily.
smith120bh marked this conversation as resolved.
Show resolved Hide resolved
*
* Syntax:
*
Expand Down
1 change: 1 addition & 0 deletions src/function/bitwise/bitOr.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const createBitOr = /* #__PURE__ */ factory(name, dependencies, ({ typed,
/**
* Bitwise OR two values, `x | y`.
* For matrices, the function is evaluated element wise.
* For non-matrices, the function is evaluated lazily.
* For units, the function is evaluated on the lowest print base.
*
* Syntax:
Expand Down
1 change: 1 addition & 0 deletions src/function/logical/and.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const createAnd = /* #__PURE__ */ factory(name, dependencies, ({ typed, m
/**
* Logical `and`. Test whether two values are both defined with a nonzero/nonempty value.
* For matrices, the function is evaluated element wise.
* For non-matrices, the function is evaluated lazily.
*
* Syntax:
*
Expand Down
1 change: 1 addition & 0 deletions src/function/logical/or.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const createOr = /* #__PURE__ */ factory(name, dependencies, ({ typed, ma
/**
* Logical `or`. Test if at least one value is defined with a nonzero/nonempty value.
* For matrices, the function is evaluated element wise.
* For non-matrices, the function is evaluated lazily.
*
* Syntax:
*
Expand Down
16 changes: 16 additions & 0 deletions test/unit-tests/expression/parse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,10 @@ describe('parse', function () {
assert.strictEqual(parseAndEval('true & false'), 0)
assert.strictEqual(parseAndEval('false & true'), 0)
assert.strictEqual(parseAndEval('false & false'), 0)

assert.strictEqual(parseAndEval('0 & undefined'), 0)
smith120bh marked this conversation as resolved.
Show resolved Hide resolved
assert.strictEqual(parseAndEval('false & undefined'), 0)
assert.throws(function () { parseAndEval('true & undefined') }, TypeError)
josdejong marked this conversation as resolved.
Show resolved Hide resolved
})

it('should parse bitwise xor ^|', function () {
Expand All @@ -1483,6 +1487,10 @@ describe('parse', function () {
assert.strictEqual(parseAndEval('true | false'), 1)
assert.strictEqual(parseAndEval('false | true'), 1)
assert.strictEqual(parseAndEval('false | false'), 0)

assert.strictEqual(parseAndEval('-1 | undefined'), -1)
assert.strictEqual(parseAndEval('true | undefined'), 1)
assert.throws(function () { parseAndEval('false | undefined') }, TypeError)
})

it('should parse bitwise left shift <<', function () {
Expand All @@ -1506,6 +1514,10 @@ describe('parse', function () {
assert.strictEqual(parseAndEval('true and false'), false)
assert.strictEqual(parseAndEval('false and true'), false)
assert.strictEqual(parseAndEval('false and false'), false)

assert.strictEqual(parseAndEval('0 and undefined'), false)
assert.strictEqual(parseAndEval('false and undefined'), false)
assert.throws(function () { parseAndEval('true and undefined') }, TypeError)
})

it('should parse logical xor', function () {
Expand All @@ -1524,6 +1536,10 @@ describe('parse', function () {
assert.strictEqual(parseAndEval('true or false'), true)
assert.strictEqual(parseAndEval('false or true'), true)
assert.strictEqual(parseAndEval('false or false'), false)

assert.strictEqual(parseAndEval('2 or undefined'), true)
assert.strictEqual(parseAndEval('true or undefined'), true)
assert.throws(function () { parseAndEval('false or undefined') }, TypeError)
})

it('should parse logical not', function () {
Expand Down