Coverage Summary for Class: ProtoJsonFlatReaderNumbersKt (com.ghost.serialization.parser)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| ProtoJsonFlatReaderNumbersKt |
100%
(1/1)
|
71.4%
(5/7)
|
41.1%
(37/90)
|
63.9%
(53/83)
|
69.5%
(392/564)
|
@file:OptIn(InternalGhostApi::class)
package com.ghost.serialization.parser
import com.ghost.serialization.InternalGhostApi
import com.ghost.serialization.parser.JsonReaderOptions
import com.ghost.serialization.parser.nextKey
import com.ghost.serialization.parser.nextString
import com.ghost.serialization.parser.skipValue
import com.ghost.serialization.parser.GhostJsonConstants as C
internal fun GhostProtoJsonFlatReader.nextProtoFloat(): Float {
val token = peekNextToken()
if (token == C.QUOTE_INT) {
val start = position + 1
if (start + 3 <= limit) {
val b0 = getByte(start)
val b1 = getByte(start + 1)
val b2 = getByte(start + 2)
// check closing quote
if (start + C.NAN_QUOTED_LEN - 1 <= limit && getByte(start + C.NAN_QUOTED_LEN - 2) == C.QUOTE_INT) {
if (b0 == C.N_UPPER_BYTE_INT && b1 == C.A_LOWER_BYTE_INT && b2 == C.N_UPPER_BYTE_INT) { // "NaN"
position = start + C.NAN_QUOTED_LEN - 1
nextTokenByte = C.RESET_TOKEN_BYTE
return Float.NaN
}
}
if (start + C.INFINITY_QUOTED_LEN - 1 <= limit && getByte(start + C.INFINITY_QUOTED_LEN - 2) == C.QUOTE_INT) {
if (b0 == C.I_BYTE_INT && matchInfinityBytes(start)) { // "Infinity"
position = start + C.INFINITY_QUOTED_LEN - 1
nextTokenByte = C.RESET_TOKEN_BYTE
return Float.POSITIVE_INFINITY
}
}
if (start + C.NEG_INFINITY_QUOTED_LEN - 1 <= limit && getByte(start + C.NEG_INFINITY_QUOTED_LEN - 2) == C.QUOTE_INT) {
if (b0 == C.MINUS_INT && getByte(start + 1) == C.I_BYTE_INT && matchInfinityBytes(start + 1)) { // "-Infinity"
position = start + C.NEG_INFINITY_QUOTED_LEN - 1
nextTokenByte = C.RESET_TOKEN_BYTE
return Float.NEGATIVE_INFINITY
}
}
}
val prev = coerceStringsToNumbers
coerceStringsToNumbers = true
try {
return this.nextFloatExtension()
} finally {
coerceStringsToNumbers = prev
}
}
return this.nextFloatExtension()
}
internal fun GhostProtoJsonFlatReader.nextProtoDouble(): Double {
val token = peekNextToken()
if (token == C.QUOTE_INT) {
val start = position + 1
if (start + 3 <= limit) {
val b0 = getByte(start)
val b1 = getByte(start + 1)
val b2 = getByte(start + 2)
if (start + C.NAN_QUOTED_LEN - 1 <= limit && getByte(start + C.NAN_QUOTED_LEN - 2) == C.QUOTE_INT) {
if (b0 == C.N_UPPER_BYTE_INT && b1 == C.A_LOWER_BYTE_INT && b2 == C.N_UPPER_BYTE_INT) { // "NaN"
position = start + C.NAN_QUOTED_LEN - 1
nextTokenByte = C.RESET_TOKEN_BYTE
return Double.NaN
}
}
if (start + C.INFINITY_QUOTED_LEN - 1 <= limit && getByte(start + C.INFINITY_QUOTED_LEN - 2) == C.QUOTE_INT) {
if (b0 == C.I_BYTE_INT && matchInfinityBytes(start)) { // "Infinity"
position = start + C.INFINITY_QUOTED_LEN - 1
nextTokenByte = C.RESET_TOKEN_BYTE
return Double.POSITIVE_INFINITY
}
}
if (start + C.NEG_INFINITY_QUOTED_LEN - 1 <= limit && getByte(start + C.NEG_INFINITY_QUOTED_LEN - 2) == C.QUOTE_INT) {
if (b0 == C.MINUS_INT && getByte(start + 1) == C.I_BYTE_INT && matchInfinityBytes(start + 1)) { // "-Infinity"
position = start + C.NEG_INFINITY_QUOTED_LEN - 1
nextTokenByte = C.RESET_TOKEN_BYTE
return Double.NEGATIVE_INFINITY
}
}
}
val prev = coerceStringsToNumbers
coerceStringsToNumbers = true
try {
return this.nextDoubleExtension()
} finally {
coerceStringsToNumbers = prev
}
}
return this.nextDoubleExtension()
}
private fun GhostProtoJsonFlatReader.matchInfinityBytes(start: Int): Boolean {
// Infinity has 8 characters: I, n, f, i, n, i, t, y
// We already checked 'I' (73) in the caller, verify remaining 7 bytes
return getByte(start + 1) == C.N_LOWER_BYTE_INT &&
getByte(start + 2) == C.F_LOWER_BYTE_INT &&
getByte(start + 3) == C.I_LOWER_BYTE_INT &&
getByte(start + 4) == C.N_LOWER_BYTE_INT &&
getByte(start + 5) == C.I_LOWER_BYTE_INT &&
getByte(start + 6) == C.T_LOWER_BYTE_INT &&
getByte(start + 7) == C.Y_LOWER_BYTE_INT
}
internal fun GhostProtoJsonFlatReader.nextProtoInt64(): Long {
val token = peekNextToken()
return if (token == C.QUOTE_INT) {
val prev = coerceStringsToNumbers
coerceStringsToNumbers = true
try {
this.nextLongExtension()
} finally {
coerceStringsToNumbers = prev
}
} else {
this.nextDoubleExtension().toLong()
}
}
internal fun GhostProtoJsonFlatReader.readProtoUInt32(): Long {
val value = nextProtoInt64()
if (value < 0L || value > C.PROTO_UINT32_MAX) {
throwError(C.ERR_PROTO_UINT32_OVERFLOW)
}
return value
}
/**
* Full `uint64` range read. The canonical proto3 JSON form is always a quoted decimal string
* (parsed directly as [ULong], no range limit); a bare JSON number falls back to the existing
* int64 path, which is only safe for values within [Long.MAX_VALUE].
*/
internal fun GhostProtoJsonFlatReader.readProtoUInt64(): ULong {
val token = peekNextToken()
return if (token == C.QUOTE_INT) {
nextString().toULong()
} else {
nextProtoInt64().toULong()
}
}
internal fun GhostProtoJsonFlatReader.readProtoEnum(options: JsonReaderOptions): Int {
val token = peekNextToken()
if (token == C.QUOTE_INT) {
val index = selectString(options)
if (index != C.MATCH_NONE) {
return index
}
throwError(C.ERR_UNKNOWN_ENUM)
} else {
// Read as integer value index
return nextInt()
}
}