StringPrototype.cs 43 KB

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