1 /*
2 * Copyright 2021 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 @file:Suppress("NOTHING_TO_INLINE")
17
18 package com.google.flatbuffers.kotlin
19
20 import com.google.flatbuffers.kotlin.FlexBuffersBuilder.Companion.SHARE_KEYS_AND_STRINGS
21 import kotlin.experimental.and
22 import kotlin.jvm.JvmInline
23 import kotlin.math.pow
24
25 /**
26 * Returns a minified version of this FlexBuffer as a JSON.
27 */
<lambda>null28 public fun Reference.toJson(): String = ArrayReadWriteBuffer(1024).let {
29 toJson(it)
30 val data = it.data() // it.getString(0, it.writePosition)
31 return data.decodeToString(0, it.writePosition)
32 }
33
34 /**
35 * Returns a minified version of this FlexBuffer as a JSON.
36 * @param out [ReadWriteBuffer] the JSON will be written.
37 */
toJsonnull38 public fun Reference.toJson(out: ReadWriteBuffer) {
39 when (type) {
40 T_STRING -> {
41 val start = buffer.indirect(end, parentWidth)
42 val size = buffer.readULong(start - byteWidth, byteWidth).toInt()
43 out.jsonEscape(buffer, start, size)
44 }
45 T_KEY -> {
46 val start = buffer.indirect(end, parentWidth)
47 val end = buffer.findFirst(0.toByte(), start)
48 out.jsonEscape(buffer, start, end - start)
49 }
50 T_BLOB -> {
51 val blob = toBlob()
52 out.jsonEscape(out, blob.end, blob.size)
53 }
54 T_INT -> out.put(toLong().toString())
55 T_UINT -> out.put(toULong().toString())
56 T_FLOAT -> out.put(toDouble().toString())
57 T_NULL -> out.put("null")
58 T_BOOL -> out.put(toBoolean().toString())
59 T_MAP -> toMap().toJson(out)
60 T_VECTOR, T_VECTOR_BOOL, T_VECTOR_FLOAT, T_VECTOR_INT,
61 T_VECTOR_UINT, T_VECTOR_KEY, T_VECTOR_STRING_DEPRECATED -> toVector().toJson(out)
62 else -> error("Unable to convert type ${type.typeToString()} to JSON")
63 }
64 }
65
66 /**
67 * Returns a minified version of this FlexBuffer as a JSON.
68 */
<lambda>null69 public fun Map.toJson(): String = ArrayReadWriteBuffer(1024).let { toJson(it); it.toString() }
70
71 /**
72 * Returns a minified version of this FlexBuffer as a JSON.
73 * @param out [ReadWriteBuffer] the JSON will be written.
74 */
toJsonnull75 public fun Map.toJson(out: ReadWriteBuffer) {
76 out.put('{'.code.toByte())
77 // key values pairs
78 for (i in 0 until size) {
79 val key = keyAt(i)
80 out.jsonEscape(buffer, key.start, key.sizeInBytes)
81 out.put(':'.code.toByte())
82 get(i).toJson(out)
83 if (i != size - 1) {
84 out.put(','.code.toByte())
85 }
86 }
87 // close bracket
88 out.put('}'.code.toByte())
89 }
90
91 /**
92 * Returns a minified version of this FlexBuffer as a JSON.
93 */
<lambda>null94 public fun Vector.toJson(): String = ArrayReadWriteBuffer(1024).let { toJson(it); it.toString() }
95
96 /**
97 * Returns a minified version of this FlexBuffer as a JSON.
98 * @param out that the JSON is being concatenated.
99 */
toJsonnull100 public fun Vector.toJson(out: ReadWriteBuffer) {
101 out.put('['.code.toByte())
102 for (i in indices) {
103 get(i).toJson(out)
104 if (i != size - 1) {
105 out.put(','.code.toByte())
106 }
107 }
108 out.put(']'.code.toByte())
109 }
110
111 /**
112 * JSONParser class is used to parse a JSON as FlexBuffers. Calling [JSONParser.parse] fiils [output]
113 * and returns a [Reference] ready to be used.
114 */
115 @ExperimentalUnsignedTypes
116 public class JSONParser(public var output: FlexBuffersBuilder = FlexBuffersBuilder(1024, SHARE_KEYS_AND_STRINGS)) {
117 private var readPos = 0
118 private var scopes = ScopeStack()
119
120 /**
121 * Parse a json as [String] and returns a [Reference] to a FlexBuffer.
122 */
parsenull123 public fun parse(data: String): Reference = parse(ArrayReadBuffer(data.encodeToByteArray()))
124
125 /**
126 * Parse a json as [ByteArray] and returns a [Reference] to a FlexBuffer.
127 */
128 public fun parse(data: ByteArray): Reference = parse(ArrayReadBuffer(data))
129
130 /**
131 * Parse a json as [ReadBuffer] and returns a [Reference] to a FlexBuffer.
132 */
133 public fun parse(data: ReadBuffer): Reference {
134 reset()
135 parseValue(data, nextToken(data), null)
136 if (readPos < data.limit) {
137 val tok = skipWhitespace(data)
138 if (tok != CHAR_EOF) {
139 makeError(data, "Extraneous charaters after parse has finished", tok)
140 }
141 }
142 output.finish()
143 return getRoot(output.buffer)
144 }
145
parseValuenull146 private fun parseValue(data: ReadBuffer, token: Token, key: String? = null): FlexBufferType {
147 return when (token) {
148 TOK_BEGIN_OBJECT -> parseObject(data, key)
149 TOK_BEGIN_ARRAY -> parseArray(data, key)
150 TOK_TRUE -> T_BOOL.also { output[key] = true }
151 TOK_FALSE -> T_BOOL.also { output[key] = false }
152 TOK_NULL -> T_NULL.also { output.putNull(key) }
153 TOK_BEGIN_QUOTE -> parseString(data, key)
154 TOK_NUMBER -> parseNumber(data, data.data(), key)
155 else -> makeError(data, "Unexpected Character while parsing", 'x'.code.toByte())
156 }
157 }
158
parseObjectnull159 private fun parseObject(data: ReadBuffer, key: String? = null): FlexBufferType {
160 this.scopes.push(SCOPE_OBJ_EMPTY)
161
162 val fPos = output.startMap()
163 val limit = data.limit
164 while (readPos <= limit) {
165 when (val tok = nextToken(data)) {
166 TOK_END_OBJECT -> {
167 this.scopes.pop()
168 output.endMap(fPos, key); return T_MAP
169 }
170 TOK_BEGIN_QUOTE -> {
171 val childKey = readString(data)
172 parseValue(data, nextToken(data), childKey)
173 }
174 else -> makeError(data, "Expecting start of object key", tok)
175 }
176 }
177 makeError(data, "Unable to parse the object", "x".toByte())
178 }
179
parseArraynull180 private fun parseArray(data: ReadBuffer, key: String? = null): FlexBufferType {
181 this.scopes.push(SCOPE_ARRAY_EMPTY)
182 val fPos = output.startVector()
183 var elementType = T_INVALID
184 var multiType = false
185 val limit = data.limit
186
187 while (readPos <= limit) {
188 when (val tok = nextToken(data)) {
189 TOK_END_ARRAY -> {
190 this.scopes.pop()
191 return if (!multiType && elementType.isScalar()) {
192 output.endTypedVector(fPos, key)
193 elementType.toElementTypedVector()
194 } else {
195 output.endVector(key, fPos)
196 T_VECTOR
197 }
198 }
199
200 else -> {
201 val newType = parseValue(data, tok, null)
202
203 if (elementType == T_INVALID) {
204 elementType = newType
205 } else if (newType != elementType) {
206 multiType = true
207 }
208 }
209 }
210 }
211 makeError(data, "Unable to parse the array")
212 }
213
parseNumbernull214 private fun parseNumber(data: ReadBuffer, array: ByteArray, key: String?): FlexBufferType {
215 val ary = array
216 var cursor = readPos
217 var c = data[readPos++]
218 var useDouble = false
219 val limit = ary.size
220 var sign = 1
221 var double: Double
222 var long = 0L
223 var digits = 0
224
225 if (c == CHAR_MINUS) {
226 cursor++
227 checkEOF(data, cursor)
228 c = ary[cursor]
229 sign = -1
230 }
231
232 // peek first byte
233 when (c) {
234 CHAR_0 -> {
235 cursor++
236 if (cursor != limit) {
237 c = ary[cursor]
238 }
239 }
240 !in CHAR_0..CHAR_9 -> makeError(data, "Invalid Number", c)
241 else -> {
242 do {
243 val digit = c - CHAR_0
244 // double = 10.0 * double + digit
245 long = 10 * long + digit
246 digits++
247 cursor++
248 if (cursor == limit) break
249 c = ary[cursor]
250 } while (c in CHAR_0..CHAR_9)
251 }
252 }
253
254 var exponent = 0
255 // If we find '.' we need to convert to double
256 if (c == CHAR_DOT) {
257 useDouble = true
258 checkEOF(data, cursor)
259 c = ary[++cursor]
260 if (c < CHAR_0 || c > CHAR_9) {
261 makeError(data, "Invalid Number", c)
262 }
263 do {
264 // double = double * 10 + (tok - CHAR_0)
265 long = 10 * long + (c - CHAR_0)
266 digits++
267 --exponent
268 cursor++
269 if (cursor == limit) break
270 c = ary[cursor]
271 } while (c in CHAR_0..CHAR_9)
272 }
273
274 // If we find 'e' we need to convert to double
275 if (c == CHAR_e || c == CHAR_E) {
276 useDouble = true
277 ++cursor
278 checkEOF(data, cursor)
279 c = ary[cursor]
280 var negativeExponent = false
281 if (c == CHAR_MINUS) {
282 ++cursor
283 checkEOF(data, cursor)
284 negativeExponent = true
285 c = ary[cursor]
286 } else if (c == CHAR_PLUS) {
287 ++cursor
288 checkEOF(data, cursor)
289 c = ary[cursor]
290 }
291 if (c < CHAR_0 || c > CHAR_9) {
292 makeError(data, "Missing exponent", c)
293 }
294 var exp = 0
295 do {
296 val digit = c - CHAR_0
297 exp = 10 * exp + digit
298 ++cursor
299 if (cursor == limit) break
300 c = ary[cursor]
301 } while (c in CHAR_0..CHAR_9)
302
303 exponent += if (negativeExponent) -exp else exp
304 }
305
306 if (digits > 17 || exponent < -19 || exponent > 19) {
307 // if the float number is not simple enough
308 // we use language's Double parsing, which is slower but
309 // produce more expected results for extreme numbers.
310 val firstPos = readPos - 1
311 val str = data.getString(firstPos, cursor - firstPos)
312 if (useDouble) {
313 double = str.toDouble()
314 output[key] = double
315 } else {
316 long = str.toLong()
317 output[key] = long
318 }
319 } else {
320 // this happens on single numbers outside any object
321 // or array
322 if (useDouble || exponent != 0) {
323 double = if (long == 0L) 0.0 else long.toDouble() * 10.0.pow(exponent)
324 double *= sign
325 output[key] = double
326 } else {
327 long *= sign
328 output[key] = long
329 }
330 }
331 readPos = cursor
332 return if (useDouble) T_FLOAT else T_INT
333 }
334
parseStringnull335 private fun parseString(data: ReadBuffer, key: String?): FlexBufferType {
336 output[key] = readString(data)
337 return T_STRING
338 }
339
readStringnull340 private fun readString(data: ReadBuffer): String {
341 val limit = data.limit
342 if (data is ArrayReadBuffer) {
343 val ary = data.data()
344 // enables range check elimination
345 return readString(data, limit) { ary[it] }
346 }
347 return readString(data, limit) { data[it] }
348 }
349
readStringnull350 private inline fun readString(data: ReadBuffer, limit: Int, crossinline fetch: (Int) -> Byte): String {
351 var cursorPos = readPos
352 var foundEscape = false
353 var currentChar: Byte = 0
354 // we loop over every 4 bytes until find any non-plain char
355 while (limit - cursorPos >= 4) {
356 currentChar = fetch(cursorPos)
357 if (!isPlainStringChar(currentChar)) {
358 foundEscape = true
359 break
360 }
361 currentChar = fetch(cursorPos + 1)
362 if (!isPlainStringChar(currentChar)) {
363 cursorPos += 1
364 foundEscape = true
365 break
366 }
367 currentChar = fetch(cursorPos + 2)
368 if (!isPlainStringChar(currentChar)) {
369 cursorPos += 2
370 foundEscape = true
371 break
372 }
373 currentChar = fetch(cursorPos + 3)
374 if (!isPlainStringChar(currentChar)) {
375 cursorPos += 3
376 foundEscape = true
377 break
378 }
379 cursorPos += 4
380 }
381 if (!foundEscape) {
382 // if non-plain string char is not found we loop over
383 // the remaining bytes
384 while (true) {
385 if (cursorPos >= limit) {
386 error("Unexpected end of string")
387 }
388 currentChar = fetch(cursorPos)
389 if (!isPlainStringChar(currentChar)) {
390 break
391 }
392 ++cursorPos
393 }
394 }
395 if (currentChar == CHAR_DOUBLE_QUOTE) {
396 val str = data.getString(readPos, cursorPos - readPos)
397 readPos = cursorPos + 1
398 return str
399 }
400 if (currentChar in 0..0x1f) {
401 error("Illegal Codepoint")
402 } else {
403 // backslash or >0x7f
404 return readStringSlow(data, currentChar, cursorPos)
405 }
406 }
407
readStringSlownull408 private fun readStringSlow(data: ReadBuffer, first: Byte, lastPos: Int): String {
409 var cursorPos = lastPos
410
411 var endOfString = lastPos
412 while (true) {
413 val pos = data.findFirst(CHAR_DOUBLE_QUOTE, endOfString)
414 when {
415 pos == -1 -> makeError(data, "Unexpected EOF, missing end of string '\"'", first)
416 data[pos - 1] == CHAR_BACKSLASH && data[pos - 2] != CHAR_BACKSLASH -> {
417 // here we are checking for double quotes preceded by backslash. eg \"
418 // we have to look past pos -2 to make sure that the backlash is not
419 // part of a previous escape, eg "\\"
420 endOfString = pos + 1
421 }
422 else -> {
423 endOfString = pos; break
424 }
425 }
426 }
427 // copy everything before the escape
428 val builder = StringBuilder(data.getString(readPos, lastPos - readPos))
429 while (true) {
430 when (val pos = data.findFirst(CHAR_BACKSLASH, cursorPos, endOfString)) {
431 -1 -> {
432 val doubleQuotePos = data.findFirst(CHAR_DOUBLE_QUOTE, cursorPos)
433 if (doubleQuotePos == -1) makeError(data, "Reached EOF before enclosing string", first)
434 val rest = data.getString(cursorPos, doubleQuotePos - cursorPos)
435 builder.append(rest)
436 readPos = doubleQuotePos + 1
437 return builder.toString()
438 }
439
440 else -> {
441 // we write everything up to \
442 builder.append(data.getString(cursorPos, pos - cursorPos))
443 val c = data[pos + 1]
444 builder.append(readEscapedChar(data, c, pos))
445 cursorPos = pos + if (c == CHAR_u) 6 else 2
446 }
447 }
448 }
449 }
450
isPlainStringCharnull451 private inline fun isPlainStringChar(c: Byte): Boolean {
452 val flags = parseFlags
453 // return c in 0x20..0x7f && c != 0x22.toByte() && c != 0x5c.toByte()
454 return (flags[c.toInt() and 0xFF] and 1) != 0.toByte()
455 }
456
isWhitespacenull457 private inline fun isWhitespace(c: Byte): Boolean {
458 val flags = parseFlags
459 // return c == '\r'.toByte() || c == '\n'.toByte() || c == '\t'.toByte() || c == ' '.toByte()
460 return (flags[c.toInt() and 0xFF] and 2) != 0.toByte()
461 }
462
resetnull463 private fun reset() {
464 readPos = 0
465 output.clear()
466 scopes.reset()
467 }
468
nextTokennull469 private fun nextToken(data: ReadBuffer): Token {
470 val scope = this.scopes.last
471
472 when (scope) {
473 SCOPE_ARRAY_EMPTY -> this.scopes.last = SCOPE_ARRAY_FILLED
474 SCOPE_ARRAY_FILLED -> {
475 when (val c = skipWhitespace(data)) {
476 CHAR_CLOSE_ARRAY -> return TOK_END_ARRAY
477 CHAR_COMMA -> Unit
478 else -> makeError(data, "Unfinished Array", c)
479 }
480 }
481 SCOPE_OBJ_EMPTY, SCOPE_OBJ_FILLED -> {
482 this.scopes.last = SCOPE_OBJ_KEY
483 // Look for a comma before the next element.
484 if (scope == SCOPE_OBJ_FILLED) {
485 when (val c = skipWhitespace(data)) {
486 CHAR_CLOSE_OBJECT -> return TOK_END_OBJECT
487 CHAR_COMMA -> Unit
488 else -> makeError(data, "Unfinished Object", c)
489 }
490 }
491 return when (val c = skipWhitespace(data)) {
492 CHAR_DOUBLE_QUOTE -> TOK_BEGIN_QUOTE
493 CHAR_CLOSE_OBJECT -> if (scope != SCOPE_OBJ_FILLED) {
494 TOK_END_OBJECT
495 } else {
496 makeError(data, "Expected Key", c)
497 }
498 else -> {
499 makeError(data, "Expected Key/Value", c)
500 }
501 }
502 }
503 SCOPE_OBJ_KEY -> {
504 this.scopes.last = SCOPE_OBJ_FILLED
505 when (val c = skipWhitespace(data)) {
506 CHAR_COLON -> Unit
507 else -> makeError(data, "Expect ${CHAR_COLON.print()}", c)
508 }
509 }
510 SCOPE_DOC_EMPTY -> this.scopes.last = SCOPE_DOC_FILLED
511 SCOPE_DOC_FILLED -> {
512 val c = skipWhitespace(data)
513 if (c != CHAR_EOF)
514 makeError(data, "Root object already finished", c)
515 return TOK_EOF
516 }
517 }
518
519 val c = skipWhitespace(data)
520 when (c) {
521 CHAR_CLOSE_ARRAY -> if (scope == SCOPE_ARRAY_EMPTY) return TOK_END_ARRAY
522 CHAR_COLON -> makeError(data, "Unexpected character", c)
523 CHAR_DOUBLE_QUOTE -> return TOK_BEGIN_QUOTE
524 CHAR_OPEN_ARRAY -> return TOK_BEGIN_ARRAY
525 CHAR_OPEN_OBJECT -> return TOK_BEGIN_OBJECT
526 CHAR_t -> {
527 checkEOF(data, readPos + 2)
528 // 0x65757274 is equivalent to ['t', 'r', 'u', 'e' ] as a 4 byte Int
529 if (data.getInt(readPos - 1) != 0x65757274) {
530 makeError(data, "Expecting keyword \"true\"", c)
531 }
532 readPos += 3
533 return TOK_TRUE
534 }
535 CHAR_n -> {
536 checkEOF(data, readPos + 2)
537 // 0x6c6c756e is equivalent to ['n', 'u', 'l', 'l' ] as a 4 byte Int
538 if (data.getInt(readPos - 1) != 0x6c6c756e) {
539 makeError(data, "Expecting keyword \"null\"", c)
540 }
541 readPos += 3
542 return TOK_NULL
543 }
544 CHAR_f -> {
545 checkEOF(data, readPos + 3)
546 // 0x65736c61 is equivalent to ['a', 'l', 's', 'e' ] as a 4 byte Int
547 if (data.getInt(readPos) != 0x65736c61) {
548 makeError(data, "Expecting keyword \"false\"", c)
549 }
550 readPos += 4
551 return TOK_FALSE
552 }
553 CHAR_0, CHAR_1, CHAR_2, CHAR_3, CHAR_4, CHAR_5,
554 CHAR_6, CHAR_7, CHAR_8, CHAR_9, CHAR_MINUS -> return TOK_NUMBER.also {
555 readPos-- // rewind one position so we don't lose first digit
556 }
557 }
558 makeError(data, "Expecting element", c)
559 }
560
561 // keeps increasing [readPos] until finds a non-whitespace byte
skipWhitespacenull562 private inline fun skipWhitespace(data: ReadBuffer): Byte {
563 val limit = data.limit
564 if (data is ArrayReadBuffer) {
565 // enables range check elimination
566 val ary = data.data()
567 return skipWhitespace(limit) { ary[it] }
568 }
569 return skipWhitespace(limit) { data[it] }
570 }
571
skipWhitespacenull572 private inline fun skipWhitespace(limit: Int, crossinline fetch: (Int) -> Byte): Byte {
573 var pos = readPos
574 while (pos < limit) {
575 val d = fetch(pos++)
576 if (!isWhitespace(d)) {
577 readPos = pos
578 return d
579 }
580 }
581 readPos = limit
582 return CHAR_EOF
583 }
584
585 // byte1 is expected to be first char before `\`
readEscapedCharnull586 private fun readEscapedChar(data: ReadBuffer, byte1: Byte, cursorPos: Int): Char {
587 return when (byte1) {
588 CHAR_u -> {
589 checkEOF(data, cursorPos + 1 + 4)
590 var result: Char = 0.toChar()
591 var i = cursorPos + 2 // cursorPos is on '\\', cursorPos + 1 is 'u'
592 val end = i + 4
593 while (i < end) {
594 val part: Byte = data[i]
595 result = (result.code shl 4).toChar()
596 result += when (part) {
597 in CHAR_0..CHAR_9 -> part - CHAR_0
598 in CHAR_a..CHAR_f -> part - CHAR_a + 10
599 in CHAR_A..CHAR_F -> part - CHAR_A + 10
600 else -> makeError(data, "Invalid utf8 escaped character", -1)
601 }
602 i++
603 }
604 result
605 }
606 CHAR_b -> '\b'
607 CHAR_t -> '\t'
608 CHAR_r -> '\r'
609 CHAR_n -> '\n'
610 CHAR_f -> 12.toChar() // '\f'
611 CHAR_DOUBLE_QUOTE, CHAR_BACKSLASH, CHAR_FORWARDSLASH -> byte1.toInt().toChar()
612 else -> makeError(data, "Invalid escape sequence.", byte1)
613 }
614 }
615
printnull616 private fun Byte.print(): String = when (this) {
617 in 0x21..0x7E -> "'${this.toInt().toChar()}'" // visible ascii chars
618 CHAR_EOF -> "EOF"
619 else -> "'0x${this.toString(16)}'"
620 }
621
makeErrornull622 private inline fun makeError(data: ReadBuffer, msg: String, tok: Byte? = null): Nothing {
623 val (line, column) = calculateErrorPosition(data, readPos)
624 if (tok != null) {
625 error("Error At ($line, $column): $msg, got ${tok.print()}")
626 } else {
627 error("Error At ($line, $column): $msg")
628 }
629 }
630
makeErrornull631 private inline fun makeError(data: ReadBuffer, msg: String, tok: Token): Nothing {
632 val (line, column) = calculateErrorPosition(data, readPos)
633 error("Error At ($line, $column): $msg, got ${tok.print()}")
634 }
635
checkEOFnull636 private inline fun checkEOF(data: ReadBuffer, pos: Int) {
637 if (pos >= data.limit)
638 makeError(data, "Unexpected end of file", -1)
639 }
640
calculateErrorPositionnull641 private fun calculateErrorPosition(data: ReadBuffer, endPos: Int): Pair<Int, Int> {
642 var line = 1
643 var column = 1
644 var current = 0
645 while (current < endPos - 1) {
646 if (data[current++] == CHAR_NEWLINE) {
647 ++line
648 column = 1
649 } else {
650 ++column
651 }
652 }
653 return Pair(line, column)
654 }
655 }
656
toPaddedHexnull657 internal inline fun Int.toPaddedHex(): String = "\\u${this.toString(16).padStart(4, '0')}"
658
659 private inline fun ReadWriteBuffer.jsonEscape(data: ReadBuffer, start: Int, size: Int) {
660 val replacements = JSON_ESCAPE_CHARS
661 put(CHAR_DOUBLE_QUOTE)
662 var last = start
663 val length: Int = size
664 val ary = data.data()
665 for (i in start until start + length) {
666 val c = ary[i].toUByte()
667 var replacement: ByteArray?
668 if (c.toInt() < 128) {
669 replacement = replacements[c.toInt()]
670 if (replacement == null) {
671 continue
672 }
673 } else {
674 continue
675 }
676 if (last < i) {
677 put(ary, last, i - last)
678 }
679 put(replacement, 0, replacement.size)
680 last = i + 1
681 }
682 if (last < (last + length)) {
683 put(ary, last, (start + length) - last)
684 }
685 put(CHAR_DOUBLE_QUOTE)
686 }
687
688 // Following escape strategy defined in RFC7159.
<lambda>null689 private val JSON_ESCAPE_CHARS: Array<ByteArray?> = arrayOfNulls<ByteArray>(128).apply {
690 this['\n'.code] = "\\n".encodeToByteArray()
691 this['\t'.code] = "\\t".encodeToByteArray()
692 this['\r'.code] = "\\r".encodeToByteArray()
693 this['\b'.code] = "\\b".encodeToByteArray()
694 this[0x0c] = "\\f".encodeToByteArray()
695 this['"'.code] = "\\\"".encodeToByteArray()
696 this['\\'.code] = "\\\\".encodeToByteArray()
697 for (i in 0..0x1f) {
698 this[i] = "\\u${i.toPaddedHex()}".encodeToByteArray()
699 }
700 }
701
702 // Scope is used to the define current space that the scanner is operating.
703 @JvmInline
704 private value class Scope(val id: Int)
705 private val SCOPE_DOC_EMPTY = Scope(0)
706 private val SCOPE_DOC_FILLED = Scope(1)
707 private val SCOPE_OBJ_EMPTY = Scope(2)
708 private val SCOPE_OBJ_KEY = Scope(3)
709 private val SCOPE_OBJ_FILLED = Scope(4)
710 private val SCOPE_ARRAY_EMPTY = Scope(5)
711 private val SCOPE_ARRAY_FILLED = Scope(6)
712
713 // Keeps the stack state of the scopes being scanned. Currently defined to have a
714 // max stack size of 22, as per tests cases defined in http://json.org/JSON_checker/
715 private class ScopeStack(
<lambda>null716 private val ary: IntArray = IntArray(22) { SCOPE_DOC_EMPTY.id },
717 var lastPos: Int = 0
718 ) {
719 var last: Scope
720 get() = Scope(ary[lastPos])
721 set(x) {
722 ary[lastPos] = x.id
723 }
724
resetnull725 fun reset() {
726 lastPos = 0
727 ary[0] = SCOPE_DOC_EMPTY.id
728 }
729
popnull730 fun pop(): Scope {
731 // println("Popping: ${last.print()}")
732 return Scope(ary[lastPos--])
733 }
734
pushnull735 fun push(scope: Scope): Scope {
736 if (lastPos == ary.size - 1)
737 error("Too much nesting reached. Max nesting is ${ary.size} levels")
738 // println("PUSHING : ${scope.print()}")
739 ary[++lastPos] = scope.id
740 return scope
741 }
742 }
743
744 @JvmInline
745 private value class Token(val id: Int) {
printnull746 fun print(): String = when (this) {
747 TOK_EOF -> "TOK_EOF"
748 TOK_NONE -> "TOK_NONE"
749 TOK_BEGIN_OBJECT -> "TOK_BEGIN_OBJECT"
750 TOK_END_OBJECT -> "TOK_END_OBJECT"
751 TOK_BEGIN_ARRAY -> "TOK_BEGIN_ARRAY"
752 TOK_END_ARRAY -> "TOK_END_ARRAY"
753 TOK_NUMBER -> "TOK_NUMBER"
754 TOK_TRUE -> "TOK_TRUE"
755 TOK_FALSE -> "TOK_FALSE"
756 TOK_NULL -> "TOK_NULL"
757 TOK_BEGIN_QUOTE -> "TOK_BEGIN_QUOTE"
758 else -> this.toString()
759 }
760 }
761
762 private val TOK_EOF = Token(-1)
763 private val TOK_NONE = Token(0)
764 private val TOK_BEGIN_OBJECT = Token(1)
765 private val TOK_END_OBJECT = Token(2)
766 private val TOK_BEGIN_ARRAY = Token(3)
767 private val TOK_END_ARRAY = Token(4)
768 private val TOK_NUMBER = Token(5)
769 private val TOK_TRUE = Token(6)
770 private val TOK_FALSE = Token(7)
771 private val TOK_NULL = Token(8)
772 private val TOK_BEGIN_QUOTE = Token(9)
773
774 private const val CHAR_NEWLINE = '\n'.code.toByte()
775 private const val CHAR_OPEN_OBJECT = '{'.code.toByte()
776 private const val CHAR_COLON = ':'.code.toByte()
777 private const val CHAR_CLOSE_OBJECT = '}'.code.toByte()
778 private const val CHAR_OPEN_ARRAY = '['.code.toByte()
779 private const val CHAR_CLOSE_ARRAY = ']'.code.toByte()
780 private const val CHAR_DOUBLE_QUOTE = '"'.code.toByte()
781 private const val CHAR_BACKSLASH = '\\'.code.toByte()
782 private const val CHAR_FORWARDSLASH = '/'.code.toByte()
783 private const val CHAR_f = 'f'.code.toByte()
784 private const val CHAR_a = 'a'.code.toByte()
785 private const val CHAR_r = 'r'.code.toByte()
786 private const val CHAR_t = 't'.code.toByte()
787 private const val CHAR_n = 'n'.code.toByte()
788 private const val CHAR_b = 'b'.code.toByte()
789 private const val CHAR_e = 'e'.code.toByte()
790 private const val CHAR_E = 'E'.code.toByte()
791 private const val CHAR_u = 'u'.code.toByte()
792 private const val CHAR_A = 'A'.code.toByte()
793 private const val CHAR_F = 'F'.code.toByte()
794 private const val CHAR_EOF = (-1).toByte()
795 private const val CHAR_COMMA = ','.code.toByte()
796 private const val CHAR_0 = '0'.code.toByte()
797 private const val CHAR_1 = '1'.code.toByte()
798 private const val CHAR_2 = '2'.code.toByte()
799 private const val CHAR_3 = '3'.code.toByte()
800 private const val CHAR_4 = '4'.code.toByte()
801 private const val CHAR_5 = '5'.code.toByte()
802 private const val CHAR_6 = '6'.code.toByte()
803 private const val CHAR_7 = '7'.code.toByte()
804 private const val CHAR_8 = '8'.code.toByte()
805 private const val CHAR_9 = '9'.code.toByte()
806 private const val CHAR_MINUS = '-'.code.toByte()
807 private const val CHAR_PLUS = '+'.code.toByte()
808 private const val CHAR_DOT = '.'.code.toByte()
809
810 // This template utilizes the One Definition Rule to create global arrays in a
811 // header. As seen in:
812 // https://github.com/chadaustin/sajson/blob/master/include/sajson.h
813 // bit 0 (1) - set if: plain ASCII string character
814 // bit 1 (2) - set if: whitespace
815 // bit 4 (0x10) - set if: 0-9 e E .
816 private val parseFlags = byteArrayOf(
817 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
818 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 0, // 0
819 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
820 3, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0x11, 1, // 2
821 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 1, 1, 1, 1, 1, 1, // 3
822 1, 1, 1, 1, 1, 0x11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
823 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 5
824 1, 1, 1, 1, 1, 0x11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
825 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
826
827 // 128-255
828 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,
829 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,
830 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,
831 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
832 )
833