main.js (11521B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2020 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive; 24 var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ); 25 var isInteger = require( '@stdlib/assert/is-integer' ).isPrimitive; 26 var isFunctionArray = require( '@stdlib/assert/is-function-array' ); 27 var isFunction = require( '@stdlib/assert/is-function' ); 28 var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives; 29 var isCollection = require( '@stdlib/assert/is-collection' ); 30 var abs = require( '@stdlib/math/base/special/abs' ); 31 var dtype = require( '@stdlib/ndarray/base/buffer-dtype' ); 32 var indexOfTypes = require( './index_of_types.js' ); 33 34 35 // MAIN // 36 37 /** 38 * Returns a strided array function interface which performs multiple dispatch. 39 * 40 * @param {(FunctionArray|Function)} fcns - list of strided array functions 41 * @param {StringArray} types - one-dimensional list of strided array argument data types 42 * @param {(Collection|null)} data - strided array function data (e.g., callbacks) 43 * @param {PositiveInteger} nargs - total number of strided array function interface arguments (including strides and offsets) 44 * @param {NonNegativeInteger} nin - number of input strided arrays 45 * @param {NonNegativeInteger} nout - number of output strided arrays 46 * @throws {TypeError} first argument must be either a function or an array of functions 47 * @throws {TypeError} second argument must be an array of strings 48 * @throws {TypeError} third argument must be an array-like object or `null` 49 * @throws {Error} third and first arguments must have the same number of elements 50 * @throws {TypeError} fourth argument must be a positive integer 51 * @throws {TypeError} fifth argument must be a nonnegative integer 52 * @throws {TypeError} sixth argument must be a nonnegative integer 53 * @throws {Error} fourth argument must be compatible with the specified number of input and output arrays 54 * @throws {Error} number of types must match the number of functions times the total number of array arguments for each function 55 * @throws {Error} interface must accept at least one strided input and/or output array 56 * @returns {Function} strided array function interface 57 * 58 * @example 59 * var unary = require( '@stdlib/strided/base/unary' ); 60 * var abs = require( '@stdlib/math/base/special/abs' ); 61 * var Float64Array = require( '@stdlib/array/float64' ); 62 * 63 * var types = [ 64 * 'float64', 'float64' 65 * ]; 66 * 67 * var data = [ 68 * abs 69 * ]; 70 * 71 * var strided = dispatch( unary, types, data, 5, 1, 1 ); 72 * 73 * // ... 74 * 75 * var x = new Float64Array( [ -1.0, -2.0, -3.0, -4.0, -5.0 ] ); 76 * var y = new Float64Array( [ 0.0, 0.0, 0.0, 0.0, 0.0 ] ); 77 * 78 * strided( x.length, x, 1, y, 1 ); 79 * // y => <Float64Array>[ 1.0, 2.0, 3.0, 4.0, 5.0 ] 80 */ 81 function dispatch( fcns, types, data, nargs, nin, nout ) { 82 var strideArgs; 83 var hasOffsets; 84 var narrays; 85 var nfcns; 86 var iout; 87 var fcn; 88 89 if ( isFunction( fcns ) ) { 90 fcn = fcns; 91 } else if ( !isFunctionArray( fcns ) ) { 92 throw new TypeError( 'invalid argument. First argument must be either a function or an array of functions. Value: `' + fcns + '`.' ); 93 } 94 if ( !isStringArray( types ) ) { 95 throw new TypeError( 'invalid argument. Second argument must be an array of strings. Value: `' + types + '`.' ); 96 } 97 if ( !isCollection( data ) && data !== null ) { 98 throw new TypeError( 'invalid argument. Third argument must be an array-like object or `null`. Value: `' + data + '`.' ); 99 } 100 if ( !isPositiveInteger( nargs ) ) { 101 throw new TypeError( 'invalid argument. Fourth argument must be a positive integer. Value: `' + nargs + '`.' ); 102 } 103 if ( !isNonNegativeInteger( nin ) ) { 104 throw new TypeError( 'invalid argument. Fifth argument must be a nonnegative integer. Value: `' + nin + '`.' ); 105 } 106 if ( !isNonNegativeInteger( nout ) ) { 107 throw new TypeError( 'invalid argument. Sixth argument must be a nonnegative integer. Value: `' + nout + '`.' ); 108 } 109 narrays = nin + nout; 110 if ( narrays === 0 ) { 111 throw new Error( 'invalid arguments. Interface must accept at least one strided input and/or output array. Based on the provided arguments, `nin+nout` equals `0`.' ); 112 } 113 if ( fcn ) { 114 nfcns = types.length / narrays; 115 if ( !isInteger( nfcns ) ) { 116 throw new Error( 'invalid argument. Unexpected number of types. A type must be specified for each strided input and output array for each provided strided array function.' ); 117 } 118 } else { 119 nfcns = fcns.length; 120 if ( types.length !== nfcns*narrays ) { 121 throw new Error( 'invalid argument. Unexpected number of types. A type must be specified for each strided input and output array for each provided strided array function.' ); 122 } 123 } 124 if ( data && data.length !== nfcns ) { 125 throw new Error( 'invalid argument. The third argument must have the same number of elements as the first argument.' ); 126 } 127 // Determine whether the strided array interface includes offsets: 128 if ( (narrays*2)+1 === nargs ) { 129 hasOffsets = false; 130 } else if ( (narrays*3)+1 === nargs ) { 131 hasOffsets = true; 132 } else { 133 throw new Error( 'invalid argument. Fourth argument is incompatible with the number of strided input and output arrays.' ); 134 } 135 // Determine the "stride" for accessing related arguments: 136 if ( hasOffsets ) { 137 strideArgs = 3; 138 } else { 139 strideArgs = 2; 140 } 141 // Compute the index of the first output strided array argument: 142 iout = ( nin*strideArgs ) + 1; 143 144 return dispatcher; 145 146 /** 147 * Strided array function interface which performs multiple dispatch. 148 * 149 * @private 150 * @param {integer} N - number of indexed elements 151 * @param {Collection} x - strided array 152 * @param {integer} strideX - index increment for `x` 153 * @param {...(Collection|integer|NonNegativeInteger)} args - array arguments (arrays, strides, and offsets) 154 * @throws {Error} insufficient arguments 155 * @throws {Error} too many arguments 156 * @throws {TypeError} first argument must be an integer 157 * @throws {TypeError} input array strides must be integers 158 * @throws {TypeError} output array strides must be integers 159 * @throws {TypeError} input array offsets must be nonnegative integers 160 * @throws {TypeError} output array offsets must be nonnegative integers 161 * @throws {TypeError} input array arguments must be array-like objects 162 * @throws {TypeError} output array arguments must be array-like objects 163 * @throws {RangeError} input array arguments must have sufficient elements based on the associated stride and the number of indexed elements 164 * @throws {RangeError} output array arguments must have sufficient elements based on the associated stride and the number of indexed elements 165 * @throws {TypeError} unable to resolve a strided array function supporting the provided array argument data types 166 * @returns {(Collection|Array<Collection>|void)} destination array(s) 167 */ 168 function dispatcher() { 169 var strides; 170 var offsets; 171 var arrays; 172 var dtypes; 173 var shape; 174 var argc; 175 var idx; 176 var N; 177 var v; 178 var f; 179 var i; 180 var j; 181 182 argc = arguments.length; 183 if ( argc !== nargs ) { 184 if ( argc < nargs ) { 185 throw new Error( 'invalid invocation. Insufficient arguments.' ); 186 } 187 throw new Error( 'invalid invocation. Too many arguments.' ); 188 } 189 N = arguments[ 0 ]; 190 if ( !isInteger( N ) ) { 191 throw new TypeError( 'invalid argument. First argument must be an integer.' ); 192 } 193 shape = [ N ]; 194 195 // Strides for both input and output strided arrays are every `strideArgs` arguments beginning from the third argument... 196 strides = []; 197 for ( i = 2; i < nargs; i += strideArgs ) { 198 v = arguments[ i ]; 199 if ( !isInteger( v ) ) { 200 if ( i < iout ) { 201 throw new TypeError( 'invalid argument. Input array stride argument must be an integer.' ); 202 } else { 203 throw new TypeError( 'invalid argument. Output array stride argument must be an integer.' ); 204 } 205 } 206 strides.push( v ); 207 } 208 if ( hasOffsets ) { 209 // Offsets for both input and output strided arrays are every `strideArgs` arguments beginning from the fourth argument... 210 offsets = []; 211 for ( i = 3; i < nargs; i += strideArgs ) { 212 v = arguments[ i ]; 213 if ( !isNonNegativeInteger( v ) ) { 214 if ( i < iout ) { 215 throw new TypeError( 'invalid argument. Input array offset argument must be a nonnegative integer.' ); 216 } else { 217 throw new TypeError( 'invalid argument. Output array offset argument must be a nonnegative integer.' ); 218 } 219 } 220 offsets.push( v ); 221 } 222 } 223 // Input and output strided arrays are every `strideArgs` arguments beginning from the second argument... 224 arrays = []; 225 dtypes = []; 226 for ( i = 1; i < nargs; i += strideArgs ) { 227 v = arguments[ i ]; 228 if ( !isCollection( v ) ) { 229 if ( i < iout ) { 230 throw new TypeError( 'invalid argument. Input array argument must be an array-like object.' ); 231 } else { 232 throw new TypeError( 'invalid argument. Output array argument must be an array-like object.' ); 233 } 234 } 235 j = (i-1) / strideArgs; 236 if ( hasOffsets ) { 237 idx = offsets[ j ] + ( (N-1)*strides[j] ); 238 if ( N > 0 && (idx < 0 || idx >= v.length) ) { 239 if ( i < iout ) { 240 throw new RangeError( 'invalid argument. Input array argument has insufficient elements based on the associated stride and the number of indexed elements.' ); 241 } else { 242 throw new RangeError( 'invalid argument. Output array argument has insufficient elements based on the associated stride and the number of indexed elements.' ); 243 } 244 } 245 } else if ( (N-1)*abs(strides[j]) >= v.length ) { 246 if ( i < iout ) { 247 throw new RangeError( 'invalid argument. Input array argument has insufficient elements based on the associated stride and the number of indexed elements.' ); 248 } else { 249 throw new RangeError( 'invalid argument. Output array argument has insufficient elements based on the associated stride and the number of indexed elements.' ); 250 } 251 } 252 arrays.push( v ); 253 dtypes.push( dtype( v ) || 'generic' ); 254 } 255 // Resolve the strided array function satisfying the input array types: 256 idx = indexOfTypes( nfcns, narrays, types, narrays, 1, 0, dtypes, 1, 0 ); // eslint-disable-line max-len 257 258 // Check whether we were able to successfully resolve a strided array function: 259 if ( idx < 0 ) { 260 throw new TypeError( 'invalid arguments. Unable to resolve a strided array function supporting the provided array argument data types.' ); 261 } 262 // Retrieve the strided array function: 263 if ( fcn ) { 264 f = fcn; 265 } else { 266 f = fcns[ idx ]; 267 } 268 // Evaluate the strided array function: 269 if ( data ) { 270 if ( hasOffsets ) { 271 f( arrays, shape, strides, offsets, data[ idx ] ); 272 } else { 273 f( arrays, shape, strides, data[ idx ] ); 274 } 275 } else if ( hasOffsets ) { 276 f( arrays, shape, strides, offsets ); 277 } else { 278 f( arrays, shape, strides ); 279 } 280 if ( nout === 1 ) { 281 return arrays[ narrays-1 ]; 282 } 283 if ( nout === 0 ) { 284 return; 285 } 286 return arrays.slice( nin ); 287 } 288 } 289 290 291 // EXPORTS // 292 293 module.exports = dispatch;