Coverage Summary for Class: GhostJsonStringReaderSubsystemKt (com.ghost.serialization.parser)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| GhostJsonStringReaderSubsystemKt |
100%
(1/1)
|
93.1%
(27/29)
|
64%
(171/267)
|
78.1%
(303/388)
|
69.7%
(1320/1893)
|
@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.parser.GhostHeuristics.initialCollectionCapacity
import com.ghost.serialization.InternalGhostApi
fun GhostJsonStringReader.beginObject() {
if (nextNonWhitespace() != C.OPEN_OBJ_INT) {
throwError(C.ERR_EXPECTED_BEGIN_OBJ)
}
depth++
if (depth > maxDepth) {
throwError(C.ERR_DEPTH_EXCEEDED)
}
if (depth < C.MAX_BITMASK_DEPTH) {
val bit = C.BITMASK_UNIT shl depth
needsCommaMask = needsCommaMask and bit.inv()
commaConsumedMask = commaConsumedMask and bit.inv()
}
}
fun GhostJsonStringReader.endObject() {
if (nextNonWhitespace() != C.CLOSE_OBJ_INT) {
throwError(C.ERR_EXPECTED_END_OBJ)
}
if (depth > 0) depth--
}
fun GhostJsonStringReader.beginArray() {
if (nextNonWhitespace() != C.OPEN_ARR_INT) {
throwError(C.ERR_EXPECTED_BEGIN_ARR)
}
depth++
if (depth > maxDepth) {
throwError(C.ERR_DEPTH_EXCEEDED)
}
if (depth < C.MAX_BITMASK_DEPTH) {
val bit = C.BITMASK_UNIT shl depth
needsCommaMask = needsCommaMask and bit.inv()
commaConsumedMask = commaConsumedMask and bit.inv()
}
}
fun GhostJsonStringReader.endArray() {
if (nextNonWhitespace() != C.CLOSE_ARR_INT) {
throwError(C.ERR_EXPECTED_END_ARR)
}
if (depth > 0) depth--
}
fun GhostJsonStringReader.hasNext(): Boolean {
val token = peekNextToken()
if (
token == C.CLOSE_ARR_INT ||
token == C.CLOSE_OBJ_INT ||
token == C.MATCH_END
) {
return false
}
if (strictMode && depth < C.MAX_BITMASK_DEPTH) {
val bit = C.BITMASK_UNIT shl depth
if ((commaConsumedMask and bit) != C.RESULT_NONE) {
if (token == C.COMMA_INT) {
commaConsumedMask = commaConsumedMask and bit.inv()
needsCommaMask = needsCommaMask or bit
}
}
if ((commaConsumedMask and bit) != C.RESULT_NONE) {
commaConsumedMask = commaConsumedMask and bit.inv()
needsCommaMask = needsCommaMask or bit
} else {
val required = (needsCommaMask and bit) != C.RESULT_NONE
if (token == C.COMMA_INT) {
if (!required) {
throwError(C.ERR_UNEXPECTED_COMMA)
}
internalSkip(1)
val next = peekNextToken()
if (next == C.CLOSE_ARR_INT || next == C.CLOSE_OBJ_INT) {
throwError(C.ERR_TRAILING_COMMA)
}
commaConsumedMask = commaConsumedMask or bit
needsCommaMask = needsCommaMask and bit.inv()
} else {
if (required) {
throwError(C.ERR_EXPECTED_COMMA)
}
needsCommaMask = needsCommaMask or bit
}
}
} else {
if (token == C.COMMA_INT) {
internalSkip(1)
val next = peekNextToken()
if (next == C.CLOSE_ARR_INT || next == C.CLOSE_OBJ_INT) {
throwError(C.ERR_TRAILING_COMMA)
}
}
}
return true
}
fun GhostJsonStringReader.nextKey(): String? {
val token = peekNextToken()
if (token == C.CLOSE_OBJ_INT) {
return null
}
if (strictMode && depth < C.MAX_BITMASK_DEPTH) {
val bit = C.BITMASK_UNIT shl depth
if ((commaConsumedMask and bit) != C.RESULT_NONE) {
commaConsumedMask = commaConsumedMask and bit.inv()
needsCommaMask = needsCommaMask or bit
} else {
val required = (needsCommaMask and bit) != C.RESULT_NONE
if (token == C.COMMA_INT) {
if (!required) {
throwError(C.ERR_UNEXPECTED_COMMA)
}
internalSkip(1)
if (peekNextToken() == C.CLOSE_OBJ_INT) {
throwError(C.ERR_TRAILING_COMMA)
}
needsCommaMask = needsCommaMask or bit
} else {
if (required) {
throwError(C.ERR_EXPECTED_COMMA_OR_CLOSE_OBJ)
}
needsCommaMask = needsCommaMask or bit
}
}
} else {
if (token == C.COMMA_INT) {
internalSkip(1)
if (peekNextToken() == C.CLOSE_OBJ_INT) {
throwError(C.ERR_TRAILING_COMMA)
}
}
}
return readQuotedString()
}
fun GhostJsonStringReader.consumeKeySeparator() {
if (nextNonWhitespace() != C.COLON_INT) {
throwError(C.ERR_EXPECTED_COLON)
}
}
fun GhostJsonStringReader.consumeArraySeparator() {
if (strictMode && depth < C.MAX_BITMASK_DEPTH) {
val bit = C.BITMASK_UNIT shl depth
if ((commaConsumedMask and bit) != C.RESULT_NONE) {
commaConsumedMask = commaConsumedMask and bit.inv()
needsCommaMask = needsCommaMask or bit
return
}
val token = peekNextToken()
val required = (needsCommaMask and bit) != C.RESULT_NONE
if (token == C.COMMA_INT) {
internalSkip(1)
val next = peekNextToken()
if (next == C.CLOSE_ARR_INT || next == C.CLOSE_OBJ_INT) {
throwError(C.ERR_TRAILING_COMMA)
}
commaConsumedMask = commaConsumedMask or bit
} else if (required) {
if (token != C.CLOSE_ARR_INT && token != C.CLOSE_OBJ_INT) {
throwError(C.ERR_EXPECTED_COMMA_OR_CLOSE_ARR)
}
} else {
if (token != C.CLOSE_ARR_INT && token != C.CLOSE_OBJ_INT) {
throwError(C.ERR_EXPECTED_COMMA_OR_CLOSE_ARR)
}
}
needsCommaMask = needsCommaMask or bit
} else {
val token = peekNextToken()
if (token == C.COMMA_INT) {
internalSkip(1)
if (peekNextToken() == C.CLOSE_ARR_INT) {
throwError(C.ERR_TRAILING_COMMA)
}
}
}
}
fun GhostJsonStringReader.nextBoolean(): Boolean {
val token = peekNextToken()
if (token == C.TRUE_CHAR_INT) {
skipAndValidateLiteral(C.TRUE_BS)
return true
}
if (token == C.FALSE_CHAR_INT) {
skipAndValidateLiteral(C.FALSE_BS)
return false
}
if (coerceBooleans) {
if (token == C.ONE_INT) {
internalSkip(1)
return true
}
if (token == C.ZERO_INT) {
internalSkip(1)
return false
}
if (token == C.QUOTE_INT) {
return matchCoerceBooleanBytes()
}
}
throwError(C.ERR_EXPECTED_BOOLEAN)
}
fun GhostJsonStringReader.nextString(): String = readQuotedString()
/**
* Reads a JSON string value that must contain exactly one [Char].
*/
fun GhostJsonStringReader.nextChar(): Char {
if (nextNonWhitespace() != C.QUOTE_INT) {
throwError(C.ERR_EXPECTED_QUOTE)
}
val start = position
val end = findClosingQuote(start, limit)
if (end != C.MATCH_END) {
val length = end - start
position = end + C.SINGLE_CHAR_SIZE
nextTokenByte = C.RESET_TOKEN_BYTE
when {
length == 0 -> throwError(C.ERR_EXPECTED_SINGLE_CHAR_STRING)
length == C.SINGLE_CHAR_JSON_LENGTH -> return rawData[start]
else -> throwError(C.ERR_SINGLE_CHAR_STRING_WRONG_LENGTH + length)
}
}
position = start - 1
val decoded = readQuotedString()
if (decoded.length != C.SINGLE_CHAR_JSON_LENGTH) {
throwError(C.ERR_SINGLE_CHAR_STRING_WRONG_LENGTH + decoded.length)
}
return decoded[0]
}
fun GhostJsonStringReader.isNextNullValue(): Boolean = peekNextToken() == C.NULL_CHAR_INT
fun GhostJsonStringReader.consumeNull() {
skipAndValidateLiteral(C.NULL_BS)
}
internal inline fun GhostJsonStringReader.findClosingQuote(start: Int, lim: Int): Int {
var currentPosition = start
val chars = rawChars
val escapeMasks = C.ESCAPE_MASKS
val unrollStep = 4
val indexOffset1 = 1
val indexOffset2 = 2
val indexOffset3 = 3
val localAsciiLimit = C.ASCII_LIMIT
val localBitmaskShift = C.BITMASK_SHIFT
val localBitmaskIndexMask = C.BITMASK_INDEX_MASK
val localBitmaskUnit = C.BITMASK_UNIT
val localResultNone = C.RESULT_NONE
val localQuoteInt = C.QUOTE_INT
val localMatchEnd = C.MATCH_END
while (currentPosition + indexOffset3 < lim) {
val byte0 = chars[currentPosition].code
if (byte0 < localAsciiLimit &&
((escapeMasks[byte0 shr localBitmaskShift] shr
(byte0 and localBitmaskIndexMask)) and localBitmaskUnit != localResultNone)
) {
if (byte0 == localQuoteInt) return currentPosition
return localMatchEnd
}
val byte1 = chars[currentPosition + indexOffset1].code
if (byte1 < localAsciiLimit &&
((escapeMasks[byte1 shr localBitmaskShift] shr
(byte1 and localBitmaskIndexMask)) and localBitmaskUnit != localResultNone)
) {
if (byte1 == localQuoteInt) return currentPosition + indexOffset1
return localMatchEnd
}
val byte2 = chars[currentPosition + indexOffset2].code
if (byte2 < localAsciiLimit &&
((escapeMasks[byte2 shr localBitmaskShift] shr
(byte2 and localBitmaskIndexMask)) and localBitmaskUnit != localResultNone)
) {
if (byte2 == localQuoteInt) return currentPosition + indexOffset2
return localMatchEnd
}
val byte3 = chars[currentPosition + indexOffset3].code
if (byte3 < localAsciiLimit &&
((escapeMasks[byte3 shr localBitmaskShift] shr
(byte3 and localBitmaskIndexMask)) and localBitmaskUnit != localResultNone)
) {
if (byte3 == localQuoteInt) return currentPosition + indexOffset3
return localMatchEnd
}
currentPosition += unrollStep
}
while (currentPosition < lim) {
val singleByte = chars[currentPosition].code
if (singleByte < localAsciiLimit &&
((escapeMasks[singleByte shr localBitmaskShift] shr
(singleByte and localBitmaskIndexMask)) and localBitmaskUnit != localResultNone)
) {
if (singleByte == localQuoteInt) {
return currentPosition
}
return localMatchEnd
}
currentPosition++
}
return localMatchEnd
}
private fun GhostJsonStringReader.matchCoerceBooleanBytes(): Boolean {
val chars = rawChars
val lim = limit
val contentStart = position + 1
val end = findClosingQuote(contentStart, lim)
if (end == -1) throwError(C.UNTERMINATED_STRING_ERROR)
val length = end - contentStart
position = end + 1
nextTokenByte = C.RESET_TOKEN_BYTE
return matchCoerceBooleanBytes(
start = contentStart,
length = length,
onError = { throwError(C.ERR_EXPECTED_BOOLEAN) },
getByte = { chars[it].code },
)
}
fun GhostJsonStringReader.selectNameAndConsume(options: JsonReaderOptions): Int =
internalSelect(options, consumeSeparator = true)
fun GhostJsonStringReader.selectString(options: JsonReaderOptions): Int =
internalSelect(options, consumeSeparator = false)
private fun GhostJsonStringReader.internalSelect(options: JsonReaderOptions, consumeSeparator: Boolean): Int {
var token = peekNextToken()
if (token == C.CLOSE_OBJ_INT) {
return -1
}
token = selectValidateCommas(token, consumeSeparator)
if (token != C.QUOTE_INT) {
throwExpectedKeyOrStringError(consumeSeparator)
}
val start = position + 1
val end = findClosingQuote(start, limit)
if (end == -1) {
throwUnterminatedStringError()
}
val length = end - start
val key = computeKeyHash(start, length, options.hasCollisions)
val dispatchTable = options.stringDispatch
val hasIndex = ((key * options.multiplier + length) shr options.shift) and (dispatchTable.size - 1)
val index = dispatchTable[hasIndex]
if (index != C.MATCH_END) {
if (verifyKeyMatch(start, length, options.rawStrings[index], consumeSeparator)) {
return index
}
}
return handleSelectNoMatch(start, end, length, consumeSeparator)
}
private fun GhostJsonStringReader.selectValidateCommas(token: Int, consumeSeparator: Boolean): Int {
var currentToken = token
if (strictMode && depth < C.MAX_BITMASK_DEPTH) {
val bit = C.BITMASK_UNIT shl depth
if ((commaConsumedMask and bit) != C.RESULT_NONE) {
commaConsumedMask = commaConsumedMask and bit.inv()
needsCommaMask = needsCommaMask or bit
} else {
val required = (needsCommaMask and bit) != C.RESULT_NONE
if (currentToken == C.COMMA_INT) {
if (!required) {
throwError(C.ERR_UNEXPECTED_COMMA)
}
internalSkip(1)
currentToken = peekNextToken()
if (currentToken == C.CLOSE_OBJ_INT) {
throwError(C.ERR_TRAILING_COMMA)
}
commaConsumedMask = commaConsumedMask and bit.inv()
needsCommaMask = needsCommaMask or bit
} else {
if (required && consumeSeparator) {
throwError(C.ERR_EXPECTED_COMMA_OR_CLOSE_OBJ)
}
needsCommaMask = needsCommaMask or bit
}
}
} else {
if (currentToken == C.COMMA_INT) {
internalSkip(1)
currentToken = peekNextToken()
if (currentToken == C.CLOSE_OBJ_INT) {
throwError(C.ERR_TRAILING_COMMA)
}
}
}
return currentToken
}
private fun GhostJsonStringReader.handleSelectNoMatch(start: Int, end: Int, length: Int, consumeSeparator: Boolean): Int {
val newPos = end + 1
position = newPos
nextTokenByte = C.MATCH_END
if (consumeSeparator) {
if (newPos < limit && getByte(newPos) == C.COLON_INT) {
position = newPos + 1
} else {
consumeKeySeparator()
}
} else if (strictMode) {
val unknownKey = rawData.substring(start, end)
throwError("${C.STRICT_MODE_UNKNOWN_FIELD}$unknownKey")
}
return C.MATCH_NONE
}
private fun GhostJsonStringReader.throwExpectedKeyOrStringError(consumeSeparator: Boolean) {
throwError(if (consumeSeparator) C.ERR_EXPECTED_KEY else C.ERR_EXPECTED_STRING)
}
private fun GhostJsonStringReader.throwUnterminatedStringError() {
throwError(C.UNTERMINATED_STRING_ERROR)
}
private inline fun GhostJsonStringReader.computeKeyHash(start: Int, length: Int, hasCollisions: Boolean): Int {
var key = 0
val chars = rawChars
if (length >= 4) {
val byte0 = chars[start].code
val byte1 = chars[start + 1].code
val byte2 = chars[start + 2].code
val byte3 = chars[start + 3].code
key = byte0 or (byte1 shl C.SHIFT_8) or (byte2 shl C.SHIFT_16) or (byte3 shl C.SHIFT_24)
if (hasCollisions) {
var ci = C.UNICODE_HEX_LENGTH
while (ci < length) { key = key * C.COLLISION_HASH_MULTIPLIER + chars[start + ci].code; ci++ }
}
} else {
if (length >= 1) {
key = key or chars[start].code
}
if (length >= 2) {
key = key or (chars[start + 1].code shl C.SHIFT_8)
}
if (length >= 3) {
key = key or (chars[start + 2].code shl C.SHIFT_16)
}
}
return key
}
private inline fun GhostJsonStringReader.verifyKeyMatch(
start: Int,
length: Int,
expected: String,
consumeSeparator: Boolean
): Boolean {
if (expected.length == length) {
val chars = rawChars
var index = 0
while (index + 3 < length) {
if (chars[start + index] != expected[index]) return false
if (chars[start + index + 1] != expected[index + 1]) return false
if (chars[start + index + 2] != expected[index + 2]) return false
if (chars[start + index + 3] != expected[index + 3]) return false
index += 4
}
while (index < length) {
if (chars[start + index] != expected[index]) return false
index++
}
val endPos = start + length
val newPos = endPos + C.SINGLE_CHAR_SIZE
position = newPos
nextTokenByte = C.RESET_TOKEN_BYTE
if (consumeSeparator) {
if (newPos < limit) {
val colonToken = chars[newPos].code
if (colonToken == C.COLON_INT) {
position = newPos + C.SINGLE_CHAR_SIZE
} else {
consumeKeySeparator()
}
} else {
consumeKeySeparator()
}
}
return true
}
return false
}
fun GhostJsonStringReader.peekStringField(name: String): String? {
return GhostDiscriminatorPeeker.peekChars(
chars = rawChars,
rawData = rawData,
start = position,
limit = limit,
key = name,
)
}
fun GhostJsonStringReader.skipValue() {
val token = peekNextToken()
when (token) {
C.OPEN_OBJ_INT -> {
beginObject()
while (hasNext()) {
if (peekNextToken() != C.QUOTE_INT) {
throwError(C.ERR_EXPECTED_KEY)
}
skipQuotedString()
consumeKeySeparator()
skipValue()
}
endObject()
}
C.OPEN_ARR_INT -> {
beginArray()
while (hasNext()) {
skipValue()
}
endArray()
}
C.QUOTE_INT -> {
skipQuotedString()
}
C.TRUE_CHAR_INT -> {
skipAndValidateLiteral(C.TRUE_BS)
}
C.FALSE_CHAR_INT -> {
skipAndValidateLiteral(C.FALSE_BS)
}
C.NULL_CHAR_INT -> {
skipAndValidateLiteral(C.NULL_BS)
}
else -> {
skipNumber()
}
}
}
inline fun <T> GhostJsonStringReader.readList(crossinline itemParser: () -> T): List<T> {
beginArray()
if (peekNextToken() == C.CLOSE_ARR_INT) {
endArray()
return emptyList()
}
val list = ArrayList<T>(initialCollectionCapacity)
val maxSize = maxCollectionSize
while (true) {
list.add(itemParser())
val next = nextNonWhitespace()
if (next == C.CLOSE_ARR_INT) {
if (depth > 0) {
depth--
}
break
}
if (next != C.COMMA_INT) {
throwError("${C.ERR_EXPECTED_COMMA_OR_CLOSE_ARR} but found $next")
}
if (list.size > maxSize) {
throwError("${C.ERR_MAX_COLLECTION_SIZE} ($maxSize)")
}
}
return list
}
inline fun <T> GhostJsonStringReader.readSet(crossinline itemParser: () -> T): Set<T> {
beginArray()
if (peekNextToken() == C.CLOSE_ARR_INT) {
endArray()
return emptySet()
}
val set = HashSet<T>(initialCollectionCapacity)
val maxSize = maxCollectionSize
while (true) {
set.add(itemParser())
val next = nextNonWhitespace()
if (next == C.CLOSE_ARR_INT) {
if (depth > 0) {
depth--
}
break
}
if (next != C.COMMA_INT) {
throwError("${C.ERR_EXPECTED_COMMA_OR_CLOSE_ARR} but found $next")
}
if (set.size > maxSize) {
throwError("${C.ERR_MAX_COLLECTION_SIZE} ($maxSize)")
}
}
return set
}
inline fun <K, V> GhostJsonStringReader.readMap(
crossinline keyParser: () -> K,
crossinline valueParser: () -> V
): Map<K, V> {
beginObject()
if (peekNextToken() == C.CLOSE_OBJ_INT) {
endObject()
return emptyMap()
}
val map = HashMap<K, V>(initialCollectionCapacity)
val maxSize = maxCollectionSize
while (true) {
val key = keyParser()
consumeKeySeparator()
val value = valueParser()
map[key] = value
val next = nextNonWhitespace()
if (next == C.CLOSE_OBJ_INT) {
if (depth > 0) {
depth--
}
break
}
if (next != C.COMMA_INT) {
throwError("${C.ERR_EXPECTED_COMMA_OR_CLOSE_OBJ} but found $next")
}
if (depth < C.MAX_BITMASK_DEPTH) {
val bit = C.BITMASK_UNIT shl depth
commaConsumedMask = commaConsumedMask and bit.inv()
needsCommaMask = needsCommaMask or bit
}
if (map.size > maxSize) {
throwError("${C.ERR_MAX_COLLECTION_SIZE} ($maxSize)")
}
}
return map
}