target parser: basic property reading

This commit is contained in:
Bixilon 2022-05-19 17:21:16 +02:00
parent 4752334f3d
commit f0942b0467
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
15 changed files with 324 additions and 20 deletions

View File

@ -19,4 +19,4 @@ abstract class ReaderError(
val reader: CommandReader,
val start: Int,
val end: Int,
) : Exception("Error at $start-$end: ${reader.string}")
) : Exception("Error at $start-$end: ${reader.string} (at ${reader.string.substring(start, end)}")

View File

@ -0,0 +1,24 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.commands.errors.reader.map
import de.bixilon.minosoft.commands.errors.ReaderError
import de.bixilon.minosoft.commands.util.CommandReader
import de.bixilon.minosoft.commands.util.ReadResult
class DuplicatedKeyMapError(
reader: CommandReader,
val key: ReadResult<*>,
val existingValue: Any?,
) : ReaderError(reader, key.start, key.end)

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.commands.errors.reader.map
import de.bixilon.minosoft.commands.errors.ReaderError
import de.bixilon.minosoft.commands.util.CommandReader
import de.bixilon.minosoft.commands.util.ReadResult
class ExpectedKeyMapError(
reader: CommandReader,
result: ReadResult<*>,
) : ReaderError(reader, result.start, result.end)

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.commands.errors.reader.map
import de.bixilon.minosoft.commands.errors.ReaderError
import de.bixilon.minosoft.commands.util.CommandReader
class InvalidAssignCharMapError(
reader: CommandReader,
pointer: Int,
val found: Int?,
) : ReaderError(reader, pointer - 1, pointer)

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.commands.errors.reader.map
import de.bixilon.minosoft.commands.errors.ReaderError
import de.bixilon.minosoft.commands.util.CommandReader
class InvalidMapSeparatorError(
reader: CommandReader,
pointer: Int,
val found: Int?,
) : ReaderError(reader, pointer - 1, pointer)

View File

