public async Task <(string?errorMessage, JsonValue?value)> TryParseAsync(TextReader stream, CancellationToken token) { var buffer = SmallBufferCache.Acquire(4); var count = await stream.ReadBlockAsync(buffer, 0, 4); if (count < 4) { SmallBufferCache.Release(buffer); return("Unexpected end of input.", null); } JsonValue?value = null; string? errorMessage = null; if ((buffer[0] == 'n' || buffer[0] == 'N') && (buffer[1] == 'u' || buffer[1] == 'U') && (buffer[2] == 'l' || buffer[2] == 'L') && (buffer[3] == 'l' || buffer[3] == 'L')) { value = JsonValue.Null; } else { errorMessage = $"Value not recognized: '{new string(buffer).Trim('\0')}'."; } SmallBufferCache.Release(buffer); return(errorMessage, value); }
public string TryParse(TextReader stream, out JsonValue value) { value = null; int count; var current = (char)stream.Peek(); if (current == 't' || current == 'T') { count = 4; } else { count = 5; } var buffer = SmallBufferCache.Acquire(count); var charsRead = stream.ReadBlock(buffer, 0, count); if (charsRead != count) { SmallBufferCache.Release(buffer); return(_unexpectedEndOfInput); } string errorMessage = null; if (count == 4) { if ((buffer[0] == 't' || buffer[0] == 'T') && (buffer[1] == 'r' || buffer[1] == 'R') && (buffer[2] == 'u' || buffer[2] == 'U') && (buffer[3] == 'e' || buffer[3] == 'E')) { value = true; } else { errorMessage = $"Value not recognized: '{new string(buffer, 0, count)}'."; } } else { if ((buffer[0] == 'f' || buffer[0] == 'F') && (buffer[1] == 'a' || buffer[1] == 'A') && (buffer[2] == 'l' || buffer[2] == 'L') && (buffer[3] == 's' || buffer[3] == 'S') && (buffer[4] == 'e' || buffer[4] == 'E')) { value = false; } else { errorMessage = $"Value not recognized: '{new string(buffer, 0, count)}'."; } } SmallBufferCache.Release(buffer); return(errorMessage); }
public bool TryParse(TextReader stream, [NotNullWhen(true)] out JsonValue?value, [NotNullWhen(false)] out string?errorMessage) { value = null; var buffer = SmallBufferCache.Acquire(4); var charsRead = stream.ReadBlock(buffer, 0, 4); if (charsRead != 4) { SmallBufferCache.Release(buffer); errorMessage = _unexpectedEndOfInput; return(false); } if ((buffer[0] == 'n' || buffer[0] == 'N') && (buffer[1] == 'u' || buffer[1] == 'U') && (buffer[2] == 'l' || buffer[2] == 'L') && (buffer[3] == 'l' || buffer[3] == 'L')) { value = JsonValue.Null; } else { errorMessage = $"Value not recognized: '{new string(buffer).Trim('\0')}'."; return(false); } SmallBufferCache.Release(buffer); errorMessage = null; return(true); }
public string TryParse(TextReader stream, out JsonValue value) { value = null; var buffer = SmallBufferCache.Acquire(4); var charsRead = stream.ReadBlock(buffer, 0, 4); if (charsRead != 4) { SmallBufferCache.Release(buffer); return(_unexpectedEndOfInput); } string errorMessage = null; if ((buffer[0] == 'n' || buffer[0] == 'N') && (buffer[1] == 'u' || buffer[1] == 'U') && (buffer[2] == 'l' || buffer[2] == 'L') && (buffer[3] == 'l' || buffer[3] == 'L')) { value = JsonValue.Null; } else { errorMessage = $"Value not recognized: '{new string(buffer).Trim('\0')}'."; } SmallBufferCache.Release(buffer); return(errorMessage); }
public async Task <(string errorMessage, JsonValue value)> TryParseAsync(TextReader stream, CancellationToken token) { var scratch = SmallBufferCache.Acquire(4); await stream.TryRead(scratch, 0, 1, token); // waste the '"' System.Diagnostics.Debug.Assert(scratch[0] == '"'); var builder = StringBuilderCache.Acquire(); var complete = false; bool mustInterpret = false; char c; while (stream.Peek() != -1) { c = (char)stream.Peek(); if (c == '\\') { mustInterpret = true; break; } await stream.TryRead(scratch, 0, 1, token); // eat the character if (c == '"') { complete = true; break; } builder.Append(c); } // if there are not any escape sequences--most of a JSON's strings--just // return the string as-is. if (!mustInterpret) { SmallBufferCache.Release(scratch); string errorMessage = null; JsonValue value = null; if (!complete) { errorMessage = "Could not find end of string value."; } else { value = StringBuilderCache.GetStringAndRelease(builder); } return(errorMessage, value); } // NOTE: TryParseInterpretedString is responsible for releasing builder // NOTE: TryParseInterpretedString assumes stream is sitting at the '\\' // NOTE: TryParseInterpretedString assumes scratch can hold at least 4 chars return(await _TryParseInterpretedStringAsync(builder, stream, scratch)); }
public async Task <(string errorMessage, JsonValue value)> TryParseAsync(TextReader stream, CancellationToken token) { var buffer = SmallBufferCache.Acquire(5); var count = await stream.ReadAsync(buffer, 0, 4); if (count < 4) { SmallBufferCache.Release(buffer); return(_unexpectedEndOfInput, null); } if (token.IsCancellationRequested) { SmallBufferCache.Release(buffer); return("Parsing incomplete. The task was cancelled.", null); } JsonValue value = null; string errorMessage = null; if ((buffer[0] == 't' || buffer[0] == 'T') && (buffer[1] == 'r' || buffer[1] == 'R') && (buffer[2] == 'u' || buffer[2] == 'U') && (buffer[3] == 'e' || buffer[3] == 'E')) { value = true; } else if ((buffer[0] == 'f' || buffer[0] == 'F') && (buffer[1] == 'a' || buffer[1] == 'A') && (buffer[2] == 'l' || buffer[2] == 'L') && (buffer[3] == 's' || buffer[3] == 'S')) { if (await stream.TryRead(buffer, 4, 1, token)) { if (buffer[4] == 'e' || buffer[4] == 'E') { value = false; } else { errorMessage = $"Value not recognized: 'fals{buffer[4]}'."; } } else { errorMessage = "Unexpected end of input."; } } else { errorMessage = $"Value not recognized: '{new string(buffer, 0, count)}'."; } SmallBufferCache.Release(buffer); return(errorMessage, value); }
public async Task <(string?errorMessage, JsonValue?value)> TryParseAsync(TextReader stream, CancellationToken token) { var buffer = StringBuilderCache.Acquire(); var scratch = SmallBufferCache.Acquire(1); string?errorMessage = null; while (stream.Peek() != -1) { if (token.IsCancellationRequested) { errorMessage = "Parsing incomplete. The task was cancelled."; break; } var c = (char)stream.Peek(); if (char.IsWhiteSpace(c) || c == ',' || c == ']' || c == '}') { break; } await stream.TryRead(scratch, 0, 1, token); // eat the character if (!_IsNumberChar(c)) { errorMessage = "Expected ',', ']', or '}'."; break; } buffer.Append(c); } SmallBufferCache.Release(scratch); if (errorMessage != null) { StringBuilderCache.Release(buffer); return(errorMessage, null); } var result = StringBuilderCache.GetStringAndRelease(buffer); if (!double.TryParse(result, NumberStyles.Any, CultureInfo.InvariantCulture, out double dbl)) { return($"Value not recognized: '{result}'", null); } return(null, dbl); }
public async Task <(string errorMessage, JsonValue value)> TryParseAsync(TextReader stream, CancellationToken token) { System.Diagnostics.Debug.Assert(stream.Peek() == '['); var scratch = SmallBufferCache.Acquire(1); var array = new JsonArray(); bool complete = false; char c; string errorMessage = null; while (stream.Peek() != -1) { if (token.IsCancellationRequested) { errorMessage = "Parsing incomplete. The task was cancelled."; break; } await stream.TryRead(scratch, 0, 1, token); // waste the '[' or ',' (errorMessage, c) = await stream.SkipWhiteSpaceAsync(scratch); if (errorMessage != null) { break; } // check for empty array if (c == ']') { if (array.Count == 0) { complete = true; await stream.TryRead(scratch, 0, 1, token); // waste the ']' break; } else { errorMessage = "Expected value."; break; } } // get value JsonValue item; (errorMessage, item) = await JsonParser.TryParseAsync(stream, token); array.Add(item); if (errorMessage != null) { break; } (errorMessage, c) = await stream.SkipWhiteSpaceAsync(scratch); if (errorMessage != null) { break; } // check for end or separator if (c == ']') { complete = true; await stream.TryRead(scratch, 0, 1, token); // waste the ']' break; } if (c != ',') { errorMessage = "Expected ','."; break; } } if (!complete) { errorMessage = "Unterminated array (missing ']')"; } SmallBufferCache.Release(scratch); return(errorMessage, array); }
public async Task <(string?errorMessage, JsonValue?value)> TryParseAsync(TextReader stream, CancellationToken token) { bool complete = false; var obj = new JsonObject(); var scratch = SmallBufferCache.Acquire(1); string?errorMessage = null; while (stream.Peek() != -1) { if (token.IsCancellationRequested) { errorMessage = "Parsing incomplete. The task was cancelled."; break; } await stream.TryRead(scratch, 0, 1, token); // waste the '{' or ',' char c; (errorMessage, c) = await stream.SkipWhiteSpaceAsync(scratch); if (errorMessage != null) { break; } // check for empty object if (c == '}') { if (obj.Count == 0) { complete = true; await stream.TryRead(scratch, 0, 1, token); // waste the '}' break; } else { errorMessage = "Expected key."; break; } } // get key (errorMessage, c) = await stream.SkipWhiteSpaceAsync(scratch); if (errorMessage != null) { break; } if (c != '\"') { errorMessage = "Expected key."; break; } JsonValue?item; (errorMessage, item) = await JsonParser.TryParseAsync(stream, token); if (errorMessage != null) { break; } var key = item !.String; // check for colon (errorMessage, c) = await stream.SkipWhiteSpaceAsync(scratch); if (errorMessage != null) { break; } if (c != ':') { obj.Add(key, null !); errorMessage = "Expected ':'."; break; } await stream.TryRead(scratch, 0, 1, token); // waste the ':' // get value (whitespace is removed in Parse) (errorMessage, item) = await JsonParser.TryParseAsync(stream, token); obj.Add(key, item !); if (errorMessage != null) { break; } (errorMessage, c) = await stream.SkipWhiteSpaceAsync(scratch); if (errorMessage != null) { break; } // check for end or separator if (c == '}') { complete = true; await stream.TryRead(scratch, 0, 1, token); // waste the '}' break; } if (c != ',') { errorMessage = "Expected ','."; break; } } if (!complete && errorMessage == null) { errorMessage = "Unterminated object (missing '}')."; } SmallBufferCache.Release(scratch); return(errorMessage, obj); }
private static async Task <(string errorMessage, JsonValue value)> _TryParseInterpretedStringAsync(StringBuilder builder, TextReader stream, char[] scratch) { // NOTE: `builder` contains the portion of the string found in `stream`, up to the first // (possible) escape sequence. System.Diagnostics.Debug.Assert('\\' == (char)stream.Peek()); System.Diagnostics.Debug.Assert(scratch.Length >= 4); bool complete = false; int?previousHex = null; char c; while (stream.Peek() != -1) { await stream.TryRead(scratch, 0, 1); // eat this character c = scratch[0]; if (c == '\\') { if (stream.Peek() == -1) { StringBuilderCache.Release(builder); SmallBufferCache.Release(scratch); return("Could not find end of string value.", null); } // escape sequence var lookAhead = (char)stream.Peek(); if (!_MustInterpretComplex(lookAhead)) { await stream.TryRead(scratch, 0, 1); // eat the simple escape c = _InterpretSimpleEscapeSequence(lookAhead); } else { // NOTE: Currently we only handle 'u' here if (lookAhead != 'u') { StringBuilderCache.Release(builder); SmallBufferCache.Release(scratch); return($"Invalid escape sequence: '\\{lookAhead}'.", null); } await stream.TryRead(scratch, 0, 1); // eat the 'u' var charsRead = await stream.ReadAsync(scratch, 0, 4); if (charsRead < 4) { StringBuilderCache.Release(builder); SmallBufferCache.Release(scratch); return("Could not find end of string value.", null); } var hexString = new string(scratch, 0, 4); var currentHex = int.Parse(hexString, NumberStyles.HexNumber); if (previousHex != null) { // Our last character was \u, so combine and emit the UTF32 codepoint currentHex = StringExtensions.CalculateUtf32(previousHex.Value, currentHex); if (currentHex.IsValidUtf32CodePoint()) { builder.Append(char.ConvertFromUtf32(currentHex)); } else { return("Invalid UTF-32 code point.", null); } previousHex = null; } else { previousHex = currentHex; } continue; } } else if (c == '"') { complete = true; break; } // Check if last character was \u, and if so emit it as-is, because // this character is not a continuation of a UTF-32 escape sequence if (previousHex != null) { builder.Append(char.ConvertFromUtf32(previousHex.Value)); previousHex = null; } // non-escape sequence builder.Append(c); } SmallBufferCache.Release(scratch); // if we had a hanging UTF32 escape sequence, apply it now if (previousHex != null) { builder.Append(char.ConvertFromUtf32(previousHex.Value)); } if (!complete) { StringBuilderCache.Release(builder); return("Could not find end of string value.", null); } JsonValue value = StringBuilderCache.GetStringAndRelease(builder); return(null, value); }
private static string _TryParseInterpretedString(StringBuilder builder, TextReader stream, out JsonValue value) { // NOTE: `builder` contains the portion of the string found in `stream`, up to the first // (possible) escape sequence. System.Diagnostics.Debug.Assert('\\' == (char)stream.Peek()); value = null; string errorMessage = null; bool complete = false; int?previousHex = null; char c; while (stream.Peek() != -1) { c = (char)stream.Read(); // eat this character if (c == '\\') { if (stream.Peek() == -1) { StringBuilderCache.Release(builder); return("Could not find end of string value."); } // escape sequence var lookAhead = (char)stream.Peek(); if (!_MustInterpretComplex(lookAhead)) { stream.Read(); // eat the simple escape c = _InterpretSimpleEscapeSequence(lookAhead); } else { // NOTE: Currently we only handle 'u' here if (lookAhead != 'u') { StringBuilderCache.Release(builder); return($"Invalid escape sequence: '\\{lookAhead}'."); } var buffer = SmallBufferCache.Acquire(4); stream.Read(); // eat the 'u' if (4 != stream.Read(buffer, 0, 4)) { StringBuilderCache.Release(builder); return("Could not find end of string value."); } var hexString = new string(buffer, 0, 4); if (!_IsValidHex(hexString, 0, 4) || !int.TryParse(hexString, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var currentHex)) { StringBuilderCache.Release(builder); return($"Invalid escape sequence: '\\{lookAhead}{hexString}'."); } if (previousHex != null) { // Our last character was \u, so combine and emit the UTF32 codepoint currentHex = StringExtensions.CalculateUtf32(previousHex.Value, currentHex); if (currentHex.IsValidUtf32CodePoint()) { builder.Append(char.ConvertFromUtf32(currentHex)); } else { value = null; return("Invalid UTF-32 code point."); } previousHex = null; } else { previousHex = currentHex; } SmallBufferCache.Release(buffer); continue; } } else if (c == '"') { complete = true; break; } // Check if last character was \u, and if so emit it as-is, because // this character is not a continuation of a UTF-32 escape sequence if (previousHex != null) { builder.Append(char.ConvertFromUtf32(previousHex.Value)); previousHex = null; } // non-escape sequence builder.Append(c); } // if we had a hanging UTF32 escape sequence, apply it now if (previousHex != null) { builder.Append(char.ConvertFromUtf32(previousHex.Value)); } if (!complete) { value = null; StringBuilderCache.Release(builder); return("Could not find end of string value."); } value = StringBuilderCache.GetStringAndRelease(builder); return(errorMessage); }