Coverage Summary for Class: GhostJsonFlatReaderNumbersKt (com.ghost.serialization.parser)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| GhostJsonFlatReaderNumbersKt |
100%
(1/1)
|
100%
(16/16)
|
64.6%
(146/226)
|
82.4%
(243/295)
|
79.1%
(1053/1331)
|
@file:OptIn(InternalGhostApi::class)
@file:Suppress("NOTHING_TO_INLINE")
package com.ghost.serialization.parser
import com.ghost.serialization.InternalGhostApi
import com.ghost.serialization.parser.GhostJsonConstants as C
/**
* Parses and returns the next [Float] value from the JSON stream.
*
* Supports coercion from strings if enabled, exponent notations, decimal fractions, and range checks.
*
* @throws com.ghost.serialization.exception.GhostJsonException
* if float format is invalid or overflows.
*/
fun GhostJsonFlatReader.nextFloatExtension(): 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
// Integer part
nextTokenByte = -1
val data = rawData
val localLimit = limit
while (position < localLimit) {
val byte = data[position].toInt() and C.BYTE_MASK
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)
}
// Decimal part
if (position < localLimit && getByte(position) == C.DOT_INT) {
val newPos = position + 1
position = newPos
while (position < localLimit) {
val byte = data[position].toInt() and C.BYTE_MASK
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)
}
}
// Exponent part
if (position < localLimit && isExponentMarker(getByte(position))) {
exponent += parseExponentValue()
}
var result = mantissa.toFloat()
if (exponent != 0) {
result *= getFloatPowerOfTen(exponent)
}
if (isNegativeValue) {
result = -result
}
validateNumericRangeFloat(result)
if (isQuoted) {
consumeNumericCoercionFooter()
}
return result
}
/**
* Parses and returns the next [Double] value from the JSON stream.
*
* Supports coercion from strings if enabled,
* exponent notations, decimal fractions, and range checks.
*
* @throws com.ghost.serialization.exception.GhostJsonException
* if double format is invalid or overflows.
*/
fun GhostJsonFlatReader.nextDoubleExtension(): 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
// Integer part
nextTokenByte = -1
val data = rawData
val localLimit = limit
while (position < localLimit) {
val byte = data[position].toInt() and C.BYTE_MASK
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)
}
// Decimal part
if (position < localLimit && getByte(position) == C.DOT_INT) {
val newPos = position + 1
position = newPos
while (position < localLimit) {
val byte = data[position].toInt() and C.BYTE_MASK
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)
}
}
// Exponent part
if (position < localLimit && isExponentMarker(getByte(position))) {
exponent += parseExponentValue()
}
var result = mantissa.toDouble()
if (exponent != 0) {
result *= getDoublePowerOfTen(exponent)
}
if (isNegativeValue) {
result = -result
}
validateNumericRange(result)
if (isQuoted) {
consumeNumericCoercionFooter()
}
return result
}
/**
* Helper to parse the exponent suffix value (e.g. e-5 or e+12) from a number.
*/
private inline fun GhostJsonFlatReader.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
}
/**
* Parses and returns the next [Int] value from the JSON stream.
*
* Supports coercion from strings if enabled, validates format, leading zeros, and range overflow.
*
* @throws com.ghost.serialization.exception.GhostJsonException if integer is invalid or overflows.
*/
fun GhostJsonFlatReader.nextIntExtension(): 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
val absoluteValue = if (startOfNumber < limit && getByte(startOfNumber) == C.ZERO_INT) {
handleLeadingZero()
0
} else {
parseIntDigits(isNegativeValue, startOfNumber)
}
val finalIntResult = if (isNegativeValue) {
-absoluteValue
} else {
absoluteValue
}
if (isQuoted) {
consumeNumericCoercionFooter()
}
return finalIntResult
}
/**
* Parses and returns the next [Long] value from the JSON stream.
*
* Supports coercion from strings if enabled, validates format, leading zeros, and range overflow.
*
* @throws com.ghost.serialization.exception.GhostJsonException if long value is invalid or overflows.
*/
fun GhostJsonFlatReader.nextLongExtension(): 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
val absoluteValue = if (startOfNumber < limit && getByte(startOfNumber) == C.ZERO_INT) {
handleLeadingZero()
0L
} else {
parseLongDigits(isNegativeValue, startOfNumber)
}
val finalLongResult = if (absoluteValue == Long.MIN_VALUE) {
absoluteValue
} else {
(if (isNegativeValue) -absoluteValue else absoluteValue)
}
if (isQuoted) {
consumeNumericCoercionFooter()
}
return finalLongResult
}
/**
* Prepares the numeric header by checking negative signs and string coercion quotes.
*/
private fun GhostJsonFlatReader.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
}
/**
* Handles validation and skipping of a single leading zero.
*/
private fun GhostJsonFlatReader.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)
}
/**
* Parses integer digits bitwise with overflow checks.
*/
private fun GhostJsonFlatReader.parseIntDigits(isNegative: Boolean, startOfNumber: Int): Int {
var accumulatedValue = 0
var digitCount = 0
var hasDigitsFound = false
nextTokenByte = -1
var earlyExitResult: Int? = null
val data = rawData
val localLimit = limit
while (position < localLimit) {
val byte = data[position].toInt() and C.BYTE_MASK
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
}
/**
* Parses long digits bitwise with overflow checks.
*/
private fun GhostJsonFlatReader.parseLongDigits(isNegative: Boolean, startOfNumber: Int): Long {
var accumulatedValue = 0L
var digitCount = 0
var hasDigitsFound = false
nextTokenByte = -1
var earlyExitResult: Long? = null
val data = rawData
val localLimit = limit
while (position < localLimit) {
val byte = data[position].toInt() and C.BYTE_MASK
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
}
/**
* Consumes the trailing quotation mark when parsing coerced numeric string values.
*/
private inline fun GhostJsonFlatReader.consumeNumericCoercionFooter() {
if (position >= limit || getByte(position) != C.QUOTE_INT) {
throwError(C.ERR_EXPECTED_COERCION_QUOTE)
}
internalSkip(1)
skipWhitespace()
}
/**
* Accumulates int value and throws if it overflows the safe JVM Int limit.
*/
private inline fun GhostJsonFlatReader.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
}
/**
* Accumulates long value and throws if it overflows the safe JVM Long limit.
*/
private inline fun GhostJsonFlatReader.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
}
/**
* Validates leading zero presence for numbers.
*/
private fun GhostJsonFlatReader.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)
}
}
}
/**
* Validates that float values parsed are finite numbers.
*/
private fun GhostJsonFlatReader.validateNumericRangeFloat(valueToValidate: Float) {
if (valueToValidate.isInfinite() || valueToValidate.isNaN()) {
throwError(C.ERR_NUMERIC_OVERFLOW)
}
}
/**
* Validates that double values parsed are finite numbers.
*/
private fun GhostJsonFlatReader.validateNumericRange(valueToValidate: Double) {
if (valueToValidate.isInfinite() || valueToValidate.isNaN()) {
throwError(C.ERR_NUMERIC_OVERFLOW)
}
}
/**
* Skips the next numeric token value in the JSON stream.
*
* Validates scientific exponent format, dot separation, and handles string coercion bounds.
*
* @throws com.ghost.serialization.exception.GhostJsonException if number is malformed.
*/
fun GhostJsonFlatReader.skipNumber() {
val header = prepareNumericHeader()
val isQuoted = (header and C.NUMERIC_HEADER_QUOTED) != 0
var hasDigits = false
// Integer part
val numberPosition = position
val numberLimit = limit
val data = rawData
if (numberPosition < numberLimit && getByte(numberPosition) == C.ZERO_INT) {
val newPos = numberPosition + 1
position = newPos
hasDigits = true
if (newPos < numberLimit && isDigit(getByte(newPos))) {
throwError(C.ERR_LEADING_ZEROS)
}
} else {
while (position < numberLimit) {
val byte = data[position].toInt() and C.BYTE_MASK
if (isDigit(byte)) {
hasDigits = true
position++
} else {
break
}
}
}
if (!hasDigits) {
throwError(C.ERR_EXPECTED_INT_PART)
}
// Decimal part
if (position < numberLimit && getByte(position) == C.DOT_INT) {
position++
var hasDecimalDigits = false
while (position < numberLimit) {
val byte = data[position].toInt() and C.BYTE_MASK
if (isDigit(byte)) {
hasDecimalDigits = true
position++
} else {
break
}
}
if (!hasDecimalDigits) {
throwError(C.ERR_EXPECTED_DECIMAL_DIGITS)
}
}
// Exponent part
if (position < numberLimit) {
val byte = getByte(position)
if (byte == C.EXP_LOWER_INT || byte == C.EXP_UPPER_INT) {
var newPos = position + 1
position = newPos
if (newPos < numberLimit) {
val sign = getByte(newPos)
if (sign == C.PLUS_INT || sign == C.MINUS_INT) {
newPos++
position = newPos
}
}
var hasExpDigits = false
while (position < numberLimit) {
val byteCode = data[position].toInt() and C.BYTE_MASK
if (isDigit(byteCode)) {
hasExpDigits = true
position++
} else {
break
}
}
if (!hasExpDigits) {
throwError(C.ERR_EXPECTED_EXPONENT_DIGITS)
}
}
}
if (isQuoted) {
consumeNumericCoercionFooter()
}
nextTokenByte = -1
}