@stdlib/utils
Version:
Standard utilities.
221 lines (195 loc) • 5.58 kB
JavaScript
/**
* @license Apache-2.0
*
* Copyright (c) 2022 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.
*/
;
// MODULES //
var isFunction = require( '@stdlib/assert/is-function' );
var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive;
var reNativeFunction = require( '@stdlib/regexp/native-function' ).REGEXP;
var function2string = require( '@stdlib/function/to-string' );
var Fcn = require( '@stdlib/function/ctor' );
var format = require( '@stdlib/string/format' );
// VARIABLES //
/**
* Captures everything that is not a right parenthesis immediately after the first left parenthesis.
*
* Regular expression: `/^\s*function[^(]*\(([^)]*)/i`
*
* - `/^\s*`
* - Match zero or more spaces at beginning
*
* - `function`
* - Match the word `function`
*
* - `[^(]*`
* - Match anything except a left parenthesis `(` zero or more times
*
* - `\(`
* - Match a left parenthesis `(`
*
* - `()`
* - Capture
*
* - `[^)]*`
* - Match anything except a right parenthesis `)` zero or more times
*
* - `)`
* - Match a right parenthesis `)`
*
* - `/i`
* - Ignore case
*
* @private
* @constant
* @type {RegExp}
* @default /^\s*function[^(]*\(([^)]*)/i
*/
var RE_PARAMETERS = /^\s*function[^(]*\(([^)]*)/i;
// MAIN //
/**
* Decorates a provided function such that the function's return value is provided as an argument to another function.
*
* @param {Function} fcn - function to decorate
* @param {NonNegativeInteger} arity - number of parameters
* @param {Function} after - function to invoke with the result of the decorated function
* @param {*} [thisArg] - evaluation context for `after`
* @throws {TypeError} first argument must be a function
* @throws {TypeError} second argument must be a nonnegative integer
* @throws {TypeError} third argument must be a function
* @returns {Function} decorator
*
* @example
* var abs = require( '@stdlib/math/base/special/abs' );
*
* function negate( v ) {
* return -v;
* }
*
* var f = decorateAfter( abs, abs.length, negate );
* // returns <Function>
*
* var v = f( -5 );
* // returns -5
*
* v = f( 5 );
* // returns -5
*
* @example
* var abs = require( '@stdlib/math/base/special/abs' );
*
* function log( v ) {
* console.log( v );
* }
*
* var f = decorateAfter( abs, abs.length, log );
* // returns <Function>
*
* var v = f( -5 );
* // returns 5
*
* v = f( 5 );
* // returns 5
*
* @example
* var abs = require( '@stdlib/math/base/special/abs' );
*
* function counter() {
* this.count += 1;
* }
*
* var ctx = {
* 'count': 0
* };
*
* var f = decorateAfter( abs, abs.length, counter, ctx );
* // returns <Function>
*
* var v = f( -5 );
* // returns 5
*
* v = f( 5 );
* // returns 5
*
* var count = ctx.count;
* // returns 2
*/
function decorateAfter( fcn, arity, after, thisArg ) {
var params;
var str;
var len;
var f;
var i;
if ( !isFunction( fcn ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', fcn ) );
}
if ( !isNonNegativeInteger( arity ) ) {
throw new TypeError( format( 'invalid argument. Second argument must be a nonnegative integer. Value: `%s`.', arity ) );
}
if ( !isFunction( after ) ) {
throw new TypeError( format( 'invalid argument. Third argument must be a function. Value: `%s`.', after ) );
}
str = function2string( fcn );
// Code generation. Start with the function definition for creating the decorator:
f = 'return function make(__$$fcn$$__,__$$after$$__,__$$thisArg$$__){';
// Include the "use strict" directive:
f += '"use strict";';
// Generate a list of function parameters:
if ( reNativeFunction.test( str ) ) {
params = [];
for ( i = 0; i < arity; i++ ) {
params.push( 'x'+i );
}
} else {
params = str.match( RE_PARAMETERS )[ 1 ];
if ( params ) {
params = params.split( ',' );
} else {
params = [];
}
len = params.length;
if ( len < arity ) {
for ( i = len; i < arity; i++ ) {
params.push( '__$$x'+i+'$$__' );
}
} else if ( len > arity ) {
params.length = arity;
}
}
params = params.join( ',' );
// Create the decorator definition:
f += 'return function decorator(' + params + '){';
// Initialize an array for storing provided arguments:
f += 'var __$$args$$__=[];';
// Gather provided arguments:
f += 'for (var __$$i$$__=0;__$$i$$__<arguments.length;__$$i$$__++){__$$args$$__.push(arguments[__$$i$$__]);}';
// Invoke the decorated function:
f += 'var __$$r1$$__ = __$$fcn$$__.apply(null,__$$args$$__);';
// Provide the result to the `after` function:
f += 'var __$$r2$$__ = __$$after$$__.call(__$$thisArg$$__,__$$r1$$__);';
// Return the result:
f += 'return (__$$r2$$__ === void 0) ? __$$r1$$__ : __$$r2$$__;';
// Close the decorator function:
f += '}';
// Close the outer function:
f += '}';
// Add a source directive for debugging:
f += '//# sourceURL=decorateAfter.factory.js';
// Create the function the global scope:
return (new Fcn( f ))()( fcn, after, thisArg );
}
// EXPORTS //
module.exports = decorateAfter;