diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java index 1d43f760f..798b1ea17 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitPlatform.java @@ -1,11 +1,14 @@ package net.momirealms.craftengine.bukkit.plugin; +import com.google.gson.JsonElement; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistryOps; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.Platform; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; import org.bukkit.Bukkit; import java.util.Map; @@ -19,14 +22,36 @@ public class BukkitPlatform implements Platform { @SuppressWarnings("unchecked") @Override - public Object nbt2Java(String nbt) { + public Object snbtToJava(String nbt) { try { Object tag = FastNMS.INSTANCE.method$TagParser$parseCompoundFully("{\"root\":" + nbt + "}"); Map map = (Map) MRegistryOps.NBT.convertTo(MRegistryOps.JAVA, tag); return map.get("root"); } catch (CommandSyntaxException e) { CraftEngine.instance().debug(e::getMessage); - throw new LocalizedResourceConfigException("warning.config.template.argument.default_value.invalid_syntax", e, nbt); + throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e, nbt); } } + + @Override + public Tag jsonToSparrowNBT(JsonElement json) { + return MRegistryOps.JSON.convertTo(MRegistryOps.SPARROW_NBT, json); + } + + @Override + public Tag snbtToSparrowNBT(String nbt) { + try { + Object tag = FastNMS.INSTANCE.method$TagParser$parseCompoundFully("{\"root\":" + nbt + "}"); + CompoundTag map = (CompoundTag) MRegistryOps.NBT.convertTo(MRegistryOps.SPARROW_NBT, tag); + return map.get("root"); + } catch (CommandSyntaxException e) { + CraftEngine.instance().debug(e::getMessage); + throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e, nbt); + } + } + + @Override + public Tag javaToSparrowNBT(Object object) { + return MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, object); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MRegistryOps.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MRegistryOps.java index da5b892ab..dd90cb582 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MRegistryOps.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MRegistryOps.java @@ -42,7 +42,7 @@ public final class MRegistryOps { // 1.20.1-1.20.4 JAVA = (DynamicOps) CoreReflections.method$RegistryOps$create.invoke(null, LegacyJavaOps.INSTANCE, FastNMS.INSTANCE.registryAccess()); } else { - JAVA = null; + throw new ReflectionInitException("Could not find JavaOps"); } NBT = (DynamicOps) CoreReflections.method$RegistryOps$create.invoke(null, ReflectionUtils.getDeclaredField(clazz$NbtOps, clazz$NbtOps, 0).get(null), FastNMS.INSTANCE.registryAccess()); JSON = (DynamicOps) CoreReflections.method$RegistryOps$create.invoke(null, JsonOps.INSTANCE, FastNMS.INSTANCE.registryAccess()); diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 5d00cf31e..47430dd7b 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -71,6 +71,7 @@ warning.config.type.float: "Issue found in file - Failed to load warning.config.type.double: "Issue found in file - Failed to load '': Cannot cast '' to double type for option ''." warning.config.type.quaternionf: "Issue found in file - Failed to load '': Cannot cast '' to Quaternionf type for option ''." warning.config.type.vector3f: "Issue found in file - Failed to load '': Cannot cast '' to Vector3f type for option ''." +warning.config.type.snbt.invalid_syntax: "Issue found in file - Failed to load '': Invalid snbt syntax ''." warning.config.number.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for number argument." warning.config.number.invalid_type: "Issue found in file - The config '' is using an invalid number argument type ''." warning.config.number.missing_argument: "Issue found in file - The config '' is missing the argument for 'number'." @@ -137,7 +138,6 @@ warning.config.template.duplicate: "Issue found in file - Duplic warning.config.template.invalid: "Issue found in file - The config '' is using an invalid template ''." warning.config.template.argument.self_increase_int.invalid_range: "Issue found in file - The template '' is using a 'from' '' larger than 'to' '' in 'self_increase_int' argument." warning.config.template.argument.list.invalid_type: "Issue found in file - The template '' is using a 'list' argument which expects a 'List' as argument while the input argument is a(n) ''." -warning.config.template.argument.default_value.invalid_syntax: "Issue found in file - The template '' is using an invalid default value '' for argument ''." warning.config.vanilla_loot.missing_type: "Issue found in file - The vanilla loot '' is missing the required 'type' argument." warning.config.vanilla_loot.invalid_type: "Issue found in file - The vanilla loot '' is using an invalid type ''. Allowed types: []." warning.config.vanilla_loot.block.invalid_target: "Issue found in file - Invalid block target '' in vanilla loot ''." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 57f3c31cf..3122c7ada 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -71,6 +71,7 @@ warning.config.type.boolean: "在文件 发现问题 - 无法加 warning.config.type.double: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为双精度类型 (选项 '')" warning.config.type.quaternionf: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为四元数类型 (选项 '')" warning.config.type.vector3f: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为三维向量类型 (选项 '')" +warning.config.type.snbt.invalid_syntax: "在文件 发现问题 - 无法加载 '': 无效的 SNBT 语法 ''." warning.config.number.missing_type: "在文件 发现问题 - 配置项 '' 缺少数字类型所需的 'type' 参数" warning.config.number.invalid_type: "在文件 发现问题 - 配置项 '' 使用了无效的数字类型 ''" warning.config.number.missing_argument: "在文件 发现问题 - 配置项 '' 缺少数字参数" @@ -137,7 +138,6 @@ warning.config.template.duplicate: "在文件 发现问题 - 重 warning.config.template.argument.self_increase_int.invalid_range: "在文件 发现问题 - 模板 '' 在 'self_increase_int' 参数中使用了一个起始值 '' 大于终止值 ''" warning.config.template.invalid: "在文件 发现问题 - 配置 '' 使用了无效的模板 ''." warning.config.template.argument.list.invalid_type: "在文件 发现问题 - 模板 '' 的 'list' 参数需要列表类型 但输入参数类型为 ''" -warning.config.template.argument.default_value.invalid_syntax: "在文件 发现问题 - 模板 '' 在参数 '' 中使用了无效的默认值 ''." warning.config.vanilla_loot.missing_type: "在文件 发现问题 - 原版战利品 '' 缺少必需的 'type' 参数" warning.config.vanilla_loot.invalid_type: "在文件 发现问题 - 原版战利品 '' 使用了无效类型 '' 允许的类型: []" warning.config.vanilla_loot.block.invalid_target: "在文件 发现问题 - 原版战利品 '' 中存在无效的方块目标 ''" diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentModifier.java index 5fb24fa13..2406e10eb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentModifier.java @@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.NetworkItemHandler; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Pair; @@ -17,15 +18,15 @@ import java.util.List; import java.util.Map; public class ComponentModifier implements ItemDataModifier { - private final List> arguments; - private JsonObject customData = null; + private final List> arguments; + private CompoundTag customData = null; public ComponentModifier(Map arguments) { - List> pairs = new ArrayList<>(arguments.size()); + List> pairs = new ArrayList<>(arguments.size()); for (Map.Entry entry : arguments.entrySet()) { Key key = Key.of(entry.getKey()); if (key.equals(ComponentKeys.CUSTOM_DATA)) { - this.customData = parseJsonObjectValue(entry.getValue()); + this.customData = (CompoundTag) parseValue(entry.getValue()); } else { pairs.add(new Pair<>(key, parseValue(entry.getValue()))); } @@ -33,28 +34,19 @@ public class ComponentModifier implements ItemDataModifier { this.arguments = pairs; } - public List> arguments() { + public List> arguments() { return arguments; } - private Object parseValue(Object value) { + private Tag parseValue(Object value) { if (value instanceof String string) { if (string.startsWith("(json) ")) { - return GsonHelper.get().fromJson(string.substring("(json) ".length()), JsonElement.class); + return CraftEngine.instance().platform().jsonToSparrowNBT(GsonHelper.get().fromJson(string.substring("(json) ".length()), JsonElement.class)); + } else if (string.startsWith("(snbt) ")) { + return CraftEngine.instance().platform().snbtToSparrowNBT(string.substring("(snbt) ".length())); } } - return value; - } - - private JsonObject parseJsonObjectValue(Object value) { - if (value instanceof String string) { - if (string.startsWith("(json) ")) { - return GsonHelper.get().fromJson(string.substring("(json) ".length()), JsonObject.class); - } - } else if (value instanceof Map map) { - return (JsonObject) GsonHelper.get().toJsonTree(map, Map.class); - } - throw new UnsupportedOperationException("Invalid minecraft:custom_data value: " + value.toString()); + return CraftEngine.instance().platform().javaToSparrowNBT(value); } @Override @@ -64,13 +56,16 @@ public class ComponentModifier implements ItemDataModifier { @Override public Item apply(Item item, ItemBuildContext context) { - for (Pair entry : this.arguments) { - item.setComponent(entry.left(), entry.right()); + for (Pair entry : this.arguments) { + item.setNBTComponent(entry.left(), entry.right()); } if (this.customData != null) { - JsonObject tag = (JsonObject) item.getJsonComponent(ComponentKeys.CUSTOM_DATA); + CompoundTag tag = (CompoundTag) item.getNBTTag(ComponentKeys.CUSTOM_DATA); if (tag != null) { - item.setComponent(ComponentKeys.CUSTOM_DATA, GsonHelper.shallowMerge(this.customData, tag)); + for (Map.Entry entry : this.customData.entrySet()) { + tag.put(entry.getKey(), entry.getValue()); + } + item.setComponent(ComponentKeys.CUSTOM_DATA, tag); } else { item.setComponent(ComponentKeys.CUSTOM_DATA, this.customData); } @@ -80,7 +75,7 @@ public class ComponentModifier implements ItemDataModifier { @Override public Item prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { - for (Pair entry : this.arguments) { + for (Pair entry : this.arguments) { Tag previous = item.getNBTComponent(entry.left()); if (previous != null) { networkData.put(entry.left().asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java index 09b32b9a5..03e0c52b2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/Platform.java @@ -1,8 +1,17 @@ package net.momirealms.craftengine.core.plugin; +import com.google.gson.JsonElement; +import net.momirealms.sparrow.nbt.Tag; + public interface Platform { void dispatchCommand(String command); - Object nbt2Java(String nbt); + Object snbtToJava(String nbt); + + Tag jsonToSparrowNBT(JsonElement json); + + Tag snbtToSparrowNBT(String nbt); + + Tag javaToSparrowNBT(Object object); } 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 4d6419008..2f6fb429a 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 @@ -76,7 +76,7 @@ public interface TemplateManager extends Manageable { this.placeholder = placeholderContent.substring(0, separatorIndex); String defaultValueString = placeholderContent.substring(separatorIndex + 2); try { - this.defaultValue = CraftEngine.instance().platform().nbt2Java(defaultValueString); + this.defaultValue = CraftEngine.instance().platform().snbtToJava(defaultValueString); } catch (LocalizedResourceConfigException e) { e.appendTailArgument(this.placeholder); throw e; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/SNBTDeserializer.java b/core/src/main/java/net/momirealms/craftengine/core/util/SNBTDeserializer.java new file mode 100644 index 000000000..f98593616 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/SNBTDeserializer.java @@ -0,0 +1,336 @@ +package net.momirealms.craftengine.core.util; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class SNBTDeserializer { + + private static final char COMPOUND_START = '{'; + private static final char COMPOUND_END = '}'; + private static final char LIST_START = '['; + private static final char LIST_END = ']'; + private static final char STRING_DELIMITER = '"'; + 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'; + private static final char SHORT_SUFFIX = 's'; + private static final char LONG_SUFFIX = 'l'; + private static final char FLOAT_SUFFIX = 'f'; + private static final char DOUBLE_SUFFIX = 'd'; + private static final char BOOLEAN_SUFFIX = 'B'; + + // 布尔值常量 + private static final String TRUE_LITERAL = "true"; + private static final String FALSE_LITERAL = "false"; + private static final int TRUE_LENGTH = 4; + private static final int FALSE_LENGTH = 5; + + // 使用 char[] 处理获得更高的性能 + private final char[] sourceContent; + private final int length; + private int position = 0; + + private SNBTDeserializer(String content) { + this.sourceContent = content.toCharArray(); + this.length = sourceContent.length; + } + + // 入口API + public static Object parse(String input) throws IllegalArgumentException { + SNBTDeserializer parser = new SNBTDeserializer(input); + Object result = parser.parseValue(); + parser.skipWhitespace(); + + if (parser.position != parser.length) { + throw new IllegalArgumentException("Extra content at end: " + + new String(parser.sourceContent, parser.position, + parser.length - parser.position)); + } + return result; + } + + // 开始解析, 步进字符. + private Object parseValue() { + skipWhitespace(); + return switch (peekCurrentChar()) { + case COMPOUND_START -> parseCompound(); + case LIST_START -> parseList(); + case STRING_DELIMITER -> parseString(); + default -> parsePrimitive(); + }; + } + + // 解析包小肠 {} + private Map parseCompound() { + position++; // 跳过 '{' + skipWhitespace(); + + Map compoundMap = new LinkedHashMap<>(16); // 避免一次扩容, 应该有一定的性能提升 + + if (position < length && sourceContent[position] != COMPOUND_END) { + do { + String key = parseKey(); + if (position >= length || sourceContent[position] != KEY_VALUE_SEPARATOR) { + throw new IllegalArgumentException("Expected ':' at position " + position); + } + position++; // 跳过 ':' + Object value = parseValue(); + compoundMap.put(key, value); + skipWhitespace(); + } while (position < length && sourceContent[position] == ELEMENT_SEPARATOR && ++position > 0); + } + + if (position >= length || sourceContent[position] != COMPOUND_END) { + throw new IllegalArgumentException("Expected '}' at position " + position); + } + position++; // 跳过 '}' + return compoundMap; + } + + // 解析列表值 [1, 2, 3] + private List parseList() { + position++; // 跳过 '[' + skipWhitespace(); + List elementList = new ArrayList<>(); + + if (position < length && sourceContent[position] != LIST_END) { + do { + elementList.add(parseValue()); + skipWhitespace(); + } while (position < length && sourceContent[position] == ELEMENT_SEPARATOR && ++position > 0); + } + + if (position >= length || sourceContent[position] != LIST_END) { + throw new IllegalArgumentException("Expected ']' at position " + position); + } + position++; // 跳过 ']' + return elementList; + } + + // 解析字符串 + private String parseString() { + position++; // 跳过开始的引号 + int start = position; + + // 扫一次字符串, 如果没有发现转义就直接返回, 发现了就再走转义解析. + // 这样可以避免创建一次 StringBuilder. + while (position < length) { + char c = sourceContent[position]; + if (c == STRING_DELIMITER) { + String result = new String(sourceContent, start, position - start); + position++; // 跳过结束引号 + return result; // 没有转义直接返回字符串. + } + // 如果发现转义字符, + else if (c == ESCAPE_CHAR) { + return parseStringWithEscape(start); + } + position++; + } + // 没有扫描到结束引号 + throw new IllegalArgumentException("Unterminated string at " + start); + } + + // 处理含转义的字符串 + private String parseStringWithEscape(int start) { + StringBuilder sb = new StringBuilder(position - start + 16); + sb.append(sourceContent, start, position - start); + + while (position < length) { + char c = sourceContent[position++]; + if (c == ESCAPE_CHAR && position < length) { + sb.append(getEscapedChar(sourceContent[position++])); + } else if (c == STRING_DELIMITER) { // 字符 + return sb.toString(); + } else { + sb.append(c); + } + } + // 没有扫描到结束引号 + throw new IllegalArgumentException("Unterminated string at " + start); + } + + // 解析Key值 + private String parseKey() { + skipWhitespace(); + // 如果有双引号就委托给string解析处理. + if (position < length && sourceContent[position] == STRING_DELIMITER) { + return parseString(); + } + + int start = position; + while (position < length) { + char c = sourceContent[position]; + if (Character.isJavaIdentifierPart(c)) { + position++; + } else { + break; + } + } + + skipWhitespace(); + return new String(sourceContent, start, position - start); + } + + // 解析原生值 + private Object parsePrimitive() { + skipWhitespace(); + int tokenStart = position; + + // 先解析获取值的长度 + while (position < length) { + char c = sourceContent[position]; + if (c <= ' ' || c == ',' || c == ']' || c == '}') break; + position++; + } + int tokenLength = position - tokenStart; + if (tokenLength == 0) { + throw new IllegalArgumentException("Empty value at position " + tokenStart); + } + + // 布尔值快速检查 + if (tokenLength == TRUE_LENGTH && matchesAt(tokenStart, TRUE_LITERAL)) { + return Boolean.TRUE; + } + if (tokenLength == FALSE_LENGTH && matchesAt(tokenStart, FALSE_LITERAL)) { + return Boolean.FALSE; + } + + // 带后缀的值处理 + try { + char lastChar = sourceContent[tokenStart + tokenLength - 1]; + if (tokenLength > 1 && isTypeSuffix(lastChar)) { + return switch (lastChar) { + case BYTE_SUFFIX -> parseByte(tokenStart, tokenLength - 1); + case SHORT_SUFFIX -> parseShort(tokenStart, tokenLength - 1); + case LONG_SUFFIX -> parseLong(tokenStart, tokenLength - 1); + case FLOAT_SUFFIX -> Float.parseFloat(new String(sourceContent, tokenStart, tokenLength - 1)); + case DOUBLE_SUFFIX -> Double.parseDouble(new String(sourceContent, tokenStart, tokenLength - 1)); + case BOOLEAN_SUFFIX -> parseBoolean(new String(sourceContent, tokenStart, tokenLength - 1)); + default -> throw new IllegalArgumentException("Invalid suffixed value " + new String(sourceContent, tokenStart, tokenLength - 1)); + }; + } + // 没有后缀就默认为 double 喵 + return Double.parseDouble(new String(sourceContent, tokenStart, tokenLength)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid number value at position " + tokenStart, e); + } + } + + // 工具函数: 快速检查布尔值字符串匹配, 忽略大小写. + private boolean matchesAt(int start, String target) { + for (int i = 0; i < target.length(); i++) { + char c1 = sourceContent[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 || c == BOOLEAN_SUFFIX; + } + + + // 手动解析值 + private byte parseByte(int start, int length) { + int value = parseInteger(start, length); + if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) + throw new IllegalArgumentException("Byte value out of range"); + return (byte) value; + } + + private short parseShort(int start, int length) { + int value = parseInteger(start, length); + if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) + throw new NumberFormatException("Short value out of range"); + return (short) value; + } + + private Boolean parseBoolean(String content) { + if ("1".equals(content)) return Boolean.TRUE; + if ("0".equals(content)) return Boolean.FALSE; + throw new NumberFormatException("Invalid boolean value"); + } + + private int parseInteger(int start, int length) { + int result = 0; + boolean negative = false; + int i = 0; + + if (sourceContent[start] == '-') { + negative = true; + i = 1; + } + + for (; i < length; i++) { + char c = sourceContent[start + i]; + if (c < '0' || c > '9') throw new NumberFormatException("Invalid integer"); + result = result * 10 + (c - '0'); + } + + return negative ? -result : result; + } + + private long parseLong(int start, int length) { + long result = 0; + boolean negative = false; + int i = 0; + + if (sourceContent[start] == '-') { + negative = true; + i = 1; + } + + for (; i < length; i++) { + char c = sourceContent[start + i]; + if (c < '0' || c > '9') throw new NumberFormatException("Invalid long"); + result = result * 10 + (c - '0'); + } + + return negative ? -result : result; + } + + // 转义字符处理 + private char getEscapedChar(char escapedChar) { + return switch (escapedChar) { + case STRING_DELIMITER -> '"'; + case ESCAPE_CHAR -> '\\'; + case COMPOUND_START -> '{'; + case COMPOUND_END -> '}'; + case LIST_START -> '['; + case LIST_END -> ']'; + case KEY_VALUE_SEPARATOR -> ':'; + case ELEMENT_SEPARATOR -> ','; + default -> escapedChar; + }; + } + + // 获取当前字符 + private char peekCurrentChar() { + if (position >= length) throw new IllegalArgumentException("Unexpected end of input at position " + position); + return sourceContent[position]; + } + + + // 跳过空格 + private void skipWhitespace() { + while (position < length) { + char c = sourceContent[position]; + if (c > ' ') { break; } // 大于空格的字符都不是空白字符 + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + position++; + } else { + break; + } + } + } + +}