text: 'use strict';
  
  
  module.exports = {
    copy: copy,
    checkDataType: checkDataType,
    checkDataTypes: checkDataTypes,
    coerceToTypes: coerceToTypes,
    toHash: toHash,
    getProperty: getProperty,
    escapeQuotes: escapeQuotes,
    equal: require('fast-deep-equal'),
    ucs2length: require('./ucs2length'),
    varOccurences: varOccurences,
    varReplace: varReplace,
    cleanUpCode: cleanUpCode,
    finalCleanUpCode: finalCleanUpCode,
    schemaHasRules: schemaHasRules,
    schemaHasRulesExcept: schemaHasRulesExcept,
    schemaUnknownRules: schemaUnknownRules,
    toQuotedString: toQuotedString,
    getPathExpr: getPathExpr,
    getPath: getPath,
    getData: getData,
    unescapeFragment: unescapeFragment,
    unescapeJsonPointer: unescapeJsonPointer,
    escapeFragment: escapeFragment,
    escapeJsonPointer: escapeJsonPointer
  };
  
  
  function copy(o, to) {
    to = to || {};
    for (var key in o) to[key] = o[key];
    return to;
  }
  
  
  function checkDataType(dataType, data, negate) {
    var EQUAL = negate ? ' !== ' : ' === '
      , AND = negate ? ' || ' : ' && '
      , OK = negate ? '!' : ''
      , NOT = negate ? '' : '!';
    switch (dataType) {
      case 'null': return data + EQUAL + 'null';
      case 'array': return OK + 'Array.isArray(' + data + ')';
      case 'object': return '(' + OK + data + AND +
                            'typeof ' + data + EQUAL + '"object"' + AND +
                            NOT + 'Array.isArray(' + data + '))';
      case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND +
                             NOT + '(' + data + ' % 1)' +
                             AND + data + EQUAL + data + ')';
      default: return 'typeof ' + data + EQUAL + '"' + dataType + '"';
    }
  }
  
  
  function checkDataTypes(dataTypes, data) {
    switch (dataTypes.length) {
      case 1: return checkDataType(dataTypes[0], data, true);
      default:
        var code = '';
        var types = toHash(dataTypes);
        if (types.array && types.object) {
          code = types.null ? '(': '(!' + data + ' || ';
          code += 'typeof ' + data + ' !== "object")';
          delete types.null;
          delete types.array;
          delete types.object;
        }
        if (types.number) delete types.integer;
        for (var t in types)
          code += (code ? ' && ' : '' ) + checkDataType(t, data, true);
  
        return code;
    }
  }
  
  
  var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]);
  function coerceToTypes(optionCoerceTypes, dataTypes) {
    if (Array.isArray(dataTypes)) {
      var types = [];
      for (var i=0; i<dataTypes.length; i++) {
        var t = dataTypes[i];
        if (COERCE_TO_TYPES[t]) types[types.length] = t;
        else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t;
      }
      if (types.length) return types;
    } else if (COERCE_TO_TYPES[dataTypes]) {
      return [dataTypes];
    } else if (optionCoerceTypes === 'array' && dataTypes === 'array') {
      return ['array'];
    }
  }
  
  
  function toHash(arr) {
    var hash = {};
    for (var i=0; i<arr.length; i++) hash[arr[i]] = true;
    return hash;
  }
  
  
  var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
  var SINGLE_QUOTE = /'|\\/g;
  function getProperty(key) {
    return typeof key == 'number'
            ? '[' + key + ']'
            : IDENTIFIER.test(key)
              ? '.' + key
              : "['" + escapeQuotes(key) + "']";
  }
  
  
  function escapeQuotes(str) {
    return str.replace(SINGLE_QUOTE, '\\$&')
              .replace(/\n/g, '\\n')
              .replace(/\r/g, '\\r')
              .replace(/\f/g, '\\f')
              .replace(/\t/g, '\\t');
  }
  
  
  function varOccurences(str, dataVar) {
    dataVar += '[^0-9]';
    var matches = str.match(new RegExp(dataVar, 'g'));
    return matches ? matches.length : 0;
  }
  
  
  function varReplace(str, dataVar, expr) {
    dataVar += '([^0-9])';
    expr = expr.replace(/\$/g, '$$$$');
    return str.replace(new RegExp(dataVar, 'g'), expr + '$1');
  }
  
  
  var EMPTY_ELSE = /else\s*{\s*}/g
    , EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g
    , EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g;
  function cleanUpCode(out) {
    return out.replace(EMPTY_ELSE, '')
              .replace(EMPTY_IF_NO_ELSE, '')
              .replace(EMPTY_IF_WITH_ELSE, 'if (!($1))');
  }
  
  
  var ERRORS_REGEXP = /[^v.]errors/g
    , REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g
    , REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g
    , RETURN_VALID = 'return errors === 0;'
    , RETURN_TRUE = 'validate.errors = null; return true;'
    , RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/
    , RETURN_DATA_ASYNC = 'return data;'
    , ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g
    , REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/;
  
  function finalCleanUpCode(out, async) {
    var matches = out.match(ERRORS_REGEXP);
    if (matches && matches.length == 2) {
      out = async
            ? out.replace(REMOVE_ERRORS_ASYNC, '')
                 .replace(RETURN_ASYNC, RETURN_DATA_ASYNC)
            : out.replace(REMOVE_ERRORS, '')
                 .replace(RETURN_VALID, RETURN_TRUE);
    }
  
    matches = out.match(ROOTDATA_REGEXP);
    if (!matches || matches.length !== 3) return out;
    return out.replace(REMOVE_ROOTDATA, '');
  }
  
  
  function schemaHasRules(schema, rules) {
    if (typeof schema == 'boolean') return !schema;
    for (var key in schema) if (rules[key]) return true;
  }
  
  
  function schemaHasRulesExcept(schema, rules, exceptKeyword) {
    if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not';
    for (var key in schema) if (key != exceptKeyword && rules[key]) return true;
  }
  
  
  function schemaUnknownRules(schema, rules) {
    if (typeof schema == 'boolean') return;
    for (var key in schema) if (!rules[key]) return key;
  }
  
  
  function toQuotedString(str) {
    return '\'' + escapeQuotes(str) + '\'';
  }
  
  
  function getPathExpr(currentPath, expr, jsonPointers, isNumber) {
    var path = jsonPointers // false by default
                ? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')')
                : (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\'');
    return joinPaths(currentPath, path);
  }
  
  
  function getPath(currentPath, prop, jsonPointers) {
    var path = jsonPointers // false by default
                ? toQuotedString('/' + escapeJsonPointer(prop))
                : toQuotedString(getProperty(prop));
    return joinPaths(currentPath, path);
  }
  
  
  var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
  var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
  function getData($data, lvl, paths) {
    var up, jsonPointer, data, matches;
    if ($data === '') return 'rootData';
    if ($data[0] == '/') {
      if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data);
      jsonPointer = $data;
      data = 'rootData';
    } else {
      matches = $data.match(RELATIVE_JSON_POINTER);
      if (!matches) throw new Error('Invalid JSON-pointer: ' + $data);
      up = +matches[1];
      jsonPointer = matches[2];
      if (jsonPointer == '#') {
        if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl);
        return paths[lvl - up];
      }
  
      if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl);
      data = 'data' + ((lvl - up) || '');
      if (!jsonPointer) return data;
    }
  
    var expr = data;
    var segments = jsonPointer.split('/');
    for (var i=0; i<segments.length; i++) {
      var segment = segments[i];
      if (segment) {
        data += getProperty(unescapeJsonPointer(segment));
        expr += ' && ' + data;
      }
    }
    return expr;
  }
  
  
  function joinPaths (a, b) {
    if (a == '""') return b;
    return (a + ' + ' + b).replace(/' \+ '/g, '');
  }
  
  
  function unescapeFragment(str) {
    return unescapeJsonPointer(decodeURIComponent(str));
  }
  
  
  function escapeFragment(str) {
    return encodeURIComponent(escapeJsonPointer(str));
  }
  
  
  function escapeJsonPointer(str) {
    return str.replace(/~/g, '~0').replace(/\//g, '~1');
  }
  
  
  function unescapeJsonPointer(str) {
    return str.replace(/~1/g, '/').replace(/~0/g, '~');
  }
  