• 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.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