Source: function/curry.js

import __ from "./placeholderArgument";
import arity from "./arity";

const _curryN = (length, received, func) => (...args) => {
  let combined = [];
  let argsIdx = 0;
  let left = length;
  let combinedIdx = 0;

  while (combinedIdx < received.length || argsIdx < args.length) {
    let arg;

    if (
      combinedIdx < received.length &&
      (received[combinedIdx] !== __ || argsIdx >= args.length)
    ) {
      arg = received[combinedIdx];
    } else {
      arg = args[argsIdx++];
    }

    if (arg !== __) {
      left--;
    }

    combined[combinedIdx++] = arg;
  }

  if (left <= 0) {
    return func.apply(this, combined);
  }

  return arity(left, _curryN(length, combined, func));
};

/**
 * Curries the provided function.
 * A curried function does not need to have all of its arguments provided at once.
 * If `f` is a ternary and `g = curry(f)`, then the following is equivalent:
 *
 * <pre>
 * g(1)(2)(3);
 * g(1)(2, 3);
 * g(1, 2)(3);
 * g(1, 2, 3);
 * </pre>
 *
 * Additionaly, the F.__ symbol can be used as a placeholder argument,
 * when you want to partially apply arguments with gaps inbetween them.
 *
 * <pre>
 * g(1, __, 3)(2);
 * g(__, __, 3)(1, 2);
 * g(__, __, 3)(__, 2)(1);
 * </pre>
 *
 * @param {function} func
 * @returns {function}
 */
const curry = (func) => _curryN(func.length, [], func);

export default curry;