瀏覽代碼

Merge remote-tracking branch 'origin/master'

Him188 5 年之前
父節點
當前提交
a434e6f569

+ 251 - 0
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/RangeTokenReader.kt

@@ -0,0 +1,251 @@
+/*
+ * Copyright 2019-2020 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/master/LICENSE
+ *
+ */
+
+@file:Suppress("MemberVisibilityCanBePrivate")
+
+package net.mamoe.mirai.console.internal.util.semver
+
+import net.mamoe.mirai.console.util.SemVersion
+import kotlin.math.max
+import kotlin.math.min
+
+internal object RangeTokenReader {
+    enum class TokenType {
+        STRING,
+
+        /* 左括号 */
+        LEFT,
+
+        /* 右括号 */
+        RIGHT,
+
+        /* || */
+        OR,
+
+        /* && */
+        AND,
+        GROUP
+    }
+
+    sealed class Token {
+        abstract val type: TokenType
+        abstract val value: String
+        abstract val position: Int
+
+        class LeftBracket(override val position: Int) : Token() {
+            override val type: TokenType get() = TokenType.LEFT
+            override val value: String get() = "{"
+
+            override fun toString(): String = "LB{"
+        }
+
+        class RightBracket(override val position: Int) : Token() {
+            override val type: TokenType get() = TokenType.RIGHT
+            override val value: String get() = "}"
+
+            override fun toString(): String = "RB}"
+        }
+
+        class Or(override val position: Int) : Token() {
+            override val type: TokenType get() = TokenType.OR
+            override val value: String get() = "||"
+            override fun toString(): String = "OR||"
+        }
+
+        class And(override val position: Int) : Token() {
+            override val type: TokenType get() = TokenType.AND
+            override val value: String get() = "&&"
+
+            override fun toString(): String = "AD&&"
+        }
+
+        class Group(val values: List<Token>, override val position: Int) : Token() {
+            override val type: TokenType get() = TokenType.GROUP
+            override val value: String get() = ""
+        }
+
+        class Raw(val source: String, val start: Int, val end: Int) : Token() {
+            override val value: String get() = source.substring(start, end)
+            override val position: Int
+                get() = start
+            override val type: TokenType get() = TokenType.STRING
+
+            override fun toString(): String = "R:$value"
+        }
+    }
+
+    fun parseToTokens(source: String): List<Token> = ArrayList<Token>(
+        max(source.length / 3, 16)
+    ).apply {
+        var index = 0
+        var position = 0
+        fun flushOld() {
+            if (position > index) {
+                val id = index
+                index = position
+                for (i in id until position) {
+                    if (!source[i].isWhitespace()) {
+                        add(Token.Raw(source, id, position))
+                        return
+                    }
+                }
+            }
+        }
+
+        val iterator = source.indices.iterator()
+        for (i in iterator) {
+            position = i
+            when (source[i]) {
+                '{' -> {
+                    flushOld()
+                    add(Token.LeftBracket(i))
+                    index = i + 1
+                }
+                '|' -> {
+                    if (source.getOrNull(i + 1) == '|') {
+                        flushOld()
+                        add(Token.Or(i))
+                        index = i + 2
+                        iterator.nextInt()
+                    }
+                }
+                '&' -> {
+                    if (source.getOrNull(i + 1) == '&') {
+                        flushOld()
+                        add(Token.And(i))
+                        index = i + 2
+                        iterator.nextInt()
+                    }
+                }
+                '}' -> {
+                    flushOld()
+                    add(Token.RightBracket(i))
+                    index = i + 1
+                }
+            }
+        }
+        position = source.length
+        flushOld()
+    }
+
+    fun collect(source: String, tokens: Iterator<Token>, root: Boolean): List<Token> = ArrayList<Token>().apply {
+        tokens.forEach { token ->
+            if (token is Token.LeftBracket) {
+                add(Token.Group(collect(source, tokens, false), token.position))
+            } else if (token is Token.RightBracket) {
+                if (root) {
+                    throw IllegalArgumentException("Syntax error: Unexpected }, ${buildMsg(source, token.position)}")
+                } else {
+                    return@apply
+                }
+            } else add(token)
+        }
+        if (!root) {
+            throw IllegalArgumentException("Syntax error: Excepted }, ${buildMsg(source, source.length)}")
+        }
+    }
+
+    private fun buildMsg(source: String, position: Int): String {
+        val ed = min(position + 10, source.length)
+        val st = max(0, position - 10)
+        return buildString {
+            append('`')
+            if (st != 0) append("...")
+            append(source, st, ed)
+            if (ed != source.length) append("...")
+            append("` at ").append(position)
+        }
+    }
+
+    fun check(source: String, tokens: Iterator<Token>, group: Token.Group?) {
+        if (!tokens.hasNext()) {
+            throw IllegalArgumentException("Syntax error: empty rule, ${buildMsg(source, group?.position ?: 0)}")
+        }
+        var type = false
+        do {
+            val next = tokens.next()
+            if (type) {
+                if (next is Token.Group || next is Token.Raw) {
+                    throw IllegalArgumentException("Syntax error: Except logic but got expression, ${buildMsg(source, next.position)}")
+                }
+            } else {
+                if (next is Token.Or || next is Token.And) {
+                    throw IllegalArgumentException("Syntax error: Except expression but got logic, ${buildMsg(source, next.position)}")
+                }
+                if (next is Token.Group) {
+                    check(source, next.values.iterator(), next)
+                }
+            }
+            type = !type
+        } while (tokens.hasNext())
+        if (!type) {
+            throw IllegalArgumentException("Syntax error: Except more expression, ${buildMsg(source, group?.values?.last()?.position ?: source.length)}")
+        }
+    }
+
+    fun parse(source: String, token: Token): SemVersion.Requirement {
+        return when (token) {
+            is Token.Group -> {
+                if (token.values.size == 1) {
+                    parse(source, token.values.first())
+                } else {
+                    val logic = token.values.asSequence().map { it.type }.filter {
+                        it == TokenType.OR || it == TokenType.AND
+                    }.toSet()
+                    if (logic.size == 2) {
+                        throw IllegalArgumentException("Syntax error: || and && cannot use in one group, ${buildMsg(source, token.position)}")
+                    }
+                    val rules = token.values.asSequence().filter {
+                        it is Token.Raw || it is Token.Group
+                    }.map { parse(source, it) }.toList()
+                    when (logic.first()) {
+                        TokenType.OR -> {
+                            return object : SemVersion.Requirement {
+                                override fun test(version: SemVersion): Boolean {
+                                    rules.forEach { if (it.test(version)) return true }
+                                    return false
+                                }
+                            }
+                        }
+                        TokenType.AND -> {
+                            return object : SemVersion.Requirement {
+                                override fun test(version: SemVersion): Boolean {
+                                    rules.forEach { if (!it.test(version)) return false }
+                                    return true
+                                }
+                            }
+                        }
+                        else -> throw AssertionError()
+                    }
+                }
+            }
+            is Token.Raw -> SemVersionInternal.parseRule(token.value)
+            else -> throw AssertionError()
+        }
+    }
+
+    fun StringBuilder.dump(prefix: String, token: Token) {
+        when (token) {
+            is Token.LeftBracket -> append("${prefix}LF {\n")
+
+            is Token.RightBracket -> append("${prefix}LR }\n")
+
+            is Token.Or -> append("${prefix}OR ||\n")
+
+            is Token.And -> append("${prefix}AND &&\n")
+            is Token.Group -> {
+                append("${prefix}GROUP {\n")
+                token.values.forEach { dump("$prefix  ", it) }
+                append("${prefix}}\n")
+            }
+            is Token.Raw -> append("${prefix}RAW ${token.value}\n")
+        }
+    }
+}

