Source: runtime.js

var Compiler = require('./compiler');


/** A set of utility functions that are called by the compiled Javascript
 *  functions, these are included locally in the output of {@link
 *  MessageFormat#compile compile()}.
 *
 * @class
 * @param {MessageFormat} mf - A MessageFormat instance
 */
function Runtime(mf) {
  this.mf = mf;
  this.setStrictNumber(mf.strictNumberSign);
}

module.exports = Runtime;


/** Utility function for `#` in plural rules
 *
 *  Will throw an Error if `value` has a non-numeric value and `offset` is
 *  non-zero or {@link MessageFormat#setStrictNumberSign} is set.
 *
 * @function Runtime#number
 * @param {number} value - The value to operate on
 * @param {string} name - The name of the argument, used for error reporting
 * @param {number} [offset=0] - An optional offset, set by the surrounding context
 * @returns {number|string} The result of applying the offset to the input value
 */
function defaultNumber(value, name, offset) {
  if (!offset) return value;
  if (isNaN(value)) throw new Error('Can\'t apply offset:' + offset + ' to argument `' + name +
                                    '` with non-numerical value ' + JSON.stringify(value) + '.');
  return value - offset;
}


/** @private */
function strictNumber(value, name, offset) {
  if (isNaN(value)) throw new Error('Argument `' + name + '` has non-numerical value ' + JSON.stringify(value) + '.');
  return value - (offset || 0);
}


/** Set how strictly the {@link number} method parses its input.
 *
 *  According to the ICU MessageFormat spec, `#` can only be used to replace a
 *  number input of a `plural` statement. By default, messageformat.js does not
 *  throw a runtime error if you use non-numeric argument with a `plural` rule,
 *  unless rule also includes a non-zero `offset`.
 *
 *  This is called by {@link MessageFormat#setStrictNumberSign} to follow the
 *  stricter ICU MessageFormat spec.
 *
 * @param {boolean} [enable=false]
 */
Runtime.prototype.setStrictNumber = function(enable) {
  this.number = enable ? strictNumber : defaultNumber;
}


/** Utility function for `{N, plural|selectordinal, ...}`
 *
 * @param {number} value - The key to use to find a pluralization rule
 * @param {number} offset - An offset to apply to `value`
 * @param {function} lcfunc - A locale function from `pluralFuncs`
 * @param {Object.<string,string>} data - The object from which results are looked up
 * @param {?boolean} isOrdinal - If true, use ordinal rather than cardinal rules
 * @returns {string} The result of the pluralization
 */
Runtime.prototype.plural = function(value, offset, lcfunc, data, isOrdinal) {
  if ({}.hasOwnProperty.call(data, value)) return data[value];
  if (offset) value -= offset;
  var key = lcfunc(value, isOrdinal);
  if (key in data) return data[key];
  return data.other;
}


/** Utility function for `{N, select, ...}`
 *
 * @param {number} value - The key to use to find a selection
 * @param {Object.<string,string>} data - The object from which results are looked up
 * @returns {string} The result of the select statement
 */
Runtime.prototype.select = function(value, data) {
  if ({}.hasOwnProperty.call(data, value)) return data[value];
  return data.other;
}


/** @private */
Runtime.prototype.toString = function(pluralFuncs, compiler) {
  function _stringify(o, level) {
    if (typeof o != 'object') {
      var funcStr = o.toString().replace(/^(function )\w*/, '$1');
      var indent = /([ \t]*)\S.*$/.exec(funcStr);
      return indent ? funcStr.replace(new RegExp('^' + indent[1], 'mg'), '') : funcStr;
    }
    var s = [];
    for (var i in o) {
      if (level == 0) s.push('var ' + i + ' = ' + _stringify(o[i], level + 1) + ';\n');
      else s.push(Compiler.propname(i) + ': ' + _stringify(o[i], level + 1));
    }
    if (level == 0) return s.join('');
    if (s.length == 0) return '{}';
    var indent = '  '; while (--level) indent += '  ';
    return '{\n' + s.join(',\n').replace(/^/gm, indent) + '\n}';
  }

  var obj = {};
  Object.keys(compiler.locales).forEach(function(lc) { obj[Compiler.funcname(lc)] = pluralFuncs[lc]; });
  Object.keys(compiler.runtime).forEach(function(fn) { obj[fn] = this[fn]; }, this);
  var fmtKeys = Object.keys(compiler.formatters);
  var fmt = this.mf.fmt;
  if (fmtKeys.length) obj.fmt = fmtKeys.reduce(function(o, key) { o[key] = fmt[key]; return o; }, {});
  return _stringify(obj, 0);
}