5
5
6
6
package kotlinx.datetime.internal.format.parser
7
7
8
- import kotlinx.datetime.internal.POWERS_OF_TEN
9
- import kotlinx.datetime.internal.DecimalFraction
8
+ import kotlinx.datetime.internal.*
10
9
11
10
/* *
12
11
* A parser that expects to receive a string consisting of [length] digits, or, if [length] is `null`,
@@ -19,13 +18,16 @@ internal sealed class NumberConsumer<in Receiver>(
19
18
val whatThisExpects : String
20
19
) {
21
20
/* *
22
- * Wholly consumes the given [input]. Should be called with a string consisting of [length] digits, or,
23
- * if [length] is `null`, with a string consisting of any number of digits. [consume] itself does not
24
- * necessarily check the length of the input string, instead expecting to be passed a valid one.
21
+ * Wholly consumes the substring of [input] between indices [start] (inclusive) and [end] (exclusive).
22
+ *
23
+ * If [length] is non-null, [end] must be equal to [start] + [length].
24
+ * In any case, the substring between [start] and [end] must consist of ASCII digits only.
25
+ * [consume] itself does not necessarily check the length of the input string,
26
+ * instead expecting to be given a valid one.
25
27
*
26
28
* Returns `null` on success and a `NumberConsumptionError` on failure.
27
29
*/
28
- abstract fun consume (storage : Receiver , input : String ): NumberConsumptionError ?
30
+ abstract fun consume (storage : Receiver , input : CharSequence , start : Int , end : Int ): NumberConsumptionError ?
29
31
}
30
32
31
33
internal interface NumberConsumptionError {
@@ -50,7 +52,6 @@ internal interface NumberConsumptionError {
50
52
/* *
51
53
* A parser that accepts an [Int] value in range from `0` to [Int.MAX_VALUE].
52
54
*/
53
- // TODO: should the parser reject excessive padding?
54
55
internal class UnsignedIntConsumer <in Receiver >(
55
56
private val minLength : Int? ,
56
57
private val maxLength : Int? ,
@@ -63,10 +64,10 @@ internal class UnsignedIntConsumer<in Receiver>(
63
64
require(length == null || length in 1 .. 9 ) { " Invalid length for field $whatThisExpects : $length " }
64
65
}
65
66
66
- override fun consume (storage : Receiver , input : String ): NumberConsumptionError ? = when {
67
- maxLength != null && input.length > maxLength -> NumberConsumptionError .TooManyDigits (maxLength)
68
- minLength != null && input.length < minLength -> NumberConsumptionError .TooFewDigits (minLength)
69
- else -> when (val result = input.toIntOrNull( )) {
67
+ override fun consume (storage : Receiver , input : CharSequence , start : Int , end : Int ): NumberConsumptionError ? = when {
68
+ maxLength != null && end - start > maxLength -> NumberConsumptionError .TooManyDigits (maxLength)
69
+ minLength != null && end - start < minLength -> NumberConsumptionError .TooFewDigits (minLength)
70
+ else -> when (val result = input.parseAsciiIntOrNull(start = start, end = end )) {
70
71
null -> NumberConsumptionError .ExpectedInt
71
72
else -> setter.setWithoutReassigning(storage, if (multiplyByMinus1) - result else result)
72
73
}
@@ -84,9 +85,13 @@ internal class ReducedIntConsumer<in Receiver>(
84
85
private val baseMod = base % modulo
85
86
private val baseFloor = base - baseMod
86
87
87
- override fun consume (storage : Receiver , input : String ): NumberConsumptionError ? = when (val result = input.toIntOrNull()) {
88
- null -> NumberConsumptionError .ExpectedInt
89
- else -> setter.setWithoutReassigning(storage, if (result >= baseMod) {
88
+ init {
89
+ require(length in 1 .. 9 ) { " Invalid length for field $whatThisExpects : $length " }
90
+ }
91
+
92
+ override fun consume (storage : Receiver , input : CharSequence , start : Int , end : Int ): NumberConsumptionError ? {
93
+ val result = input.parseAsciiInt(start = start, end = end)
94
+ return setter.setWithoutReassigning(storage, if (result >= baseMod) {
90
95
baseFloor + result
91
96
} else {
92
97
baseFloor + modulo + result
@@ -100,31 +105,35 @@ internal class ReducedIntConsumer<in Receiver>(
100
105
internal class ConstantNumberConsumer <in Receiver >(
101
106
private val expected : String
102
107
) : NumberConsumer<Receiver>(expected.length, " the predefined string $expected " ) {
103
- override fun consume (storage : Receiver , input : String ): NumberConsumptionError ? = if (input == expected) {
104
- null
105
- } else {
106
- NumberConsumptionError .WrongConstant (expected)
107
- }
108
+ override fun consume (storage : Receiver , input : CharSequence , start : Int , end : Int ): NumberConsumptionError ? =
109
+ if (input.substring(startIndex = start, endIndex = end) == expected) {
110
+ null
111
+ } else {
112
+ NumberConsumptionError .WrongConstant (expected)
113
+ }
108
114
}
109
115
110
116
internal class FractionPartConsumer <in Receiver >(
111
- private val minLength : Int? ,
112
- private val maxLength : Int? ,
117
+ private val minLength : Int ,
118
+ private val maxLength : Int ,
113
119
private val setter : AssignableField <Receiver , DecimalFraction >,
114
120
name : String ,
115
121
) : NumberConsumer<Receiver>(if (minLength == maxLength) minLength else null , name) {
116
122
init {
117
- require(minLength == null || minLength in 1 .. 9 ) { " Invalid length for field $whatThisExpects : $length " }
118
- // TODO: bounds on maxLength
123
+ require(minLength in 1 .. 9 ) {
124
+ " Invalid minimum length $minLength for field $whatThisExpects : expected 1..9"
125
+ }
126
+ require(maxLength in minLength.. 9 ) {
127
+ " Invalid maximum length $maxLength for field $whatThisExpects : expected $minLength ..9"
128
+ }
119
129
}
120
130
121
- override fun consume (storage : Receiver , input : String ): NumberConsumptionError ? = when {
122
- minLength != null && input.length < minLength -> NumberConsumptionError .TooFewDigits (minLength)
123
- maxLength != null && input.length > maxLength -> NumberConsumptionError .TooManyDigits (maxLength)
124
- else -> when (val numerator = input.toIntOrNull()) {
125
- null -> NumberConsumptionError .TooManyDigits (9 )
126
- else -> setter.setWithoutReassigning(storage, DecimalFraction (numerator, input.length))
127
- }
131
+ override fun consume (storage : Receiver , input : CharSequence , start : Int , end : Int ): NumberConsumptionError ? = when {
132
+ end - start < minLength -> NumberConsumptionError .TooFewDigits (minLength)
133
+ end - start > maxLength -> NumberConsumptionError .TooManyDigits (maxLength)
134
+ else -> setter.setWithoutReassigning(
135
+ storage, DecimalFraction (input.parseAsciiInt(start = start, end = end), end - start)
136
+ )
128
137
}
129
138
}
130
139
@@ -135,3 +144,36 @@ private fun <Object, Type> AssignableField<Object, Type>.setWithoutReassigning(
135
144
val conflictingValue = trySetWithoutReassigning(receiver, value) ? : return null
136
145
return NumberConsumptionError .Conflicting (conflictingValue)
137
146
}
147
+
148
+ /* *
149
+ * Parses a substring of the receiver string as a positive ASCII integer.
150
+ *
151
+ * All characters between [start] (inclusive) and [end] (exclusive) must be ASCII digits,
152
+ * and the size of the substring must be at most 9, but the function does not check it.
153
+ */
154
+ private fun CharSequence.parseAsciiInt (start : Int , end : Int ): Int {
155
+ var result = 0
156
+ for (i in start until end) {
157
+ val digit = this [i]
158
+ result = result * 10 + digit.asciiDigitToInt()
159
+ }
160
+ return result
161
+ }
162
+
163
+ /* *
164
+ * Parses a substring of the receiver string as a positive ASCII integer.
165
+ *
166
+ * All characters between [start] (inclusive) and [end] (exclusive) must be ASCII digits,
167
+ * but the function does not check it.
168
+ *
169
+ * Returns `null` if the result does not fit into a positive [Int].
170
+ */
171
+ private fun CharSequence.parseAsciiIntOrNull (start : Int , end : Int ): Int? {
172
+ var result = 0
173
+ for (i in start until end) {
174
+ val digit = this [i]
175
+ result = result * 10 + digit.asciiDigitToInt()
176
+ if (result < 0 ) return null
177
+ }
178
+ return result
179
+ }
0 commit comments