package mods.betterfoliage.config.match enum class MatchMethod { EXACT_MATCH, EXTENDS, CONTAINS; fun description(isSuccess: Boolean) = when (this) { EXACT_MATCH -> if (isSuccess) "matches" else "does not match" EXTENDS -> if (isSuccess) "extends" else "does not extend" CONTAINS -> if (isSuccess) "contains" else "does not contain" } } /** * Basic Either monad implementation */ sealed class Either { class Left(val left: L) : Either() class Right(val right: R) : Either() fun leftOrNull() = if (this is Left) left else null fun rightOrNull() = if (this is Right) right else null fun map(func: (R) -> R2): Either = when (this) { is Left -> this is Right -> Right(func(right)) } fun mapLeft(func: (L) -> L2): Either = when (this) { is Left -> Left(func(left)) is Right -> this } fun ifRight(action: (R) -> Unit) { if (this is Right) action(right) } companion object { fun ofLeft(left: L) = Left(left) fun ofRight(right: R) = Right(right) } } // this cannot be inside the class for variance reasons fun Either.flatMap(func: (R) -> Either) = when (this) { is Either.Left -> this is Either.Right -> func(right) } fun Either.flatMapLeft(func: (L) -> Either) = when (this) { is Either.Left -> func(left) is Either.Right -> this } fun Either.flatten() = when (this) { is Either.Left -> left is Either.Right -> right } interface MAnything { val value: T val immutable: Boolean } class MListAll(val list: List>) : MAnything { override val value get() = list.all { it.value } override val immutable get() = list.all { it.immutable } } class MListAny(val list: List>) : MAnything { override val value get() = list.any { it.value } override val immutable get() = list.all { it.immutable } } class MNegated(val inner: MAnything) : MAnything { override val value get() = !inner.value override val immutable get() = inner.immutable } /** * Value with metadata related to rule matching applied. * * @param value the wrapped value * @param description human-readable description of what the value represents * @param configSource identifies where the value is described in the config * @param immutable true if the value never changes * (another [MValue] constructed in the same way will have the same value) * */ class MValue( override val value: T, val description: String, val configSource: ConfigSource, override val immutable: Boolean, ) : MAnything { companion object { fun right(value: T, description: String, configSource: ConfigSource, immutable: Boolean = true) = Either.ofRight(MValue(value, description, configSource, immutable)) fun left(description: String, configSource: ConfigSource, immutable: Boolean = true) = Either.ofLeft(MValue(false, description, configSource, immutable)) } } typealias MEither = Either, MValue> val Node.Value.asEither get() = MValue.right(value, value, configSource, true) fun Node.Value.left(description: String) = MValue.left(description, configSource) fun Node.invalidTypeFor(type: String) = MValue.left("invalid type for $type: [${this::class.java.name}]", configSource) fun Node.error(description: String) = MValue.left(description, configSource) fun MEither.mapValue(func: (T) -> R) = map { MValue(func(it.value), it.description, it.configSource, it.immutable) } fun MEither.mapDescription(func: (MValue) -> String) = map { MValue(it.value, func(it), it.configSource, it.immutable) } fun MEither.map( func: (T) -> R, description: (MValue, R) -> String ) = map { t -> func(t.value).let { r -> MValue(r, description(t, r), t.configSource, t.immutable) } } fun MEither.mapNotNull( func: (T) -> R?, dLeft: (MValue) -> String = { it.description }, dRight: (MValue, R) -> String = { m, _ -> m.description } ) = flatMap { t -> func(t.value)?.let { r -> MValue.right(r, dRight(t, r), t.configSource, t.immutable) } ?: MValue.left(dLeft(t), t.configSource, t.immutable) } fun MEither.toRight(value: T) = flatMapLeft { MValue.right(value, it.description, it.configSource, it.immutable) } data class MComparison( private val opTrue: String, private val opFalse: String, val testFunc: (T1, T2) -> Boolean ) { fun compare(value1: MEither, value2: MEither) = when { value1 is Either.Left -> value1 value2 is Either.Left -> value2 else -> { val isSuccess = testFunc((value1 as Either.Right).right.value, (value2 as Either.Right).right.value) MValue.right( isSuccess, "${value1.right.description} ${if (isSuccess) opTrue else opFalse} ${value2.right.description}", value2.right.configSource, value1.right.immutable && value2.right.immutable ) } }.flatten() companion object { fun of(matchMethod: MatchMethod, testFunc: (T1, T2) -> Boolean) = MComparison(matchMethod.description(true), matchMethod.description(false), testFunc) val equals = of(MatchMethod.EXACT_MATCH) { t1: Any, t2: Any -> t1 == t2 } } }