55
66package kotlinx.datetime.internal.format.parser
77
8- import kotlinx.datetime.internal.POWERS_OF_TEN
9- import kotlinx.datetime.internal.DecimalFraction
8+ import kotlinx.datetime.internal.*
109
1110/* *
1211 * 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>(
1918 val whatThisExpects : String
2019) {
2120 /* *
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.
2527 *
2628 * Returns `null` on success and a `NumberConsumptionError` on failure.
2729 */
28- abstract fun consume (storage : Receiver , input : String ): NumberConsumptionError ?
30+ abstract fun consume (storage : Receiver , input : CharSequence , start : Int , end : Int ): NumberConsumptionError ?
2931}
3032
3133internal interface NumberConsumptionError {
@@ -50,7 +52,6 @@ internal interface NumberConsumptionError {
5052/* *
5153 * A parser that accepts an [Int] value in range from `0` to [Int.MAX_VALUE].
5254 */
53- // TODO: should the parser reject excessive padding?
5455internal class UnsignedIntConsumer <in Receiver >(
5556 private val minLength : Int? ,
5657 private val maxLength : Int? ,
@@ -63,10 +64,10 @@ internal class UnsignedIntConsumer<in Receiver>(
6364 require(length == null || length in 1 .. 9 ) { " Invalid length for field $whatThisExpects : $length " }
6465 }
6566
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 )) {
7071 null -> NumberConsumptionError .ExpectedInt
7172 else -> setter.setWithoutReassigning(storage, if (multiplyByMinus1) - result else result)
7273 }
@@ -84,9 +85,13 @@ internal class ReducedIntConsumer<in Receiver>(
8485 private val baseMod = base % modulo
8586 private val baseFloor = base - baseMod
8687
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) {
9095 baseFloor + result
9196 } else {
9297 baseFloor + modulo + result
@@ -100,31 +105,35 @@ internal class ReducedIntConsumer<in Receiver>(
100105internal class ConstantNumberConsumer <in Receiver >(
101106 private val expected : String
102107) : 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+ }
108114}
109115
110116internal class FractionPartConsumer <in Receiver >(
111- private val minLength : Int? ,
112- private val maxLength : Int? ,
117+ private val minLength : Int ,
118+ private val maxLength : Int ,
113119 private val setter : AssignableField <Receiver , DecimalFraction >,
114120 name : String ,
115121) : NumberConsumer<Receiver>(if (minLength == maxLength) minLength else null , name) {
116122 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+ }
119129 }
120130
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+ )
128137 }
129138}
130139
@@ -135,3 +144,36 @@ private fun <Object, Type> AssignableField<Object, Type>.setWithoutReassigning(
135144 val conflictingValue = trySetWithoutReassigning(receiver, value) ? : return null
136145 return NumberConsumptionError .Conflicting (conflictingValue)
137146}
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