using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Jint.Native.Array;
using Jint.Native.Function;
using Jint.Native.Object;
using Jint.Native.RegExp;
using Jint.Native.Symbol;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
namespace Jint.Native.String
{
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4
///
public sealed class StringPrototype : StringInstance
{
private StringPrototype(Engine engine)
: base(engine)
{
}
public static StringPrototype CreatePrototypeObject(Engine engine, StringConstructor stringConstructor)
{
var obj = new StringPrototype(engine);
obj.Prototype = engine.Object.PrototypeObject;
obj.PrimitiveValue = JsString.Empty;
obj.Extensible = true;
obj.SetOwnProperty("length", new PropertyDescriptor(0, PropertyFlag.AllForbidden));
obj.SetOwnProperty("constructor", new PropertyDescriptor(stringConstructor, PropertyFlag.NonEnumerable));
return obj;
}
public void Configure()
{
FastAddProperty("toString", new ClrFunctionInstance(Engine, "toString", ToStringString, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("valueOf", new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("charAt", new ClrFunctionInstance(Engine, "charAt", CharAt, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("charCodeAt", new ClrFunctionInstance(Engine, "charCodeAt", CharCodeAt, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("codePointAt", new ClrFunctionInstance(Engine, "codePointAt", CodePointAt, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("concat", new ClrFunctionInstance(Engine, "concat", Concat, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("indexOf", new ClrFunctionInstance(Engine, "indexOf", IndexOf, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("endsWith", new ClrFunctionInstance(Engine, "endsWith", EndsWith, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("startsWith", new ClrFunctionInstance(Engine, "startsWith", StartsWith, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("lastIndexOf", new ClrFunctionInstance(Engine, "lastIndexOf", LastIndexOf, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("localeCompare", new ClrFunctionInstance(Engine, "localeCompare", LocaleCompare, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("match", new ClrFunctionInstance(Engine, "match", Match, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("replace", new ClrFunctionInstance(Engine, "replace", Replace, 2, PropertyFlag.Configurable), true, false, true);
FastAddProperty("search", new ClrFunctionInstance(Engine, "search", Search, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("slice", new ClrFunctionInstance(Engine, "slice", Slice, 2, PropertyFlag.Configurable), true, false, true);
FastAddProperty("split", new ClrFunctionInstance(Engine, "split", Split, 2, PropertyFlag.Configurable), true, false, true);
FastAddProperty("substr", new ClrFunctionInstance(Engine, "substr", Substr, 2), true, false, true);
FastAddProperty("substring", new ClrFunctionInstance(Engine, "substring", Substring, 2, PropertyFlag.Configurable), true, false, true);
FastAddProperty("toLowerCase", new ClrFunctionInstance(Engine, "toLowerCase", ToLowerCase, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("toLocaleLowerCase", new ClrFunctionInstance(Engine, "toLocaleLowerCase", ToLocaleLowerCase, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("toUpperCase", new ClrFunctionInstance(Engine, "toUpperCase", ToUpperCase, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("toLocaleUpperCase", new ClrFunctionInstance(Engine, "toLocaleUpperCase", ToLocaleUpperCase, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("trim", new ClrFunctionInstance(Engine, "trim", Trim, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("trimStart", new ClrFunctionInstance(Engine, "trimStart", TrimStart, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("trimEnd", new ClrFunctionInstance(Engine, "trimEnd", TrimEnd, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("padStart", new ClrFunctionInstance(Engine, "padStart", PadStart, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("padEnd", new ClrFunctionInstance(Engine, "padEnd", PadEnd, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("includes", new ClrFunctionInstance(Engine, "includes", Includes, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty("normalize", new ClrFunctionInstance(Engine, "normalize", Normalize, 0, PropertyFlag.Configurable), true, false, true);
FastAddProperty("repeat", new ClrFunctionInstance(Engine, "repeat", Repeat, 1, PropertyFlag.Configurable), true, false, true);
FastAddProperty(GlobalSymbolRegistry.Iterator._value, new ClrFunctionInstance(Engine, "[Symbol.iterator]", Iterator, 0, PropertyFlag.Configurable), true, false, true);
}
private ObjectInstance Iterator(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(_engine, thisObj);
var str = TypeConverter.ToString(thisObj);
return _engine.Iterator.Construct(str);
}
private JsValue ToStringString(JsValue thisObj, JsValue[] arguments)
{
var s = TypeConverter.ToObject(Engine, thisObj) as StringInstance;
if (ReferenceEquals(s, null))
{
ExceptionHelper.ThrowTypeError(Engine);
}
return s.PrimitiveValue;
}
// http://msdn.microsoft.com/en-us/library/system.char.iswhitespace(v=vs.110).aspx
// http://en.wikipedia.org/wiki/Byte_order_mark
const char BOM_CHAR = '\uFEFF';
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsWhiteSpaceEx(char c)
{
return char.IsWhiteSpace(c) || c == BOM_CHAR;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string TrimEndEx(string s)
{
if (s.Length == 0)
return string.Empty;
if (!IsWhiteSpaceEx(s[s.Length - 1]))
return s;
return TrimEnd(s);
}
private static string TrimEnd(string s)
{
var i = s.Length - 1;
while (i >= 0)
{
if (IsWhiteSpaceEx(s[i]))
i--;
else
break;
}
return i >= 0 ? s.Substring(0, i + 1) : string.Empty;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string TrimStartEx(string s)
{
if (s.Length == 0)
return string.Empty;
if (!IsWhiteSpaceEx(s[0]))
return s;
return TrimStart(s);
}
private static string TrimStart(string s)
{
var i = 0;
while (i < s.Length)
{
if (IsWhiteSpaceEx(s[i]))
i++;
else
break;
}
return i >= s.Length ? string.Empty : s.Substring(i);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string TrimEx(string s)
{
return TrimEndEx(TrimStartEx(s));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private JsValue Trim(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
return TrimEx(s);
}
private JsValue TrimStart(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
return TrimStartEx(s);
}
private JsValue TrimEnd(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
return TrimEndEx(s);
}
private JsValue ToLocaleUpperCase(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(_engine, thisObj);
var s = TypeConverter.ToString(thisObj);
return s.ToUpper();
}
private JsValue ToUpperCase(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(_engine, thisObj);
var s = TypeConverter.ToString(thisObj);
return s.ToUpperInvariant();
}
private JsValue ToLocaleLowerCase(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(_engine, thisObj);
var s = TypeConverter.ToString(thisObj);
return s.ToLower();
}
private JsValue ToLowerCase(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(_engine, thisObj);
var s = TypeConverter.ToString(thisObj);
return s.ToLowerInvariant();
}
private static int ToIntegerSupportInfinity(JsValue numberVal)
{
var doubleVal = TypeConverter.ToInteger(numberVal);
int intVal;
if (double.IsPositiveInfinity(doubleVal))
intVal = int.MaxValue;
else if (double.IsNegativeInfinity(doubleVal))
intVal = int.MinValue;
else
intVal = (int) doubleVal;
return intVal;
}
private JsValue Substring(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var start = TypeConverter.ToNumber(arguments.At(0));
var end = TypeConverter.ToNumber(arguments.At(1));
if (double.IsNaN(start) || start < 0)
{
start = 0;
}
if (double.IsNaN(end) || end < 0)
{
end = 0;
}
var len = s.Length;
var intStart = ToIntegerSupportInfinity(start);
var intEnd = arguments.At(1).IsUndefined() ? len : ToIntegerSupportInfinity(end);
var finalStart = System.Math.Min(len, System.Math.Max(intStart, 0));
var finalEnd = System.Math.Min(len, System.Math.Max(intEnd, 0));
// Swap value if finalStart < finalEnd
var from = System.Math.Min(finalStart, finalEnd);
var to = System.Math.Max(finalStart, finalEnd);
var length = to - from;
if (length == 0)
{
return string.Empty;
}
if (length == 1)
{
return TypeConverter.ToString(s[from]);
}
return s.Substring(from, length);
}
private static JsValue Substr(JsValue thisObj, JsValue[] arguments)
{
var s = TypeConverter.ToString(thisObj);
var start = TypeConverter.ToInteger(arguments.At(0));
var length = arguments.At(1).IsUndefined()
? double.PositiveInfinity
: TypeConverter.ToInteger(arguments.At(1));
start = start >= 0 ? start : System.Math.Max(s.Length + start, 0);
length = System.Math.Min(System.Math.Max(length, 0), s.Length - start);
if (length <= 0)
{
return "";
}
var startIndex = TypeConverter.ToInt32(start);
var l = TypeConverter.ToInt32(length);
if (l == 1)
{
return TypeConverter.ToString(s[startIndex]);
}
return s.Substring(startIndex, l);
}
private JsValue Split(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var separator = arguments.At(0);
// Coerce into a number, true will become 1
var l = arguments.At(1);
var limit = l.IsUndefined() ? uint.MaxValue : TypeConverter.ToUint32(l);
var len = s.Length;
if (limit == 0)
{
return Engine.Array.Construct(Arguments.Empty);
}
if (separator.IsNull())
{
separator = Native.Null.Text;
}
else if (separator.IsUndefined())
{
var jsValues = _engine._jsValueArrayPool.RentArray(1);
jsValues[0] = s;
var arrayInstance = (ArrayInstance)Engine.Array.Construct(jsValues);
_engine._jsValueArrayPool.ReturnArray(jsValues);
return arrayInstance;
}
else
{
if (!separator.IsRegExp())
{
separator = TypeConverter.ToString(separator); // Coerce into a string, for an object call toString()
}
}
var rx = TypeConverter.ToObject(Engine, separator) as RegExpInstance;
const string regExpForMatchingAllCharactere = "(?:)";
if (!ReferenceEquals(rx, null) &&
rx.Source != regExpForMatchingAllCharactere // We need pattern to be defined -> for s.split(new RegExp)
)
{
var a = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
var match = rx.Value.Match(s, 0);
if (!match.Success) // No match at all return the string in an array
{
a.SetIndexValue(0, s, updateLength: true);
return a;
}
int lastIndex = 0;
uint index = 0;
while (match.Success && index < limit)
{
if (match.Length == 0 && (match.Index == 0 || match.Index == len || match.Index == lastIndex))
{
match = match.NextMatch();
continue;
}
// Add the match results to the array.
a.SetIndexValue(index++, s.Substring(lastIndex, match.Index - lastIndex), updateLength: true);
if (index >= limit)
{
return a;
}
lastIndex = match.Index + match.Length;
for (int i = 1; i < match.Groups.Count; i++)
{
var group = match.Groups[i];
var item = Undefined;
if (group.Captures.Count > 0)
{
item = match.Groups[i].Value;
}
a.SetIndexValue(index++, item, updateLength: true);
if (index >= limit)
{
return a;
}
}
match = match.NextMatch();
if (!match.Success) // Add the last part of the split
{
a.SetIndexValue(index++, s.Substring(lastIndex), updateLength: true);
}
}
return a;
}
else
{
var segments = StringExecutionContext.Current.SplitSegmentList;
segments.Clear();
var sep = TypeConverter.ToString(separator);
if (sep == string.Empty || (!ReferenceEquals(rx, null) && rx.Source == regExpForMatchingAllCharactere)) // for s.split(new RegExp)
{
if (s.Length > segments.Capacity)
{
segments.Capacity = s.Length;
}
for (var i = 0; i < s.Length; i++)
{
segments.Add(TypeConverter.ToString(s[i]));
}
}
else
{
var array = StringExecutionContext.Current.SplitArray1;
array[0] = sep;
segments.AddRange(s.Split(array, StringSplitOptions.None));
}
var length = (uint) System.Math.Min(segments.Count, limit);
var a = Engine.Array.ConstructFast(length);
for (int i = 0; i < length; i++)
{
a.SetIndexValue((uint) i, segments[i], updateLength: false);
}
a.SetLength(length);
return a;
}
}
private JsValue Slice(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var start = TypeConverter.ToNumber(arguments.At(0));
if (double.IsNegativeInfinity(start))
{
start = 0;
}
if (double.IsPositiveInfinity(start))
{
return string.Empty;
}
var s = TypeConverter.ToString(thisObj);
var end = TypeConverter.ToNumber(arguments.At(1));
if (double.IsPositiveInfinity(end))
{
end = s.Length;
}
var len = s.Length;
var intStart = (int) start;
var intEnd = arguments.At(1).IsUndefined() ? len : (int) TypeConverter.ToInteger(end);
var from = intStart < 0 ? System.Math.Max(len + intStart, 0) : System.Math.Min(intStart, len);
var to = intEnd < 0 ? System.Math.Max(len + intEnd, 0) : System.Math.Min(intEnd, len);
var span = System.Math.Max(to - from, 0);
if (span == 0)
{
return string.Empty;
}
if (span == 1)
{
return TypeConverter.ToString(s[from]);
}
return s.Substring(from, span);
}
private JsValue Search(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var regex = arguments.At(0);
if (regex.IsUndefined())
{
regex = string.Empty;
}
else if (regex.IsNull())
{
regex = Native.Null.Text;
}
var rx = TypeConverter.ToObject(Engine, regex) as RegExpInstance ?? (RegExpInstance)Engine.RegExp.Construct(new[] { regex });
var match = rx.Value.Match(s);
if (!match.Success)
{
return -1;
}
return match.Index;
}
private JsValue Replace(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var thisString = TypeConverter.ToString(thisObj);
var searchValue = arguments.At(0);
var replaceValue = arguments.At(1);
// If the second parameter is not a function we create one
var replaceFunction = replaceValue.TryCast();
if (ReferenceEquals(replaceFunction, null))
{
replaceFunction = new ClrFunctionInstance(Engine, "anonymous", (self, args) =>
{
var replaceString = TypeConverter.ToString(replaceValue);
var matchValue = TypeConverter.ToString(args.At(0));
var matchIndex = (int)TypeConverter.ToInteger(args.At(args.Length - 2));
// Check if the replacement string contains any patterns.
bool replaceTextContainsPattern = replaceString.IndexOf('$') >= 0;
// If there is no pattern, replace the pattern as is.
if (replaceTextContainsPattern == false)
return replaceString;
// Patterns
// $$ Inserts a "$".
// $& Inserts the matched substring.
// $` Inserts the portion of the string that precedes the matched substring.
// $' Inserts the portion of the string that follows the matched substring.
// $n or $nn Where n or nn are decimal digits, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object.
var replacementBuilder = StringExecutionContext.Current.GetStringBuilder(0);
replacementBuilder.Clear();
for (int i = 0; i < replaceString.Length; i++)
{
char c = replaceString[i];
if (c == '$' && i < replaceString.Length - 1)
{
c = replaceString[++i];
if (c == '$')
replacementBuilder.Append('$');
else if (c == '&')
replacementBuilder.Append(matchValue);
else if (c == '`')
replacementBuilder.Append(thisString.Substring(0, matchIndex));
else if (c == '\'')
replacementBuilder.Append(thisString.Substring(matchIndex + matchValue.Length));
else if (c >= '0' && c <= '9')
{
int matchNumber1 = c - '0';
// The match number can be one or two digits long.
int matchNumber2 = 0;
if (i < replaceString.Length - 1 && replaceString[i + 1] >= '0' && replaceString[i + 1] <= '9')
matchNumber2 = matchNumber1 * 10 + (replaceString[i + 1] - '0');
// Try the two digit capture first.
if (matchNumber2 > 0 && matchNumber2 < args.Length - 2)
{
// Two digit capture replacement.
replacementBuilder.Append(TypeConverter.ToString(args[matchNumber2]));
i++;
}
else if (matchNumber1 > 0 && matchNumber1 < args.Length - 2)
{
// Single digit capture replacement.
replacementBuilder.Append(TypeConverter.ToString(args[matchNumber1]));
}
else
{
// Capture does not exist.
replacementBuilder.Append('$');
i--;
}
}
else
{
// Unknown replacement pattern.
replacementBuilder.Append('$');
replacementBuilder.Append(c);
}
}
else
replacementBuilder.Append(c);
}
return replacementBuilder.ToString();
});
}
// searchValue is a regular expression
if (searchValue.IsNull())
{
searchValue = Native.Null.Text;
}
if (searchValue.IsUndefined())
{
searchValue = Native.Undefined.Text;
}
var rx = TypeConverter.ToObject(Engine, searchValue) as RegExpInstance;
if (!ReferenceEquals(rx, null))
{
// Replace the input string with replaceText, recording the last match found.
string result = rx.Value.Replace(thisString, match =>
{
var args = new JsValue[match.Groups.Count + 2];
for (var k = 0; k < match.Groups.Count; k++)
{
var group = match.Groups[k];
args[k] = @group.Value;
}
args[match.Groups.Count] = match.Index;
args[match.Groups.Count + 1] = thisString;
var v = TypeConverter.ToString(replaceFunction.Call(Undefined, args));
return v;
}, rx.Global == true ? -1 : 1);
// Set the deprecated RegExp properties if at least one match was found.
//if (lastMatch != null)
// this.Engine.RegExp.SetDeprecatedProperties(input, lastMatch);
return result;
}
// searchValue is a string
else
{
var substr = TypeConverter.ToString(searchValue);
// Find the first occurrance of substr.
int start = thisString.IndexOf(substr, StringComparison.Ordinal);
if (start == -1)
return thisString;
int end = start + substr.Length;
var args = _engine._jsValueArrayPool.RentArray(3);
args[0] = substr;
args[1] = start;
args[2] = thisString;
var replaceString = TypeConverter.ToString(replaceFunction.Call(Undefined, args));
_engine._jsValueArrayPool.ReturnArray(args);
// Replace only the first match.
var result = StringExecutionContext.Current.GetStringBuilder(thisString.Length + (substr.Length - substr.Length));
result.Clear();
result.Append(thisString, 0, start);
result.Append(replaceString);
result.Append(thisString, end, thisString.Length - end);
return result.ToString();
}
}
private JsValue Match(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var regex = arguments.At(0);
var rx = regex.TryCast();
rx = rx ?? (RegExpInstance) Engine.RegExp.Construct(new[] {regex});
var global = ((JsBoolean) rx.Get("global"))._value;
if (!global)
{
return Engine.RegExp.PrototypeObject.Exec(rx, Arguments.From(s));
}
else
{
rx.Put("lastIndex", 0, false);
var a = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
double previousLastIndex = 0;
uint n = 0;
var lastMatch = true;
while (lastMatch)
{
var result = Engine.RegExp.PrototypeObject.Exec(rx, Arguments.From(s)).TryCast();
if (ReferenceEquals(result, null))
{
lastMatch = false;
}
else
{
var thisIndex = ((JsNumber) rx.Get("lastIndex"))._value;
if (thisIndex == previousLastIndex)
{
rx.Put("lastIndex", thisIndex + 1, false);
previousLastIndex = thisIndex;
}
var matchStr = result.Get("0");
a.SetIndexValue(n, matchStr, updateLength: false);
n++;
}
}
if (n == 0)
{
return Null;
}
a.SetLength(n);
return a;
}
}
private JsValue LocaleCompare(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var that = TypeConverter.ToString(arguments.At(0));
return string.CompareOrdinal(s, that);
}
private JsValue LastIndexOf(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var searchStr = TypeConverter.ToString(arguments.At(0));
double numPos = double.NaN;
if (arguments.Length > 1 && !arguments[1].IsUndefined())
{
numPos = TypeConverter.ToNumber(arguments[1]);
}
var pos = double.IsNaN(numPos) ? double.PositiveInfinity : TypeConverter.ToInteger(numPos);
var len = s.Length;
var start = (int)System.Math.Min(System.Math.Max(pos, 0), len);
var searchLen = searchStr.Length;
var i = start;
bool found;
do
{
found = true;
var j = 0;
while (found && j < searchLen)
{
if ((i + searchLen > len) || (s[i + j] != searchStr[j]))
{
found = false;
}
else
{
j++;
}
}
if (!found)
{
i--;
}
} while (!found && i >= 0);
return i;
}
private JsValue IndexOf(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var searchStr = TypeConverter.ToString(arguments.At(0));
double pos = 0;
if (arguments.Length > 1 && !arguments[1].IsUndefined())
{
pos = TypeConverter.ToInteger(arguments[1]);
}
if (pos >= s.Length)
{
return -1;
}
if (pos < 0)
{
pos = 0;
}
return s.IndexOf(searchStr, (int) pos, StringComparison.Ordinal);
}
private JsValue Concat(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
// try to hint capacity if possible
int capacity = 0;
for (int i = 0; i < arguments.Length; ++i)
{
if (arguments[i].Type == Types.String)
{
capacity += arguments[i].AsStringWithoutTypeCheck().Length;
}
}
var value = TypeConverter.ToString(thisObj);
capacity += value.Length;
if (!(thisObj is JsString jsString))
{
jsString = new JsString.ConcatenatedString(value, capacity);
}
else
{
jsString = jsString.EnsureCapacity(capacity);
}
for (int i = 0; i < arguments.Length; i++)
{
jsString = jsString.Append(arguments[i]);
}
return jsString;
}
private JsValue CharCodeAt(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
JsValue pos = arguments.Length > 0 ? arguments[0] : 0;
var s = TypeConverter.ToString(thisObj);
var position = (int)TypeConverter.ToInteger(pos);
if (position < 0 || position >= s.Length)
{
return JsNumber.DoubleNaN;
}
return (double) s[position];
}
private JsValue CodePointAt(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
JsValue pos = arguments.Length > 0 ? arguments[0] : 0;
var s = TypeConverter.ToString(thisObj);
var position = (int)TypeConverter.ToInteger(pos);
if (position < 0 || position >= s.Length)
{
return Undefined;
}
var first = (double) s[position];
if (first >= 0xD800 && first <= 0xDBFF && s.Length > position + 1)
{
double second = s[position + 1];
if (second >= 0xDC00 && second <= 0xDFFF)
{
return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
}
}
return first;
}
private JsValue CharAt(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var position = TypeConverter.ToInteger(arguments.At(0));
var size = s.Length;
if (position >= size || position < 0)
{
return "";
}
return TypeConverter.ToString(s[(int) position]);
}
private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
{
if (thisObj is StringInstance si)
{
return si.PrimitiveValue;
}
if (thisObj is JsString)
{
return thisObj;
}
return ExceptionHelper.ThrowTypeError(Engine);
}
///
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
///
/// The original string object
///
/// argument[0] is the target length of the output string
/// argument[1] is the string to pad with
///
///
private JsValue PadStart(JsValue thisObj, JsValue[] arguments)
{
return Pad(thisObj, arguments, true);
}
///
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
///
/// The original string object
///
/// argument[0] is the target length of the output string
/// argument[1] is the string to pad with
///
///
private JsValue PadEnd(JsValue thisObj, JsValue[] arguments)
{
return Pad(thisObj, arguments, false);
}
private JsValue Pad(JsValue thisObj, JsValue[] arguments, bool padStart)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var targetLength = TypeConverter.ToInt32(arguments.At(0));
var padStringValue = arguments.At(1);
var padString = padStringValue.IsUndefined()
? " "
: TypeConverter.ToString(padStringValue);
var s = TypeConverter.ToString(thisObj);
if (s.Length > targetLength || padString.Length == 0)
{
return s;
}
targetLength = targetLength - s.Length;
if (targetLength > padString.Length)
{
padString = string.Join("", Enumerable.Repeat(padString, (targetLength / padString.Length) + 1));
}
return padStart
? $"{padString.Substring(0, targetLength)}{s}"
: $"{s}{padString.Substring(0, targetLength)}";
}
///
/// https://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.startswith
///
private JsValue StartsWith(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var searchString = arguments.At(0);
if (ReferenceEquals(searchString, Null))
{
searchString = Native.Null.Text;
}
else
{
if (searchString.IsRegExp())
{
ExceptionHelper.ThrowTypeError(Engine);
}
}
var searchStr = TypeConverter.ToString(searchString);
var pos = TypeConverter.ToInt32(arguments.At(1));
var len = s.Length;
var start = System.Math.Min(System.Math.Max(pos, 0), len);
var searchLength = searchStr.Length;
if (searchLength + start > len)
{
return false;
}
for (var i = 0; i < searchLength; i++)
{
if (s[start + i] != searchStr[i])
{
return false;
}
}
return true;
}
///
/// https://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.endswith
///
private JsValue EndsWith(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s = TypeConverter.ToString(thisObj);
var searchString = arguments.At(0);
if (ReferenceEquals(searchString, Null))
{
searchString = Native.Null.Text;
}
else
{
if (searchString.IsRegExp())
{
ExceptionHelper.ThrowTypeError(Engine);
}
}
var searchStr = TypeConverter.ToString(searchString);
var len = s.Length;
var pos = TypeConverter.ToInt32(arguments.At(1, len));
var end = System.Math.Min(System.Math.Max(pos, 0), len);
var searchLength = searchStr.Length;
var start = end - searchLength;
if (start < 0)
{
return false;
}
for (var i = 0; i < searchLength; i++)
{
if (s[start + i] != searchStr[i])
{
return false;
}
}
return true;
}
private JsValue Includes(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var s1 = TypeConverter.ToString(thisObj);
var searchString = arguments.At(0);
if (searchString.IsRegExp())
{
return ExceptionHelper.ThrowTypeError(_engine, "First argument to String.prototype.includes must not be a regular expression");
}
var searchStr = TypeConverter.ToString(searchString);
double pos = 0;
if (arguments.Length > 1 && !arguments[1].IsUndefined())
{
pos = TypeConverter.ToInteger(arguments[1]);
}
if (searchStr.Length == 0)
{
return true;
}
if (pos >= s1.Length)
{
return false;
}
if (pos < 0)
{
pos = 0;
}
return s1.IndexOf(searchStr, (int) pos, StringComparison.Ordinal) > -1;
}
private JsValue Normalize(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var str = TypeConverter.ToString(thisObj);
var param = arguments.At(0);
var form = "NFC";
if (!param.IsUndefined())
{
form = TypeConverter.ToString(param);
}
var nf = NormalizationForm.FormC;
switch (form)
{
case "NFC":
nf = NormalizationForm.FormC;
break;
case "NFD":
nf = NormalizationForm.FormD;
break;
case "NFKC":
nf = NormalizationForm.FormKC;
break;
case "NFKD":
nf = NormalizationForm.FormKD;
break;
default:
ExceptionHelper.ThrowRangeError(
_engine,
"The normalization form should be one of NFC, NFD, NFKC, NFKD.");
break;
}
return str.Normalize(nf);
}
private JsValue Repeat(JsValue thisObj, JsValue[] arguments)
{
TypeConverter.CheckObjectCoercible(Engine, thisObj);
var str = TypeConverter.ToString(thisObj);
var n = (int) TypeConverter.ToInteger(arguments.At(0));
if (n < 0)
{
return ExceptionHelper.ThrowRangeError(_engine, "Invalid count value");
}
if (n == 0 || str.Length == 0)
{
return JsString.Empty;
}
if (str.Length == 1)
{
return new string(str[0], n);
}
var sb = new StringBuilder(n * str.Length);
for (var i = 0; i < n; ++i)
{
sb.Append(str);
}
return sb.ToString();
}
}
}