/* * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ package moe.nea.firmament.features.texturepack 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 = "(?[\\[\\(])(?[0-9.]+)?,(?[0-9.]+)?(?[\\]\\)])" .toPattern() fun parseRange(string: String): RangeMatcher? { intervalSpec.useMatch(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.entries.joinToString("|") {it.operator}})(?[0-9.]+)".toPattern() fun parseOperator(string: String): OperatorMatcher? { operatorPattern.useMatch(string) { val operatorName = group("operator") val operator = Operator.entries.find { it.operator == operatorName }!! val value = group("value").toDouble() return OperatorMatcher(operator, value) } return null } 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 } } } }