• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization.json.internal
6 
7 import kotlinx.serialization.json.internal.CharMappings.CHAR_TO_TOKEN
8 import kotlinx.serialization.json.internal.CharMappings.ESCAPE_2_CHAR
9 import kotlin.js.*
10 import kotlin.jvm.*
11 import kotlin.math.*
12 
13 internal const val lenientHint = "Use 'isLenient = true' in 'Json {}' builder to accept non-compliant JSON."
14 internal const val coerceInputValuesHint = "Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value."
15 internal const val specialFlowingValuesHint =
16     "It is possible to deserialize them using 'JsonBuilder.allowSpecialFloatingPointValues = true'"
17 internal const val ignoreUnknownKeysHint = "Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys."
18 internal const val allowStructuredMapKeysHint =
19     "Use 'allowStructuredMapKeys = true' in 'Json {}' builder to convert such maps to [key1, value1, key2, value2,...] arrays."
20 
21 // special strings
22 internal const val NULL = "null"
23 
24 // special chars
25 internal const val COMMA = ','
26 internal const val COLON = ':'
27 internal const val BEGIN_OBJ = '{'
28 internal const val END_OBJ = '}'
29 internal const val BEGIN_LIST = '['
30 internal const val END_LIST = ']'
31 internal const val STRING = '"'
32 internal const val STRING_ESC = '\\'
33 
34 internal const val INVALID = 0.toChar()
35 internal const val UNICODE_ESC = 'u'
36 
37 // token classes
38 internal const val TC_OTHER: Byte = 0
39 internal const val TC_STRING: Byte = 1
40 internal const val TC_STRING_ESC: Byte = 2
41 internal const val TC_WHITESPACE: Byte = 3
42 internal const val TC_COMMA: Byte = 4
43 internal const val TC_COLON: Byte = 5
44 internal const val TC_BEGIN_OBJ: Byte = 6
45 internal const val TC_END_OBJ: Byte = 7
46 internal const val TC_BEGIN_LIST: Byte = 8
47 internal const val TC_END_LIST: Byte = 9
48 internal const val TC_EOF: Byte = 10
49 internal const val TC_INVALID: Byte = Byte.MAX_VALUE
50 
51 // mapping from chars to token classes
52 private const val CTC_MAX = 0x7e
53 
54 // mapping from escape chars real chars
55 private const val ESC2C_MAX = 0x75
56 
57 internal const val asciiCaseMask = 1 shl 5
58 
tokenDescriptionnull59 internal fun tokenDescription(token: Byte) = when (token) {
60     TC_STRING -> "quotation mark '\"'"
61     TC_STRING_ESC -> "string escape sequence '\\'"
62     TC_COMMA -> "comma ','"
63     TC_COLON -> "colon ':'"
64     TC_BEGIN_OBJ -> "start of the object '{'"
65     TC_END_OBJ -> "end of the object '}'"
66     TC_BEGIN_LIST -> "start of the array '['"
67     TC_END_LIST -> "end of the array ']'"
68     TC_EOF -> "end of the input"
69     TC_INVALID -> "invalid token"
70     else -> "valid token" // should never happen
71 }
72 
73 // object instead of @SharedImmutable because there is mutual initialization in [initC2ESC] and [initC2TC]
74 internal object CharMappings {
75     @JvmField
76     val ESCAPE_2_CHAR = CharArray(ESC2C_MAX)
77 
78     @JvmField
79     val CHAR_TO_TOKEN = ByteArray(CTC_MAX)
80 
81     init {
82         initEscape()
83         initCharToToken()
84     }
85 
initEscapenull86     private fun initEscape() {
87         for (i in 0x00..0x1f) {
88             initC2ESC(i, UNICODE_ESC)
89         }
90 
91         initC2ESC(0x08, 'b')
92         initC2ESC(0x09, 't')
93         initC2ESC(0x0a, 'n')
94         initC2ESC(0x0c, 'f')
95         initC2ESC(0x0d, 'r')
96         initC2ESC('/', '/')
97         initC2ESC(STRING, STRING)
98         initC2ESC(STRING_ESC, STRING_ESC)
99     }
100 
initCharToTokennull101     private fun initCharToToken() {
102         for (i in 0..0x20) {
103             initC2TC(i, TC_INVALID)
104         }
105 
106         initC2TC(0x09, TC_WHITESPACE)
107         initC2TC(0x0a, TC_WHITESPACE)
108         initC2TC(0x0d, TC_WHITESPACE)
109         initC2TC(0x20, TC_WHITESPACE)
110         initC2TC(COMMA, TC_COMMA)
111         initC2TC(COLON, TC_COLON)
112         initC2TC(BEGIN_OBJ, TC_BEGIN_OBJ)
113         initC2TC(END_OBJ, TC_END_OBJ)
114         initC2TC(BEGIN_LIST, TC_BEGIN_LIST)
115         initC2TC(END_LIST, TC_END_LIST)
116         initC2TC(STRING, TC_STRING)
117         initC2TC(STRING_ESC, TC_STRING_ESC)
118     }
119 
initC2ESCnull120     private fun initC2ESC(c: Int, esc: Char) {
121         if (esc != UNICODE_ESC) ESCAPE_2_CHAR[esc.code] = c.toChar()
122     }
123 
initC2ESCnull124     private fun initC2ESC(c: Char, esc: Char) = initC2ESC(c.code, esc)
125 
126     private fun initC2TC(c: Int, cl: Byte) {
127         CHAR_TO_TOKEN[c] = cl
128     }
129 
initC2TCnull130     private fun initC2TC(c: Char, cl: Byte) = initC2TC(c.code, cl)
131 }
132 
133 internal fun charToTokenClass(c: Char) = if (c.code < CTC_MAX) CHAR_TO_TOKEN[c.code] else TC_OTHER
134 
135 internal fun escapeToChar(c: Int): Char = if (c < ESC2C_MAX) ESCAPE_2_CHAR[c] else INVALID
136 
137 /**
138  * The base class that reads the JSON from the given char sequence source.
139  * It has two implementations: one over the raw [String] instance, [StringJsonLexer],
140  * and one over an arbitrary stream of data, [ReaderJsonLexer] (JVM-only).
141  *
142  * [AbstractJsonLexer] contains base implementation for cold or not performance-sensitive
143  * methods on top of [CharSequence], but [StringJsonLexer] overrides some
144  * of them for the performance reasons (devirtualization of [CharSequence] and avoid
145  * of additional spills).
146  */
147 internal abstract class AbstractJsonLexer {
148 
149     protected abstract val source: CharSequence
150 
151     @JvmField
152     internal var currentPosition: Int = 0 // position in source
153 
154     @JvmField
155     val path = JsonPath()
156 
157     open fun ensureHaveChars() {}
158 
159     fun isNotEof(): Boolean = peekNextToken() != TC_EOF
160 
161     // Used as bound check in loops
162     abstract fun prefetchOrEof(position: Int): Int
163 
164     abstract fun tryConsumeComma(): Boolean
165 
166     abstract fun canConsumeValue(): Boolean
167 
168     abstract fun consumeNextToken(): Byte
169 
170     protected fun isValidValueStart(c: Char): Boolean {
171         return when (c) {
172             '}', ']', ':', ',' -> false
173             else -> true
174         }
175     }
176 
177     fun expectEof() {
178         val nextToken = consumeNextToken()
179         if (nextToken != TC_EOF)
180             fail("Expected EOF after parsing, but had ${source[currentPosition - 1]} instead")
181     }
182 
183     /*
184      * Peeked string for coerced enums.
185      * If the value was picked, 'consumeString' will take it without scanning the source.
186      */
187     private var peekedString: String? = null
188     protected var escapedString = StringBuilder()
189 
190     // TODO consider replacing usages of this method in JsonParser with char overload
191     fun consumeNextToken(expected: Byte): Byte {
192         val token = consumeNextToken()
193         if (token != expected) {
194             fail(expected)
195         }
196         return token
197     }
198 
199     open fun consumeNextToken(expected: Char) {
200         ensureHaveChars()
201         val source = source
202         var cpos = currentPosition
203         while (true) {
204             cpos = prefetchOrEof(cpos)
205             if (cpos == -1) break // could be inline function but KT-1436
206             val c = source[cpos++]
207             if (c == ' ' || c == '\n' || c == '\r' || c == '\t') continue
208             currentPosition = cpos
209             if (c == expected) return
210             unexpectedToken(expected)
211         }
212         currentPosition = cpos
213         unexpectedToken(expected) // EOF
214     }
215 
216     protected fun unexpectedToken(expected: Char) {
217         if (currentPosition > 0 && expected == STRING) {
218             val inputLiteral = withPositionRollback {
219                 currentPosition--
220                 consumeStringLenient()
221             }
222             if (inputLiteral == NULL)
223                 fail("Expected string literal but 'null' literal was found", currentPosition - 1, coerceInputValuesHint)
224         }
225         fail(charToTokenClass(expected))
226     }
227 
228     internal fun fail(expectedToken: Byte, wasConsumed: Boolean = true): Nothing {
229         // Slow path, never called in normal code, can avoid optimizing it
230         val expected = tokenDescription(expectedToken)
231         val position = if (wasConsumed) currentPosition - 1 else currentPosition
232         val s = if (currentPosition == source.length || position < 0) "EOF" else source[position].toString()
233         fail("Expected $expected, but had '$s' instead", position)
234     }
235 
236     fun peekNextToken(): Byte {
237         val source = source
238         var cpos = currentPosition
239         while (true) {
240             cpos = prefetchOrEof(cpos)
241             if (cpos == -1) break
242             val ch = source[cpos]
243             if (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t') {
244                 ++cpos
245                 continue
246             }
247             currentPosition = cpos
248             return charToTokenClass(ch)
249         }
250         currentPosition = cpos
251         return TC_EOF
252     }
253 
254     /**
255      * Tries to consume `null` token from input.
256      * Returns `false` if the next 4 chars in input are not `null`,
257      * `true` otherwise and consumes it if [doConsume] is `true`.
258      */
259     fun tryConsumeNull(doConsume: Boolean = true): Boolean {
260         var current = skipWhitespaces()
261         current = prefetchOrEof(current)
262         // Cannot consume null due to EOF, maybe something else
263         val len = source.length - current
264         if (len < 4 || current == -1) return false
265         for (i in 0..3) {
266             if (NULL[i] != source[current + i]) return false
267         }
268         /*
269          * If we're in lenient mode, this might be the string with 'null' prefix,
270          * distinguish it from 'null'
271          */
272         if (len > 4 && charToTokenClass(source[current + 4]) == TC_OTHER) return false
273 
274         if (doConsume) {
275             currentPosition = current + 4
276         }
277         return true
278     }
279 
280     open fun skipWhitespaces(): Int {
281         var current = currentPosition
282         // Skip whitespaces
283         while (true) {
284             current = prefetchOrEof(current)
285             if (current == -1) break
286             val c = source[current]
287             // Faster than char2TokenClass actually
288             if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
289                 ++current
290             } else {
291                 break
292             }
293         }
294         currentPosition = current
295         return current
296     }
297 
298     abstract fun peekLeadingMatchingValue(keyToMatch: String, isLenient: Boolean): String?
299 
300     fun peekString(isLenient: Boolean): String? {
301         val token = peekNextToken()
302         val string = if (isLenient) {
303             if (token != TC_STRING && token != TC_OTHER) return null
304             consumeStringLenient()
305         } else {
306             if (token != TC_STRING) return null
307             consumeString()
308         }
309         peekedString = string
310         return string
311     }
312 
313     fun discardPeeked() {
314         peekedString = null
315     }
316 
317     open fun indexOf(char: Char, startPos: Int) = source.indexOf(char, startPos)
318     open fun substring(startPos: Int, endPos: Int) =  source.substring(startPos, endPos)
319 
320     /*
321      * This method is a copy of consumeString, but used for key of json objects, so there
322      * is no need to lookup peeked string.
323      */
324     abstract fun consumeKeyString(): String
325 
326     private fun insideString(isLenient: Boolean, char: Char): Boolean = if (isLenient) {
327         charToTokenClass(char) == TC_OTHER
328     } else {
329         char != STRING
330     }
331 
332     open fun consumeStringChunked(isLenient: Boolean, consumeChunk: (stringChunk: String) -> Unit) { // open to allow simpler implementations (i.e. StringJsonLexer)
333         val nextToken = peekNextToken()
334         if (isLenient && nextToken != TC_OTHER) return // noting to consume
335 
336         if (!isLenient) {
337             consumeNextToken(STRING)
338         }
339         var currentPosition = this.currentPosition
340         var lastPosition = currentPosition
341         var char = source[currentPosition] // Avoid two range checks visible in the profiler
342         var usedAppend = false
343         while (insideString(isLenient, char)) {
344             if (!isLenient && char == STRING_ESC) { // handle escaping only in non-lenient mode
345                 usedAppend = true
346                 currentPosition = prefetchOrEof(appendEscape(lastPosition, currentPosition))
347                 lastPosition = currentPosition
348             } else {
349                 currentPosition++
350             }
351             if (currentPosition >= source.length) {
352                 // end of chunk
353                 writeRange(lastPosition, currentPosition, usedAppend, consumeChunk)
354                 usedAppend = false
355                 currentPosition = prefetchOrEof(currentPosition)
356                 if (currentPosition == -1)
357                     fail("EOF", currentPosition)
358                 lastPosition = currentPosition
359             }
360             char = source[currentPosition]
361         }
362         writeRange(lastPosition, currentPosition, usedAppend, consumeChunk)
363         this.currentPosition = currentPosition
364         if (!isLenient) {
365             consumeNextToken(STRING)
366         }
367     }
368 
369     private fun writeRange(fromIndex: Int, toIndex: Int, currentChunkHasEscape: Boolean, consumeChunk: (stringChunk: String) -> Unit) {
370         if (currentChunkHasEscape) {
371             consumeChunk(decodedString(fromIndex, toIndex))
372         } else {
373             consumeChunk(substring(fromIndex, toIndex))
374         }
375     }
376 
377 
378     fun consumeString(): String {
379         if (peekedString != null) {
380             return takePeeked()
381         }
382 
383         return consumeKeyString()
384     }
385 
386     @JsName("consumeString2") // WA for JS issue
387     protected fun consumeString(source: CharSequence, startPosition: Int, current: Int): String {
388         var currentPosition = current
389         var lastPosition = startPosition
390         var char = source[currentPosition] // Avoid two range checks visible in the profiler
391         var usedAppend = false
392         while (char != STRING) {
393             if (char == STRING_ESC) {
394                 usedAppend = true
395                 currentPosition = prefetchOrEof(appendEscape(lastPosition, currentPosition))
396                 if (currentPosition == -1)
397                     fail("Unexpected EOF", currentPosition)
398                 lastPosition = currentPosition
399             } else if (++currentPosition >= source.length) {
400                 usedAppend = true
401                 // end of chunk
402                 appendRange(lastPosition, currentPosition)
403                 currentPosition = prefetchOrEof(currentPosition)
404                 if (currentPosition == -1)
405                     fail("Unexpected EOF", currentPosition)
406                 lastPosition = currentPosition
407             }
408             char = source[currentPosition]
409         }
410 
411         val string = if (!usedAppend) {
412             // there was no escaped chars
413             substring(lastPosition, currentPosition)
414         } else {
415             // some escaped chars were there
416             decodedString(lastPosition, currentPosition)
417         }
418         this.currentPosition = currentPosition + 1
419         return string
420     }
421 
422     private fun appendEscape(lastPosition: Int, current: Int): Int {
423         appendRange(lastPosition, current)
424         return appendEsc(current + 1)
425     }
426 
427     private fun decodedString(lastPosition: Int, currentPosition: Int): String {
428         appendRange(lastPosition, currentPosition)
429         val result = escapedString.toString()
430         escapedString.setLength(0)
431         return result
432     }
433 
434     private fun takePeeked(): String {
435         return peekedString!!.also { peekedString = null }
436     }
437 
438     fun consumeStringLenientNotNull(): String {
439         val result = consumeStringLenient()
440         /*
441          * Check if lenient value is 'null' _without_ quotation marks and fail for non-nullable read if so.
442          */
443         if (result == NULL && wasUnquotedString()) {
444             fail("Unexpected 'null' value instead of string literal")
445         }
446         return result
447     }
448 
449     private fun wasUnquotedString(): Boolean {
450         // Is invoked _only_ when the 'null' string was read, thus 'cP - 1' is always within bounds
451         return source[currentPosition - 1] != STRING
452     }
453 
454     // Allows consuming unquoted string
455     fun consumeStringLenient(): String {
456         if (peekedString != null) {
457             return takePeeked()
458         }
459         var current = skipWhitespaces()
460         if (current >= source.length || current == -1) fail("EOF", current)
461         val token = charToTokenClass(source[current])
462         if (token == TC_STRING) {
463             return consumeString()
464         }
465 
466         if (token != TC_OTHER) {
467             fail("Expected beginning of the string, but got ${source[current]}")
468         }
469         var usedAppend = false
470         while (charToTokenClass(source[current]) == TC_OTHER) {
471             ++current
472             if (current >= source.length) {
473                 usedAppend = true
474                 appendRange(currentPosition, current)
475                 val eof = prefetchOrEof(current)
476                 if (eof == -1) {
477                     // to handle plain lenient strings, such as top-level
478                     currentPosition = current
479                     return decodedString(0, 0)
480                 } else {
481                     current = eof
482                 }
483             }
484         }
485         val result = if (!usedAppend) {
486             substring(currentPosition, current)
487         } else {
488             decodedString(currentPosition, current)
489         }
490         currentPosition = current
491         return result
492     }
493 
494     // initializes buf usage upon the first encountered escaped char
495     protected open fun appendRange(fromIndex: Int, toIndex: Int) {
496         escapedString.append(source, fromIndex, toIndex)
497     }
498 
499     private fun appendEsc(startPosition: Int): Int {
500         var currentPosition = startPosition
501         currentPosition = prefetchOrEof(currentPosition)
502         if (currentPosition == -1) fail("Expected escape sequence to continue, got EOF")
503         val currentChar = source[currentPosition++]
504         if (currentChar == UNICODE_ESC) {
505             return appendHex(source, currentPosition)
506         }
507 
508         val c = escapeToChar(currentChar.code)
509         if (c == INVALID) fail("Invalid escaped char '$currentChar'")
510         escapedString.append(c)
511         return currentPosition
512     }
513 
514     private fun appendHex(source: CharSequence, startPos: Int): Int {
515         if (startPos + 4 >= source.length) {
516             currentPosition = startPos
517             ensureHaveChars()
518             if (currentPosition + 4 >= source.length)
519                 fail("Unexpected EOF during unicode escape")
520             return appendHex(source, currentPosition)
521         }
522         escapedString.append(
523             ((fromHexChar(source, startPos) shl 12) +
524                     (fromHexChar(source, startPos + 1) shl 8) +
525                     (fromHexChar(source, startPos + 2) shl 4) +
526                     fromHexChar(source, startPos + 3)).toChar()
527         )
528         return startPos + 4
529     }
530 
531     internal inline fun require(condition: Boolean, position: Int = currentPosition, message: () -> String) {
532         if (!condition) fail(message(), position)
533     }
534 
535     private fun fromHexChar(source: CharSequence, currentPosition: Int): Int {
536         return when (val character = source[currentPosition]) {
537             in '0'..'9' -> character.code - '0'.code
538             in 'a'..'f' -> character.code - 'a'.code + 10
539             in 'A'..'F' -> character.code - 'A'.code + 10
540             else -> fail("Invalid toHexChar char '$character' in unicode escape")
541         }
542     }
543 
544     fun skipElement(allowLenientStrings: Boolean) {
545         val tokenStack = mutableListOf<Byte>()
546         var lastToken = peekNextToken()
547         if (lastToken != TC_BEGIN_LIST && lastToken != TC_BEGIN_OBJ) {
548             consumeStringLenient()
549             return
550         }
551         while (true) {
552             lastToken = peekNextToken()
553             if (lastToken == TC_STRING) {
554                 if (allowLenientStrings) consumeStringLenient() else consumeKeyString()
555                 continue
556             }
557             when (lastToken) {
558                 TC_BEGIN_LIST, TC_BEGIN_OBJ -> {
559                     tokenStack.add(lastToken)
560                 }
561                 TC_END_LIST -> {
562                     if (tokenStack.last() != TC_BEGIN_LIST) throw JsonDecodingException(
563                         currentPosition,
564                         "found ] instead of } at path: $path",
565                         source
566                     )
567                     tokenStack.removeLast()
568                 }
569                 TC_END_OBJ -> {
570                     if (tokenStack.last() != TC_BEGIN_OBJ) throw JsonDecodingException(
571                         currentPosition,
572                         "found } instead of ] at path: $path",
573                         source
574                     )
575                     tokenStack.removeLast()
576                 }
577                 TC_EOF -> fail("Unexpected end of input due to malformed JSON during ignoring unknown keys")
578             }
579             consumeNextToken()
580             if (tokenStack.size == 0) return
581         }
582     }
583 
584     override fun toString(): String {
585         return "JsonReader(source='$source', currentPosition=$currentPosition)"
586     }
587 
588     fun failOnUnknownKey(key: String) {
589         // At this moment we already have both key and semicolon (and whitespaces! consumed),
590         // but still would like an error to point to the beginning of the key, so we are backtracking it
591         val processed = substring(0, currentPosition)
592         val lastIndexOf = processed.lastIndexOf(key)
593         fail("Encountered an unknown key '$key'", lastIndexOf, ignoreUnknownKeysHint)
594     }
595 
596     fun fail(message: String, position: Int = currentPosition, hint: String = ""): Nothing {
597         val hintMessage = if (hint.isEmpty()) "" else "\n$hint"
598         throw JsonDecodingException(position, message + " at path: " + path.getPath() + hintMessage, source)
599     }
600 
601     fun consumeNumericLiteral(): Long {
602         /*
603          * This is an optimized (~40% for numbers) version of consumeString().toLong()
604          * that doesn't allocate and also doesn't support any radix but 10
605          */
606         var current = skipWhitespaces()
607         current = prefetchOrEof(current)
608         if (current >= source.length || current == -1) fail("EOF")
609         val hasQuotation = if (source[current] == STRING) {
610             // Check it again
611             // not sure if should call ensureHaveChars() because threshold is far greater than chars count in MAX_LONG
612             if (++current == source.length) fail("EOF")
613             true
614         } else {
615             false
616         }
617         var accumulator = 0L
618         var exponentAccumulator = 0L
619         var isNegative = false
620         var isExponentPositive = false
621         var hasExponent = false
622         val start = current
623         while (current != source.length) {
624             val ch: Char = source[current]
625             if ((ch == 'e' || ch == 'E') && !hasExponent) {
626                 if (current == start) fail("Unexpected symbol $ch in numeric literal")
627                 isExponentPositive = true
628                 hasExponent = true
629                 ++current
630                 continue
631             }
632             if (ch == '-' && hasExponent) {
633                 if (current == start) fail("Unexpected symbol '-' in numeric literal")
634                 isExponentPositive = false
635                 ++current
636                 continue
637             }
638             if (ch == '+' && hasExponent) {
639                 if (current == start) fail("Unexpected symbol '+' in numeric literal")
640                 isExponentPositive = true
641                 ++current
642                 continue
643             }
644             if (ch == '-') {
645                 if (current != start) fail("Unexpected symbol '-' in numeric literal")
646                 isNegative = true
647                 ++current
648                 continue
649             }
650             val token = charToTokenClass(ch)
651             if (token != TC_OTHER) break
652             ++current
653             val digit = ch - '0'
654             if (digit !in 0..9) fail("Unexpected symbol '$ch' in numeric literal")
655             if (hasExponent) {
656                 exponentAccumulator = exponentAccumulator * 10 + digit
657                 continue
658             }
659             accumulator = accumulator * 10 - digit
660             if (accumulator > 0) fail("Numeric value overflow")
661         }
662         val hasChars = current != start
663         if (start == current || (isNegative && start == current - 1)) {
664             fail("Expected numeric literal")
665         }
666         if (hasQuotation) {
667             if (!hasChars) fail("EOF")
668             if (source[current] != STRING) fail("Expected closing quotation mark")
669             ++current
670         }
671         currentPosition = current
672 
673         fun calculateExponent(exponentAccumulator: Long, isExponentPositive: Boolean): Double = when (isExponentPositive) {
674             false -> 10.0.pow(-exponentAccumulator.toDouble())
675             true -> 10.0.pow(exponentAccumulator.toDouble())
676         }
677 
678         if (hasExponent) {
679             val doubleAccumulator  = accumulator.toDouble() * calculateExponent(exponentAccumulator, isExponentPositive)
680             if (doubleAccumulator > Long.MAX_VALUE || doubleAccumulator < Long.MIN_VALUE) fail("Numeric value overflow")
681             if (floor(doubleAccumulator) != doubleAccumulator) fail("Can't convert $doubleAccumulator to Long")
682             accumulator = doubleAccumulator.toLong()
683         }
684 
685         return when {
686             isNegative -> accumulator
687             accumulator != Long.MIN_VALUE -> -accumulator
688             else -> fail("Numeric value overflow")
689         }
690     }
691 
692 
693     fun consumeBoolean(): Boolean {
694         return consumeBoolean(skipWhitespaces())
695     }
696 
697     fun consumeBooleanLenient(): Boolean {
698         var current = skipWhitespaces()
699         if (current == source.length) fail("EOF")
700         val hasQuotation = if (source[current] == STRING) {
701             ++current
702             true
703         } else {
704             false
705         }
706         val result = consumeBoolean(current)
707         if (hasQuotation) {
708             if (currentPosition == source.length) fail("EOF")
709             if (source[currentPosition] != STRING)
710                 fail("Expected closing quotation mark")
711             ++currentPosition
712         }
713         return result
714     }
715 
716     @JsName("consumeBoolean2") // WA for JS issue
717     private fun consumeBoolean(start: Int): Boolean {
718         /*
719          * In ASCII representation, upper and lower case letters are different
720          * in 6-th bit and we leverage this fact, our implementation consumes boolean literals
721          * in a case-insensitive manner.
722          */
723         var current = prefetchOrEof(start)
724         if (current >= source.length || current == -1) fail("EOF")
725         return when (source[current++].code or asciiCaseMask) {
726             't'.code -> {
727                 consumeBooleanLiteral("rue", current)
728                 true
729             }
730             'f'.code -> {
731                 consumeBooleanLiteral("alse", current)
732                 false
733             }
734             else -> {
735                 fail("Expected valid boolean literal prefix, but had '${consumeStringLenient()}'")
736             }
737         }
738     }
739 
740     private fun consumeBooleanLiteral(literalSuffix: String, current: Int) {
741         if (source.length - current < literalSuffix.length) {
742             fail("Unexpected end of boolean literal")
743         }
744 
745         for (i in literalSuffix.indices) {
746             val expected = literalSuffix[i]
747             val actual = source[current + i]
748             if (expected.code != actual.code or asciiCaseMask) {
749                 fail("Expected valid boolean literal prefix, but had '${consumeStringLenient()}'")
750             }
751         }
752 
753         currentPosition = current + literalSuffix.length
754     }
755 
756     private inline fun <T> withPositionRollback(action: () -> T): T {
757         val snapshot = currentPosition
758         try {
759             return action()
760         } finally {
761             currentPosition = snapshot
762         }
763     }
764 }
765