mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-19 15:09:15 +00:00
更贴切原版的snbt实现
This commit is contained in:
@@ -31,34 +31,11 @@ public class BukkitPlatform implements Platform {
|
|||||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command);
|
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public Object snbtToJava(String nbt) {
|
|
||||||
try {
|
|
||||||
Object tag = FastNMS.INSTANCE.method$TagParser$parseCompoundFully("{\"root\":" + nbt + "}");
|
|
||||||
Map<String, Object> map = (Map<String, Object>) MRegistryOps.NBT.convertTo(MRegistryOps.JAVA, tag);
|
|
||||||
return map.get("root");
|
|
||||||
} catch (CommandSyntaxException e) {
|
|
||||||
throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e, nbt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Tag jsonToSparrowNBT(JsonElement json) {
|
public Tag jsonToSparrowNBT(JsonElement json) {
|
||||||
return MRegistryOps.JSON.convertTo(MRegistryOps.SPARROW_NBT, 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) {
|
|
||||||
throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e, nbt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Tag javaToSparrowNBT(Object object) {
|
public Tag javaToSparrowNBT(Object object) {
|
||||||
return MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, object);
|
return MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, object);
|
||||||
|
|||||||
@@ -90,6 +90,32 @@ warning.config.type.vec3d: "<yellow>Issue found in file <arg:0> - Failed to load
|
|||||||
warning.config.type.map: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Map type for option '<arg:3>'.</yellow>"
|
warning.config.type.map: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Map type for option '<arg:3>'.</yellow>"
|
||||||
warning.config.type.aabb: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to AABB type for option '<arg:3>'.</yellow>"
|
warning.config.type.aabb: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to AABB type for option '<arg:3>'.</yellow>"
|
||||||
warning.config.type.snbt.invalid_syntax: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Invalid snbt syntax '<arg:2>'.</yellow>"
|
warning.config.type.snbt.invalid_syntax: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Invalid snbt syntax '<arg:2>'.</yellow>"
|
||||||
|
warning.config.type.snbt.invalid_syntax.parse_error: "<yellow><arg:0> at position <arg:1>: <arg:2></yellow>"
|
||||||
|
warning.config.type.snbt.invalid_syntax.here: "<yellow><--[HERE]</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_string_uuid: "<yellow>Expected a string representing a valid UUID</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_number_or_boolean: "<yellow>Expected a number or a boolean</yellow>"
|
||||||
|
warning.config.type.snbt.parser.trailing: "<yellow>Unexpected trailing data</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected.compound: "<yellow>Expected compound tag</yellow>"
|
||||||
|
warning.config.type.snbt.parser.number_parse_failure: "<yellow>Failed to parse number: <arg:0></yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_hex_escape: "<yellow>Expected a character literal of length <arg:0></yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_codepoint: "<yellow>Invalid Unicode character value: <arg:0></yellow>"
|
||||||
|
warning.config.type.snbt.parser.no_such_operation: "<yellow>No such operation: <arg:0></yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_integer_type: "<yellow>Expected an integer number</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_float_type: "<yellow>Expected a floating point number</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_non_negative_number: "<yellow>Expected a non-negative number</yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_character_name: "<yellow>Invalid Unicode character name</yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_array_element_type: "<yellow>Invalid array element type</yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_unquoted_start: "<yellow>Unquoted strings can't start with digits 0-9, + or -</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_unquoted_string: "<yellow>Expected a valid unquoted string</yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_string_contents: "<yellow>Invalid string contents</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_binary_numeral: "<yellow>Expected a binary number</yellow>"
|
||||||
|
warning.config.type.snbt.parser.underscore_not_allowed: "<yellow>Underscore is not allowed in binary numerals</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_decimal_numeral: "<yellow>Expected a decimal number</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_hex_numeral: "<yellow>Expected a hexadecimal number</yellow>"
|
||||||
|
warning.config.type.snbt.parser.empty_key: "<yellow>Key cannot be empty</yellow>"
|
||||||
|
warning.config.type.snbt.parser.leading_zero_not_allowed: "<yellow>Decimal numbers can't start with 0</yellow>"
|
||||||
|
warning.config.type.snbt.parser.infinity_not_allowed: "<yellow>Non-finite numbers are not allowed</yellow>"
|
||||||
|
warning.config.type.snbt.parser.incorrect: "<yellow>Expected literal <arg:0></yellow>"
|
||||||
warning.config.number.missing_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'type' argument for number argument.</yellow>"
|
warning.config.number.missing_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'type' argument for number argument.</yellow>"
|
||||||
warning.config.number.invalid_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid number argument type '<arg:2>'.</yellow>"
|
warning.config.number.invalid_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid number argument type '<arg:2>'.</yellow>"
|
||||||
warning.config.number.missing_argument: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the argument for 'number'.</yellow>"
|
warning.config.number.missing_argument: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the argument for 'number'.</yellow>"
|
||||||
|
|||||||
@@ -88,6 +88,32 @@ warning.config.type.vector3f: "<yellow>在文件 <arg:0> 发现问题 - 无法
|
|||||||
warning.config.type.vec3d: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为双精度浮点数三维向量类型 (选项 '<arg:3>')</yellow>"
|
warning.config.type.vec3d: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为双精度浮点数三维向量类型 (选项 '<arg:3>')</yellow>"
|
||||||
warning.config.type.map: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为映射类型 (选项 '<arg:3>')</yellow>"
|
warning.config.type.map: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为映射类型 (选项 '<arg:3>')</yellow>"
|
||||||
warning.config.type.snbt.invalid_syntax: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无效的 SNBT 语法 '<arg:2>'</yellow>"
|
warning.config.type.snbt.invalid_syntax: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无效的 SNBT 语法 '<arg:2>'</yellow>"
|
||||||
|
warning.config.type.snbt.invalid_syntax.parse_error: "<yellow><arg:0>, 位于第 <arg:1> 个字符: <arg:2></yellow>"
|
||||||
|
warning.config.type.snbt.invalid_syntax.here: "<yellow><--[此处]</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_string_uuid: "<yellow>应为表示有效 UUID 的字符串</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_number_or_boolean: "<yellow>应为数字或布尔型</yellow>"
|
||||||
|
warning.config.type.snbt.parser.trailing: "<yellow>多余的尾随数据</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected.compound: "<yellow>应为复合标签</yellow>"
|
||||||
|
warning.config.type.snbt.parser.number_parse_failure: "<yellow>解析数字失败: <arg:0></yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_hex_escape: "<yellow>字符字面量长度应为 <arg:0></yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_codepoint: "<yellow>无效的 Unicode 字符码位: <arg:0></yellow>"
|
||||||
|
warning.config.type.snbt.parser.no_such_operation: "<yellow>不存在的操作: <arg:0></yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_integer_type: "<yellow>应为整数</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_float_type: "<yellow>应为浮点数</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_non_negative_number: "<yellow>应为非负数</yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_character_name: "<yellow>无效的 Unicode 字符名称</yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_array_element_type: "<yellow>无效的数组元素类型</yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_unquoted_start: "<yellow>无引号字符串不能以数字 0-9、+ 或 - 开头</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_unquoted_string: "<yellow>应为有效的无引号字符串</yellow>"
|
||||||
|
warning.config.type.snbt.parser.invalid_string_contents: "<yellow>无效的字符串内容</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_binary_numeral: "<yellow>应为二进制数</yellow>"
|
||||||
|
warning.config.type.snbt.parser.underscore_not_allowed: "<yellow>数字的开头和结尾不允许使用下划线字符</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_decimal_numeral: "<yellow>应为十进制数</yellow>"
|
||||||
|
warning.config.type.snbt.parser.expected_hex_numeral: "<yellow>应为十六进制数</yellow>"
|
||||||
|
warning.config.type.snbt.parser.empty_key: "<yellow>键不能为空</yellow>"
|
||||||
|
warning.config.type.snbt.parser.leading_zero_not_allowed: "<yellow>十进制数不能以 0 开头</yellow>"
|
||||||
|
warning.config.type.snbt.parser.infinity_not_allowed: "<yellow>不允许使用非有限数的数值</yellow>"
|
||||||
|
warning.config.type.snbt.parser.incorrect: "<yellow>应为字面量 <arg:0></yellow>"
|
||||||
warning.config.number.missing_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 缺少数字类型所需的 'type' 参数</yellow>"
|
warning.config.number.missing_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 缺少数字类型所需的 'type' 参数</yellow>"
|
||||||
warning.config.number.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 使用了无效的数字类型 '<arg:2>'</yellow>"
|
warning.config.number.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 使用了无效的数字类型 '<arg:2>'</yellow>"
|
||||||
warning.config.number.missing_argument: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 缺少数字参数</yellow>"
|
warning.config.number.missing_argument: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 缺少数字参数</yellow>"
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package net.momirealms.craftengine.core.item.modifier;
|
package net.momirealms.craftengine.core.item.modifier;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import net.momirealms.craftengine.core.item.*;
|
import net.momirealms.craftengine.core.item.*;
|
||||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||||
|
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||||
import net.momirealms.craftengine.core.util.GsonHelper;
|
import net.momirealms.craftengine.core.util.GsonHelper;
|
||||||
import net.momirealms.craftengine.core.util.Key;
|
import net.momirealms.craftengine.core.util.Key;
|
||||||
import net.momirealms.craftengine.core.util.Pair;
|
import net.momirealms.craftengine.core.util.Pair;
|
||||||
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
|
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.TagParser;
|
||||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||||
import net.momirealms.sparrow.nbt.Tag;
|
import net.momirealms.sparrow.nbt.Tag;
|
||||||
|
|
||||||
@@ -41,7 +44,12 @@ public class ComponentsModifier<I> implements ItemDataModifier<I> {
|
|||||||
if (string.startsWith("(json) ")) {
|
if (string.startsWith("(json) ")) {
|
||||||
return CraftEngine.instance().platform().jsonToSparrowNBT(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) ")) {
|
} else if (string.startsWith("(snbt) ")) {
|
||||||
return CraftEngine.instance().platform().snbtToSparrowNBT(string.substring("(snbt) ".length()));
|
String snbt = string.substring("(snbt) ".length());
|
||||||
|
try {
|
||||||
|
return TagParser.parseCompoundFully(snbt);
|
||||||
|
} catch (CommandSyntaxException e) {
|
||||||
|
throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return CraftEngine.instance().platform().javaToSparrowNBT(value);
|
return CraftEngine.instance().platform().javaToSparrowNBT(value);
|
||||||
|
|||||||
@@ -10,12 +10,8 @@ public interface Platform {
|
|||||||
|
|
||||||
void dispatchCommand(String command);
|
void dispatchCommand(String command);
|
||||||
|
|
||||||
Object snbtToJava(String nbt);
|
|
||||||
|
|
||||||
Tag jsonToSparrowNBT(JsonElement json);
|
Tag jsonToSparrowNBT(JsonElement json);
|
||||||
|
|
||||||
Tag snbtToSparrowNBT(String nbt);
|
|
||||||
|
|
||||||
Tag javaToSparrowNBT(Object object);
|
Tag javaToSparrowNBT(Object object);
|
||||||
|
|
||||||
World getWorld(String name);
|
World getWorld(String name);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package net.momirealms.craftengine.core.plugin.config.template;
|
package net.momirealms.craftengine.core.plugin.config.template;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgument;
|
import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgument;
|
||||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||||
import net.momirealms.craftengine.core.util.SNBTReader;
|
import net.momirealms.craftengine.core.util.snbt.TagParser;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -67,8 +68,14 @@ public interface ArgumentString {
|
|||||||
} else {
|
} else {
|
||||||
this.placeholder = placeholderContent.substring(0, separatorIndex);
|
this.placeholder = placeholderContent.substring(0, separatorIndex);
|
||||||
String defaultValueString = placeholderContent.substring(separatorIndex + 2);
|
String defaultValueString = placeholderContent.substring(separatorIndex + 2);
|
||||||
|
Object parsed;
|
||||||
try {
|
try {
|
||||||
this.defaultValue = ((TemplateManagerImpl) TemplateManager.INSTANCE).preprocessUnknownValue(new SNBTReader(defaultValueString).deserializeAsJava());
|
parsed = TagParser.parseObjectFully(defaultValueString);
|
||||||
|
} catch (CommandSyntaxException e) {
|
||||||
|
throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e.getMessage());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.defaultValue = ((TemplateManagerImpl) TemplateManager.INSTANCE).preprocessUnknownValue(parsed);
|
||||||
} catch (LocalizedResourceConfigException e) {
|
} catch (LocalizedResourceConfigException e) {
|
||||||
e.appendTailArgument(this.placeholder);
|
e.appendTailArgument(this.placeholder);
|
||||||
throw e;
|
throw e;
|
||||||
|
|||||||
@@ -415,4 +415,8 @@ public class MiscUtils {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int growByHalf(int value, int minValue) {
|
||||||
|
return (int) Math.max(Math.min((long) value + (value >> 1), 2147483639L), minValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,289 +0,0 @@
|
|||||||
package net.momirealms.craftengine.core.util;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public final class SNBTReader extends DefaultStringReader {
|
|
||||||
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 SINGLE_QUOTES = '\'';
|
|
||||||
private static final char DOUBLE_QUOTES = '"';
|
|
||||||
private static final char KEY_VALUE_SEPARATOR = ':';
|
|
||||||
private static final char ELEMENT_SEPARATOR = ',';
|
|
||||||
|
|
||||||
private static final char ARRAY_DELIMITER = ';';
|
|
||||||
private static final char BYTE_ARRAY = 'b';
|
|
||||||
private static final char INT_ARRAY = 'i';
|
|
||||||
private static final char LONG_ARRAY = 'l';
|
|
||||||
|
|
||||||
public SNBTReader(String content) {
|
|
||||||
super(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object deserializeAsJava() {
|
|
||||||
Object result = this.parseValue();
|
|
||||||
this.skipWhitespace();
|
|
||||||
if (getCursor() != getTotalLength())
|
|
||||||
throw new IllegalArgumentException("Extra content at end: " + substring(getCursor(), getTotalLength()));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始解析, 步进字符.
|
|
||||||
private Object parseValue() {
|
|
||||||
skipWhitespace();
|
|
||||||
return switch (peek()) {
|
|
||||||
case COMPOUND_START -> parseCompound();
|
|
||||||
case LIST_START -> parseList();
|
|
||||||
case DOUBLE_QUOTES -> {
|
|
||||||
skip();
|
|
||||||
yield readStringUntil(DOUBLE_QUOTES);
|
|
||||||
}
|
|
||||||
case SINGLE_QUOTES -> {
|
|
||||||
skip();
|
|
||||||
yield readStringUntil(SINGLE_QUOTES);
|
|
||||||
}
|
|
||||||
default -> parsePrimitive();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析包小肠 {}
|
|
||||||
private Map<String, Object> parseCompound() {
|
|
||||||
skip(); // 跳过 '{'
|
|
||||||
skipWhitespace();
|
|
||||||
|
|
||||||
Map<String, Object> compoundMap = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
if (canRead() && peek() != COMPOUND_END) {
|
|
||||||
do {
|
|
||||||
String key = parseKey();
|
|
||||||
if (!canRead() || peek() != KEY_VALUE_SEPARATOR) {
|
|
||||||
throw new IllegalArgumentException("Expected ':' at position " + getCursor());
|
|
||||||
}
|
|
||||||
skip(); // 跳过 ':'
|
|
||||||
Object value = parseValue();
|
|
||||||
compoundMap.put(key, value);
|
|
||||||
skipWhitespace();
|
|
||||||
} while (canRead() && peek() == ELEMENT_SEPARATOR && ++super.cursor > 0 /* 跳过 ',' */);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canRead() || peek() != COMPOUND_END) {
|
|
||||||
throw new IllegalArgumentException("Expected '}' at position " + getCursor());
|
|
||||||
}
|
|
||||||
skip(); // 跳过 '}'
|
|
||||||
return compoundMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析列表值 [1, 2, 3]
|
|
||||||
private Object parseList() {
|
|
||||||
skip(); // 跳过 '['
|
|
||||||
skipWhitespace();
|
|
||||||
|
|
||||||
// 检查接下来的2个非空格字符, 确认是否要走数组解析.
|
|
||||||
if (canRead()) {
|
|
||||||
setMarker(cursor); // 记录指针, 尝试解析数组.
|
|
||||||
char typeChar = Character.toLowerCase(peek());
|
|
||||||
if (typeChar == BYTE_ARRAY || typeChar == INT_ARRAY || typeChar == LONG_ARRAY) {
|
|
||||||
skip();
|
|
||||||
skipWhitespace();
|
|
||||||
if (canRead() && peek() == ARRAY_DELIMITER) { // 下一个必须是 ';'
|
|
||||||
skip();
|
|
||||||
switch (typeChar) { // 解析并返回数组喵
|
|
||||||
case BYTE_ARRAY -> {
|
|
||||||
return parseArray(list -> {
|
|
||||||
byte[] bytes = new byte[list.size()];
|
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
|
||||||
bytes[i] = list.get(i).byteValue();
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case INT_ARRAY -> {
|
|
||||||
return parseArray(list -> {
|
|
||||||
int[] ints = new int[list.size()];
|
|
||||||
for (int i = 0; i < ints.length; i++) {
|
|
||||||
ints[i] = list.get(i).intValue();
|
|
||||||
}
|
|
||||||
return ints;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case LONG_ARRAY -> {
|
|
||||||
return parseArray(list -> {
|
|
||||||
long[] longs = new long[list.size()];
|
|
||||||
for (int i = 0; i < longs.length; i++) {
|
|
||||||
longs[i] = list.get(i).longValue();
|
|
||||||
}
|
|
||||||
return longs;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
restore(); // 复原指针.
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Object> elementList = new ArrayList<>();
|
|
||||||
|
|
||||||
if (canRead() && peek() != LIST_END) {
|
|
||||||
do {
|
|
||||||
elementList.add(parseValue());
|
|
||||||
skipWhitespace();
|
|
||||||
} while (canRead() && peek() == ELEMENT_SEPARATOR && ++super.cursor > 0 /* 跳过 ',' */);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canRead() || peek() != LIST_END) {
|
|
||||||
throw new IllegalArgumentException("Expected ']' at position " + getCursor());
|
|
||||||
}
|
|
||||||
skip(); // 跳过 ']'
|
|
||||||
return elementList;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析数组 [I; 11, 41, 54]
|
|
||||||
// ArrayType -> B, I, L.
|
|
||||||
private Object parseArray(Function<List<Number>, Object> convertor) {
|
|
||||||
skipWhitespace();
|
|
||||||
// 用来暂存解析出的数字
|
|
||||||
List<Number> elements = new ArrayList<>();
|
|
||||||
if (canRead() && peek() != LIST_END) {
|
|
||||||
do {
|
|
||||||
Object element = parseValue();
|
|
||||||
|
|
||||||
// 1.21.6的SNBT原版是支持 {key:[B;1,2b,0xFF]} 这种奇葩写法的, 越界部分会被自动舍弃, 如0xff的byte值为-1.
|
|
||||||
// 如果需要和原版对齐, 那么只需要判断是否是数字就行了.
|
|
||||||
// if (!(element instanceof Number number))
|
|
||||||
// throw new IllegalArgumentException("Error element type at pos " + getCursor());
|
|
||||||
if (!(element instanceof Number number))
|
|
||||||
throw new IllegalArgumentException("Error parsing number at pos " + getCursor());
|
|
||||||
|
|
||||||
elements.add(number); // 校验通过后加入
|
|
||||||
skipWhitespace();
|
|
||||||
} while (canRead() && peek() == ELEMENT_SEPARATOR && ++cursor > 0 /* 跳过 ',' */);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canRead() || peek() != LIST_END)
|
|
||||||
throw new IllegalArgumentException("Expected ']' at position " + getCursor());
|
|
||||||
skip(); // 跳过 ']'
|
|
||||||
return convertor.apply(elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析Key值
|
|
||||||
private String parseKey() {
|
|
||||||
skipWhitespace();
|
|
||||||
if (!canRead()) {
|
|
||||||
throw new IllegalArgumentException("Unterminated key at " + getCursor());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有双引号就委托给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)) skip(); else break;
|
|
||||||
}
|
|
||||||
|
|
||||||
String key = substring(start, getCursor());
|
|
||||||
skipWhitespace(); // 跳过 key 后面的空格.
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析原生值
|
|
||||||
private Object parsePrimitive() {
|
|
||||||
// 先解析获取值的长度
|
|
||||||
int tokenStart = getCursor();
|
|
||||||
int lastWhitespace = 0; // 记录值末尾的空格数量,{a:炒鸡 大保健} 和 {a: 炒鸡 大保健 } 都应解析成 "炒鸡 大保健".
|
|
||||||
boolean contentHasWhitespace = false; // 记录值中有没有空格.
|
|
||||||
while (canRead()) {
|
|
||||||
char c = peek();
|
|
||||||
if (c == ',' || c == ']' || c == '}') break;
|
|
||||||
skip();
|
|
||||||
if (c == ' ') {
|
|
||||||
lastWhitespace++; // 遇到空格先增加值, 代表值尾部空格数量.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (lastWhitespace > 0) {
|
|
||||||
lastWhitespace = 0; // 遇到正常字符时清空记录的尾部空格数.
|
|
||||||
contentHasWhitespace = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int tokenLength = getCursor() - tokenStart - lastWhitespace; // 计算值长度需要再减去尾部空格.
|
|
||||||
if (tokenLength == 0) return null; // 如果值长度为0则返回null.
|
|
||||||
if (contentHasWhitespace) return substring(tokenStart, tokenStart + tokenLength); // 如果值的中间有空格, 一定是字符串, 可直接返回.
|
|
||||||
|
|
||||||
// 布尔值检查
|
|
||||||
if (tokenLength == 4) {
|
|
||||||
if (matchesAt(tokenStart, "true")) return Boolean.TRUE;
|
|
||||||
if (matchesAt(tokenStart, "null")) return null; // 支持 {key:null}.
|
|
||||||
} else if (tokenLength == 5) {
|
|
||||||
if (matchesAt(tokenStart, "false")) return Boolean.FALSE;
|
|
||||||
}
|
|
||||||
if (tokenLength > 1) {
|
|
||||||
// 至少有1个字符,给了后缀的可能性
|
|
||||||
char lastChar = charAt(tokenStart + tokenLength - 1);
|
|
||||||
try {
|
|
||||||
switch (lastChar) {
|
|
||||||
case 'b', 'B' -> {
|
|
||||||
return Byte.parseByte(substring(tokenStart, tokenStart + tokenLength - 1));
|
|
||||||
}
|
|
||||||
case 's', 'S' -> {
|
|
||||||
return Short.parseShort(substring(tokenStart, tokenStart + tokenLength - 1));
|
|
||||||
}
|
|
||||||
case 'l', 'L' -> {
|
|
||||||
return Long.parseLong(substring(tokenStart, tokenStart + tokenLength - 1));
|
|
||||||
}
|
|
||||||
case 'f', 'F' -> {
|
|
||||||
return Float.parseFloat(substring(tokenStart, tokenStart + tokenLength));
|
|
||||||
}
|
|
||||||
case 'd', 'D' -> {
|
|
||||||
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 substring(tokenStart, tokenStart + tokenLength);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
char onlyChar = charAt(tokenStart);
|
|
||||||
if (isNumber(onlyChar)) {
|
|
||||||
return onlyChar - '0';
|
|
||||||
} else {
|
|
||||||
return String.valueOf(onlyChar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工具函数: 快速检查布尔值字符串匹配, 忽略大小写.
|
|
||||||
private boolean matchesAt(int start, String target) {
|
|
||||||
for (int i = 0; i < target.length(); i++) {
|
|
||||||
char c1 = charAt(start + i);
|
|
||||||
char c2 = target.charAt(i);
|
|
||||||
if (c1 != c2 && c1 != (c2 ^ 32)) return false; // 忽略大小写比较
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,865 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableMap.Builder;
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
||||||
|
import com.mojang.serialization.DynamicOps;
|
||||||
|
import com.mojang.serialization.JavaOps;
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteList;
|
||||||
|
import it.unimi.dsi.fastutil.chars.CharList;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.*;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.Dictionary;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.grammar.*;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.LongStream;
|
||||||
|
|
||||||
|
public class SnbtGrammar {
|
||||||
|
private static final DynamicCommandExceptionType ERROR_NUMBER_PARSE_FAILURE = new LocalizedDynamicCommandExceptionType(
|
||||||
|
number -> new LocalizedMessage("warning.config.type.snbt.parser.number_parse_failure", String.valueOf(number))
|
||||||
|
);
|
||||||
|
static final DynamicCommandExceptionType ERROR_EXPECTED_HEX_ESCAPE = new LocalizedDynamicCommandExceptionType(
|
||||||
|
length -> new LocalizedMessage("warning.config.type.snbt.parser.expected_hex_escape", String.valueOf(length))
|
||||||
|
);
|
||||||
|
private static final DynamicCommandExceptionType ERROR_INVALID_CODEPOINT = new LocalizedDynamicCommandExceptionType(
|
||||||
|
codepoint -> new LocalizedMessage("warning.config.type.snbt.parser.invalid_codepoint", String.valueOf(codepoint))
|
||||||
|
);
|
||||||
|
private static final DynamicCommandExceptionType ERROR_NO_SUCH_OPERATION = new LocalizedDynamicCommandExceptionType(
|
||||||
|
operation -> new LocalizedMessage("warning.config.type.snbt.parser.no_such_operation", String.valueOf(operation))
|
||||||
|
);
|
||||||
|
static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_INTEGER_TYPE = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_integer_type"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_FLOAT_TYPE = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_float_type"))
|
||||||
|
);
|
||||||
|
static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_NON_NEGATIVE_NUMBER = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_non_negative_number"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_INVALID_CHARACTER_NAME = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.invalid_character_name"))
|
||||||
|
);
|
||||||
|
static final DelayedException<CommandSyntaxException> ERROR_INVALID_ARRAY_ELEMENT_TYPE = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.invalid_array_element_type"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_INVALID_UNQUOTED_START = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.invalid_unquoted_start"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_UNQUOTED_STRING = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_unquoted_string"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_INVALID_STRING_CONTENTS = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.invalid_string_contents"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_BINARY_NUMERAL = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_binary_numeral"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_UNDERSCORE_NOT_ALLOWED = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.underscore_not_allowed"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_DECIMAL_NUMERAL = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_decimal_numeral"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_HEX_NUMERAL = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("Expected a hexadecimal number"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_EMPTY_KEY = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.empty_key"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_LEADING_ZERO_NOT_ALLOWED = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.leading_zero_not_allowed"))
|
||||||
|
);
|
||||||
|
private static final DelayedException<CommandSyntaxException> ERROR_INFINITY_NOT_ALLOWED = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.infinity_not_allowed"))
|
||||||
|
);
|
||||||
|
private static final NumberRunParseRule BINARY_NUMERAL = new NumberRunParseRule(ERROR_EXPECTED_BINARY_NUMERAL, ERROR_UNDERSCORE_NOT_ALLOWED) {
|
||||||
|
@Override
|
||||||
|
protected boolean isAccepted(char c) {
|
||||||
|
return switch (c) {
|
||||||
|
case '0', '1', '_' -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final NumberRunParseRule DECIMAL_NUMERAL = new NumberRunParseRule(ERROR_EXPECTED_DECIMAL_NUMERAL, ERROR_UNDERSCORE_NOT_ALLOWED) {
|
||||||
|
@Override
|
||||||
|
protected boolean isAccepted(char c) {
|
||||||
|
return switch (c) {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_' -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final NumberRunParseRule HEX_NUMERAL = new NumberRunParseRule(ERROR_EXPECTED_HEX_NUMERAL, ERROR_UNDERSCORE_NOT_ALLOWED) {
|
||||||
|
@Override
|
||||||
|
protected boolean isAccepted(char c) {
|
||||||
|
return switch (c) {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', '_', 'a', 'b', 'c', 'd', 'e', 'f' -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final GreedyPredicateParseRule PLAIN_STRING_CHUNK = new GreedyPredicateParseRule(1, ERROR_INVALID_STRING_CONTENTS) {
|
||||||
|
@Override
|
||||||
|
protected boolean isAccepted(char c) {
|
||||||
|
return switch (c) {
|
||||||
|
case '"', '\'', '\\' -> false;
|
||||||
|
default -> true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final StringReaderTerms.TerminalCharacters NUMBER_LOOKEAHEAD = new StringReaderTerms.TerminalCharacters(CharList.of()) {
|
||||||
|
@Override
|
||||||
|
protected boolean isAccepted(char c) {
|
||||||
|
return canStartNumber(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Pattern UNICODE_NAME = Pattern.compile("[-a-zA-Z0-9 ]+");
|
||||||
|
|
||||||
|
static DelayedException<CommandSyntaxException> createNumberParseError(NumberFormatException numberFormatException) {
|
||||||
|
return DelayedException.create(ERROR_NUMBER_PARSE_FAILURE, numberFormatException.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAllowedToStartUnquotedString(char c) {
|
||||||
|
return !canStartNumber(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean canStartNumber(char c) {
|
||||||
|
return switch (c) {
|
||||||
|
case '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean needsUnderscoreRemoval(String text) {
|
||||||
|
return text.indexOf(95) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void cleanAndAppend(StringBuilder stringBuilder, String text) {
|
||||||
|
cleanAndAppend(stringBuilder, text, needsUnderscoreRemoval(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanAndAppend(StringBuilder stringBuilder, String text, boolean removeUnderscores) {
|
||||||
|
if (removeUnderscores) {
|
||||||
|
for (char c : text.toCharArray()) {
|
||||||
|
if (c != '_') {
|
||||||
|
stringBuilder.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stringBuilder.append(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static short parseUnsignedShort(String text, int radix) {
|
||||||
|
int i = Integer.parseInt(text, radix);
|
||||||
|
if (i >> 16 == 0) {
|
||||||
|
return (short)i;
|
||||||
|
}
|
||||||
|
throw new NumberFormatException("out of range: " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T> T createFloat(
|
||||||
|
DynamicOps<T> ops,
|
||||||
|
Sign sign,
|
||||||
|
@Nullable String wholePart,
|
||||||
|
@Nullable String fractionPart,
|
||||||
|
@Nullable Signed<String> exponentPart,
|
||||||
|
@Nullable TypeSuffix suffix,
|
||||||
|
ParseState<?> parseState
|
||||||
|
) {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
sign.append(stringBuilder);
|
||||||
|
if (wholePart != null) {
|
||||||
|
cleanAndAppend(stringBuilder, wholePart);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fractionPart != null) {
|
||||||
|
stringBuilder.append('.');
|
||||||
|
cleanAndAppend(stringBuilder, fractionPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exponentPart != null) {
|
||||||
|
stringBuilder.append('e');
|
||||||
|
exponentPart.sign().append(stringBuilder);
|
||||||
|
cleanAndAppend(stringBuilder, exponentPart.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String string = stringBuilder.toString();
|
||||||
|
|
||||||
|
return switch (suffix) {
|
||||||
|
case null -> convertDouble(ops, parseState, string);
|
||||||
|
case FLOAT -> convertFloat(ops, parseState, string);
|
||||||
|
case DOUBLE -> convertDouble(ops, parseState, string);
|
||||||
|
default -> {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_EXPECTED_FLOAT_TYPE);
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (NumberFormatException var11) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), createNumberParseError(var11));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T> T convertFloat(DynamicOps<T> ops, ParseState<?> parseState, String value) {
|
||||||
|
float f = Float.parseFloat(value);
|
||||||
|
if (!Float.isFinite(f)) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_INFINITY_NOT_ALLOWED);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ops.createFloat(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T> T convertDouble(DynamicOps<T> ops, ParseState<?> parseState, String value) {
|
||||||
|
double d = Double.parseDouble(value);
|
||||||
|
if (!Double.isFinite(d)) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_INFINITY_NOT_ALLOWED);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ops.createDouble(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinList(List<String> list) {
|
||||||
|
return switch (list.size()) {
|
||||||
|
case 0 -> "";
|
||||||
|
case 1 -> list.getFirst();
|
||||||
|
default -> String.join("", list);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> Grammar<T> createParser(DynamicOps<T> ops) {
|
||||||
|
T object = ops.createBoolean(true);
|
||||||
|
T object1 = ops.createBoolean(false);
|
||||||
|
T object2 = ops.emptyMap();
|
||||||
|
T object3 = ops.emptyList();
|
||||||
|
Dictionary<StringReader> dictionary = new Dictionary<>();
|
||||||
|
Atom<Sign> atom = Atom.of("sign");
|
||||||
|
dictionary.put(
|
||||||
|
atom,
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(StringReaderTerms.character('+'), Term.marker(atom, Sign.PLUS)),
|
||||||
|
Term.sequence(StringReaderTerms.character('-'), Term.marker(atom, Sign.MINUS))
|
||||||
|
),
|
||||||
|
scope -> scope.getOrThrow(atom)
|
||||||
|
);
|
||||||
|
Atom<IntegerSuffix> atom1 = Atom.of("integer_suffix");
|
||||||
|
dictionary.put(
|
||||||
|
atom1,
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('u', 'U'),
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('b', 'B'),
|
||||||
|
Term.marker(atom1, new IntegerSuffix(SignedPrefix.UNSIGNED, TypeSuffix.BYTE))
|
||||||
|
),
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('s', 'S'),
|
||||||
|
Term.marker(atom1, new IntegerSuffix(SignedPrefix.UNSIGNED, TypeSuffix.SHORT))
|
||||||
|
),
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('i', 'I'),
|
||||||
|
Term.marker(atom1, new IntegerSuffix(SignedPrefix.UNSIGNED, TypeSuffix.INT))
|
||||||
|
),
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('l', 'L'),
|
||||||
|
Term.marker(atom1, new IntegerSuffix(SignedPrefix.UNSIGNED, TypeSuffix.LONG))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('s', 'S'),
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('b', 'B'),
|
||||||
|
Term.marker(atom1, new IntegerSuffix(SignedPrefix.SIGNED, TypeSuffix.BYTE))
|
||||||
|
),
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('s', 'S'),
|
||||||
|
Term.marker(atom1, new IntegerSuffix(SignedPrefix.SIGNED, TypeSuffix.SHORT))
|
||||||
|
),
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('i', 'I'),
|
||||||
|
Term.marker(atom1, new IntegerSuffix(SignedPrefix.SIGNED, TypeSuffix.INT))
|
||||||
|
),
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.characters('l', 'L'),
|
||||||
|
Term.marker(atom1, new IntegerSuffix(SignedPrefix.SIGNED, TypeSuffix.LONG))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Term.sequence(StringReaderTerms.characters('b', 'B'), Term.marker(atom1, new IntegerSuffix(null, TypeSuffix.BYTE))),
|
||||||
|
Term.sequence(StringReaderTerms.characters('s', 'S'), Term.marker(atom1, new IntegerSuffix(null, TypeSuffix.SHORT))),
|
||||||
|
Term.sequence(StringReaderTerms.characters('i', 'I'), Term.marker(atom1, new IntegerSuffix(null, TypeSuffix.INT))),
|
||||||
|
Term.sequence(StringReaderTerms.characters('l', 'L'), Term.marker(atom1, new IntegerSuffix(null, TypeSuffix.LONG)))
|
||||||
|
),
|
||||||
|
scope -> scope.getOrThrow(atom1)
|
||||||
|
);
|
||||||
|
Atom<String> atom2 = Atom.of("binary_numeral");
|
||||||
|
dictionary.put(atom2, BINARY_NUMERAL);
|
||||||
|
Atom<String> atom3 = Atom.of("decimal_numeral");
|
||||||
|
dictionary.put(atom3, DECIMAL_NUMERAL);
|
||||||
|
Atom<String> atom4 = Atom.of("hex_numeral");
|
||||||
|
dictionary.put(atom4, HEX_NUMERAL);
|
||||||
|
Atom<IntegerLiteral> atom5 = Atom.of("integer_literal");
|
||||||
|
NamedRule<StringReader, IntegerLiteral> namedRule = dictionary.put(
|
||||||
|
atom5,
|
||||||
|
Term.sequence(
|
||||||
|
Term.optional(dictionary.named(atom)),
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.character('0'),
|
||||||
|
Term.cut(),
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(StringReaderTerms.characters('x', 'X'), Term.cut(), dictionary.named(atom4)),
|
||||||
|
Term.sequence(StringReaderTerms.characters('b', 'B'), dictionary.named(atom2)),
|
||||||
|
Term.sequence(dictionary.named(atom3), Term.cut(), Term.fail(ERROR_LEADING_ZERO_NOT_ALLOWED)),
|
||||||
|
Term.marker(atom3, "0")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
dictionary.named(atom3)
|
||||||
|
),
|
||||||
|
Term.optional(dictionary.named(atom1))
|
||||||
|
),
|
||||||
|
scope -> {
|
||||||
|
IntegerSuffix integerSuffix = scope.getOrDefault(atom1, IntegerSuffix.EMPTY);
|
||||||
|
Sign sign = scope.getOrDefault(atom, Sign.PLUS);
|
||||||
|
String string = scope.get(atom3);
|
||||||
|
if (string != null) {
|
||||||
|
return new IntegerLiteral(sign, Base.DECIMAL, string, integerSuffix);
|
||||||
|
}
|
||||||
|
String string1 = scope.get(atom4);
|
||||||
|
if (string1 != null) {
|
||||||
|
return new IntegerLiteral(sign, Base.HEX, string1, integerSuffix);
|
||||||
|
}
|
||||||
|
String string2 = scope.getOrThrow(atom2);
|
||||||
|
return new IntegerLiteral(sign, Base.BINARY, string2, integerSuffix);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Atom<TypeSuffix> atom6 = Atom.of("float_type_suffix");
|
||||||
|
dictionary.put(
|
||||||
|
atom6,
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(StringReaderTerms.characters('f', 'F'), Term.marker(atom6, TypeSuffix.FLOAT)),
|
||||||
|
Term.sequence(StringReaderTerms.characters('d', 'D'), Term.marker(atom6, TypeSuffix.DOUBLE))
|
||||||
|
),
|
||||||
|
scope -> scope.getOrThrow(atom6)
|
||||||
|
);
|
||||||
|
Atom<Signed<String>> atom7 = Atom.of("float_exponent_part");
|
||||||
|
dictionary.put(
|
||||||
|
atom7,
|
||||||
|
Term.sequence(StringReaderTerms.characters('e', 'E'), Term.optional(dictionary.named(atom)), dictionary.named(atom3)),
|
||||||
|
scope -> new Signed<>(scope.getOrDefault(atom, Sign.PLUS), scope.getOrThrow(atom3))
|
||||||
|
);
|
||||||
|
Atom<String> atom8 = Atom.of("float_whole_part");
|
||||||
|
Atom<String> atom9 = Atom.of("float_fraction_part");
|
||||||
|
Atom<T> atom10 = Atom.of("float_literal");
|
||||||
|
dictionary.putComplex(
|
||||||
|
atom10,
|
||||||
|
Term.sequence(
|
||||||
|
Term.optional(dictionary.named(atom)),
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(
|
||||||
|
dictionary.namedWithAlias(atom3, atom8),
|
||||||
|
StringReaderTerms.character('.'),
|
||||||
|
Term.cut(),
|
||||||
|
Term.optional(dictionary.namedWithAlias(atom3, atom9)),
|
||||||
|
Term.optional(dictionary.named(atom7)),
|
||||||
|
Term.optional(dictionary.named(atom6))
|
||||||
|
),
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.character('.'),
|
||||||
|
Term.cut(),
|
||||||
|
dictionary.namedWithAlias(atom3, atom9),
|
||||||
|
Term.optional(dictionary.named(atom7)),
|
||||||
|
Term.optional(dictionary.named(atom6))
|
||||||
|
),
|
||||||
|
Term.sequence(dictionary.namedWithAlias(atom3, atom8), dictionary.named(atom7), Term.cut(), Term.optional(dictionary.named(atom6))),
|
||||||
|
Term.sequence(dictionary.namedWithAlias(atom3, atom8), Term.optional(dictionary.named(atom7)), dictionary.named(atom6))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
parseState -> {
|
||||||
|
Scope scope = parseState.scope();
|
||||||
|
Sign sign = scope.getOrDefault(atom, Sign.PLUS);
|
||||||
|
String string = scope.get(atom8);
|
||||||
|
String string1 = scope.get(atom9);
|
||||||
|
Signed<String> signed = scope.get(atom7);
|
||||||
|
TypeSuffix typeSuffix = scope.get(atom6);
|
||||||
|
return createFloat(ops, sign, string, string1, signed, typeSuffix, parseState);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Atom<String> atom11 = Atom.of("string_hex_2");
|
||||||
|
dictionary.put(atom11, new SimpleHexLiteralParseRule(2));
|
||||||
|
Atom<String> atom12 = Atom.of("string_hex_4");
|
||||||
|
dictionary.put(atom12, new SimpleHexLiteralParseRule(4));
|
||||||
|
Atom<String> atom13 = Atom.of("string_hex_8");
|
||||||
|
dictionary.put(atom13, new SimpleHexLiteralParseRule(8));
|
||||||
|
Atom<String> atom14 = Atom.of("string_unicode_name");
|
||||||
|
dictionary.put(atom14, new GreedyPatternParseRule(UNICODE_NAME, ERROR_INVALID_CHARACTER_NAME));
|
||||||
|
Atom<String> atom15 = Atom.of("string_escape_sequence");
|
||||||
|
dictionary.putComplex(
|
||||||
|
atom15,
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(StringReaderTerms.character('b'), Term.marker(atom15, "\b")),
|
||||||
|
Term.sequence(StringReaderTerms.character('s'), Term.marker(atom15, " ")),
|
||||||
|
Term.sequence(StringReaderTerms.character('t'), Term.marker(atom15, "\t")),
|
||||||
|
Term.sequence(StringReaderTerms.character('n'), Term.marker(atom15, "\n")),
|
||||||
|
Term.sequence(StringReaderTerms.character('f'), Term.marker(atom15, "\f")),
|
||||||
|
Term.sequence(StringReaderTerms.character('r'), Term.marker(atom15, "\r")),
|
||||||
|
Term.sequence(StringReaderTerms.character('\\'), Term.marker(atom15, "\\")),
|
||||||
|
Term.sequence(StringReaderTerms.character('\''), Term.marker(atom15, "'")),
|
||||||
|
Term.sequence(StringReaderTerms.character('"'), Term.marker(atom15, "\"")),
|
||||||
|
Term.sequence(StringReaderTerms.character('x'), dictionary.named(atom11)),
|
||||||
|
Term.sequence(StringReaderTerms.character('u'), dictionary.named(atom12)),
|
||||||
|
Term.sequence(StringReaderTerms.character('U'), dictionary.named(atom13)),
|
||||||
|
Term.sequence(StringReaderTerms.character('N'), StringReaderTerms.character('{'), dictionary.named(atom14), StringReaderTerms.character('}'))
|
||||||
|
),
|
||||||
|
parseState -> {
|
||||||
|
Scope scope = parseState.scope();
|
||||||
|
String string = scope.getAny(atom15);
|
||||||
|
if (string != null) {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
String string1 = scope.getAny(atom11, atom12, atom13);
|
||||||
|
if (string1 != null) {
|
||||||
|
int i = HexFormat.fromHexDigits(string1);
|
||||||
|
if (!Character.isValidCodePoint(i)) {
|
||||||
|
parseState.errorCollector()
|
||||||
|
.store(parseState.mark(), DelayedException.create(ERROR_INVALID_CODEPOINT, String.format(Locale.ROOT, "U+%08X", i)));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Character.toString(i);
|
||||||
|
}
|
||||||
|
String string2 = scope.getOrThrow(atom14);
|
||||||
|
|
||||||
|
int i1;
|
||||||
|
try {
|
||||||
|
i1 = Character.codePointOf(string2);
|
||||||
|
} catch (IllegalArgumentException var12x) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_INVALID_CHARACTER_NAME);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Character.toString(i1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Atom<String> atom16 = Atom.of("string_plain_contents");
|
||||||
|
dictionary.put(atom16, PLAIN_STRING_CHUNK);
|
||||||
|
Atom<List<String>> atom17 = Atom.of("string_chunks");
|
||||||
|
Atom<String> atom18 = Atom.of("string_contents");
|
||||||
|
Atom<String> atom19 = Atom.of("single_quoted_string_chunk");
|
||||||
|
NamedRule<StringReader, String> namedRule1 = dictionary.put(
|
||||||
|
atom19,
|
||||||
|
Term.alternative(
|
||||||
|
dictionary.namedWithAlias(atom16, atom18),
|
||||||
|
Term.sequence(StringReaderTerms.character('\\'), dictionary.namedWithAlias(atom15, atom18)),
|
||||||
|
Term.sequence(StringReaderTerms.character('"'), Term.marker(atom18, "\""))
|
||||||
|
),
|
||||||
|
scope -> scope.getOrThrow(atom18)
|
||||||
|
);
|
||||||
|
Atom<String> atom20 = Atom.of("single_quoted_string_contents");
|
||||||
|
dictionary.put(atom20, Term.repeated(namedRule1, atom17), scope -> joinList(scope.getOrThrow(atom17)));
|
||||||
|
Atom<String> atom21 = Atom.of("double_quoted_string_chunk");
|
||||||
|
NamedRule<StringReader, String> namedRule2 = dictionary.put(
|
||||||
|
atom21,
|
||||||
|
Term.alternative(
|
||||||
|
dictionary.namedWithAlias(atom16, atom18),
|
||||||
|
Term.sequence(StringReaderTerms.character('\\'), dictionary.namedWithAlias(atom15, atom18)),
|
||||||
|
Term.sequence(StringReaderTerms.character('\''), Term.marker(atom18, "'"))
|
||||||
|
),
|
||||||
|
scope -> scope.getOrThrow(atom18)
|
||||||
|
);
|
||||||
|
Atom<String> atom22 = Atom.of("double_quoted_string_contents");
|
||||||
|
dictionary.put(atom22, Term.repeated(namedRule2, atom17), scope -> joinList(scope.getOrThrow(atom17)));
|
||||||
|
Atom<String> atom23 = Atom.of("quoted_string_literal");
|
||||||
|
dictionary.put(
|
||||||
|
atom23,
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.character('"'), Term.cut(), Term.optional(dictionary.namedWithAlias(atom22, atom18)), StringReaderTerms.character('"')
|
||||||
|
),
|
||||||
|
Term.sequence(StringReaderTerms.character('\''), Term.optional(dictionary.namedWithAlias(atom20, atom18)), StringReaderTerms.character('\''))
|
||||||
|
),
|
||||||
|
scope -> scope.getOrThrow(atom18)
|
||||||
|
);
|
||||||
|
Atom<String> atom24 = Atom.of("unquoted_string");
|
||||||
|
dictionary.put(atom24, new UnquotedStringParseRule(1, ERROR_EXPECTED_UNQUOTED_STRING));
|
||||||
|
Atom<T> atom25 = Atom.of("literal");
|
||||||
|
Atom<List<T>> atom26 = Atom.of("arguments");
|
||||||
|
dictionary.put(
|
||||||
|
atom26, Term.repeatedWithTrailingSeparator(dictionary.forward(atom25), atom26, StringReaderTerms.character(TagParser.ELEMENT_SEPARATOR)), scope -> scope.getOrThrow(atom26)
|
||||||
|
);
|
||||||
|
Atom<T> atom27 = Atom.of("unquoted_string_or_builtin");
|
||||||
|
dictionary.putComplex(
|
||||||
|
atom27,
|
||||||
|
Term.sequence(
|
||||||
|
dictionary.named(atom24),
|
||||||
|
Term.optional(Term.sequence(StringReaderTerms.character('('), dictionary.named(atom26), StringReaderTerms.character(')')))
|
||||||
|
),
|
||||||
|
parseState -> {
|
||||||
|
Scope scope = parseState.scope();
|
||||||
|
String string = scope.getOrThrow(atom24);
|
||||||
|
if (!string.isEmpty() && isAllowedToStartUnquotedString(string.charAt(0))) {
|
||||||
|
List<T> list = scope.get(atom26);
|
||||||
|
if (list != null) {
|
||||||
|
SnbtOperations.BuiltinKey builtinKey = new SnbtOperations.BuiltinKey(string, list.size());
|
||||||
|
SnbtOperations.BuiltinOperation builtinOperation = SnbtOperations.BUILTIN_OPERATIONS.get(builtinKey);
|
||||||
|
if (builtinOperation != null) {
|
||||||
|
return builtinOperation.run(ops, list, parseState);
|
||||||
|
}
|
||||||
|
parseState.errorCollector().store(parseState.mark(), DelayedException.create(ERROR_NO_SUCH_OPERATION, builtinKey.toString()));
|
||||||
|
return null;
|
||||||
|
} else if (string.equalsIgnoreCase("true")) {
|
||||||
|
return object;
|
||||||
|
} else if (string.equalsIgnoreCase("false")) {
|
||||||
|
return object1;
|
||||||
|
} else if (string.equalsIgnoreCase("null")) {
|
||||||
|
return Objects.requireNonNullElseGet(ops.empty(), () -> {
|
||||||
|
T nullString = ops.createString("null");
|
||||||
|
if ("null".equals(nullString)) { // 确定是 Java 类型的
|
||||||
|
return (T) CachedParseState.JAVA_NULL_VALUE_MARKER;
|
||||||
|
}
|
||||||
|
return nullString;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ops.createString(string);
|
||||||
|
}
|
||||||
|
parseState.errorCollector().store(parseState.mark(), SnbtOperations.BUILTIN_IDS, ERROR_INVALID_UNQUOTED_START);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Atom<String> atom28 = Atom.of("map_key");
|
||||||
|
dictionary.put(atom28, Term.alternative(dictionary.named(atom23), dictionary.named(atom24)), scope -> scope.getAnyOrThrow(atom23, atom24));
|
||||||
|
Atom<Entry<String, T>> atom29 = Atom.of("map_entry");
|
||||||
|
NamedRule<StringReader, Entry<String, T>> namedRule3 = dictionary.putComplex(
|
||||||
|
atom29, Term.sequence(dictionary.named(atom28), StringReaderTerms.character(TagParser.NAME_VALUE_SEPARATOR), dictionary.named(atom25)), parseState -> {
|
||||||
|
Scope scope = parseState.scope();
|
||||||
|
String string = scope.getOrThrow(atom28);
|
||||||
|
if (string.isEmpty()) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_EMPTY_KEY);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T orThrow = scope.getOrThrow(atom25);
|
||||||
|
return Map.entry(string, orThrow);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Atom<List<Entry<String, T>>> atom30 = Atom.of("map_entries");
|
||||||
|
dictionary.put(atom30, Term.repeatedWithTrailingSeparator(namedRule3, atom30, StringReaderTerms.character(TagParser.ELEMENT_SEPARATOR)), scope -> scope.getOrThrow(atom30));
|
||||||
|
Atom<T> atom31 = Atom.of("map_literal");
|
||||||
|
dictionary.put(atom31, Term.sequence(StringReaderTerms.character('{'), Scope.increaseDepth(), dictionary.named(atom30), Scope.decreaseDepth(), StringReaderTerms.character('}')), scope -> { // Paper - track depth
|
||||||
|
List<Entry<String, T>> list = scope.getOrThrow(atom30);
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return object2;
|
||||||
|
} else {
|
||||||
|
Builder<T, T> builder = ImmutableMap.builderWithExpectedSize(list.size());
|
||||||
|
|
||||||
|
for (Entry<String, T> entry : list) {
|
||||||
|
builder.put(ops.createString(entry.getKey()), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops.createMap(builder.buildKeepingLast());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Atom<List<T>> atom32 = Atom.of("list_entries");
|
||||||
|
dictionary.put(
|
||||||
|
atom32, Term.repeatedWithTrailingSeparator(dictionary.forward(atom25), atom32, StringReaderTerms.character(TagParser.ELEMENT_SEPARATOR)), scope -> scope.getOrThrow(atom32)
|
||||||
|
);
|
||||||
|
Atom<ArrayPrefix> atom33 = Atom.of("array_prefix");
|
||||||
|
dictionary.put(
|
||||||
|
atom33,
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(StringReaderTerms.character('B'), Term.marker(atom33, ArrayPrefix.BYTE)),
|
||||||
|
Term.sequence(StringReaderTerms.character('L'), Term.marker(atom33, ArrayPrefix.LONG)),
|
||||||
|
Term.sequence(StringReaderTerms.character('I'), Term.marker(atom33, ArrayPrefix.INT))
|
||||||
|
),
|
||||||
|
scope -> scope.getOrThrow(atom33)
|
||||||
|
);
|
||||||
|
Atom<List<IntegerLiteral>> atom34 = Atom.of("int_array_entries");
|
||||||
|
dictionary.put(atom34, Term.repeatedWithTrailingSeparator(namedRule, atom34, StringReaderTerms.character(TagParser.ELEMENT_SEPARATOR)), scope -> scope.getOrThrow(atom34));
|
||||||
|
Atom<T> atom35 = Atom.of("list_literal");
|
||||||
|
dictionary.putComplex(
|
||||||
|
atom35,
|
||||||
|
Term.sequence(
|
||||||
|
StringReaderTerms.character('['),
|
||||||
|
Scope.increaseDepth(),
|
||||||
|
Term.alternative(Term.sequence(dictionary.named(atom33), StringReaderTerms.character(';'), dictionary.named(atom34)), dictionary.named(atom32)),
|
||||||
|
Scope.decreaseDepth(),
|
||||||
|
StringReaderTerms.character(']')
|
||||||
|
),
|
||||||
|
parseState -> {
|
||||||
|
Scope scope = parseState.scope();
|
||||||
|
ArrayPrefix arrayPrefix = scope.get(atom33);
|
||||||
|
if (arrayPrefix != null) {
|
||||||
|
List<IntegerLiteral> list = scope.getOrThrow(atom34);
|
||||||
|
return list.isEmpty() ? arrayPrefix.create(ops) : arrayPrefix.create(ops, list, parseState);
|
||||||
|
}
|
||||||
|
List<T> list = scope.getOrThrow(atom32);
|
||||||
|
return list.isEmpty() ? object3 : ops.createList(list.stream());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
NamedRule<StringReader, T> namedRule4 = dictionary.putComplex(
|
||||||
|
atom25,
|
||||||
|
Term.alternative(
|
||||||
|
Term.sequence(Term.positiveLookahead(NUMBER_LOOKEAHEAD), Term.alternative(dictionary.namedWithAlias(atom10, atom25), dictionary.named(atom5))),
|
||||||
|
Term.sequence(Term.positiveLookahead(StringReaderTerms.characters('"', '\'')), Term.cut(), dictionary.named(atom23)),
|
||||||
|
Term.sequence(Term.positiveLookahead(StringReaderTerms.character('{')), Term.cut(), dictionary.namedWithAlias(atom31, atom25)),
|
||||||
|
Term.sequence(Term.positiveLookahead(StringReaderTerms.character('[')), Term.cut(), dictionary.namedWithAlias(atom35, atom25)),
|
||||||
|
dictionary.namedWithAlias(atom27, atom25)
|
||||||
|
),
|
||||||
|
parseState -> {
|
||||||
|
Scope scope = parseState.scope();
|
||||||
|
String string = scope.get(atom23);
|
||||||
|
if (string != null) {
|
||||||
|
return ops.createString(string);
|
||||||
|
}
|
||||||
|
IntegerLiteral integerLiteral = scope.get(atom5);
|
||||||
|
return integerLiteral != null ? integerLiteral.create(ops, parseState) : scope.getOrThrow(atom25);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return new Grammar<>(dictionary, namedRule4);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ArrayPrefix {
|
||||||
|
BYTE(TypeSuffix.BYTE) {
|
||||||
|
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T create(DynamicOps<T> ops) {
|
||||||
|
return ops.createByteList(EMPTY_BUFFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T> T create(DynamicOps<T> ops, List<IntegerLiteral> values, ParseState<?> parseState) {
|
||||||
|
ByteList list = new ByteArrayList();
|
||||||
|
|
||||||
|
for (IntegerLiteral integerLiteral : values) {
|
||||||
|
Number number = this.buildNumber(integerLiteral, parseState);
|
||||||
|
if (number == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(number.byteValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops.createByteList(ByteBuffer.wrap(list.toByteArray()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
INT(TypeSuffix.INT, TypeSuffix.BYTE, TypeSuffix.SHORT) {
|
||||||
|
@Override
|
||||||
|
public <T> T create(DynamicOps<T> ops) {
|
||||||
|
return ops.createIntList(IntStream.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T> T create(DynamicOps<T> ops, List<IntegerLiteral> values, ParseState<?> parseState) {
|
||||||
|
IntStream.Builder builder = IntStream.builder();
|
||||||
|
|
||||||
|
for (IntegerLiteral integerLiteral : values) {
|
||||||
|
Number number = this.buildNumber(integerLiteral, parseState);
|
||||||
|
if (number == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.add(number.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops.createIntList(builder.build());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LONG(TypeSuffix.LONG, TypeSuffix.BYTE, TypeSuffix.SHORT, TypeSuffix.INT) {
|
||||||
|
@Override
|
||||||
|
public <T> T create(DynamicOps<T> ops) {
|
||||||
|
return ops.createLongList(LongStream.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T> T create(DynamicOps<T> ops, List<IntegerLiteral> values, ParseState<?> parseState) {
|
||||||
|
LongStream.Builder builder = LongStream.builder();
|
||||||
|
|
||||||
|
for (IntegerLiteral integerLiteral : values) {
|
||||||
|
Number number = this.buildNumber(integerLiteral, parseState);
|
||||||
|
if (number == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.add(number.longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops.createLongList(builder.build());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final TypeSuffix defaultType;
|
||||||
|
private final Set<TypeSuffix> additionalTypes;
|
||||||
|
|
||||||
|
ArrayPrefix(final TypeSuffix defaultType, final TypeSuffix... additionalTypes) {
|
||||||
|
this.additionalTypes = Set.of(additionalTypes);
|
||||||
|
this.defaultType = defaultType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowed(TypeSuffix suffix) {
|
||||||
|
return suffix == this.defaultType || this.additionalTypes.contains(suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract <T> T create(DynamicOps<T> ops);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract <T> T create(DynamicOps<T> ops, List<IntegerLiteral> values, ParseState<?> parseState);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected Number buildNumber(IntegerLiteral value, ParseState<?> parseState) {
|
||||||
|
TypeSuffix typeSuffix = this.computeType(value.suffix);
|
||||||
|
if (typeSuffix == null) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_INVALID_ARRAY_ELEMENT_TYPE);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (Number)value.create(JavaOps.INSTANCE, typeSuffix, parseState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private TypeSuffix computeType(IntegerSuffix suffix) {
|
||||||
|
TypeSuffix typeSuffix = suffix.type();
|
||||||
|
if (typeSuffix == null) {
|
||||||
|
return this.defaultType;
|
||||||
|
}
|
||||||
|
return !this.isAllowed(typeSuffix) ? null : typeSuffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Base {
|
||||||
|
BINARY,
|
||||||
|
DECIMAL,
|
||||||
|
HEX
|
||||||
|
}
|
||||||
|
|
||||||
|
record IntegerLiteral(Sign sign, Base base, String digits, IntegerSuffix suffix) {
|
||||||
|
private SignedPrefix signedOrDefault() {
|
||||||
|
return Objects.requireNonNullElseGet(this.suffix.signed, () -> switch (this.base) {
|
||||||
|
case BINARY, HEX -> SignedPrefix.UNSIGNED;
|
||||||
|
case DECIMAL -> SignedPrefix.SIGNED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cleanupDigits(Sign sign) {
|
||||||
|
boolean flag = needsUnderscoreRemoval(this.digits);
|
||||||
|
if (sign != Sign.MINUS && !flag) {
|
||||||
|
return this.digits;
|
||||||
|
}
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
sign.append(stringBuilder);
|
||||||
|
cleanAndAppend(stringBuilder, this.digits, flag);
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public <T> T create(DynamicOps<T> ops, ParseState<?> parseState) {
|
||||||
|
return this.create(ops, Objects.requireNonNullElse(this.suffix.type, TypeSuffix.INT), parseState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public <T> T create(DynamicOps<T> ops, TypeSuffix typeSuffix, ParseState<?> parseState) {
|
||||||
|
boolean flag = this.signedOrDefault() == SignedPrefix.SIGNED;
|
||||||
|
if (!flag && this.sign == Sign.MINUS) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_EXPECTED_NON_NEGATIVE_NUMBER);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String string = this.cleanupDigits(this.sign);
|
||||||
|
|
||||||
|
int i = switch (this.base) {
|
||||||
|
case BINARY -> 2;
|
||||||
|
case DECIMAL -> 10;
|
||||||
|
case HEX -> 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (flag) {
|
||||||
|
return switch (typeSuffix) {
|
||||||
|
case BYTE -> ops.createByte(Byte.parseByte(string, i));
|
||||||
|
case SHORT -> ops.createShort(Short.parseShort(string, i));
|
||||||
|
case INT -> ops.createInt(Integer.parseInt(string, i));
|
||||||
|
case LONG -> ops.createLong(Long.parseLong(string, i));
|
||||||
|
default -> {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_EXPECTED_INTEGER_TYPE);
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return switch (typeSuffix) {
|
||||||
|
case BYTE -> ops.createByte(com.google.common.primitives.UnsignedBytes.parseUnsignedByte(string, i));
|
||||||
|
case SHORT -> ops.createShort(parseUnsignedShort(string, i));
|
||||||
|
case INT -> ops.createInt(Integer.parseUnsignedInt(string, i));
|
||||||
|
case LONG -> ops.createLong(Long.parseUnsignedLong(string, i));
|
||||||
|
default -> {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), ERROR_EXPECTED_INTEGER_TYPE);
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (NumberFormatException var8) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), createNumberParseError(var8));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record IntegerSuffix(@Nullable SignedPrefix signed, @Nullable TypeSuffix type) {
|
||||||
|
public static final IntegerSuffix EMPTY = new IntegerSuffix(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Sign {
|
||||||
|
PLUS,
|
||||||
|
MINUS;
|
||||||
|
|
||||||
|
public void append(StringBuilder stringBuilder) {
|
||||||
|
if (this == MINUS) {
|
||||||
|
stringBuilder.append("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Signed<T>(Sign sign, T value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SignedPrefix {
|
||||||
|
SIGNED,
|
||||||
|
UNSIGNED
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SimpleHexLiteralParseRule extends GreedyPredicateParseRule {
|
||||||
|
public SimpleHexLiteralParseRule(int minSize) {
|
||||||
|
super(minSize, minSize, DelayedException.create(ERROR_EXPECTED_HEX_ESCAPE, String.valueOf(minSize)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isAccepted(char c) {
|
||||||
|
return switch (c) {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f' -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TypeSuffix {
|
||||||
|
FLOAT,
|
||||||
|
DOUBLE,
|
||||||
|
BYTE,
|
||||||
|
SHORT,
|
||||||
|
INT,
|
||||||
|
LONG
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||||
|
import com.mojang.serialization.DynamicOps;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.*;
|
||||||
|
import net.momirealms.sparrow.nbt.util.UUIDUtil;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class SnbtOperations {
|
||||||
|
static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_STRING_UUID = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_string_uuid"))
|
||||||
|
);
|
||||||
|
static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_NUMBER_OR_BOOLEAN = DelayedException.create(
|
||||||
|
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_number_or_boolean"))
|
||||||
|
);
|
||||||
|
public static final String BUILTIN_TRUE = "true";
|
||||||
|
public static final String BUILTIN_FALSE = "false";
|
||||||
|
public static final Map<BuiltinKey, BuiltinOperation> BUILTIN_OPERATIONS = Map.of(
|
||||||
|
new BuiltinKey("bool", 1), new BuiltinOperation() {
|
||||||
|
@Override
|
||||||
|
public <T> T run(DynamicOps<T> ops, List<T> args, ParseState<StringReader> parseState) {
|
||||||
|
Boolean bool = convert(ops, args.getFirst());
|
||||||
|
if (bool == null) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), SnbtOperations.ERROR_EXPECTED_NUMBER_OR_BOOLEAN);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return ops.createBoolean(bool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T> Boolean convert(DynamicOps<T> ops, T value) {
|
||||||
|
Optional<Boolean> optional = ops.getBooleanValue(value).result();
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
return optional.get();
|
||||||
|
} else {
|
||||||
|
Optional<Number> optional1 = ops.getNumberValue(value).result();
|
||||||
|
return optional1.isPresent() ? optional1.get().doubleValue() != 0.0 : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, new BuiltinKey("uuid", 1), new BuiltinOperation() {
|
||||||
|
@Override
|
||||||
|
public <T> T run(DynamicOps<T> ops, List<T> args, ParseState<StringReader> parseState) {
|
||||||
|
Optional<String> optional = ops.getStringValue(args.getFirst()).result();
|
||||||
|
if (optional.isEmpty()) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), SnbtOperations.ERROR_EXPECTED_STRING_UUID);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
UUID uuid;
|
||||||
|
try {
|
||||||
|
uuid = UUID.fromString(optional.get());
|
||||||
|
} catch (IllegalArgumentException var7) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), SnbtOperations.ERROR_EXPECTED_STRING_UUID);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops.createIntList(IntStream.of(UUIDUtil.uuidToIntArray(uuid)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
public static final SuggestionSupplier<StringReader> BUILTIN_IDS = new SuggestionSupplier<>() {
|
||||||
|
private final Set<String> keys = Stream.concat(
|
||||||
|
Stream.of(BUILTIN_FALSE, BUILTIN_TRUE), SnbtOperations.BUILTIN_OPERATIONS.keySet().stream().map(BuiltinKey::id)
|
||||||
|
)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> possibleValues(ParseState<StringReader> parseState) {
|
||||||
|
return this.keys.stream();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public record BuiltinKey(String id, int argCount) {
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return this.id + "/" + this.argCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface BuiltinOperation {
|
||||||
|
@Nullable
|
||||||
|
<T> T run(DynamicOps<T> ops, List<T> args, ParseState<StringReader> parseState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||||
|
import com.mojang.serialization.DynamicOps;
|
||||||
|
import com.mojang.serialization.JavaOps;
|
||||||
|
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.LocalizedMessage;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.LocalizedSimpleCommandExceptionType;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.grammar.Grammar;
|
||||||
|
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||||
|
import net.momirealms.sparrow.nbt.Tag;
|
||||||
|
import net.momirealms.sparrow.nbt.codec.LegacyJavaOps;
|
||||||
|
import net.momirealms.sparrow.nbt.codec.LegacyNBTOps;
|
||||||
|
import net.momirealms.sparrow.nbt.codec.NBTOps;
|
||||||
|
|
||||||
|
public class TagParser<T> {
|
||||||
|
public static final SimpleCommandExceptionType ERROR_TRAILING_DATA = new LocalizedSimpleCommandExceptionType(
|
||||||
|
new LocalizedMessage("warning.config.type.snbt.parser.trailing")
|
||||||
|
);
|
||||||
|
public static final SimpleCommandExceptionType ERROR_EXPECTED_COMPOUND = new LocalizedSimpleCommandExceptionType(
|
||||||
|
new LocalizedMessage("warning.config.type.snbt.parser.expected.compound")
|
||||||
|
);
|
||||||
|
public static final char ELEMENT_SEPARATOR = ',';
|
||||||
|
public static final char NAME_VALUE_SEPARATOR = ':';
|
||||||
|
private static final TagParser<Tag> NBT_OPS_PARSER = create(VersionHelper.isOrAbove1_20_5() ? NBTOps.INSTANCE : LegacyNBTOps.INSTANCE);
|
||||||
|
private static final TagParser<Object> JAVA_OPS_PARSER = create(VersionHelper.isOrAbove1_20_5() ? JavaOps.INSTANCE : LegacyJavaOps.INSTANCE);
|
||||||
|
private final DynamicOps<T> ops;
|
||||||
|
private final Grammar<T> grammar;
|
||||||
|
|
||||||
|
private TagParser(DynamicOps<T> ops, Grammar<T> grammar) {
|
||||||
|
this.ops = ops;
|
||||||
|
this.grammar = grammar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicOps<T> ops() {
|
||||||
|
return this.ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> TagParser<T> create(DynamicOps<T> ops) {
|
||||||
|
return new TagParser<>(ops, SnbtGrammar.createParser(ops));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompoundTag castToCompoundOrThrow(StringReader reader, Tag tag) throws CommandSyntaxException {
|
||||||
|
if (tag instanceof CompoundTag compoundTag) {
|
||||||
|
return compoundTag;
|
||||||
|
}
|
||||||
|
throw ERROR_EXPECTED_COMPOUND.createWithContext(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompoundTag parseCompoundFully(String data) throws CommandSyntaxException {
|
||||||
|
StringReader stringReader = new StringReader(data);
|
||||||
|
return parseCompoundAsArgument(stringReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object parseObjectFully(String data) throws CommandSyntaxException {
|
||||||
|
StringReader stringReader = new StringReader(data);
|
||||||
|
return parseObjectAsArgument(stringReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T parseFully(String text) throws CommandSyntaxException {
|
||||||
|
return this.parseFully(new StringReader(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public T parseFully(StringReader reader) throws CommandSyntaxException {
|
||||||
|
T object = this.grammar.parse(reader);
|
||||||
|
reader.skipWhitespace();
|
||||||
|
if (reader.canRead()) {
|
||||||
|
throw ERROR_TRAILING_DATA.createWithContext(reader);
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T parseAsArgument(StringReader reader) throws CommandSyntaxException {
|
||||||
|
return this.grammar.parse(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompoundTag parseCompoundAsArgument(StringReader reader) throws CommandSyntaxException {
|
||||||
|
Tag tag = NBT_OPS_PARSER.parseAsArgument(reader);
|
||||||
|
return castToCompoundOrThrow(reader, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object parseObjectAsArgument(StringReader reader) throws CommandSyntaxException {
|
||||||
|
return JAVA_OPS_PARSER.parseAsArgument(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public record Atom<T>(String name) {
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return "<" + this.name + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Atom<T> of(String name) {
|
||||||
|
return new Atom<>(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntList;
|
||||||
|
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public abstract class CachedParseState<S> implements ParseState<S> {
|
||||||
|
private PositionCache[] positionCache = new PositionCache[256];
|
||||||
|
private final ErrorCollector<S> errorCollector;
|
||||||
|
private final Scope scope = new Scope();
|
||||||
|
private SimpleControl[] controlCache = new SimpleControl[16];
|
||||||
|
private int nextControlToReturn;
|
||||||
|
private final Silent silent = new Silent();
|
||||||
|
private final IntList markedNull = new IntArrayList();
|
||||||
|
public static final Object JAVA_NULL_VALUE_MARKER = new Object() {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected CachedParseState(ErrorCollector<S> errorCollector) {
|
||||||
|
this.errorCollector = errorCollector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Scope scope() {
|
||||||
|
return this.scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ErrorCollector<S> errorCollector() {
|
||||||
|
return this.errorCollector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T> T parse(NamedRule<S, T> rule) {
|
||||||
|
int i = this.mark();
|
||||||
|
PositionCache cacheForPosition = this.getCacheForPosition(i);
|
||||||
|
int i1 = cacheForPosition.findKeyIndex(rule.name());
|
||||||
|
if (i1 != -1) {
|
||||||
|
CacheEntry<T> value = cacheForPosition.getValue(i1);
|
||||||
|
if (value != null) {
|
||||||
|
if (value == CachedParseState.CacheEntry.NEGATIVE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.restore(value.markAfterParse);
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i1 = cacheForPosition.allocateNewEntry(rule.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
T object = rule.value().parse(this);
|
||||||
|
CacheEntry<T> cacheEntry;
|
||||||
|
if (object == null) {
|
||||||
|
cacheEntry = (CacheEntry<T>) CacheEntry.NEGATIVE;
|
||||||
|
} else {
|
||||||
|
cacheEntry = new CacheEntry<>(object, this.mark());
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheForPosition.setValue(i1, cacheEntry);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PositionCache getCacheForPosition(int position) {
|
||||||
|
int i = this.positionCache.length;
|
||||||
|
if (position >= i) {
|
||||||
|
int i1 = MiscUtils.growByHalf(i, position + 1);
|
||||||
|
PositionCache[] positionCaches = new PositionCache[i1];
|
||||||
|
System.arraycopy(this.positionCache, 0, positionCaches, 0, i);
|
||||||
|
this.positionCache = positionCaches;
|
||||||
|
}
|
||||||
|
|
||||||
|
PositionCache positionCache = this.positionCache[position];
|
||||||
|
if (positionCache == null) {
|
||||||
|
positionCache = new PositionCache();
|
||||||
|
this.positionCache[position] = positionCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
return positionCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Control acquireControl() {
|
||||||
|
int i = this.controlCache.length;
|
||||||
|
if (this.nextControlToReturn >= i) {
|
||||||
|
int i1 = MiscUtils.growByHalf(i, this.nextControlToReturn + 1);
|
||||||
|
SimpleControl[] simpleControls = new SimpleControl[i1];
|
||||||
|
System.arraycopy(this.controlCache, 0, simpleControls, 0, i);
|
||||||
|
this.controlCache = simpleControls;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i1 = this.nextControlToReturn++;
|
||||||
|
SimpleControl simpleControl = this.controlCache[i1];
|
||||||
|
if (simpleControl == null) {
|
||||||
|
simpleControl = new SimpleControl();
|
||||||
|
this.controlCache[i1] = simpleControl;
|
||||||
|
} else {
|
||||||
|
simpleControl.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return simpleControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseControl() {
|
||||||
|
this.nextControlToReturn--;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParseState<S> silent() {
|
||||||
|
return this.silent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markNull(int mark) {
|
||||||
|
this.markedNull.add(mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNull(int mark) {
|
||||||
|
return this.markedNull.contains(mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
record CacheEntry<T>(@Nullable T value, int markAfterParse) {
|
||||||
|
public static final CacheEntry<?> NEGATIVE = new CacheEntry<>(null, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PositionCache {
|
||||||
|
public static final int ENTRY_STRIDE = 2;
|
||||||
|
private static final int NOT_FOUND = -1;
|
||||||
|
private Object[] atomCache = new Object[16];
|
||||||
|
private int nextKey;
|
||||||
|
|
||||||
|
public int findKeyIndex(Atom<?> atom) {
|
||||||
|
for (int i = 0; i < this.nextKey; i += ENTRY_STRIDE) {
|
||||||
|
if (this.atomCache[i] == atom) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int allocateNewEntry(Atom<?> entry) {
|
||||||
|
int i = this.nextKey;
|
||||||
|
this.nextKey += 2;
|
||||||
|
int i1 = i + 1;
|
||||||
|
int i2 = this.atomCache.length;
|
||||||
|
if (i1 >= i2) {
|
||||||
|
int i3 = MiscUtils.growByHalf(i2, i1 + 1);
|
||||||
|
Object[] objects = new Object[i3];
|
||||||
|
System.arraycopy(this.atomCache, 0, objects, 0, i2);
|
||||||
|
this.atomCache = objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.atomCache[i] = entry;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public <T> CacheEntry<T> getValue(int index) {
|
||||||
|
return (CacheEntry<T>)this.atomCache[index + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(int index, CacheEntry<?> value) {
|
||||||
|
this.atomCache[index + 1] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Silent implements ParseState<S> {
|
||||||
|
private final ErrorCollector<S> silentCollector = new ErrorCollector.Nop<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ErrorCollector<S> errorCollector() {
|
||||||
|
return this.silentCollector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Scope scope() {
|
||||||
|
return CachedParseState.this.scope();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T> T parse(NamedRule<S, T> rule) {
|
||||||
|
return CachedParseState.this.parse(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public S input() {
|
||||||
|
return CachedParseState.this.input();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int mark() {
|
||||||
|
return CachedParseState.this.mark();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restore(int cursor) {
|
||||||
|
CachedParseState.this.restore(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Control acquireControl() {
|
||||||
|
return CachedParseState.this.acquireControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseControl() {
|
||||||
|
CachedParseState.this.releaseControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParseState<S> silent() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markNull(int mark) {
|
||||||
|
CachedParseState.this.markNull(mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNull(int mark) {
|
||||||
|
return CachedParseState.this.isNull(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SimpleControl implements Control {
|
||||||
|
private boolean hasCut;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cut() {
|
||||||
|
this.hasCut = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasCut() {
|
||||||
|
return this.hasCut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.hasCut = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
public interface Control {
|
||||||
|
Control UNBOUND = new Control() {
|
||||||
|
@Override
|
||||||
|
public void cut() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasCut() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void cut();
|
||||||
|
|
||||||
|
boolean hasCut();
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
||||||
|
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.grammar.StringReaderTerms;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface DelayedException<T extends Exception> {
|
||||||
|
T create(String message, int cursor);
|
||||||
|
|
||||||
|
static DelayedException<CommandSyntaxException> create(SimpleCommandExceptionType exception) {
|
||||||
|
return (message, cursor) -> exception.createWithContext(StringReaderTerms.createReader(message, cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
static DelayedException<CommandSyntaxException> create(DynamicCommandExceptionType exception, String argument) {
|
||||||
|
return (message, cursor) -> exception.createWithContext(StringReaderTerms.createReader(message, cursor), argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class Dictionary<S> {
|
||||||
|
private final Map<Atom<?>, Entry<S, ?>> terms = new IdentityHashMap<>();
|
||||||
|
|
||||||
|
public <T> NamedRule<S, T> put(Atom<T> name, Rule<S, T> rule) {
|
||||||
|
Entry<S, T> entry = (Entry<S, T>)this.terms.computeIfAbsent(name, Entry::new);
|
||||||
|
if (entry.value != null) {
|
||||||
|
throw new IllegalArgumentException("Trying to override rule: " + name);
|
||||||
|
} else {
|
||||||
|
entry.value = rule;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> NamedRule<S, T> putComplex(Atom<T> name, Term<S> term, Rule.RuleAction<S, T> ruleAction) {
|
||||||
|
return this.put(name, Rule.fromTerm(term, ruleAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> NamedRule<S, T> put(Atom<T> name, Term<S> term, Rule.SimpleRuleAction<S, T> ruleAction) {
|
||||||
|
return this.put(name, Rule.fromTerm(term, ruleAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkAllBound() {
|
||||||
|
List<? extends Atom<?>> list = this.terms.entrySet().stream().filter(entry -> entry.getValue() == null).map(Map.Entry::getKey).toList();
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Unbound names: " + list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> NamedRule<S, T> forward(Atom<T> name) {
|
||||||
|
return this.getOrCreateEntry(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> Entry<S, T> getOrCreateEntry(Atom<T> name) {
|
||||||
|
return (Entry<S, T>)this.terms.computeIfAbsent(name, Entry::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Term<S> named(Atom<T> name) {
|
||||||
|
return new Reference<>(this.getOrCreateEntry(name), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Term<S> namedWithAlias(Atom<T> name, Atom<T> alias) {
|
||||||
|
return new Reference<>(this.getOrCreateEntry(name), alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Entry<S, T> implements NamedRule<S, T>, Supplier<String> {
|
||||||
|
private final Atom<T> name;
|
||||||
|
@Nullable
|
||||||
|
Rule<S, T> value;
|
||||||
|
|
||||||
|
private Entry(Atom<T> name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Atom<T> name() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rule<S, T> value() {
|
||||||
|
return Objects.requireNonNull(this.value, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
return "Unbound rule " + this.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Reference<S, T>(Entry<S, T> ruleToParse, Atom<T> nameToStore) implements Term<S> {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
T object = parseState.parse(this.ruleToParse);
|
||||||
|
if (object == null) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
scope.put(this.nameToStore, object);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public interface ErrorCollector<S> {
|
||||||
|
void store(int cursor, SuggestionSupplier<S> suggestions, Object reason);
|
||||||
|
|
||||||
|
default void store(int cursor, Object reason) {
|
||||||
|
this.store(cursor, SuggestionSupplier.empty(), reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish(int cursor);
|
||||||
|
|
||||||
|
class LongestOnly<S> implements ErrorCollector<S> {
|
||||||
|
private MutableErrorEntry<S>[] entries = new MutableErrorEntry[16];
|
||||||
|
private int nextErrorEntry;
|
||||||
|
private int lastCursor = -1;
|
||||||
|
|
||||||
|
private void discardErrorsFromShorterParse(int cursor) {
|
||||||
|
if (cursor > this.lastCursor) {
|
||||||
|
this.lastCursor = cursor;
|
||||||
|
this.nextErrorEntry = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish(int cursor) {
|
||||||
|
this.discardErrorsFromShorterParse(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void store(int cursor, SuggestionSupplier<S> suggestions, Object reason) {
|
||||||
|
this.discardErrorsFromShorterParse(cursor);
|
||||||
|
if (cursor == this.lastCursor) {
|
||||||
|
this.addErrorEntry(suggestions, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addErrorEntry(SuggestionSupplier<S> suggestions, Object reason) {
|
||||||
|
int i = this.entries.length;
|
||||||
|
if (this.nextErrorEntry >= i) {
|
||||||
|
int i1 = MiscUtils.growByHalf(i, this.nextErrorEntry + 1);
|
||||||
|
MutableErrorEntry<S>[] mutableErrorEntrys = new MutableErrorEntry[i1];
|
||||||
|
System.arraycopy(this.entries, 0, mutableErrorEntrys, 0, i);
|
||||||
|
this.entries = mutableErrorEntrys;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i1 = this.nextErrorEntry++;
|
||||||
|
MutableErrorEntry<S> mutableErrorEntry = this.entries[i1];
|
||||||
|
if (mutableErrorEntry == null) {
|
||||||
|
mutableErrorEntry = new MutableErrorEntry<>();
|
||||||
|
this.entries[i1] = mutableErrorEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutableErrorEntry.suggestions = suggestions;
|
||||||
|
mutableErrorEntry.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ErrorEntry<S>> entries() {
|
||||||
|
int i = this.nextErrorEntry;
|
||||||
|
if (i == 0) {
|
||||||
|
return List.of();
|
||||||
|
} else {
|
||||||
|
List<ErrorEntry<S>> list = new ArrayList<>(i);
|
||||||
|
|
||||||
|
for (int i1 = 0; i1 < i; i1++) {
|
||||||
|
MutableErrorEntry<S> mutableErrorEntry = this.entries[i1];
|
||||||
|
list.add(new ErrorEntry<>(this.lastCursor, mutableErrorEntry.suggestions, mutableErrorEntry.reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int cursor() {
|
||||||
|
return this.lastCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MutableErrorEntry<S> {
|
||||||
|
SuggestionSupplier<S> suggestions = SuggestionSupplier.empty();
|
||||||
|
Object reason = "empty";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Nop<S> implements ErrorCollector<S> {
|
||||||
|
@Override
|
||||||
|
public void store(int cursor, SuggestionSupplier<S> suggestions, Object reason) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish(int cursor) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
public record ErrorEntry<S>(int cursor, SuggestionSupplier<S> suggestions, Object reason) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.Message;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandExceptionType;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||||
|
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class LocalizedCommandSyntaxException extends CommandSyntaxException {
|
||||||
|
public static final int CONTEXT_AMOUNT = 10;
|
||||||
|
public static final String PARSE_ERROR_NODE = "warning.config.type.snbt.invalid_syntax.parse_error";
|
||||||
|
public static final String HERE_NODE = "warning.config.type.snbt.invalid_syntax.here";
|
||||||
|
private final Message message;
|
||||||
|
private final String input;
|
||||||
|
private final int cursor;
|
||||||
|
|
||||||
|
public LocalizedCommandSyntaxException(CommandExceptionType type, Message message) {
|
||||||
|
super(type, message);
|
||||||
|
this.message = message;
|
||||||
|
this.input = null;
|
||||||
|
this.cursor = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalizedCommandSyntaxException(CommandExceptionType type, Message message, String input, int cursor) {
|
||||||
|
super(type, message, input, cursor);
|
||||||
|
this.message = message;
|
||||||
|
this.input = input;
|
||||||
|
this.cursor = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
String message = this.message.getString();
|
||||||
|
final String context = getContext();
|
||||||
|
if (context == null) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
return generateLocalizedMessage(
|
||||||
|
PARSE_ERROR_NODE,
|
||||||
|
() -> message + " at position " + this.cursor + ": " + context,
|
||||||
|
message, String.valueOf(this.cursor), context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContext() {
|
||||||
|
if (this.input == null || this.cursor < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
final int cursor = Math.min(this.input.length(), this.cursor);
|
||||||
|
|
||||||
|
if (cursor > CONTEXT_AMOUNT) {
|
||||||
|
builder.append("...");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(this.input, Math.max(0, cursor - CONTEXT_AMOUNT), cursor);
|
||||||
|
builder.append(generateLocalizedMessage(HERE_NODE, () -> "<--[HERE]"));
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String generateLocalizedMessage(String node, Supplier<String> fallback, String... arguments) {
|
||||||
|
try {
|
||||||
|
String rawMessage = Optional.ofNullable(TranslationManager.instance()
|
||||||
|
.miniMessageTranslation(node)).orElse(fallback.get());
|
||||||
|
String cleanMessage = AdventureHelper.miniMessage()
|
||||||
|
.stripTags(rawMessage);
|
||||||
|
for (int i = 0; i < arguments.length; i++) {
|
||||||
|
cleanMessage = cleanMessage.replace(
|
||||||
|
"<arg:" + i + ">",
|
||||||
|
arguments[i] != null ? arguments[i] : "null"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return cleanMessage;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return fallback.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.ImmutableStringReader;
|
||||||
|
import com.mojang.brigadier.Message;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
||||||
|
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class LocalizedDynamicCommandExceptionType extends DynamicCommandExceptionType {
|
||||||
|
private final Function<Object, Message> function;
|
||||||
|
|
||||||
|
public LocalizedDynamicCommandExceptionType(Function<Object, Message> function) {
|
||||||
|
super(function);
|
||||||
|
this.function = function;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandSyntaxException create(final Object arg) {
|
||||||
|
return new LocalizedCommandSyntaxException(this, function.apply(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandSyntaxException createWithContext(final ImmutableStringReader reader, final Object arg) {
|
||||||
|
return new LocalizedCommandSyntaxException(this, function.apply(arg), reader.getString(), reader.getCursor());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.Message;
|
||||||
|
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||||
|
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class LocalizedMessage implements Message {
|
||||||
|
private final String node;
|
||||||
|
private final String[] arguments;
|
||||||
|
|
||||||
|
public LocalizedMessage(
|
||||||
|
@NotNull String node,
|
||||||
|
@Nullable String... arguments
|
||||||
|
) {
|
||||||
|
this.node = node;
|
||||||
|
this.arguments = arguments != null
|
||||||
|
? Arrays.copyOf(arguments, arguments.length)
|
||||||
|
: new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString() {
|
||||||
|
return generateLocalizedMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateLocalizedMessage() {
|
||||||
|
try {
|
||||||
|
String rawMessage = Optional.ofNullable(TranslationManager.instance()
|
||||||
|
.miniMessageTranslation(this.node)).orElse(this.node);
|
||||||
|
String cleanMessage = AdventureHelper.miniMessage()
|
||||||
|
.stripTags(rawMessage);
|
||||||
|
for (int i = 0; i < arguments.length; i++) {
|
||||||
|
cleanMessage = cleanMessage.replace(
|
||||||
|
"<arg:" + i + ">",
|
||||||
|
arguments[i] != null ? arguments[i] : "null"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return cleanMessage;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return String.format(
|
||||||
|
"Failed to translate. Node: %s, Arguments: %s. Cause: %s",
|
||||||
|
node,
|
||||||
|
Arrays.toString(arguments),
|
||||||
|
e.getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.ImmutableStringReader;
|
||||||
|
import com.mojang.brigadier.Message;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||||
|
|
||||||
|
public class LocalizedSimpleCommandExceptionType extends SimpleCommandExceptionType {
|
||||||
|
private final Message message;
|
||||||
|
|
||||||
|
public LocalizedSimpleCommandExceptionType(Message message) {
|
||||||
|
super(message);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandSyntaxException create() {
|
||||||
|
return new LocalizedCommandSyntaxException(this, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandSyntaxException createWithContext(final ImmutableStringReader reader) {
|
||||||
|
return new LocalizedCommandSyntaxException(this, message, reader.getString(), reader.getCursor());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
public interface NamedRule<S, T> {
|
||||||
|
Atom<T> name();
|
||||||
|
|
||||||
|
Rule<S, T> value();
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface ParseState<S> {
|
||||||
|
Scope scope();
|
||||||
|
|
||||||
|
ErrorCollector<S> errorCollector();
|
||||||
|
|
||||||
|
default <T> Optional<T> parseTopRule(NamedRule<S, T> rule) {
|
||||||
|
T object = this.parse(rule);
|
||||||
|
if (object != null) {
|
||||||
|
this.errorCollector().finish(this.mark());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.scope().hasOnlySingleFrame()) {
|
||||||
|
throw new IllegalStateException("Malformed scope: " + this.scope());
|
||||||
|
} else {
|
||||||
|
return Optional.ofNullable(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
<T> T parse(NamedRule<S, T> rule);
|
||||||
|
|
||||||
|
S input();
|
||||||
|
|
||||||
|
int mark();
|
||||||
|
|
||||||
|
void restore(int cursor);
|
||||||
|
|
||||||
|
Control acquireControl();
|
||||||
|
|
||||||
|
void releaseControl();
|
||||||
|
|
||||||
|
ParseState<S> silent();
|
||||||
|
|
||||||
|
void markNull(int mark);
|
||||||
|
|
||||||
|
boolean isNull(int mark);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public interface Rule<S, T> {
|
||||||
|
@Nullable
|
||||||
|
T parse(ParseState<S> parseState);
|
||||||
|
|
||||||
|
static <S, T> Rule<S, T> fromTerm(Term<S> child, RuleAction<S, T> action) {
|
||||||
|
return new WrappedTerm<>(action, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S, T> Rule<S, T> fromTerm(Term<S> child, SimpleRuleAction<S, T> action) {
|
||||||
|
return new WrappedTerm<>(action, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface RuleAction<S, T> {
|
||||||
|
@Nullable
|
||||||
|
T run(ParseState<S> parseState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface SimpleRuleAction<S, T> extends RuleAction<S, T> {
|
||||||
|
T run(Scope scope);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default T run(ParseState<S> parseState) {
|
||||||
|
return this.run(parseState.scope());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record WrappedTerm<S, T>(RuleAction<S, T> action, Term<S> child) implements Rule<S, T> {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public T parse(ParseState<S> parseState) {
|
||||||
|
Scope scope = parseState.scope();
|
||||||
|
scope.pushFrame();
|
||||||
|
|
||||||
|
T var3;
|
||||||
|
try {
|
||||||
|
if (!this.child.parse(parseState, scope, Control.UNBOUND)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var3 = this.action.run(parseState);
|
||||||
|
} finally {
|
||||||
|
scope.popFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
return var3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public final class Scope {
|
||||||
|
private static final int NOT_FOUND = -1;
|
||||||
|
private static final Object FRAME_START_MARKER = new Object() {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "frame";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final int ENTRY_STRIDE = 2;
|
||||||
|
private Object[] stack = new Object[128];
|
||||||
|
private int topEntryKeyIndex = 0;
|
||||||
|
private int topMarkerKeyIndex = 0;
|
||||||
|
private int depth;
|
||||||
|
|
||||||
|
public Scope() {
|
||||||
|
this.stack[0] = FRAME_START_MARKER;
|
||||||
|
this.stack[1] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int valueIndex(Atom<?> name) {
|
||||||
|
for (int i = this.topEntryKeyIndex; i > this.topMarkerKeyIndex; i -= ENTRY_STRIDE) {
|
||||||
|
Object object = this.stack[i];
|
||||||
|
|
||||||
|
assert object instanceof Atom;
|
||||||
|
|
||||||
|
if (object == name) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int valueIndexForAny(Atom<?>... names) {
|
||||||
|
for (int i = this.topEntryKeyIndex; i > this.topMarkerKeyIndex; i -= ENTRY_STRIDE) {
|
||||||
|
Object object = this.stack[i];
|
||||||
|
|
||||||
|
assert object instanceof Atom;
|
||||||
|
|
||||||
|
for (Atom<?> atom : names) {
|
||||||
|
if (atom == object) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureCapacity(int requiredCapacitty) {
|
||||||
|
int i = this.stack.length;
|
||||||
|
int i1 = this.topEntryKeyIndex + 1;
|
||||||
|
int i2 = i1 + requiredCapacitty * 2;
|
||||||
|
if (i2 >= i) {
|
||||||
|
int i3 = MiscUtils.growByHalf(i, i2 + 1);
|
||||||
|
Object[] objects = new Object[i3];
|
||||||
|
System.arraycopy(this.stack, 0, objects, 0, i);
|
||||||
|
this.stack = objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert this.validateStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupNewFrame() {
|
||||||
|
this.topEntryKeyIndex += ENTRY_STRIDE;
|
||||||
|
this.stack[this.topEntryKeyIndex] = FRAME_START_MARKER;
|
||||||
|
this.stack[this.topEntryKeyIndex + 1] = this.topMarkerKeyIndex;
|
||||||
|
this.topMarkerKeyIndex = this.topEntryKeyIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushFrame() {
|
||||||
|
this.ensureCapacity(1);
|
||||||
|
this.setupNewFrame();
|
||||||
|
|
||||||
|
assert this.validateStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPreviousMarkerIndex(int markerIndex) {
|
||||||
|
return (Integer)this.stack[markerIndex + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popFrame() {
|
||||||
|
assert this.topMarkerKeyIndex != 0;
|
||||||
|
|
||||||
|
this.topEntryKeyIndex = this.topMarkerKeyIndex - ENTRY_STRIDE;
|
||||||
|
this.topMarkerKeyIndex = this.getPreviousMarkerIndex(this.topMarkerKeyIndex);
|
||||||
|
|
||||||
|
assert this.validateStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void splitFrame() {
|
||||||
|
int i = this.topMarkerKeyIndex;
|
||||||
|
int i1 = (this.topEntryKeyIndex - this.topMarkerKeyIndex) / ENTRY_STRIDE;
|
||||||
|
this.ensureCapacity(i1 + 1);
|
||||||
|
this.setupNewFrame();
|
||||||
|
int i2 = i + ENTRY_STRIDE;
|
||||||
|
int i3 = this.topEntryKeyIndex;
|
||||||
|
|
||||||
|
for (int i4 = 0; i4 < i1; i4++) {
|
||||||
|
i3 += ENTRY_STRIDE;
|
||||||
|
Object object = this.stack[i2];
|
||||||
|
|
||||||
|
assert object != null;
|
||||||
|
|
||||||
|
this.stack[i3] = object;
|
||||||
|
this.stack[i3 + 1] = null;
|
||||||
|
i2 += ENTRY_STRIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.topEntryKeyIndex = i3;
|
||||||
|
|
||||||
|
assert this.validateStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearFrameValues() {
|
||||||
|
for (int i = this.topEntryKeyIndex; i > this.topMarkerKeyIndex; i -= ENTRY_STRIDE) {
|
||||||
|
assert this.stack[i] instanceof Atom;
|
||||||
|
|
||||||
|
this.stack[i + 1] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert this.validateStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mergeFrame() {
|
||||||
|
int previousMarkerIndex = this.getPreviousMarkerIndex(this.topMarkerKeyIndex);
|
||||||
|
int i = previousMarkerIndex;
|
||||||
|
int i1 = this.topMarkerKeyIndex;
|
||||||
|
|
||||||
|
while (i1 < this.topEntryKeyIndex) {
|
||||||
|
i += ENTRY_STRIDE;
|
||||||
|
i1 += ENTRY_STRIDE;
|
||||||
|
Object object = this.stack[i1];
|
||||||
|
|
||||||
|
assert object instanceof Atom;
|
||||||
|
|
||||||
|
Object object1 = this.stack[i1 + 1];
|
||||||
|
Object object2 = this.stack[i];
|
||||||
|
if (object2 != object) {
|
||||||
|
this.stack[i] = object;
|
||||||
|
this.stack[i + 1] = object1;
|
||||||
|
} else if (object1 != null) {
|
||||||
|
this.stack[i + 1] = object1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.topEntryKeyIndex = i;
|
||||||
|
this.topMarkerKeyIndex = previousMarkerIndex;
|
||||||
|
|
||||||
|
assert this.validateStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void put(Atom<T> atom, @Nullable T value) {
|
||||||
|
int i = this.valueIndex(atom);
|
||||||
|
if (i != NOT_FOUND) {
|
||||||
|
this.stack[i] = value;
|
||||||
|
} else {
|
||||||
|
this.ensureCapacity(1);
|
||||||
|
this.topEntryKeyIndex += ENTRY_STRIDE;
|
||||||
|
this.stack[this.topEntryKeyIndex] = atom;
|
||||||
|
this.stack[this.topEntryKeyIndex + 1] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert this.validateStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public <T> T get(Atom<T> atom) {
|
||||||
|
int i = this.valueIndex(atom);
|
||||||
|
return (T)(i != NOT_FOUND ? this.stack[i] : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getOrThrow(Atom<T> atom) {
|
||||||
|
int i = this.valueIndex(atom);
|
||||||
|
if (i == NOT_FOUND) {
|
||||||
|
throw new IllegalArgumentException("No value for atom " + atom);
|
||||||
|
} else {
|
||||||
|
return (T)this.stack[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getOrDefault(Atom<T> atom, T defaultValue) {
|
||||||
|
int i = this.valueIndex(atom);
|
||||||
|
return (T)(i != NOT_FOUND ? this.stack[i] : defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@SafeVarargs
|
||||||
|
public final <T> T getAny(Atom<? extends T>... atoms) {
|
||||||
|
int i = this.valueIndexForAny(atoms);
|
||||||
|
return (T)(i != NOT_FOUND ? this.stack[i] : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final <T> T getAnyOrThrow(Atom<? extends T>... atoms) {
|
||||||
|
int i = this.valueIndexForAny(atoms);
|
||||||
|
if (i == NOT_FOUND) {
|
||||||
|
throw new IllegalArgumentException("No value for atoms " + Arrays.toString(atoms));
|
||||||
|
} else {
|
||||||
|
return (T)this.stack[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
boolean flag = true;
|
||||||
|
|
||||||
|
for (int i = 0; i <= this.topEntryKeyIndex; i += ENTRY_STRIDE) {
|
||||||
|
Object object = this.stack[i];
|
||||||
|
Object object1 = this.stack[i + 1];
|
||||||
|
if (object == FRAME_START_MARKER) {
|
||||||
|
stringBuilder.append('|');
|
||||||
|
flag = true;
|
||||||
|
} else {
|
||||||
|
if (!flag) {
|
||||||
|
stringBuilder.append(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
flag = false;
|
||||||
|
stringBuilder.append(object).append(':').append(object1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public Map<Atom<?>, ?> lastFrame() {
|
||||||
|
HashMap<Atom<?>, Object> map = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = this.topEntryKeyIndex; i > this.topMarkerKeyIndex; i -= ENTRY_STRIDE) {
|
||||||
|
Object object = this.stack[i];
|
||||||
|
Object object1 = this.stack[i + 1];
|
||||||
|
map.put((Atom<?>)object, object1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOnlySingleFrame() {
|
||||||
|
for (int i = this.topEntryKeyIndex; i > 0; i--) {
|
||||||
|
if (this.stack[i] == FRAME_START_MARKER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stack[0] != FRAME_START_MARKER) {
|
||||||
|
throw new IllegalStateException("Corrupted stack");
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateStructure() {
|
||||||
|
assert this.topMarkerKeyIndex >= 0;
|
||||||
|
|
||||||
|
assert this.topEntryKeyIndex >= this.topMarkerKeyIndex;
|
||||||
|
|
||||||
|
for (int i = 0; i <= this.topEntryKeyIndex; i += ENTRY_STRIDE) {
|
||||||
|
Object object = this.stack[i];
|
||||||
|
if (object != FRAME_START_MARKER && !(object instanceof Atom)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int ix = this.topMarkerKeyIndex; ix != 0; ix = this.getPreviousMarkerIndex(ix)) {
|
||||||
|
Object object = this.stack[ix];
|
||||||
|
if (object != FRAME_START_MARKER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked","rawtypes"})
|
||||||
|
public static <S> Term<S> increaseDepth() {
|
||||||
|
class IncreasingDepthTerm<W> implements Term<W> {
|
||||||
|
public static final IncreasingDepthTerm INSTANCE = new IncreasingDepthTerm();
|
||||||
|
@Override
|
||||||
|
public boolean parse(final ParseState<W> parseState, final Scope scope, final Control control) {
|
||||||
|
if (++scope.depth > 512) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), new IllegalStateException("Too deep"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (Term<S>) IncreasingDepthTerm.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked","rawtypes"})
|
||||||
|
public static <S> Term<S> decreaseDepth() {
|
||||||
|
class DecreasingDepthTerm<W> implements Term<W> {
|
||||||
|
public static final DecreasingDepthTerm INSTANCE = new DecreasingDepthTerm();
|
||||||
|
@Override
|
||||||
|
public boolean parse(final ParseState<W> parseState, final Scope scope, final Control control) {
|
||||||
|
scope.depth--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (Term<S>) DecreasingDepthTerm.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public interface SuggestionSupplier<S> {
|
||||||
|
Stream<String> possibleValues(ParseState<S> parseState);
|
||||||
|
|
||||||
|
static <S> SuggestionSupplier<S> empty() {
|
||||||
|
return parseState -> Stream.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface Term<S> {
|
||||||
|
boolean parse(ParseState<S> parseState, Scope scope, Control control);
|
||||||
|
|
||||||
|
static <S, T> Term<S> marker(Atom<T> name, T value) {
|
||||||
|
return new Marker<>(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
static <S> Term<S> sequence(Term<S>... elements) {
|
||||||
|
return new Sequence<>(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
static <S> Term<S> alternative(Term<S>... elements) {
|
||||||
|
return new Alternative<>(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S> Term<S> optional(Term<S> term) {
|
||||||
|
return new Maybe<>(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S, T> Term<S> repeated(NamedRule<S, T> element, Atom<List<T>> listName) {
|
||||||
|
return repeated(element, listName, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S, T> Term<S> repeated(NamedRule<S, T> element, Atom<List<T>> listName, int minRepetitions) {
|
||||||
|
return new Repeated<>(element, listName, minRepetitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S, T> Term<S> repeatedWithTrailingSeparator(NamedRule<S, T> element, Atom<List<T>> listName, Term<S> seperator) {
|
||||||
|
return repeatedWithTrailingSeparator(element, listName, seperator, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S, T> Term<S> repeatedWithTrailingSeparator(NamedRule<S, T> element, Atom<List<T>> listName, Term<S> seperator, int minRepetitions) {
|
||||||
|
return new RepeatedWithSeparator<>(element, listName, seperator, minRepetitions, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S> Term<S> positiveLookahead(Term<S> term) {
|
||||||
|
return new LookAhead<>(term, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S> Term<S> cut() {
|
||||||
|
return new Term<>() {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
control.cut();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "↑";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S> Term<S> empty() {
|
||||||
|
return new Term<>() {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ε";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <S> Term<S> fail(final Object reason) {
|
||||||
|
return new Term<>() {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "fail";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
record Alternative<S>(Term<S>[] elements) implements Term<S> {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
Control control1 = parseState.acquireControl();
|
||||||
|
|
||||||
|
try {
|
||||||
|
int i = parseState.mark();
|
||||||
|
scope.splitFrame();
|
||||||
|
|
||||||
|
for (Term<S> term : this.elements) {
|
||||||
|
if (term.parse(parseState, scope, control1)) {
|
||||||
|
scope.mergeFrame();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.clearFrameValues();
|
||||||
|
parseState.restore(i);
|
||||||
|
if (control1.hasCut()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.popFrame();
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
parseState.releaseControl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record LookAhead<S>(Term<S> term, boolean positive) implements Term<S> {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
int i = parseState.mark();
|
||||||
|
boolean flag = this.term.parse(parseState.silent(), scope, control);
|
||||||
|
parseState.restore(i);
|
||||||
|
return this.positive == flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Marker<S, T>(Atom<T> name, T value) implements Term<S> {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
scope.put(this.name, this.value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Maybe<S>(Term<S> term) implements Term<S> {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
int i = parseState.mark();
|
||||||
|
if (!this.term.parse(parseState, scope, control)) {
|
||||||
|
parseState.restore(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Repeated<S, T>(NamedRule<S, T> element, Atom<List<T>> listName, int minRepetitions) implements Term<S> {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
int i = parseState.mark();
|
||||||
|
List<T> list = new ArrayList<>(this.minRepetitions);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int i1 = parseState.mark();
|
||||||
|
T object = parseState.parse(this.element);
|
||||||
|
if (object == null) {
|
||||||
|
parseState.restore(i1);
|
||||||
|
if (list.size() < this.minRepetitions) {
|
||||||
|
parseState.restore(i);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
scope.put(this.listName, list);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record RepeatedWithSeparator<S, T>(
|
||||||
|
NamedRule<S, T> element, Atom<List<T>> listName, Term<S> separator, int minRepetitions, boolean allowTrailingSeparator
|
||||||
|
) implements Term<S> {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
int i = parseState.mark();
|
||||||
|
List<T> list = new ArrayList<>(this.minRepetitions);
|
||||||
|
boolean flag = true;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int i1 = parseState.mark();
|
||||||
|
if (!flag && !this.separator.parse(parseState, scope, control)) {
|
||||||
|
parseState.restore(i1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i2 = parseState.mark();
|
||||||
|
T object = parseState.parse(this.element);
|
||||||
|
if (object == null) {
|
||||||
|
if (flag) {
|
||||||
|
parseState.restore(i2);
|
||||||
|
} else {
|
||||||
|
if (!this.allowTrailingSeparator) {
|
||||||
|
parseState.restore(i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseState.restore(i2);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(object);
|
||||||
|
flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.size() < this.minRepetitions) {
|
||||||
|
parseState.restore(i);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
scope.put(this.listName, list);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record Sequence<S>(Term<S>[] elements) implements Term<S> {
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<S> parseState, Scope scope, Control control) {
|
||||||
|
int i = parseState.mark();
|
||||||
|
|
||||||
|
for (Term<S> term : this.elements) {
|
||||||
|
if (!term.parse(parseState, scope, control)) {
|
||||||
|
parseState.restore(i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse.grammar;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public record Grammar<T>(Dictionary<StringReader> rules, NamedRule<StringReader, T> top) {
|
||||||
|
public Grammar {
|
||||||
|
rules.checkAllBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<T> parse(ParseState<StringReader> parseState) {
|
||||||
|
return parseState.parseTopRule(this.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T parse(StringReader reader) throws CommandSyntaxException {
|
||||||
|
ErrorCollector.LongestOnly<StringReader> longestOnly = new ErrorCollector.LongestOnly<>();
|
||||||
|
StringReaderParserState stringReaderParserState = new StringReaderParserState(longestOnly, reader);
|
||||||
|
Optional<T> optional = this.parse(stringReaderParserState);
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
T result = optional.get();
|
||||||
|
if (CachedParseState.JAVA_NULL_VALUE_MARKER.equals(result)) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
List<ErrorEntry<StringReader>> list = longestOnly.entries();
|
||||||
|
List<Exception> list1 = list.stream().<Exception>mapMulti((errorEntry, consumer) -> {
|
||||||
|
if (errorEntry.reason() instanceof DelayedException<?> delayedException) {
|
||||||
|
consumer.accept(delayedException.create(reader.getString(), errorEntry.cursor()));
|
||||||
|
} else if (errorEntry.reason() instanceof Exception exception1) {
|
||||||
|
consumer.accept(exception1);
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
for (Exception exception : list1) {
|
||||||
|
if (exception instanceof CommandSyntaxException commandSyntaxException) {
|
||||||
|
throw commandSyntaxException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list1.size() == 1 && list1.getFirst() instanceof RuntimeException runtimeException) {
|
||||||
|
throw runtimeException;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Failed to parse: " + list.stream().map(ErrorEntry::toString).collect(Collectors.joining(", ")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse.grammar;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.DelayedException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.ParseState;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.Rule;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class GreedyPatternParseRule implements Rule<StringReader, String> {
|
||||||
|
private final Pattern pattern;
|
||||||
|
private final DelayedException<CommandSyntaxException> error;
|
||||||
|
|
||||||
|
public GreedyPatternParseRule(Pattern pattern, DelayedException<CommandSyntaxException> error) {
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String parse(ParseState<StringReader> parseState) {
|
||||||
|
StringReader stringReader = parseState.input();
|
||||||
|
String string = stringReader.getString();
|
||||||
|
Matcher matcher = this.pattern.matcher(string).region(stringReader.getCursor(), string.length());
|
||||||
|
if (!matcher.lookingAt()) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), this.error);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
stringReader.setCursor(matcher.end());
|
||||||
|
return matcher.group(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse.grammar;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.DelayedException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.ParseState;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.Rule;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public abstract class GreedyPredicateParseRule implements Rule<StringReader, String> {
|
||||||
|
private final int minSize;
|
||||||
|
private final int maxSize;
|
||||||
|
private final DelayedException<CommandSyntaxException> error;
|
||||||
|
|
||||||
|
public GreedyPredicateParseRule(int minSize, DelayedException<CommandSyntaxException> error) {
|
||||||
|
this(minSize, Integer.MAX_VALUE, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GreedyPredicateParseRule(int minSize, int maxSize, DelayedException<CommandSyntaxException> error) {
|
||||||
|
this.minSize = minSize;
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String parse(ParseState<StringReader> parseState) {
|
||||||
|
StringReader stringReader = parseState.input();
|
||||||
|
String string = stringReader.getString();
|
||||||
|
int cursor = stringReader.getCursor();
|
||||||
|
int i = cursor;
|
||||||
|
|
||||||
|
while (i < string.length() && this.isAccepted(string.charAt(i)) && i - cursor < this.maxSize) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i1 = i - cursor;
|
||||||
|
if (i1 < this.minSize) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), this.error);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
stringReader.setCursor(i);
|
||||||
|
return string.substring(cursor, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean isAccepted(char c);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse.grammar;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.DelayedException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.ParseState;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.Rule;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public abstract class NumberRunParseRule implements Rule<StringReader, String> {
|
||||||
|
private final DelayedException<CommandSyntaxException> noValueError;
|
||||||
|
private final DelayedException<CommandSyntaxException> underscoreNotAllowedError;
|
||||||
|
|
||||||
|
public NumberRunParseRule(DelayedException<CommandSyntaxException> noValueError, DelayedException<CommandSyntaxException> underscoreNotAllowedError) {
|
||||||
|
this.noValueError = noValueError;
|
||||||
|
this.underscoreNotAllowedError = underscoreNotAllowedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String parse(ParseState<StringReader> parseState) {
|
||||||
|
StringReader stringReader = parseState.input();
|
||||||
|
stringReader.skipWhitespace();
|
||||||
|
String string = stringReader.getString();
|
||||||
|
int cursor = stringReader.getCursor();
|
||||||
|
int i = cursor;
|
||||||
|
|
||||||
|
while (i < string.length() && this.isAccepted(string.charAt(i))) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i1 = i - cursor;
|
||||||
|
if (i1 == 0) {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), this.noValueError);
|
||||||
|
return null;
|
||||||
|
} else if (string.charAt(cursor) != '_' && string.charAt(i - 1) != '_') {
|
||||||
|
stringReader.setCursor(i);
|
||||||
|
return string.substring(cursor, i);
|
||||||
|
} else {
|
||||||
|
parseState.errorCollector().store(parseState.mark(), this.underscoreNotAllowedError);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean isAccepted(char c);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse.grammar;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.CachedParseState;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.ErrorCollector;
|
||||||
|
|
||||||
|
public class StringReaderParserState extends CachedParseState<StringReader> {
|
||||||
|
private final StringReader input;
|
||||||
|
|
||||||
|
public StringReaderParserState(ErrorCollector<StringReader> errorCollector, StringReader input) {
|
||||||
|
super(errorCollector);
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringReader input() {
|
||||||
|
return this.input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int mark() {
|
||||||
|
return this.input.getCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restore(int cursor) {
|
||||||
|
this.input.setCursor(cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse.grammar;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
||||||
|
import it.unimi.dsi.fastutil.chars.CharList;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.*;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public interface StringReaderTerms {
|
||||||
|
DynamicCommandExceptionType LITERAL_INCORRECT = new LocalizedDynamicCommandExceptionType(
|
||||||
|
expected -> new LocalizedMessage("warning.config.type.snbt.parser.incorrect", String.valueOf(expected))
|
||||||
|
);
|
||||||
|
|
||||||
|
static Term<StringReader> character(final char value) {
|
||||||
|
return new TerminalCharacters(CharList.of(value)) {
|
||||||
|
@Override
|
||||||
|
protected boolean isAccepted(char c) {
|
||||||
|
return value == c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Term<StringReader> characters(final char value1, final char value2) {
|
||||||
|
return new TerminalCharacters(CharList.of(value1, value2)) {
|
||||||
|
@Override
|
||||||
|
protected boolean isAccepted(char c) {
|
||||||
|
return c == value1 || c == value2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static StringReader createReader(String input, int cursor) {
|
||||||
|
StringReader stringReader = new StringReader(input);
|
||||||
|
stringReader.setCursor(cursor);
|
||||||
|
return stringReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class TerminalCharacters implements Term<StringReader> {
|
||||||
|
private final DelayedException<CommandSyntaxException> error;
|
||||||
|
private final SuggestionSupplier<StringReader> suggestions;
|
||||||
|
|
||||||
|
public TerminalCharacters(CharList characters) {
|
||||||
|
String string = characters.intStream().mapToObj(Character::toString).collect(Collectors.joining("|"));
|
||||||
|
this.error = DelayedException.create(LITERAL_INCORRECT, string);
|
||||||
|
this.suggestions = parseState -> characters.intStream().mapToObj(Character::toString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean parse(ParseState<StringReader> parseState, Scope scope, Control control) {
|
||||||
|
parseState.input().skipWhitespace();
|
||||||
|
int i = parseState.mark();
|
||||||
|
if (parseState.input().canRead() && this.isAccepted(parseState.input().read())) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
parseState.errorCollector().store(i, this.suggestions, this.error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean isAccepted(char c);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package net.momirealms.craftengine.core.util.snbt.parse.grammar;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.DelayedException;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.ParseState;
|
||||||
|
import net.momirealms.craftengine.core.util.snbt.parse.Rule;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class UnquotedStringParseRule implements Rule<StringReader, String> {
|
||||||
|
private final int minSize;
|
||||||
|
private final DelayedException<CommandSyntaxException> error;
|
||||||
|
|
||||||
|
public UnquotedStringParseRule(int minSize, DelayedException<CommandSyntaxException> error) {
|
||||||
|
this.minSize = minSize;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String parse(ParseState<StringReader> parseState) {
|
||||||
|
parseState.input().skipWhitespace();
|
||||||
|
int i = parseState.mark();
|
||||||
|
String unquotedString = parseState.input().readUnquotedString();
|
||||||
|
if (unquotedString.length() < this.minSize) {
|
||||||
|
parseState.errorCollector().store(i, this.error);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return unquotedString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
|
|||||||
# Project settings
|
# Project settings
|
||||||
project_version=0.0.65.15
|
project_version=0.0.65.15
|
||||||
config_version=60
|
config_version=60
|
||||||
lang_version=41
|
lang_version=42
|
||||||
project_group=net.momirealms
|
project_group=net.momirealms
|
||||||
latest_supported_version=1.21.10
|
latest_supported_version=1.21.10
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user