Source: meta.js

/**
 * Meta-programming helpers.
 * @module @lumjs/core/meta
 */

const {F,S,isArray,isa,console} = require('./types');

/**
 * Get a stacktrace. Differs from browser to browser.
 *
 * Uses the `stack` property of an `Error` object as the source.
 * This is a super simplistic hack. For a more complete solution, try
 * the `stacktrace-js` library, which will be used in the new `@lumjs/debug`
 * library as a dependency.
 *
 * @param {string} [msg] - A message for the Error object.
 *
 * @returns {string[]} An array of stack strings.
 * @alias module:@lumjs/core/meta.stacktrace
 */
function stacktrace(msg)
{
  return (new Error(msg)).stack.split("\n");
}

exports.stacktrace = stacktrace;

/**
 * Abstract classes for Javascript.
 * @alias module:@lumjs/core/meta.AbstractClass
 */
class AbstractClass
{
  /**
   * If you want to mark a method as abstract use this.
   */
  $abstract(name)
  {
    if (name.indexOf('(') === -1)
    { // Add empty method signature.
      name += '()';
    }
    throw new Error(`Abstract method ${name} was not implemented`);
  }

  /**
   * Check for required properties
   * 
   * @param {...(string|Array)} needs - What is needed
   * 
   * If this is a `string` it should be in a format like:
   * 
   * - `methodName(arg1,arg2,arg3)`
   * - `anotherMethod(number, string, object) : boolean`
   * - `yetAnother (className) : resultClass`
   * 
   * The names are case sensitive, and we'll look for the method after 
   * stripping off anything from the first *non-word* character.
   * 
   * If this is an `Array`, the first item must be the name of a property,
   * and each other item should be a type checking value, or array of type 
   * checking values from the [TYPES]{@link module:@lumjs/core/types.TYPES} 
   * object, as used by [isa()]{@link module:@lumjs/core/types.isa}.
   * 
   * If you are calling this in an abstract class constructor, likely only 
   * the method checks will be useful, as the `super()` call must be done 
   * *before* any instance property assignments.
   * 
   */
  $needs(...needs)
  {
    const className = this.constructor.name;

    const getName = fullName => fullName.replace(/\W.*/, '');
    const missing = propName => 
    { 
      throw new Error(`${className} is missing ${propName}`); 
    }

    for (const need of needs)
    {
      if (typeof need === S)
      { // A simple method
        const meth = getName(need);
        if (typeof this[meth] !== F)
        {
          missing(need);
        }
      }
      else if (isArray(need))
      {
        const prop = getName(need[0]);
        const types = need.slice(1);
        if (!isa(this[prop], ...types))
        {
          missing(need);
        }
      }
    }
  }

}

exports.AbstractClass = AbstractClass;

/**
 * Function prototypes for async, generator, and async generator functions.
 * @namespace
 * @alias module:@lumjs/core/meta.Functions
 */
const Functions =
{
  /**
   * Constructor for dynamic generator functions.
   */
  Generator: Object.getPrototypeOf(function*(){}).constructor,

  /**
   * Constructor for dynamic async functions.
   */
  Async: Object.getPrototypeOf(async function(){}).constructor,

  /**
   * Constructor for dynamic async generator functions.
   */
  AsyncGenerator: Object.getPrototypeOf(async function*(){}).constructor,

}

exports.Functions = Functions;

/**
 * A placeholder function for when something is not implemented.
 * 
 * @param {boolean} [fatal=true] If `true` throw Error.
 *   If `false` use `console.error()` instead.
 * @param {string} [prefix=''] A prefix for the error message.
 * 
 * @returns {void}
 * @alias module:@lumjs/core/meta.NYI
 */
function NYI(fatal=true, prefix='') 
{ 
  const msg = prefix+"« NOT YET IMPLEMENTED »";
  if (fatal)
    throw new Error(msg);
  else
    console.error(msg);
}

exports.NYI = NYI;