Started moving towards passing around one big reader, instead of lots of strings

This commit is contained in:
Nathan Adams
2017-07-25 15:07:58 +02:00
parent a7674e984d
commit 9e481e3f24
13 changed files with 560 additions and 366 deletions
@@ -0,0 +1,175 @@
package com.mojang.brigadier;
import com.mojang.brigadier.exceptions.CommandException;
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
public class StringReader {
private static final char SYNTAX_ESCAPE = '\\';
private static final char SYNTAX_QUOTE = '"';
public static final SimpleCommandExceptionType ERROR_EXPECTED_START_OF_QUOTE = new SimpleCommandExceptionType("parsing.quote.expected.start", "Expected quote to start a string");
public static final SimpleCommandExceptionType ERROR_EXPECTED_END_OF_QUOTE = new SimpleCommandExceptionType("parsing.quote.expected.end", "Unclosed quoted string");
public static final ParameterizedCommandExceptionType ERROR_INVALID_ESCAPE = new ParameterizedCommandExceptionType("parsing.quote.escape", "Invalid escape sequence '\\${character}' in quoted string)", "character");
public static final ParameterizedCommandExceptionType ERROR_INVALID_BOOL = new ParameterizedCommandExceptionType("parsing.bool.invalid", "Invalid bool, expected true or false but found '${value}'", "value");
public static final ParameterizedCommandExceptionType ERROR_INVALID_INT = new ParameterizedCommandExceptionType("parsing.int.invalid", "Invalid integer '${value}'", "value");
public static final SimpleCommandExceptionType ERROR_EXPECTED_INT = new SimpleCommandExceptionType("parsing.int.expected", "Expected integer");
public static final ParameterizedCommandExceptionType ERROR_INVALID_DOUBLE = new ParameterizedCommandExceptionType("parsing.double.invalid", "Invalid double '${value}'", "value");
public static final SimpleCommandExceptionType ERROR_EXPECTED_DOUBLE = new SimpleCommandExceptionType("parsing.double.expected", "Expected double");
private final String string;
private int cursor;
public StringReader(String string) {
this.string = string;
}
public String getString() {
return string;
}
public void setCursor(int cursor) {
this.cursor = cursor;
}
public int getRemainingLength() {
return string.length() - cursor;
}
public int getTotalLength() {
return string.length();
}
public int getCursor() {
return cursor;
}
public String getRead() {
return string.substring(0, cursor);
}
public String getRemaining() {
return string.substring(cursor);
}
public boolean canRead(final int length) {
return cursor + length <= string.length();
}
public boolean canRead() {
return canRead(1);
}
public char peek() {
return string.charAt(cursor);
}
public char read() {
return string.charAt(cursor++);
}
public void skip() {
cursor++;
}
private static boolean isAllowedNumber(final char c) {
return c >= '0' && c <= '9' || c == '.' || c == '-';
}
public int readInt() throws CommandException {
final int start = cursor;
while (canRead() && isAllowedNumber(peek())) {
skip();
}
final String number = string.substring(start, cursor);
if (number.isEmpty()) {
throw ERROR_EXPECTED_INT.create();
}
try {
return Integer.parseInt(number);
} catch (final NumberFormatException ex) {
throw ERROR_INVALID_INT.create(number);
}
}
public double readDouble() throws CommandException {
final int start = cursor;
while (canRead() && isAllowedNumber(peek())) {
skip();
}
final String number = string.substring(start, cursor);
if (number.isEmpty()) {
throw ERROR_EXPECTED_DOUBLE.create();
}
try {
return Double.parseDouble(number);
} catch (final NumberFormatException ex) {
throw ERROR_INVALID_DOUBLE.create(number);
}
}
private static boolean isAllowedInUnquotedString(final char c) {
return c >= '0' && c <= '9'
|| c >= 'A' && c <= 'Z'
|| c >= 'a' && c <= 'z'
|| c == '_' || c == '-'
|| c == '.' || c == '+';
}
public String readUnquotedString() throws CommandException {
final int start = cursor;
while (canRead() && isAllowedInUnquotedString(peek())) {
skip();
}
return string.substring(start, cursor);
}
public String readQuotedString() throws CommandException {
if (!canRead()) {
return "";
} else if (peek() != SYNTAX_QUOTE) {
throw ERROR_EXPECTED_START_OF_QUOTE.create();
}
skip();
final StringBuilder result = new StringBuilder();
boolean escaped = false;
while (canRead()) {
final char c = read();
if (escaped) {
if (c == SYNTAX_QUOTE || c == SYNTAX_ESCAPE) {
result.append(c);
escaped = false;
} else {
throw ERROR_INVALID_ESCAPE.create(String.valueOf(c));
}
} else if (c == SYNTAX_ESCAPE) {
escaped = true;
} else if (c == SYNTAX_QUOTE) {
return result.toString();
} else {
result.append(c);
}
}
throw ERROR_EXPECTED_END_OF_QUOTE.create();
}
public String readString() throws CommandException {
if (canRead() && peek() == SYNTAX_QUOTE) {
return readQuotedString();
} else {
return readUnquotedString();
}
}
public boolean readBoolean() throws CommandException {
String value = readString();
if (value.equals("true")) {
return true;
} else if (value.equals("false")) {
return false;
} else {
throw ERROR_INVALID_BOOL.create(value);
}
}
}
@@ -1,5 +1,6 @@
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
import com.mojang.brigadier.exceptions.CommandException;
@@ -7,7 +8,13 @@ import com.mojang.brigadier.exceptions.CommandException;
import java.util.Set;
public interface ArgumentType<T> {
<S> ParsedArgument<S, T> parse(String command, CommandContextBuilder<S> contextBuilder) throws CommandException;
default <S> ParsedArgument<S, T> parse(String command, CommandContextBuilder<S> contextBuilder) throws CommandException {
StringReader reader = new StringReader(command);
T result = parse(reader, contextBuilder);
return new ParsedArgument<>(reader.getRead(), result);
}
<S> T parse(StringReader reader, CommandContextBuilder<S> contextBuilder) throws CommandException;
default <S> void listSuggestions(String command, Set<String> output, CommandContextBuilder<S> contextBuilder) {}
@@ -1,6 +1,7 @@
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
@@ -22,20 +23,8 @@ public class BoolArgumentType implements ArgumentType<Boolean> {
}
@Override
public <S> ParsedArgument<S, Boolean> parse(String command, CommandContextBuilder<S> contextBuilder) throws CommandException {
int end = command.indexOf(CommandDispatcher.ARGUMENT_SEPARATOR);
String raw = command;
if (end > -1) {
raw = command.substring(0, end);
}
if (raw.equals("true")) {
return new ParsedArgument<>(raw, true);
} else if (raw.equals("false")) {
return new ParsedArgument<>(raw, false);
} else {
throw ERROR_INVALID.create();
}
public <S> Boolean parse(StringReader reader, CommandContextBuilder<S> contextBuilder) throws CommandException {
return reader.readBoolean();
}
@Override
@@ -1,29 +0,0 @@
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
import com.mojang.brigadier.exceptions.CommandException;
import java.util.Arrays;
import java.util.Set;
public class CommandArgumentType<T> implements ArgumentType<ParseResults<T>> {
public static <S> CommandArgumentType<S> command() {
return new CommandArgumentType<S>();
}
@Override
public <S> ParsedArgument<S, ParseResults<T>> parse(String command, CommandContextBuilder<S> contextBuilder) throws CommandException {
final ParseResults<S> parse = contextBuilder.getDispatcher().parse(command, contextBuilder.getSource());
//noinspection unchecked
return new ParsedArgument<>(command, (ParseResults<T>) parse);
}
@Override
public <S> void listSuggestions(String command, Set<String> output, CommandContextBuilder<S> contextBuilder) {
final String[] suggestions = contextBuilder.getDispatcher().getCompletionSuggestions(command, contextBuilder.getSource());
output.addAll(Arrays.asList(suggestions));
}
}
@@ -1,6 +1,7 @@
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
@@ -10,7 +11,6 @@ import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import java.util.Objects;
public class IntegerArgumentType implements ArgumentType<Integer> {
public static final ParameterizedCommandExceptionType ERROR_NOT_A_NUMBER = new ParameterizedCommandExceptionType("argument.integer.invalid", "Expected an integer, found '${found}'", "found");
public static final ParameterizedCommandExceptionType ERROR_WRONG_SUFFIX = new ParameterizedCommandExceptionType("argument.integer.wrongsuffix", "Expected suffix '${suffix}'", "suffix");
public static final ParameterizedCommandExceptionType ERROR_TOO_SMALL = new ParameterizedCommandExceptionType("argument.integer.low", "Integer must not be less than ${minimum}, found ${found}", "found", "minimum");
public static final ParameterizedCommandExceptionType ERROR_TOO_BIG = new ParameterizedCommandExceptionType("argument.integer.big", "Integer must not be more than ${maximum}, found ${found}", "found", "maximum");
@@ -46,38 +46,22 @@ public class IntegerArgumentType implements ArgumentType<Integer> {
}
@Override
public <S> ParsedArgument<S, Integer> parse(String command, CommandContextBuilder<S> contextBuilder) throws CommandException {
int end = command.indexOf(CommandDispatcher.ARGUMENT_SEPARATOR);
String raw = command;
if (end > -1) {
raw = command.substring(0, end);
}
if (raw.length() < suffix.length()) {
throw ERROR_WRONG_SUFFIX.create(this.suffix);
}
String number = raw.substring(0, raw.length() - suffix.length());
String suffix = raw.substring(number.length());
if (!suffix.equals(this.suffix)) {
throw ERROR_WRONG_SUFFIX.create(this.suffix);
}
try {
int value = Integer.parseInt(number);
if (value < minimum) {
throw ERROR_TOO_SMALL.create(value, minimum);
public <S> Integer parse(StringReader reader, CommandContextBuilder<S> contextBuilder) throws CommandException {
int result = reader.readInt();
for (int i = 0; i < suffix.length(); i++) {
if (reader.canRead() && reader.peek() == suffix.charAt(i)) {
reader.skip();
} else {
throw ERROR_WRONG_SUFFIX.create(suffix);
}
if (value > maximum) {
throw ERROR_TOO_BIG.create(value, maximum);
}
return new ParsedArgument<>(raw, value);
} catch (NumberFormatException ignored) {
throw ERROR_NOT_A_NUMBER.create(number);
}
if (result < minimum) {
throw ERROR_TOO_SMALL.create(result, minimum);
}
if (result > maximum) {
throw ERROR_TOO_BIG.create(result, maximum);
}
return result;
}
@Override
@@ -1,6 +1,7 @@
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
@@ -9,11 +10,6 @@ import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
public class StringArgumentType implements ArgumentType<String> {
public static final ParameterizedCommandExceptionType ERROR_INVALID_ESCAPE = new ParameterizedCommandExceptionType("argument.string.escape.invalid", "Unknown or invalid escape sequence: ${input}", "input");
public static final SimpleCommandExceptionType ERROR_UNEXPECTED_ESCAPE = new SimpleCommandExceptionType("argument.string.escape.unexpected", "Unexpected escape sequence, please quote the whole argument");
public static final SimpleCommandExceptionType ERROR_UNEXPECTED_START_OF_QUOTE = new SimpleCommandExceptionType("argument.string.quote.unexpected_start", "Unexpected start-of-quote character (\"), please quote the whole argument");
public static final SimpleCommandExceptionType ERROR_UNEXPECTED_END_OF_QUOTE = new SimpleCommandExceptionType("argument.string.quote.unexpected_end", "Unexpected end-of-quote character (\"), it must be at the end or followed by a space (' ') for the next argument");
public static final SimpleCommandExceptionType ERROR_EXPECTED_END_OF_QUOTE = new SimpleCommandExceptionType("argument.string.quote.expected_end", "Expected end-of-quote character (\") but found no more input");
private final StringType type;
private StringArgumentType(StringType type) {
@@ -37,59 +33,15 @@ public class StringArgumentType implements ArgumentType<String> {
}
@Override
public <S> ParsedArgument<S, String> parse(String command, CommandContextBuilder<S> contextBuilder) throws CommandException {
public <S> String parse(StringReader reader, CommandContextBuilder<S> contextBuilder) throws CommandException {
if (type == StringType.GREEDY_PHRASE) {
return new ParsedArgument<>(command, command);
String text = reader.getRemaining();
reader.setCursor(reader.getTotalLength());
return text;
} else if (type == StringType.SINGLE_WORD) {
int index = command.indexOf(CommandDispatcher.ARGUMENT_SEPARATOR);
if (index > 0) {
final String word = command.substring(0, index);
return new ParsedArgument<>(word, word);
} else {
return new ParsedArgument<>(command, command);
}
return reader.readUnquotedString();
} else {
StringBuilder result = new StringBuilder();
int i = 0;
boolean escaped = false;
boolean quoted = false;
while (i < command.length()) {
char c = command.charAt(i);
if (escaped) {
if (c == '"' || c == '\\') {
result.append(c);
} else {
throw ERROR_INVALID_ESCAPE.create("\\" + c);
}
escaped = false;
} else if (c == '\\') {
if (quoted) {
escaped = true;
} else {
throw ERROR_UNEXPECTED_ESCAPE.create();
}
} else if (c == '"') {
if (i == 0) {
quoted = true;
} else if (!quoted) {
throw ERROR_UNEXPECTED_START_OF_QUOTE.create();
} else if (i == command.length() - 1 || command.charAt(i + 1) == CommandDispatcher.ARGUMENT_SEPARATOR_CHAR) {
i++;
break;
} else {
throw ERROR_UNEXPECTED_END_OF_QUOTE.create();
}
} else if (!quoted && c == CommandDispatcher.ARGUMENT_SEPARATOR_CHAR) {
break;
} else if (quoted && i == command.length() - 1) {
throw ERROR_EXPECTED_END_OF_QUOTE.create();
} else {
result.append(c);
}
i++;
}
return new ParsedArgument<>(command.substring(0, i), result.toString());
return reader.readString();
}
}