Code restructure and began documentation

This commit is contained in:
493msi 2020-09-23 16:51:17 +02:00
parent aa70f1bbe2
commit 5952a26fee
10 changed files with 216 additions and 10 deletions

View File

@ -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

View File

@ -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.

View File

@ -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()

View File

@ -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();
}

View File

@ -91,6 +91,6 @@ public class CommandContextBuilder
UNRESOLVED_PREFIX,
UNRESOLVED_COMMAND_NAME,
UNRESOLVED_PARAMETERS,
UNRESOLVED_UNEXPECTED_STATE;
UNRESOLVED_UNEXPECTED_STATE
}
}

View File

@ -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.
*
* <p><em>
* Method must be public and non-static.
* </em></p>
*
* <p>
* While classes implementing the Command API must be final, it is not required for the handler methods
* as that would be redundant.
* </p>
*
* @author 493msi
*
* @since 20.2.0.0-alpha.2
* */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InvokeHandler
{
}

View File

@ -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;
}

View File

@ -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<String>
{
public StringResolver()
{
super(String::valueOf);
}
@Override
public Class<String> getOutputType()
{
return String.class;
}
}

View File

@ -0,0 +1,31 @@
package cz.tefek.pluto.command.resolver.command;
import cz.tefek.pluto.command.CommandBase;
import cz.tefek.pluto.command.invoke.InvokeHandler;
public final class TestCommand extends CommandBase
{
@Override
public String name()
{
return "test";
}
@Override
public String[] aliases()
{
return new String[0];
}
@Override
public String description()
{
return "The test command - prints Hello World to stdout.";
}
@InvokeHandler
public void invoke()
{
System.out.println("Hello World!");
}
}

View File

@ -0,0 +1,21 @@
package cz.tefek.pluto.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Denotes that the target field or method should be a constant expression - it has no state and always yields
* the same deterministic result for given input. Generally, annotated methods should be thread-safe, however
* this is not required.
*
* @author 493msi
*
* @since 20.2.0.0-alpha.2
* */
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface ConstantExpression
{
}