LibAC-dart/lib/nbt/Tag.dart

529 lines
12 KiB
Dart

import 'dart:core';
import 'Stream.dart';
import 'impl/ByteArrayTag.dart';
import 'impl/ByteTag.dart';
import 'impl/CompoundTag.dart';
import 'impl/DoubleTag.dart';
import 'impl/EndTag.dart';
import 'impl/FloatTag.dart';
import 'impl/IntArrayTag.dart';
import 'impl/IntTag.dart';
import 'impl/ListTag.dart';
import 'impl/LongArrayTag.dart';
import 'impl/LongTag.dart';
import 'impl/ShortTag.dart';
import 'impl/StringTag.dart';
enum TagType {
End(0),
Byte(1),
Short(2),
Int(3),
Long(4),
Float(5),
Double(6),
ByteArray(7),
String(8),
List(9),
Compound(10),
IntArray(11),
LongArray(12);
final int byte;
const TagType(this.byte);
static TagType get(int byte) {
for (TagType type in values) {
if (type.byte == byte) {
return type;
}
}
return TagType.End;
}
static const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static TagType getStringifiedTagType(StringReader reader) {
reader.startSeek(); // Enter fake read mode
TagType ret = TagType.End;
bool isQuoted = false;
bool isNumeric = true; // Assume true until proven otherwise
bool isAlpha = true; // Assume true until proven otherwise
bool hasDecimalPoint = false;
StringBuffer buffer = StringBuffer();
while (reader.canRead) {
var val =
reader.peek(); // Peek at the next character without consuming it
if (val == ',' || val == '\n' || val == "]" || val == "}") {
break; // Stop at comma or newline
}
if (val == '"') {
reader.next(); // Consume the quote character
isQuoted = true; // Toggle quoted state
break;
}
if (val == "'") {
reader.next();
isQuoted = true;
break;
}
if (val == "-" || val == "+") {
reader.next();
}
if (val == '{') {
ret = TagType.Compound; // Detected a CompoundTag
reader.next(); // Consume '{'
reader.endSeek(); // Restore the original stream position
return ret;
}
if (val == '[') {
reader.next(); // Consume '['
// Peek ahead to differentiate between List and Array
var prefix = reader.readUntil(";", 2).toUpperCase();
switch (prefix) {
case "B":
ret = TagType.ByteArray;
break;
case "I":
ret = TagType.IntArray;
break;
case "L":
ret = TagType.LongArray;
break;
default:
ret = TagType.List; // No type prefix, it's a List
break;
}
reader.endSeek(); // Restore the original stream position
return ret;
}
// Adjusting numeric and alphabetic checks
var current = reader.next(); // Consume the character
buffer.write(current);
// Updated check to allow digits, decimal points, and numeric suffixes
if (!RegExp(r'^[0-9]$').hasMatch(current)) {
if (current == '.' && !hasDecimalPoint) {
hasDecimalPoint = true; // Allow only one decimal point
} else if (!RegExp(r'^[sSbBiIlLfFdD]$').hasMatch(current)) {
isNumeric = false; // Not purely numeric or allowed decimal/suffix
}
}
// Check if current character is purely alphabetical
if (!RegExp(r'^[A-Za-z]$').hasMatch(current)) {
isAlpha = false; // Not purely alphabetical
}
}
var input = buffer.toString().trim();
reader.endSeek(); // Restore the original stream position
if (input.isEmpty) {
return TagType.String; // No input detected
}
if (isQuoted) {
return TagType.String; // Quoted string
}
if (isNumeric) {
// Check the last character for type indicator (only for numeric input)
var lastChar = input.substring(input.length - 1).toUpperCase();
switch (lastChar) {
case 'S':
return TagType.Short;
case 'B':
return TagType.Byte;
case 'I':
return TagType.Int;
case 'F':
return TagType.Float;
case 'D':
return TagType.Double;
case 'L':
return TagType.Long;
default:
return TagType
.Int; // Default to Int if purely numeric with no specific suffix
}
} else if (isAlpha && !input.contains(' ')) {
return TagType.String; // Unquoted purely alphabetical string
} else {
return TagType.String; // Unquoted string with mixed content or spaces
}
}
}
abstract class Tag {
int getType() {
return getTagType().byte;
}
TagType getTagType();
TagType _parentTagType = TagType.End;
Tag? _parentTag;
bool get hasParent => _parentTag != null;
void updateParent(Tag? tag) {
if (tag == null) {
_parentTag = null;
setParentTagType(TagType.End);
} else {
_parentTag = tag;
setParentTagType(tag.getTagType());
}
}
Tag? get getParent => _parentTag;
TagType get parentTagType => _parentTagType;
void setParentTagType(TagType type) {
_parentTagType = type;
}
void writeValue(ByteLayer data);
void readValue(ByteLayer data);
String? _key;
String getKey() {
return (_key == null ? "" : _key!);
}
void setKey(String key) {
_key = key;
}
dynamic getValue();
void setValue(dynamic val);
static Tag readNamedTag(ByteLayer data) {
var type = data.readByte();
if (type == 0) {
return EndTag();
} else {
Tag tag = makeTagOfType(TagType.get(type));
tag._key = data.readString();
tag.readValue(data);
return tag;
}
}
static void writeNamedTag(Tag tag, ByteLayer data) {
data.writeByte(tag.getType());
if (tag.getType() != 0) {
data.writeString(tag.getKey());
tag.writeValue(data);
}
}
static void writeStringifiedNamedTag(
Tag tag, StringBuilder builder, int indents) {
if (tag.getType() != 0) {
if (!builder.isEmpty) {
// Write name
if (tag._key == "") {
builder.append("${"".padLeft(indents, '\t')}");
} else {
if (tag.shouldQuoteName()) {
builder.append("${"".padLeft(indents, "\t")}\"${tag.getKey()}\": ");
} else
builder.append("${"".padLeft(indents, '\t')}${tag.getKey()}: ");
}
}
tag.writeStringifiedValue(builder, indents + 1, false);
}
}
static Tag readStringifiedNamedTag(StringReader string) {
String name = "";
if (string.peek() == "{" || string.peek() == "[") {
// No name
name = "";
} else {
name = string.readString();
string.expect(":");
}
TagType type = TagType.getStringifiedTagType(string);
Tag tag = Tag.makeTagOfType(type);
tag._key = name;
try {
tag.readStringifiedValue(string);
} catch (E, stack) {
print(E);
print(string.getSnapshot());
print(stack);
}
return tag;
}
bool shouldQuoteName() {
if (getKey() == "") {
return false;
} else {
String letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (int i = 0; i < getKey().length; i++) {
String digit = getKey().substring(i, i + 1);
if (letters.indexOf(digit) == -1) {
return true;
}
}
return false;
}
}
bool shouldQuote(String value) {
if (value == "") {
return true;
} else {
String letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (int i = 0; i < value.length; i++) {
String digit = value.substring(i, i + 1);
if (letters.indexOf(digit) == -1) {
return true;
}
}
return false;
}
}
bool shouldUseSingleQuotes(String value) {
return value.contains("\"");
}
bool shouldEscapeSingleQuotes(String value) {
return value.contains("'");
}
void writeStringifiedValue(StringBuilder builder, int indent, bool isList);
void readStringifiedValue(StringReader reader);
bool equals(dynamic object) {
if (object == null || object is! Tag) return false;
Tag tag = object;
if (tag.getType() != getType()) return false;
if (!(getKey() == tag.getKey())) return false;
return true;
}
static Tag makeTagOfType(TagType type) {
switch (type) {
case TagType.Byte:
{
return ByteTag();
}
case TagType.ByteArray:
{
return ByteArrayTag();
}
case TagType.Compound:
{
return CompoundTag();
}
case TagType.Double:
{
return DoubleTag();
}
case TagType.End:
{
return EndTag();
}
case TagType.Short:
{
return ShortTag();
}
case TagType.Int:
{
return IntTag();
}
case TagType.Long:
{
return LongTag();
}
case TagType.Float:
{
return FloatTag();
}
case TagType.IntArray:
{
return IntArrayTag();
}
case TagType.LongArray:
{
return LongArrayTag();
}
case TagType.List:
{
return ListTag();
}
case TagType.String:
{
return StringTag();
}
}
}
int asByte() {
if (this is ByteTag) {
return (this as ByteTag).value;
} else {
return 0;
}
}
List<int> asByteArray() {
if (this is ByteArrayTag) {
return (this as ByteArrayTag).value;
} else {
return [];
}
}
double asDouble() {
if (this is DoubleTag) {
return (this as DoubleTag).value;
} else {
return 0.0;
}
}
double asFloat() {
if (this is FloatTag) {
return (this as FloatTag).value;
} else {
return 0.0;
}
}
List<int> asIntArray() {
if (this is IntArrayTag) {
return (this as IntArrayTag).value;
} else {
return [];
}
}
int asInt() {
if (this is IntTag) {
return (this as IntTag).value;
} else {
return 0;
}
}
List<int> asLongArray() {
if (this is LongArrayTag) {
return (this as LongArrayTag).value;
} else {
return [];
}
}
int asLong() {
if (this is LongTag) {
return (this as LongTag).value;
} else {
return 0;
}
}
int asShort() {
if (this is ShortTag) {
return (this as ShortTag).value;
} else {
return 0;
}
}
String asString() {
if (this is StringTag) {
return (this as StringTag).value;
} else {
return "";
}
}
CompoundTag asCompoundTag() {
if (this is CompoundTag) {
return this as CompoundTag;
} else
return CompoundTag();
}
void prettyPrint(int indent, bool recurse);
static String getCanonicalName(TagType type) {
switch (type) {
case TagType.String:
return "TAG_String";
case TagType.List:
return "TAG_List";
case TagType.LongArray:
return "TAG_LongArray";
case TagType.Long:
return "TAG_Long";
case TagType.IntArray:
return "TAG_IntArray";
case TagType.Int:
return "TAG_Int";
case TagType.Compound:
return "TAG_Compound";
case TagType.ByteArray:
return "TAG_ByteArray";
case TagType.Byte:
return "TAG_Byte";
case TagType.Double:
return "TAG_Double";
case TagType.Float:
return "TAG_Float";
case TagType.Short:
return "TAG_Short";
case TagType.End:
return "TAG_End";
}
}
}
class NBTAccountant {
static int _prettyIndex = 0;
static void printRead(Tag tag) {
tag.prettyPrint(_prettyIndex, false);
}
static void visitTag() {
_prettyIndex++;
}
static void leaveTag(Tag tag) {
if (tag is CompoundTag || tag is ListTag) {
if (tag is CompoundTag) {
CompoundTag ct = tag;
ct.endPrettyPrint(_prettyIndex);
} else if (tag is ListTag) {
ListTag lt = tag;
lt.endPrettyPrint(_prettyIndex);
}
}
_prettyIndex--;
}
}