regenerate
Version:
Generate JavaScript-compatible regular expressions based on a given set of Unicode symbols or code points.
1,269 lines (1,179 loc) • 36.9 kB
JavaScript
/*! https://mths.be/regenerate v1.4.2 by @mathias | MIT license */
;(function(root) {
// Detect free variables `exports`.
var freeExports = typeof exports == 'object' && exports;
// Detect free variable `module`.
var freeModule = typeof module == 'object' && module &&
module.exports == freeExports && module;
// Detect free variable `global`, from Node.js/io.js or Browserified code,
// and use it as `root`.
var freeGlobal = typeof global == 'object' && global;
if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
root = freeGlobal;
}
/*--------------------------------------------------------------------------*/
var ERRORS = {
'rangeOrder': 'A range\u2019s `stop` value must be greater than or equal ' +
'to the `start` value.',
'codePointRange': 'Invalid code point value. Code points range from ' +
'U+000000 to U+10FFFF.'
};
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs
var HIGH_SURROGATE_MIN = 0xD800;
var HIGH_SURROGATE_MAX = 0xDBFF;
var LOW_SURROGATE_MIN = 0xDC00;
var LOW_SURROGATE_MAX = 0xDFFF;
// In Regenerate output, `\0` is never preceded by `\` because we sort by
// code point value, so let’s keep this regular expression simple.
var regexNull = /\\x00([^0123456789]|$)/g;
var object = {};
var hasOwnProperty = object.hasOwnProperty;
var extend = function(destination, source) {
var key;
for (key in source) {
if (hasOwnProperty.call(source, key)) {
destination[key] = source[key];
}
}
return destination;
};
var forEach = function(array, callback) {
var index = -1;
var length = array.length;
while (++index < length) {
callback(array[index], index);
}
};
var toString = object.toString;
var isArray = function(value) {
return toString.call(value) == '[object Array]';
};
var isNumber = function(value) {
return typeof value == 'number' ||
toString.call(value) == '[object Number]';
};
// `hexBytes[n]` is the uppercase, zero-padded, two-digit hexadecimal
// representation of `n`, for `0 <= n <= 0xFF`.
var hexBytes = [];
(function() {
var digits = '0123456789ABCDEF';
var index = -1;
while (++index <= 0xFF) {
hexBytes[index] = digits.charAt(index >> 4) + digits.charAt(index & 0xF);
}
}());
// These assume that `number` is an integer in the range the output can
// represent, which is the case for the BMP code points they're used with.
var hex2 = function(number) {
return hexBytes[number];
};
var hex4 = function(number) {
return hexBytes[number >> 8] + hexBytes[number & 0xFF];
};
var slice = [].slice;
/*--------------------------------------------------------------------------*/
var compareNumbers = function(a, b) {
return a - b;
};
// Turns an array of code points into data. The code points may be in any
// order and may contain duplicates. Note: unsorted `codePoints` are sorted
// in place.
var dataFromCodePoints = function(codePoints) {
var length = codePoints.length;
var index = 0;
var start;
var end;
var codePoint;
var result = [];
// Turn runs of consecutive code points into `(start, end)` pairs, skipping
// duplicates. Input is often sorted already, so rather than sorting up
// front, sort and start over only once a code point turns out to be
// smaller than the one before it.
while (index < length) {
// The inner loop only stops at a code point above `end`, so `start` is
// always greater than result[result.length - 1].
start = codePoints[index];
end = start + 1;
while (++index < length && (codePoint = codePoints[index]) <= end) {
if (codePoint == end) {
++end;
} else if (codePoint < end - 1) {
codePoints.sort(compareNumbers);
return dataFromCodePoints(codePoints);
}
}
result.push(start, end);
}
return result;
};
var dataRemove = function(data, codePoint) {
// Iterate over the data per `(start, end)` pair.
var index = 0;
var start;
var end;
var length = data.length;
while (index < length) {
start = data[index];
end = data[index + 1];
if (codePoint >= start && codePoint < end) {
// Modify this pair.
if (codePoint == start) {
if (end == start + 1) {
// Just remove `start` and `end`.
data.splice(index, 2);
return data;
} else {
// Just replace `start` with a new value.
data[index] = codePoint + 1;
return data;
}
} else if (codePoint == end - 1) {
// Just replace `end` with a new value.
data[index + 1] = codePoint;
return data;
} else {
// Replace `[start, end]` with `[startA, endA, startB, endB]`.
data.splice(index, 2, start, codePoint, codePoint + 1, end);
return data;
}
}
index += 2;
}
return data;
};
var dataRemoveRange = function(data, rangeStart, rangeEnd) {
if (rangeEnd < rangeStart) {
throw Error(ERRORS.rangeOrder);
}
var length = data.length;
var rangeEndExclusive = rangeEnd + 1;
var index = 0;
var first;
var start;
var end;
// Skip the pairs that end before the range starts.
while (index < length && data[index + 1] <= rangeStart) {
index += 2;
}
// Find the pairs that overlap the range, i.e. `data[first]` up to (but not
// including) `data[index]`.
first = index;
while (index < length && data[index] < rangeEndExclusive) {
index += 2;
}
if (first == index) {
return data;
}
// Replace the overlapping pairs with a single `splice`, keeping the parts of
// the first and last pair that lie outside the range, if any.
// E.g. we have `[0, 11, 14, 31]` and want to remove 4-20 -> `[0, 4, 21, 31]`.
start = data[first];
end = data[index - 1];
if (start < rangeStart) {
if (end > rangeEndExclusive) {
data.splice(
first, index - first, start, rangeStart, rangeEndExclusive, end
);
} else {
data.splice(first, index - first, start, rangeStart);
}
} else if (end > rangeEndExclusive) {
data.splice(first, index - first, rangeEndExclusive, end);
} else {
data.splice(first, index - first);
}
return data;
};
var dataAdd = function(data, codePoint) {
// Iterate over the data per `(start, end)` pair.
var index = 0;
var start;
var end;
var lastIndex = null;
var length = data.length;
if (codePoint < 0x0 || codePoint > 0x10FFFF) {
throw RangeError(ERRORS.codePointRange);
}
// Fast path: the code point lies at or after the start of the last pair,
// which is the common case when adding code points in ascending order.
if (length && codePoint >= data[length - 2]) {
end = data[length - 1];
if (codePoint > end) {
data.push(codePoint, codePoint + 1);
} else if (codePoint == end) {
data[length - 1] = codePoint + 1;
}
return data;
}
while (index < length) {
start = data[index];
end = data[index + 1];
// Check if the code point is already in the set.
if (codePoint >= start && codePoint < end) {
return data;
}
if (codePoint == start - 1) {
// Just replace `start` with a new value.
data[index] = codePoint;
return data;
}
// At this point, if `start` is `greater` than `codePoint`, insert a new
// `[start, end]` pair before the current pair, or after the current pair
// if there is a known `lastIndex`.
if (start > codePoint) {
data.splice(
lastIndex != null ? lastIndex + 2 : 0,
0,
codePoint,
codePoint + 1
);
return data;
}
if (codePoint == end) {
// Check if adding this code point causes two separate ranges to become
// a single range, e.g. `dataAdd([0, 4, 5, 10], 4)` → `[0, 10]`.
if (codePoint + 1 == data[index + 2]) {
data.splice(index, 4, start, data[index + 3]);
return data;
}
// Else, just replace `end` with a new value.
data[index + 1] = codePoint + 1;
return data;
}
lastIndex = index;
index += 2;
}
// The loop has finished; add the new pair to the end of the data set.
data.push(codePoint, codePoint + 1);
return data;
};
var dataAddRange = function(data, rangeStart, rangeEnd) {
if (rangeEnd < rangeStart) {
throw Error(ERRORS.rangeOrder);
}
if (
rangeStart < 0x0 || rangeStart > 0x10FFFF ||
rangeEnd < 0x0 || rangeEnd > 0x10FFFF
) {
throw RangeError(ERRORS.codePointRange);
}
var length = data.length;
var rangeEndExclusive = rangeEnd + 1;
var index = 0;
var first;
var start;
var end;
// Fast path: the range starts at or after the start of the last pair, which
// is the common case when adding ranges in ascending order.
if (length && rangeStart >= data[length - 2]) {
end = data[length - 1];
if (rangeStart > end) {
data.push(rangeStart, rangeEndExclusive);
} else if (rangeEndExclusive > end) {
// The range overlaps or touches the last pair; extend it.
data[length - 1] = rangeEndExclusive;
}
return data;
}
// Skip the pairs that end before the range starts, without touching it.
while (index < length && data[index + 1] < rangeStart) {
index += 2;
}
// Find the pairs that overlap or touch the range, i.e. `data[first]` up to
// (but not including) `data[index]`.
first = index;
while (index < length && data[index] <= rangeEndExclusive) {
index += 2;
}
if (first == index) {
// Nothing to merge with; insert a new pair.
data.splice(first, 0, rangeStart, rangeEndExclusive);
return data;
}
// Merge the range and the pairs it overlaps or touches into a single pair.
// E.g. `[0, 11, 12, 14, 20, 31]` and you add 5-15 -> `[0, 16, 20, 31]`.
start = data[first] < rangeStart ? data[first] : rangeStart;
end = data[index - 1] > rangeEndExclusive ?
data[index - 1] :
rangeEndExclusive;
if (index - first == 2) {
data[first] = start;
data[first + 1] = end;
} else {
data.splice(first, index - first, start, end);
}
return data;
};
var dataContains = function(data, codePoint) {
// Binary search for the number of `(start, end)` pairs that start at or
// before `codePoint`. `codePoint` is in the set if it lies before the end of
// the last of those pairs.
var length = data.length;
var low = 0;
var high = length >> 1;
var middle;
// Exit early if `codePoint` is not within `data`'s overall range.
if (!length || codePoint < data[0] || codePoint >= data[length - 1]) {
return false;
}
while (low < high) {
middle = (low + high) >> 1;
if (data[middle << 1] <= codePoint) {
low = middle + 1;
} else {
high = middle;
}
}
return low > 0 && codePoint < data[(low << 1) - 1];
};
var dataIntersectionData = function(dataA, dataB) {
var indexA = 0;
var indexB = 0;
var lengthA = dataA.length;
var lengthB = dataB.length;
var startA, endA, startB, endB, start, end;
var result = [];
while (indexA < lengthA && indexB < lengthB) {
startA = dataA[indexA];
endA = dataA[indexA + 1];
startB = dataB[indexB];
endB = dataB[indexB + 1];
start = startA > startB ? startA : startB;
end = endA < endB ? endA : endB;
if (start < end) {
result.push(start, end);
}
if (endA < endB) {
indexA += 2;
} else {
indexB += 2;
}
}
return result;
};
var dataUnion = function(dataA, dataB) {
// Walk both sorted lists of `(start, end)` pairs in lockstep, always taking
// the pair that starts first, and merge it into the previous output pair if
// the two overlap or touch.
var indexA = 0;
var indexB = 0;
var lengthA = dataA.length;
var lengthB = dataB.length;
var start;
var end;
var last;
var result = [];
while (indexA < lengthA || indexB < lengthB) {
if (
indexB >= lengthB ||
(indexA < lengthA && dataA[indexA] <= dataB[indexB])
) {
start = dataA[indexA];
end = dataA[indexA + 1];
indexA += 2;
} else {
start = dataB[indexB];
end = dataB[indexB + 1];
indexB += 2;
}
last = result.length - 1;
if (last > 0 && start <= result[last]) {
if (end > result[last]) {
result[last] = end;
}
} else {
result.push(start, end);
}
}
return result;
};
var dataDifference = function(dataA, dataB) {
// For each pair in `dataA`, cut out the overlapping pairs of `dataB`.
var indexA = 0;
var indexB = 0;
var lengthA = dataA.length;
var lengthB = dataB.length;
var index;
var start;
var end;
var result = [];
while (indexA < lengthA) {
start = dataA[indexA];
end = dataA[indexA + 1];
indexA += 2;
// Skip the pairs of `dataB` that end before this pair starts.
while (indexB < lengthB && dataB[indexB + 1] <= start) {
indexB += 2;
}
index = indexB;
while (start < end && index < lengthB && dataB[index] < end) {
if (dataB[index] > start) {
result.push(start, dataB[index]);
}
if (dataB[index + 1] > start) {
start = dataB[index + 1];
}
index += 2;
}
if (start < end) {
result.push(start, end);
}
}
return result;
};
// Collects the code points in `values` (anything `add` or `remove` accepts in
// an array: code points, symbols, nested arrays, and Regenerate instances)
// and returns them as data. With `validate`, out-of-range code points throw
// like `dataAdd` does; otherwise they’re skipped, as `dataRemove` does.
var dataFromValues = function(values, validate) {
var codePoints = [];
var sets = [];
var collect = function(values) {
var index = -1;
var length = values.length;
var value;
var codePoint;
while (++index < length) {
value = values[index];
// Check for primitive numbers first, as they’re the common case.
if (typeof value == 'number') {
codePoint = value;
} else if (value == null) {
continue;
} else if (value instanceof regenerate) {
sets.push(value.data);
continue;
} else if (isArray(value)) {
collect(value);
continue;
} else {
codePoint = isNumber(value) ? +value : symbolToCodePoint(value);
}
if (codePoint >= 0x0 && codePoint <= 0x10FFFF) {
codePoints.push(codePoint);
} else if (validate) {
throw RangeError(ERRORS.codePointRange);
}
}
};
collect(values);
var data = dataFromCodePoints(codePoints);
var index = -1;
var length = sets.length;
while (++index < length) {
data = dataUnion(data, sets[index]);
}
return data;
};
var dataIsEmpty = function(data) {
return !data.length;
};
var dataIsSingleton = function(data) {
// Check if the set only represents a single code point.
return data.length == 2 && data[0] + 1 == data[1];
};
var dataToArray = function(data) {
// Iterate over the data per `(start, end)` pair.
var index = 0;
var start;
var end;
var result = [];
var length = data.length;
while (index < length) {
start = data[index];
end = data[index + 1];
while (start < end) {
result.push(start);
++start;
}
index += 2;
}
return result;
};
/*--------------------------------------------------------------------------*/
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
var floor = Math.floor;
var highSurrogate = function(codePoint) {
return parseInt(
floor((codePoint - 0x10000) / 0x400) + HIGH_SURROGATE_MIN,
10
);
};
var lowSurrogate = function(codePoint) {
return parseInt(
(codePoint - 0x10000) % 0x400 + LOW_SURROGATE_MIN,
10
);
};
var stringFromCharCode = String.fromCharCode;
var codePointToString = function(codePoint) {
var string;
// https://mathiasbynens.be/notes/javascript-escapes#single
// Note: the `\b` escape sequence for U+0008 BACKSPACE in strings has a
// different meaning in regular expressions (word boundary), so it cannot
// be used here.
if (codePoint == 0x09) {
string = '\\t';
}
// Note: IE < 9 treats `'\v'` as `'v'`, so avoid using it.
// else if (codePoint == 0x0B) {
// string = '\\v';
// }
else if (codePoint == 0x0A) {
string = '\\n';
}
else if (codePoint == 0x0C) {
string = '\\f';
}
else if (codePoint == 0x0D) {
string = '\\r';
}
else if (codePoint == 0x2D) {
// https://mathiasbynens.be/notes/javascript-escapes#hexadecimal
// Note: `-` (U+002D HYPHEN-MINUS) is escaped in this way rather
// than by backslash-escaping, in case the output is used outside
// of a character class in a `u` RegExp. /\-/u throws, but
// /\x2D/u is fine.
string = '\\x2D';
}
else if (codePoint == 0x5C) {
string = '\\\\';
}
else if (
codePoint == 0x24 ||
(codePoint >= 0x28 && codePoint <= 0x2B) ||
codePoint == 0x2E || codePoint == 0x2F ||
codePoint == 0x3F ||
(codePoint >= 0x5B && codePoint <= 0x5E) ||
(codePoint >= 0x7B && codePoint <= 0x7D)
) {
// The code point maps to an unsafe printable ASCII character;
// backslash-escape it. Here’s the list of those symbols:
//
// $()*+./?[\]^{|}
//
// This matches SyntaxCharacters as well as `/` (U+002F SOLIDUS).
// https://tc39.github.io/ecma262/#prod-SyntaxCharacter
string = '\\' + stringFromCharCode(codePoint);
}
else if (codePoint >= 0x20 && codePoint <= 0x7E) {
// The code point maps to one of these printable ASCII symbols
// (including the space character):
//
// !"#%&',0123456789:;<=>@ABCDEFGHIJKLMNO
// PQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz~
//
// These can safely be used directly.
string = stringFromCharCode(codePoint);
}
else if (codePoint <= 0xFF) {
string = '\\x' + hex2(codePoint);
}
else { // `codePoint <= 0xFFFF` holds true.
// https://mathiasbynens.be/notes/javascript-escapes#unicode
string = '\\u' + hex4(codePoint);
}
// There’s no need to account for astral symbols / surrogate pairs here,
// since `codePointToString` is private and only used for BMP code points.
// But if that’s what you need, just add an `else` block with this code:
//
// string = '\\u' + hex4(highSurrogate(codePoint))
// + '\\u' + hex4(lowSurrogate(codePoint));
return string;
};
var codePointToStringUnicode = function(codePoint) {
if (codePoint <= 0xFFFF) {
if (codePoint < HIGH_SURROGATE_MIN || codePoint > LOW_SURROGATE_MAX) {
return codePointToString(codePoint);
}
// With the `u` flag, `\uD834\uDF06` is read as the single code point
// U+1D306, so a high surrogate followed by a low surrogate would no
// longer match either of them. `\u{…}` escapes are never combined.
return '\\u{' + hex4(codePoint) + '}';
}
// Astral code points have five or six hexadecimal digits: their highest
// byte is 0x01 to 0x10, written without its leading zero, if any.
var high = codePoint >> 16;
return '\\u{' +
(high < 0x10 ? hexBytes[high].charAt(1) : '10') +
hexBytes[(codePoint >> 8) & 0xFF] +
hexBytes[codePoint & 0xFF] +
'}';
};
var symbolToCodePoint = function(symbol) {
var length = symbol.length;
var first = symbol.charCodeAt(0);
var second;
if (
first >= HIGH_SURROGATE_MIN && first <= HIGH_SURROGATE_MAX &&
length > 1 // There is a next code unit.
) {
// `first` is a high surrogate, and there is a next character. Assume
// it’s a low surrogate (else it’s invalid usage of Regenerate anyway).
second = symbol.charCodeAt(1);
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
return (first - HIGH_SURROGATE_MIN) * 0x400 +
second - LOW_SURROGATE_MIN + 0x10000;
}
return first;
};
// Turns a code point or a symbol into a primitive code point, so that
// `Number` objects don’t end up in the data. Primitive numbers are returned
// as is, which keeps them fast to work with.
var toCodePoint = function(value) {
return typeof value == 'number' ? value :
isNumber(value) ? +value : symbolToCodePoint(value);
};
var createBMPCharacterClasses = function(data) {
// Iterate over the data per `(start, end)` pair.
var result = '';
var index = 0;
var start;
var end;
var length = data.length;
if (dataIsSingleton(data)) {
return codePointToString(data[0]);
}
while (index < length) {
start = data[index];
end = data[index + 1] - 1; // Note: the `- 1` makes `end` inclusive.
if (start == end) {
result += codePointToString(start);
} else if (start + 1 == end) {
result += codePointToString(start) + codePointToString(end);
} else {
result += codePointToString(start) + '-' + codePointToString(end);
}
index += 2;
}
return '[' + result + ']';
};
var createUnicodeCharacterClasses = function(data) {
// Iterate over the data per `(start, end)` pair.
var result = '';
var index = 0;
var start;
var end;
var length = data.length;
if (dataIsSingleton(data)) {
return codePointToStringUnicode(data[0]);
}
while (index < length) {
start = data[index];
end = data[index + 1] - 1; // Note: the `- 1` makes `end` inclusive.
if (start == end) {
result += codePointToStringUnicode(start);
} else if (start + 1 == end) {
result += codePointToStringUnicode(start) + codePointToStringUnicode(end);
} else {
result += codePointToStringUnicode(start) + '-' + codePointToStringUnicode(end);
}
index += 2;
}
return '[' + result + ']';
};
var splitAtBMP = function(data) {
// Iterate over the data per `(start, end)` pair.
var loneHighSurrogates = [];
var loneLowSurrogates = [];
var bmp = [];
var astral = [];
var index = 0;
var start;
var end;
var length = data.length;
while (index < length) {
start = data[index];
end = data[index + 1] - 1; // Note: the `- 1` makes `end` inclusive.
if (start < HIGH_SURROGATE_MIN) {
// The range starts and ends before the high surrogate range.
// E.g. (0, 0x10).
if (end < HIGH_SURROGATE_MIN) {
bmp.push(start, end + 1);
}
// The range starts before the high surrogate range and ends within it.
// E.g. (0, 0xD855).
if (end >= HIGH_SURROGATE_MIN && end <= HIGH_SURROGATE_MAX) {
bmp.push(start, HIGH_SURROGATE_MIN);
loneHighSurrogates.push(HIGH_SURROGATE_MIN, end + 1);
}
// The range starts before the high surrogate range and ends in the low
// surrogate range. E.g. (0, 0xDCFF).
if (end >= LOW_SURROGATE_MIN && end <= LOW_SURROGATE_MAX) {
bmp.push(start, HIGH_SURROGATE_MIN);
loneHighSurrogates.push(HIGH_SURROGATE_MIN, HIGH_SURROGATE_MAX + 1);
loneLowSurrogates.push(LOW_SURROGATE_MIN, end + 1);
}
// The range starts before the high surrogate range and ends after the
// low surrogate range. E.g. (0, 0x10FFFF).
if (end > LOW_SURROGATE_MAX) {
bmp.push(start, HIGH_SURROGATE_MIN);
loneHighSurrogates.push(HIGH_SURROGATE_MIN, HIGH_SURROGATE_MAX + 1);
loneLowSurrogates.push(LOW_SURROGATE_MIN, LOW_SURROGATE_MAX + 1);
if (end <= 0xFFFF) {
bmp.push(LOW_SURROGATE_MAX + 1, end + 1);
} else {
bmp.push(LOW_SURROGATE_MAX + 1, 0xFFFF + 1);
astral.push(0xFFFF + 1, end + 1);
}
}
} else if (start >= HIGH_SURROGATE_MIN && start <= HIGH_SURROGATE_MAX) {
// The range starts and ends in the high surrogate range.
// E.g. (0xD855, 0xD866).
if (end >= HIGH_SURROGATE_MIN && end <= HIGH_SURROGATE_MAX) {
loneHighSurrogates.push(start, end + 1);
}
// The range starts in the high surrogate range and ends in the low
// surrogate range. E.g. (0xD855, 0xDCFF).
if (end >= LOW_SURROGATE_MIN && end <= LOW_SURROGATE_MAX) {
loneHighSurrogates.push(start, HIGH_SURROGATE_MAX + 1);
loneLowSurrogates.push(LOW_SURROGATE_MIN, end + 1);
}
// The range starts in the high surrogate range and ends after the low
// surrogate range. E.g. (0xD855, 0x10FFFF).
if (end > LOW_SURROGATE_MAX) {
loneHighSurrogates.push(start, HIGH_SURROGATE_MAX + 1);
loneLowSurrogates.push(LOW_SURROGATE_MIN, LOW_SURROGATE_MAX + 1);
if (end <= 0xFFFF) {
bmp.push(LOW_SURROGATE_MAX + 1, end + 1);
} else {
bmp.push(LOW_SURROGATE_MAX + 1, 0xFFFF + 1);
astral.push(0xFFFF + 1, end + 1);
}
}
} else if (start >= LOW_SURROGATE_MIN && start <= LOW_SURROGATE_MAX) {
// The range starts and ends in the low surrogate range.
// E.g. (0xDCFF, 0xDDFF).
if (end >= LOW_SURROGATE_MIN && end <= LOW_SURROGATE_MAX) {
loneLowSurrogates.push(start, end + 1);
}
// The range starts in the low surrogate range and ends after the low
// surrogate range. E.g. (0xDCFF, 0x10FFFF).
if (end > LOW_SURROGATE_MAX) {
loneLowSurrogates.push(start, LOW_SURROGATE_MAX + 1);
if (end <= 0xFFFF) {
bmp.push(LOW_SURROGATE_MAX + 1, end + 1);
} else {
bmp.push(LOW_SURROGATE_MAX + 1, 0xFFFF + 1);
astral.push(0xFFFF + 1, end + 1);
}
}
} else if (start > LOW_SURROGATE_MAX && start <= 0xFFFF) {
// The range starts and ends after the low surrogate range.
// E.g. (0xFFAA, 0x10FFFF).
if (end <= 0xFFFF) {
bmp.push(start, end + 1);
} else {
bmp.push(start, 0xFFFF + 1);
astral.push(0xFFFF + 1, end + 1);
}
} else {
// The range starts and ends in the astral range.
astral.push(start, end + 1);
}
index += 2;
}
return {
'loneHighSurrogates': loneHighSurrogates,
'loneLowSurrogates': loneLowSurrogates,
'bmp': bmp,
'astral': astral
};
};
var optimizeSurrogateMappings = function(surrogateMappings) {
var result = [];
var tmpLow = [];
var addLow = false;
var mapping;
var nextMapping;
var highSurrogates;
var lowSurrogates;
var nextHighSurrogates;
var nextLowSurrogates;
var index = -1;
var length = surrogateMappings.length;
while (++index < length) {
mapping = surrogateMappings[index];
nextMapping = surrogateMappings[index + 1];
if (!nextMapping) {
result.push(mapping);
continue;
}
highSurrogates = mapping[0];
lowSurrogates = mapping[1];
nextHighSurrogates = nextMapping[0];
nextLowSurrogates = nextMapping[1];
// Check for identical high surrogate ranges.
tmpLow = lowSurrogates;
while (
nextHighSurrogates &&
highSurrogates[0] == nextHighSurrogates[0] &&
highSurrogates[1] == nextHighSurrogates[1]
) {
// Merge with the next item.
if (dataIsSingleton(nextLowSurrogates)) {
tmpLow = dataAdd(tmpLow, nextLowSurrogates[0]);
} else {
tmpLow = dataAddRange(
tmpLow,
nextLowSurrogates[0],
nextLowSurrogates[1] - 1
);
}
++index;
mapping = surrogateMappings[index];
highSurrogates = mapping[0];
lowSurrogates = mapping[1];
nextMapping = surrogateMappings[index + 1];
nextHighSurrogates = nextMapping && nextMapping[0];
nextLowSurrogates = nextMapping && nextMapping[1];
addLow = true;
}
result.push([
highSurrogates,
addLow ? tmpLow : lowSurrogates
]);
addLow = false;
}
return optimizeByLowSurrogates(result);
};
var optimizeByLowSurrogates = function(surrogateMappings) {
if (surrogateMappings.length == 1) {
return surrogateMappings;
}
var index = -1;
var innerIndex = -1;
while (++index < surrogateMappings.length) {
var mapping = surrogateMappings[index];
var lowSurrogates = mapping[1];
if (lowSurrogates.length !== 2) {
// Merge can only be performed when both lowSurrogates.length and
// otherLowSurrogates.length are equal, that is, both are single
// ranges of length 2.
continue;
}
var lowSurrogateStart = lowSurrogates[0];
var lowSurrogateEnd = lowSurrogates[1];
innerIndex = index; // Note: the loop starts at the next index.
while (++innerIndex < surrogateMappings.length) {
var otherMapping = surrogateMappings[innerIndex];
var otherLowSurrogates = otherMapping[1];
var otherLowSurrogateStart = otherLowSurrogates[0];
var otherLowSurrogateEnd = otherLowSurrogates[1];
if (
lowSurrogateStart == otherLowSurrogateStart &&
lowSurrogateEnd == otherLowSurrogateEnd &&
otherLowSurrogates.length === 2
) {
// Add the code points in the other item to this one.
if (dataIsSingleton(otherMapping[0])) {
mapping[0] = dataAdd(mapping[0], otherMapping[0][0]);
} else {
mapping[0] = dataAddRange(
mapping[0],
otherMapping[0][0],
otherMapping[0][1] - 1
);
}
// Remove the other, now redundant, item.
surrogateMappings.splice(innerIndex, 1);
--innerIndex;
}
}
}
return surrogateMappings;
};
var surrogateSet = function(data) {
// Exit early if `data` is an empty set.
if (!data.length) {
return [];
}
// Iterate over the data per `(start, end)` pair.
var index = 0;
var start;
var end;
var startHigh;
var startLow;
var endHigh;
var endLow;
var surrogateMappings = [];
var length = data.length;
while (index < length) {
start = data[index];
end = data[index + 1] - 1;
startHigh = highSurrogate(start);
startLow = lowSurrogate(start);
endHigh = highSurrogate(end);
endLow = lowSurrogate(end);
var startsWithLowestLowSurrogate = startLow == LOW_SURROGATE_MIN;
var endsWithHighestLowSurrogate = endLow == LOW_SURROGATE_MAX;
var complete = false;
// Append the previous high-surrogate-to-low-surrogate mappings.
// Step 1: `(startHigh, startLow)` to `(startHigh, LOW_SURROGATE_MAX)`.
if (
startHigh == endHigh ||
startsWithLowestLowSurrogate && endsWithHighestLowSurrogate
) {
surrogateMappings.push([
[startHigh, endHigh + 1],
[startLow, endLow + 1]
]);
complete = true;
} else {
surrogateMappings.push([
[startHigh, startHigh + 1],
[startLow, LOW_SURROGATE_MAX + 1]
]);
}
// Step 2: `(startHigh + 1, LOW_SURROGATE_MIN)` to
// `(endHigh - 1, LOW_SURROGATE_MAX)`.
if (!complete && startHigh + 1 < endHigh) {
if (endsWithHighestLowSurrogate) {
// Combine step 2 and step 3.
surrogateMappings.push([
[startHigh + 1, endHigh + 1],
[LOW_SURROGATE_MIN, endLow + 1]
]);
complete = true;
} else {
surrogateMappings.push([
[startHigh + 1, endHigh],
[LOW_SURROGATE_MIN, LOW_SURROGATE_MAX + 1]
]);
}
}
// Step 3. `(endHigh, LOW_SURROGATE_MIN)` to `(endHigh, endLow)`.
if (!complete) {
surrogateMappings.push([
[endHigh, endHigh + 1],
[LOW_SURROGATE_MIN, endLow + 1]
]);
}
index += 2;
}
// The format of `surrogateMappings` is as follows:
//
// [ surrogateMapping1, surrogateMapping2 ]
//
// i.e.:
//
// [
// [ highSurrogates1, lowSurrogates1 ],
// [ highSurrogates2, lowSurrogates2 ]
// ]
return optimizeSurrogateMappings(surrogateMappings);
};
var createSurrogateCharacterClasses = function(surrogateMappings) {
var result = [];
forEach(surrogateMappings, function(surrogateMapping) {
var highSurrogates = surrogateMapping[0];
var lowSurrogates = surrogateMapping[1];
result.push(
createBMPCharacterClasses(highSurrogates) +
createBMPCharacterClasses(lowSurrogates)
);
});
return result.join('|');
};
var createCharacterClassesFromData = function(data, bmpOnly, hasUnicodeFlag) {
if (hasUnicodeFlag) {
return createUnicodeCharacterClasses(data);
}
var result = [];
var parts = splitAtBMP(data);
var loneHighSurrogates = parts.loneHighSurrogates;
var loneLowSurrogates = parts.loneLowSurrogates;
var bmp = parts.bmp;
var astral = parts.astral;
var hasLoneHighSurrogates = !dataIsEmpty(loneHighSurrogates);
var hasLoneLowSurrogates = !dataIsEmpty(loneLowSurrogates);
var surrogateMappings = surrogateSet(astral);
if (bmpOnly) {
bmp = dataUnion(bmp, loneHighSurrogates);
hasLoneHighSurrogates = false;
bmp = dataUnion(bmp, loneLowSurrogates);
hasLoneLowSurrogates = false;
}
if (!dataIsEmpty(bmp)) {
// The data set contains BMP code points that are not high surrogates
// needed for astral code points in the set.
result.push(createBMPCharacterClasses(bmp));
}
if (surrogateMappings.length) {
// The data set contains astral code points; append character classes
// based on their surrogate pairs.
result.push(createSurrogateCharacterClasses(surrogateMappings));
}
// https://gist.github.com/mathiasbynens/bbe7f870208abcfec860
if (hasLoneHighSurrogates) {
result.push(
createBMPCharacterClasses(loneHighSurrogates) +
// Make sure the high surrogates aren’t part of a surrogate pair.
'(?![\\uDC00-\\uDFFF])'
);
}
if (hasLoneLowSurrogates) {
result.push(
// It is not possible to accurately assert the low surrogates aren’t
// part of a surrogate pair, since JavaScript regular expressions do
// not support lookbehind.
'(?:[^\\uD800-\\uDBFF]|^)' +
createBMPCharacterClasses(loneLowSurrogates)
);
}
return result.join('|');
};
/*--------------------------------------------------------------------------*/
// `regenerate` can be used as a constructor (and new methods can be added to
// its prototype) but also as a regular function, the latter of which is the
// documented and most common usage. For that reason, it’s not capitalized.
var regenerate = function(value) {
if (arguments.length > 1) {
value = slice.call(arguments);
}
if (this instanceof regenerate) {
this.data = [];
return this.add(value);
}
return (new regenerate).add(value);
};
regenerate.version = '1.4.2';
var proto = regenerate.prototype;
extend(proto, {
'add': function(value) {
var $this = this;
if (arguments.length > 1) {
value = slice.call(arguments);
}
if (value == null) {
return $this;
}
if (value instanceof regenerate) {
// Allow passing other Regenerate instances.
$this.data = dataUnion($this.data, value.data);
return $this;
}
if (isArray(value)) {
$this.data = dataUnion($this.data, dataFromValues(value, true));
return $this;
}
$this.data = dataAdd(
$this.data,
toCodePoint(value)
);
return $this;
},
'remove': function(value) {
var $this = this;
if (arguments.length > 1) {
value = slice.call(arguments);
}
if (value == null) {
return $this;
}
if (value instanceof regenerate) {
// Allow passing other Regenerate instances.
$this.data = dataDifference($this.data, value.data);
return $this;
}
if (isArray(value)) {
$this.data = dataDifference($this.data, dataFromValues(value, false));
return $this;
}
$this.data = dataRemove(
$this.data,
toCodePoint(value)
);
return $this;
},
'addRange': function(start, end) {
var $this = this;
$this.data = dataAddRange($this.data,
toCodePoint(start),
toCodePoint(end)
);
return $this;
},
'removeRange': function(start, end) {
var $this = this;
var startCodePoint = toCodePoint(start);
var endCodePoint = toCodePoint(end);
$this.data = dataRemoveRange(
$this.data,
startCodePoint,
endCodePoint
);
return $this;
},
'intersection': function(argument) {
var $this = this;
if (argument instanceof regenerate) {
// Allow passing other Regenerate instances.
$this.data = dataIntersectionData($this.data, argument.data);
return $this;
}
$this.data = dataIntersectionData(
$this.data,
dataFromValues(argument, false)
);
return $this;
},
'contains': function(codePoint) {
return dataContains(
this.data,
isNumber(codePoint) ? codePoint : symbolToCodePoint(codePoint)
);
},
'clone': function() {
var set = new regenerate;
set.data = this.data.slice(0);
return set;
},
'toString': function(options) {
var result = createCharacterClassesFromData(
this.data,
options ? options.bmpOnly : false,
options ? options.hasUnicodeFlag : false
);
if (!result) {
// For an empty set, return something that can be inserted `/here/` to
// form a valid regular expression. Avoid `(?:)` since that matches the
// empty string.
return '[]';
}
// Use `\0` instead of `\x00` where possible.
return result.replace(regexNull, '\\0$1');
},
'toRegExp': function(flags) {
var pattern = this.toString(
/[uv]/.test(flags || '') ?
{ 'hasUnicodeFlag': true } :
null
);
return RegExp(pattern, flags || '');
},
'valueOf': function() { // Note: `valueOf` is aliased as `toArray`.
return dataToArray(this.data);
}
});
proto.toArray = proto.valueOf;
// Some AMD build optimizers, like r.js, check for specific condition patterns
// like the following:
if (
typeof define == 'function' &&
typeof define.amd == 'object' &&
define.amd
) {
define(function() {
return regenerate;
});
} else if (freeExports && !freeExports.nodeType) {
if (freeModule) { // in Node.js, io.js, or RingoJS v0.8.0+
freeModule.exports = regenerate;
} else { // in Narwhal or RingoJS v0.7.0-
freeExports.regenerate = regenerate;
}
} else { // in Rhino or a web browser
root.regenerate = regenerate;
}
}(this));