• 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 internal class StringJsonLexer(override val source: String) : AbstractJsonLexer() {
8 
prefetchOrEofnull9     override fun prefetchOrEof(position: Int): Int = if (position < source.length) position else -1
10 
11     override fun consumeNextToken(): Byte {
12         val source = source
13         while (currentPosition != -1 && currentPosition < source.length) {
14             val ch = source[currentPosition++]
15             return when (val tc = charToTokenClass(ch)) {
16                 TC_WHITESPACE -> continue
17                 else -> tc
18             }
19         }
20         return TC_EOF
21     }
22 
tryConsumeCommanull23     override fun tryConsumeComma(): Boolean {
24         val current = skipWhitespaces()
25         if (current == source.length || current == -1) return false
26         if (source[current] == ',') {
27             ++currentPosition
28             return true
29         }
30         return false
31     }
32 
canConsumeValuenull33     override fun canConsumeValue(): Boolean {
34         var current = currentPosition
35         if (current == -1) return false
36         while (current < source.length) {
37             val c = source[current]
38             // Inlined skipWhitespaces without field spill and nested loop. Also faster then char2TokenClass
39             if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
40                 ++current
41                 continue
42             }
43             currentPosition = current
44             return isValidValueStart(c)
45         }
46         currentPosition = current
47         return false
48     }
49 
skipWhitespacesnull50     override fun skipWhitespaces(): Int {
51         var current = currentPosition
52         if (current == -1) return current
53         // Skip whitespaces
54         while (current < source.length) {
55             val c = source[current]
56             // Faster than char2TokenClass actually
57             if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
58                 ++current
59             } else {
60                 break
61             }
62         }
63         currentPosition = current
64         return current
65     }
66 
consumeNextTokennull67     override fun consumeNextToken(expected: Char) {
68         if (currentPosition == -1) unexpectedToken(expected)
69         val source = source
70         while (currentPosition < source.length) {
71             val c = source[currentPosition++]
72             if (c == ' ' || c == '\n' || c == '\r' || c == '\t') continue
73             if (c == expected) return
74             unexpectedToken(expected)
75         }
76         currentPosition = -1 // for correct EOF reporting
77         unexpectedToken(expected) // EOF
78     }
79 
consumeKeyStringnull80     override fun consumeKeyString(): String {
81         /*
82          * For strings we assume that escaped symbols are rather an exception, so firstly
83          * we optimistically scan for closing quote via intrinsified and blazing-fast 'indexOf',
84          * than do our pessimistic check for backslash and fallback to slow-path if necessary.
85          */
86         consumeNextToken(STRING)
87         val current = currentPosition
88         val closingQuote = source.indexOf('"', current)
89         if (closingQuote == -1) {
90             // advance currentPosition to a token after the end of the string to guess position in the error msg
91             // (not always correct, as `:`/`,` are valid contents of the string, but good guess anyway)
92             consumeStringLenient()
93             fail(TC_STRING, wasConsumed = false)
94         }
95         // Now we _optimistically_ know where the string ends (it might have been an escaped quote)
96         for (i in current until closingQuote) {
97             // Encountered escape sequence, should fallback to "slow" path and symbolic scanning
98             if (source[i] == STRING_ESC) {
99                 return consumeString(source, currentPosition, i)
100             }
101         }
102         this.currentPosition = closingQuote + 1
103         return source.substring(current, closingQuote)
104     }
105 
consumeStringChunkednull106     override fun consumeStringChunked(isLenient: Boolean, consumeChunk: (stringChunk: String) -> Unit) {
107         (if (isLenient) consumeStringLenient() else consumeString()).chunked(BATCH_SIZE).forEach(consumeChunk)
108     }
109 
peekLeadingMatchingValuenull110     override fun peekLeadingMatchingValue(keyToMatch: String, isLenient: Boolean): String? {
111         val positionSnapshot = currentPosition
112         try {
113             if (consumeNextToken() != TC_BEGIN_OBJ) return null // Malformed JSON, bailout
114             val firstKey = peekString(isLenient)
115             if (firstKey != keyToMatch) return null
116             discardPeeked() // consume firstKey
117             if (consumeNextToken() != TC_COLON) return null
118             return peekString(isLenient)
119         } finally {
120             // Restore the position
121             currentPosition = positionSnapshot
122             discardPeeked()
123         }
124     }
125 }
126 
127