@ -22,7 +22,13 @@ import de.bixilon.minosoft.commands.parser.minecraft.target.targets.identifier.n
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.identifier.name.NameEntityTarget
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.identifier.uuid.InvalidUUIDError
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.identifier.uuid.UUIDEntityTarget
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.SelectorEntityTarget
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.error.InvalidSelectorKeyError
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.error.InvalidTargetSelector
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.TargetProperties
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.TargetProperty
import de.bixilon.minosoft.commands.util.CommandReader
import de.bixilon.minosoft.commands.util.ReadResult
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
@ -40,7 +46,7 @@ class TargetParser(
override val placeholder = ChatComponent.of("<target>")
override fun parse(reader: CommandReader): EntityTarget {
if (!reader.canPeekNext()) {
if (!reader.canPeek()) {
throw ExpectedArgumentError(reader)
}
return if (reader.peek() == '@'.code) {
@ -50,8 +56,26 @@ class TargetParser(
}
}
fun CommandReader.parseSelector(): EntityTarget {
TODO()
fun CommandReader.parseSelector(): SelectorEntityTarget {
unsafeRead('@'.code)
val selectorChar = readNext() ?: throw ExpectedArgumentError(this)
val selector = TargetSelectors.BY_CHAR[selectorChar.toChar()] ?: throw InvalidTargetSelector(this)
val properties: Map<String, TargetProperty> = readMap({ readKey() }, { readValue(it) }) ?: emptyMap()
return SelectorEntityTarget(selector, properties)
}
private fun CommandReader.readKey(): String? {
if (peek() == '"'.code) {
return readUnquotedString()
}
return readUntil('='.code)
}
private fun CommandReader.readValue(key: ReadResult<String>): TargetProperty {
val target = TargetProperties[key.result] ?: throw InvalidSelectorKeyError(this, key)
return target.read(this)
}
fun parseEntityIdentifier(reader: CommandReader): EntityTarget {

View File

@ -13,20 +13,13 @@
package de.bixilon.minosoft.commands.parser.minecraft.target
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.DistanceProperty
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.GamemodeProperty
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.NameProperty
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.TypeProperty
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.rotation.PitchRotation
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.rotation.YawRotation
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.sort.Sorting
data class TargetProperties(
val selector: TargetSelectors,
@Deprecated("")
data class TargetPropertiesLEGACY(
var x: Double?,
var y: Double?,
var z: Double?,
var distance: DistanceProperty?,
var volumeX: Double?,
var volumeY: Double?,
var volumeZ: Double?,
@ -36,11 +29,6 @@ data class TargetProperties(
var sort: Sorting?,
var limit: Int? = null,
var level: IntRange? = null,
var gamemode: GamemodeProperty? = null,
var name: NameProperty? = null,
var xRotation: PitchRotation? = null,
var yRotation: YawRotation? = null,
var type: TypeProperty? = null,
var nbt: Any? = null, // ToDo
var advancements: Any? = null, // ToDo
var predicate: Any? = null, // ToDo

View File

@ -13,6 +13,8 @@
package de.bixilon.minosoft.commands.parser.minecraft.target
import de.bixilon.kutil.enums.EnumUtil
import de.bixilon.kutil.enums.ValuesEnum
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.sort.Sorting
import de.bixilon.minosoft.data.entities.entities.Entity
@ -30,4 +32,16 @@ enum class TargetSelectors(
fun sort(selected: MutableList<Entity>) {
sorting.sort(selected)
}
companion object : ValuesEnum<TargetSelectors> {
override val VALUES: Array<TargetSelectors> = values()
override val NAME_MAP: Map<String, TargetSelectors> = EnumUtil.getEnumValues(VALUES)
val BY_CHAR: MutableMap<Char, TargetSelectors> = mutableMapOf()
init {
for (value in VALUES) {
BY_CHAR[value.char] = value
}
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector
import de.bixilon.minosoft.commands.errors.parser.ParserError
import de.bixilon.minosoft.commands.util.CommandReader
import de.bixilon.minosoft.commands.util.ReadResult
class SelectorParserError(
reader: CommandReader,
result: ReadResult<*>,
) : ParserError(reader, result)

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.error
import de.bixilon.minosoft.commands.errors.parser.ParserError
import de.bixilon.minosoft.commands.util.CommandReader
import de.bixilon.minosoft.commands.util.ReadResult
class InvalidSelectorKeyError(
reader: CommandReader,
key: ReadResult<String>,
) : ParserError(reader, key)

View File

@ -0,0 +1,21 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.error
import de.bixilon.minosoft.commands.errors.ReaderError
import de.bixilon.minosoft.commands.util.CommandReader
class InvalidTargetSelector(
reader: CommandReader,
) : ReaderError(reader, reader.pointer - 1, reader.pointer)

View File

@ -34,4 +34,8 @@ object TargetProperties {
fun register(factory: TargetPropertyFactory<*>) {
properties[factory.name] = factory
}
operator fun get(key: String): TargetPropertyFactory<*>? {
return properties[key]
}
}

View File

@ -37,7 +37,7 @@ class EnumParser<E : Enum<*>>(
}
fun CommandReader.readEnum(): E? {
return values.getOrNull(readString()?.lowercase()) // ToDo: Allow ordinals
return values.getOrNull(readWord()?.lowercase()) // ToDo: Allow ordinals
}
override fun getSuggestions(reader: CommandReader): List<E> {

View File

@ -13,7 +13,12 @@
package de.bixilon.minosoft.commands.util
import de.bixilon.kutil.cast.CastUtil.unsafeCast
import de.bixilon.minosoft.commands.errors.reader.*
import de.bixilon.minosoft.commands.errors.reader.map.DuplicatedKeyMapError
import de.bixilon.minosoft.commands.errors.reader.map.ExpectedKeyMapError
import de.bixilon.minosoft.commands.errors.reader.map.InvalidAssignCharMapError
import de.bixilon.minosoft.commands.errors.reader.map.InvalidMapSeparatorError
import de.bixilon.minosoft.commands.errors.reader.number.NegativeNumberError
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.util.KUtil.toResourceLocation
@ -214,7 +219,7 @@ open class CommandReader(val string: String) {
}
val builder = StringBuilder()
while (true) {
val peek = peek()
val peek = peekNext()
if (peek == null) {
if (required) {
throw OutOfBoundsError(this, length - 1)
@ -308,6 +313,51 @@ open class CommandReader(val string: String) {
return ReadResult(start, end, read, result)
}
fun <K, V> readMap(keyReader: CommandReader.() -> K?, valueReader: CommandReader.(key: ReadResult<K>) -> V): Map<K, V>? {
if (!canPeekNext()) {
return null
}
if (peekNext() != '['.code) {
return null
}
readNext() // [
val map: MutableMap<K, V> = mutableMapOf()
while (true) {
if (peek() == ']'.code) {
break
}
skipWhitespaces()
val key = readResult { keyReader(this) }
if (key.result == null) {
throw ExpectedKeyMapError(this, key)
}
val existing: V? = map[key.result]
if (existing != null) {
throw DuplicatedKeyMapError(this, key, existing)
}
val assign = read()
if (assign != '='.code) {
throw InvalidAssignCharMapError(this, pointer - 1, assign)
}
skipWhitespaces()
val value = valueReader(this, key.unsafeCast())
map[key.result] = value
val end = read()
if (end == ']'.code) {
break
}
if (end != ','.code) {
throw InvalidMapSeparatorError(this, pointer - 1, end)
}
}
return map
}
override fun toString(): String {
return string.substring(pointer, string.length)
}
companion object {
const val STRING_QUOTE = '"'.code
const val STRING_SINGLE_QUOTE = '\''.code

View File

@ -13,15 +13,21 @@
package de.bixilon.minosoft.commands.parser.minecraft.target
import de.bixilon.minosoft.commands.errors.reader.map.DuplicatedKeyMapError
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.identifier.name.InvalidNameError
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.identifier.name.NameEntityTarget
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.identifier.uuid.InvalidUUIDError
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.identifier.uuid.UUIDEntityTarget
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.SelectorEntityTarget
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.GamemodeProperty
import de.bixilon.minosoft.commands.parser.minecraft.target.targets.selector.properties.NameProperty
import de.bixilon.minosoft.commands.util.CommandReader
import de.bixilon.minosoft.data.abilities.Gamemodes
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
internal class TargetParserTest {
@ -75,4 +81,61 @@ internal class TargetParserTest {
val parser = TargetParser()
assertThrows<InvalidUUIDError> { parser.parse(reader) }
}
@Test
fun testSelectorDetection() {
val reader = CommandReader("@a")
val parser = TargetParser()
val parsed = parser.parse(reader)
assert(parsed is SelectorEntityTarget)
parsed as SelectorEntityTarget
}
private fun getSelector(command: String): SelectorEntityTarget {
val reader = CommandReader(command)
val parser = TargetParser()
val parsed = parser.parse(reader)
return parsed as SelectorEntityTarget
}
@Test
fun testSimpleSelector() {
val parsed = getSelector("@a")
assertEquals(parsed.selector, TargetSelectors.ALL_PLAYERS)
}
@Test
fun testPropertiesEmpty() {
val parsed = getSelector("@a")
assertTrue(parsed.properties.isEmpty())
}
@Test
fun testEmptyPropertiesEmpty() {
val parsed = getSelector("@a[]")
assertTrue(parsed.properties.isEmpty())
}
@Test
fun testSingleProperties() {
val parsed = getSelector("@a[name=Test]")
assertEquals(parsed.properties.size, 1)
assertTrue(parsed.properties["name"] is NameProperty)
assertTrue((parsed.properties["name"] as NameProperty).name == "Test")
}
@Test
fun testMultipleProperties() {
val parsed = getSelector("@a[name=Test,gamemode=creative]")
assertEquals(parsed.properties.size, 2)
assertTrue(parsed.properties["name"] is NameProperty)
assertEquals((parsed.properties["name"] as NameProperty).name, "Test")
assertTrue(parsed.properties["gamemode"] is GamemodeProperty)
assertEquals((parsed.properties["gamemode"] as GamemodeProperty).gamemode, Gamemodes.CREATIVE)
}
@Test
fun testDuplicatedProperties() {
assertThrows<DuplicatedKeyMapError> { getSelector("@a[name=Test,name=Test2]") }
}
}