Implement basic SNBT reader and testsuite
This commit is contained in:
parent
396c660113
commit
a0f372693b
17 changed files with 352 additions and 2 deletions
|
@ -5,7 +5,7 @@ import 'Tag.dart';
|
|||
import 'impl/CompoundTag.dart';
|
||||
|
||||
class SnbtIo {
|
||||
static void write(String file, CompoundTag tag) {
|
||||
static void writeToFile(String file, CompoundTag tag) {
|
||||
File handle = File(file);
|
||||
|
||||
if (handle.existsSync())
|
||||
|
@ -15,4 +15,22 @@ class SnbtIo {
|
|||
Tag.writeStringifiedNamedTag(tag, builder, 0);
|
||||
handle.writeAsString(builder.toString());
|
||||
}
|
||||
|
||||
static Future<Tag> readFromFile(String file) async {
|
||||
File fi = File(file);
|
||||
String data = await fi.readAsString();
|
||||
StringReader reader = StringReader(data);
|
||||
|
||||
return Tag.readStringifiedNamedTag(reader);
|
||||
}
|
||||
|
||||
static String writeToString(CompoundTag tag) {
|
||||
StringBuilder builder = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(tag, builder, 0);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static Future<Tag> readFromString(String data) async {
|
||||
return Tag.readStringifiedNamedTag(StringReader(data));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -408,3 +408,124 @@ class StringBuilder {
|
|||
return "${_buffer}";
|
||||
}
|
||||
}
|
||||
|
||||
class StringReader {
|
||||
final String _buffer;
|
||||
int _position = 0;
|
||||
int _lastPostion = 0;
|
||||
|
||||
StringReader(this._buffer);
|
||||
|
||||
// Check if there's more to read
|
||||
bool get canRead => _position < _buffer.length;
|
||||
|
||||
// Read the next character
|
||||
String next() {
|
||||
if (canRead) {
|
||||
skipWhitespace();
|
||||
return _buffer[_position++];
|
||||
} else {
|
||||
throw Exception("End of buffer reached");
|
||||
}
|
||||
}
|
||||
|
||||
// Peek the next character without advancing the position
|
||||
String peek() {
|
||||
skipWhitespace();
|
||||
if (canRead) {
|
||||
return _buffer[_position];
|
||||
} else {
|
||||
throw Exception("End of buffer reached");
|
||||
}
|
||||
}
|
||||
|
||||
// Skip any whitespace characters
|
||||
void skipWhitespace() {
|
||||
while (canRead && isWhitespace(_buffer[_position])) {
|
||||
_position++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a character is a whitespace
|
||||
bool isWhitespace(String char) {
|
||||
return char.trim().isEmpty;
|
||||
}
|
||||
|
||||
// Read until a specific character is found
|
||||
String readUntil(String stopChar) {
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead && peek() != stopChar) {
|
||||
result.write(next());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Read a string enclosed in double quotes
|
||||
String readQuotedString() {
|
||||
if (next() != '"') {
|
||||
throw Exception('Expected double quotes at the start of a string');
|
||||
}
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead) {
|
||||
String char = next();
|
||||
if (char == '"') {
|
||||
break;
|
||||
}
|
||||
result.write(char);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Read a number (int or double)
|
||||
String readNumber() {
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead && (isDigit(peek()) || peek() == '.' || peek() == '-')) {
|
||||
result.write(next());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Check if a character is a digit
|
||||
bool isDigit(String char) {
|
||||
return int.tryParse(char) != null;
|
||||
}
|
||||
|
||||
// Read an unquoted string (used for keys in SNBT)
|
||||
String readUnquotedString() {
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead &&
|
||||
!isWhitespace(peek()) &&
|
||||
peek() != ':' &&
|
||||
peek() != ',' &&
|
||||
peek() != '{' &&
|
||||
peek() != '}' &&
|
||||
peek() != '[' &&
|
||||
peek() != ']') {
|
||||
result.write(next());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
String readString() {
|
||||
if (peek() == "\"") {
|
||||
return readQuotedString();
|
||||
} else
|
||||
return readUnquotedString();
|
||||
}
|
||||
|
||||
// Read a specific character and throw an exception if it's not found
|
||||
void expect(String expectedChar) {
|
||||
if (next().toLowerCase() != expectedChar.toLowerCase()) {
|
||||
throw Exception('Expected $expectedChar');
|
||||
}
|
||||
}
|
||||
|
||||
void startSeek() {
|
||||
_lastPostion = _position;
|
||||
}
|
||||
|
||||
void endSeek() {
|
||||
_position = _lastPostion;
|
||||
_lastPostion = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,79 @@ enum TagType {
|
|||
|
||||
return TagType.End;
|
||||
}
|
||||
|
||||
static TagType getStringifiedTagType(StringReader reader) {
|
||||
reader.startSeek();
|
||||
TagType ret = TagType.End;
|
||||
|
||||
// Start to determine the next tag type
|
||||
while (reader.canRead && ret == TagType.End) {
|
||||
var val = reader.next().toUpperCase();
|
||||
switch (val) {
|
||||
case "{":
|
||||
{
|
||||
ret = TagType.Compound;
|
||||
break;
|
||||
}
|
||||
case "[":
|
||||
{
|
||||
// Check for a type Prefix
|
||||
var X = reader.readUntil(";");
|
||||
switch (X.toUpperCase()) {
|
||||
case "B":
|
||||
{
|
||||
ret = TagType.ByteArray;
|
||||
break;
|
||||
}
|
||||
case "I":
|
||||
{
|
||||
ret = TagType.IntArray;
|
||||
break;
|
||||
}
|
||||
case "L":
|
||||
{
|
||||
ret = TagType.LongArray;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
ret = TagType.List;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "B":
|
||||
{
|
||||
ret = TagType.Byte;
|
||||
break;
|
||||
}
|
||||
case "D":
|
||||
{
|
||||
ret = TagType.Double;
|
||||
break;
|
||||
}
|
||||
case "F":
|
||||
{
|
||||
ret = TagType.Float;
|
||||
break;
|
||||
}
|
||||
case "L":
|
||||
{
|
||||
ret = TagType.Long;
|
||||
break;
|
||||
}
|
||||
case "S":
|
||||
{
|
||||
ret = TagType.Short;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader.endSeek();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Tag {
|
||||
|
@ -100,6 +173,23 @@ abstract class Tag {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
tag.readStringifiedValue(string);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
bool shouldQuoteName() {
|
||||
if (getKey() == "") {
|
||||
return false;
|
||||
|
@ -116,6 +206,7 @@ abstract class Tag {
|
|||
}
|
||||
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList);
|
||||
void readStringifiedValue(StringReader reader);
|
||||
|
||||
bool equals(dynamic object) {
|
||||
if (object == null || object is! Tag) return false;
|
||||
|
|
|
@ -52,4 +52,16 @@ class ByteArrayTag extends Tag {
|
|||
builder.append(
|
||||
"${isList ? "".padLeft(indent, '\t') : ""}[B; ${value.join("B, ")}B]");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("[");
|
||||
while (reader.peek() != "]") {
|
||||
value.add(int.parse(reader.readNumber()));
|
||||
reader.expect("b");
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
reader.expect("]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,4 +39,12 @@ class ByteTag extends Tag {
|
|||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${value}b");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
String val = reader.readNumber();
|
||||
value = int.parse(val);
|
||||
|
||||
reader.expect("b");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,4 +190,16 @@ class CompoundTag extends Tag implements Map<String, Tag> {
|
|||
|
||||
@override
|
||||
Iterable<Tag> get values => value.values;
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("{");
|
||||
|
||||
while (reader.peek() != "}") {
|
||||
Tag tag = Tag.readStringifiedNamedTag(reader);
|
||||
put(tag.getKey(), tag);
|
||||
}
|
||||
|
||||
reader.expect("}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,4 +39,12 @@ class DoubleTag extends Tag {
|
|||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}d");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
double val = double.parse(reader.readNumber());
|
||||
value = val;
|
||||
|
||||
reader.expect("d");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,4 +22,7 @@ class EndTag extends Tag {
|
|||
|
||||
@override
|
||||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {}
|
||||
}
|
||||
|
|
|
@ -38,4 +38,12 @@ class FloatTag extends Tag {
|
|||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}f");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
double val = double.parse(reader.readNumber());
|
||||
value = val;
|
||||
|
||||
reader.expect("f");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,4 +51,15 @@ class IntArrayTag extends Tag {
|
|||
builder.append(
|
||||
"${isList ? "".padLeft(indent, '\t') : ""}[I; ${value.join('I, ')}I]");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("[");
|
||||
while (reader.peek() != "]") {
|
||||
value.add(int.parse(reader.readNumber()));
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
reader.expect("]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,4 +38,10 @@ class IntTag extends Tag {
|
|||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}i");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
String val = reader.readNumber();
|
||||
value = int.parse(val);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,4 +103,15 @@ class ListTag extends Tag {
|
|||
}
|
||||
builder.append("\n${"".padLeft(indent - 1, '\t')}]");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("[");
|
||||
while (reader.peek() != "]") {
|
||||
value.add(Tag.readStringifiedNamedTag(reader));
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
reader.expect("]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,4 +55,16 @@ class LongArrayTag extends Tag {
|
|||
builder.append(
|
||||
"${isList ? "".padLeft(indent, '\t') : ""}[L; ${value.join('L, ')}L]");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
reader.expect("[");
|
||||
while (reader.peek() != "]") {
|
||||
value.add(int.parse(reader.readNumber()));
|
||||
reader.expect("l");
|
||||
|
||||
if (reader.peek() == ",") reader.next();
|
||||
}
|
||||
reader.expect("]");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,4 +38,12 @@ class LongTag extends Tag {
|
|||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}L");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
int val = int.parse(reader.readNumber());
|
||||
value = val;
|
||||
|
||||
reader.expect("l");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,4 +38,12 @@ class ShortTag extends Tag {
|
|||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}${value}S");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
int val = int.parse(reader.readNumber());
|
||||
value = val;
|
||||
|
||||
reader.expect("s");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,4 +38,10 @@ class StringTag extends Tag {
|
|||
void writeStringifiedValue(StringBuilder builder, int indent, bool isList) {
|
||||
builder.append("${isList ? "".padLeft(indent, '\t') : ""}\"${value}\"");
|
||||
}
|
||||
|
||||
@override
|
||||
void readStringifiedValue(StringReader reader) {
|
||||
String str = reader.readString();
|
||||
value = str;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,12 +82,19 @@ void main() {
|
|||
await NbtIo.read("${Directory.current.path}/test/bigtest.nbt");
|
||||
String output = "${Directory.current.path}/build/bigtest.snbt";
|
||||
File file = File(output);
|
||||
SnbtIo.write(output, ct);
|
||||
SnbtIo.writeToFile(output, ct);
|
||||
|
||||
// Expect that the file exists
|
||||
expect(file.existsSync(), true);
|
||||
});
|
||||
|
||||
test("Read BigTest from SNBT file", () async {
|
||||
CompoundTag tag = await SnbtIo.readFromFile(
|
||||
"${Directory.current.path}/build/bigtest.snbt") as CompoundTag;
|
||||
|
||||
expect(tag.containsKey("stringTest"), true);
|
||||
});
|
||||
|
||||
test("Write NULL UUID to NBT", () async {
|
||||
CompoundTag tag = CompoundTag();
|
||||
NbtUtils.writeUUID(tag, "test", NbtUUID.fromUUID(UUID.ZERO));
|
||||
|
|
Loading…
Reference in a new issue