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()) + } }