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

Class Class, % Method, % Branch, % Line, % Instruction, %
GhostJsonStringReaderNumbersKt 100% (1/1) 92.9% (13/14) 63% (136/216) 82.5% (235/285) 79.6% (1006/1264)


 @file:Suppress("NOTHING_TO_INLINE")
 @file:OptIn(InternalGhostApi::class)
 
 package com.ghost.serialization.parser
 
 import com.ghost.serialization.parser.GhostJsonConstants as C
 import com.ghost.serialization.InternalGhostApi
 
 fun GhostJsonStringReader.nextInt(): Int {
     val header = prepareNumericHeader()
     val isQuoted = (header and C.NUMERIC_HEADER_QUOTED) != 0
     val isNegativeValue = (header and C.NUMERIC_HEADER_NEGATIVE) != 0
 
     val startOfNumber = position
     validateLeadingZero()
 
     val accumulatedValue = parseIntDigits(isNegativeValue, startOfNumber)
 
     if (isQuoted) {
         consumeNumericCoercionFooter()
     }
     nextTokenByte = C.RESET_TOKEN_BYTE
 
     return if (isNegativeValue) -accumulatedValue else accumulatedValue
 }
 
 fun GhostJsonStringReader.nextLong(): Long {
     val header = prepareNumericHeader()
     val isQuoted = (header and C.NUMERIC_HEADER_QUOTED) != 0
     val isNegativeValue = (header and C.NUMERIC_HEADER_NEGATIVE) != 0
 
     val startOfNumber = position
     validateLeadingZero()
 
     val accumulatedValue = parseLongDigits(isNegativeValue, startOfNumber)
 
     if (isQuoted) {
         consumeNumericCoercionFooter()
     }
     nextTokenByte = C.RESET_TOKEN_BYTE
 
     return if (isNegativeValue) -accumulatedValue else accumulatedValue
 }
 
 fun GhostJsonStringReader.nextFloat(): Float {
     val header = prepareNumericHeader()
     val isQuoted = (header and C.NUMERIC_HEADER_QUOTED) != 0
     val isNegativeValue = (header and C.NUMERIC_HEADER_NEGATIVE) != 0
 
     validateLeadingZero()
 
     var mantissa = 0L
     var exponent = 0
     var digitCount = 0
 
     nextTokenByte = -1
     val localLimit = limit
     val chars = rawData
     while (position < localLimit) {
         val byte = chars[position].code
         if (isDigit(byte)) {
             val digit = byte - C.ZERO_INT
             if (digitCount < C.FLOAT_PRECISION_LIMIT) {
                 mantissa = mantissa * C.BASE_TEN + digit
                 digitCount++
             } else {
                 exponent++
             }
             position++
         } else {
             break
         }
     }
 
     if (digitCount == 0) {
         throwError(C.ERR_EXPECTED_INT_PART)
     }
 
     if (position < localLimit && getByte(position) == C.DOT_INT) {
         val newPos = position + 1
         position = newPos
         while (position < localLimit) {
             val byte = chars[position].code
             if (isDigit(byte)) {
                 val digit = byte - C.ZERO_INT
                 if (digitCount < C.FLOAT_PRECISION_LIMIT) {
                     mantissa = mantissa * C.BASE_TEN + digit
                     digitCount++
                     exponent--
                 }
                 position++
             } else {
                 break
             }
         }
         if (position == newPos) {
             throwError(C.ERR_EXPECTED_DECIMAL_DIGITS)
         }
     }
 
     if (position < localLimit && isExponentMarker(getByte(position))) {
         exponent += parseExponentValue()
     }
 
     var result = mantissa.toFloat()
     if (exponent != 0) {
         result *= getFloatPowerOfTen(exponent)
     }
 
     if (isNegativeValue) {
         result = -result
     }
     if (result.isInfinite() || result.isNaN()) {
         throwError(C.ERR_NUMERIC_OVERFLOW)
     }
 
     if (isQuoted) {
         consumeNumericCoercionFooter()
     }
 
     return result
 }
 
 fun GhostJsonStringReader.nextDouble(): Double {
     val header = prepareNumericHeader()
     val isQuoted = (header and C.NUMERIC_HEADER_QUOTED) != 0
     val isNegativeValue = (header and C.NUMERIC_HEADER_NEGATIVE) != 0
 
     validateLeadingZero()
 
     var mantissa = 0L
     var exponent = 0
     var digitCount = 0
 
     nextTokenByte = -1
     val localLimit = limit
     val chars = rawData
     while (position < localLimit) {
         val byte = chars[position].code
         if (isDigit(byte)) {
             val digit = byte - C.ZERO_INT
             if (digitCount < C.DOUBLE_PRECISION_LIMIT) {
                 mantissa = mantissa * C.BASE_TEN + digit
                 digitCount++
             } else {
                 exponent++
             }
             position++
         } else {
             break
         }
     }
 
     if (digitCount == 0) {
         throwError(C.ERR_EXPECTED_INT_PART)
     }
 
     if (position < localLimit && getByte(position) == C.DOT_INT) {
         val newPos = position + 1
         position = newPos
         while (position < localLimit) {
             val byte = chars[position].code
             if (isDigit(byte)) {
                 val digitValue = byte - C.ZERO_INT
                 if (digitCount < C.DOUBLE_PRECISION_LIMIT) {
                     mantissa = mantissa * C.BASE_TEN + digitValue
                     digitCount++
                     exponent--
                 }
                 position++
             } else {
                 break
             }
         }
         if (position == newPos) {
             throwError(C.ERR_EXPECTED_DECIMAL_DIGITS)
         }
     }
 
     if (position < localLimit && isExponentMarker(getByte(position))) {
         exponent += parseExponentValue()
     }
 
     var result = mantissa.toDouble()
     if (exponent != 0) {
         result *= getDoublePowerOfTen(exponent)
     }
 
     if (isNegativeValue) {
         result = -result
     }
     if (result.isInfinite() || result.isNaN()) {
         throwError(C.ERR_NUMERIC_OVERFLOW)
     }
 
     if (isQuoted) {
         consumeNumericCoercionFooter()
     }
 
     return result
 }
 
 private fun GhostJsonStringReader.parseExponentValue(): Int {
     position++
     var isExpNegative = false
     if (position < limit) {
         val marker = getByte(position)
         if (marker == C.MINUS_INT) {
             isExpNegative = true
             position++
         } else if (marker == C.PLUS_INT) {
             position++
         }
     }
 
     var expValue = 0
     var hasExpDigits = false
     while (position < limit) {
         val currentByteInt = getByte(position)
         if (isDigit(currentByteInt)) {
             if (expValue < C.EXPONENT_CLAMP_THRESHOLD) {
                 expValue = expValue * C.BASE_TEN + (currentByteInt - C.ZERO_INT)
             }
             hasExpDigits = true
             position++
         } else {
             break
         }
     }
 
     if (!hasExpDigits) {
         throwError(C.ERR_EXPECTED_EXPONENT_DIGITS)
     }
     return if (isExpNegative) -expValue else expValue
 }
 
 private fun GhostJsonStringReader.prepareNumericHeader(): Int {
     if (nextTokenByte == C.RESET_TOKEN_BYTE) {
         skipWhitespace()
     }
     if (position >= limit) {
         throwError(C.ERR_EXPECTED_NUMBER)
     }
 
     var header = 0
     var token = nextTokenByte
 
     if (token == C.QUOTE_INT) {
         if (!coerceStringsToNumbers) {
             throwError(C.ERR_COERCION_DISABLED)
         }
         position++
         nextTokenByte = C.RESET_TOKEN_BYTE
         skipWhitespace()
         if (position >= limit) {
             throwError(C.ERR_EXPECTED_NUMBER)
         }
         token = nextTokenByte
         header = header or C.NUMERIC_HEADER_QUOTED
     }
 
     if (token == C.MINUS_INT) {
         if (position + 1 >= limit) {
             throwError(C.ERR_ISOLATED_MINUS)
         }
         position++
         nextTokenByte = C.RESET_TOKEN_BYTE
         header = header or C.NUMERIC_HEADER_NEGATIVE
     }
 
     return header
 }
 
 private fun GhostJsonStringReader.handleLeadingZero() {
     val nextCursor = position + 1
     if (nextCursor < limit) {
         val nextDigitByte = getByte(nextCursor)
         if (nextDigitByte in C.ZERO_INT..C.NINE_INT) {
             throwError(C.ERR_LEADING_ZEROS)
         }
     }
     internalSkip(1)
 }
 
 private fun GhostJsonStringReader.parseIntDigits(isNegative: Boolean, startOfNumber: Int): Int {
     var accumulatedValue = 0
     var digitCount = 0
     var hasDigitsFound = false
     nextTokenByte = -1
     var earlyExitResult: Int? = null
 
     val localLimit = limit
     val chars = rawData
     while (position < localLimit) {
         val byte = chars[position].code
         if (isDigit(byte)) {
             val digit = byte - C.ZERO_INT
             accumulatedValue = if (digitCount < C.INT_SAFE_DIGITS) {
                 accumulatedValue * C.BASE_TEN + digit
             } else {
                 calculateIntWithOverflowCheck(accumulatedValue, digit, isNegative)
             }
             digitCount++
             hasDigitsFound = true
             position++
         } else {
             if (isNumericSeparator(byte)) {
                 position = startOfNumber
                 earlyExitResult = nextDouble().toInt()
             }
             break
         }
     }
 
     if (earlyExitResult != null) {
         return earlyExitResult
     }
     if (!hasDigitsFound) {
         throwError(C.ERR_EXPECTED_INT_PART)
     }
     return accumulatedValue
 }
 
 private fun GhostJsonStringReader.parseLongDigits(isNegative: Boolean, startOfNumber: Int): Long {
     var accumulatedValue = 0L
     var digitCount = 0
     var hasDigitsFound = false
     nextTokenByte = -1
     var earlyExitResult: Long? = null
 
     val localLimit = limit
     val chars = rawData
     while (position < localLimit) {
         val byte = chars[position].code
         if (isDigit(byte)) {
             val digit = byte - C.ZERO_INT
             accumulatedValue = if (digitCount < C.LONG_SAFE_DIGITS) {
                 accumulatedValue * C.BASE_TEN + digit
             } else {
                 calculateLongWithOverflowCheck(accumulatedValue, digit, isNegative)
             }
             digitCount++
             hasDigitsFound = true
             position++
         } else {
             if (isNumericSeparator(byte)) {
                 position = startOfNumber
                 earlyExitResult = nextDouble().toLong()
             }
             break
         }
     }
 
     if (earlyExitResult != null) {
         return earlyExitResult
     }
     if (!hasDigitsFound) {
         throwError(C.ERR_EXPECTED_INT_PART)
     }
     return accumulatedValue
 }
 
 private inline fun GhostJsonStringReader.consumeNumericCoercionFooter() {
     if (position >= limit || getByte(position) != C.QUOTE_INT) {
         throwError(C.ERR_EXPECTED_COERCION_QUOTE)
     }
     internalSkip(1)
     skipWhitespace()
 }
 
 private inline fun GhostJsonStringReader.calculateIntWithOverflowCheck(
     current: Int,
     digitValue: Int,
     isNegative: Boolean
 ): Int {
     if (current > C.INT_OVERFLOW_LIMIT ||
         (current == C.INT_OVERFLOW_LIMIT &&
                 digitValue > (if (isNegative) C.INT_MIN_LAST_DIGIT else C.INT_MAX_LAST_DIGIT))
     ) {
         throwError(C.ERR_INT_OVERFLOW)
     }
     return current * C.BASE_TEN + digitValue
 }
 
 private inline fun GhostJsonStringReader.calculateLongWithOverflowCheck(
     current: Long,
     digitValue: Int,
     isNegative: Boolean
 ): Long {
     if (current == Long.MIN_VALUE ||
         current > C.LONG_OVERFLOW_LIMIT ||
         (current == C.LONG_OVERFLOW_LIMIT && digitValue > C.LONG_MAX_LAST_DIGIT)
     ) {
         if (isNegative && current == C.LONG_OVERFLOW_LIMIT &&
             digitValue == C.LONG_MIN_LAST_DIGIT
         ) {
             return Long.MIN_VALUE
         }
         throwError(C.ERR_LONG_OVERFLOW)
     }
     return current * C.BASE_TEN + digitValue
 }
 
 private fun GhostJsonStringReader.validateLeadingZero() {
     if (position < limit && getByte(position) == C.ZERO_INT &&
         position + 1 < limit
     ) {
         val nextDigitByte = getByte(position + 1)
         if (nextDigitByte in C.ZERO_INT..C.NINE_INT) {
             throwError(C.ERR_LEADING_ZEROS)
         }
     }
 }
 
 fun GhostJsonStringReader.skipNumber() {
     val header = prepareNumericHeader()
     val isQuoted = (header and C.NUMERIC_HEADER_QUOTED) != 0
 
     var hasDigits = false
 
     val numberPosition = position
     val numberLimit = limit
     val chars = rawData
     if (numberPosition < numberLimit && chars[numberPosition].code == C.ZERO_INT) {
         val newPos = numberPosition + 1
         position = newPos
         hasDigits = true
         if (newPos < numberLimit && isDigit(chars[newPos].code)) {
             throwError(C.ERR_LEADING_ZEROS)
         }
     } else {
         while (position < numberLimit) {
             val byte = chars[position].code
             if (isDigit(byte)) {
                 hasDigits = true
                 position++
             } else {
                 break
             }
         }
     }
 
     if (!hasDigits) {
         throwError(C.ERR_EXPECTED_INT_PART)
     }
 
     if (position < numberLimit && chars[position].code == C.DOT_INT) {
         position++
         var hasDecimalDigits = false
         while (position < numberLimit) {
             val byte = chars[position].code
             if (isDigit(byte)) {
                 hasDecimalDigits = true
                 position++
             } else {
                 break
             }
         }
         if (!hasDecimalDigits) {
             throwError(C.ERR_EXPECTED_DECIMAL_DIGITS)
         }
     }
 
     if (position < numberLimit) {
         val byte = chars[position].code
         if (byte == C.EXP_LOWER_INT || byte == C.EXP_UPPER_INT) {
             var newPos = position + 1
             position = newPos
             if (newPos < numberLimit) {
                 val sign = chars[newPos].code
                 if (sign == C.PLUS_INT || sign == C.MINUS_INT) {
                     newPos++
                     position = newPos
                 }
             }
 
             var hasExpDigits = false
             while (position < numberLimit) {
                 val byteCode = chars[position].code
                 if (isDigit(byteCode)) {
                     hasExpDigits = true
                     position++
                 } else {
                     break
                 }
             }
             if (!hasExpDigits) {
                 throwError(C.ERR_EXPECTED_EXPONENT_DIGITS)
             }
         }
     }
 
     if (isQuoted) {
         consumeNumericCoercionFooter()
     }
     nextTokenByte = -1
 }