Parsing function arguments (v3)

Revision 3 of this benchmark created on


Setup

const delimiters = {
	brackets: {
		open: ["{","[","("],
		close: ["}","]",")"]
	},
	str: ["'",'"',"`"],
	destructuring: {
        obj: ["{","}"],
        arr: ["[","]"]
	},
	cmmt: {
		open: ["/*","//"],
		close: ["*/", null]
	}
}
/**
 * Parses the parameters of a function.toString() and returns them as an object
 * @param {*} fn The function to get the parameters from
 * @param {*} args arguments to pass to the function
 * @returns An object with the parameters and their default values
 * @author [bye-csavier](https://github.com/bye-csavier)
*/
function getFnArgs(fn, ...args){

    if(typeof fn != 'function') return null;

	let [subStr, parsedArgs, openDelimiters, argList] = ['', "", ['('], ''];
	
	fn = fn.toString().split('') // function to "char" array
	let len = fn.length, READ_ARG = true, INSIDE_STR = false, INSIDE_DESTRUCT = {obj: false, arr: false}, INSIDE_CMMT = {block: false, line: false};
	
	const addArg = () =>{
		subStr = subStr.replace(/\s|\./g, '');
		if(subStr.length == 0) return;
		parsedArgs += `${subStr}:${subStr},\n`;
		subStr = '';
	}

	for(let i=fn.indexOf('(')+1, char; i<len && openDelimiters.length > 0; i++){
		
		char = fn[i];
		let idx = -1;

		if(!INSIDE_STR && !INSIDE_CMMT.block && !INSIDE_CMMT.line){

			if(char == "/" && fn[i+1] == "/") INSIDE_CMMT.line = true;
			else if(char == "/" && fn[i+1] == "*") INSIDE_CMMT.block = true;
			else if(READ_ARG && char == delimiters.destructuring.obj[0]){ INSIDE_DESTRUCT.obj = true; openDelimiters.push(char); idx=0; }
            else if(READ_ARG && char == delimiters.destructuring.arr[0]){ INSIDE_DESTRUCT.arr = true; openDelimiters.push(char); idx=0; }
            else if((INSIDE_DESTRUCT.obj && char == delimiters.destructuring.obj[1]) || (INSIDE_DESTRUCT.arr && char == delimiters.destructuring.arr[1])){
                INSIDE_DESTRUCT.obj = false; INSIDE_DESTRUCT.arr = false;
                if(READ_ARG) addArg();
                openDelimiters.pop(); idx=0;
            }
            else if(delimiters.brackets.open.indexOf(char) > -1) 
            {
                openDelimiters.push(char);
                idx = 0;
            }
            else{
                idx = delimiters.brackets.close.indexOf(char);
                if(idx > -1 && delimiters.brackets.open.indexOf(openDelimiters[openDelimiters.length-1]) == idx) openDelimiters.pop();
            }

		}

		if(INSIDE_CMMT.line){ if(char == "\n") INSIDE_CMMT.line = false; }
		else if(INSIDE_CMMT.block){ if(char == "/" && fn[i-1] == "*") INSIDE_CMMT.block = false; }
		else if(READ_ARG && !INSIDE_STR && idx < 0){
			if(char == ',') addArg();
			else if(char == "="){addArg(); READ_ARG = false;}
            else if(INSIDE_DESTRUCT.obj && char == ':') subStr = "";
			else subStr += char;
		}
		else if((openDelimiters.length == 1 || (INSIDE_DESTRUCT.obj || INSIDE_DESTRUCT.arr)) && char == ',') READ_ARG = true;
		else if(delimiters.str.indexOf(char) > -1){
			if(!INSIDE_STR) INSIDE_STR = char;
			else if(INSIDE_STR == char) INSIDE_STR = false;
		}

		argList += char;
	}
	addArg(); // to also push the last arg

	fn = eval(`(${argList}=>{return {${parsedArgs}}}`);
    parsedArgs = fn(...args);

	return parsedArgs;
}

const testFn = (
    nonEmpty=' ',
    end=')', 
    y=end, 
    x, 
    z=x, 
    comment='//', 
    template='`', 
    {destructuring, test = '2', ...rest}, 
    {defDestruct, defDestruct_a, defDestruct_b} = {defDestruct: '1', defDestruct_a: 2, defDestruct_b: '3'},
    a = {1: '2', 'hi': [3,4,'==)']},
    anArr = [z,y],
    brackets = (1.23+')'),
    math = Math.round(1.34*10),
    h={a:'ok \"', b:{c:'now=2,tricky=2', d:'rlly'}},
    [test2, destructuring2 = 3, two],
    [destructuring3,, 
        notIgnored],
    [destructuring4, ...rest2],
    [defDestruct2, defDestruct2_a] = [4,5],
    {a: destrct5, c: destrct5_a, e : destrct5_b = 5},
	lastArg
) => {}
const testFn2 = (a, 
    b=2, c={a:2, b:'lol'}, dee, e=[2,3,"a"],/*lol joke=2,2*/ foook=e, g=`test, ok`, h={a:'ok \"', b:{c:'lol=2,tricky=2', d:'lol'}}, i=[2,5,['x']], lastArg = "done" //ajaim, noMatch = 2)
) => {}

Test runner

Ready to run.

Testing in
TestOps/sec
parsing function arguments 1
getFnArgs(testFn,
undefined, 
            undefined, 
            undefined, 
            5, 
            undefined, 
            undefined, 
            undefined, 
            {destructuring: undefined, test:undefined, c: 3, b:4}, 
            undefined,
            undefined, 
            undefined, 
            undefined,
            undefined,
            undefined,
            [1,,'2'],
            [1,2,3],
            ["dest4", 1,2, {rest2:3}],
            undefined,
            {a: 1, c: null},
            undefined)
            
ready
parsing function arguments 2
getFnArgs(testFn2)
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.