StringPrototype.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. using System;
  2. using System.Globalization;
  3. using System.Text;
  4. using Jint.Native.Array;
  5. using Jint.Native.Object;
  6. using Jint.Native.RegExp;
  7. using Jint.Runtime;
  8. using Jint.Runtime.Descriptors;
  9. using Jint.Runtime.Interop;
  10. namespace Jint.Native.String
  11. {
  12. /// <summary>
  13. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4
  14. /// </summary>
  15. public sealed class StringPrototype : StringInstance
  16. {
  17. private StringPrototype(Engine engine)
  18. : base(engine)
  19. {
  20. }
  21. public static StringPrototype CreatePrototypeObject(Engine engine, StringConstructor stringConstructor)
  22. {
  23. var obj = new StringPrototype(engine);
  24. obj.Prototype = engine.Object.PrototypeObject;
  25. obj.PrimitiveValue = "";
  26. obj.Extensible = true;
  27. obj.FastAddProperty("constructor", stringConstructor, true, false, true);
  28. return obj;
  29. }
  30. public void Configure()
  31. {
  32. FastAddProperty("toString", new ClrFunctionInstance<object, string>(Engine, ToStringString), true, false, true);
  33. FastAddProperty("valueOf", new ClrFunctionInstance<object, string>(Engine, ValueOf), true, false, true);
  34. FastAddProperty("charAt", new ClrFunctionInstance<object, object>(Engine, CharAt, 1), true, false, true);
  35. FastAddProperty("charCodeAt", new ClrFunctionInstance<object, object>(Engine, CharCodeAt, 1), true, false, true);
  36. FastAddProperty("concat", new ClrFunctionInstance<object, string>(Engine, Concat, 1), true, false, true);
  37. FastAddProperty("indexOf", new ClrFunctionInstance<object, double>(Engine, IndexOf, 1), true, false, true);
  38. FastAddProperty("lastIndexOf", new ClrFunctionInstance<object, double>(Engine, LastIndexOf, 1), true, false, true);
  39. FastAddProperty("localeCompare", new ClrFunctionInstance<object, double>(Engine, LocaleCompare), true, false, true);
  40. FastAddProperty("match", new ClrFunctionInstance<object, object>(Engine, Match, 1), true, false, true);
  41. FastAddProperty("replace", new ClrFunctionInstance<object, object>(Engine, Replace), true, false, true);
  42. FastAddProperty("search", new ClrFunctionInstance<object, double>(Engine, Search, 1), true, false, true);
  43. FastAddProperty("slice", new ClrFunctionInstance<object, string>(Engine, Slice, 2), true, false, true);
  44. FastAddProperty("split", new ClrFunctionInstance<object, ArrayInstance>(Engine, Split, 2), true, false, true);
  45. FastAddProperty("substring", new ClrFunctionInstance<object, string>(Engine, Substring, 2), true, false, true);
  46. FastAddProperty("toLowerCase", new ClrFunctionInstance<object, string>(Engine, ToLowerCase), true, false, true);
  47. FastAddProperty("toLocaleLowerCase", new ClrFunctionInstance<object, string>(Engine, ToLocaleLowerCase), true, false, true);
  48. FastAddProperty("toUpperCase", new ClrFunctionInstance<object, string>(Engine, ToUpperCase), true, false, true);
  49. FastAddProperty("toLocaleUpperCase", new ClrFunctionInstance<object, string>(Engine, ToLocaleUpperCase), true, false, true);
  50. FastAddProperty("trim", new ClrFunctionInstance<object, string>(Engine, Trim), true, false, true);
  51. }
  52. private string ToStringString(object thisObj, object[] arguments)
  53. {
  54. var s = TypeConverter.ToObject(Engine, thisObj) as StringInstance;
  55. if (s == null)
  56. {
  57. throw new JavaScriptException(Engine.TypeError);
  58. }
  59. return s.PrimitiveValue;
  60. }
  61. private string Trim(object thisObj, object[] arguments)
  62. {
  63. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  64. var s = TypeConverter.ToString(thisObj);
  65. return s.Trim();
  66. }
  67. private static string ToLocaleUpperCase(object thisObj, object[] arguments)
  68. {
  69. var s = TypeConverter.ToString(thisObj);
  70. return s.ToUpper();
  71. }
  72. private static string ToUpperCase(object thisObj, object[] arguments)
  73. {
  74. var s = TypeConverter.ToString(thisObj);
  75. return s.ToUpperInvariant();
  76. }
  77. private static string ToLocaleLowerCase(object thisObj, object[] arguments)
  78. {
  79. var s = TypeConverter.ToString(thisObj);
  80. return s.ToLower();
  81. }
  82. private static string ToLowerCase(object thisObj, object[] arguments)
  83. {
  84. var s = TypeConverter.ToString(thisObj);
  85. return s.ToLowerInvariant();
  86. }
  87. private string Substring(object thisObj, object[] arguments)
  88. {
  89. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  90. var s = TypeConverter.ToString(thisObj);
  91. var start = TypeConverter.ToNumber(arguments.At(0));
  92. var end = TypeConverter.ToNumber(arguments.At(1));
  93. if (double.IsNaN(start) || start < 0)
  94. {
  95. start = 0;
  96. }
  97. if (double.IsNaN(end) || end < 0)
  98. {
  99. end = 0;
  100. }
  101. var len = s.Length;
  102. var intStart = (int) TypeConverter.ToInteger(start);
  103. var intEnd = arguments.At(1) == Undefined.Instance ? len : (int) TypeConverter.ToInteger(end);
  104. var finalStart = System.Math.Min(len, System.Math.Max(intStart, 0));
  105. var finalEnd = System.Math.Min(len, System.Math.Max(intEnd, 0));
  106. var from = System.Math.Min(finalStart, finalEnd);
  107. var to = System.Math.Max(finalStart, finalEnd);
  108. return s.Substring(from, to - from);
  109. }
  110. private ArrayInstance Split(object thisObj, object[] arguments)
  111. {
  112. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  113. var s = TypeConverter.ToString(thisObj);
  114. var separator = arguments.At(0);
  115. var l = arguments.At(1);
  116. var a = (ArrayInstance) Engine.Array.Construct(Arguments.Empty);
  117. var lengthA = 0;
  118. var limit = l == Undefined.Instance ? UInt32.MaxValue : TypeConverter.ToUint32(l);
  119. var len = s.Length;
  120. var p = 0;
  121. if (limit == 0)
  122. {
  123. return a;
  124. }
  125. if (separator == Undefined.Instance)
  126. {
  127. return (ArrayInstance) Engine.Array.Construct(Arguments.From(s));
  128. }
  129. var rx = TypeConverter.ToObject(Engine, separator) as RegExpInstance;
  130. if (rx != null)
  131. {
  132. var match = rx.Value.Match(s, 0);
  133. int lastIndex = 0;
  134. int index = 0;
  135. while (match.Success && index < limit)
  136. {
  137. if (match.Length == 0 && (match.Index == 0 || match.Index == len || match.Index == lastIndex))
  138. {
  139. match = match.NextMatch();
  140. continue;
  141. }
  142. // Add the match results to the array.
  143. a.DefineOwnProperty(index++.ToString(), new DataDescriptor(s.Substring(lastIndex, match.Index - lastIndex)) {Writable = true, Enumerable = true,Configurable = true}, false);
  144. if (index >= limit)
  145. {
  146. return a;
  147. }
  148. lastIndex = match.Index + match.Length;
  149. for (int i = 1; i < match.Groups.Count; i++)
  150. {
  151. var group = match.Groups[i];
  152. object item = Undefined.Instance;
  153. if (group.Captures.Count > 0)
  154. {
  155. item = match.Groups[i].Value;
  156. }
  157. a.DefineOwnProperty(index++.ToString(), new DataDescriptor(item) { Writable = true, Enumerable = true, Configurable = true }, false);
  158. if (index >= limit)
  159. {
  160. return a;
  161. }
  162. }
  163. match = match.NextMatch();
  164. }
  165. return a;
  166. }
  167. else
  168. {
  169. var sep = TypeConverter.ToString(separator);
  170. var segments = s.Split(new [] { sep }, StringSplitOptions.None);
  171. for (int i = 0; i < segments.Length && i < limit; i++)
  172. {
  173. a.DefineOwnProperty(i.ToString(), new DataDescriptor(segments[i]) {Writable = true, Enumerable = true, Configurable=true} , false);
  174. }
  175. return a;
  176. }
  177. }
  178. private string Slice(object thisObj, object[] arguments)
  179. {
  180. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  181. var s = TypeConverter.ToString(thisObj);
  182. var start = TypeConverter.ToNumber(arguments.At(0));
  183. var end = TypeConverter.ToNumber(arguments.At(1));
  184. var len = s.Length;
  185. var intStart = (int)TypeConverter.ToInteger(start);
  186. var intEnd = arguments.At(1) == Undefined.Instance ? len : (int)TypeConverter.ToInteger(end);
  187. var from = intStart < 0 ? System.Math.Max(len + intStart, 0) : System.Math.Min(intStart, len);
  188. var to = intEnd < 0 ? System.Math.Max(len + intEnd, 0) : System.Math.Min(intEnd, len);
  189. var span = System.Math.Max(to - from, 0);
  190. return s.Substring(from, span);
  191. }
  192. private double Search(object thisObj, object[] arguments)
  193. {
  194. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  195. var s = TypeConverter.ToString(thisObj);
  196. var regex = arguments.At(0);
  197. var rx = TypeConverter.ToObject(Engine, regex) as RegExpInstance ?? (RegExpInstance)Engine.RegExp.Construct(new[] { regex });
  198. var match = rx.Value.Match(s);
  199. if (!match.Success)
  200. {
  201. return -1;
  202. }
  203. return match.Index;
  204. }
  205. private object Replace(object thisObj, object[] arguments)
  206. {
  207. throw new NotImplementedException();
  208. /*
  209. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  210. var s = TypeConverter.ToString(thisObj);
  211. var searchValue = arguments.At(0);
  212. var replaceValue = arguments.At(1);
  213. var rx = TypeConverter.ToObject(Engine, searchValue) as RegExpInstance;
  214. if (rx != null)
  215. {
  216. // Check if the replacement string contains any patterns.
  217. bool replaceTextContainsPattern = replaceText.IndexOf('$') >= 0;
  218. // Replace the input string with replaceText, recording the last match found.
  219. Match lastMatch = null;
  220. string result = rx.Value.Replace(s, match =>
  221. {
  222. lastMatch = match;
  223. // If there is no pattern, replace the pattern as is.
  224. if (replaceTextContainsPattern == false)
  225. return replaceText;
  226. // Patterns
  227. // $$ Inserts a "$".
  228. // $& Inserts the matched substring.
  229. // $` Inserts the portion of the string that precedes the matched substring.
  230. // $' Inserts the portion of the string that follows the matched substring.
  231. // $n or $nn Where n or nn are decimal digits, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object.
  232. var replacementBuilder = new System.Text.StringBuilder();
  233. for (int i = 0; i < replaceText.Length; i++)
  234. {
  235. char c = replaceText[i];
  236. if (c == '$' && i < replaceText.Length - 1)
  237. {
  238. c = replaceText[++i];
  239. if (c == '$')
  240. replacementBuilder.Append('$');
  241. else if (c == '&')
  242. replacementBuilder.Append(match.Value);
  243. else if (c == '`')
  244. replacementBuilder.Append(input.Substring(0, match.Index));
  245. else if (c == '\'')
  246. replacementBuilder.Append(input.Substring(match.Index + match.Length));
  247. else if (c >= '0' && c <= '9')
  248. {
  249. int matchNumber1 = c - '0';
  250. // The match number can be one or two digits long.
  251. int matchNumber2 = 0;
  252. if (i < replaceText.Length - 1 && replaceText[i + 1] >= '0' && replaceText[i + 1] <= '9')
  253. matchNumber2 = matchNumber1*10 + (replaceText[i + 1] - '0');
  254. // Try the two digit capture first.
  255. if (matchNumber2 > 0 && matchNumber2 < match.Groups.Count)
  256. {
  257. // Two digit capture replacement.
  258. replacementBuilder.Append(match.Groups[matchNumber2].Value);
  259. i++;
  260. }
  261. else if (matchNumber1 > 0 && matchNumber1 < match.Groups.Count)
  262. {
  263. // Single digit capture replacement.
  264. replacementBuilder.Append(match.Groups[matchNumber1].Value);
  265. }
  266. else
  267. {
  268. // Capture does not exist.
  269. replacementBuilder.Append('$');
  270. i--;
  271. }
  272. }
  273. else
  274. {
  275. // Unknown replacement pattern.
  276. replacementBuilder.Append('$');
  277. replacementBuilder.Append(c);
  278. }
  279. }
  280. else
  281. replacementBuilder.Append(c);
  282. }
  283. return replacementBuilder.ToString();
  284. }, this.Global == true ? -1 : 1);
  285. // Set the deprecated RegExp properties if at least one match was found.
  286. if (lastMatch != null)
  287. this.Engine.RegExp.SetDeprecatedProperties(input, lastMatch);
  288. return result;
  289. }
  290. else
  291. {
  292. var searchString = TypeConverter.ToString(searchValue);
  293. var m = 0;
  294. // Find the first occurrance of substr.
  295. int start = thisObject.IndexOf(substr, StringComparison.Ordinal);
  296. if (start == -1)
  297. return thisObject;
  298. int end = start + substr.Length;
  299. // Replace only the first match.
  300. var result = new System.Text.StringBuilder(thisObject.Length + (replaceText.Length - substr.Length));
  301. result.Append(thisObject, 0, start);
  302. result.Append(replaceText);
  303. result.Append(thisObject, end, thisObject.Length - end);
  304. return result.ToString();
  305. }
  306. * */
  307. }
  308. private object Match(object thisObj, object[] arguments)
  309. {
  310. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  311. var s = TypeConverter.ToString(thisObj);
  312. var regex = arguments.At(0);
  313. RegExpInstance rx = null;
  314. if (TypeConverter.GetType(regex) == Types.Object)
  315. {
  316. rx = regex as RegExpInstance;
  317. }
  318. rx = rx ?? (RegExpInstance) Engine.RegExp.Construct(new[] {regex});
  319. var global = (bool) rx.Get("global");
  320. if (!global)
  321. {
  322. return Engine.RegExp.PrototypeObject.Exec(rx, Arguments.From(s));
  323. }
  324. else
  325. {
  326. rx.Put("lastIndex", (double) 0, false);
  327. var a = Engine.Array.Construct(Arguments.Empty);
  328. double previousLastIndex = 0;
  329. var n = 0;
  330. var lastMatch = true;
  331. while (lastMatch)
  332. {
  333. var result = Engine.RegExp.PrototypeObject.Exec(rx, Arguments.From(s)) as ObjectInstance;
  334. if (result == null)
  335. {
  336. lastMatch = false;
  337. }
  338. else
  339. {
  340. var thisIndex = (double) rx.Get("lastIndex");
  341. if (thisIndex == previousLastIndex)
  342. {
  343. rx.Put("lastIndex", (double) thisIndex + 1, false);
  344. previousLastIndex = thisIndex;
  345. }
  346. var matchStr = result.Get("0");
  347. a.DefineOwnProperty(TypeConverter.ToString(n), new DataDescriptor(matchStr) { Writable = true, Enumerable = true, Configurable = true}, false);
  348. n++;
  349. }
  350. }
  351. if (n == 0)
  352. {
  353. return Null.Instance;
  354. }
  355. return a;
  356. }
  357. }
  358. private double LocaleCompare(object thisObj, object[] arguments)
  359. {
  360. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  361. var s = TypeConverter.ToString(thisObj);
  362. var that = TypeConverter.ToString(arguments.Length > 0 ? arguments[0] : Undefined.Instance);
  363. return string.CompareOrdinal(s, that);
  364. }
  365. private double LastIndexOf(object thisObj, object[] arguments)
  366. {
  367. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  368. var s = TypeConverter.ToString(thisObj);
  369. var searchStr = TypeConverter.ToString(arguments.At(0));
  370. double numPos = arguments.At(0) == Undefined.Instance ? double.NaN : TypeConverter.ToNumber(arguments.At(0));
  371. double pos = double.IsNaN(numPos) ? double.PositiveInfinity : TypeConverter.ToInteger(numPos);
  372. var len = s.Length;
  373. var start = System.Math.Min(len, System.Math.Max(pos, 0));
  374. var searchLen = searchStr.Length;
  375. return s.LastIndexOf(searchStr, len - (int) start, StringComparison.Ordinal);
  376. }
  377. private double IndexOf(object thisObj, object[] arguments)
  378. {
  379. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  380. var s = TypeConverter.ToString(thisObj);
  381. var searchStr = TypeConverter.ToString(arguments.Length > 0 ? arguments[0] : Undefined.Instance);
  382. double pos = 0;
  383. if (arguments.Length > 1 && arguments[1] != Undefined.Instance)
  384. {
  385. pos = TypeConverter.ToInteger(arguments[1]);
  386. }
  387. if (pos >= s.Length)
  388. {
  389. return -1;
  390. }
  391. if (pos < 0)
  392. {
  393. pos = 0;
  394. }
  395. return s.IndexOf(searchStr, (int) pos, StringComparison.Ordinal);
  396. }
  397. private string Concat(object thisObj, object[] arguments)
  398. {
  399. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  400. var s = TypeConverter.ToString(thisObj);
  401. var sb = new StringBuilder(s);
  402. for (int i = 0; i < arguments.Length; i++)
  403. {
  404. sb.Append(TypeConverter.ToString(arguments[i]));
  405. }
  406. return sb.ToString();
  407. }
  408. private object CharCodeAt(object thisObj, object[] arguments)
  409. {
  410. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  411. object pos = arguments.Length > 0 ? arguments[0] : 0;
  412. var s = TypeConverter.ToString(thisObj);
  413. var position = (int)TypeConverter.ToInteger(pos);
  414. if (position < 0 || position >= s.Length)
  415. {
  416. return double.NaN;
  417. }
  418. return (uint)s[position];
  419. }
  420. private object CharAt(object thisObj, object[] arguments)
  421. {
  422. TypeConverter.CheckObjectCoercible(Engine, thisObj);
  423. var s = TypeConverter.ToString(thisObj);
  424. var position = TypeConverter.ToInteger(arguments.At(0));
  425. var size = s.Length;
  426. if (position >= size || position < 0)
  427. {
  428. return "";
  429. }
  430. return s[(int) position].ToString();
  431. }
  432. private string ValueOf(object thisObj, object[] arguments)
  433. {
  434. var s = thisObj as StringInstance;
  435. if (s == null)
  436. {
  437. throw new JavaScriptException(Engine.TypeError);
  438. }
  439. return s.PrimitiveValue;
  440. }
  441. }
  442. }