Skip to content

Commit 207e0c5

Browse files
committed
Encode the form of formats for parsing and formatting anything
`FormatStructure` defines how a thing is parsed and/or formatted. It encodes a group of `FieldFormatDirective` instances and directs how they should behave as a collective: * should they be parsed/output optionally? * can there be several alternatives during parsing? * etc. The intended way to use the format machinery is to specify a `FormatStructure` instance and obtain a parser or a formatter by calling `parser()` or `formatter()` on it. `CachedFormatStructure` can be used as a thin caching wrapper, ensuring `parser()` and `formatter()` don't get called repeatedly.
1 parent 7509d73 commit 207e0c5

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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.format.formatter.*
9+
import kotlinx.datetime.internal.format.parser.*
10+
11+
internal sealed interface FormatStructure<in T> {
12+
fun parser(): ParserStructure<T>
13+
fun formatter(): FormatterStructure<T>
14+
}
15+
16+
internal class BasicFormatStructure<in T>(
17+
val directive: FieldFormatDirective<T>
18+
) : NonConcatenatedFormatStructure<T> {
19+
override fun toString(): String = "BasicFormatStructure($directive)"
20+
21+
override fun equals(other: Any?): Boolean = other is BasicFormatStructure<*> && directive == other.directive
22+
override fun hashCode(): Int = directive.hashCode()
23+
24+
override fun parser(): ParserStructure<T> = directive.parser()
25+
override fun formatter(): FormatterStructure<T> = directive.formatter()
26+
}
27+
28+
internal class ConstantFormatStructure<in T>(
29+
val string: String
30+
) : NonConcatenatedFormatStructure<T> {
31+
override fun toString(): String = "ConstantFormatStructure($string)"
32+
33+
override fun equals(other: Any?): Boolean = other is ConstantFormatStructure<*> && string == other.string
34+
override fun hashCode(): Int = string.hashCode()
35+
36+
override fun parser(): ParserStructure<T> = ParserStructure(
37+
operations = when {
38+
string.isEmpty() -> emptyList()
39+
else -> buildList {
40+
val suffix = if (string[0].isDigit()) {
41+
add(NumberSpanParserOperation(listOf(ConstantNumberConsumer(string.takeWhile { it.isDigit() }))))
42+
string.dropWhile { it.isDigit() }
43+
} else {
44+
string
45+
}
46+
if (suffix.isNotEmpty()) {
47+
if (suffix[suffix.length - 1].isDigit()) {
48+
add(PlainStringParserOperation(suffix.dropLastWhile { it.isDigit() }))
49+
add(NumberSpanParserOperation(listOf(ConstantNumberConsumer(suffix.takeLastWhile { it.isDigit() }))))
50+
} else {
51+
add(PlainStringParserOperation(suffix))
52+
}
53+
}
54+
}
55+
},
56+
followedBy = emptyList()
57+
)
58+
59+
override fun formatter(): FormatterStructure<T> = ConstantStringFormatterStructure(string)
60+
}
61+
62+
internal class SignedFormatStructure<in T>(
63+
val format: FormatStructure<T>,
64+
val withPlusSign: Boolean,
65+
) : NonConcatenatedFormatStructure<T> {
66+
67+
private val fieldSigns = basicFormats(format).mapNotNull { it.field.sign }.toSet()
68+
69+
init {
70+
require(fieldSigns.isNotEmpty()) { "Signed format must contain at least one field with a sign" }
71+
}
72+
73+
override fun toString(): String = "SignedFormatStructure($format)"
74+
75+
override fun equals(other: Any?): Boolean =
76+
other is SignedFormatStructure<*> && format == other.format && withPlusSign == other.withPlusSign
77+
78+
override fun hashCode(): Int = 31 * format.hashCode() + withPlusSign.hashCode()
79+
80+
override fun parser(): ParserStructure<T> = listOf(
81+
ParserStructure(
82+
operations = listOf(
83+
SignParser(
84+
isNegativeSetter = { value, isNegative ->
85+
for (field in fieldSigns) {
86+
val wasNegative = field.isNegative.getter(value) == true
87+
// TODO: replacing `!=` with `xor` fails on JS
88+
field.isNegative.trySetWithoutReassigning(value, isNegative != wasNegative)
89+
}
90+
},
91+
withPlusSign = withPlusSign,
92+
whatThisExpects = "sign for ${this.fieldSigns}"
93+
)
94+
),
95+
emptyList()
96+
), format.parser()
97+
).concat()
98+
99+
override fun formatter(): FormatterStructure<T> {
100+
val innerFormat = format.formatter()
101+
fun checkIfAllNegative(value: T): Boolean {
102+
var seenNonZero = false
103+
for (check in fieldSigns) {
104+
when {
105+
check.isNegative.getter(value) == true -> seenNonZero = true
106+
check.isZero(value) -> continue
107+
else -> return false
108+
}
109+
}
110+
return seenNonZero
111+
}
112+
return SignedFormatter(
113+
innerFormat,
114+
::checkIfAllNegative,
115+
withPlusSign
116+
)
117+
}
118+
}
119+
120+
internal class AlternativesParsingFormatStructure<in T>(
121+
val mainFormat: FormatStructure<T>,
122+
val formats: List<FormatStructure<T>>,
123+
) : NonConcatenatedFormatStructure<T> {
124+
override fun toString(): String = "AlternativesParsing($formats)"
125+
126+
override fun equals(other: Any?): Boolean =
127+
other is AlternativesParsingFormatStructure<*> && mainFormat == other.mainFormat && formats == other.formats
128+
129+
override fun hashCode(): Int = 31 * mainFormat.hashCode() + formats.hashCode()
130+
131+
override fun parser(): ParserStructure<T> = ParserStructure(operations = emptyList(), followedBy = buildList {
132+
add(mainFormat.parser())
133+
for (format in formats) {
134+
add(format.parser())
135+
}
136+
})
137+
138+
override fun formatter(): FormatterStructure<T> = mainFormat.formatter()
139+
}
140+
141+
internal class OptionalFormatStructure<in T>(
142+
val onZero: String,
143+
val format: FormatStructure<T>,
144+
) : NonConcatenatedFormatStructure<T> {
145+
override fun toString(): String = "Optional($onZero, $format)"
146+
147+
private val fields = basicFormats(format).map { it.field }.distinct().map { field ->
148+
PropertyWithDefault.fromField(field)
149+
}
150+
151+
override fun equals(other: Any?): Boolean =
152+
other is OptionalFormatStructure<*> && onZero == other.onZero && format == other.format
153+
154+
override fun hashCode(): Int = 31 * onZero.hashCode() + format.hashCode()
155+
156+
override fun parser(): ParserStructure<T> = ParserStructure(
157+
operations = emptyList(),
158+
followedBy = listOf(
159+
format.parser(),
160+
listOf(
161+
ConstantFormatStructure<T>(onZero).parser(),
162+
ParserStructure(
163+
listOf(
164+
UnconditionalModification {
165+
for (field in fields) {
166+
field.assignDefault(it)
167+
}
168+
}
169+
),
170+
emptyList()
171+
)
172+
).concat()
173+
)
174+
)
175+
176+
override fun formatter(): FormatterStructure<T> {
177+
val formatter = format.formatter()
178+
val predicate = conjunctionPredicate(fields.map { it.isDefaultComparisonPredicate() })
179+
return ConditionalFormatter(
180+
listOf(
181+
predicate::test to ConstantStringFormatterStructure(onZero),
182+
Truth::test to formatter
183+
)
184+
)
185+
}
186+
187+
private class PropertyWithDefault<in T, E> private constructor(
188+
private val accessor: Accessor<T, E>,
189+
private val defaultValue: E
190+
) {
191+
companion object {
192+
fun <T, E> fromField(field: FieldSpec<T, E>): PropertyWithDefault<T, E> {
193+
val default = field.defaultValue
194+
require(default != null) {
195+
"The field '${field.name}' does not define a default value"
196+
}
197+
return PropertyWithDefault(field.accessor, default)
198+
}
199+
}
200+
201+
inline fun assignDefault(target: T) {
202+
accessor.trySetWithoutReassigning(target, defaultValue)
203+
}
204+
205+
inline fun isDefaultComparisonPredicate() = ComparisonPredicate(defaultValue, accessor::getter)
206+
}
207+
}
208+
209+
internal sealed interface NonConcatenatedFormatStructure<in T> : FormatStructure<T>
210+
211+
internal open class ConcatenatedFormatStructure<in T>(
212+
val formats: List<NonConcatenatedFormatStructure<T>>
213+
) : FormatStructure<T> {
214+
override fun toString(): String = "ConcatenatedFormatStructure(${formats.joinToString(", ")})"
215+
216+
override fun equals(other: Any?): Boolean = other is ConcatenatedFormatStructure<*> && formats == other.formats
217+
218+
override fun hashCode(): Int = formats.hashCode()
219+
220+
override fun parser(): ParserStructure<T> = formats.map { it.parser() }.concat()
221+
222+
override fun formatter(): FormatterStructure<T> {
223+
val formatters = formats.map { it.formatter() }
224+
return if (formatters.size == 1) {
225+
formatters.single()
226+
} else {
227+
ConcatenatedFormatter(formatters)
228+
}
229+
}
230+
}
231+
232+
internal class CachedFormatStructure<in T>(formats: List<NonConcatenatedFormatStructure<T>>) :
233+
ConcatenatedFormatStructure<T>(formats) {
234+
private val cachedFormatter: FormatterStructure<T> by lazy { super.formatter() }
235+
private val cachedParser: ParserStructure<T> by lazy { super.parser() }
236+
237+
override fun formatter(): FormatterStructure<T> = cachedFormatter
238+
239+
override fun parser(): ParserStructure<T> = cachedParser
240+
}
241+
242+
private fun <T> basicFormats(format: FormatStructure<T>): List<FieldFormatDirective<T>> = buildList {
243+
fun rec(format: FormatStructure<T>) {
244+
when (format) {
245+
is BasicFormatStructure -> add(format.directive)
246+
is ConcatenatedFormatStructure -> format.formats.forEach { rec(it) }
247+
is ConstantFormatStructure -> {}
248+
is SignedFormatStructure -> rec(format.format)
249+
is AlternativesParsingFormatStructure -> {
250+
rec(format.mainFormat); format.formats.forEach { rec(it) }
251+
}
252+
253+
is OptionalFormatStructure -> rec(format.format)
254+
}
255+
}
256+
rec(format)
257+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
internal interface Predicate<in T> {
9+
fun test(value: T): Boolean
10+
}
11+
12+
internal object Truth: Predicate<Any?> {
13+
override fun test(value: Any?): Boolean = true
14+
}
15+
16+
internal class ComparisonPredicate<in T, E>(
17+
private val expectedValue: E,
18+
private val getter: (T) -> E?
19+
): Predicate<T> {
20+
override fun test(value: T): Boolean = getter(value) == expectedValue
21+
}
22+
23+
private class ConjunctionPredicate<in T>(
24+
private val predicates: List<Predicate<T>>
25+
): Predicate<T> {
26+
override fun test(value: T): Boolean = predicates.all { it.test(value) }
27+
}
28+
29+
internal fun<T> conjunctionPredicate(
30+
predicates: List<Predicate<T>>
31+
): Predicate<T> = when {
32+
predicates.isEmpty() -> Truth
33+
predicates.size == 1 -> predicates.single()
34+
else -> ConjunctionPredicate(predicates)
35+
}

0 commit comments

Comments
 (0)