diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java index 26fbbf310..a69d39c67 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java @@ -78,7 +78,7 @@ public interface TemplateManager extends Manageable { String defaultValueString = placeholderContent.substring(separatorIndex + 2); try { // TODO 改进报错检测 - this.defaultValue = SNBTReader.deserializeAsJava(defaultValueString); + this.defaultValue = new SNBTReader(defaultValueString).deserializeAsJava(); } catch (LocalizedResourceConfigException e) { e.appendTailArgument(this.placeholder); throw e; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/DefaultStringReader.java b/core/src/main/java/net/momirealms/craftengine/core/util/DefaultStringReader.java index 18a83e636..26099643f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/DefaultStringReader.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/DefaultStringReader.java @@ -112,6 +112,16 @@ public class DefaultStringReader implements StringReader { this.cursor += count; } + @Override + public char charAt(int index) { + return this.string.charAt(index); + } + + @Override + public String substring(int start, int end) { + return this.string.substring(start, end); + } + public static boolean isAllowedNumber(char c) { return c >= '0' && c <= '9' || c == '.' || c == '-'; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java b/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java index 10963176f..c42e99e8b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java @@ -15,7 +15,6 @@ public final class SNBTReader extends DefaultStringReader { private static final char DOUBLE_QUOTES = '"'; private static final char KEY_VALUE_SEPARATOR = ':'; private static final char ELEMENT_SEPARATOR = ','; - private static final char ESCAPE_CHAR = '\\'; // 数字类型后缀 private static final char BYTE_SUFFIX = 'b'; @@ -24,21 +23,15 @@ public final class SNBTReader extends DefaultStringReader { private static final char FLOAT_SUFFIX = 'f'; private static final char DOUBLE_SUFFIX = 'd'; - private final int length; - private SNBTReader(String content) { + public SNBTReader(String content) { super(content); - this.length = this.string.length(); } - // 入口API - public static Object deserializeAsJava(String input) throws IllegalArgumentException { - SNBTReader parser = new SNBTReader(input); - Object result = parser.parseValue(); - parser.skipWhitespace(); - - if (parser.cursor != parser.length) - throw new IllegalArgumentException("Extra content at end: " + parser.string.substring(parser.cursor, parser.length - parser.cursor)); - + public Object deserializeAsJava() { + Object result = this.parseValue(); + this.skipWhitespace(); + if (getCursor() != getTotalLength()) + throw new IllegalArgumentException("Extra content at end: " + substring(getCursor(), getTotalLength())); return result; } @@ -48,120 +41,90 @@ public final class SNBTReader extends DefaultStringReader { return switch (peek()) { case COMPOUND_START -> parseCompound(); case LIST_START -> parseList(); - case DOUBLE_QUOTES -> parseString(DOUBLE_QUOTES); - case SINGLE_QUOTES -> parseString(SINGLE_QUOTES); + case DOUBLE_QUOTES -> { + skip(); + yield readStringUntil(DOUBLE_QUOTES); + } + case SINGLE_QUOTES -> { + skip(); + yield readStringUntil(SINGLE_QUOTES); + } default -> parsePrimitive(); }; } // 解析包小肠 {} private Map parseCompound() { - cursor++; // 跳过 '{' + skip(); // 跳过 '{' skipWhitespace(); Map compoundMap = new LinkedHashMap<>(); - if (cursor < length && peek() != COMPOUND_END) { + if (canRead() && peek() != COMPOUND_END) { do { String key = parseKey(); - if (cursor >= length || peek() != KEY_VALUE_SEPARATOR) { - throw new IllegalArgumentException("Expected ':' at position " + cursor); + if (!canRead() || peek() != KEY_VALUE_SEPARATOR) { + throw new IllegalArgumentException("Expected ':' at position " + getCursor()); } - cursor++; // 跳过 ':' + skip(); // 跳过 ':' Object value = parseValue(); compoundMap.put(key, value); skipWhitespace(); - } while (cursor < length && peek() == ELEMENT_SEPARATOR && ++cursor > 0); + } while (canRead() && peek() == ELEMENT_SEPARATOR && ++super.cursor > 0 /* 跳过 ',' */); } - if (cursor >= length || peek() != COMPOUND_END) { - throw new IllegalArgumentException("Expected '}' at position " + cursor); + if (!canRead() || peek() != COMPOUND_END) { + throw new IllegalArgumentException("Expected '}' at position " + getCursor()); } - cursor++; // 跳过 '}' + skip(); // 跳过 '}' return compoundMap; } // 解析列表值 [1, 2, 3] private List parseList() { - cursor++; // 跳过 '[' + skip(); // 跳过 '[' skipWhitespace(); List elementList = new ArrayList<>(); - if (cursor < length && peek() != LIST_END) { + if (canRead() && peek() != LIST_END) { do { elementList.add(parseValue()); skipWhitespace(); - } while (cursor < length && peek() == ELEMENT_SEPARATOR && ++cursor > 0); + } while (canRead() && peek() == ELEMENT_SEPARATOR && ++super.cursor > 0 /* 跳过 ',' */); } - if (cursor >= length || peek() != LIST_END) { - throw new IllegalArgumentException("Expected ']' at position " + cursor); + if (!canRead() || peek() != LIST_END) { + throw new IllegalArgumentException("Expected ']' at position " + getCursor()); } - cursor++; // 跳过 ']' + skip(); // 跳过 ']' return elementList; } - // 解析字符串 - private String parseString(char delimiter) { - cursor++; // 跳过开始的引号 - int start = cursor; - - // 扫一次字符串, 如果没有发现转义就直接返回, 发现了就再走转义解析. - // 这样可以避免创建一次 StringBuilder. - while (cursor < length) { - char c = peek(); - if (c == delimiter) { - String result = string.substring(start, cursor); - cursor++; // 跳过结束引号 - return result; // 没有转义直接返回字符串. - } - // 如果发现转义字符,进行转义处理. - else if (c == ESCAPE_CHAR) { - return parseStringWithEscape(start, delimiter); - } - cursor++; - } - // 没有扫描到结束引号 - throw new IllegalArgumentException("Unterminated string at " + start); - } - - // 处理含转义的字符串 - private String parseStringWithEscape(int start, char delimiter) { - // 先把之前遍历没有包含转义的字符保存 - StringBuilder sb = new StringBuilder(cursor - start + 16); - sb.append(string, start, cursor); - - while (cursor < length) { - char c = read(); - if (c == ESCAPE_CHAR && cursor < length) { - sb.append(getEscapedChar(read())); // 解析转义. - } else if (c == delimiter) { // 发现结束分隔字符, 返回. - return sb.toString(); - } else { - sb.append(c); - } - } - // 没有扫描到结束引号 - throw new IllegalArgumentException("Unterminated string at " + start); - } - // 解析Key值 private String parseKey() { skipWhitespace(); - // 如果有双引号就委托给string解析处理. - if (cursor < length) { - if (peek() == STRING_DELIMITER) return parseString(STRING_DELIMITER); - if (peek() == SINGLE_QUOTES) return parseString(SINGLE_QUOTES); + if (!canRead()) { + throw new IllegalArgumentException("Unterminated key at " + getCursor()); } - int start = cursor; - while (cursor < length) { + // 如果有双引号就委托给string解析处理. + char peek = peek(); + if (peek == STRING_DELIMITER) { + skip(); + return readStringUntil(STRING_DELIMITER); + } else if (peek == SINGLE_QUOTES) { + skip(); + return readStringUntil(SINGLE_QUOTES); + } + + int start = getCursor(); + while (canRead()) { char c = peek(); if (c == ' ') break; // 忽略 key 后面的空格, { a :1} 应当解析成 {a:1} - if (Character.isJavaIdentifierPart(c)) cursor++; else break; + if (Character.isJavaIdentifierPart(c)) skip(); else break; } - String key = string.substring(start, cursor); + String key = substring(start, getCursor()); skipWhitespace(); // 跳过 key 后面的空格. return key; } @@ -169,13 +132,13 @@ public final class SNBTReader extends DefaultStringReader { // 解析原生值 private Object parsePrimitive() { // 先解析获取值的长度 - int tokenStart = cursor; + int tokenStart = getCursor(); int lastWhitespace = 0; // 记录值末尾的空格数量,{a:炒鸡 大保健} 和 {a: 炒鸡 大保健 } 都应解析成 "炒鸡 大保健". boolean contentHasWhitespace = false; // 记录值中有没有空格. - while (cursor < length) { + while (canRead()) { char c = peek(); if (c == ',' || c == ']' || c == '}') break; - cursor++; + skip(); if (c == ' ') { lastWhitespace++; // 遇到空格先增加值, 代表值尾部空格数量. continue; @@ -185,99 +148,70 @@ public final class SNBTReader extends DefaultStringReader { contentHasWhitespace = true; } } - int tokenLength = cursor - tokenStart - lastWhitespace; // 计算值长度需要再减去尾部空格. + int tokenLength = getCursor() - tokenStart - lastWhitespace; // 计算值长度需要再减去尾部空格. if (tokenLength == 0) throw new IllegalArgumentException("Empty value at position " + tokenStart); - String fullContent = string.substring(tokenStart, tokenStart + tokenLength); - if (contentHasWhitespace) return fullContent; // 如果值的中间有空格, 一定是字符串, 可直接返回. + if (contentHasWhitespace) return substring(tokenStart, tokenStart + tokenLength); // 如果值的中间有空格, 一定是字符串, 可直接返回. // 布尔值检查 - if ("1B".equals(fullContent) || (tokenLength == 4 && matchesAt(tokenStart, "true"))) return Boolean.TRUE; - if ("0B".equals(fullContent) || (tokenLength == 5 && matchesAt(tokenStart, "false"))) return Boolean.FALSE; - - int numberType = isNumber(tokenStart, tokenStart + tokenLength); // 返回0代表不是合法数字, 1代表整数, 2代表小数. - if (numberType == 1) return Integer.parseInt(fullContent); - if (numberType == 2) return Double.parseDouble(fullContent); - - // 带后缀的值处理 - char lastChar = string.charAt(tokenStart + tokenLength - 1); - if (tokenLength > 1 && // 要求: 长度>1 - isTypeSuffix(lastChar) && // 要求: 有效后缀 - numberType > 0 // 要求: 除后缀外是合法数字 - ) { - final String content = string.substring(tokenStart, tokenStart + tokenLength - 1); + if (tokenLength == 4) { + if (matchesAt(tokenStart, "true")) return Boolean.TRUE; + } else if (tokenLength == 5) { + if (matchesAt(tokenStart, "false")) return Boolean.FALSE; + } + if (tokenLength > 1) { + // 至少有1个字符,给了后缀的可能性 + char lastChar = charAt(tokenStart + tokenLength - 1); try { - return switch (lastChar) { - case BYTE_SUFFIX -> Byte.parseByte(content); - case SHORT_SUFFIX -> Short.parseShort(content); - case LONG_SUFFIX -> Long.parseLong(content); - case FLOAT_SUFFIX -> Float.parseFloat(content); - case DOUBLE_SUFFIX -> Double.parseDouble(content); - default -> new IllegalArgumentException("Parse Error with: " + content); // 永远不应进入此 case. - }; + switch (lastChar) { + case BYTE_SUFFIX -> { + return Byte.parseByte(substring(tokenStart, tokenStart + tokenLength - 1)); + } + case SHORT_SUFFIX -> { + return Short.parseShort(substring(tokenStart, tokenStart + tokenLength - 1)); + } + case LONG_SUFFIX -> { + return Long.parseLong(substring(tokenStart, tokenStart + tokenLength - 1)); + } + case FLOAT_SUFFIX -> { + return Float.parseFloat(substring(tokenStart, tokenStart + tokenLength)); + } + case DOUBLE_SUFFIX -> { + return Double.parseDouble(substring(tokenStart, tokenStart + tokenLength)); + } + default -> { + String fullString = substring(tokenStart, tokenStart + tokenLength); + try { + double d = Double.parseDouble(fullString); + if (d % 1 != 0 || fullString.contains(".") || fullString.contains("e")) { + return d; + } else { + return (int) d; + } + } catch (NumberFormatException e) { + return fullString; + } + } + } } catch (NumberFormatException e) { - return fullContent; // 如果有神人写了 128b 一类的超范围值, 就当做字符串返回好了. + return substring(tokenStart, tokenStart + tokenLength); + } + } else { + char onlyChar = charAt(tokenStart); + if (isNumber(onlyChar)) { + return onlyChar - '0'; + } else { + return String.valueOf(onlyChar); } } - - // 都无法匹配就默认为 String 喵~ - return fullContent; } // 工具函数: 快速检查布尔值字符串匹配, 忽略大小写. private boolean matchesAt(int start, String target) { for (int i = 0; i < target.length(); i++) { - char c1 = string.charAt(start + i); + char c1 = charAt(start + i); char c2 = target.charAt(i); if (c1 != c2 && c1 != (c2 ^ 32)) return false; // 忽略大小写比较 } return true; } - - // 工具函数: 合法后缀检查 - private boolean isTypeSuffix(char c) { - return c == BYTE_SUFFIX || c == SHORT_SUFFIX || c == LONG_SUFFIX || c == FLOAT_SUFFIX || c == DOUBLE_SUFFIX; - } - - // 转义字符处理 - private char getEscapedChar(char escapedChar) { - return switch (escapedChar) { - case DOUBLE_QUOTES -> '"'; - case SINGLE_QUOTES -> '\''; - case ESCAPE_CHAR -> '\\'; - case COMPOUND_START -> '{'; - case COMPOUND_END -> '}'; - case LIST_START -> '['; - case LIST_END -> ']'; - case KEY_VALUE_SEPARATOR -> ':'; - case ELEMENT_SEPARATOR -> ','; - default -> escapedChar; - }; - } - - // 检查是不是合法数字. - // 返回0代表不合法, 1代表整数, 2代表小数. - private int isNumber(int start, int end) { - // 跳过负号 - if (string.charAt(start) == '-') start++; - - // 除负号外第一个字符必须是数字. - char c1 = string.charAt(start); - if (c1 < '0' || c1 > '9') return 0; - - // 检查剩余的数字, 只能有一个小数点 - boolean hasDecimal = false; - for (; start < end; start++) { - char c = string.charAt(start); - if (c < '0' || c > '9') { - if (c == '.') { - if (hasDecimal) return 0; // bro 不能有2个小数点. - hasDecimal = true; - } else { - return 0; - } - } - } - - return hasDecimal ? 2 : 1; - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/StringReader.java b/core/src/main/java/net/momirealms/craftengine/core/util/StringReader.java index 2202bed2c..71dd4568a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/StringReader.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/StringReader.java @@ -40,6 +40,10 @@ public interface StringReader { void skip(int count); + char charAt(int index); + + String substring(int start, int end); + void skipWhitespace(); void skipWhitespaceAndComment();