/*jslint maxlen:80, browser:true */
/*
* Properties used by searchOf and searchLastOf implementation.
*/
/*property
MAX_SAFE_INTEGER, abs, add, apply, call, configurable, defineProperty,
enumerable, exec, floor, global, hasOwnProperty, ignoreCase, index,
lastIndex, lastIndexOf, length, max, min, multiline, pow, prototype,
remove, replace, searchLastOf, searchOf, source, toString, value, writable
*/
/*
* Properties used in the testing of searchOf and searchLastOf implimentation.
*/
/*property
appendChild, createTextNode, getElementById, indexOf, lastIndexOf, length,
searchLastOf, searchOf, unshift
*/
(function () {
'use strict';
var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1,
getNativeFlags = new RegExp('\\/([a-z]*)$', 'i'),
clipDups = new RegExp('([\\s\\S])(?=[\\s\\S]*\\1)', 'g'),
pToString = Object.prototype.toString,
pHasOwn = Object.prototype.hasOwnProperty,
stringTagRegExp;
/**
* Defines a new property directly on an object, or modifies an existing
* property on an object, and returns the object.
*
* @private
* @function
* @param {Object} object
* @param {string} property
* @param {Object} descriptor
* @returns {Object}
* @see https://goo.gl/CZnEqg
*/
function $defineProperty(object, property, descriptor) {
if (Object.defineProperty) {
Object.defineProperty(object, property, descriptor);
} else {
object[property] = descriptor.value;
}
return object;
}
/**
* Returns true if the operands are strictly equal with no type conversion.
*
* @private
* @function
* @param {*} a
* @param {*} b
* @returns {boolean}
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.4
*/
function $strictEqual(a, b) {
return a === b;
}
/**
* Returns true if the operand inputArg is undefined.
*
* @private
* @function
* @param {*} inputArg
* @returns {boolean}
*/
function $isUndefined(inputArg) {
return $strictEqual(typeof inputArg, 'undefined');
}
/**
* Provides a string representation of the supplied object in the form
* "[object type]", where type is the object type.
*
* @private
* @function
* @param {*} inputArg The object for which a class string represntation
* is required.
* @returns {string} A string value of the form "[object type]".
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.2
*/
function $toStringTag(inputArg) {
var val;
if (inputArg === null) {
val = '[object Null]';
} else if ($isUndefined(inputArg)) {
val = '[object Undefined]';
} else {
val = pToString.call(inputArg);
}
return val;
}
/**
* The string tag representation of a RegExp object.
*
* @private
* @type {string}
*/
stringTagRegExp = $toStringTag(getNativeFlags);
/**
* Returns true if the operand inputArg is a RegExp.
*
* @private
* @function
* @param {*} inputArg
* @returns {boolean}
*/
function $isRegExp(inputArg) {
return $toStringTag(inputArg) === stringTagRegExp &&
pHasOwn.call(inputArg, 'ignoreCase') &&
typeof inputArg.ignoreCase === 'boolean' &&
pHasOwn.call(inputArg, 'global') &&
typeof inputArg.global === 'boolean' &&
pHasOwn.call(inputArg, 'multiline') &&
typeof inputArg.multiline === 'boolean' &&
pHasOwn.call(inputArg, 'source') &&
typeof inputArg.source === 'string';
}
/**
* The abstract operation throws an error if its argument is a value that
* cannot be converted to an Object, otherwise returns the argument.
*
* @private
* @function
* @param {*} inputArg The object to be tested.
* @throws {TypeError} If inputArg is null or undefined.
* @returns {*} The inputArg if coercible.
* @see https://goo.gl/5GcmVq
*/
function $requireObjectCoercible(inputArg) {
var errStr;
if (inputArg === null || $isUndefined(inputArg)) {
errStr = 'Cannot convert argument to object: ' + inputArg;
throw new TypeError(errStr);
}
return inputArg;
}
/**
* The abstract operation converts its argument to a value of type string
*
* @private
* @function
* @param {*} inputArg
* @returns {string}
* @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tostring
*/
function $toString(inputArg) {
var type,
val;
if (inputArg === null) {
val = 'null';
} else {
type = typeof inputArg;
if (type === 'string') {
val = inputArg;
} else if (type === 'undefined') {
val = type;
} else {
if (type === 'symbol') {
throw new TypeError('Cannot convert symbol to string');
}
val = String(inputArg);
}
}
return val;
}
/**
* Returns a string only if the arguments is coercible otherwise throws an
* error.
*
* @private
* @function
* @param {*} inputArg
* @throws {TypeError} If inputArg is null or undefined.
* @returns {string}
*/
function $onlyCoercibleToString(inputArg) {
return $toString($requireObjectCoercible(inputArg));
}
/**
* The function evaluates the passed value and converts it to an integer.
*
* @private
* @function
* @param {*} inputArg The object to be converted to an integer.
* @returns {number} If the target value is NaN, null or undefined, 0 is
* returned. If the target value is false, 0 is returned
* and if true, 1 is returned.
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
*/
function $toInteger(inputArg) {
var number = +inputArg,
val = 0;
if ($strictEqual(number, number)) {
if (!number || number === Infinity || number === -Infinity) {
val = number;
} else {
val = (number > 0 || -1) * Math.floor(Math.abs(number));
}
}
return val;
}
/**
* Copies a regex object. Allows adding and removing native flags while
* copying the regex.
*
* @private
* @function
* @param {RegExp} regex Regex to copy.
* @param {Object} [options] Allows specifying native flags to add or
* remove while copying the regex.
* @returns {RegExp} Copy of the provided regex, possibly with modified
* flags.
*/
function $copyRegExp(regex, options) {
var flags,
opts,
rx;
if (options !== null && typeof options === 'object') {
opts = options;
} else {
opts = {};
}
// Get native flags in use
flags = getNativeFlags.exec($toString(regex))[1];
flags = $onlyCoercibleToString(flags);
if (opts.add) {
flags += opts.add;
flags = flags.replace(clipDups, '');
}
if (opts.remove) {
// Would need to escape `options.remove` if this was public
rx = new RegExp('[' + opts.remove + ']+', 'g');
flags = flags.replace(rx, '');
}
return new RegExp(regex.source, flags);
}
/**
* The abstract operation ToLength converts its argument to an integer
* suitable for use as the length of an array-like object.
*
* @private
* @function
* @param {*} inputArg The object to be converted to a length.
* @returns {number} If len <= +0 then +0 else if len is +INFINITY then
* 2^53-1 else min(len, 2^53-1).
* @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
*/
function $toLength(inputArg) {
return Math.min(Math.max($toInteger(inputArg), 0), MAX_SAFE_INTEGER);
}
/**
* Copies a regex object so that it is suitable for use with searchOf and
* searchLastOf methods.
*
* @private
* @function
* @param {RegExp} regex Regex to copy.
* @returns {RegExp}
*/
function $toSearchRegExp(regex) {
return $copyRegExp(regex, {
add: 'g',
remove: 'y'
});
}
/**
* Returns true if the operand inputArg is a member of one of the types
* Undefined, Null, Boolean, Number, Symbol, or String.
*
* @private
* @function
* @param {*} inputArg
* @returns {boolean}
* @see https://goo.gl/W68ywJ
* @see https://goo.gl/ev7881
*/
function $isPrimitive(inputArg) {
var type = typeof inputArg;
return type === 'undefined' ||
inputArg === null ||
type === 'boolean' ||
type === 'string' ||
type === 'number' ||
type === 'symbol';
}
/**
* The abstract operation converts its argument to a value of type Object
* but fixes some environment bugs.
*
* @private
* @function
* @param {*} inputArg The argument to be converted to an object.
* @throws {TypeError} If inputArg is not coercible to an object.
* @returns {Object} Value of inputArg as type Object.
* @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.9
*/
function $toObject(inputArg) {
var object;
if ($isPrimitive($requireObjectCoercible(inputArg))) {
object = Object(inputArg);
} else {
object = inputArg;
}
return object;
}
/**
* Converts a single argument that is an array-like object or list (eg.
* arguments, NodeList, DOMTokenList (used by classList), NamedNodeMap
* (used by attributes property)) into a new Array() and returns it.
* This is a partial implementation of the ES6 Array.from
*
* @private
* @function
* @param {Object} arrayLike
* @returns {Array}
*/
function $toArray(arrayLike) {
var object = $toObject(arrayLike),
length = $toLength(object.length),
array = [],
index = 0;
array.length = length;
while (index < length) {
array[index] = object[index];
index += 1;
}
return array;
}
if (!String.prototype.searchOf) {
/**
* This method returns the index within the calling String object of
* the first occurrence of the specified value, starting the search at
* fromIndex. Returns -1 if the value is not found.
*
* @function
* @this {string}
* @param {RegExp|string} regex A regular expression object or a String.
* Anything else is implicitly converted to
* a String.
* @param {Number} [fromIndex] The location within the calling string
* to start the search from. It can be any
* integer. The default value is 0. If
* fromIndex < 0 the entire string is
* searched (same as passing 0). If
* fromIndex >= str.length, the method will
* return -1 unless searchValue is an empty
* string in which case str.length is
* returned.
* @returns {Number} If successful, returns the index of the first
* match of the regular expression inside the
* string. Otherwise, it returns -1.
*/
$defineProperty(String.prototype, 'searchOf', {
enumerable: false,
configurable: true,
writable: true,
value: function (regex) {
var str = $onlyCoercibleToString(this),
args = $toArray(arguments),
result = -1,
fromIndex,
match,
rx;
if (!$isRegExp(regex)) {
return String.prototype.indexOf.apply(str, args);
}
if ($toLength(args.length) > 1) {
fromIndex = +args[1];
if (fromIndex < 0) {
fromIndex = 0;
}
} else {
fromIndex = 0;
}
if (fromIndex >= $toLength(str.length)) {
return result;
}
rx = $toSearchRegExp(regex);
rx.lastIndex = fromIndex;
match = rx.exec(str);
if (match) {
result = +match.index;
}
return result;
}
});
}
if (!String.prototype.searchLastOf) {
/**
* This method returns the index within the calling String object of
* the last occurrence of the specified value, or -1 if not found.
* The calling string is searched backward, starting at fromIndex.
*
* @function
* @this {string}
* @param {RegExp|string} regex A regular expression object or a String.
* Anything else is implicitly converted to
* a String.
* @param {Number} [fromIndex] Optional. The location within the
* calling string to start the search at,
* indexed from left to right. It can be
* any integer. The default value is
* str.length. If it is negative, it is
* treated as 0. If fromIndex > str.length,
* fromIndex is treated as str.length.
* @returns {Number} If successful, returns the index of the first
* match of the regular expression inside the
* string. Otherwise, it returns -1.
*/
$defineProperty(String.prototype, 'searchLastOf', {
enumerable: false,
configurable: true,
writable: true,
value: function (regex) {
var str = $onlyCoercibleToString(this),
args = $toArray(arguments),
result = -1,
fromIndex,
length,
match,
pos,
rx;
if (!$isRegExp(regex)) {
return String.prototype.lastIndexOf.apply(str, args);
}
length = $toLength(str.length);
if (!$strictEqual(args[1], args[1])) {
fromIndex = length;
} else {
if ($toLength(args.length) > 1) {
fromIndex = $toInteger(args[1]);
} else {
fromIndex = length - 1;
}
}
if (fromIndex >= 0) {
fromIndex = Math.min(fromIndex, length - 1);
} else {
fromIndex = length - Math.abs(fromIndex);
}
pos = 0;
rx = $toSearchRegExp(regex);
while (pos <= fromIndex) {
rx.lastIndex = pos;
match = rx.exec(str);
if (!match) {
break;
}
pos = +match.index;
if (pos <= fromIndex) {
result = pos;
}
pos += 1;
}
return result;
}
});
}
}());
(function () {
'use strict';
/*
* testing as follow to make sure that at least for one character regexp,
* the result is the same as if we used indexOf
*/
var pre = document.getElementById('out');
function log(result) {
pre.appendChild(document.createTextNode(result + '\n'));
}
function test(str) {
var i = str.length + 2,
r,
a,
b;
while (i) {
a = str.indexOf('a', i);
b = str.searchOf(/a/, i);
r = ['Failed', 'searchOf', str, i, a, b];
if (a === b) {
r[0] = 'Passed';
}
log(r);
a = str.lastIndexOf('a', i);
b = str.searchLastOf(/a/, i);
r = ['Failed', 'searchLastOf', str, i, a, b];
if (a === b) {
r[0] = 'Passed';
}
log(r);
i -= 1;
}
}
/*
* Look for the a among the xes
*/
test('xxx');
test('axx');
test('xax');
test('xxa');
test('axa');
test('xaa');
test('aax');
test('aaa');
}());
<pre id="out"></pre>