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

Class Class, % Method, % Branch, % Line, % Instruction, %
GhostDiscriminatorPeeker 100% (1/1) 91.7% (11/12) 77.6% (45/58) 89.1% (90/101) 95.9% (422/440)


 package com.ghost.serialization.parser
 
 import com.ghost.serialization.InternalGhostApi
 import com.ghost.serialization.parser.GhostJsonConstants.BACKSLASH_INT
 import com.ghost.serialization.parser.GhostJsonConstants.BYTE_MASK
 import com.ghost.serialization.parser.GhostJsonConstants.BYTE_SHIFT_UNIT
 import com.ghost.serialization.parser.GhostJsonConstants.CLOSE_ARR_INT
 import com.ghost.serialization.parser.GhostJsonConstants.CLOSE_OBJ_INT
 import com.ghost.serialization.parser.GhostJsonConstants.COLON_INT
 import com.ghost.serialization.parser.GhostJsonConstants.OPEN_ARR_INT
 import com.ghost.serialization.parser.GhostJsonConstants.OPEN_OBJ_INT
 import com.ghost.serialization.parser.GhostJsonConstants.QUOTE_INT
 import com.ghost.serialization.parser.GhostJsonConstants.RESULT_NONE
 import com.ghost.serialization.parser.GhostJsonConstants.SPACE_INT
 import com.ghost.serialization.parser.GhostHeuristics.maxDiscriminatorPeekDistance
 import okio.ByteString
 
 /**
  * Internal utility to peek at a JSON discriminator value without full parsing.
  * Optimized for speed and zero allocations.
  */
 @InternalGhostApi
 object GhostDiscriminatorPeeker {
 
     /**
      * Attempts to find the value of [key] in the JSON object starting at [start].
      * Returns the value as a string, or null if not found or if the object is too complex.
      *
      * This method decides the branch (Streaming vs Array) once and enters a zero-cost
      * modular implementation.
      *
      * Used by KSP-generated serializers for polymorphic deserialization.
      */
     fun peek(
         source: GhostSource,
         rawData: ByteArray,
         isStreaming: Boolean,
         start: Int,
         limit: Int,
         key: ByteString,
     ): String? {
         val getByte = if (isStreaming) {
             { pos: Int -> source[pos] }
         } else {
             { pos: Int -> rawData[pos].toInt() and BYTE_MASK }
         }
         return peekInternal(
             start = start,
             limit = limit,
             keySize = key.size,
             getByte = getByte,
             matchesKey = { keyStart -> source.contentEquals(keyStart, key) },
             decodeValue = { valueStart, valueEnd -> source.decodeToString(valueStart, valueEnd) },
         )
     }
 
     /**
      * Char-channel peek for [GhostJsonStringReader] — same scan logic as [peek], zero UTF-8 bridge.
      */
     fun peekChars(
         chars: CharArray,
         rawData: String,
         start: Int,
         limit: Int,
         key: String,
     ): String? {
         return peekInternal(
             start = start,
             limit = limit,
             keySize = key.length,
             getByte = { pos -> chars[pos].code },
             matchesKey = { keyStart -> contentEqualsKey(chars, keyStart, key) },
             decodeValue = { valueStart, valueEnd -> rawData.substring(valueStart, valueEnd) },
         )
     }
 
     @Suppress("CascadeIf")
     @PublishedApi
     internal inline fun peekInternal(
         start: Int,
         limit: Int,
         keySize: Int,
         crossinline getByte: (Int) -> Int,
         crossinline matchesKey: (keyStart: Int) -> Boolean,
         crossinline decodeValue: (valueStart: Int, valueEnd: Int) -> String?,
     ): String? {
         var position = skipLeadingWhitespace(start, limit, getByte)
         if (position >= limit) {
             return null
         }
 
         when (getByte(position)) {
             OPEN_OBJ_INT -> position++
             QUOTE_INT -> {
                 // Already inside an object (e.g. after beginObject() on the string channel).
             }
             else -> return null
         }
 
         val scanLimit = (position + maxDiscriminatorPeekDistance)
             .coerceAtMost(limit)
 
         while (position < scanLimit) {
             val byte = getByte(position)
 
             if (byte == QUOTE_INT) {
                 val keyStart = position + 1
 
                 if (
                     keyStart + keySize < scanLimit &&
                     getByte(keyStart + keySize) == QUOTE_INT &&
                     matchesKey(keyStart)
                 ) {
                     return tryExtractValue(
                         keyStart + keySize + 1,
                         scanLimit,
                         getByte,
                         decodeValue,
                     )
                 }
 
                 position = skipString(keyStart, scanLimit, getByte)
             } else if (byte == OPEN_OBJ_INT) {
                 position = skipBalanced(
                     start = position,
                     open = OPEN_OBJ_INT,
                     close = CLOSE_OBJ_INT,
                     limit = scanLimit,
                     getByte = getByte,
                 )
             } else if (byte == OPEN_ARR_INT) {
                 position = skipBalanced(
                     start = position,
                     open = OPEN_ARR_INT,
                     close = CLOSE_ARR_INT,
                     limit = scanLimit,
                     getByte = getByte,
                 )
             } else {
                 position++
             }
         }
         return null
     }
 
     @PublishedApi
     internal inline fun tryExtractValue(
         start: Int,
         limit: Int,
         crossinline getByte: (Int) -> Int,
         crossinline decodeValue: (valueStart: Int, valueEnd: Int) -> String?,
     ): String? {
         val colonPosition = skipWhitespaceAndExpect(
             start,
             limit,
             COLON_INT,
             getByte,
         )
         if (colonPosition == -1) {
             return null
         }
 
         val quotePosition = skipWhitespaceAndExpect(
             colonPosition,
             limit,
             QUOTE_INT,
             getByte,
         )
         if (quotePosition == -1) {
             return null
         }
 
         return extractValue(
             quotePosition,
             limit,
             getByte,
             decodeValue,
         )
     }
 
     @PublishedApi
     internal inline fun skipLeadingWhitespace(
         start: Int,
         limit: Int,
         crossinline getByte: (Int) -> Int,
     ): Int {
         val mask = GhostJsonConstants.WHITESPACE_MASK
         var position = start
         while (position < limit) {
             val byte = getByte(position)
             if (
                 byte > SPACE_INT ||
                 (mask and (BYTE_SHIFT_UNIT shl byte)) == RESULT_NONE
             ) {
                 return position
             }
             position++
         }
         return position
     }
 
     @PublishedApi
     internal inline fun skipWhitespaceAndExpect(
         start: Int,
         limit: Int,
         expected: Int,
         crossinline getByte: (Int) -> Int,
     ): Int {
         val position = skipLeadingWhitespace(start, limit, getByte)
         if (position >= limit) {
             return -1
         }
         return if (getByte(position) == expected) {
             position + 1
         } else {
             -1
         }
     }
 
     @PublishedApi
     internal inline fun skipBalanced(
         start: Int,
         open: Int,
         close: Int,
         limit: Int,
         crossinline getByte: (Int) -> Int,
     ): Int {
         if (getByte(start) != open) {
             return start + 1
         }
         var position = start + 1
         var depth = 1
         while (position < limit && depth > 0) {
             when (val byte = getByte(position)) {
                 QUOTE_INT -> position = skipString(position + 1, limit, getByte)
                 OPEN_OBJ_INT, OPEN_ARR_INT -> {
                     depth++
                     position++
                 }
                 CLOSE_OBJ_INT, CLOSE_ARR_INT -> {
                     depth--
                     position++
                 }
                 else -> position++
             }
         }
         return position
     }
 
     @PublishedApi
     internal inline fun skipString(
         start: Int,
         limit: Int,
         crossinline getByte: (Int) -> Int,
     ): Int {
         var position = start
         while (position < limit) {
             val skipByte = getByte(position)
             if (skipByte == QUOTE_INT) {
                 return position + 1
             }
             if (skipByte == BACKSLASH_INT) {
                 position++
             }
             position++
         }
         return limit
     }
 
     @PublishedApi
     internal inline fun extractValue(
         start: Int,
         limit: Int,
         crossinline getByte: (Int) -> Int,
         crossinline decodeValue: (valueStart: Int, valueEnd: Int) -> String?,
     ): String? {
         var position = start
         val valueStart = position
         while (position < limit) {
             val valueByte = getByte(position)
             if (valueByte == QUOTE_INT) {
                 return decodeValue(valueStart, position)
             }
             if (valueByte == BACKSLASH_INT) {
                 return null
             }
             position++
         }
         return null
     }
 
     @PublishedApi
     internal fun contentEqualsKey(
         chars: CharArray,
         keyStart: Int,
         key: String,
     ): Boolean {
         val keySize = key.length
         for (i in 0 until keySize) {
             if (chars[keyStart + i] != key[i]) {
                 return false
             }
         }
         return true
     }
 }