aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt
blob: b0d51781485a1bc49c373a3d6f0f7150df5d23b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package moe.nea.firmament.features.texturepack.predicates

import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import moe.nea.firmament.util.useMatch

abstract class NumberMatcher {
	abstract fun test(number: Number): Boolean


	companion object {
		fun parse(jsonElement: JsonElement): NumberMatcher? {
			if (jsonElement is JsonPrimitive) {
				if (jsonElement.isString) {
					val string = jsonElement.asString
					return parseRange(string) ?: parseOperator(string)
				}
				if (jsonElement.isNumber) {
					val number = jsonElement.asNumber
					val hasDecimals = (number.toString().contains("."))
					return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble())
				}
			}
			return null
		}

		private val intervalSpec =
			"(?<beginningOpen>[\\[\\(])(?<beginning>[0-9.]+)?,(?<ending>[0-9.]+)?(?<endingOpen>[\\]\\)])"
				.toPattern()

		fun parseRange(string: String): RangeMatcher? {
			intervalSpec.useMatch<Nothing>(string) {
				// Open in the set-theory sense, meaning does not include its end.
				val beginningOpen = group("beginningOpen") == "("
				val endingOpen = group("endingOpen") == ")"
				val beginning = group("beginning")?.toDouble()
				val ending = group("ending")?.toDouble()
				return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
			}
			return null
		}

		enum class Operator(val operator: String) {
			LESS("<") {
				override fun matches(comparisonResult: Int): Boolean {
					return comparisonResult < 0
				}
			},
			LESS_EQUALS("<=") {
				override fun matches(comparisonResult: Int): Boolean {
					return comparisonResult <= 0
				}
			},
			GREATER(">") {
				override fun matches(comparisonResult: Int): Boolean {
					return comparisonResult > 0
				}
			},
			GREATER_EQUALS(">=") {
				override fun matches(comparisonResult: Int): Boolean {
					return comparisonResult >= 0
				}
			},
			;

			abstract fun matches(comparisonResult: Int): Boolean
		}

		private val operatorPattern =
			"(?<operator>${Operator.entries.joinToString("|") { it.operator }})(?<value>[0-9.]+)".toPattern()

		fun parseOperator(string: String): OperatorMatcher? {
			return operatorPattern.useMatch(string) {
				val operatorName = group("operator")
				val operator = Operator.entries.find { it.operator == operatorName }!!
				val value = group("value").toDouble()
				OperatorMatcher(operator, value)
			}
		}

		data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() {
			override fun test(number: Number): Boolean {
				return operator.matches(number.toDouble().compareTo(value))
			}
		}


		data class MatchNumberExact(val number: Number) : NumberMatcher() {
			override fun test(number: Number): Boolean {
				return when (this.number) {
					is Double -> number.toDouble() == this.number.toDouble()
					else -> number.toLong() == this.number.toLong()
				}
			}
		}

		data class RangeMatcher(
			val beginning: Double?,
			val beginningInclusive: Boolean,
			val ending: Double?,
			val endingInclusive: Boolean,
		) : NumberMatcher() {
			override fun test(number: Number): Boolean {
				val value = number.toDouble()
				if (beginning != null) {
					if (beginningInclusive) {
						if (value < beginning) return false
					} else {
						if (value <= beginning) return false
					}
				}
				if (ending != null) {
					if (endingInclusive) {
						if (value > ending) return false
					} else {
						if (value >= ending) return false
					}
				}
				return true
			}
		}
	}

}