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