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`,
@@ -50,7 +49,6 @@ internal interface NumberConsumptionError {
50
49
/* *
51
50
* A parser that accepts an [Int] value in range from `0` to [Int.MAX_VALUE].
52
51
*/
53
- // TODO: should the parser reject excessive padding?
54
52
internal class UnsignedIntConsumer <in Receiver >(
55
53
private val minLength : Int? ,
56
54
private val maxLength : Int? ,
@@ -66,7 +64,7 @@ internal class UnsignedIntConsumer<in Receiver>(
66
64
override fun consume (storage : Receiver , input : String ): NumberConsumptionError ? = when {
67
65
maxLength != null && input.length > maxLength -> NumberConsumptionError .TooManyDigits (maxLength)
68
66
minLength != null && input.length < minLength -> NumberConsumptionError .TooFewDigits (minLength)
69
- else -> when (val result = input.toIntOrNull ()) {
67
+ else -> when (val result = input.parseAsciiIntOrNull ()) {
70
68
null -> NumberConsumptionError .ExpectedInt
71
69
else -> setter.setWithoutReassigning(storage, if (multiplyByMinus1) - result else result)
72
70
}
@@ -84,9 +82,13 @@ internal class ReducedIntConsumer<in Receiver>(
84
82
private val baseMod = base % modulo
85
83
private val baseFloor = base - baseMod
86
84
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) {
85
+ init {
86
+ require(length in 1 .. 9 ) { " Invalid length for field $whatThisExpects : $length " }
87
+ }
88
+
89
+ override fun consume (storage : Receiver , input : String ): NumberConsumptionError ? {
90
+ val result = input.parseAsciiInt()
91
+ return setter.setWithoutReassigning(storage, if (result >= baseMod) {
90
92
baseFloor + result
91
93
} else {
92
94
baseFloor + modulo + result
@@ -108,23 +110,24 @@ internal class ConstantNumberConsumer<in Receiver>(
108
110
}
109
111
110
112
internal class FractionPartConsumer <in Receiver >(
111
- private val minLength : Int? ,
112
- private val maxLength : Int? ,
113
+ private val minLength : Int ,
114
+ private val maxLength : Int ,
113
115
private val setter : AssignableField <Receiver , DecimalFraction >,
114
116
name : String ,
115
117
) : NumberConsumer<Receiver>(if (minLength == maxLength) minLength else null , name) {
116
118
init {
117
- require(minLength == null || minLength in 1 .. 9 ) { " Invalid length for field $whatThisExpects : $length " }
118
- // TODO: bounds on maxLength
119
+ require(minLength in 1 .. 9 ) {
120
+ " Invalid minimum length $minLength for field $whatThisExpects : expected 1..9"
121
+ }
122
+ require(maxLength in minLength.. 9 ) {
123
+ " Invalid maximum length $maxLength for field $whatThisExpects : expected $minLength ..9"
124
+ }
119
125
}
120
126
121
127
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
- }
128
+ input.length < minLength -> NumberConsumptionError .TooFewDigits (minLength)
129
+ input.length > maxLength -> NumberConsumptionError .TooManyDigits (maxLength)
130
+ else -> setter.setWithoutReassigning(storage, DecimalFraction (input.parseAsciiInt(), input.length))
128
131
}
129
132
}
130
133
@@ -135,3 +138,36 @@ private fun <Object, Type> AssignableField<Object, Type>.setWithoutReassigning(
135
138
val conflictingValue = trySetWithoutReassigning(receiver, value) ? : return null
136
139
return NumberConsumptionError .Conflicting (conflictingValue)
137
140
}
141
+
142
+ /* *
143
+ * Parses a substring of the receiver string as a positive ASCII integer.
144
+ *
145
+ * All characters between [start] (inclusive) and [end] (exclusive) must be ASCII digits,
146
+ * and the size of the substring must be at most 9, but the function does not check it.
147
+ */
148
+ private fun String.parseAsciiInt (start : Int = 0, end : Int = length): Int {
149
+ var result = 0
150
+ for (i in start until end) {
151
+ val digit = this [i]
152
+ result = result * 10 + digit.asciiDigitToInt()
153
+ }
154
+ return result
155
+ }
156
+
157
+ /* *
158
+ * Parses a substring of the receiver string as a positive ASCII integer.
159
+ *
160
+ * All characters between [start] (inclusive) and [end] (exclusive) must be ASCII digits,
161
+ * but the function does not check it.
162
+ *
163
+ * Returns `null` if the result does not fit into a positive [Int].
164
+ */
165
+ private fun String.parseAsciiIntOrNull (start : Int = 0, end : Int = length): Int? {
166
+ var result = 0
167
+ for (i in start until end) {
168
+ val digit = this [i]
169
+ result = result * 10 + digit.asciiDigitToInt()
170
+ if (result < 0 ) return null
171
+ }
172
+ return result
173
+ }
0 commit comments