StringPrototype.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
  1. using System.Runtime.CompilerServices;
  2. using System.Text;
  3. using Jint.Collections;
  4. using Jint.Native.Object;
  5. using Jint.Native.RegExp;
  6. using Jint.Native.Symbol;
  7. using Jint.Pooling;
  8. using Jint.Runtime;
  9. using Jint.Runtime.Descriptors;
  10. using Jint.Runtime.Interop;
  11. namespace Jint.Native.String
  12. {
  13. /// <summary>
  14. /// https://tc39.es/ecma262/#sec-properties-of-the-string-prototype-object
  15. /// </summary>
  16. internal sealed class StringPrototype : StringInstance
  17. {
  18. private readonly Realm _realm;
  19. private readonly StringConstructor _constructor;
  20. internal StringPrototype(
  21. Engine engine,
  22. Realm realm,
  23. StringConstructor constructor,
  24. ObjectPrototype objectPrototype)
  25. : base(engine, JsString.Empty)
  26. {
  27. _prototype = objectPrototype;
  28. _length = PropertyDescriptor.AllForbiddenDescriptor.NumberZero;
  29. _realm = realm;
  30. _constructor = constructor;
  31. }
  32. protected override void Initialize()
  33. {
  34. const PropertyFlag lengthFlags = PropertyFlag.Configurable;
  35. const PropertyFlag propertyFlags = lengthFlags | PropertyFlag.Writable;
  36. var trimStart = new PropertyDescriptor(new ClrFunctionInstance(Engine, "trimStart", TrimStart, 0, lengthFlags), propertyFlags);
  37. var trimEnd = new PropertyDescriptor(new ClrFunctionInstance(Engine, "trimEnd", TrimEnd, 0, lengthFlags), propertyFlags);
  38. var properties = new PropertyDictionary(35, checkExistingKeys: false)
  39. {
  40. ["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
  41. ["toString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toString", ToStringString, 0, lengthFlags), propertyFlags),
  42. ["valueOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, lengthFlags), propertyFlags),
  43. ["charAt"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "charAt", CharAt, 1, lengthFlags), propertyFlags),
  44. ["charCodeAt"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "charCodeAt", CharCodeAt, 1, lengthFlags), propertyFlags),
  45. ["codePointAt"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "codePointAt", CodePointAt, 1, lengthFlags), propertyFlags),
  46. ["concat"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "concat", Concat, 1, lengthFlags), propertyFlags),
  47. ["indexOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "indexOf", IndexOf, 1, lengthFlags), propertyFlags),
  48. ["endsWith"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "endsWith", EndsWith, 1, lengthFlags), propertyFlags),
  49. ["startsWith"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "startsWith", StartsWith, 1, lengthFlags), propertyFlags),
  50. ["lastIndexOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "lastIndexOf", LastIndexOf, 1, lengthFlags), propertyFlags),
  51. ["localeCompare"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "localeCompare", LocaleCompare, 1, lengthFlags), propertyFlags),
  52. ["match"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "match", Match, 1, lengthFlags), propertyFlags),
  53. ["matchAll"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "matchAll", MatchAll, 1, lengthFlags), propertyFlags),
  54. ["replace"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "replace", Replace, 2, lengthFlags), propertyFlags),
  55. ["replaceAll"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "replaceAll", ReplaceAll, 2, lengthFlags), propertyFlags),
  56. ["search"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "search", Search, 1, lengthFlags), propertyFlags),
  57. ["slice"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "slice", Slice, 2, lengthFlags), propertyFlags),
  58. ["split"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "split", Split, 2, lengthFlags), propertyFlags),
  59. ["substr"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "substr", Substr, 2), propertyFlags),
  60. ["substring"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "substring", Substring, 2, lengthFlags), propertyFlags),
  61. ["toLowerCase"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toLowerCase", ToLowerCase, 0, lengthFlags), propertyFlags),
  62. ["toLocaleLowerCase"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toLocaleLowerCase", ToLocaleLowerCase, 0, lengthFlags), propertyFlags),
  63. ["toUpperCase"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toUpperCase", ToUpperCase, 0, lengthFlags), propertyFlags),
  64. ["toLocaleUpperCase"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toLocaleUpperCase", ToLocaleUpperCase, 0, lengthFlags), propertyFlags),
  65. ["trim"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "trim", Trim, 0, lengthFlags), propertyFlags),
  66. ["trimStart"] = trimStart,
  67. ["trimEnd"] = trimEnd,
  68. ["trimLeft"] = trimStart,
  69. ["trimRight"] = trimEnd,
  70. ["padStart"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "padStart", PadStart, 1, lengthFlags), propertyFlags),
  71. ["padEnd"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "padEnd", PadEnd, 1, lengthFlags), propertyFlags),
  72. ["includes"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "includes", Includes, 1, lengthFlags), propertyFlags),
  73. ["normalize"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "normalize", Normalize, 0, lengthFlags), propertyFlags),
  74. ["repeat"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "repeat", Repeat, 1, lengthFlags), propertyFlags),
  75. ["at"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "at", At, 1, lengthFlags), propertyFlags),
  76. };
  77. SetProperties(properties);
  78. var symbols = new SymbolDictionary(1)
  79. {
  80. [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "[Symbol.iterator]", Iterator, 0, lengthFlags), propertyFlags)
  81. };
  82. SetSymbols(symbols);
  83. }
  84. private ObjectInstance Iterator(JsValue thisObj, JsValue[] arguments)
  85. {
  86. TypeConverter.CheckObjectCoercible(_engine, thisObj);
  87. var str = TypeConverter.ToString(thisObj);
  88. return _realm.Intrinsics.StringIteratorPrototype.Construct(str);
  89. }
  90. private JsValue ToStringString(JsValue thisObj, JsValue[] arguments)
  91. {
  92. if (thisObj.IsString())
  93. {
  94. return thisObj;
  95. }
  96. var s = TypeConverter.ToObject(_realm, thisObj) as StringInstance;
  97. if (ReferenceEquals(s, null))
  98. {
  99. ExceptionHelper.ThrowTypeError(_realm);
  100. }
  101. return s.StringData;
  102. }
  103. // http://msdn.microsoft.com/en-us/library/system.char.iswhitespace(v=vs.110).aspx
  104. // http://en.wikipedia.org/wiki/Byte_order_mark
  105. const char BOM_CHAR = '\uFEFF';
  106. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  107. private static bool IsWhiteSpaceEx(char c)
  108. {
  109. return char.IsWhiteSpace(c) || c == BOM_CHAR;
  110. }
  111. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  112. private static string TrimEndEx(string s)
  113. {
  114. if (s.Length == 0)
  115. return string.Empty;
  116. if (!IsWhiteSpaceEx(s[s.Length - 1]))
  117. return s;
  118. return TrimEnd(s);
  119. }
  120. private static string TrimEnd(string s)
  121. {
  122. var i = s.Length - 1;
  123. while (i >= 0)
  124. {
  125. if (IsWhiteSpaceEx(s[i]))
  126. i--;
  127. else
  128. break;
  129. }
  130. return i >= 0 ? s.Substring(0, i + 1) : string.Empty;
  131. }
  132. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  133. internal static string TrimStartEx(string s)
  134. {
  135. if (s.Length == 0)
  136. return string.Empty;
  137. if (!IsWhiteSpaceEx(s[0]))
  138. return s;
  139. return TrimStart(s);
  140. }
  141. private static string TrimStart(string s)
  142. {
  143. var i = 0;
  144. while (i < s.Length)
  145. {
  146. if (IsWhiteSpaceEx(s[i]))
  147. i++;
  148. else
  149. break;
  150. }
  151. return i >= s.Length ? string.Empty : s.Substring(i);
  152. }
  153. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  154. internal static string TrimEx(string s)
  155. {
  156. return TrimEndEx(TrimStartEx(s));
  157. }
  158. /// <summary>
  159. /// https://tc39.es/ecma262/#sec-string.prototype.trim
  160. /// </summary>
  161. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  162. private JsValue Trim(JsValue thisObj, JsValue[] arguments)
  163. {
  164. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  165. var s = TypeConverter.ToJsString(thisObj);
  166. if (s.Length == 0 || (!IsWhiteSpaceEx(s[0]) && !IsWhiteSpaceEx(s[s.Length - 1])))
  167. {
  168. return s;
  169. }
  170. return TrimEx(s.ToString());
  171. }
  172. /// <summary>
  173. /// https://tc39.es/ecma262/#sec-string.prototype.trimstart
  174. /// </summary>
  175. private JsValue TrimStart(JsValue thisObj, JsValue[] arguments)
  176. {
  177. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  178. var s = TypeConverter.ToJsString(thisObj);
  179. if (s.Length == 0 || !IsWhiteSpaceEx(s[0]))
  180. {
  181. return s;
  182. }
  183. return TrimStartEx(s.ToString());
  184. }
  185. /// <summary>
  186. /// https://tc39.es/ecma262/#sec-string.prototype.trimend
  187. /// </summary>
  188. private JsValue TrimEnd(JsValue thisObj, JsValue[] arguments)
  189. {
  190. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  191. var s = TypeConverter.ToJsString(thisObj);
  192. if (s.Length == 0 || !IsWhiteSpaceEx(s[s.Length - 1]))
  193. {
  194. return s;
  195. }
  196. return TrimEndEx(s.ToString());
  197. }
  198. private JsValue ToLocaleUpperCase(JsValue thisObj, JsValue[] arguments)
  199. {
  200. TypeConverter.CheckObjectCoercible(_engine, thisObj);
  201. var s = TypeConverter.ToString(thisObj);
  202. return new JsString(s.ToUpper());
  203. }
  204. private JsValue ToUpperCase(JsValue thisObj, JsValue[] arguments)
  205. {
  206. TypeConverter.CheckObjectCoercible(_engine, thisObj);
  207. var s = TypeConverter.ToString(thisObj);
  208. return new JsString(s.ToUpperInvariant());
  209. }
  210. private JsValue ToLocaleLowerCase(JsValue thisObj, JsValue[] arguments)
  211. {
  212. TypeConverter.CheckObjectCoercible(_engine, thisObj);
  213. var s = TypeConverter.ToString(thisObj);
  214. return new JsString(s.ToLower());
  215. }
  216. private JsValue ToLowerCase(JsValue thisObj, JsValue[] arguments)
  217. {
  218. TypeConverter.CheckObjectCoercible(_engine, thisObj);
  219. var s = TypeConverter.ToString(thisObj);
  220. return s.ToLowerInvariant();
  221. }
  222. private static int ToIntegerSupportInfinity(JsValue numberVal)
  223. {
  224. return numberVal._type == InternalTypes.Integer
  225. ? numberVal.AsInteger()
  226. : ToIntegerSupportInfinityUnlikely(numberVal);
  227. }
  228. [MethodImpl(MethodImplOptions.NoInlining)]
  229. private static int ToIntegerSupportInfinityUnlikely(JsValue numberVal)
  230. {
  231. var doubleVal = TypeConverter.ToInteger(numberVal);
  232. int intVal;
  233. if (double.IsPositiveInfinity(doubleVal))
  234. intVal = int.MaxValue;
  235. else if (double.IsNegativeInfinity(doubleVal))
  236. intVal = int.MinValue;
  237. else
  238. intVal = (int) doubleVal;
  239. return intVal;
  240. }
  241. private JsValue Substring(JsValue thisObj, JsValue[] arguments)
  242. {
  243. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  244. var s = TypeConverter.ToString(thisObj);
  245. var start = TypeConverter.ToNumber(arguments.At(0));
  246. var end = TypeConverter.ToNumber(arguments.At(1));
  247. if (double.IsNaN(start) || start < 0)
  248. {
  249. start = 0;
  250. }
  251. if (double.IsNaN(end) || end < 0)
  252. {
  253. end = 0;
  254. }
  255. var len = s.Length;
  256. var intStart = ToIntegerSupportInfinity(start);
  257. var intEnd = arguments.At(1).IsUndefined() ? len : ToIntegerSupportInfinity(end);
  258. var finalStart = System.Math.Min(len, System.Math.Max(intStart, 0));
  259. var finalEnd = System.Math.Min(len, System.Math.Max(intEnd, 0));
  260. // Swap value if finalStart < finalEnd
  261. var from = System.Math.Min(finalStart, finalEnd);
  262. var to = System.Math.Max(finalStart, finalEnd);
  263. var length = to - from;
  264. if (length == 0)
  265. {
  266. return JsString.Empty;
  267. }
  268. if (length == 1)
  269. {
  270. return JsString.Create(s[from]);
  271. }
  272. return new JsString(s.Substring(from, length));
  273. }
  274. private static JsValue Substr(JsValue thisObj, JsValue[] arguments)
  275. {
  276. var s = TypeConverter.ToString(thisObj);
  277. var start = TypeConverter.ToInteger(arguments.At(0));
  278. var length = arguments.At(1).IsUndefined()
  279. ? double.PositiveInfinity
  280. : TypeConverter.ToInteger(arguments.At(1));
  281. start = start >= 0 ? start : System.Math.Max(s.Length + start, 0);
  282. length = System.Math.Min(System.Math.Max(length, 0), s.Length - start);
  283. if (length <= 0)
  284. {
  285. return JsString.Empty;
  286. }
  287. var startIndex = TypeConverter.ToInt32(start);
  288. var l = TypeConverter.ToInt32(length);
  289. if (l == 1)
  290. {
  291. return TypeConverter.ToString(s[startIndex]);
  292. }
  293. return s.Substring(startIndex, l);
  294. }
  295. /// <summary>
  296. /// https://tc39.es/ecma262/#sec-string.prototype.split
  297. /// </summary>
  298. private JsValue Split(JsValue thisObj, JsValue[] arguments)
  299. {
  300. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  301. var separator = arguments.At(0);
  302. var limit = arguments.At(1);
  303. // fast path for empty regexp
  304. if (separator is RegExpInstance R && R.Source == RegExpInstance.regExpForMatchingAllCharacters)
  305. {
  306. separator = JsString.Empty;
  307. }
  308. if (separator is ObjectInstance oi)
  309. {
  310. var splitter = GetMethod(_realm, oi, GlobalSymbolRegistry.Split);
  311. if (splitter != null)
  312. {
  313. return splitter.Call(separator, new[] { thisObj, limit });
  314. }
  315. }
  316. var s = TypeConverter.ToString(thisObj);
  317. // Coerce into a number, true will become 1
  318. var lim = limit.IsUndefined() ? uint.MaxValue : TypeConverter.ToUint32(limit);
  319. if (separator.IsNull())
  320. {
  321. separator = "null";
  322. }
  323. else if (!separator.IsUndefined())
  324. {
  325. if (!separator.IsRegExp())
  326. {
  327. separator = TypeConverter.ToJsString(separator); // Coerce into a string, for an object call toString()
  328. }
  329. }
  330. if (lim == 0)
  331. {
  332. return _realm.Intrinsics.Array.ArrayCreate(0);
  333. }
  334. if (separator.IsUndefined())
  335. {
  336. var arrayInstance = _realm.Intrinsics.Array.ArrayCreate(1);
  337. arrayInstance.SetIndexValue(0, s, updateLength: false);
  338. return arrayInstance;
  339. }
  340. return SplitWithStringSeparator(_realm, separator, s, lim);
  341. }
  342. internal static JsValue SplitWithStringSeparator(Realm realm, JsValue separator, string s, uint lim)
  343. {
  344. var segments = StringExecutionContext.Current.SplitSegmentList;
  345. segments.Clear();
  346. var sep = TypeConverter.ToString(separator);
  347. if (sep == string.Empty)
  348. {
  349. if (s.Length > segments.Capacity)
  350. {
  351. segments.Capacity = s.Length;
  352. }
  353. for (var i = 0; i < s.Length; i++)
  354. {
  355. segments.Add(TypeConverter.ToString(s[i]));
  356. }
  357. }
  358. else
  359. {
  360. var array = StringExecutionContext.Current.SplitArray1;
  361. array[0] = sep;
  362. segments.AddRange(s.Split(array, StringSplitOptions.None));
  363. }
  364. var length = (uint) System.Math.Min(segments.Count, lim);
  365. var a = realm.Intrinsics.Array.ArrayCreate(length);
  366. for (int i = 0; i < length; i++)
  367. {
  368. a.SetIndexValue((uint) i, segments[i], updateLength: false);
  369. }
  370. a.SetLength(length);
  371. return a;
  372. }
  373. /// <summary>
  374. /// https://tc39.es/proposal-relative-indexing-method/#sec-string-prototype-additions
  375. /// </summary>
  376. private JsValue At(JsValue thisObj, JsValue[] arguments)
  377. {
  378. TypeConverter.CheckObjectCoercible(_engine, thisObj);
  379. var start = arguments.At(0);
  380. var o = thisObj.ToString();
  381. long len = o.Length;
  382. var relativeIndex = TypeConverter.ToInteger(start);
  383. int k;
  384. if (relativeIndex < 0)
  385. {
  386. k = (int) (len + relativeIndex);
  387. }
  388. else
  389. {
  390. k = (int) relativeIndex;
  391. }
  392. if (k < 0 || k >= len)
  393. {
  394. return Undefined;
  395. }
  396. return o[k];
  397. }
  398. private JsValue Slice(JsValue thisObj, JsValue[] arguments)
  399. {
  400. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  401. var start = TypeConverter.ToNumber(arguments.At(0));
  402. if (double.IsNegativeInfinity(start))
  403. {
  404. start = 0;
  405. }
  406. if (double.IsPositiveInfinity(start))
  407. {
  408. return JsString.Empty;
  409. }
  410. var s = TypeConverter.ToJsString(thisObj);
  411. var end = TypeConverter.ToNumber(arguments.At(1));
  412. if (double.IsPositiveInfinity(end))
  413. {
  414. end = s.Length;
  415. }
  416. var len = s.Length;
  417. var intStart = (int) start;
  418. var intEnd = arguments.At(1).IsUndefined() ? len : (int) TypeConverter.ToInteger(end);
  419. var from = intStart < 0 ? System.Math.Max(len + intStart, 0) : System.Math.Min(intStart, len);
  420. var to = intEnd < 0 ? System.Math.Max(len + intEnd, 0) : System.Math.Min(intEnd, len);
  421. var span = System.Math.Max(to - from, 0);
  422. if (span == 0)
  423. {
  424. return JsString.Empty;
  425. }
  426. if (span == 1)
  427. {
  428. return JsString.Create(s[from]);
  429. }
  430. return s.Substring(from, span);
  431. }
  432. private JsValue Search(JsValue thisObj, JsValue[] arguments)
  433. {
  434. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  435. var regex = arguments.At(0);
  436. if (regex is ObjectInstance oi)
  437. {
  438. var searcher = GetMethod(_realm, oi, GlobalSymbolRegistry.Search);
  439. if (searcher != null)
  440. {
  441. return searcher.Call(regex, new[] { thisObj });
  442. }
  443. }
  444. var rx = (RegExpInstance) _realm.Intrinsics.RegExp.Construct(new[] {regex});
  445. var s = TypeConverter.ToJsString(thisObj);
  446. return _engine.Invoke(rx, GlobalSymbolRegistry.Search, new JsValue[] { s });
  447. }
  448. /// <summary>
  449. /// https://tc39.es/ecma262/#sec-string.prototype.replace
  450. /// </summary>
  451. private JsValue Replace(JsValue thisObj, JsValue[] arguments)
  452. {
  453. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  454. var searchValue = arguments.At(0);
  455. var replaceValue = arguments.At(1);
  456. if (!searchValue.IsNullOrUndefined())
  457. {
  458. var replacer = GetMethod(_realm, searchValue, GlobalSymbolRegistry.Replace);
  459. if (replacer != null)
  460. {
  461. return replacer.Call(searchValue, thisObj, replaceValue);
  462. }
  463. }
  464. var thisString = TypeConverter.ToJsString(thisObj);
  465. var searchString = TypeConverter.ToString(searchValue);
  466. var functionalReplace = replaceValue is ICallable;
  467. if (!functionalReplace)
  468. {
  469. replaceValue = TypeConverter.ToJsString(replaceValue);
  470. }
  471. var position = thisString.IndexOf(searchString);
  472. if (position < 0)
  473. {
  474. return thisString;
  475. }
  476. string replStr;
  477. if (functionalReplace)
  478. {
  479. var replValue = ((ICallable) replaceValue).Call(Undefined, searchString, position, thisString);
  480. replStr = TypeConverter.ToString(replValue);
  481. }
  482. else
  483. {
  484. var captures = System.Array.Empty<string>();
  485. replStr = RegExpPrototype.GetSubstitution(searchString, thisString.ToString(), position, captures, Undefined, TypeConverter.ToString(replaceValue));
  486. }
  487. var tailPos = position + searchString.Length;
  488. var newString = thisString.Substring(0, position) + replStr + thisString.Substring(tailPos);
  489. return newString;
  490. }
  491. /// <summary>
  492. /// https://tc39.es/ecma262/#sec-string.prototype.replaceall
  493. /// </summary>
  494. private JsValue ReplaceAll(JsValue thisObj, JsValue[] arguments)
  495. {
  496. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  497. var searchValue = arguments.At(0);
  498. var replaceValue = arguments.At(1);
  499. if (!searchValue.IsNullOrUndefined())
  500. {
  501. if (searchValue.IsRegExp())
  502. {
  503. var flags = searchValue.Get(RegExpPrototype.PropertyFlags);
  504. TypeConverter.CheckObjectCoercible(_engine, flags);
  505. if (TypeConverter.ToString(flags).IndexOf('g') < 0)
  506. {
  507. ExceptionHelper.ThrowTypeError(_realm, "String.prototype.replaceAll called with a non-global RegExp argument");
  508. }
  509. }
  510. var replacer = GetMethod(_realm, searchValue, GlobalSymbolRegistry.Replace);
  511. if (replacer != null)
  512. {
  513. return replacer.Call(searchValue, thisObj, replaceValue);
  514. }
  515. }
  516. var thisString = TypeConverter.ToString(thisObj);
  517. var searchString = TypeConverter.ToString(searchValue);
  518. var functionalReplace = replaceValue is ICallable;
  519. if (!functionalReplace)
  520. {
  521. replaceValue = TypeConverter.ToJsString(replaceValue);
  522. // check fast case
  523. var newValue = replaceValue.ToString();
  524. if (newValue.IndexOf('$') < 0 && searchString.Length > 0)
  525. {
  526. // just plain old string replace
  527. return thisString.Replace(searchString, newValue);
  528. }
  529. }
  530. // https://tc39.es/ecma262/#sec-stringindexof
  531. static int StringIndexOf(string s, string search, int fromIndex)
  532. {
  533. if (search.Length == 0 && fromIndex <= s.Length)
  534. {
  535. return fromIndex;
  536. }
  537. return fromIndex < s.Length
  538. ? s.IndexOf(search, fromIndex, StringComparison.Ordinal)
  539. : -1;
  540. }
  541. var searchLength = searchString.Length;
  542. var advanceBy = System.Math.Max(1, searchLength);
  543. var endOfLastMatch = 0;
  544. using var pool = StringBuilderPool.Rent();
  545. var result = pool.Builder;
  546. var position = StringIndexOf(thisString, searchString, 0);
  547. while (position != -1)
  548. {
  549. string replacement;
  550. var preserved = thisString.Substring(endOfLastMatch, position - endOfLastMatch);
  551. if (functionalReplace)
  552. {
  553. var replValue = ((ICallable) replaceValue).Call(Undefined, searchString, position, thisString);
  554. replacement = TypeConverter.ToString(replValue);
  555. }
  556. else
  557. {
  558. var captures = System.Array.Empty<string>();
  559. replacement = RegExpPrototype.GetSubstitution(searchString, thisString, position, captures, Undefined, TypeConverter.ToString(replaceValue));
  560. }
  561. result.Append(preserved).Append(replacement);
  562. endOfLastMatch = position + searchLength;
  563. position = StringIndexOf(thisString, searchString, position + advanceBy);
  564. }
  565. if (endOfLastMatch < thisString.Length)
  566. {
  567. result.Append(thisString.Substring(endOfLastMatch));
  568. }
  569. return result.ToString();
  570. }
  571. private JsValue Match(JsValue thisObj, JsValue[] arguments)
  572. {
  573. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  574. var regex = arguments.At(0);
  575. if (regex is ObjectInstance oi)
  576. {
  577. var matcher = GetMethod(_realm, oi, GlobalSymbolRegistry.Match);
  578. if (matcher != null)
  579. {
  580. return matcher.Call(regex, new[] { thisObj });
  581. }
  582. }
  583. var rx = (RegExpInstance) _realm.Intrinsics.RegExp.Construct(new[] {regex});
  584. var s = TypeConverter.ToJsString(thisObj);
  585. return _engine.Invoke(rx, GlobalSymbolRegistry.Match, new JsValue[] { s });
  586. }
  587. private JsValue MatchAll(JsValue thisObj, JsValue[] arguments)
  588. {
  589. TypeConverter.CheckObjectCoercible(_engine, thisObj);
  590. var regex = arguments.At(0);
  591. if (!regex.IsNullOrUndefined())
  592. {
  593. if (regex.IsRegExp())
  594. {
  595. var flags = regex.Get(RegExpPrototype.PropertyFlags);
  596. TypeConverter.CheckObjectCoercible(_engine, flags);
  597. if (TypeConverter.ToString(flags).IndexOf('g') < 0)
  598. {
  599. ExceptionHelper.ThrowTypeError(_realm);
  600. }
  601. }
  602. var matcher = GetMethod(_realm, (ObjectInstance) regex, GlobalSymbolRegistry.MatchAll);
  603. if (matcher != null)
  604. {
  605. return matcher.Call(regex, new[] { thisObj });
  606. }
  607. }
  608. var s = TypeConverter.ToJsString(thisObj);
  609. var rx = (RegExpInstance) _realm.Intrinsics.RegExp.Construct(new[] { regex, "g" });
  610. return _engine.Invoke(rx, GlobalSymbolRegistry.MatchAll, new JsValue[] { s });
  611. }
  612. private JsValue LocaleCompare(JsValue thisObj, JsValue[] arguments)
  613. {
  614. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  615. var s = TypeConverter.ToString(thisObj);
  616. var that = TypeConverter.ToString(arguments.At(0));
  617. return string.CompareOrdinal(s.Normalize(NormalizationForm.FormKD), that.Normalize(NormalizationForm.FormKD));
  618. }
  619. /// <summary>
  620. /// https://tc39.es/ecma262/#sec-string.prototype.lastindexof
  621. /// </summary>
  622. private JsValue LastIndexOf(JsValue thisObj, JsValue[] arguments)
  623. {
  624. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  625. var jsString = TypeConverter.ToJsString(thisObj);
  626. var searchStr = TypeConverter.ToString(arguments.At(0));
  627. double numPos = double.NaN;
  628. if (arguments.Length > 1 && !arguments[1].IsUndefined())
  629. {
  630. numPos = TypeConverter.ToNumber(arguments[1]);
  631. }
  632. var pos = double.IsNaN(numPos) ? double.PositiveInfinity : TypeConverter.ToInteger(numPos);
  633. var len = jsString.Length;
  634. var start = (int)System.Math.Min(System.Math.Max(pos, 0), len);
  635. var searchLen = searchStr.Length;
  636. if (searchLen > len)
  637. {
  638. return JsNumber.IntegerNegativeOne;
  639. }
  640. var s = jsString.ToString();
  641. var i = start;
  642. bool found;
  643. do
  644. {
  645. found = true;
  646. var j = 0;
  647. while (found && j < searchLen)
  648. {
  649. if (i + searchLen > len || s[i + j] != searchStr[j])
  650. {
  651. found = false;
  652. }
  653. else
  654. {
  655. j++;
  656. }
  657. }
  658. if (!found)
  659. {
  660. i--;
  661. }
  662. } while (!found && i >= 0);
  663. return i;
  664. }
  665. /// <summary>
  666. /// https://tc39.es/ecma262/#sec-string.prototype.indexof
  667. /// </summary>
  668. private JsValue IndexOf(JsValue thisObj, JsValue[] arguments)
  669. {
  670. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  671. var s = TypeConverter.ToJsString(thisObj);
  672. var searchStr = TypeConverter.ToString(arguments.At(0));
  673. double pos = 0;
  674. if (arguments.Length > 1 && !arguments[1].IsUndefined())
  675. {
  676. pos = TypeConverter.ToInteger(arguments[1]);
  677. }
  678. if (pos > s.Length)
  679. {
  680. pos = s.Length;
  681. }
  682. if (pos < 0)
  683. {
  684. pos = 0;
  685. }
  686. return s.IndexOf(searchStr, (int) pos);
  687. }
  688. private JsValue Concat(JsValue thisObj, JsValue[] arguments)
  689. {
  690. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  691. if (thisObj is not JsString jsString)
  692. {
  693. jsString = new JsString.ConcatenatedString(TypeConverter.ToString(thisObj));
  694. }
  695. else
  696. {
  697. jsString = jsString.EnsureCapacity(0);
  698. }
  699. foreach (var argument in arguments)
  700. {
  701. jsString = jsString.Append(argument);
  702. }
  703. return jsString;
  704. }
  705. private JsValue CharCodeAt(JsValue thisObj, JsValue[] arguments)
  706. {
  707. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  708. JsValue pos = arguments.Length > 0 ? arguments[0] : 0;
  709. var s = TypeConverter.ToJsString(thisObj);
  710. var position = (int) TypeConverter.ToInteger(pos);
  711. if (position < 0 || position >= s.Length)
  712. {
  713. return JsNumber.DoubleNaN;
  714. }
  715. return (long) s[position];
  716. }
  717. /// <summary>
  718. /// https://tc39.es/ecma262/#sec-string.prototype.codepointat
  719. /// </summary>
  720. private JsValue CodePointAt(JsValue thisObj, JsValue[] arguments)
  721. {
  722. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  723. JsValue pos = arguments.Length > 0 ? arguments[0] : 0;
  724. var s = TypeConverter.ToString(thisObj);
  725. var position = (int)TypeConverter.ToInteger(pos);
  726. if (position < 0 || position >= s.Length)
  727. {
  728. return Undefined;
  729. }
  730. var first = (long) s[position];
  731. if (first >= 0xD800 && first <= 0xDBFF && s.Length > position + 1)
  732. {
  733. long second = s[position + 1];
  734. if (second >= 0xDC00 && second <= 0xDFFF)
  735. {
  736. return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
  737. }
  738. }
  739. return first;
  740. }
  741. private JsValue CharAt(JsValue thisObj, JsValue[] arguments)
  742. {
  743. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  744. var s = TypeConverter.ToJsString(thisObj);
  745. var position = TypeConverter.ToInteger(arguments.At(0));
  746. var size = s.Length;
  747. if (position >= size || position < 0)
  748. {
  749. return JsString.Empty;
  750. }
  751. return JsString.Create(s[(int) position]);
  752. }
  753. private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
  754. {
  755. if (thisObj is StringInstance si)
  756. {
  757. return si.StringData;
  758. }
  759. if (thisObj is JsString)
  760. {
  761. return thisObj;
  762. }
  763. ExceptionHelper.ThrowTypeError(_realm);
  764. return Undefined;
  765. }
  766. /// <summary>
  767. /// https://tc39.es/ecma262/#sec-string.prototype.padstart
  768. /// </summary>
  769. private JsValue PadStart(JsValue thisObj, JsValue[] arguments)
  770. {
  771. return StringPad(thisObj, arguments, true);
  772. }
  773. /// <summary>
  774. /// https://tc39.es/ecma262/#sec-string.prototype.padend
  775. /// </summary>
  776. private JsValue PadEnd(JsValue thisObj, JsValue[] arguments)
  777. {
  778. return StringPad(thisObj, arguments, false);
  779. }
  780. /// <summary>
  781. /// https://tc39.es/ecma262/#sec-stringpad
  782. /// </summary>
  783. private JsValue StringPad(JsValue thisObj, JsValue[] arguments, bool padStart)
  784. {
  785. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  786. var s = TypeConverter.ToJsString(thisObj);
  787. var targetLength = TypeConverter.ToInt32(arguments.At(0));
  788. var padStringValue = arguments.At(1);
  789. var padString = padStringValue.IsUndefined()
  790. ? " "
  791. : TypeConverter.ToString(padStringValue);
  792. if (s.Length > targetLength || padString.Length == 0)
  793. {
  794. return s;
  795. }
  796. targetLength = targetLength - s.Length;
  797. if (targetLength > padString.Length)
  798. {
  799. padString = string.Join("", Enumerable.Repeat(padString, (targetLength / padString.Length) + 1));
  800. }
  801. return padStart
  802. ? $"{padString.Substring(0, targetLength)}{s}"
  803. : $"{s}{padString.Substring(0, targetLength)}";
  804. }
  805. /// <summary>
  806. /// https://tc39.es/ecma262/#sec-string.prototype.startswith
  807. /// </summary>
  808. private JsValue StartsWith(JsValue thisObj, JsValue[] arguments)
  809. {
  810. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  811. var s = TypeConverter.ToJsString(thisObj);
  812. var searchString = arguments.At(0);
  813. if (ReferenceEquals(searchString, Null))
  814. {
  815. searchString = "null";
  816. }
  817. else
  818. {
  819. if (searchString.IsRegExp())
  820. {
  821. ExceptionHelper.ThrowTypeError(_realm);
  822. }
  823. }
  824. var searchStr = TypeConverter.ToString(searchString);
  825. var pos = TypeConverter.ToInt32(arguments.At(1));
  826. var len = s.Length;
  827. var start = System.Math.Min(System.Math.Max(pos, 0), len);
  828. return s.StartsWith(searchStr, start);
  829. }
  830. /// <summary>
  831. /// https://tc39.es/ecma262/#sec-string.prototype.endswith
  832. /// </summary>
  833. private JsValue EndsWith(JsValue thisObj, JsValue[] arguments)
  834. {
  835. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  836. var s = TypeConverter.ToJsString(thisObj);
  837. var searchString = arguments.At(0);
  838. if (ReferenceEquals(searchString, Null))
  839. {
  840. searchString = "null";
  841. }
  842. else
  843. {
  844. if (searchString.IsRegExp())
  845. {
  846. ExceptionHelper.ThrowTypeError(_realm);
  847. }
  848. }
  849. var searchStr = TypeConverter.ToString(searchString);
  850. var len = s.Length;
  851. var pos = TypeConverter.ToInt32(arguments.At(1, len));
  852. var end = System.Math.Min(System.Math.Max(pos, 0), len);
  853. return s.EndsWith(searchStr, end);
  854. }
  855. /// <summary>
  856. /// https://tc39.es/ecma262/#sec-string.prototype.includes
  857. /// </summary>
  858. private JsValue Includes(JsValue thisObj, JsValue[] arguments)
  859. {
  860. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  861. var s = TypeConverter.ToJsString(thisObj);
  862. var searchString = arguments.At(0);
  863. if (searchString.IsRegExp())
  864. {
  865. ExceptionHelper.ThrowTypeError(_realm, "First argument to String.prototype.includes must not be a regular expression");
  866. }
  867. var searchStr = TypeConverter.ToString(searchString);
  868. double pos = 0;
  869. if (arguments.Length > 1 && !arguments[1].IsUndefined())
  870. {
  871. pos = TypeConverter.ToInteger(arguments[1]);
  872. }
  873. if (searchStr.Length == 0)
  874. {
  875. return JsBoolean.True;
  876. }
  877. if (pos < 0)
  878. {
  879. pos = 0;
  880. }
  881. return s.IndexOf(searchStr, (int) pos) > -1;
  882. }
  883. private JsValue Normalize(JsValue thisObj, JsValue[] arguments)
  884. {
  885. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  886. var str = TypeConverter.ToString(thisObj);
  887. var param = arguments.At(0);
  888. var form = "NFC";
  889. if (!param.IsUndefined())
  890. {
  891. form = TypeConverter.ToString(param);
  892. }
  893. var nf = NormalizationForm.FormC;
  894. switch (form)
  895. {
  896. case "NFC":
  897. nf = NormalizationForm.FormC;
  898. break;
  899. case "NFD":
  900. nf = NormalizationForm.FormD;
  901. break;
  902. case "NFKC":
  903. nf = NormalizationForm.FormKC;
  904. break;
  905. case "NFKD":
  906. nf = NormalizationForm.FormKD;
  907. break;
  908. default:
  909. ExceptionHelper.ThrowRangeError(
  910. _realm,
  911. "The normalization form should be one of NFC, NFD, NFKC, NFKD.");
  912. break;
  913. }
  914. return str.Normalize(nf);
  915. }
  916. /// <summary>
  917. /// https://tc39.es/ecma262/#sec-string.prototype.repeat
  918. /// </summary>
  919. private JsValue Repeat(JsValue thisObj, JsValue[] arguments)
  920. {
  921. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  922. var s = TypeConverter.ToString(thisObj);
  923. var count = arguments.At(0);
  924. var n = TypeConverter.ToIntegerOrInfinity(count);
  925. if (n < 0 || double.IsPositiveInfinity(n))
  926. {
  927. ExceptionHelper.ThrowRangeError(_realm, "Invalid count value");
  928. }
  929. if (n == 0 || s.Length == 0)
  930. {
  931. return JsString.Empty;
  932. }
  933. if (s.Length == 1)
  934. {
  935. return new string(s[0], (int) n);
  936. }
  937. using var sb = StringBuilderPool.Rent();
  938. sb.Builder.EnsureCapacity((int) (n * s.Length));
  939. for (var i = 0; i < n; ++i)
  940. {
  941. sb.Builder.Append(s);
  942. }
  943. return sb.ToString();
  944. }
  945. }
  946. }