예제 #1
0
    private static async ValueTask <String> ReadJSONStringAsync(
        CharacterReader reader,
        StreamReaderWithResizableBuffer stream,
        Boolean startQuoteRead
        )
    {
        Char charRead;
        var  proceed = startQuoteRead;

        if (!startQuoteRead)
        {
            stream.EraseReadBytesFromBuffer();
            charRead = await reader.ReadNextCharacterAsync(stream, c => !Char.IsWhiteSpace(c));

            proceed = charRead == STR_START;
        }

        String str;
        var    encoding  = reader.Encoding;
        var    eencoding = encoding.Encoding;

        if (proceed)
        {
            // At this point, we have read the starting quote, now read the contents.
            var asciiSize = encoding.BytesPerASCIICharacter;
            var startIdx  = stream.ReadBytesCount;

            async ValueTask <Int32> DecodeUnicodeEscape()
            {
                var decodeIdx = stream.ReadBytesCount;
                await stream.ReadMoreOrThrow(4 *asciiSize);

                return((encoding.ReadHexDecimal(stream.Buffer, ref decodeIdx) << 8) | (encoding.ReadHexDecimal(stream.Buffer, ref decodeIdx)));
            }

            // Read string, but mind the escapes
            Int32 curIdx;
            do
            {
                curIdx   = stream.ReadBytesCount;
                charRead = await reader.TryReadNextCharacterAsync(stream) ?? STR_END;

                if (charRead == STR_ESCAPE_PREFIX)
                {
                    // Escape handling - next character decides what we will do
                    charRead = await reader.TryReadNextCharacterAsync(stream) ?? STR_END;

                    Byte replacementByte = 0;
                    switch (charRead)
                    {
                    case STR_END:
                    case STR_ESCAPE_PREFIX:
                    case '/':
                        // Actual value is just just read char minus the '\'
                        replacementByte = (Byte)charRead;
                        break;

                    case 'b':
                        // Backspace
                        replacementByte = (Byte)'\b';
                        break;

                    case 'f':
                        // Form feed
                        replacementByte = (Byte)'\f';
                        break;

                    case 'n':
                        // New line
                        replacementByte = (Byte)'\n';
                        break;

                    case 'r':
                        // Carriage return
                        replacementByte = (Byte)'\r';
                        break;

                    case 't':
                        // Horizontal tab
                        replacementByte = (Byte)'\t';
                        break;

                    case 'u':
                        // Unicode sequence - followed by four hexadecimal digits
                        var code = await DecodeUnicodeEscape();

                        if (code <= Char.MaxValue && code >= Char.MinValue && Char.IsSurrogate((charRead = (Char)code)))
                        {
                            var  idxAfterDecode = stream.ReadBytesCount;
                            Char?nullableChar;
                            if (
                                (nullableChar = await reader.TryReadNextCharacterAsync(stream)).HasValue && nullableChar.Value == STR_ESCAPE_PREFIX &&
                                (nullableChar = await reader.TryReadNextCharacterAsync(stream)).HasValue && nullableChar.Value == 'u'
                                )
                            {
                                var code2 = await DecodeUnicodeEscape();

                                reader.GetBytes(charRead, (Char)code2, stream.Buffer, ref curIdx);
                            }
                            else
                            {
                                // Orphaned surrogate character...
                                stream.UnreadBytes(stream.ReadBytesCount - idxAfterDecode);
                                reader.GetBytes(charRead, stream.Buffer, ref curIdx);
                            }
                        }
                        else
                        {
                            var codeStr = Char.ConvertFromUtf32(code);
                            // Overwrite '\uXXXX' with actual character
                            curIdx += eencoding.GetBytes(codeStr, 0, codeStr.Length, stream.Buffer, curIdx);
                        }
                        break;

                    default:
                        // Just let it slide
                        curIdx = stream.ReadBytesCount;
                        break;
                    }

                    if (replacementByte > 0)
                    {
                        // We just read ASCII char, which should be now replaced
                        encoding.WriteASCIIByte(stream.Buffer, ref curIdx, replacementByte);
                    }

                    // Erase anything extra
                    stream.EraseReadBufferSegment(curIdx, stream.ReadBytesCount - curIdx);

                    // Always read next char
                    charRead = (Char)0;
                }
            } while (charRead != STR_END);

            var strByteCount = stream.ReadBytesCount - startIdx - asciiSize;
            str = strByteCount <= 0 ? "" : eencoding.GetString(stream.Buffer, startIdx, strByteCount);
        }
        else
        {
            str = null;
        }

        return(str);
    }