/*!

* regjsgen 0.5.0
* Copyright 2014-2018 Benjamin Tan <https://bnjmnt4n.now.sh/>
* Available under MIT license <https://github.com/bnjmnt4n/regjsgen/blob/master/LICENSE>
*/

;(function() {

'use strict';

// Used to determine if values are of the language type `Object`.
var objectTypes = {
  'function': true,
  'object': true
};

// Used as a reference to the global object.
var root = (objectTypes[typeof window] && window) || this;

// Detect free variable `exports`.
var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;

// Detect free variable `module`.
var hasFreeModule = objectTypes[typeof module] && module && !module.nodeType;

// Detect free variable `global` from Node.js or Browserified code and use it as `root`.
var freeGlobal = freeExports && hasFreeModule && typeof global == 'object' && global;
if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal || freeGlobal.self === freeGlobal)) {
  root = freeGlobal;
}

// Used to check objects for own properties.
var hasOwnProperty = Object.prototype.hasOwnProperty;

/*--------------------------------------------------------------------------*/

// Generates a string based on the given code point.
// Based on https://mths.be/fromcodepoint by @mathias.
function fromCodePoint() {
  var codePoint = Number(arguments[0]);

  if (
    !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
    codePoint < 0 || // not a valid Unicode code point
    codePoint > 0x10FFFF || // not a valid Unicode code point
    Math.floor(codePoint) != codePoint // not an integer
  ) {
    throw RangeError('Invalid code point: ' + codePoint);
  }

  if (codePoint <= 0xFFFF) {
    // BMP code point
    return String.fromCharCode(codePoint);
  } else {
    // Astral code point; split in surrogate halves
    // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
    codePoint -= 0x10000;
    var highSurrogate = (codePoint >> 10) + 0xD800;
    var lowSurrogate = (codePoint % 0x400) + 0xDC00;
    return String.fromCharCode(highSurrogate, lowSurrogate);
  }
}

/*--------------------------------------------------------------------------*/

// Ensures that nodes have the correct types.
var assertTypeRegexMap = {};
function assertType(type, expected) {
  if (expected.indexOf('|') == -1) {
    if (type == expected) {
      return;
    }

    throw Error('Invalid node type: ' + type + '; expected type: ' + expected);
  }

  expected = hasOwnProperty.call(assertTypeRegexMap, expected)
    ? assertTypeRegexMap[expected]
    : (assertTypeRegexMap[expected] = RegExp('^(?:' + expected + ')$'));

  if (expected.test(type)) {
    return;
  }

  throw Error('Invalid node type: ' + type + '; expected types: ' + expected);
}

/*--------------------------------------------------------------------------*/

// Generates a regular expression string based on an AST.
function generate(node) {
  var type = node.type;

  if (hasOwnProperty.call(generators, type)) {
    return generators[type](node);
  }

  throw Error('Invalid node type: ' + type);
}

/*--------------------------------------------------------------------------*/

function generateAlternative(node) {
  assertType(node.type, 'alternative');

  var terms = node.body,
      i = -1,
      length = terms.length,
      result = '';

  while (++i < length) {
    result += generateTerm(terms[i]);
  }

  return result;
}

function generateAnchor(node) {
  assertType(node.type, 'anchor');

  switch (node.kind) {
    case 'start':
      return '^';
    case 'end':
      return '$';
    case 'boundary':
      return '\\b';
    case 'not-boundary':
      return '\\B';
    default:
      throw Error('Invalid assertion');
  }
}

function generateAtom(node) {
  assertType(node.type, 'anchor|characterClass|characterClassEscape|dot|group|reference|value');

  return generate(node);
}

function generateCharacterClass(node) {
  assertType(node.type, 'characterClass');

  var classRanges = node.body,
      i = -1,
      length = classRanges.length,
      result = '';

  if (node.negative) {
    result += '^';
  }

  while (++i < length) {
    result += generateClassAtom(classRanges[i]);
  }

  return '[' + result + ']';
}

function generateCharacterClassEscape(node) {
  assertType(node.type, 'characterClassEscape');

  return '\\' + node.value;
}

function generateUnicodePropertyEscape(node) {
  assertType(node.type, 'unicodePropertyEscape');

  return '\\' + (node.negative ? 'P' : 'p') + '{' + node.value + '}';
}

function generateCharacterClassRange(node) {
  assertType(node.type, 'characterClassRange');

  var min = node.min,
      max = node.max;

  if (min.type == 'characterClassRange' || max.type == 'characterClassRange') {
    throw Error('Invalid character class range');
  }

  return generateClassAtom(min) + '-' + generateClassAtom(max);
}

function generateClassAtom(node) {
  assertType(node.type, 'anchor|characterClassEscape|characterClassRange|dot|value');

  return generate(node);
}