+ 55 - 40
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/SemVersionInternal.kt → backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/semver/SemVersionInternal.kt

@@ -8,12 +8,9 @@
  *
  */
 
-/*
- * @author Karlatemp <[email protected]> <https://github.com/Karlatemp>
- */
-
-package net.mamoe.mirai.console.internal.util
+package net.mamoe.mirai.console.internal.util.semver
 
+import net.mamoe.mirai.console.internal.util.semver.RangeTokenReader.dump
 import net.mamoe.mirai.console.util.SemVersion
 import kotlin.math.max
 import kotlin.math.min
@@ -22,10 +19,9 @@ import kotlin.math.min
 internal object SemVersionInternal {
     private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex()
     private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex()
-    private val versionRange = """([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\-\s*([0-9]+(\.[0-9]+)+(|[\-+].+))""".toRegex()
     private val versionMathRange =
-        """\[([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))\]""".toRegex()
-    private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex()
+        """([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex()
+    private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\!\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex()
 
     private val SEM_VERSION_REGEX =
         """^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""".toRegex()
@@ -69,16 +65,19 @@ internal object SemVersionInternal {
                 }
             }
         }
+        val mainVersion = version.substring(0, mainVersionEnd).parseMainVersion()
         return SemVersion(
-            mainVersion = version.substring(0, mainVersionEnd).parseMainVersion(),
+            major = mainVersion[0],
+            minor = mainVersion[1],
+            patch = mainVersion.getOrNull(2),
             identifier = identifier,
             metadata = metadata
         )
     }
 
     @JvmStatic
-    private fun String.parseRule(): SemVersion.Requirement {
-        val trimmed = trim()
+    internal fun parseRule(rule: String): SemVersion.Requirement {
+        val trimmed = rule.trim()
         if (directVersion.matches(trimmed)) {
             val parsed = SemVersion.invoke(trimmed)
             return object : SemVersion.Requirement {
@@ -95,22 +94,40 @@ internal object SemVersionInternal {
                 override fun test(version: SemVersion): Boolean = regex.matches(version.toString())
             }
         }
-        (versionRange.matchEntire(trimmed) ?: versionMathRange.matchEntire(trimmed))?.let { range ->
-            var start = SemVersion.invoke(range.groupValues[1])
-            var end = SemVersion.invoke(range.groupValues[4])
+        versionMathRange.matchEntire(trimmed)?.let { range ->
+            // 1 mode
+            // 2 first
+            // 5 sec
+            // 8 type
+            var typeStart = range.groupValues[1][0]
+            var typeEnd = range.groupValues[8][0]
+            var start = SemVersion.invoke(range.groupValues[2])
+            var end = SemVersion.invoke(range.groupValues[5])
             if (start > end) {
                 val c = end
                 end = start
                 start = c
+                val x = typeEnd
+                typeEnd = typeStart
+                typeStart = x
+            }
+            val a: (SemVersion) -> Boolean = when (typeStart) {
+                '[', ']' -> ({ start <= it })
+                '(', ')' -> ({ start < it })
+                else -> throw AssertionError()
+            }
+            val b: (SemVersion) -> Boolean = when (typeEnd) {
+                '[', ']' -> ({ it <= end })
+                '(', ')' -> ({ it < end })
+                else -> throw AssertionError()
             }
-            val compareRange = start..end
             return object : SemVersion.Requirement {
-                override fun test(version: SemVersion): Boolean = version in compareRange
+                override fun test(version: SemVersion): Boolean = a(version) && b(version)
             }
         }
         versionRule.matchEntire(trimmed)?.let { result ->
             val operator = result.groupValues[1]
-            val version1 = SemVersion.invoke(result.groupValues[7])
+            val version1 = SemVersion.invoke(result.groupValues[8])
             return when (operator) {
                 ">=" -> {
                     object : SemVersion.Requirement {
@@ -137,10 +154,15 @@ internal object SemVersionInternal {
                         override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0
                     }
                 }
+                "!=" -> {
+                    object : SemVersion.Requirement {
+                        override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0
+                    }
+                }
                 else -> error("operator=$operator, version=$version1")
             }
         }
-        throw IllegalArgumentException("Cannot parse $this")
+        throw IllegalArgumentException("Cannot parse $rule")
     }
 
     private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement {
@@ -155,19 +177,16 @@ internal object SemVersionInternal {
         if (requirement.isBlank()) {
             throw IllegalArgumentException("Invalid requirement: Empty requirement rule.")
         }
-        return requirement.split("||").map {
-            it.parseRule().withRule(it)
-        }.let { checks ->
-            if (checks.size == 1) return checks[0]
-            object : SemVersion.Requirement {
-                override fun test(version: SemVersion): Boolean {
-                    checks.forEach { rule ->
-                        if (rule.test(version)) return true
-                    }
-                    return false
-                }
-            }.withRule(requirement)
-        }
+        val tokens = RangeTokenReader.parseToTokens(requirement)
+        val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true)
+        RangeTokenReader.check(requirement, collected.iterator(), null)
+        return kotlin.runCatching {
+            RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)).withRule(requirement)
+        }.onFailure { error ->
+            throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString {
+                collected.forEach { dump("", it) }
+            }, error)
+        }.getOrThrow()
     }
 
     @JvmStatic
@@ -176,16 +195,12 @@ internal object SemVersionInternal {
 
         // If $this equals $other (without metadata),
         // return same.
-        if (other.mainVersion.contentEquals(source.mainVersion) && source.identifier == other.identifier) {
-            return 0
-        }
-        fun IntArray.getSafe(index: Int) = getOrElse(index) { 0 }
-
         // Compare main-version
-        for (index in 0 until (max(source.mainVersion.size, other.mainVersion.size))) {
-            val result = source.mainVersion.getSafe(index).compareTo(other.mainVersion.getSafe(index))
-            if (result != 0) return result
-        }
+
+        source.major.compareTo(other.major).takeUnless { it == 0 }?.let { return it }
+        source.minor.compareTo(other.minor).takeUnless { it == 0 }?.let { return it }
+        (source.patch ?: 0).compareTo(other.patch ?: 0).takeUnless { it == 0 }?.let { return it }
+
         // If main-versions are same.
         var identifier0 = source.identifier
         var identifier1 = other.identifier

+ 30 - 15
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersion.kt

@@ -23,7 +23,7 @@ import kotlinx.serialization.builtins.serializer
 import net.mamoe.mirai.console.compiler.common.ResolveContext
 import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSION
 import net.mamoe.mirai.console.internal.data.map
-import net.mamoe.mirai.console.internal.util.SemVersionInternal
+import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal
 import net.mamoe.mirai.console.util.SemVersion.Companion.equals
 import net.mamoe.mirai.console.util.SemVersion.Requirement
 
@@ -32,10 +32,13 @@ import net.mamoe.mirai.console.util.SemVersion.Requirement
  *
  * 解析示例:
  *
- * `1.0.0-M4+c25733b8` 将会解析出三个内容, mainVersion (核心版本号), [identifier] (先行版本号) 和 [metadata] (元数据).
+ * `1.0.0-M4+c25733b8` 将会解析出下面的内容,
+ * [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) 和 [metadata] (元数据).
  * ```
  * SemVersion(
- *   mainVersion = IntArray [1, 0, 0],
+ *   major = 1,
+ *   minor = 0,
+ *   patch = 0,
  *   identifier  = "M4"
  *   metadata    = "c25733b8"
  * )
@@ -53,8 +56,12 @@ public data class SemVersion
  * @see SemVersion.invoke 字符串解析
  */
 internal constructor(
-    /** 核心版本号, 由主版本号, 次版本号和修订号组成, 其中修订号不一定存在 */
-    public val mainVersion: IntArray,
+    /** 主版本号 */
+    public val major: Int,
+    /** 次版本号 */
+    public val minor: Int,
+    /** 修订号 */
+    public val patch: Int?,
     /** 先行版本号识别符 */
     public val identifier: String? = null,
     /** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */
@@ -106,21 +113,28 @@ internal constructor(
          *
          * - `1.0.0-M4`       要求 1.0.0-M4 版本, 且只能是 1.0.0-M4 版本
          * - `1.x`            要求 1.x 版本
-         * - `1.0.0 - 1.2.0`  要求 1.0.0 到 1.2.0 的任意版本, 注意 `-` 两边必须要有空格
-         * - `[1.0.0, 1.2.0]` 与 `1.0.0 - 1.2.0` 一致
          * - `> 1.0.0-RC`     要求 1.0.0-RC 之后的版本, 不能是 1.0.0-RC
          * - `>= 1.0.0-RC`    要求 1.0.0-RC 或之后的版本, 可以是 1.0.0-RC
          * - `< 1.0.0-RC`     要求 1.0.0-RC 之前的版本, 不能是 1.0.0-RC
          * - `<= 1.0.0-RC`    要求 1.0.0-RC 或之前的版本, 可以是 1.0.0-RC
+         * - `!= 1.0.0-RC`    要求 除了1.0.0-RC 的任何版本
+         *     - `[1.0.0, 1.2.0]`
+         *     - `(1.0.0, 1.2.0]`
+         *     - `[1.0.0, 1.2.0)`
+         *     - `(1.0.0, 1.2.0)` [数学区间](https://baike.baidu.com/item/%E5%8C%BA%E9%97%B4/1273117)
          *
-         * 对于多个规则, 也允许使用 `||` 拼接.
+         * 对于多个规则, 允许使用逻辑符号 `{}`, `||`, `&&`
          * 例如:
          * - `1.x || 2.x || 3.0.0`
          * - `<= 0.5.3 || >= 1.0.0`
+         * - `{> 1.0 && < 1.5} || {> 1.8}`
+         * - `{> 1.0 && < 1.5} || {> 1.8}`
+         * - `> 1.0.0 && != 1.2.0`
          *
          * 特别注意:
          * - 依赖规则版本号不需要携带版本号元数据, 元数据不参与依赖需求的检查
          * - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号
+         * - 因为 `()` 已经用于数学区间, 使用 `{}` 替代 `()`
          */
         @Throws(IllegalArgumentException::class)
         @JvmStatic
@@ -151,14 +165,14 @@ internal constructor(
     @Transient
     private val toString: String by lazy(LazyThreadSafetyMode.NONE) {
         buildString {
-            mainVersion.joinTo(this, ".")
+            append(major)
+            append('.').append(minor)
+            patch?.let { append('.').append(it) }
             identifier?.let { identifier ->
-                append('-')
-                append(identifier)
+                append('-').append(identifier)
             }
             metadata?.let { metadata ->
-                append('+')
-                append(metadata)
+                append('+').append(metadata)
             }
         }
     }
@@ -169,7 +183,7 @@ internal constructor(
      * 将 [SemVersion] 转为 Kotlin data class 风格的 [String]
      */
     public fun toStructuredString(): String {
-        return "SemVersion(mainVersion=${mainVersion.contentToString()}, identifier=$identifier, metadata=$metadata)"
+        return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)"
     }
 
     override fun equals(other: Any?): Boolean {
@@ -182,7 +196,8 @@ internal constructor(
     }
 
     override fun hashCode(): Int {
-        var result = mainVersion.contentHashCode()
+        var result = major shl minor
+        result *= (patch ?: 1)
         result = 31 * result + (identifier?.hashCode() ?: 0)
         result = 31 * result + (metadata?.hashCode() ?: 0)
         return result

+ 8 - 3
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/SemVersionRangeRequirementBuilder.kt

@@ -26,7 +26,7 @@ public object SemVersionRangeRequirementBuilder {
             }
 
             override fun toString(): String {
-                return "(${this@or}) or ($other)"
+                return "{${this@or}} || {$other}"
             }
         }
     }
@@ -48,7 +48,7 @@ public object SemVersionRangeRequirementBuilder {
             }
 
             override fun toString(): String {
-                return "(${this@and}) or ($other)"
+                return "{${this@and}} && {$other}"
             }
         }
     }
@@ -64,7 +64,12 @@ public object SemVersionRangeRequirementBuilder {
 
     @Suppress("NOTHING_TO_INLINE")
     @ILoveHim188moeForever
-    public inline fun custom(rule: SemVersion.Requirement): SemVersion.Requirement = rule
+    public fun custom(rule: (SemVersion) -> Boolean): SemVersion.Requirement = object : SemVersion.Requirement {
+        override fun test(version: SemVersion): Boolean = rule(version)
+        override fun toString(): String {
+            return "Custom{$rule}"
+        }
+    }
 
     /**
      * 标注一个 [SemVersionRangeRequirementBuilder] DSL

+ 41 - 8
backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/util/TestSemVersion.kt

@@ -48,6 +48,12 @@ internal class TestSemVersion {
             return this
         }
 
+        fun assertInvalid(requirement: String) {
+            kotlin.runCatching {
+                SemVersion.parseRangeRequirement(requirement)
+            }.onSuccess { assert(false) { requirement } }
+        }
+
         fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement {
             assert(!test(version)) { version }
             return this
@@ -59,19 +65,24 @@ internal class TestSemVersion {
             .assert("1.0").assert("1.1")
             .assert("1.5").assert("1.14514")
             .assertFalse("2.33")
+        SemVersion.parseRangeRequirement("2.0||1.2.x")
+        SemVersion.parseRangeRequirement("{2.0||1.2.x} && 1.1.0 &&1.2.3")
         SemVersion.parseRangeRequirement("2.0 || 1.2.x")
             .assert("2.0").assert("2.0.0")
             .assertFalse("2.1")
             .assert("1.2.5").assert("1.2.0").assertFalse("1.2")
             .assertFalse("1.0.0")
-        SemVersion.parseRangeRequirement("1.0.0 - 114.514.1919")
-            .assert("1.0.0")
-            .assert("114.514").assert("114.514.1919")
-            .assertFalse("0.0.1")
-            .assertFalse("4444.4444")
         SemVersion.parseRangeRequirement("[1.0.0, 19190.0]")
             .assert("1.0.0").assertFalse("0.1.0")
             .assert("19190.0").assertFalse("19198.10")
+        SemVersion.parseRangeRequirement("[1.0.0, 2.0.0)")
+            .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0")
+        SemVersion.parseRangeRequirement("(2.0.0, 1.0.0]")
+            .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0")
+        SemVersion.parseRangeRequirement("(2.0.0, 1.0.0)")
+            .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0")
+        SemVersion.parseRangeRequirement("(1.0.0, 2.0.0)")
+            .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0")
         SemVersion.parseRangeRequirement(" >= 1.0.0")
             .assert("1.0.0")
             .assert("114.514.1919")
@@ -79,9 +90,30 @@ internal class TestSemVersion {
             .assertFalse("0.98774587")
         SemVersion.parseRangeRequirement("> 1.0.0")
             .assertFalse("1.0.0")
-        kotlin.runCatching { SemVersion.parseRangeRequirement("WPOXAXW") }
-            .onSuccess { assert(false) }
+        SemVersion.parseRangeRequirement("!= 1.0.0 && != 2.0.0")
+            .assert("1.2.3").assert("2.1.1")
+            .assertFalse("1.0").assertFalse("1.0.0")
+            .assertFalse("2.0").assertFalse("2.0.0")
+            .assert("2.0.1").assert("1.0.1")
 
+        SemVersion.parseRangeRequirement("> 1.0.0 || < 0.9.0")
+            .assertFalse("1.0.0")
+            .assert("0.8.0")
+            .assertFalse("0.9.0")
+        SemVersion.parseRangeRequirement("{>= 1.0.0 && <= 1.2.3} || {>= 2.0.0 && <= 2.2.3}")
+            .assertFalse("1.3.0")
+            .assert("1.0.0").assert("1.2.3")
+            .assertFalse("0.9.0")
+            .assert("2.0.0").assert("2.2.3").assertFalse("2.3.4")
+
+        assertInvalid("WPOXAXW")
+        assertInvalid("1.0.0 || 1.0.0 && 1.0.0")
+        assertInvalid("{")
+        assertInvalid("}")
+        assertInvalid("")
+        assertInvalid("1.2.3 - 3.2.1")
+        assertInvalid("1.5.78 &&")
+        assertInvalid("|| 1.0.0")
     }
 
     private fun String.check() {
@@ -112,8 +144,9 @@ internal class TestSemVersion {
         "5.1+68-7".check()
         "5.1+68-".check()
     }
+
     @Test
-    internal fun testSemVersionOfficial(){
+    internal fun testSemVersionOfficial() {
         """
             1.0-RC
             0.0.4