| /* |
| * Copyright 2021 Google Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| @file:Suppress("NOTHING_TO_INLINE") |
| |
| package com.google.flatbuffers.kotlin |
| |
| import com.google.flatbuffers.kotlin.FlexBuffersBuilder.Companion.SHARE_KEYS_AND_STRINGS |
| import kotlin.experimental.and |
| import kotlin.jvm.JvmInline |
| import kotlin.math.pow |
| |
| /** |
| * Returns a minified version of this FlexBuffer as a JSON. |
| */ |
| public fun Reference.toJson(): String = ArrayReadWriteBuffer(1024).let { |
| toJson(it) |
| val data = it.data() // it.getString(0, it.writePosition) |
| return data.decodeToString(0, it.writePosition) |
| } |
| |
| /** |
| * Returns a minified version of this FlexBuffer as a JSON. |
| * @param out [ReadWriteBuffer] the JSON will be written. |
| */ |
| public fun Reference.toJson(out: ReadWriteBuffer) { |
| when (type) { |
| T_STRING -> { |
| val start = buffer.indirect(end, parentWidth) |
| val size = buffer.readULong(start - byteWidth, byteWidth).toInt() |
| out.jsonEscape(buffer, start, size) |
| } |
| T_KEY -> { |
| val start = buffer.indirect(end, parentWidth) |
| val end = buffer.findFirst(0.toByte(), start) |
| out.jsonEscape(buffer, start, end - start) |
| } |
| T_BLOB -> { |
| val blob = toBlob() |
| out.jsonEscape(out, blob.end, blob.size) |
| } |
| T_INT -> out.put(toLong().toString()) |
| T_UINT -> out.put(toULong().toString()) |
| T_FLOAT -> out.put(toDouble().toString()) |
| T_NULL -> out.put("null") |
| T_BOOL -> out.put(toBoolean().toString()) |
| T_MAP -> toMap().toJson(out) |
| T_VECTOR, T_VECTOR_BOOL, T_VECTOR_FLOAT, T_VECTOR_INT, |
| T_VECTOR_UINT, T_VECTOR_KEY, T_VECTOR_STRING_DEPRECATED -> toVector().toJson(out) |
| else -> error("Unable to convert type ${type.typeToString()} to JSON") |
| } |
| } |
| |
| /** |
| * Returns a minified version of this FlexBuffer as a JSON. |
| */ |
| public fun Map.toJson(): String = ArrayReadWriteBuffer(1024).let { toJson(it); it.toString() } |
| |
| /** |
| * Returns a minified version of this FlexBuffer as a JSON. |
| * @param out [ReadWriteBuffer] the JSON will be written. |
| */ |
| public fun Map.toJson(out: ReadWriteBuffer) { |
| out.put('{'.code.toByte()) |
| // key values pairs |
| for (i in 0 until size) { |
| val key = keyAt(i) |
| out.jsonEscape(buffer, key.start, key.sizeInBytes) |
| out.put(':'.code.toByte()) |
| get(i).toJson(out) |
| if (i != size - 1) { |
| out.put(','.code.toByte()) |
| } |
| } |
| // close bracket |
| out.put('}'.code.toByte()) |
| } |
| |
| /** |
| * Returns a minified version of this FlexBuffer as a JSON. |
| */ |
| public fun Vector.toJson(): String = ArrayReadWriteBuffer(1024).let { toJson(it); it.toString() } |
| |
| /** |
| * Returns a minified version of this FlexBuffer as a JSON. |
| * @param out that the JSON is being concatenated. |
| */ |
| public fun Vector.toJson(out: ReadWriteBuffer) { |
| out.put('['.code.toByte()) |
| for (i in indices) { |
| get(i).toJson(out) |
| if (i != size - 1) { |
| out.put(','.code.toByte()) |
| } |
| } |
| out.put(']'.code.toByte()) |
| } |
| |
| /** |
| * JSONParser class is used to parse a JSON as FlexBuffers. Calling [JSONParser.parse] fiils [output] |
| * and returns a [Reference] ready to be used. |
| */ |
| @ExperimentalUnsignedTypes |
| public class JSONParser(public var output: FlexBuffersBuilder = FlexBuffersBuilder(1024, SHARE_KEYS_AND_STRINGS)) { |
| private var readPos = 0 |
| private var scopes = ScopeStack() |
| |
| /** |
| * Parse a json as [String] and returns a [Reference] to a FlexBuffer. |
| */ |
| public fun parse(data: String): Reference = parse(ArrayReadBuffer(data.encodeToByteArray())) |
| |
| /** |
| * Parse a json as [ByteArray] and returns a [Reference] to a FlexBuffer. |
| */ |
| public fun parse(data: ByteArray): Reference = parse(ArrayReadBuffer(data)) |
| |
| /** |
| * Parse a json as [ReadBuffer] and returns a [Reference] to a FlexBuffer. |
| */ |
| public fun parse(data: ReadBuffer): Reference { |
| reset() |
| parseValue(data, nextToken(data), null) |
| if (readPos < data.limit) { |
| val tok = skipWhitespace(data) |
| if (tok != CHAR_EOF) { |
| makeError(data, "Extraneous charaters after parse has finished", tok) |
| } |
| } |
| output.finish() |
| return getRoot(output.buffer) |
| } |
| |
| private fun parseValue(data: ReadBuffer, token: Token, key: String? = null): FlexBufferType { |
| return when (token) { |
| TOK_BEGIN_OBJECT -> parseObject(data, key) |
| TOK_BEGIN_ARRAY -> parseArray(data, key) |
| TOK_TRUE -> T_BOOL.also { output[key] = true } |
| TOK_FALSE -> T_BOOL.also { output[key] = false } |
| TOK_NULL -> T_NULL.also { output.putNull(key) } |
| TOK_BEGIN_QUOTE -> parseString(data, key) |
| TOK_NUMBER -> parseNumber(data, data.data(), key) |
| else -> makeError(data, "Unexpected Character while parsing", 'x'.code.toByte()) |
| } |
| } |
| |
| private fun parseObject(data: ReadBuffer, key: String? = null): FlexBufferType { |
| this.scopes.push(SCOPE_OBJ_EMPTY) |
| |
| val fPos = output.startMap() |
| val limit = data.limit |
| while (readPos <= limit) { |
| when (val tok = nextToken(data)) { |
| TOK_END_OBJECT -> { |
| this.scopes.pop() |
| output.endMap(fPos, key); return T_MAP |
| } |
| TOK_BEGIN_QUOTE -> { |
| val childKey = readString(data) |
| parseValue(data, nextToken(data), childKey) |
| } |
| else -> makeError(data, "Expecting start of object key", tok) |
| } |
| } |
| makeError(data, "Unable to parse the object", "x".toByte()) |
| } |
| |
| private fun parseArray(data: ReadBuffer, key: String? = null): FlexBufferType { |
| this.scopes.push(SCOPE_ARRAY_EMPTY) |
| val fPos = output.startVector() |
| var elementType = T_INVALID |
| var multiType = false |
| val limit = data.limit |
| |
| while (readPos <= limit) { |
| when (val tok = nextToken(data)) { |
| TOK_END_ARRAY -> { |
| this.scopes.pop() |
| return if (!multiType && elementType.isScalar()) { |
| output.endTypedVector(fPos, key) |
| elementType.toElementTypedVector() |
| } else { |
| output.endVector(key, fPos) |
| T_VECTOR |
| } |
| } |
| |
| else -> { |
| val newType = parseValue(data, tok, null) |
| |
| if (elementType == T_INVALID) { |
| elementType = newType |
| } else if (newType != elementType) { |
| multiType = true |
| } |
| } |
| } |
| } |
| makeError(data, "Unable to parse the array") |
| } |
| |
| private fun parseNumber(data: ReadBuffer, array: ByteArray, key: String?): FlexBufferType { |
| val ary = array |
| var cursor = readPos |
| var c = data[readPos++] |
| var useDouble = false |
| val limit = ary.size |
| var sign = 1 |
| var double: Double |
| var long = 0L |
| var digits = 0 |
| |
| if (c == CHAR_MINUS) { |
| cursor++ |
| checkEOF(data, cursor) |
| c = ary[cursor] |
| sign = -1 |
| } |
| |
| // peek first byte |
| when (c) { |
| CHAR_0 -> { |
| cursor++ |
| if (cursor != limit) { |
| c = ary[cursor] |
| } |
| } |
| !in CHAR_0..CHAR_9 -> makeError(data, "Invalid Number", c) |
| else -> { |
| do { |
| val digit = c - CHAR_0 |
| // double = 10.0 * double + digit |
| long = 10 * long + digit |
| digits++ |
| cursor++ |
| if (cursor == limit) break |
| c = ary[cursor] |
| } while (c in CHAR_0..CHAR_9) |
| } |
| } |
| |
| var exponent = 0 |
| // If we find '.' we need to convert to double |
| if (c == CHAR_DOT) { |
| useDouble = true |
| checkEOF(data, cursor) |
| c = ary[++cursor] |
| if (c < CHAR_0 || c > CHAR_9) { |
| makeError(data, "Invalid Number", c) |
| } |
| do { |
| // double = double * 10 + (tok - CHAR_0) |
| long = 10 * long + (c - CHAR_0) |
| digits++ |
| --exponent |
| cursor++ |
| if (cursor == limit) break |
| c = ary[cursor] |
| } while (c in CHAR_0..CHAR_9) |
| } |
| |
| // If we find 'e' we need to convert to double |
| if (c == CHAR_e || c == CHAR_E) { |
| useDouble = true |
| ++cursor |
| checkEOF(data, cursor) |
| c = ary[cursor] |
| var negativeExponent = false |
| if (c == CHAR_MINUS) { |
| ++cursor |
| checkEOF(data, cursor) |
| negativeExponent = true |
| c = ary[cursor] |
| } else if (c == CHAR_PLUS) { |
| ++cursor |
| checkEOF(data, cursor) |
| c = ary[cursor] |
| } |
| if (c < CHAR_0 || c > CHAR_9) { |
| makeError(data, "Missing exponent", c) |
| } |
| var exp = 0 |
| do { |
| val digit = c - CHAR_0 |
| exp = 10 * exp + digit |
| ++cursor |
| if (cursor == limit) break |
| c = ary[cursor] |
| } while (c in CHAR_0..CHAR_9) |
| |
| exponent += if (negativeExponent) -exp else exp |
| } |
| |
| if (digits > 17 || exponent < -19 || exponent > 19) { |
| // if the float number is not simple enough |
| // we use language's Double parsing, which is slower but |
| // produce more expected results for extreme numbers. |
| val firstPos = readPos - 1 |
| val str = data.getString(firstPos, cursor - firstPos) |
| if (useDouble) { |
| double = str.toDouble() |
| output[key] = double |
| } else { |
| long = str.toLong() |
| output[key] = long |
| } |
| } else { |
| // this happens on single numbers outside any object |
| // or array |
| if (useDouble || exponent != 0) { |
| double = if (long == 0L) 0.0 else long.toDouble() * 10.0.pow(exponent) |
| double *= sign |
| output[key] = double |
| } else { |
| long *= sign |
| output[key] = long |
| } |
| } |
| readPos = cursor |
| return if (useDouble) T_FLOAT else T_INT |
| } |
| |
| private fun parseString(data: ReadBuffer, key: String?): FlexBufferType { |
| output[key] = readString(data) |
| return T_STRING |
| } |
| |
| private fun readString(data: ReadBuffer): String { |
| val limit = data.limit |
| if (data is ArrayReadBuffer) { |
| val ary = data.data() |
| // enables range check elimination |
| return readString(data, limit) { ary[it] } |
| } |
| return readString(data, limit) { data[it] } |
| } |
| |
| private inline fun readString(data: ReadBuffer, limit: Int, crossinline fetch: (Int) -> Byte): String { |
| var cursorPos = readPos |
| var foundEscape = false |
| var currentChar: Byte = 0 |
| // we loop over every 4 bytes until find any non-plain char |
| while (limit - cursorPos >= 4) { |
| currentChar = fetch(cursorPos) |
| if (!isPlainStringChar(currentChar)) { |
| foundEscape = true |
| break |
| } |
| currentChar = fetch(cursorPos + 1) |
| if (!isPlainStringChar(currentChar)) { |
| cursorPos += 1 |
| foundEscape = true |
| break |
| } |
| currentChar = fetch(cursorPos + 2) |
| if (!isPlainStringChar(currentChar)) { |
| cursorPos += 2 |
| foundEscape = true |
| break |
| } |
| currentChar = fetch(cursorPos + 3) |
| if (!isPlainStringChar(currentChar)) { |
| cursorPos += 3 |
| foundEscape = true |
| break |
| } |
| cursorPos += 4 |
| } |
| if (!foundEscape) { |
| // if non-plain string char is not found we loop over |
| // the remaining bytes |
| while (true) { |
| if (cursorPos >= limit) { |
| error("Unexpected end of string") |
| } |
| currentChar = fetch(cursorPos) |
| if (!isPlainStringChar(currentChar)) { |
| break |
| } |
| ++cursorPos |
| } |
| } |
| if (currentChar == CHAR_DOUBLE_QUOTE) { |
| val str = data.getString(readPos, cursorPos - readPos) |
| readPos = cursorPos + 1 |
| return str |
| } |
| if (currentChar in 0..0x1f) { |
| error("Illegal Codepoint") |
| } else { |
| // backslash or >0x7f |
| return readStringSlow(data, currentChar, cursorPos) |
| } |
| } |
| |
| private fun readStringSlow(data: ReadBuffer, first: Byte, lastPos: Int): String { |
| var cursorPos = lastPos |
| |
| var endOfString = lastPos |
| while (true) { |
| val pos = data.findFirst(CHAR_DOUBLE_QUOTE, endOfString) |
| when { |
| pos == -1 -> makeError(data, "Unexpected EOF, missing end of string '\"'", first) |
| data[pos - 1] == CHAR_BACKSLASH && data[pos - 2] != CHAR_BACKSLASH -> { |
| // here we are checking for double quotes preceded by backslash. eg \" |
| // we have to look past pos -2 to make sure that the backlash is not |
| // part of a previous escape, eg "\\" |
| endOfString = pos + 1 |
| } |
| else -> { |
| endOfString = pos; break |
| } |
| } |
| } |
| // copy everything before the escape |
| val builder = StringBuilder(data.getString(readPos, lastPos - readPos)) |
| while (true) { |
| when (val pos = data.findFirst(CHAR_BACKSLASH, cursorPos, endOfString)) { |
| -1 -> { |
| val doubleQuotePos = data.findFirst(CHAR_DOUBLE_QUOTE, cursorPos) |
| if (doubleQuotePos == -1) makeError(data, "Reached EOF before enclosing string", first) |
| val rest = data.getString(cursorPos, doubleQuotePos - cursorPos) |
| builder.append(rest) |
| readPos = doubleQuotePos + 1 |
| return builder.toString() |
| } |
| |
| else -> { |
| // we write everything up to \ |
| builder.append(data.getString(cursorPos, pos - cursorPos)) |
| val c = data[pos + 1] |
| builder.append(readEscapedChar(data, c, pos)) |
| cursorPos = pos + if (c == CHAR_u) 6 else 2 |
| } |
| } |
| } |
| } |
| |
| private inline fun isPlainStringChar(c: Byte): Boolean { |
| val flags = parseFlags |
| // return c in 0x20..0x7f && c != 0x22.toByte() && c != 0x5c.toByte() |
| return (flags[c.toInt() and 0xFF] and 1) != 0.toByte() |
| } |
| |
| private inline fun isWhitespace(c: Byte): Boolean { |
| val flags = parseFlags |
| // return c == '\r'.toByte() || c == '\n'.toByte() || c == '\t'.toByte() || c == ' '.toByte() |
| return (flags[c.toInt() and 0xFF] and 2) != 0.toByte() |
| } |
| |
| private fun reset() { |
| readPos = 0 |
| output.clear() |
| scopes.reset() |
| } |
| |
| private fun nextToken(data: ReadBuffer): Token { |
| val scope = this.scopes.last |
| |
| when (scope) { |
| SCOPE_ARRAY_EMPTY -> this.scopes.last = SCOPE_ARRAY_FILLED |
| SCOPE_ARRAY_FILLED -> { |
| when (val c = skipWhitespace(data)) { |
| CHAR_CLOSE_ARRAY -> return TOK_END_ARRAY |
| CHAR_COMMA -> Unit |
| else -> makeError(data, "Unfinished Array", c) |
| } |
| } |
| SCOPE_OBJ_EMPTY, SCOPE_OBJ_FILLED -> { |
| this.scopes.last = SCOPE_OBJ_KEY |
| // Look for a comma before the next element. |
| if (scope == SCOPE_OBJ_FILLED) { |
| when (val c = skipWhitespace(data)) { |
| CHAR_CLOSE_OBJECT -> return TOK_END_OBJECT |
| CHAR_COMMA -> Unit |
| else -> makeError(data, "Unfinished Object", c) |
| } |
| } |
| return when (val c = skipWhitespace(data)) { |
| CHAR_DOUBLE_QUOTE -> TOK_BEGIN_QUOTE |
| CHAR_CLOSE_OBJECT -> if (scope != SCOPE_OBJ_FILLED) { |
| TOK_END_OBJECT |
| } else { |
| makeError(data, "Expected Key", c) |
| } |
| else -> { |
| makeError(data, "Expected Key/Value", c) |
| } |
| } |
| } |
| SCOPE_OBJ_KEY -> { |
| this.scopes.last = SCOPE_OBJ_FILLED |
| when (val c = skipWhitespace(data)) { |
| CHAR_COLON -> Unit |
| else -> makeError(data, "Expect ${CHAR_COLON.print()}", c) |
| } |
| } |
| SCOPE_DOC_EMPTY -> this.scopes.last = SCOPE_DOC_FILLED |
| SCOPE_DOC_FILLED -> { |
| val c = skipWhitespace(data) |
| if (c != CHAR_EOF) |
| makeError(data, "Root object already finished", c) |
| return TOK_EOF |
| } |
| } |
| |
| val c = skipWhitespace(data) |
| when (c) { |
| CHAR_CLOSE_ARRAY -> if (scope == SCOPE_ARRAY_EMPTY) return TOK_END_ARRAY |
| CHAR_COLON -> makeError(data, "Unexpected character", c) |
| CHAR_DOUBLE_QUOTE -> return TOK_BEGIN_QUOTE |
| CHAR_OPEN_ARRAY -> return TOK_BEGIN_ARRAY |
| CHAR_OPEN_OBJECT -> return TOK_BEGIN_OBJECT |
| CHAR_t -> { |
| checkEOF(data, readPos + 2) |
| // 0x65757274 is equivalent to ['t', 'r', 'u', 'e' ] as a 4 byte Int |
| if (data.getInt(readPos - 1) != 0x65757274) { |
| makeError(data, "Expecting keyword \"true\"", c) |
| } |
| readPos += 3 |
| return TOK_TRUE |
| } |
| CHAR_n -> { |
| checkEOF(data, readPos + 2) |
| // 0x6c6c756e is equivalent to ['n', 'u', 'l', 'l' ] as a 4 byte Int |
| if (data.getInt(readPos - 1) != 0x6c6c756e) { |
| makeError(data, "Expecting keyword \"null\"", c) |
| } |
| readPos += 3 |
| return TOK_NULL |
| } |
| CHAR_f -> { |
| checkEOF(data, readPos + 3) |
| // 0x65736c61 is equivalent to ['a', 'l', 's', 'e' ] as a 4 byte Int |
| if (data.getInt(readPos) != 0x65736c61) { |
| makeError(data, "Expecting keyword \"false\"", c) |
| } |
| readPos += 4 |
| return TOK_FALSE |
| } |
| CHAR_0, CHAR_1, CHAR_2, CHAR_3, CHAR_4, CHAR_5, |
| CHAR_6, CHAR_7, CHAR_8, CHAR_9, CHAR_MINUS -> return TOK_NUMBER.also { |
| readPos-- // rewind one position so we don't lose first digit |
| } |
| } |
| makeError(data, "Expecting element", c) |
| } |
| |
| // keeps increasing [readPos] until finds a non-whitespace byte |
| private inline fun skipWhitespace(data: ReadBuffer): Byte { |
| val limit = data.limit |
| if (data is ArrayReadBuffer) { |
| // enables range check elimination |
| val ary = data.data() |
| return skipWhitespace(limit) { ary[it] } |
| } |
| return skipWhitespace(limit) { data[it] } |
| } |
| |
| private inline fun skipWhitespace(limit: Int, crossinline fetch: (Int) -> Byte): Byte { |
| var pos = readPos |
| while (pos < limit) { |
| val d = fetch(pos++) |
| if (!isWhitespace(d)) { |
| readPos = pos |
| return d |
| } |
| } |
| readPos = limit |
| return CHAR_EOF |
| } |
| |
| // byte1 is expected to be first char before `\` |
| private fun readEscapedChar(data: ReadBuffer, byte1: Byte, cursorPos: Int): Char { |
| return when (byte1) { |
| CHAR_u -> { |
| checkEOF(data, cursorPos + 1 + 4) |
| var result: Char = 0.toChar() |
| var i = cursorPos + 2 // cursorPos is on '\\', cursorPos + 1 is 'u' |
| val end = i + 4 |
| while (i < end) { |
| val part: Byte = data[i] |
| result = (result.code shl 4).toChar() |
| result += when (part) { |
| in CHAR_0..CHAR_9 -> part - CHAR_0 |
| in CHAR_a..CHAR_f -> part - CHAR_a + 10 |
| in CHAR_A..CHAR_F -> part - CHAR_A + 10 |
| else -> makeError(data, "Invalid utf8 escaped character", -1) |
| } |
| i++ |
| } |
| result |
| } |
| CHAR_b -> '\b' |
| CHAR_t -> '\t' |
| CHAR_r -> '\r' |
| CHAR_n -> '\n' |
| CHAR_f -> 12.toChar() // '\f' |
| CHAR_DOUBLE_QUOTE, CHAR_BACKSLASH, CHAR_FORWARDSLASH -> byte1.toInt().toChar() |
| else -> makeError(data, "Invalid escape sequence.", byte1) |
| } |
| } |
| |
| private fun Byte.print(): String = when (this) { |
| in 0x21..0x7E -> "'${this.toInt().toChar()}'" // visible ascii chars |
| CHAR_EOF -> "EOF" |
| else -> "'0x${this.toString(16)}'" |
| } |
| |
| private inline fun makeError(data: ReadBuffer, msg: String, tok: Byte? = null): Nothing { |
| val (line, column) = calculateErrorPosition(data, readPos) |
| if (tok != null) { |
| error("Error At ($line, $column): $msg, got ${tok.print()}") |
| } else { |
| error("Error At ($line, $column): $msg") |
| } |
| } |
| |
| private inline fun makeError(data: ReadBuffer, msg: String, tok: Token): Nothing { |
| val (line, column) = calculateErrorPosition(data, readPos) |
| error("Error At ($line, $column): $msg, got ${tok.print()}") |
| } |
| |
| private inline fun checkEOF(data: ReadBuffer, pos: Int) { |
| if (pos >= data.limit) |
| makeError(data, "Unexpected end of file", -1) |
| } |
| |
| private fun calculateErrorPosition(data: ReadBuffer, endPos: Int): Pair<Int, Int> { |
| var line = 1 |
| var column = 1 |
| var current = 0 |
| while (current < endPos - 1) { |
| if (data[current++] == CHAR_NEWLINE) { |
| ++line |
| column = 1 |
| } else { |
| ++column |
| } |
| } |
| return Pair(line, column) |
| } |
| } |
| |
| internal inline fun Int.toPaddedHex(): String = "\\u${this.toString(16).padStart(4, '0')}" |
| |
| private inline fun ReadWriteBuffer.jsonEscape(data: ReadBuffer, start: Int, size: Int) { |
| val replacements = JSON_ESCAPE_CHARS |
| put(CHAR_DOUBLE_QUOTE) |
| var last = start |
| val length: Int = size |
| val ary = data.data() |
| for (i in start until start + length) { |
| val c = ary[i].toUByte() |
| var replacement: ByteArray? |
| if (c.toInt() < 128) { |
| replacement = replacements[c.toInt()] |
| if (replacement == null) { |
| continue |
| } |
| } else { |
| continue |
| } |
| if (last < i) { |
| put(ary, last, i - last) |
| } |
| put(replacement, 0, replacement.size) |
| last = i + 1 |
| } |
| if (last < (last + length)) { |
| put(ary, last, (start + length) - last) |
| } |
| put(CHAR_DOUBLE_QUOTE) |
| } |
| |
| // Following escape strategy defined in RFC7159. |
| private val JSON_ESCAPE_CHARS: Array<ByteArray?> = arrayOfNulls<ByteArray>(128).apply { |
| this['\n'.code] = "\\n".encodeToByteArray() |
| this['\t'.code] = "\\t".encodeToByteArray() |
| this['\r'.code] = "\\r".encodeToByteArray() |
| this['\b'.code] = "\\b".encodeToByteArray() |
| this[0x0c] = "\\f".encodeToByteArray() |
| this['"'.code] = "\\\"".encodeToByteArray() |
| this['\\'.code] = "\\\\".encodeToByteArray() |
| for (i in 0..0x1f) { |
| this[i] = "\\u${i.toPaddedHex()}".encodeToByteArray() |
| } |
| } |
| |
| // Scope is used to the define current space that the scanner is operating. |
| @JvmInline |
| private value class Scope(val id: Int) |
| private val SCOPE_DOC_EMPTY = Scope(0) |
| private val SCOPE_DOC_FILLED = Scope(1) |
| private val SCOPE_OBJ_EMPTY = Scope(2) |
| private val SCOPE_OBJ_KEY = Scope(3) |
| private val SCOPE_OBJ_FILLED = Scope(4) |
| private val SCOPE_ARRAY_EMPTY = Scope(5) |
| private val SCOPE_ARRAY_FILLED = Scope(6) |
| |
| // Keeps the stack state of the scopes being scanned. Currently defined to have a |
| // max stack size of 22, as per tests cases defined in http://json.org/JSON_checker/ |
| private class ScopeStack( |
| private val ary: IntArray = IntArray(22) { SCOPE_DOC_EMPTY.id }, |
| var lastPos: Int = 0 |
| ) { |
| var last: Scope |
| get() = Scope(ary[lastPos]) |
| set(x) { |
| ary[lastPos] = x.id |
| } |
| |
| fun reset() { |
| lastPos = 0 |
| ary[0] = SCOPE_DOC_EMPTY.id |
| } |
| |
| fun pop(): Scope { |
| // println("Popping: ${last.print()}") |
| return Scope(ary[lastPos--]) |
| } |
| |
| fun push(scope: Scope): Scope { |
| if (lastPos == ary.size - 1) |
| error("Too much nesting reached. Max nesting is ${ary.size} levels") |
| // println("PUSHING : ${scope.print()}") |
| ary[++lastPos] = scope.id |
| return scope |
| } |
| } |
| |
| @JvmInline |
| private value class Token(val id: Int) { |
| fun print(): String = when (this) { |
| TOK_EOF -> "TOK_EOF" |
| TOK_NONE -> "TOK_NONE" |
| TOK_BEGIN_OBJECT -> "TOK_BEGIN_OBJECT" |
| TOK_END_OBJECT -> "TOK_END_OBJECT" |
| TOK_BEGIN_ARRAY -> "TOK_BEGIN_ARRAY" |
| TOK_END_ARRAY -> "TOK_END_ARRAY" |
| TOK_NUMBER -> "TOK_NUMBER" |
| TOK_TRUE -> "TOK_TRUE" |
| TOK_FALSE -> "TOK_FALSE" |
| TOK_NULL -> "TOK_NULL" |
| TOK_BEGIN_QUOTE -> "TOK_BEGIN_QUOTE" |
| else -> this.toString() |
| } |
| } |
| |
| private val TOK_EOF = Token(-1) |
| private val TOK_NONE = Token(0) |
| private val TOK_BEGIN_OBJECT = Token(1) |
| private val TOK_END_OBJECT = Token(2) |
| private val TOK_BEGIN_ARRAY = Token(3) |
| private val TOK_END_ARRAY = Token(4) |
| private val TOK_NUMBER = Token(5) |
| private val TOK_TRUE = Token(6) |
| private val TOK_FALSE = Token(7) |
| private val TOK_NULL = Token(8) |
| private val TOK_BEGIN_QUOTE = Token(9) |
| |
| private const val CHAR_NEWLINE = '\n'.code.toByte() |
| private const val CHAR_OPEN_OBJECT = '{'.code.toByte() |
| private const val CHAR_COLON = ':'.code.toByte() |
| private const val CHAR_CLOSE_OBJECT = '}'.code.toByte() |
| private const val CHAR_OPEN_ARRAY = '['.code.toByte() |
| private const val CHAR_CLOSE_ARRAY = ']'.code.toByte() |
| private const val CHAR_DOUBLE_QUOTE = '"'.code.toByte() |
| private const val CHAR_BACKSLASH = '\\'.code.toByte() |
| private const val CHAR_FORWARDSLASH = '/'.code.toByte() |
| private const val CHAR_f = 'f'.code.toByte() |
| private const val CHAR_a = 'a'.code.toByte() |
| private const val CHAR_r = 'r'.code.toByte() |
| private const val CHAR_t = 't'.code.toByte() |
| private const val CHAR_n = 'n'.code.toByte() |
| private const val CHAR_b = 'b'.code.toByte() |
| private const val CHAR_e = 'e'.code.toByte() |
| private const val CHAR_E = 'E'.code.toByte() |
| private const val CHAR_u = 'u'.code.toByte() |
| private const val CHAR_A = 'A'.code.toByte() |
| private const val CHAR_F = 'F'.code.toByte() |
| private const val CHAR_EOF = (-1).toByte() |
| private const val CHAR_COMMA = ','.code.toByte() |
| private const val CHAR_0 = '0'.code.toByte() |
| private const val CHAR_1 = '1'.code.toByte() |
| private const val CHAR_2 = '2'.code.toByte() |
| private const val CHAR_3 = '3'.code.toByte() |
| private const val CHAR_4 = '4'.code.toByte() |
| private const val CHAR_5 = '5'.code.toByte() |
| private const val CHAR_6 = '6'.code.toByte() |
| private const val CHAR_7 = '7'.code.toByte() |
| private const val CHAR_8 = '8'.code.toByte() |
| private const val CHAR_9 = '9'.code.toByte() |
| private const val CHAR_MINUS = '-'.code.toByte() |
| private const val CHAR_PLUS = '+'.code.toByte() |
| private const val CHAR_DOT = '.'.code.toByte() |
| |
| // This template utilizes the One Definition Rule to create global arrays in a |
| // header. As seen in: |
| // https://github.com/chadaustin/sajson/blob/master/include/sajson.h |
| // bit 0 (1) - set if: plain ASCII string character |
| // bit 1 (2) - set if: whitespace |
| // bit 4 (0x10) - set if: 0-9 e E . |
| private val parseFlags = byteArrayOf( |
| // 0 1 2 3 4 5 6 7 8 9 A B C D E F |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 0, // 0 |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 |
| 3, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0x11, 1, // 2 |
| 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 1, 1, 1, 1, 1, 1, // 3 |
| 1, 1, 1, 1, 1, 0x11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 5 |
| 1, 1, 1, 1, 1, 0x11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 |
| |
| // 128-255 |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 |
| ) |