Skip to content

Commit 7509d73

Browse files
committed
Add a way to specify how a field should be parsed and formatted
We need a way to specify "format a month as a name" and "format a month as a number without padding." The new `FieldFormatDirective` interface encodes this concept.
1 parent 5db4440 commit 7509d73

File tree

1 file changed

+235
-0
lines changed

1 file changed

+235
-0
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright 2019-2023 JetBrains s.r.o. and contributors.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.datetime.internal.format
7+
8+
import kotlinx.datetime.internal.DecimalFraction
9+
import kotlinx.datetime.internal.format.formatter.*
10+
import kotlinx.datetime.internal.format.parser.*
11+
12+
/**
13+
* A directive that specifies a way to parse and format the [field].
14+
*/
15+
internal interface FieldFormatDirective<in Target> {
16+
/**
17+
* The field parsed and formatted by this directive.
18+
*/
19+
val field: FieldSpec<Target, *>
20+
21+
/**
22+
* The formatter operation that formats the field.
23+
*/
24+
fun formatter(): FormatterStructure<Target>
25+
26+
/**
27+
* The parser structure that parses the field.
28+
*/
29+
fun parser(): ParserStructure<Target>
30+
}
31+
32+
/**
33+
* A directive for a decimal format of an integer field that is known to be unsigned.
34+
* The field is formatted with the field padded to [minDigits] with zeroes,
35+
* and the parser expects the field to be at least [minDigits] digits long.
36+
*/
37+
internal abstract class UnsignedIntFieldFormatDirective<in Target>(
38+
final override val field: UnsignedFieldSpec<Target>,
39+
private val minDigits: Int,
40+
private val spacePadding: Int?,
41+
) : FieldFormatDirective<Target> {
42+
43+
private val maxDigits: Int = field.maxDigits
44+
45+
init {
46+
require(minDigits >= 0) {
47+
"The minimum number of digits ($minDigits) is negative"
48+
}
49+
require(maxDigits >= minDigits) {
50+
"The maximum number of digits ($maxDigits) is less than the minimum number of digits ($minDigits)"
51+
}
52+
if (spacePadding != null) {
53+
require(spacePadding > minDigits) {
54+
"The space padding ($spacePadding) should be more than the minimum number of digits ($minDigits)"
55+
}
56+
}
57+
}
58+
59+
override fun formatter(): FormatterStructure<Target> {
60+
val formatter = UnsignedIntFormatterStructure(
61+
number = field.accessor::getterNotNull,
62+
zeroPadding = minDigits,
63+
)
64+
return if (spacePadding != null) SpacePaddedFormatter(formatter, spacePadding) else formatter
65+
}
66+
67+
override fun parser(): ParserStructure<Target> =
68+
spaceAndZeroPaddedUnsignedInt(minDigits, maxDigits, spacePadding, field.accessor, field.name)
69+
}
70+
71+
/**
72+
* A directive for a string-based format of an integer field that is known to be unsigned.
73+
*/
74+
internal abstract class NamedUnsignedIntFieldFormatDirective<in Target>(
75+
final override val field: UnsignedFieldSpec<Target>,
76+
private val values: List<String>,
77+
private val name: String,
78+
) : FieldFormatDirective<Target> {
79+
80+
init {
81+
require(values.size == field.maxValue - field.minValue + 1) {
82+
"The number of values (${values.size}) in $values does not match the range of the field (${field.maxValue - field.minValue + 1})"
83+
}
84+
}
85+
86+
private fun getStringValue(target: Target): String = field.accessor.getterNotNull(target).let {
87+
values.getOrNull(it-field.minValue)
88+
?: "The value $it of ${field.name} does not have a corresponding string representation"
89+
}
90+
91+
private inner class AssignableString: AssignableField<Target, String> {
92+
override fun trySetWithoutReassigning(container: Target, newValue: String): String? =
93+
field.accessor.trySetWithoutReassigning(container, values.indexOf(newValue) + field.minValue)?.let {
94+
values[it - field.minValue]
95+
}
96+
97+
override val name: String get() = this@NamedUnsignedIntFieldFormatDirective.name
98+
}
99+
100+
override fun formatter(): FormatterStructure<Target> =
101+
StringFormatterStructure(::getStringValue)
102+
103+
override fun parser(): ParserStructure<Target> =
104+
ParserStructure(
105+
listOf(
106+
StringSetParserOperation(values, AssignableString(), "One of $values for $name")
107+
), emptyList()
108+
)
109+
}
110+
111+
/**
112+
* A directive for a string-based format of an enum field.
113+
*/
114+
internal abstract class NamedEnumIntFieldFormatDirective<in Target, Type>(
115+
final override val field: FieldSpec<Target, Type>,
116+
private val mapping: Map<Type, String>,
117+
private val name: String,
118+
) : FieldFormatDirective<Target> {
119+
120+
private val reverseMapping = mapping.entries.associate { it.value to it.key }
121+
122+
private fun getStringValue(target: Target): String = field.accessor.getterNotNull(target).let {
123+
mapping[field.accessor.getterNotNull(target)]
124+
?: "The value $it of ${field.name} does not have a corresponding string representation"
125+
}
126+
127+
private inner class AssignableString: AssignableField<Target, String> {
128+
override fun trySetWithoutReassigning(container: Target, newValue: String): String? =
129+
field.accessor.trySetWithoutReassigning(container, reverseMapping[newValue]!!)?.let { mapping[it] }
130+
131+
override val name: String get() = this@NamedEnumIntFieldFormatDirective.name
132+
}
133+
134+
override fun formatter(): FormatterStructure<Target> =
135+
StringFormatterStructure(::getStringValue)
136+
137+
override fun parser(): ParserStructure<Target> =
138+
ParserStructure(
139+
listOf(
140+
StringSetParserOperation(mapping.values, AssignableString(), "One of ${mapping.values} for $name")
141+
), emptyList()
142+
)
143+
}
144+
145+
internal abstract class StringFieldFormatDirective<in Target>(
146+
final override val field: FieldSpec<Target, String>,
147+
private val acceptedStrings: Set<String>,
148+
) : FieldFormatDirective<Target> {
149+
150+
init {
151+
require(acceptedStrings.isNotEmpty()) {
152+
"The set of accepted strings is empty"
153+
}
154+
}
155+
156+
override fun formatter(): FormatterStructure<Target> =
157+
StringFormatterStructure(field.accessor::getterNotNull)
158+
159+
override fun parser(): ParserStructure<Target> =
160+
ParserStructure(
161+
listOf(StringSetParserOperation(acceptedStrings, field.accessor, field.name)),
162+
emptyList()
163+
)
164+
}
165+
166+
internal abstract class SignedIntFieldFormatDirective<in Target>(
167+
final override val field: FieldSpec<Target, Int>,
168+
private val minDigits: Int?,
169+
private val maxDigits: Int?,
170+
private val spacePadding: Int?,
171+
private val outputPlusOnExceededWidth: Int?,
172+
) : FieldFormatDirective<Target> {
173+
174+
init {
175+
require(minDigits == null || minDigits >= 0) { "The minimum number of digits ($minDigits) is negative" }
176+
require(maxDigits == null || minDigits == null || maxDigits >= minDigits) {
177+
"The maximum number of digits ($maxDigits) is less than the minimum number of digits ($minDigits)"
178+
}
179+
}
180+
181+
override fun formatter(): FormatterStructure<Target> {
182+
val formatter = SignedIntFormatterStructure(
183+
number = field.accessor::getterNotNull,
184+
zeroPadding = minDigits ?: 0,
185+
outputPlusOnExceededWidth = outputPlusOnExceededWidth,
186+
)
187+
return if (spacePadding != null) SpacePaddedFormatter(formatter, spacePadding) else formatter
188+
}
189+
190+
override fun parser(): ParserStructure<Target> =
191+
SignedIntParser(
192+
minDigits = minDigits,
193+
maxDigits = maxDigits,
194+
spacePadding = spacePadding,
195+
field.accessor,
196+
field.name,
197+
plusOnExceedsWidth = outputPlusOnExceededWidth,
198+
)
199+
}
200+
201+
internal abstract class DecimalFractionFieldFormatDirective<in Target>(
202+
final override val field: FieldSpec<Target, DecimalFraction>,
203+
private val minDigits: Int,
204+
private val maxDigits: Int,
205+
private val zerosToAdd: List<Int>,
206+
) : FieldFormatDirective<Target> {
207+
override fun formatter(): FormatterStructure<Target> =
208+
DecimalFractionFormatterStructure(field.accessor::getterNotNull, minDigits, maxDigits, zerosToAdd)
209+
210+
override fun parser(): ParserStructure<Target> = ParserStructure(
211+
listOf(
212+
NumberSpanParserOperation(
213+
listOf(FractionPartConsumer(minDigits, maxDigits, field.accessor, field.name))
214+
)
215+
),
216+
emptyList()
217+
)
218+
}
219+
220+
internal abstract class ReducedIntFieldDirective<in Target>(
221+
final override val field: FieldSpec<Target, Int>,
222+
private val digits: Int,
223+
private val base: Int,
224+
) : FieldFormatDirective<Target> {
225+
226+
override fun formatter(): FormatterStructure<Target> =
227+
ReducedIntFormatterStructure(
228+
number = field.accessor::getterNotNull,
229+
digits = digits,
230+
base = base,
231+
)
232+
233+
override fun parser(): ParserStructure<Target> =
234+
ReducedIntParser(digits = digits, base = base, field.accessor, field.name)
235+
}

0 commit comments

Comments
 (0)