diff --git a/UPDATE_NOTES.md b/UPDATE_NOTES.md index 61317c9..b6ecce3 100644 --- a/UPDATE_NOTES.md +++ b/UPDATE_NOTES.md @@ -17,7 +17,7 @@ can now only be modified only through public setters * `[PlutoCore]` Refactored `InputBus` and added several convenience methods * `[PlutoCore]` Refactored input callbacks * `[PlutoStatic]` Slight cleanup in the `Display` and `DisplayBuilder` classes -* `[PlutoCommandParser]` Code cleanup +* `[PlutoCommandParser]` **Initial release** ## 20.2.0.0-alpha.1 * `[PlutoLib#cz.tefek.pluto.io.logger]` Refactored the Logger subsystem diff --git a/plutocommandparser/README.md b/plutocommandparser/README.md new file mode 100644 index 0000000..5869341 --- /dev/null +++ b/plutocommandparser/README.md @@ -0,0 +1,45 @@ +# plutoengine:plutocommandparser + +PlutoEngine's command parser. + +## Description + +PlutoCommandParser is an attempt to streamline my previous attempts at command parsers. Its main +goal is to provide a streamlined and flexible tokenizer, parser and evaluator for a simple +user-friendly CLI-like language called `PlutoCmd`. + +## Goals + +* High syntax error tolerance +* Provide implementations for basic Java types, such as primitives and Strings +* Allow extensibility while providing a strong foundation +* Complete user control over localization, no hardcoded Strings + +## Non-goals + +PlutoCmd is *not* a replacement for standard scripting languages and will most likely +never be a Turing-complete language. + + +## Implementation style + +### Command implementation + +Each command has its own *final* class, abstractions over CommandBase are howered allowed. +Note the usage of `ConstantExpression` annotation over some interface methods. These +annotations **must** be respected - methods must be stateless and deterministic. + +## PlutoCmd language specification + +### General syntax + +``` +[prefix]command [arg1] [arg2] ... [argN] +``` + +`prefix` is an optional identifier String to distinguish PlutoCmd commands from other commands. + +`command` is an alias for a command, handled by one of the command handlers + +`argX` is an argument of the invoked command-function, optionally quoted to preserve whitespace. + diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/CommandBase.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/CommandBase.java index 709670e..128239e 100644 --- a/plutocommandparser/src/main/java/cz/tefek/pluto/command/CommandBase.java +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/CommandBase.java @@ -1,14 +1,59 @@ package cz.tefek.pluto.command; -public abstract class CommandBase +import java.lang.reflect.Modifier; + +import cz.tefek.pluto.command.invoke.InvokeHandler; + +public abstract class CommandBase implements ICommand { - public abstract String name(); + private final Class extends CommandBase> clazz; - public abstract String[] aliases(); + public final Class> commandClass() + { + return this.clazz; + } - public abstract String description(); + protected CommandBase() + { + this.clazz = this.getClass(); - public abstract Class> commandClass(); + if (!Modifier.isFinal(this.clazz.getModifiers())) + { + // Class must be final + // Throwing an exception here is okay, since this is the developer's fault + // + // You can still create abstract wrappers for CommandBase, however implemented commands + // must be final. + throw new RuntimeException("Command classes MUST be final. Offender: " + this.clazz); + } + + var methods = this.clazz.getMethods(); + + for (var method : methods) + { + // Silently skip methods without annotations + if (!method.isAnnotationPresent(InvokeHandler.class)) + continue; + + var modifiers = method.getModifiers(); + + if (Modifier.isStatic(modifiers)) + { + // Method must be non-static + // Throwing an exception here is okay, since this is the developer's fault + throw new RuntimeException("Invoke handlers MUST NOT be static. Offender: " + method); + } + + if (!Modifier.isPublic(modifiers)) + { + // Method must be public + // Throwing an exception here is okay, since this is the developer's fault + throw new RuntimeException("Invoke handlers MUST be public. Offender: " + method); + } + + + } + } @Override public final int hashCode() diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/ICommand.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/ICommand.java new file mode 100644 index 0000000..63298a3 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/ICommand.java @@ -0,0 +1,18 @@ +package cz.tefek.pluto.command; + +import cz.tefek.pluto.annotation.ConstantExpression; + +public interface ICommand +{ + @ConstantExpression + String name(); + + @ConstantExpression + String[] aliases(); + + @ConstantExpression + String description(); + + @ConstantExpression + Class> commandClass(); +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/context/CommandContextBuilder.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/context/CommandContextBuilder.java index 083b2fd..b6d9cbf 100644 --- a/plutocommandparser/src/main/java/cz/tefek/pluto/command/context/CommandContextBuilder.java +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/context/CommandContextBuilder.java @@ -91,6 +91,6 @@ public class CommandContextBuilder UNRESOLVED_PREFIX, UNRESOLVED_COMMAND_NAME, UNRESOLVED_PARAMETERS, - UNRESOLVED_UNEXPECTED_STATE; + UNRESOLVED_UNEXPECTED_STATE } } diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/invoke/InvokeHandler.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/invoke/InvokeHandler.java new file mode 100644 index 0000000..f84a088 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/invoke/InvokeHandler.java @@ -0,0 +1,28 @@ +package cz.tefek.pluto.command.invoke; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denotes a handler for an invocation of a command. + * + *
+ * Method must be public and non-static. + *
+ * + *+ * While classes implementing the Command API must be final, it is not required for the handler methods + * as that would be redundant. + *
+ * + * @author 493msi + * + * @since 20.2.0.0-alpha.2 + * */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface InvokeHandler +{ +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java index fcf93a5..97c5e0b 100644 --- a/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java @@ -131,9 +131,6 @@ public class CommandParser break; case END: - this.state = EnumParserState.UNEXPECTED_STATE_FALLBACK; - return false; - default: this.state = EnumParserState.UNEXPECTED_STATE_FALLBACK; return false; @@ -156,6 +153,8 @@ public class CommandParser return false; } + this.ctx.command(this.command); + return true; } diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/StringResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/StringResolver.java new file mode 100644 index 0000000..5de9f2b --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/StringResolver.java @@ -0,0 +1,19 @@ +package cz.tefek.pluto.command.resolver.primitive; + +import java.util.function.Function; + +import cz.tefek.pluto.command.resolver.GenericResolver; + +public class StringResolver extends GenericResolver