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

Reduced callback #3256

Merged
merged 17 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
67 changes: 26 additions & 41 deletions src/expression/transform/filter.transform.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { applyCallback } from '../../utils/applyCallback.js'
import { filter, filterRegExp } from '../../utils/array.js'
import { createFilter } from '../../function/matrix/filter.js'
import { factory } from '../../utils/factory.js'
import { isFunctionAssignmentNode, isSymbolNode } from '../../utils/is.js'
import { compileInlineExpression } from './utils/compileInlineExpression.js'
import { createTransformCallback } from './utils/transformCallback.js'

const name = 'filter'
const dependencies = ['typed']
Expand All @@ -16,57 +16,42 @@ export const createFilterTransform = /* #__PURE__ */ factory(name, dependencies,
* so you can do something like 'filter([3, -2, 5], x > 0)'.
*/
function filterTransform (args, math, scope) {
let x, callback
const filter = createFilter({ typed })
const transformCallback = createTransformCallback({ typed })

if (args[0]) {
x = args[0].compile().evaluate(scope)
if (args.length === 0) {
return filter()
}
let x = args[0]

if (args[1]) {
if (isSymbolNode(args[1]) || isFunctionAssignmentNode(args[1])) {
if (args.length === 1) {
return filter(x)
}

const N = args.length - 1
let callback = args[N]

if (x) {
x = _compileAndEvaluate(x, scope)
}

if (callback) {
if (isSymbolNode(callback) || isFunctionAssignmentNode(callback)) {
// a function pointer, like filter([3, -2, 5], myTestFunction)
callback = args[1].compile().evaluate(scope)
callback = _compileAndEvaluate(callback, scope)
} else {
// an expression like filter([3, -2, 5], x > 0)
callback = compileInlineExpression(args[1], math, scope)
callback = compileInlineExpression(callback, math, scope)
}
}

return filter(x, callback)
return filter(x, transformCallback(callback, N))
}
filterTransform.rawArgs = true

// one based version of function filter
const filter = typed('filter', {
'Array, function': _filter,

'Matrix, function': function (x, test) {
return x.create(_filter(x.toArray(), test), x.datatype())
},

'Array, RegExp': filterRegExp,

'Matrix, RegExp': function (x, test) {
return x.create(filterRegExp(x.toArray(), test), x.datatype())
}
})
function _compileAndEvaluate (arg, scope) {
return arg.compile().evaluate(scope)
}

return filterTransform
}, { isTransformFunction: true })

/**
* Filter values in a callback given a callback function
*
* !!! Passes a one-based index !!!
*
* @param {Array} x
* @param {Function} callback
* @return {Array} Returns the filtered array
* @private
*/
function _filter (x, callback) {
return filter(x, function (value, index, array) {
// invoke the callback function with the right number of arguments
return applyCallback(callback, value, [index + 1], array, 'filter')
})
}
57 changes: 27 additions & 30 deletions src/expression/transform/forEach.transform.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { applyCallback } from '../../utils/applyCallback.js'
import { forEach } from '../../utils/array.js'
import { createForEach } from '../../function/matrix/forEach.js'
import { createTransformCallback } from './utils/transformCallback.js'
import { factory } from '../../utils/factory.js'
import { isFunctionAssignmentNode, isSymbolNode } from '../../utils/is.js'
import { compileInlineExpression } from './utils/compileInlineExpression.js'
Expand All @@ -14,44 +14,41 @@ export const createForEachTransform = /* #__PURE__ */ factory(name, dependencies
*
* This transform creates a one-based index instead of a zero-based index
*/
const forEach = createForEach({ typed })
const transformCallback = createTransformCallback({ typed })
function forEachTransform (args, math, scope) {
let x, callback
if (args.length === 0) {
return forEach()
}
let x = args[0]

if (args.length === 1) {
return forEach(x)
}

const N = args.length - 1
let callback = args[N]

if (args[0]) {
x = args[0].compile().evaluate(scope)
if (x) {
x = _compileAndEvaluate(x, scope)
}

if (args[1]) {
if (isSymbolNode(args[1]) || isFunctionAssignmentNode(args[1])) {
// a function pointer, like forEach([3, -2, 5], myTestFunction)
callback = args[1].compile().evaluate(scope)
if (callback) {
if (isSymbolNode(callback) || isFunctionAssignmentNode(callback)) {
// a function pointer, like filter([3, -2, 5], myTestFunction)
callback = _compileAndEvaluate(callback, scope)
} else {
// an expression like forEach([3, -2, 5], x > 0 ? callback1(x) : callback2(x) )
callback = compileInlineExpression(args[1], math, scope)
// an expression like filter([3, -2, 5], x > 0)
callback = compileInlineExpression(callback, math, scope)
}
}

return _forEach(x, callback)
return forEach(x, transformCallback(callback, N))
}
forEachTransform.rawArgs = true

// one-based version of forEach
const _forEach = typed('forEach', {
'Array | Matrix, function': function (array, callback) {
const recurse = function (value, index) {
if (Array.isArray(value)) {
forEach(value, function (child, i) {
// we create a copy of the index array and append the new index value
recurse(child, index.concat(i + 1)) // one based index, hence i+1
})
} else {
// invoke the callback function with the right number of arguments
return applyCallback(callback, value, index, array, 'forEach')
}
}
recurse(array.valueOf(), []) // pass Array
}
})

function _compileAndEvaluate (arg, scope) {
return arg.compile().evaluate(scope)
}
return forEachTransform
}, { isTransformFunction: true })
94 changes: 5 additions & 89 deletions src/expression/transform/map.transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { factory } from '../../utils/factory.js'
import { isFunctionAssignmentNode, isSymbolNode } from '../../utils/is.js'
import { createMap } from '../../function/matrix/map.js'
import { compileInlineExpression } from './utils/compileInlineExpression.js'
import { createTransformCallback } from './utils/transformCallback.js'

const name = 'map'
const dependencies = ['typed']
Expand All @@ -14,6 +15,7 @@ export const createMapTransform = /* #__PURE__ */ factory(name, dependencies, ({
* This transform creates a one-based index instead of a zero-based index
*/
const map = createMap({ typed })
const transformCallback = createTransformCallback({ typed })

function mapTransform (args, math, scope) {
if (args.length === 0) {
Expand All @@ -24,9 +26,8 @@ export const createMapTransform = /* #__PURE__ */ factory(name, dependencies, ({
return map(args[0])
}
const N = args.length - 1
let X, callback
callback = args[N]
X = args.slice(0, N)
let X = args.slice(0, N)
let callback = args[N]
X = X.map(arg => _compileAndEvaluate(arg, scope))

if (callback) {
Expand All @@ -38,7 +39,7 @@ export const createMapTransform = /* #__PURE__ */ factory(name, dependencies, ({
callback = compileInlineExpression(callback, math, scope)
}
}
return map(...X, _transformCallback(callback, N))
return map(...X, transformCallback(callback, N))

function _compileAndEvaluate (arg, scope) {
return arg.compile().evaluate(scope)
Expand All @@ -47,89 +48,4 @@ export const createMapTransform = /* #__PURE__ */ factory(name, dependencies, ({
mapTransform.rawArgs = true

return mapTransform

/**
* Transforms the given callback function based on its type and number of arrays.
*
* @param {Function} callback - The callback function to transform.
* @param {number} numberOfArrays - The number of arrays to pass to the callback function.
* @returns {*} - The transformed callback function.
*/
function _transformCallback (callback, numberOfArrays) {
if (typed.isTypedFunction(callback)) {
return _transformTypedCallbackFunction(callback, numberOfArrays)
} else {
return _transformCallbackFunction(callback, callback.length, numberOfArrays)
}
}

/**
* Transforms the given typed callback function based on the number of arrays.
*
* @param {Function} typedFunction - The typed callback function to transform.
* @param {number} numberOfArrays - The number of arrays to pass to the callback function.
* @returns {*} - The transformed typed callback function.
*/
function _transformTypedCallbackFunction (typedFunction, numberOfArrays) {
const signatures = Object.fromEntries(
Object.entries(typedFunction.signatures)
.map(([signature, callbackFunction]) => {
const numberOfCallbackInputs = signature.split(',').length
if (typed.isTypedFunction(callbackFunction)) {
return [signature, _transformTypedCallbackFunction(callbackFunction, numberOfArrays)]
} else {
return [signature, _transformCallbackFunction(callbackFunction, numberOfCallbackInputs, numberOfArrays)]
}
})
)

if (typeof typedFunction.name === 'string') {
return typed(typedFunction.name, signatures)
} else {
return typed(signatures)
}
}
}, { isTransformFunction: true })

/**
* Transforms the callback function based on the number of callback inputs and arrays.
* There are three cases:
* 1. The callback function has N arguments.
* 2. The callback function has N+1 arguments.
* 3. The callback function has 2N+1 arguments.
*
* @param {Function} callbackFunction - The callback function to transform.
* @param {number} numberOfCallbackInputs - The number of callback inputs.
* @param {number} numberOfArrays - The number of arrays.
* @returns {Function} The transformed callback function.
*/
function _transformCallbackFunction (callbackFunction, numberOfCallbackInputs, numberOfArrays) {
if (numberOfCallbackInputs === numberOfArrays) {
return callbackFunction
} else if (numberOfCallbackInputs === numberOfArrays + 1) {
return function (...args) {
const vals = args.slice(0, numberOfArrays)
const idx = _transformDims(args[numberOfArrays])
return callbackFunction(...vals, idx)
}
} else if (numberOfCallbackInputs > numberOfArrays + 1) {
return function (...args) {
const vals = args.slice(0, numberOfArrays)
const idx = _transformDims(args[numberOfArrays])
const rest = args.slice(numberOfArrays + 1)
return callbackFunction(...vals, idx, ...rest)
}
} else {
return callbackFunction
}
}

/**
* Transforms the dimensions by adding 1 to each dimension.
*
* @param {Array} dims - The dimensions to transform.
* @returns {Array} The transformed dimensions.
*/
function _transformDims (dims) {
return dims.map(dim => dim.isBigNumber ? dim.plus(1) : dim + 1)
}
91 changes: 91 additions & 0 deletions src/expression/transform/utils/transformCallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { factory } from '../../../utils/factory.js'

const name = 'transformCallback'
const dependencies = ['typed']

export const createTransformCallback = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
/**
* Transforms the given callback function based on its type and number of arrays.
*
* @param {Function} callback - The callback function to transform.
* @param {number} numberOfArrays - The number of arrays to pass to the callback function.
* @returns {*} - The transformed callback function.
*/
return function (callback, numberOfArrays) {
if (typed.isTypedFunction(callback)) {
return _transformTypedCallbackFunction(callback, numberOfArrays)
} else {
return _transformCallbackFunction(callback, callback.length, numberOfArrays)
}
}

/**
* Transforms the given typed callback function based on the number of arrays.
*
* @param {Function} typedFunction - The typed callback function to transform.
* @param {number} numberOfArrays - The number of arrays to pass to the callback function.
* @returns {*} - The transformed callback function.
*/
function _transformTypedCallbackFunction (typedFunction, numberOfArrays) {
const signatures = Object.fromEntries(
Object.entries(typedFunction.signatures)
.map(([signature, callbackFunction]) => {
const numberOfCallbackInputs = signature.split(',').length
if (typed.isTypedFunction(callbackFunction)) {
return [signature, _transformTypedCallbackFunction(callbackFunction, numberOfArrays)]
} else {
return [signature, _transformCallbackFunction(callbackFunction, numberOfCallbackInputs, numberOfArrays)]
}
})
)

if (typeof typedFunction.name === 'string') {
return typed(typedFunction.name, signatures)
} else {
return typed(signatures)
}
}
})

/**
* Transforms the callback function based on the number of callback inputs and arrays.
* There are three cases:
* 1. The callback function has N arguments.
* 2. The callback function has N+1 arguments.
* 3. The callback function has 2N+1 arguments.
*
* @param {Function} callbackFunction - The callback function to transform.
* @param {number} numberOfCallbackInputs - The number of callback inputs.
* @param {number} numberOfArrays - The number of arrays.
* @returns {Function} The transformed callback function.
*/
function _transformCallbackFunction (callbackFunction, numberOfCallbackInputs, numberOfArrays) {
if (numberOfCallbackInputs === numberOfArrays) {
return callbackFunction
} else if (numberOfCallbackInputs === numberOfArrays + 1) {
return function (...args) {
const vals = args.slice(0, numberOfArrays)
const idx = _transformDims(args[numberOfArrays])
return callbackFunction(...vals, idx)
}
} else if (numberOfCallbackInputs > numberOfArrays + 1) {
return function (...args) {
const vals = args.slice(0, numberOfArrays)
const idx = _transformDims(args[numberOfArrays])
const rest = args.slice(numberOfArrays + 1)
return callbackFunction(...vals, idx, ...rest)
}
} else {
return callbackFunction
}
}

/**
* Transforms the dimensions by adding 1 to each dimension.
*
* @param {Array} dims - The dimensions to transform.
* @returns {Array} The transformed dimensions.
*/
function _transformDims (dims) {
return dims.map(dim => dim + 1)
}
Loading