/** @module */
import * as parsel from "https://parsel.verou.me/dist/parsel.js";
import matches from "./matches.js";
/**
* Extract all or some function calls from a string
* @param {string} value - The value to extract function calls from.
* Note that this will also extract nested function calls, you can use `pos` to discard those if they are not of interest.
* @param {Object} [test]
* @param {string|RegExp|Function|Array} test.names
* @param {string|RegExp|Function|Array} test.args
* @param {Boolean} test.topLevel If true, only return top-level functions
* @return {Array<Object>} Array of objects, one for each function call with `{name, args, pos}` keys
*/
export default function extractFunctionCalls(value, test) {
// First, extract all function calls
let ret = [];
for (let match of value.matchAll(/(?<name>[\w-]+)\(/gi)) {
let index = match.index;
let openParen = index + match[0].length;
let rawArgs = parsel.gobbleParens(value, openParen - 1);
let args = rawArgs.slice(1, -1).trim();
let name = match.groups.name;
ret.push({name, pos: [index, index + match[0].length + rawArgs.length - 1], args})
}
if (test) {
if (test.names || test.args) {
ret = ret.filter(f => {
return matches(f.name, test.names) && matches(f.args, test.args);
});
}
if (test.topLevel && ret.length > 0) {
// Filter out nested functions
let [start, end] = ret[0].pos;
// Note that because we did the rest of the filtering earlier, this only takes into account
// the functions that passed the test. E.g. if we're only extracting rgb() functions, it will
// NOT consider linear-gradient(rgb(...)) as nested.
ret = ret.filter(f => {
let [s, e] = f.pos;
if (s > start && e < end) {
// Nested
return false;
}
// Not nested
[start, end] = [s, e];
return true;
});
}
}
return ret;
}