Source: infernalUtils.js


module.exports = {
    equals: equals,
    parseParameters: parseFactPaths,
    getContext: getContext,
    compilePath: compilePath,
    typeof: _typeof
};

//https://regex101.com/r/zYhguP/6/
const paramRegex = /(?:\/\*?@ *([\w/.]+?) *\*\/ *\w+,?)|[(,]? *(\w+) *[,)]?/g;
const trailingTermRemovalRegex = /[^/]+?$/;
const pathCompactionRegex = /\/[^/.]+\/\.\./;
const currentPathRemoval = /\.\//g;

/**
 * Compare two values and return true if they are equal. Fix date comparison issue and insure
 * both types are the same.
 * 
 * @param {*} a The first value used to compare.
 * @param {*} b The second value used to caompare.
 */
function equals(a, b) {
    var aValue = a;
    if (a instanceof Date) {
        aValue = a.getTime(); 
    }
    var bValue = b;
    if (b instanceof Date) {
        bValue = b.getTime();
    }
    return (aValue === bValue);
}


function _typeof(o) {
    if (o instanceof Date) {
        return "date";
    }
    else if (o instanceof Array) {
        return "array";
    }
    else {
        return typeof o;
    }
}

/**
 * Generator that parses all fact paths derived from the given rule parameters. Either return the
 * parameter name or the path contained by the optional attribute comment. Single parameter lambda
 * function must put the parameter between parenthesis to be parsed properly.
 * 
 * @example
 * 
 * Using an attribute comment:
 * 
 *     async function(/¤@ /path/to/fact ¤/ param1, param2) {}
 * 
 * In the above example: 
 * 
 *   1) ¤ replaces * because using * and / one after the other ends the documentation comment.
 *   2) returned fact paths would be ['/path/to/fact', 'param2']
 * 
 * @param {AsyncFunction} rule The rule 
 */
function* parseFactPaths(rule) {
    let allParamsRegex = /\((.+?)\)|= ?(?:async)? ?(\w+) ?=>/g;
    let paramCode = (rule.toString().split(')')[0] + ')');
    let paramCodeWithoutEol = paramCode.replace(/\s+/g, " ");
    let allParamMatch = allParamsRegex.exec(paramCodeWithoutEol);
    if (!allParamMatch) return;

    let allParams = allParamMatch[1];
    let paramMatch = paramRegex.exec(allParams);
    do {
        yield (paramMatch[1] || paramMatch[2]);
        paramMatch = paramRegex.exec(allParams);
    } 
    while (paramMatch)
}


/**
 * Extracts the context from the fact or rule name. Must be a compiled path, not a path that
 * contains "../" or "./".
 * 
 * @example
 * 
 * Given the rule path: '/path/to/some/rule'
 * The returned value would be: '/path/to/some'
 * 
 * @param {String} compiledPath The compiled path to extract the context from.
 */
function getContext(compiledPath) {
    return compiledPath.replace(trailingTermRemovalRegex, "");
}

/**
 * Create a compacted fact name from a given fact name. Path do not end with a trailing '/'.
 * 
 *  '../' Denote the parent term. Move up one term in the context or path.
 *  './'  Denote the current term. Removed from the context or path.
 * 
 * Examples: 
 * 
 *  '/a/given/../correct/./path' is compiled to '/a/correct/path'
 * 
 * @param {String} path The path or context to compile into a context.
 */
function compilePath(path) {
    let current = path;
    let next = path.replace(pathCompactionRegex, "");
    while (next != current) {
        current = next;
        next = current.replace(pathCompactionRegex, "");
    }
    if (current.startsWith("/..")) {
        throw new Error(`Unable to compile the path '${path}' properly.`);
    }
    return current.replace(currentPathRemoval, "");
}