diff --git a/doc/CLI.md b/doc/CLI.md
new file mode 100644
index 000000000..dddaf15e5
--- /dev/null
+++ b/doc/CLI.md
@@ -0,0 +1,8 @@
+# CLI ToDo
+
+- [ ] set default root node to `null`
+- [ ] all parsers
+- [ ] better suggestions
+- [ ] gui suggestions
+- [ ] improved entity parser
+- [ ] some cli commands
diff --git a/src/main/java/de/bixilon/minosoft/commands/errors/DeadEndError.kt b/src/main/java/de/bixilon/minosoft/commands/errors/DeadEndError.kt
new file mode 100644
index 000000000..f523b245a
--- /dev/null
+++ b/src/main/java/de/bixilon/minosoft/commands/errors/DeadEndError.kt
@@ -0,0 +1,20 @@
+/*
+ * 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 .
+ *
+ * This software is not affiliated with Mojang AB, the original developer of Minecraft.
+ */
+
+package de.bixilon.minosoft.commands.errors
+
+import de.bixilon.minosoft.commands.util.CommandReader
+
+class DeadEndError(
+ reader: CommandReader,
+) : ReaderError(reader, reader.pointer, reader.pointer)
diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt
index 17d3a5805..0fea216c4 100644
--- a/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt
+++ b/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt
@@ -35,9 +35,13 @@ abstract class CommandNode(
open fun execute(reader: CommandReader, stack: CommandStack) {
val pointer = reader.pointer
val stackSize = stack.size
- var lastError: Throwable? = null
- var highestStack = 0
+
+ var childError: Throwable? = null
+ var errorStack = -1
+
for (child in children) {
+ reader.pointer = pointer
+ stack.reset(stackSize)
try {
executeChild(child, reader, stack)
if (reader.canPeek()) {
@@ -45,18 +49,13 @@ abstract class CommandNode(
}
return
} catch (error: Throwable) {
- val size = stack.size
- if (size >= highestStack) {
- highestStack = size
- lastError = error
+ if (stack.size > errorStack) {
+ errorStack = stack.size
+ childError = error
}
}
- reader.pointer = pointer
-
- stack.reset(stackSize)
}
-
- throw lastError ?: if (reader.canPeek()) throw TrailingTextArgument(reader) else return
+ throw childError ?: return
}
diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/ExecutableNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/ExecutableNode.kt
index 8ecad6ac4..ce56bd4a3 100644
--- a/src/main/java/de/bixilon/minosoft/commands/nodes/ExecutableNode.kt
+++ b/src/main/java/de/bixilon/minosoft/commands/nodes/ExecutableNode.kt
@@ -13,6 +13,7 @@
package de.bixilon.minosoft.commands.nodes
+import de.bixilon.minosoft.commands.errors.DeadEndError
import de.bixilon.minosoft.commands.errors.ExpectedArgumentError
import de.bixilon.minosoft.commands.stack.CommandExecutor
import de.bixilon.minosoft.commands.stack.CommandStack
@@ -38,11 +39,12 @@ abstract class ExecutableNode(
}
override fun execute(reader: CommandReader, stack: CommandStack) {
- reader.skipWhitespaces()
if (!reader.canPeekNext()) {
// empty string
if (executable) {
return execute(stack)
+ } else if (children.isEmpty()) {
+ throw DeadEndError(reader)
} else {
throw ExpectedArgumentError(reader)
}
diff --git a/src/test/java/de/bixilon/minosoft/commands/nodes/ExecutionChildReadingTest.kt b/src/test/java/de/bixilon/minosoft/commands/nodes/ExecutionChildReadingTest.kt
new file mode 100644
index 000000000..5a375af98
--- /dev/null
+++ b/src/test/java/de/bixilon/minosoft/commands/nodes/ExecutionChildReadingTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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 .
+ *
+ * This software is not affiliated with Mojang AB, the original developer of Minecraft.
+ */
+
+package de.bixilon.minosoft.commands.nodes
+
+import de.bixilon.minosoft.commands.errors.DeadEndError
+import de.bixilon.minosoft.commands.errors.ExpectedArgumentError
+import de.bixilon.minosoft.commands.errors.literal.ExpectedLiteralArgument
+import de.bixilon.minosoft.commands.errors.literal.InvalidLiteralArgumentError
+import de.bixilon.minosoft.commands.errors.literal.TrailingTextArgument
+import de.bixilon.minosoft.commands.parser.brigadier.string.StringParseError
+import de.bixilon.minosoft.commands.parser.brigadier.string.StringParser
+import de.bixilon.minosoft.commands.stack.CommandStack
+import de.bixilon.minosoft.commands.util.CommandReader
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertDoesNotThrow
+import org.junit.jupiter.api.assertThrows
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+
+internal class ExecutionChildReadingTest {
+
+ private fun createCommand(): CommandNode {
+ return RootNode()
+ .addChild(LiteralNode("0_literal"))
+ .addChild(
+ LiteralNode("1_literal")
+ .addChild(LiteralNode("1_literal_2"))
+ .addChild(LiteralNode("2_literal_2", executable = true))
+ )
+ .addChild(LiteralNode("2_literal", executable = true))
+ .addChild(
+ LiteralNode("1_execute")
+ .addChild(ArgumentNode("args", StringParser(StringParser.StringModes.GREEDY), executable = true))
+ )
+ }
+
+ @Test
+ fun testCreation() {
+ assertTrue(createCommand() is RootNode)
+ }
+
+ @Test
+ fun testValid() {
+ val stack = CommandStack()
+ assertDoesNotThrow { (createCommand().execute(CommandReader("1_execute test"), stack)) }
+ assertNotNull(stack["1_execute"])
+ assertEquals(stack["args"], "test")
+ }
+
+ @Test
+ fun testValid2() {
+ val stack = CommandStack()
+ assertDoesNotThrow { (createCommand().execute(CommandReader("1_execute this is a really long valid greedy read string..."), stack)) }
+ assertNotNull(stack["1_execute"])
+ assertEquals(stack["args"], "this is a really long valid greedy read string...")
+ }
+
+ @Test
+ fun testInvalidLiteralValid() {
+ assertThrows { (createCommand().execute(CommandReader("3_404_not_found"), CommandStack())) }
+ }
+
+ @Test
+ fun testBlankStringArgument() {
+ assertThrows { (createCommand().execute(CommandReader("1_execute "), CommandStack())) }
+ }
+
+ @Test
+ fun testNoStringArgument() {
+ assertThrows { (createCommand().execute(CommandReader("1_execute"), CommandStack())) }
+ }
+
+ @Test
+ fun testDeadEnd() {
+ assertThrows { (createCommand().execute(CommandReader("0_literal"), CommandStack())) }
+ }
+
+ @Test
+ fun testTrailingData() {
+ assertThrows { (createCommand().execute(CommandReader("0_literal test"), CommandStack())) }
+ }
+
+ @Test
+ fun testEmpty() {
+ assertThrows { (createCommand().execute(CommandReader(""), CommandStack())) }
+ }
+
+
+ @Test
+ fun testTrailingWhitespace() {
+ assertDoesNotThrow { createCommand().execute(CommandReader("2_literal "), CommandStack()) }
+ }
+
+ @Test
+ fun test2TrailingWhitespace() {
+ assertDoesNotThrow { createCommand().execute(CommandReader("1_literal 2_literal_2 "), CommandStack()) }
+ }
+}
diff --git a/src/test/java/de/bixilon/minosoft/commands/nodes/SuggestionChildReadingTest.kt b/src/test/java/de/bixilon/minosoft/commands/nodes/SuggestionChildReadingTest.kt
index c3b2aa6c5..4fde257bd 100644
--- a/src/test/java/de/bixilon/minosoft/commands/nodes/SuggestionChildReadingTest.kt
+++ b/src/test/java/de/bixilon/minosoft/commands/nodes/SuggestionChildReadingTest.kt
@@ -14,6 +14,7 @@
package de.bixilon.minosoft.commands.nodes
import de.bixilon.minosoft.commands.errors.literal.InvalidLiteralArgumentError
+import de.bixilon.minosoft.commands.errors.literal.TrailingTextArgument
import de.bixilon.minosoft.commands.errors.reader.ExpectedWhitespaceError
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParseError
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParser
@@ -29,7 +30,11 @@ internal class SuggestionChildReadingTest {
private fun createCommand(): CommandNode {
return RootNode()
- .addChild(LiteralNode("1_literal"))
+ .addChild(
+ LiteralNode("1_literal")
+ .addChild(LiteralNode("1_literal_2"))
+ .addChild(LiteralNode("2_literal_2"))
+ )
.addChild(LiteralNode("2_literal"))
.addChild(
LiteralNode("1_execute")
@@ -76,4 +81,34 @@ internal class SuggestionChildReadingTest {
fun testEmptySuggestions() {
assertEquals(createCommand().getSuggestions(CommandReader(""), CommandStack()), listOf("1_literal", "2_literal", "1_execute"))
}
+
+ @Test
+ fun testTrailingData() {
+ assertThrows { (createCommand().getSuggestions(CommandReader("2_literal test"), CommandStack())) }
+ }
+
+ @Test
+ fun test2InvalidLiteral() {
+ assertThrows { (createCommand().getSuggestions(CommandReader("1_literal test"), CommandStack())) }
+ }
+
+ @Test
+ fun test2EmptyLevelSuggestions() {
+ assertEquals(createCommand().getSuggestions(CommandReader("1_literal"), CommandStack()), listOf("1_literal_2", "2_literal_2"))
+ }
+
+ @Test
+ fun test2TrailingData() {
+ assertThrows { (createCommand().getSuggestions(CommandReader("2_literal 1_literal_2 test"), CommandStack())) }
+ }
+
+ @Test
+ fun testTrailingWhitespace() {
+ assertEquals(createCommand().getSuggestions(CommandReader("2_literal "), CommandStack()), emptyList())
+ }
+
+ @Test
+ fun test2TrailingWhitespace() {
+ assertEquals(createCommand().getSuggestions(CommandReader("1_literal 1_literal_2 "), CommandStack()), emptyList())
+ }
}