Compare commits
5 Commits
master
...
plutocomma
Author | SHA1 | Date |
---|---|---|
493msi | 9804e9f3f5 | |
493msi | c3db492a6d | |
493msi | 5952a26fee | |
493msi | aa70f1bbe2 | |
493msi | c3bd358bed |
|
@ -17,6 +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]` **Initial release**
|
||||
|
||||
## 20.2.0.0-alpha.1
|
||||
* `[PlutoLib#cz.tefek.pluto.io.logger]` Refactored the Logger subsystem
|
||||
|
|
|
@ -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 modular 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 the `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's handlers
|
||||
|
||||
`argX` is an argument of the invoked command-function, optionally quoted to preserve whitespace.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
apply plugin: 'java-library'
|
||||
|
||||
description = ""
|
||||
|
||||
dependencies {
|
||||
api project(":plutolib")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package cz.tefek.pluto.command;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import cz.tefek.pluto.command.invoke.InvokeHandler;
|
||||
|
||||
public abstract class CommandBase implements ICommand
|
||||
{
|
||||
private final Class<? extends CommandBase> clazz;
|
||||
|
||||
public final Class<?> commandClass()
|
||||
{
|
||||
return this.clazz;
|
||||
}
|
||||
|
||||
protected CommandBase()
|
||||
{
|
||||
this.clazz = this.getClass();
|
||||
|
||||
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()
|
||||
{
|
||||
return this.name().hashCode();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package cz.tefek.pluto.command.context;
|
||||
|
||||
import cz.tefek.pluto.command.CommandBase;
|
||||
|
||||
public class CommandContextBuilder
|
||||
{
|
||||
private final CommandContext ctx;
|
||||
|
||||
public CommandContextBuilder()
|
||||
{
|
||||
this.ctx = new CommandContext();
|
||||
}
|
||||
|
||||
public CommandContextBuilder prefix(String prefix)
|
||||
{
|
||||
this.ctx.usedPrefix = prefix;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandContextBuilder alias(String alias)
|
||||
{
|
||||
this.ctx.usedAlias = alias;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandContextBuilder command(CommandBase command)
|
||||
{
|
||||
this.ctx.command = command;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandContext resolved()
|
||||
{
|
||||
this.ctx.resolved = true;
|
||||
|
||||
return this.ctx;
|
||||
}
|
||||
|
||||
public CommandContext unresolved(EnumCommandParseFailure cause)
|
||||
{
|
||||
this.ctx.resolved = false;
|
||||
|
||||
return this.ctx;
|
||||
}
|
||||
|
||||
public static class CommandContext
|
||||
{
|
||||
private boolean resolved;
|
||||
private EnumCommandParseFailure failureCause;
|
||||
|
||||
private String usedPrefix;
|
||||
private String usedAlias;
|
||||
private CommandBase command;
|
||||
|
||||
private CommandContext()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public String getUsedPrefix()
|
||||
{
|
||||
return this.usedPrefix;
|
||||
}
|
||||
|
||||
public String getUsedAlias()
|
||||
{
|
||||
return this.usedAlias;
|
||||
}
|
||||
|
||||
public CommandBase getCommand()
|
||||
{
|
||||
return this.command;
|
||||
}
|
||||
|
||||
public boolean isResolved()
|
||||
{
|
||||
return this.resolved;
|
||||
}
|
||||
|
||||
public EnumCommandParseFailure getFailureCause()
|
||||
{
|
||||
return this.failureCause;
|
||||
}
|
||||
}
|
||||
|
||||
public enum EnumCommandParseFailure
|
||||
{
|
||||
UNRESOLVED_PREFIX,
|
||||
UNRESOLVED_COMMAND_NAME,
|
||||
UNRESOLVED_PARAMETERS,
|
||||
UNRESOLVED_UNEXPECTED_STATE
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
package cz.tefek.pluto.command.parser;
|
||||
|
||||
import java.util.PrimitiveIterator.OfInt;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import cz.tefek.pluto.command.CommandBase;
|
||||
import cz.tefek.pluto.command.context.CommandContextBuilder;
|
||||
import cz.tefek.pluto.command.context.CommandContextBuilder.CommandContext;
|
||||
import cz.tefek.pluto.command.context.CommandContextBuilder.EnumCommandParseFailure;
|
||||
import cz.tefek.pluto.command.registry.CommandRegistry;
|
||||
|
||||
public class CommandParser
|
||||
{
|
||||
private final String text;
|
||||
private Set<OfInt> prefixes;
|
||||
private EnumParserState state;
|
||||
|
||||
private StringBuilder prefixBuilder;
|
||||
private StringBuilder commandNameBuilder;
|
||||
|
||||
private CommandBase command;
|
||||
|
||||
private StringBuilder parameterBuilder;
|
||||
|
||||
private CommandContextBuilder ctx;
|
||||
|
||||
private static final int CP_QUOTE = '"';
|
||||
|
||||
public CommandParser(String text)
|
||||
{
|
||||
this.text = text;
|
||||
this.state = EnumParserState.BEGIN;
|
||||
}
|
||||
|
||||
private boolean readCodepoint(int cp)
|
||||
{
|
||||
switch (this.state)
|
||||
{
|
||||
case READING_PREFIX:
|
||||
this.prefixBuilder.appendCodePoint(cp);
|
||||
|
||||
this.prefixes.removeIf(ii -> ii.nextInt() != cp);
|
||||
|
||||
if (this.prefixes.isEmpty())
|
||||
{
|
||||
this.state = EnumParserState.END_NO_PREFIX;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.hasEmptyPrefix())
|
||||
{
|
||||
this.ctx.prefix(this.prefixBuilder.toString());
|
||||
this.state = EnumParserState.READING_COMMAND;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case READING_COMMAND:
|
||||
this.commandNameBuilder.appendCodePoint(cp);
|
||||
|
||||
if (Character.isWhitespace(cp))
|
||||
{
|
||||
if (!this.resolveCommand())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.state = EnumParserState.READ_WHITESPACE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case READ_WHITESPACE:
|
||||
if (Character.isWhitespace(cp))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (cp == CP_QUOTE)
|
||||
{
|
||||
this.state = EnumParserState.READING_PARAMETER_QUOTED;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.parameterBuilder.appendCodePoint(cp);
|
||||
this.state = EnumParserState.READING_PARAMETER;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case READING_PARAMETER_QUOTED:
|
||||
if (cp == CP_QUOTE)
|
||||
{
|
||||
this.state = EnumParserState.READING_PARAMETER_CANDIDATE_UNQUOTE;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.parameterBuilder.appendCodePoint(cp);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case READING_PARAMETER:
|
||||
if (Character.isWhitespace(cp))
|
||||
{
|
||||
this.emitParameter();
|
||||
this.state = EnumParserState.READ_WHITESPACE;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.parameterBuilder.appendCodePoint(cp);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case READING_PARAMETER_CANDIDATE_UNQUOTE:
|
||||
if (Character.isWhitespace(cp))
|
||||
{
|
||||
this.emitParameter();
|
||||
this.state = EnumParserState.READ_WHITESPACE;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.parameterBuilder.appendCodePoint(cp);
|
||||
this.state = EnumParserState.READING_PARAMETER_QUOTED;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case END:
|
||||
default:
|
||||
this.state = EnumParserState.UNEXPECTED_STATE_FALLBACK;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean resolveCommand()
|
||||
{
|
||||
var alias = this.commandNameBuilder.toString();
|
||||
|
||||
this.ctx.alias(alias);
|
||||
|
||||
this.command = CommandRegistry.getByAlias(alias);
|
||||
|
||||
if (this.command == null)
|
||||
{
|
||||
this.state = EnumParserState.END_NO_COMMAND;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.ctx.command(this.command);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void emitParameter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private boolean hasEmptyPrefix()
|
||||
{
|
||||
return this.prefixes.stream().anyMatch(Predicate.not(OfInt::hasNext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse using this parser and supplied prefixes. This function also
|
||||
* resolves the command context and the parameters. Yeah it does a lot of
|
||||
* stuff.
|
||||
*
|
||||
*/
|
||||
public CommandContext parse(Set<String> prefixes)
|
||||
{
|
||||
if (this.state != EnumParserState.BEGIN)
|
||||
{
|
||||
throw new IllegalStateException("Cannot run a parser that is not in the BEGIN state.");
|
||||
}
|
||||
|
||||
this.prefixBuilder = new StringBuilder();
|
||||
this.ctx = new CommandContextBuilder();
|
||||
|
||||
this.prefixes = prefixes.stream().map(String::codePoints).map(IntStream::iterator).collect(Collectors.toSet());
|
||||
|
||||
if (prefixes.isEmpty() || this.hasEmptyPrefix())
|
||||
{
|
||||
this.state = EnumParserState.READING_COMMAND;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.state = EnumParserState.READING_PREFIX;
|
||||
}
|
||||
|
||||
var cps = this.text.codePoints();
|
||||
|
||||
for (var cpIt = cps.iterator(); cpIt.hasNext(); )
|
||||
if (!this.readCodepoint(cpIt.next()))
|
||||
break;
|
||||
|
||||
// Update the state for EOF
|
||||
switch (this.state)
|
||||
{
|
||||
case READING_PARAMETER_QUOTED:
|
||||
case READ_WHITESPACE:
|
||||
case READING_PARAMETER:
|
||||
case READING_PARAMETER_CANDIDATE_UNQUOTE:
|
||||
this.state = EnumParserState.END;
|
||||
this.emitParameter();
|
||||
|
||||
break;
|
||||
|
||||
case READING_COMMAND:
|
||||
if (this.resolveCommand())
|
||||
{
|
||||
this.state = EnumParserState.END;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Check the end state
|
||||
switch (this.state)
|
||||
{
|
||||
case READING_PREFIX:
|
||||
case END_NO_PREFIX:
|
||||
return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_PREFIX);
|
||||
|
||||
case END_NO_COMMAND:
|
||||
return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_COMMAND_NAME);
|
||||
|
||||
case END:
|
||||
break;
|
||||
|
||||
default:
|
||||
return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_UNEXPECTED_STATE);
|
||||
}
|
||||
|
||||
// At this point we are 100% sure the command was resolved and can validate the parameters
|
||||
|
||||
/*
|
||||
*
|
||||
* TODO: Validate parameters here
|
||||
*
|
||||
*/
|
||||
|
||||
return this.ctx.resolved();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package cz.tefek.pluto.command.parser;
|
||||
|
||||
public enum EnumParserState
|
||||
{
|
||||
BEGIN,
|
||||
READING_PREFIX,
|
||||
READING_COMMAND,
|
||||
READING_PARAMETER,
|
||||
READING_PARAMETER_QUOTED,
|
||||
READING_PARAMETER_CANDIDATE_UNQUOTE,
|
||||
READ_WHITESPACE,
|
||||
END_NO_PREFIX,
|
||||
END_NO_COMMAND,
|
||||
END_EMISSION_FAILURE,
|
||||
END,
|
||||
UNEXPECTED_STATE_FALLBACK;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cz.tefek.pluto.command.platform;
|
||||
|
||||
import cz.tefek.pluto.command.context.CommandContextBuilder.EnumCommandParseFailure;
|
||||
|
||||
public abstract class CommandPlatform
|
||||
{
|
||||
public abstract String getID();
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public boolean shouldWarnOn(EnumCommandParseFailure failure)
|
||||
{
|
||||
switch (failure)
|
||||
{
|
||||
case UNRESOLVED_PREFIX:
|
||||
case UNRESOLVED_COMMAND_NAME:
|
||||
case UNRESOLVED_UNEXPECTED_STATE:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract int getMessageLimit();
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package cz.tefek.pluto.command.registry;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import cz.tefek.pluto.command.CommandBase;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public final class CommandRegistry
|
||||
{
|
||||
private static CommandRegistry instance;
|
||||
|
||||
private final Set<CommandBase> commands;
|
||||
private final Map<String, CommandBase> aliasTable;
|
||||
|
||||
static
|
||||
{
|
||||
instance = new CommandRegistry();
|
||||
}
|
||||
|
||||
private CommandRegistry()
|
||||
{
|
||||
this.aliasTable = new HashMap<>();
|
||||
this.commands = new HashSet<>();
|
||||
}
|
||||
|
||||
private void registerAlias(String alias, CommandBase command)
|
||||
{
|
||||
if (this.aliasTable.containsKey(alias))
|
||||
{
|
||||
Logger.logf(SmartSeverity.ERROR, "Alias '%s' for command '%s' is already used, skipping.%n", alias, command.name());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.aliasTable.put(alias, command);
|
||||
}
|
||||
|
||||
public static void registerCommand(CommandBase command)
|
||||
{
|
||||
if (!instance.commands.add(command))
|
||||
{
|
||||
Logger.logf(SmartSeverity.ERROR, "Command '%s' is already registered, skipping.%n", command.name());
|
||||
return;
|
||||
}
|
||||
|
||||
instance.registerAlias(command.name(), command);
|
||||
|
||||
Arrays.stream(command.aliases()).forEach(alias -> instance.registerAlias(alias, command));
|
||||
}
|
||||
|
||||
public static CommandBase getByAlias(String alias)
|
||||
{
|
||||
return instance.aliasTable.get(alias);
|
||||
}
|
||||
|
||||
public static Set<CommandBase> getCommands()
|
||||
{
|
||||
return Collections.unmodifiableSet(instance.commands);
|
||||
}
|
||||
|
||||
public static void clear()
|
||||
{
|
||||
instance = new CommandRegistry();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package cz.tefek.pluto.command.resolver;
|
||||
|
||||
public abstract class AbstractResolveException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -8934505669078637864L;
|
||||
|
||||
public AbstractResolveException(Exception exception)
|
||||
{
|
||||
super(exception);
|
||||
}
|
||||
|
||||
public AbstractResolveException(String what)
|
||||
{
|
||||
super(what);
|
||||
}
|
||||
|
||||
protected AbstractResolveException()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package cz.tefek.pluto.command.resolver;
|
||||
|
||||
public abstract class AbstractResolver
|
||||
{
|
||||
public abstract Class<?> getOutputType();
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package cz.tefek.pluto.command.resolver;
|
||||
|
||||
public enum EnumResolveFailure
|
||||
{
|
||||
INT_PARSE,
|
||||
LONG_PARSE,
|
||||
FLOAT_PARSE,
|
||||
DOUBLE_PARSE,
|
||||
FRAC_INVALID_PERCENTAGE,
|
||||
FRAC_PERCENTAGE_OUT_OF_RANGE,
|
||||
FRAC_VALUE_HIGHER_THAN_BASE,
|
||||
OTHER;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package cz.tefek.pluto.command.resolver;
|
||||
|
||||
public class ResolveException extends AbstractResolveException
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -3098373878754649161L;
|
||||
|
||||
private EnumResolveFailure failure;
|
||||
|
||||
public ResolveException(EnumResolveFailure failure)
|
||||
{
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
public EnumResolveFailure getResolveFailure()
|
||||
{
|
||||
return this.failure;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cz.tefek.pluto.command.resolver.generic;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.AbstractResolver;
|
||||
|
||||
public abstract class GenericResolver<R> extends AbstractResolver
|
||||
{
|
||||
protected Function<String, R> func;
|
||||
|
||||
public GenericResolver(Function<String, R> func)
|
||||
{
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
public R apply(String param)
|
||||
{
|
||||
return this.func.apply(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract Class<? super R> getOutputType();
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package cz.tefek.pluto.command.resolver.generic;
|
||||
|
||||
public class StringResolver extends GenericResolver<String>
|
||||
{
|
||||
public StringResolver()
|
||||
{
|
||||
super(String::valueOf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getOutputType()
|
||||
{
|
||||
return String.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cz.tefek.pluto.command.resolver.primitive;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.EnumResolveFailure;
|
||||
import cz.tefek.pluto.command.resolver.ResolveException;
|
||||
|
||||
public class BasicDoubleResolver extends DoubleResolver
|
||||
{
|
||||
public BasicDoubleResolver()
|
||||
{
|
||||
super(str ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return Double.parseDouble(str);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
throw new ResolveException(EnumResolveFailure.DOUBLE_PARSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package cz.tefek.pluto.command.resolver.primitive;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.EnumResolveFailure;
|
||||
import cz.tefek.pluto.command.resolver.ResolveException;
|
||||
|
||||
public class BasicIntResolver extends IntResolver
|
||||
{
|
||||
public BasicIntResolver()
|
||||
{
|
||||
super(str ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return Integer.parseInt(str);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
throw new ResolveException(EnumResolveFailure.INT_PARSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cz.tefek.pluto.command.resolver.primitive;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.EnumResolveFailure;
|
||||
import cz.tefek.pluto.command.resolver.ResolveException;
|
||||
|
||||
public class BasicLongResolver extends LongResolver
|
||||
{
|
||||
public BasicLongResolver()
|
||||
{
|
||||
super(str ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return Long.parseLong(str);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
throw new ResolveException(EnumResolveFailure.LONG_PARSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cz.tefek.pluto.command.resolver.primitive;
|
||||
|
||||
import java.util.function.ToDoubleFunction;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.AbstractResolver;
|
||||
|
||||
public class DoubleResolver extends AbstractResolver
|
||||
{
|
||||
protected ToDoubleFunction<String> func;
|
||||
|
||||
public DoubleResolver(ToDoubleFunction<String> func)
|
||||
{
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
public double apply(String param)
|
||||
{
|
||||
return this.func.applyAsDouble(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getOutputType()
|
||||
{
|
||||
return double.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package cz.tefek.pluto.command.resolver.primitive;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.EnumResolveFailure;
|
||||
import cz.tefek.pluto.command.resolver.ResolveException;
|
||||
|
||||
public class IntFractionResolver extends IntResolver
|
||||
{
|
||||
public IntFractionResolver(int base)
|
||||
{
|
||||
super(str -> parseAmount(str, base));
|
||||
}
|
||||
|
||||
private static int parseAmount(String amountString, int base) throws ResolveException
|
||||
{
|
||||
if (amountString.equalsIgnoreCase("all") || amountString.equalsIgnoreCase("everything"))
|
||||
{
|
||||
return base;
|
||||
}
|
||||
|
||||
if (amountString.equalsIgnoreCase("half"))
|
||||
{
|
||||
return base / 2;
|
||||
}
|
||||
|
||||
if (amountString.endsWith("%"))
|
||||
{
|
||||
try
|
||||
{
|
||||
float percentage = Float.parseFloat(amountString.substring(0, amountString.length() - 1));
|
||||
|
||||
if (percentage < 0 || percentage > 100)
|
||||
{
|
||||
throw new ResolveException(EnumResolveFailure.FRAC_PERCENTAGE_OUT_OF_RANGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Math.round(percentage / 100.0f * base);
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e1)
|
||||
{
|
||||
throw new ResolveException(EnumResolveFailure.FRAC_INVALID_PERCENTAGE);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int amount = Integer.parseInt(amountString);
|
||||
|
||||
if (amount > base)
|
||||
{
|
||||
throw new ResolveException(EnumResolveFailure.FRAC_VALUE_HIGHER_THAN_BASE);
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
throw new ResolveException(EnumResolveFailure.INT_PARSE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cz.tefek.pluto.command.resolver.primitive;
|
||||
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.AbstractResolver;
|
||||
|
||||
public class IntResolver extends AbstractResolver
|
||||
{
|
||||
protected ToIntFunction<String> func;
|
||||
|
||||
public IntResolver(ToIntFunction<String> func)
|
||||
{
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
public int apply(String param)
|
||||
{
|
||||
return this.func.applyAsInt(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Class<?> getOutputType()
|
||||
{
|
||||
return int.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cz.tefek.pluto.command.resolver.primitive;
|
||||
|
||||
import java.util.function.ToLongFunction;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.AbstractResolver;
|
||||
|
||||
public class LongResolver extends AbstractResolver
|
||||
{
|
||||
protected ToLongFunction<String> func;
|
||||
|
||||
public LongResolver(ToLongFunction<String> func)
|
||||
{
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
public long apply(String param)
|
||||
{
|
||||
return this.func.applyAsLong(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Class<?> getOutputType()
|
||||
{
|
||||
return long.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package cz.tefek.pluto.command.resolver;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import cz.tefek.pluto.command.resolver.primitive.BasicIntResolver;
|
||||
|
||||
class BasicIntResolverTest
|
||||
{
|
||||
@Test
|
||||
void parse()
|
||||
{
|
||||
var resolver = new BasicIntResolver();
|
||||
|
||||
Assertions.assertEquals(5, resolver.apply("5"));
|
||||
|
||||
Assertions.assertEquals(-50, resolver.apply("-50"));
|
||||
|
||||
Assertions.assertEquals(155, resolver.apply("155"));
|
||||
|
||||
Assertions.assertEquals(0, resolver.apply("0"));
|
||||
|
||||
Assertions.assertEquals(-0, resolver.apply("-0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptions()
|
||||
{
|
||||
var resolver = new BasicIntResolver();
|
||||
|
||||
// No foreign characters
|
||||
Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply("abc"));
|
||||
|
||||
// No floats
|
||||
Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply("12.5"));
|
||||
|
||||
// No empty strings
|
||||
Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply(""));
|
||||
|
||||
}
|
||||
}
|
|
@ -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!");
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
include 'plutolib',
|
||||
'plutocommandparser',
|
||||
'plutostatic',
|
||||
'plutotexturing',
|
||||
'plutomesher',
|
||||
|
|
Loading…
Reference in New Issue