Example #1
0
        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);
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #4
0
        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));
        }
Example #6
0
        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);
        }