function generateDisjunction(node) {
  assertType(node.type, 'disjunction');

  var body = node.body,
      i = -1,
      length = body.length,
      result = '';

  while (++i < length) {
    if (i != 0) {
      result += '|';
    }
    result += generate(body[i]);
  }

  return result;
}

function generateDot(node) {
  assertType(node.type, 'dot');

  return '.';
}

function generateGroup(node) {
  assertType(node.type, 'group');

  var result = '';

  switch (node.behavior) {
    case 'normal':
      if (node.name) {
        result += '?<' + generateIdentifier(node.name) + '>';
      }
      break;
    case 'ignore':
      result += '?:';
      break;
    case 'lookahead':
      result += '?=';
      break;
    case 'negativeLookahead':
      result += '?!';
      break;
    case 'lookbehind':
      result += '?<=';
      break;
    case 'negativeLookbehind':
      result += '?<!';
      break;
    default:
      throw Error('Invalid behaviour: ' + node.behaviour);
  }

  var body = node.body,
      i = -1,
      length = body.length;

  while (++i < length) {
    result += generate(body[i]);
  }

  return '(' + result + ')';
}

function generateIdentifier(node) {
  assertType(node.type, 'identifier');

  return node.value;
}

function generateQuantifier(node) {
  assertType(node.type, 'quantifier');

  var quantifier = '',
      min = node.min,
      max = node.max;

  if (max == null) {
    if (min == 0) {
      quantifier = '*';
    } else if (min == 1) {
      quantifier = '+';
    } else {
      quantifier = '{' + min + ',}';
    }
  } else if (min == max) {
    quantifier = '{' + min + '}';
  } else if (min == 0 && max == 1) {
    quantifier = '?';
  } else {
    quantifier = '{' + min + ',' + max + '}';
  }

  if (!node.greedy) {
    quantifier += '?';
  }

  return generateAtom(node.body[0]) + quantifier;
}

function generateReference(node) {
  assertType(node.type, 'reference');

  if (node.matchIndex) {
    return '\\' + node.matchIndex;
  }
  if (node.name) {
    return '\\k<' + generateIdentifier(node.name) + '>';
  }

  throw new Error('Unknown reference type');
}

function generateTerm(node) {
  assertType(node.type, 'anchor|characterClass|characterClassEscape|empty|group|quantifier|reference|unicodePropertyEscape|value');

  return generate(node);
}

function generateValue(node) {
  assertType(node.type, 'value');

  var kind = node.kind,
      codePoint = node.codePoint;

  if (typeof codePoint != 'number') {
    throw new Error('Invalid code point: ' + codePoint);
  }

  switch (kind) {
    case 'controlLetter':
      return '\\c' + fromCodePoint(codePoint + 64);
    case 'hexadecimalEscape':
      return '\\x' + ('00' + codePoint.toString(16).toUpperCase()).slice(-2);
    case 'identifier':
      return '\\' + fromCodePoint(codePoint);
    case 'null':
      return '\\' + codePoint;
    case 'octal':
      return '\\' + codePoint.toString(8);
    case 'singleEscape':
      switch (codePoint) {
        case 0x0008:
          return '\\b';
        case 0x0009:
          return '\\t';
        case 0x000A:
          return '\\n';
        case 0x000B:
          return '\\v';
        case 0x000C:
          return '\\f';
        case 0x000D:
          return '\\r';
        default:
          throw Error('Invalid code point: ' + codePoint);
      }
    case 'symbol':
      return fromCodePoint(codePoint);
    case 'unicodeEscape':
      return '\\u' + ('0000' + codePoint.toString(16).toUpperCase()).slice(-4);
    case 'unicodeCodePointEscape':
      return '\\u{' + codePoint.toString(16).toUpperCase() + '}';
    default:
      throw Error('Unsupported node kind: ' + kind);
  }
}

/*--------------------------------------------------------------------------*/

// Used to generate strings for each node type.
var generators = {
  'alternative': generateAlternative,
  'anchor': generateAnchor,
  'characterClass': generateCharacterClass,
  'characterClassEscape': generateCharacterClassEscape,
  'characterClassRange': generateCharacterClassRange,
  'unicodePropertyEscape': generateUnicodePropertyEscape,
  'disjunction': generateDisjunction,
  'dot': generateDot,
  'group': generateGroup,
  'quantifier': generateQuantifier,
  'reference': generateReference,
  'value': generateValue
};

/*--------------------------------------------------------------------------*/

// Export regjsgen.
var regjsgen = {
  'generate': generate
};

// Some AMD build optimizers, like r.js, check for condition patterns like the following:
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
  // Define as an anonymous module so it can be aliased through path mapping.
  define(function() {
    return regjsgen;
  });

  root.regjsgen = regjsgen;
}
// Check for `exports` after `define` in case a build optimizer adds an `exports` object.
else if (freeExports && hasFreeModule) {
  // Export for CommonJS support.
  freeExports.generate = generate;
}
else {
  // Export to the global object.
  root.regjsgen = regjsgen;
}

}.call(this));