JsonParser.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. using System.Buffers;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Globalization;
  4. using System.Runtime.CompilerServices;
  5. using System.Text;
  6. using Esprima;
  7. using Jint.Native.Object;
  8. using Jint.Pooling;
  9. using Jint.Runtime;
  10. namespace Jint.Native.Json
  11. {
  12. public sealed class JsonParser
  13. {
  14. private readonly Engine _engine;
  15. private readonly int _maxDepth;
  16. /// <summary>
  17. /// Creates a new parser using the recursion depth specified in <see cref="JsonOptions.MaxParseDepth"/>.
  18. /// </summary>
  19. public JsonParser(Engine engine)
  20. : this(engine, engine.Options.Json.MaxParseDepth)
  21. {
  22. }
  23. public JsonParser(Engine engine, int maxDepth)
  24. {
  25. if (maxDepth < 0)
  26. {
  27. throw new ArgumentOutOfRangeException(nameof(maxDepth), $"Max depth must be greater or equal to zero");
  28. }
  29. _maxDepth = maxDepth;
  30. _engine = engine;
  31. // Two tokens are "live" during parsing,
  32. // lookahead and the current one on the stack
  33. // To add a safety boundary to not overwrite
  34. // "still in use" stuff, the buffer contains 5
  35. // instead of 2 tokens.
  36. _tokenBuffer = new Token[5];
  37. for (int i = 0; i < _tokenBuffer.Length; i++)
  38. {
  39. _tokenBuffer[i] = new Token();
  40. }
  41. _tokenBufferIndex = 0;
  42. }
  43. private int _index; // position in the stream
  44. private int _length; // length of the stream
  45. private Token _lookahead = null!;
  46. private string _source = null!;
  47. private readonly Token[] _tokenBuffer;
  48. private int _tokenBufferIndex;
  49. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  50. private static bool IsDecimalDigit(char ch)
  51. {
  52. // * For characters, which are before the '0', the equation will be negative and then wrap
  53. // around because of the unsigned short cast
  54. // * For characters, which are after the '9', the equation will be positive, but > 9
  55. // * For digits, the equation will be between int(0) and int(9)
  56. return ((uint) (ch - '0')) <= 9;
  57. }
  58. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  59. private static bool IsLowerCaseHexAlpha(char ch)
  60. {
  61. return ((uint) (ch - 'a')) <= 5;
  62. }
  63. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  64. private static bool IsUpperCaseHexAlpha(char ch)
  65. {
  66. return ((uint) (ch - 'A')) <= 5;
  67. }
  68. private static bool IsHexDigit(char ch)
  69. {
  70. return
  71. IsDecimalDigit(ch) ||
  72. IsLowerCaseHexAlpha(ch) ||
  73. IsUpperCaseHexAlpha(ch)
  74. ;
  75. }
  76. private static bool IsWhiteSpace(char ch)
  77. {
  78. return (ch == ' ') ||
  79. (ch == '\t') ||
  80. (ch == '\n') ||
  81. (ch == '\r');
  82. }
  83. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  84. private static bool IsLineTerminator(char ch)
  85. {
  86. return (ch == 10) || (ch == 13) || (ch == 0x2028) || (ch == 0x2029);
  87. }
  88. private char ScanHexEscape()
  89. {
  90. int code = char.MinValue;
  91. for (int i = 0; i < 4; ++i)
  92. {
  93. if (_index < _length + 1 && IsHexDigit(_source[_index]))
  94. {
  95. char ch = _source[_index++];
  96. code = code * 16 + "0123456789abcdef".IndexOf(ch.ToString(), StringComparison.OrdinalIgnoreCase);
  97. }
  98. else
  99. {
  100. ThrowError(_index, Messages.ExpectedHexadecimalDigit);
  101. }
  102. }
  103. return (char) code;
  104. }
  105. private char ReadToNextSignificantCharacter()
  106. {
  107. char result = _index < _length ? _source[_index] : char.MinValue;
  108. while (IsWhiteSpace(result))
  109. {
  110. if ((++_index) >= _length)
  111. {
  112. return char.MinValue;
  113. }
  114. result = _source[_index];
  115. }
  116. return result;
  117. }
  118. private Token CreateToken(Tokens type, string text, char firstCharacter, JsValue value, in TextRange range)
  119. {
  120. Token result = _tokenBuffer[_tokenBufferIndex++];
  121. if (_tokenBufferIndex >= _tokenBuffer.Length)
  122. {
  123. _tokenBufferIndex = 0;
  124. }
  125. result.Type = type;
  126. result.Text = text;
  127. result.FirstCharacter = firstCharacter;
  128. result.Value = value;
  129. result.Range = range;
  130. return result;
  131. }
  132. private Token ScanPunctuator()
  133. {
  134. int start = _index;
  135. char code = start < _source.Length ? _source[_index] : char.MinValue;
  136. string value = ScanPunctuatorValue(start, code);
  137. ++_index;
  138. return CreateToken(Tokens.Punctuator, value, code, JsValue.Undefined, new TextRange(start, _index));
  139. }
  140. private string ScanPunctuatorValue(int start, char code)
  141. {
  142. switch (code)
  143. {
  144. case '.': return ".";
  145. case ',': return ",";
  146. case '{': return "{";
  147. case '}': return "}";
  148. case '[': return "[";
  149. case ']': return "]";
  150. case ':': return ":";
  151. default:
  152. ThrowError(start, Messages.UnexpectedToken, code);
  153. return null!;
  154. }
  155. }
  156. private Token ScanNumericLiteral(ref State state)
  157. {
  158. var sb = state.TokenBuffer;
  159. var start = _index;
  160. var ch = _source.CharCodeAt(_index);
  161. var canBeInteger = true;
  162. // Number start with a -
  163. if (ch == '-')
  164. {
  165. sb.Append(ch);
  166. ch = _source.CharCodeAt(++_index);
  167. }
  168. if (ch != '.')
  169. {
  170. var firstCharacter = ch;
  171. sb.Append(ch);
  172. ch = _source.CharCodeAt(++_index);
  173. // Hex number starts with '0x'.
  174. // Octal number starts with '0'.
  175. if (sb.Length == 1 && firstCharacter == '0')
  176. {
  177. canBeInteger = false;
  178. // decimal number starts with '0' such as '09' is illegal.
  179. if (ch > 0 && IsDecimalDigit(ch))
  180. {
  181. ThrowError(_index, Messages.UnexpectedToken, ch);
  182. }
  183. }
  184. while (IsDecimalDigit((ch = _source.CharCodeAt(_index))))
  185. {
  186. sb.Append(ch);
  187. _index++;
  188. }
  189. }
  190. if (ch == '.')
  191. {
  192. canBeInteger = false;
  193. sb.Append(ch);
  194. _index++;
  195. while (IsDecimalDigit((ch = _source.CharCodeAt(_index))))
  196. {
  197. sb.Append(ch);
  198. _index++;
  199. }
  200. }
  201. if (ch is 'e' or 'E')
  202. {
  203. canBeInteger = false;
  204. sb.Append(ch);
  205. ch = _source.CharCodeAt(++_index);
  206. if (ch is '+' or '-')
  207. {
  208. sb.Append(ch);
  209. ch = _source.CharCodeAt(++_index);
  210. }
  211. if (IsDecimalDigit(ch))
  212. {
  213. while (IsDecimalDigit(ch = _source.CharCodeAt(_index)))
  214. {
  215. sb.Append(ch);
  216. _index++;
  217. }
  218. }
  219. else
  220. {
  221. ThrowError(_index, Messages.UnexpectedToken, _source.CharCodeAt(_index));
  222. }
  223. }
  224. var number = sb.ToString();
  225. sb.Clear();
  226. JsNumber value;
  227. if (canBeInteger && long.TryParse(number, out var longResult) && longResult != -0)
  228. {
  229. value = JsNumber.Create(longResult);
  230. }
  231. else
  232. {
  233. value = new JsNumber(double.Parse(number, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture));
  234. }
  235. return CreateToken(Tokens.Number, number, '\0', value, new TextRange(start, _index));
  236. }
  237. private Token ScanBooleanLiteral()
  238. {
  239. var start = _index;
  240. if (ConsumeMatch("true"))
  241. {
  242. return CreateToken(Tokens.BooleanLiteral, "true", '\t', JsBoolean.True, new TextRange(start, _index));
  243. }
  244. if (ConsumeMatch("false"))
  245. {
  246. return CreateToken(Tokens.BooleanLiteral, "false", '\f', JsBoolean.False, new TextRange(start, _index));
  247. }
  248. ThrowError(start, Messages.UnexpectedTokenIllegal);
  249. return null!;
  250. }
  251. private bool ConsumeMatch(string text)
  252. {
  253. var start = _index;
  254. var length = text.Length;
  255. if (start + length - 1 < _source.Length && _source.AsSpan(start, length).SequenceEqual(text.AsSpan()))
  256. {
  257. _index += length;
  258. return true;
  259. }
  260. return false;
  261. }
  262. private Token ScanNullLiteral()
  263. {
  264. int start = _index;
  265. if (ConsumeMatch("null"))
  266. {
  267. return CreateToken(Tokens.NullLiteral, "null", 'n', JsValue.Null, new TextRange(start, _index));
  268. }
  269. ThrowError(start, Messages.UnexpectedTokenIllegal);
  270. return null!;
  271. }
  272. private Token ScanStringLiteral(ref State state)
  273. {
  274. char quote = _source[_index];
  275. int start = _index;
  276. ++_index;
  277. var sb = state.TokenBuffer;
  278. while (_index < _length)
  279. {
  280. char ch = _source[_index++];
  281. if (ch == quote)
  282. {
  283. quote = char.MinValue;
  284. break;
  285. }
  286. if (ch <= 31)
  287. {
  288. ThrowError(_index - 1, Messages.InvalidCharacter);
  289. }
  290. if (ch == '\\')
  291. {
  292. ch = _source.CharCodeAt(_index++);
  293. switch (ch)
  294. {
  295. case '"':
  296. sb.Append('"');
  297. break;
  298. case '\\':
  299. sb.Append('\\');
  300. break;
  301. case '/':
  302. sb.Append('/');
  303. break;
  304. case 'n':
  305. sb.Append('\n');
  306. break;
  307. case 'r':
  308. sb.Append('\r');
  309. break;
  310. case 't':
  311. sb.Append('\t');
  312. break;
  313. case 'u':
  314. sb.Append(ScanHexEscape());
  315. break;
  316. case 'b':
  317. sb.Append('\b');
  318. break;
  319. case 'f':
  320. sb.Append('\f');
  321. break;
  322. default:
  323. ThrowError(_index - 1, Messages.UnexpectedToken, ch);
  324. break;
  325. }
  326. }
  327. else if (IsLineTerminator(ch))
  328. {
  329. break;
  330. }
  331. else
  332. {
  333. sb.Append(ch);
  334. }
  335. }
  336. if (quote != 0)
  337. {
  338. // unterminated string literal
  339. ThrowError(_index, Messages.UnexpectedEOS);
  340. }
  341. string value = sb.ToString();
  342. sb.Clear();
  343. return CreateToken(Tokens.String, value, '\"', new JsString(value), new TextRange(start, _index));
  344. }
  345. private Token Advance(ref State state)
  346. {
  347. char ch = ReadToNextSignificantCharacter();
  348. if (ch == char.MinValue)
  349. {
  350. return CreateToken(Tokens.EOF, string.Empty, '\0', JsValue.Undefined, new TextRange(_index, _index));
  351. }
  352. // String literal starts with double quote (#34).
  353. // Single quote (#39) are not allowed in JSON.
  354. if (ch == '"')
  355. {
  356. return ScanStringLiteral(ref state);
  357. }
  358. if (ch == '-') // Negative Number
  359. {
  360. if (IsDecimalDigit(_source.CharCodeAt(_index + 1)))
  361. {
  362. return ScanNumericLiteral(ref state);
  363. }
  364. return ScanPunctuator();
  365. }
  366. if (IsDecimalDigit(ch))
  367. {
  368. return ScanNumericLiteral(ref state);
  369. }
  370. if (ch == 't' || ch == 'f')
  371. {
  372. return ScanBooleanLiteral();
  373. }
  374. if (ch == 'n')
  375. {
  376. return ScanNullLiteral();
  377. }
  378. return ScanPunctuator();
  379. }
  380. private Token Lex(ref State state)
  381. {
  382. Token token = _lookahead;
  383. _index = token.Range.End;
  384. _lookahead = Advance(ref state);
  385. _index = token.Range.End;
  386. return token;
  387. }
  388. private void Peek(ref State state)
  389. {
  390. int pos = _index;
  391. _lookahead = Advance(ref state);
  392. _index = pos;
  393. }
  394. [DoesNotReturn]
  395. private void ThrowDepthLimitReached(Token token)
  396. {
  397. ThrowError(token.Range.Start, Messages.MaxDepthLevelReached);
  398. }
  399. [DoesNotReturn]
  400. private void ThrowError(Token token, string messageFormat, params object[] arguments)
  401. {
  402. ThrowError(token.Range.Start, messageFormat, arguments);
  403. }
  404. [DoesNotReturn]
  405. private void ThrowError(int position, string messageFormat, params object[] arguments)
  406. {
  407. var msg = string.Format(CultureInfo.InvariantCulture, messageFormat, arguments);
  408. ExceptionHelper.ThrowSyntaxError(_engine.Realm, $"{msg} at position {position}");
  409. }
  410. // Throw an exception because of the token.
  411. private void ThrowUnexpected(Token token)
  412. {
  413. if (token.Type == Tokens.EOF)
  414. {
  415. ThrowError(token, Messages.UnexpectedEOS);
  416. }
  417. if (token.Type == Tokens.Number)
  418. {
  419. ThrowError(token, Messages.UnexpectedNumber);
  420. }
  421. if (token.Type == Tokens.String)
  422. {
  423. ThrowError(token, Messages.UnexpectedString);
  424. }
  425. // BooleanLiteral, NullLiteral, or Punctuator.
  426. ThrowError(token, Messages.UnexpectedToken, token.Text);
  427. }
  428. // Expect the next token to match the specified punctuator.
  429. // If not, an exception will be thrown.
  430. private void Expect(ref State state, char value)
  431. {
  432. Token token = Lex(ref state);
  433. if (token.Type != Tokens.Punctuator || value != token.FirstCharacter)
  434. {
  435. ThrowUnexpected(token);
  436. }
  437. }
  438. // Return true if the next token matches the specified punctuator.
  439. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  440. public bool Match(char value)
  441. {
  442. return _lookahead.Type == Tokens.Punctuator && value == _lookahead.FirstCharacter;
  443. }
  444. private ObjectInstance ParseJsonArray(ref State state)
  445. {
  446. if ((++state.CurrentDepth) > _maxDepth)
  447. {
  448. ThrowDepthLimitReached(_lookahead);
  449. }
  450. /*
  451. To speed up performance, the list allocation is deferred.
  452. First the elements are stored within an array received
  453. from the .NET array pool.
  454. If a list contains less elements that the size that array,
  455. a Jint array is constructed with the values stored in that
  456. array.
  457. When the number of elements exceed the buffer size,
  458. The elements-array gets created and filled with the content
  459. of the array. The array will then turn into an
  460. intermediate buffer which gets flushed to the list
  461. when its full.
  462. */
  463. List<JsValue>? elements = null;
  464. Expect(ref state, '[');
  465. int bufferIndex = 0;
  466. JsArray? result = null;
  467. JsValue[] buffer = ArrayPool<JsValue>.Shared.Rent(16);
  468. try
  469. {
  470. while (!Match(']'))
  471. {
  472. buffer[bufferIndex++] = ParseJsonValue(ref state);
  473. if (!Match(']'))
  474. {
  475. Expect(ref state, ',');
  476. }
  477. if (bufferIndex >= buffer.Length)
  478. {
  479. if (elements is null)
  480. {
  481. elements = new List<JsValue>(buffer);
  482. }
  483. else
  484. {
  485. elements.AddRange(buffer);
  486. }
  487. bufferIndex = 0;
  488. }
  489. }
  490. // BufferIndex = 0 has two meanings
  491. // * Empty JSON array (elements will be null)
  492. // * The buffer array has just been flushed (elements will NOT be null)
  493. if (bufferIndex > 0)
  494. {
  495. if (elements is null)
  496. {
  497. // No element list has been created, all values did fit into the array.
  498. // The Jint-Array can get constructed from that array.
  499. var data = new JsValue[bufferIndex];
  500. System.Array.Copy(buffer, data, length: bufferIndex);
  501. result = new JsArray(_engine, data);
  502. }
  503. else
  504. {
  505. // An element list has been created. Flush the
  506. // remaining added items within the array to that list.
  507. for (var i = 0; i < bufferIndex; ++i)
  508. {
  509. elements.Add(buffer[i]);
  510. }
  511. }
  512. }
  513. else if (elements is null)
  514. {
  515. // the JSON array did not have any elements
  516. // aka: []
  517. result = new JsArray(_engine);
  518. }
  519. }
  520. finally
  521. {
  522. ArrayPool<JsValue>.Shared.Return(buffer);
  523. }
  524. Expect(ref state, ']');
  525. state.CurrentDepth--;
  526. return result ?? new JsArray(_engine, elements!.ToArray());
  527. }
  528. private ObjectInstance ParseJsonObject(ref State state)
  529. {
  530. if ((++state.CurrentDepth) > _maxDepth)
  531. {
  532. ThrowDepthLimitReached(_lookahead);
  533. }
  534. Expect(ref state, '{');
  535. var obj = new JsObject(_engine);
  536. while (!Match('}'))
  537. {
  538. Tokens type = _lookahead.Type;
  539. if (type != Tokens.String)
  540. {
  541. ThrowUnexpected(Lex(ref state));
  542. }
  543. var nameToken = Lex(ref state);
  544. var name = nameToken.Text;
  545. if (PropertyNameContainsInvalidCharacters(name))
  546. {
  547. ThrowError(nameToken, Messages.InvalidCharacter);
  548. }
  549. Expect(ref state, ':');
  550. var value = ParseJsonValue(ref state);
  551. obj.FastSetDataProperty(name, value);
  552. if (!Match('}'))
  553. {
  554. Expect(ref state, ',');
  555. }
  556. }
  557. Expect(ref state, '}');
  558. state.CurrentDepth--;
  559. return obj;
  560. }
  561. private static bool PropertyNameContainsInvalidCharacters(string propertyName)
  562. {
  563. const char max = (char) 31;
  564. foreach (var c in propertyName)
  565. {
  566. if (c != '\t' && c <= max)
  567. {
  568. return true;
  569. }
  570. }
  571. return false;
  572. }
  573. /// <summary>
  574. /// Optimization.
  575. /// By calling Lex().Value for each type, we parse the token twice.
  576. /// It was already parsed by the peek() method.
  577. /// _lookahead.Value already contain the value.
  578. /// </summary>
  579. private JsValue ParseJsonValue(ref State state)
  580. {
  581. Tokens type = _lookahead.Type;
  582. switch (type)
  583. {
  584. case Tokens.NullLiteral:
  585. case Tokens.BooleanLiteral:
  586. case Tokens.String:
  587. case Tokens.Number:
  588. return Lex(ref state).Value;
  589. case Tokens.Punctuator:
  590. if (_lookahead.FirstCharacter == '[')
  591. {
  592. return ParseJsonArray(ref state);
  593. }
  594. if (_lookahead.FirstCharacter == '{')
  595. {
  596. return ParseJsonObject(ref state);
  597. }
  598. ThrowUnexpected(Lex(ref state));
  599. break;
  600. }
  601. ThrowUnexpected(Lex(ref state));
  602. // can't be reached
  603. return JsValue.Null;
  604. }
  605. public JsValue Parse(string code)
  606. {
  607. return Parse(code, null);
  608. }
  609. public JsValue Parse(string code, ParserOptions? options)
  610. {
  611. _source = code;
  612. _index = 0;
  613. _length = _source.Length;
  614. _lookahead = null!;
  615. using var wrapper = StringBuilderPool.Rent();
  616. State state = new State(wrapper.Builder);
  617. Peek(ref state);
  618. JsValue jsv = ParseJsonValue(ref state);
  619. Peek(ref state);
  620. if (_lookahead.Type != Tokens.EOF)
  621. {
  622. ThrowError(_lookahead, Messages.UnexpectedToken, _lookahead.Text);
  623. }
  624. return jsv;
  625. }
  626. private ref struct State
  627. {
  628. public State(StringBuilder tokenBuffer)
  629. {
  630. TokenBuffer = tokenBuffer;
  631. CurrentDepth = 0;
  632. }
  633. /// <summary>
  634. /// StringBuilder instance which can be used to collect
  635. /// characters into a single string. Must only be used
  636. /// when no child-parser gets called. Must be cleared
  637. /// after usage.
  638. /// </summary>
  639. public StringBuilder TokenBuffer { get; }
  640. /// <summary>
  641. /// The current recursion depth
  642. /// </summary>
  643. public int CurrentDepth { get; set; }
  644. }
  645. private enum Tokens
  646. {
  647. NullLiteral,
  648. BooleanLiteral,
  649. String,
  650. Number,
  651. Punctuator,
  652. EOF,
  653. };
  654. private sealed class Token
  655. {
  656. public Tokens Type;
  657. public char FirstCharacter;
  658. public JsValue Value = JsValue.Undefined;
  659. public string Text = null!;
  660. public TextRange Range;
  661. }
  662. private readonly struct TextRange
  663. {
  664. public TextRange(int start, int end)
  665. {
  666. Start = start;
  667. End = end;
  668. }
  669. public int Start { get; }
  670. public int End { get; }
  671. }
  672. static class Messages
  673. {
  674. public const string InvalidCharacter = "Invalid character in JSON";
  675. public const string ExpectedHexadecimalDigit = "Expected hexadecimal digit in JSON";
  676. public const string UnexpectedToken = "Unexpected token '{0}' in JSON";
  677. public const string UnexpectedTokenIllegal = "Unexpected token ILLEGAL in JSON";
  678. public const string UnexpectedNumber = "Unexpected number in JSON";
  679. public const string UnexpectedString = "Unexpected string in JSON";
  680. public const string UnexpectedEOS = "Unexpected end of JSON input";
  681. public const string MaxDepthLevelReached = "Max. depth level of JSON reached";
  682. };
  683. }
  684. internal static class StringExtensions
  685. {
  686. public static char CharCodeAt(this string source, int index)
  687. {
  688. if (index > source.Length - 1)
  689. {
  690. // char.MinValue is used as the null value
  691. return char.MinValue;
  692. }
  693. return source[index];
  694. }
  695. }
  696. }