Coverage Summary for Class: GhostJsonFlatReaderStringsKt (com.ghost.serialization.parser)

Class Class, % Method, % Branch, % Line, % Instruction, %
GhostJsonFlatReaderStringsKt 100% (1/1) 100% (5/5) 58.8% (60/102) 69.7% (122/175) 72.1% (678/941)


 @file:OptIn(InternalGhostApi::class)
 
 package com.ghost.serialization.parser
 
 import com.ghost.serialization.InternalGhostApi
 import com.ghost.serialization.acquireScratchBuffer
 import com.ghost.serialization.releaseScratchBuffer
 import com.ghost.serialization.parser.GhostJsonConstants as C
 
 /**
  * Reads a double-quoted JSON string from the raw byte array, parsing escape sequences
  * and caching string instances in the stringPool when appropriate to save memory.
  *
  * @return The decoded string value.
  * @throws com.ghost.serialization.exception.GhostJsonException
  * if the string is malformed, unescaped control character is found,
  * or it is unterminated.
  */
 fun GhostJsonFlatReader.readQuotedString(): String {
     if (nextNonWhitespace() != C.QUOTE_INT) {
         throwError(C.ERR_EXPECTED_QUOTE)
     }
 
     val start = position
     val localData = rawData
     val scanResult = scanStringImpl(start, limit) { localData[it].toInt() and C.BYTE_MASK }
 
     if (scanResult != -1L) {
         val length = ((scanResult and C.SCAN_LENGTH_MASK) ushr C.SCAN_LENGTH_SHIFT).toInt()
         val rollingHash = scanResult.toInt()
         val only7Bit = (scanResult and C.SCAN_7BIT_BIT) != 0L
         lastScanContentWas7BitOnly = only7Bit
         val end = start + length
         if (length <= 0) {
             position = end + 1
             nextTokenByte = C.RESET_TOKEN_BYTE
             return ""
         }
         if (length > GhostHeuristics.maxStringPoolLength) {
             val result = source.decodeJsonStringRange(start, end, only7Bit)
             position = end + 1
             nextTokenByte = C.RESET_TOKEN_BYTE
             return result
         }
 
         val poolBucketIndex = rollingHash and (C.STR_POOL_SIZE - 1)
         if (stringPoolHashes[poolBucketIndex] == rollingHash) {
             val cachedString = stringPool[poolBucketIndex]
             if (only7Bit && cachedString != null && contentEqualsStringImpl(
                     start,
                     length,
                     cachedString
                 ) { localData[it].toInt() and C.BYTE_MASK }
             ) {
                 position = end + 1
                 nextTokenByte = C.RESET_TOKEN_BYTE
                 return cachedString
             }
         }
 
         val decodedString = source.decodeJsonStringRange(start, end, only7Bit)
         if (only7Bit) {
             stringPool[poolBucketIndex] = decodedString
             stringPoolHashes[poolBucketIndex] = rollingHash
         }
         position = end + 1
         nextTokenByte = C.RESET_TOKEN_BYTE
         return decodedString
     }
 
     return readQuotedStringSlow(start)
 }
 
 private fun GhostJsonFlatReader.readQuotedStringSlow(start: Int): String {
     // Slow path: manual string building for escapes (Bitwise & Zero-Allocation approach)
     var outBuffer = acquireScratchBuffer(C.TIER_SMALL_INT)
     var outPos = 0
 
     try {
         var pos = start
         while (pos < limit) {
             val byteValue = getByte(pos++)
             if (byteValue == C.QUOTE_INT) {
                 position = pos
                 nextTokenByte = C.RESET_TOKEN_BYTE
                 return outBuffer.decodeToString(0, outPos)
             }
 
             if (byteValue == C.BACKSLASH_INT) {
                 if (pos >= limit) {
                     position = pos
                     throwError(C.UNTERMINATED_ESCAPE_ERROR)
                 }
                 when (val escaped = getByte(pos++)) {
                     C.UNICODE_PREFIX_U_INT -> {
                         if (pos + C.UNICODE_HEX_LENGTH > limit) {
                             position = pos
                             throwError(C.UNTERMINATED_UNICODE_ERROR)
                         }
 
                         var code = parseUnicodeHex(pos)
                         pos += C.UNICODE_HEX_LENGTH
 
                         if (code in C.HIGH_SURROGATE_START..C.HIGH_SURROGATE_END) {
                             if (pos + C.SURROGATE_OFFSET <= limit &&
                                 getByte(pos) == C.BACKSLASH_INT &&
                                 getByte(pos + C.SINGLE_CHAR_SIZE) == C.UNICODE_PREFIX_U_INT
                             ) {
                                 // Valid surrogate pair check
                                 pos += C.UNICODE_ESCAPE_PREFIX_SIZE
                                 val lowCode = parseUnicodeHex(pos)
                                 if (lowCode in C.LOW_SURROGATE_START..C.LOW_SURROGATE_END) {
                                     pos += C.UNICODE_HEX_LENGTH
                                     code = C.UNICODE_BASE +
                                             ((code - C.HIGH_SURROGATE_START) shl C.SHIFT_10) +
                                             (lowCode - C.LOW_SURROGATE_START)
                                 } else {
                                     position = pos
                                     throwError(C.ERR_HIGH_SURROGATE)
                                 }
                             } else {
                                 position = pos
                                 throwError(C.ERR_HIGH_SURROGATE)
                             }
                         }
 
                         // Encode code point to UTF-8 bytes in outBuffer
                         if (code <= C.UTF8_1BYTE_MAX) {
                             if (outPos + 1 > outBuffer.size) {
                                 outBuffer = growBuffer(outBuffer, outPos)
                             }
                             outBuffer[outPos++] = code.toByte()
                         } else if (code <= C.UTF8_2BYTE_MAX) {
                             if (outPos + 2 > outBuffer.size) {
                                 outBuffer = growBuffer(outBuffer, outPos)
                             }
                             outBuffer[outPos++] =
                                 (C.UTF8_2BYTE_PREFIX or (code shr C.UTF8_SHIFT_6)).toByte()
                             outBuffer[outPos++] =
                                 (C.UTF8_CONT_PREFIX or (code and C.UTF8_CONT_MASK)).toByte()
                         } else if (code <= C.BMP_LIMIT) {
                             if (outPos + 3 > outBuffer.size) {
                                 outBuffer = growBuffer(outBuffer, outPos)
                             }
                             outBuffer[outPos++] =
                                 (C.UTF8_3BYTE_PREFIX or (code shr C.UTF8_SHIFT_12)).toByte()
                             outBuffer[outPos++] =
                                 (C.UTF8_CONT_PREFIX or ((code shr C.UTF8_SHIFT_6) and C.UTF8_CONT_MASK)).toByte()
                             outBuffer[outPos++] =
                                 (C.UTF8_CONT_PREFIX or (code and C.UTF8_CONT_MASK)).toByte()
                         } else {
                             if (outPos + 4 > outBuffer.size) {
                                 outBuffer = growBuffer(outBuffer, outPos)
                             }
                             outBuffer[outPos++] =
                                 (C.UTF8_4BYTE_PREFIX or (code shr C.UTF8_SHIFT_18)).toByte()
                             outBuffer[outPos++] =
                                 (C.UTF8_CONT_PREFIX or ((code shr C.UTF8_SHIFT_12) and C.UTF8_CONT_MASK)).toByte()
                             outBuffer[outPos++] =
                                 (C.UTF8_CONT_PREFIX or ((code shr C.UTF8_SHIFT_6) and C.UTF8_CONT_MASK)).toByte()
                             outBuffer[outPos++] =
                                 (C.UTF8_CONT_PREFIX or (code and C.UTF8_CONT_MASK)).toByte()
                         }
                     }
 
                     C.N_BYTE_INT -> {
                         if (outPos + 1 > outBuffer.size) {
                             outBuffer = growBuffer(outBuffer, outPos)
                         }
                         outBuffer[outPos++] = C.LF_INT.toByte()
                     }
 
                     C.R_BYTE_INT -> {
                         if (outPos + 1 > outBuffer.size) {
                             outBuffer = growBuffer(outBuffer, outPos)
                         }
                         outBuffer[outPos++] = C.CR_INT.toByte()
                     }
 
                     C.T_BYTE_INT -> {
                         if (outPos + 1 > outBuffer.size) {
                             outBuffer = growBuffer(outBuffer, outPos)
                         }
                         outBuffer[outPos++] = C.TAB_INT.toByte()
                     }
 
                     C.B_BYTE_INT -> {
                         if (outPos + 1 > outBuffer.size) {
                             outBuffer = growBuffer(outBuffer, outPos)
                         }
                         outBuffer[outPos++] = C.BS_INT.toByte()
                     }
 
                     C.F_BYTE_INT -> {
                         if (outPos + 1 > outBuffer.size) {
                             outBuffer = growBuffer(outBuffer, outPos)
                         }
                         outBuffer[outPos++] = C.FF_INT.toByte()
                     }
 
                     else -> {
                         if (outPos + 1 > outBuffer.size) {
                             outBuffer = growBuffer(outBuffer, outPos)
                         }
                         outBuffer[outPos++] = escaped.toByte()
                     }
                 }
             } else if (byteValue < C.SPACE_INT) {
                 position = pos
                 throwError(C.UNESCAPED_CONTROL_CHAR_ERROR)
             } else {
                 if (outPos + 1 > outBuffer.size) {
                     outBuffer = growBuffer(outBuffer, outPos)
                 }
                 outBuffer[outPos++] = byteValue.toByte()
             }
         }
         position = pos
     } finally {
         releaseScratchBuffer(outBuffer)
     }
     throwError(C.UNTERMINATED_STRING_ERROR)
 }
 
 /**
  * Skips a double-quoted JSON string in the raw byte array without decoding its content.
  *
  * @throws com.ghost.serialization.exception.GhostJsonException
  * if the string is malformed or unterminated.
  */
 fun GhostJsonFlatReader.skipQuotedString() {
     if (nextNonWhitespace() != C.QUOTE_INT) {
         throwError(C.ERR_EXPECTED_QUOTE)
     }
 
     val start = position
     val localData = rawData
     val end = findClosingQuoteImpl(start, limit) { localData[it].toInt() and C.BYTE_MASK }
     if (end != -1) {
         position = end + 1
         return
     }
 
     var pos = start
     while (pos < limit) {
         val byteValue = getByte(pos++)
         if (byteValue == C.QUOTE_INT) {
             position = pos
             nextTokenByte = C.RESET_TOKEN_BYTE
             return
         }
 
         if (byteValue == C.BACKSLASH_INT) {
             if (pos >= limit) {
                 position = pos
                 throwError(C.UNTERMINATED_ESCAPE_ERROR)
             }
             val escaped = getByte(pos++)
 
             if (escaped == C.UNICODE_PREFIX_U_INT) {
                 if (pos + C.UNICODE_HEX_LENGTH > limit) {
                     position = pos
                     throwError(C.UNTERMINATED_UNICODE_ERROR)
                 }
                 parseUnicodeHex(pos)
                 pos += C.UNICODE_HEX_LENGTH
             }
         } else if (byteValue < C.SPACE_INT) {
             position = pos
             throwError(C.UNESCAPED_CONTROL_CHAR_ERROR)
         }
     }
     position = pos
     throwError(C.UNTERMINATED_STRING_ERROR)
 }
 
 /**
  * Parses 4 hex digits from the byte array at the given position and returns the resulting code point.
  */
 private fun GhostJsonFlatReader.parseUnicodeHex(currentPosition: Int): Int {
     val hexByte0 = getByte(currentPosition)
     val hexByte1 = getByte(currentPosition + 1)
     val hexByte2 = getByte(currentPosition + 2)
     val hexByte3 = getByte(currentPosition + 3)
 
     val hexLookupTable = C.HEX_LUT
     val digitValue0 = hexLookupTable[hexByte0]
     val digitValue1 = hexLookupTable[hexByte1]
     val digitValue2 = hexLookupTable[hexByte2]
     val digitValue3 = hexLookupTable[hexByte3]
 
     if ((digitValue0 or digitValue1 or digitValue2 or digitValue3) < 0) {
         throwError("Invalid unicode escape at $currentPosition")
     }
 
     return (digitValue0 shl C.SHIFT_12) or
             (digitValue1 shl C.SHIFT_8) or
             (digitValue2 shl C.SHIFT_4) or
             digitValue3
 }
 
 /**
  * Utility helper to grow a temporary byte array buffer.
  */
 private fun growBuffer(outBuffer: ByteArray, outPos: Int): ByteArray {
     val newBuffer = acquireScratchBuffer(outBuffer.size * C.BUFFER_SCALE_FACTOR)
     outBuffer.copyInto(newBuffer, 0, 0, outPos)
     releaseScratchBuffer(outBuffer)
     return newBuffer
 }