diff --git a/to-fancy/lib/ctor.js b/to-fancy/lib/ctor.js new file mode 100644 index 00000000..7af31b1a --- /dev/null +++ b/to-fancy/lib/ctor.js @@ -0,0 +1,93 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2024 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MAIN // + +/** +* Returns a trap for constructing new array instances. +* +* @private +* @param {Object} ctx - context object +* @param {Function} ctx.array2fancy - function for creating a proxied array +* @returns {Function} handler +*/ +function factory( ctx ) { + return constructor; + + /** + * Trap for constructing new array instances. + * + * @private + * @param {Object} target - target object + * @param {Array} args - list of constructor arguments + * @param {Object} newTarget - constructor that was originally called + * @returns {*} new instance + */ + function constructor( target, args ) { + var x; + var a; + + a = args; + switch ( a.length ) { + case 0: + x = new target(); + break; + case 1: + x = new target( a[0] ); + break; + case 2: + x = new target( a[0], a[1] ); + break; + case 3: + x = new target( a[0], a[1], a[2] ); + break; + case 4: + x = new target( a[0], a[1], a[2], a[3] ); + break; + case 5: + x = new target( a[0], a[1], a[2], a[3], a[4] ); + break; + case 6: + x = new target( a[0], a[1], a[2], a[3], a[4], a[5] ); + break; + case 7: + x = new target( a[0], a[1], a[2], a[3], a[4], a[5], a[6] ); + break; + case 8: + x = new target( a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7] ); + break; + case 9: + x = new target( a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8] ); // eslint-disable-line max-len + break; + case 10: + x = new target( a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9] ); // eslint-disable-line max-len + break; + default: + // Fallback to using `apply`; however, some constructors may error if the constructor is not callable (i.e., if a constructor always requires `new`): + x = target.apply( null, a ); + } + return ctx.array2fancy( x ); + } +} + + +// EXPORTS // + +module.exports = factory; diff --git a/to-fancy/lib/get.js b/to-fancy/lib/get.js index ad04ffb6..ccd444f9 100644 --- a/to-fancy/lib/get.js +++ b/to-fancy/lib/get.js @@ -35,9 +35,10 @@ var getSlice = require( './get_slice.js' ); * * @private * @param {Object} ctx - context object -* @param {Function} ctx.array2fancy - function for creating a proxied array * @param {Function} ctx.getter - accessor for retrieving array elements * @param {boolean} ctx.strict - boolean indicating whether to enforce strict bounds checking +* @param {Function} ctx.ctor - proxied array constructor +* @param {Function} ctx.postGetSlice - function to process a retrieved slice * @returns {Function} handler */ function factory( ctx ) { @@ -61,7 +62,7 @@ function factory( ctx ) { if ( hasProperty( target, property ) || !isString( property ) ) { return getValue( target, property, receiver, ctx ); } - return getSlice( target, property, receiver, ctx.strict ); + return getSlice( target, property, receiver, ctx ); } } diff --git a/to-fancy/lib/get_slice.js b/to-fancy/lib/get_slice.js index d73f54a3..f92cb9c6 100644 --- a/to-fancy/lib/get_slice.js +++ b/to-fancy/lib/get_slice.js @@ -34,19 +34,21 @@ var prop2slice = require( './prop2slice.js' ); * @param {Object} target - target object * @param {string} property - property name * @param {Object} receiver - the proxy object or an object inheriting from the proxy -* @param {boolean} strict - boolean indicating whether to enforce strict bounds checking +* @param {Object} ctx - context object +* @param {Function} ctx.postGetSlice - function to process a retrieved slice +* @param {boolean} ctx.strict - boolean indicating whether to enforce strict bounds checking * @throws {Error} invalid slice operation * @throws {RangeError} slice exceeds array bounds * @returns {(Collection|void)} result */ -function getSlice( target, property, receiver, strict ) { - var s = prop2slice( target, property, strict ); +function getSlice( target, property, receiver, ctx ) { + var s = prop2slice( target, property, ctx.strict ); if ( s === null ) { // Ensure consistency with normal array behavior by returning `undefined` for any "unrecognized" property name: return; } try { - return slice( receiver, s, strict ); + return ctx.postGetSlice( slice( target, s, ctx.strict ) ); } catch ( err ) { // In principle, we should only error when in "strict" mode and a slice exceeds array bounds... throw new err.constructor( errMessage( err.message ) ); diff --git a/to-fancy/lib/get_slice_wrapper.js b/to-fancy/lib/get_slice_wrapper.js new file mode 100644 index 00000000..7909762f --- /dev/null +++ b/to-fancy/lib/get_slice_wrapper.js @@ -0,0 +1,50 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2024 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MAIN // + +/** +* Returns a wrapper function for processing slices after retrieval. +* +* @private +* @param {Function} array2fancy - function for creating a proxied array +* @param {Object} opts - options +* @param {boolean} opts.strict - boolean indicating whether to perform strict bounds checking +* @returns {Function} wrapper function +*/ +function wrapper( array2fancy, opts ) { + return wrap; + + /** + * Returns a proxied array. + * + * @private + * @param {Array} x - input array + * @returns {Array} proxied array + */ + function wrap( x ) { + return array2fancy( x, opts ); + } +} + + +// EXPORTS // + +module.exports = wrapper; diff --git a/to-fancy/lib/get_value.js b/to-fancy/lib/get_value.js index 299c165d..84ac3951 100644 --- a/to-fancy/lib/get_value.js +++ b/to-fancy/lib/get_value.js @@ -33,14 +33,14 @@ var isFunction = require( '@stdlib/assert/is-function' ); * @param {(string|symbol)} property - property * @param {Object} receiver - the proxy object or an object inheriting from the proxy * @param {Object} ctx - context object -* @param {Function} ctx.array2fancy - function for creating a proxied array +* @param {Function} ctx.ctor - proxied array constructor * @returns {*} result */ function getValue( target, property, receiver, ctx ) { var value = target[ property ]; if ( isFunction( value ) ) { if ( value === target.constructor ) { - return ctor; + return ctx.ctor; } return wrapper; } @@ -62,62 +62,6 @@ function getValue( target, property, receiver, ctx ) { } return value.apply( ( this === receiver ) ? target : this, args ); // eslint-disable-line no-invalid-this } - - /** - * Constructor wrapper. - * - * @private - * @returns {*} results - */ - function ctor() { - var x; - var a; - var i; - - a = []; - for ( i = 0; i < arguments.length; i++ ) { - a.push( arguments[ i ] ); - } - switch ( a.length ) { - case 0: - x = new value(); - break; - case 1: - x = new value( a[0] ); - break; - case 2: - x = new value( a[0], a[1] ); - break; - case 3: - x = new value( a[0], a[1], a[2] ); - break; - case 4: - x = new value( a[0], a[1], a[2], a[3] ); - break; - case 5: - x = new value( a[0], a[1], a[2], a[3], a[4] ); - break; - case 6: - x = new value( a[0], a[1], a[2], a[3], a[4], a[5] ); - break; - case 7: - x = new value( a[0], a[1], a[2], a[3], a[4], a[5], a[6] ); - break; - case 8: - x = new value( a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7] ); - break; - case 9: - x = new value( a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8] ); // eslint-disable-line max-len - break; - case 10: - x = new value( a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9] ); // eslint-disable-line max-len - break; - default: - // Fallback to using `apply`; however, some constructors may error if the constructor is not callable (i.e., always requires `new`): - x = value.apply( null, a ); - } - return ctx.array2fancy( x ); - } } diff --git a/to-fancy/lib/main.js b/to-fancy/lib/main.js index 40080810..c7184757 100644 --- a/to-fancy/lib/main.js +++ b/to-fancy/lib/main.js @@ -25,10 +25,12 @@ var Proxy = require( '@stdlib/proxy/ctor' ); var arraylike2object = require( './../../base/arraylike2object' ); var format = require( '@stdlib/string/format' ); var setElementWrapper = require( './set_element_wrapper.js' ); +var getSliceWrapper = require( './get_slice_wrapper.js' ); var hasProxySupport = require( './has_proxy_support.js' ); var defaults = require( './defaults.js' ); var validate = require( './validate.js' ); var validator = require( './validator.js' ); +var ctor = require( './ctor.js' ); var get = require( './get.js' ); var set = require( './set.js' ); @@ -91,11 +93,16 @@ function array2fancy( x ) { 'dtype': dt, 'getter': arr.accessors[ 0 ], 'setter': arr.accessors[ 1 ], - 'valueWrapper': setElementWrapper( dt ), + 'preSetElement': setElementWrapper( dt ), + 'postGetSlice': getSliceWrapper( array2fancy, opts ), 'strict': opts.strict, 'validator': validator( dt ), - 'array2fancy': array2fancy + 'array2fancy': array2fancy, + 'ctor': null }; + o.ctor = new Proxy( x.constructor || Array, { + 'construct': ctor( o ) + }); return new Proxy( x, { 'get': get( o ), 'set': set( o ) diff --git a/to-fancy/lib/set.js b/to-fancy/lib/set.js index 784a9802..d16df3ab 100644 --- a/to-fancy/lib/set.js +++ b/to-fancy/lib/set.js @@ -38,6 +38,9 @@ var setSlice = require( './set_slice.js' ); * @param {Function} ctx.setter - accessor for setting array elements * @param {string} ctx.dtype - array data type * @param {boolean} ctx.strict - boolean indicating whether to enforce strict bounds checking +* @param {Function} ctx.validator - function for validating new values +* @param {Function} ctx.setter - accessor for setting array elements +* @param {(Function|null)} ctx.preSetElement - function for normalizing new values (if necessary) * @returns {Function} handler */ function factory( ctx ) { diff --git a/to-fancy/lib/set_element.js b/to-fancy/lib/set_element.js index edddc6ec..fb2946cc 100644 --- a/to-fancy/lib/set_element.js +++ b/to-fancy/lib/set_element.js @@ -37,7 +37,7 @@ var resolveIndex = require( './resolve_index.js' ); * @param {string} ctx.dtype - target array data type * @param {boolean} ctx.strict - boolean indicating whether to enforce strict bounds checking * @param {Function} ctx.validator - function for validating new values -* @param {(Function|null)} ctx.valueWrapper - function for normalizing new values (if necessary) +* @param {(Function|null)} ctx.preSetElement - function for normalizing new values (if necessary) * @throws {TypeError} assigned value cannot be safely cast to the target array data type * @throws {TypeError} target array must have a supported data type * @returns {boolean} boolean indicating whether assignment succeeded @@ -50,8 +50,8 @@ function setElement( target, property, value, ctx ) { if ( err ) { throw err; } - if ( ctx.valueWrapper ) { - v = ctx.valueWrapper( value ); + if ( ctx.preSetElement ) { + v = ctx.preSetElement( value ); } else { v = value; } diff --git a/to-fancy/test/test.get.slice.js b/to-fancy/test/test.get.slice.js index 6857cd73..bb9db4c6 100644 --- a/to-fancy/test/test.get.slice.js +++ b/to-fancy/test/test.get.slice.js @@ -341,3 +341,97 @@ tape( 'when `strict` is `true`, the function returns an array-like object which }; } }); + +tape( 'the function returns an array-like object supporting slices which can themselves support slices (typed)', opts, function test( t ) { + var expected; + var actual; + var x; + var y; + var z; + var s; + + x = new Int32Array( [ 1, 2, 3, 4 ] ); + y = array2fancy( x ); + + s = new Slice(); + expected = new Int32Array( [ 1, 2, 3, 4 ] ); + actual = y[ s ]; + + t.strictEqual( actual instanceof Int32Array, true, 'returns expected value' ); + t.notEqual( actual, x, 'different reference' ); + t.notEqual( actual, y, 'different reference' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + z = actual; + + s = new Slice( null, null, 2 ); + expected = new Int32Array( [ 1, 3 ] ); + actual = z[ s ]; + + t.strictEqual( actual instanceof Int32Array, true, 'returns expected value' ); + t.notEqual( actual, x, 'different reference' ); + t.notEqual( actual, y, 'different reference' ); + t.notEqual( actual, z, 'different reference' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + z = actual; + + s = new Slice( null, null, -1 ); + expected = new Int32Array( [ 3, 1 ] ); + actual = z[ s ]; + + t.strictEqual( actual instanceof Int32Array, true, 'returns expected value' ); + t.notEqual( actual, x, 'different reference' ); + t.notEqual( actual, y, 'different reference' ); + t.notEqual( actual, z, 'different reference' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + t.end(); +}); + +tape( 'the function returns an array-like object supporting slices which can themselves support slices (generic)', opts, function test( t ) { + var expected; + var actual; + var x; + var y; + var z; + var s; + + x = [ 1, 2, 3, 4 ]; + y = array2fancy( x ); + + s = new Slice(); + expected = [ 1, 2, 3, 4 ]; + actual = y[ s ]; + + t.strictEqual( actual instanceof Array, true, 'returns expected value' ); + t.notEqual( actual, x, 'different reference' ); + t.notEqual( actual, y, 'different reference' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + z = actual; + + s = new Slice( null, null, 2 ); + expected = [ 1, 3 ]; + actual = z[ s ]; + + t.strictEqual( actual instanceof Array, true, 'returns expected value' ); + t.notEqual( actual, x, 'different reference' ); + t.notEqual( actual, y, 'different reference' ); + t.notEqual( actual, z, 'different reference' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + z = actual; + + s = new Slice( null, null, -1 ); + expected = [ 3, 1 ]; + actual = z[ s ]; + + t.strictEqual( actual instanceof Array, true, 'returns expected value' ); + t.notEqual( actual, x, 'different reference' ); + t.notEqual( actual, y, 'different reference' ); + t.notEqual( actual, z, 'different reference' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + t.end(); +}); diff --git a/to-fancy/test/test.js b/to-fancy/test/test.js index a14895b4..71ea5060 100644 --- a/to-fancy/test/test.js +++ b/to-fancy/test/test.js @@ -22,11 +22,19 @@ var tape = require( 'tape' ); var proxyquire = require( 'proxyquire' ); +var hasProxySupport = require( '@stdlib/assert/has-proxy-support' ); var Int32Array = require( './../../int32' ); var propertiesIn = require( '@stdlib/utils/properties-in' ); var array2fancy = require( './../lib' ); +// VARIABLES // + +var opts = { + 'skip': !hasProxySupport() +}; + + // TESTS // tape( 'main export is a function', function test( t ) { @@ -288,6 +296,76 @@ tape( 'the function returns an array-like object supporting the calling of input } }); +tape( 'the function returns an array-like object supporting the return of array instances supporting slice expressions (generic)', opts, function test( t ) { + var expected; + var actual; + var x; + var y; + var z; + + x = [ 1, 2, 3, 4 ]; + y = array2fancy( x ); + + expected = [ 1, 2, 3, 4 ]; + actual = y.slice(); + + t.strictEqual( actual instanceof Array, true, 'returns expected value' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + t.strictEqual( x[ ':' ], void 0, 'returns expected value' ); + + expected = [ 1, 2, 3, 4 ]; + actual = y[ ':' ]; + + t.strictEqual( actual instanceof Array, true, 'returns expected value' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + z = actual; + + expected = [ 1, 2, 3, 4 ]; + actual = z[ ':' ]; + + t.strictEqual( actual instanceof Array, true, 'returns expected value' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + t.end(); +}); + +tape( 'the function returns an array-like object supporting the return of array instances supporting slice expressions (typed)', opts, function test( t ) { + var expected; + var actual; + var x; + var y; + var z; + + x = new Int32Array( [ 1, 2, 3, 4 ] ); + y = array2fancy( x ); + + expected = new Int32Array( [ 1, 2, 3, 4 ] ); + actual = y.slice(); + + t.strictEqual( actual instanceof Int32Array, true, 'returns expected value' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + t.strictEqual( x[ ':' ], void 0, 'returns expected value' ); + + expected = new Int32Array( [ 1, 2, 3, 4 ] ); + actual = y[ ':' ]; + + t.strictEqual( actual instanceof Int32Array, true, 'returns expected value' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + z = actual; + + expected = new Int32Array( [ 1, 2, 3, 4 ] ); + actual = z[ ':' ]; + + t.strictEqual( actual instanceof Int32Array, true, 'returns expected value' ); + t.deepEqual( actual, expected, 'returns expected value' ); + + t.end(); +}); + tape( 'the function returns an array-like object supporting constructor access (generic)', function test( t ) { var expected; var actual;