Skip to content

Commit 90bf21f

Browse files
committed
Implement general machinery for formatting entities as text
1 parent ea56baf commit 90bf21f

File tree

2 files changed

+196
-0
lines changed

2 files changed

+196
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2019-2022 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.formatter
7+
8+
internal interface FormatterStructure<in T> {
9+
fun format(obj: T, builder: Appendable, minusNotRequired: Boolean = false)
10+
}
11+
12+
internal class SpacePaddedFormatter<in T>(
13+
private val formatter: FormatterStructure<T>,
14+
private val padding: Int,
15+
): FormatterStructure<T> {
16+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
17+
val string = StringBuilder().let {
18+
formatter.format(obj, it, minusNotRequired)
19+
it.toString()
20+
}
21+
repeat(padding - string.length) { builder.append(' ') }
22+
builder.append(string)
23+
}
24+
}
25+
26+
internal class ConditionalFormatter<in T>(
27+
private val formatters: List<Pair<T.() -> Boolean, FormatterStructure<T>>>
28+
): FormatterStructure<T> {
29+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
30+
for ((condition, formatter) in formatters) {
31+
if (obj.condition()) {
32+
formatter.format(obj, builder, minusNotRequired)
33+
return
34+
}
35+
}
36+
}
37+
}
38+
39+
internal class SignedFormatter<in T>(
40+
private val formatter: FormatterStructure<T>,
41+
private val allSubFormatsNegative: T.() -> Boolean,
42+
private val alwaysOutputSign: Boolean,
43+
): FormatterStructure<T> {
44+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
45+
val sign = if (!minusNotRequired && obj.allSubFormatsNegative()) {
46+
'-'
47+
} else if (alwaysOutputSign) {
48+
'+'
49+
} else {
50+
null
51+
}
52+
sign?.let { builder.append(it) }
53+
formatter.format(obj, builder, minusNotRequired = minusNotRequired || sign == '-')
54+
}
55+
}
56+
57+
internal class ConcatenatedFormatter<in T>(
58+
private val formatters: List<FormatterStructure<T>>,
59+
): FormatterStructure<T> {
60+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
61+
for (formatter in formatters) {
62+
formatter.format(obj, builder, minusNotRequired)
63+
}
64+
}
65+
}
66+
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2019-2022 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.formatter
7+
8+
import kotlinx.datetime.internal.DecimalFraction
9+
import kotlinx.datetime.internal.POWERS_OF_TEN
10+
import kotlin.math.absoluteValue
11+
12+
internal class ConstantStringFormatterStructure<in T>(
13+
private val string: String,
14+
): FormatterStructure<T> {
15+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
16+
builder.append(string)
17+
}
18+
}
19+
20+
internal class UnsignedIntFormatterStructure<in T>(
21+
private val number: (T) -> Int,
22+
private val zeroPadding: Int,
23+
): FormatterStructure<T> {
24+
25+
init {
26+
require(zeroPadding >= 0) { "The minimum number of digits ($zeroPadding) is negative" }
27+
require(zeroPadding <= 9) { "The minimum number of digits ($zeroPadding) exceeds the length of an Int" }
28+
}
29+
30+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
31+
val num = number(obj)
32+
val numberStr = num.toString()
33+
repeat(zeroPadding - numberStr.length) { builder.append('0') }
34+
builder.append(numberStr)
35+
}
36+
}
37+
38+
internal class SignedIntFormatterStructure<in T>(
39+
private val number: (T) -> Int,
40+
private val zeroPadding: Int,
41+
private val outputPlusOnExceededWidth: Int?,
42+
): FormatterStructure<T> {
43+
44+
init {
45+
require(zeroPadding >= 0) { "The minimum number of digits ($zeroPadding) is negative" }
46+
require(zeroPadding <= 9) { "The minimum number of digits ($zeroPadding) exceeds the length of an Int" }
47+
}
48+
49+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
50+
val innerBuilder = StringBuilder()
51+
val number = number(obj).let { if (minusNotRequired && it < 0) -it else it }
52+
if (outputPlusOnExceededWidth != null && number >= POWERS_OF_TEN[outputPlusOnExceededWidth]) {
53+
innerBuilder.append('+')
54+
}
55+
if (number.absoluteValue < POWERS_OF_TEN[zeroPadding - 1]) {
56+
// needs padding
57+
if (number >= 0) {
58+
innerBuilder.append((number + POWERS_OF_TEN[zeroPadding])).deleteAt(0)
59+
} else {
60+
innerBuilder.append((number - POWERS_OF_TEN[zeroPadding])).deleteAt(1)
61+
}
62+
} else {
63+
innerBuilder.append(number)
64+
}
65+
builder.append(innerBuilder)
66+
}
67+
}
68+
69+
internal class DecimalFractionFormatterStructure<in T>(
70+
private val number: (T) -> DecimalFraction,
71+
private val minDigits: Int,
72+
private val maxDigits: Int,
73+
private val zerosToAdd: List<Int>,
74+
): FormatterStructure<T> {
75+
76+
init {
77+
require(minDigits in 1..9) {
78+
"The minimum number of digits ($minDigits) is not in range 1..9"
79+
}
80+
require(maxDigits in minDigits..9) {
81+
"The maximum number of digits ($maxDigits) is not in range $minDigits..9"
82+
}
83+
}
84+
85+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
86+
val number = number(obj)
87+
// round the number to `maxDigits` significant figures
88+
val numberWithRequiredPrecision = number.fractionalPartWithNDigits(maxDigits)
89+
// we strip away trailing zeros while we can
90+
var zerosToStrip = 0
91+
while (maxDigits > minDigits + zerosToStrip && numberWithRequiredPrecision % POWERS_OF_TEN[zerosToStrip + 1] == 0) {
92+
++zerosToStrip
93+
}
94+
// we add some zeros back if it means making the number that's being output prettier, like `.01` becoming `.010`
95+
val zerosToAddBack = zerosToAdd[maxDigits - zerosToStrip - 1]
96+
if (zerosToStrip >= zerosToAddBack) zerosToStrip -= zerosToAddBack
97+
// the final stage of outputting the number
98+
val digitsToOutput = maxDigits - zerosToStrip
99+
val numberToOutput = numberWithRequiredPrecision / POWERS_OF_TEN[zerosToStrip]
100+
builder.append((numberToOutput + POWERS_OF_TEN[digitsToOutput]).toString().substring(1))
101+
}
102+
}
103+
104+
internal class StringFormatterStructure<in T>(
105+
private val string: (T) -> String,
106+
): FormatterStructure<T> {
107+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
108+
builder.append(string(obj))
109+
}
110+
}
111+
112+
internal class ReducedIntFormatterStructure<in T>(
113+
private val number: (T) -> Int,
114+
private val digits: Int,
115+
private val base: Int,
116+
): FormatterStructure<T> {
117+
override fun format(obj: T, builder: Appendable, minusNotRequired: Boolean) {
118+
val number = number(obj)
119+
if (number - base in 0 until POWERS_OF_TEN[digits]) {
120+
// the number fits
121+
val numberStr = (number % POWERS_OF_TEN[digits]).toString()
122+
val zeroPaddingStr = '0'.toString().repeat(maxOf(0, digits - numberStr.length))
123+
builder.append(zeroPaddingStr, numberStr)
124+
} else {
125+
if (number >= 0)
126+
builder.append("+")
127+
builder.append(number.toString())
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)