Completely rework SNBT Parser
(NOTE: ChatGPT Was used for regex only)
This commit is contained in:
parent
7c87ef444f
commit
84192c69db
11 changed files with 150 additions and 107 deletions
|
@ -4,7 +4,6 @@
|
|||
This tool will generate a HTML report for the base path specified.
|
||||
*/
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:libac_dart/argparse/Args.dart';
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
class Constants {
|
||||
static const VERSION = "1.2.082424+1243";
|
||||
static const VERSION = "1.2.082924+0654";
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'Tag.dart';
|
|||
import 'impl/CompoundTag.dart';
|
||||
|
||||
class SnbtIo {
|
||||
static void writeToFile(String file, CompoundTag tag) {
|
||||
static Future<void> writeToFile(String file, CompoundTag tag) async {
|
||||
File handle = File(file);
|
||||
|
||||
if (handle.existsSync())
|
||||
|
@ -13,7 +13,7 @@ class SnbtIo {
|
|||
|
||||
StringBuilder builder = StringBuilder();
|
||||
Tag.writeStringifiedNamedTag(tag, builder, 0);
|
||||
handle.writeAsString(builder.toString());
|
||||
await handle.writeAsString(builder.toString());
|
||||
}
|
||||
|
||||
static Future<Tag> readFromFile(String file) async {
|
||||
|
@ -21,7 +21,15 @@ class SnbtIo {
|
|||
String data = await fi.readAsString();
|
||||
StringReader reader = StringReader(data);
|
||||
|
||||
return Tag.readStringifiedNamedTag(reader);
|
||||
Tag tag = CompoundTag();
|
||||
try {
|
||||
tag = Tag.readStringifiedNamedTag(reader);
|
||||
} catch (E, stack) {
|
||||
print("FATAL ERROR OCCURED AT LOCATION:\n${reader.getSnapshot()}");
|
||||
print(E);
|
||||
print(stack);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
static String writeToString(CompoundTag tag) {
|
||||
|
|
|
@ -417,8 +417,12 @@ class StringReader {
|
|||
|
||||
StringReader(this._buffer);
|
||||
|
||||
// Check if there's more to read
|
||||
bool get canRead => _position < _buffer.length;
|
||||
/// Check if there's more to read
|
||||
bool get canRead => _canRead();
|
||||
bool _canRead() {
|
||||
skipWhitespace();
|
||||
return _position < _buffer.length;
|
||||
}
|
||||
|
||||
// Get the number of chars seeked
|
||||
int get getSeeked => _lastPostion - _position;
|
||||
|
@ -433,6 +437,18 @@ class StringReader {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generates a snapshot of the text location if applicable
|
||||
String getSnapshot() {
|
||||
if (canRead) {
|
||||
if (_position + 64 < _buffer.length) {
|
||||
return _buffer.substring(_position, _position + 64);
|
||||
} else {
|
||||
return _buffer.substring(_position);
|
||||
}
|
||||
} else
|
||||
return "";
|
||||
}
|
||||
|
||||
// Peek the next character without advancing the position
|
||||
String peek() {
|
||||
skipWhitespace();
|
||||
|
@ -446,7 +462,7 @@ class StringReader {
|
|||
// Skip any whitespace characters
|
||||
void skipWhitespace() {
|
||||
if (_quotedString) return; // We need whitespace for strings
|
||||
while (canRead && isWhitespace(_buffer[_position])) {
|
||||
while ((_position < _buffer.length) && isWhitespace(_buffer[_position])) {
|
||||
_position++;
|
||||
}
|
||||
}
|
||||
|
@ -457,10 +473,12 @@ class StringReader {
|
|||
}
|
||||
|
||||
// Read until a specific character is found
|
||||
String readUntil(String stopChar) {
|
||||
String readUntil(String stopChar, int maxChars) {
|
||||
StringBuffer result = StringBuffer();
|
||||
while (canRead && peek() != stopChar) {
|
||||
int index = 0;
|
||||
while (canRead && peek() != stopChar && index < maxChars) {
|
||||
result.write(next());
|
||||
index++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
|
173
lib/nbt/Tag.dart
173
lib/nbt/Tag.dart
|
@ -1,3 +1,5 @@
|
|||
import 'dart:core';
|
||||
|
||||
import 'Stream.dart';
|
||||
import 'impl/ByteArrayTag.dart';
|
||||
import 'impl/ByteTag.dart';
|
||||
|
@ -42,110 +44,113 @@ enum TagType {
|
|||
}
|
||||
|
||||
static const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
static TagType getStringifiedTagType(StringReader reader) {
|
||||
reader.startSeek();
|
||||
reader.startSeek(); // Enter fake read mode
|
||||
TagType ret = TagType.End;
|
||||
bool isNumber = true;
|
||||
bool isAlpha = true; // Is digits only
|
||||
bool isQuoted = false;
|
||||
bool isNumeric = true; // Assume true until proven otherwise
|
||||
bool isAlpha = true; // Assume true until proven otherwise
|
||||
StringBuffer buffer = StringBuffer();
|
||||
|
||||
// Start to determine the next tag type
|
||||
while (reader.canRead && ret == TagType.End) {
|
||||
var val = reader.next().toUpperCase();
|
||||
if (ALPHABET.indexOf(val) == -1) isAlpha = false;
|
||||
while (reader.canRead) {
|
||||
var val =
|
||||
reader.peek(); // Peek at the next character without consuming it
|
||||
|
||||
switch (val) {
|
||||
case "{":
|
||||
{
|
||||
ret = TagType.Compound;
|
||||
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;
|
||||
}
|
||||
case "[":
|
||||
{
|
||||
// Check for a type Prefix
|
||||
var X = reader.readUntil(";");
|
||||
switch (X.toUpperCase()) {
|
||||
|
||||
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;
|
||||
ret = TagType.List; // No type prefix, it's a List
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "B":
|
||||
{
|
||||
ret = TagType.Byte;
|
||||
break;
|
||||
}
|
||||
case "D":
|
||||
{
|
||||
ret = TagType.Double;
|
||||
break;
|
||||
}
|
||||
case "F":
|
||||
{
|
||||
ret = TagType.Float;
|
||||
break;
|
||||
}
|
||||
case "I":
|
||||
{
|
||||
ret = TagType.Int;
|
||||
break;
|
||||
}
|
||||
case "L":
|
||||
{
|
||||
ret = TagType.Long;
|
||||
break;
|
||||
}
|
||||
case "S":
|
||||
{
|
||||
ret = TagType.Short;
|
||||
break;
|
||||
}
|
||||
case "\"":
|
||||
{
|
||||
ret = TagType.String;
|
||||
break;
|
||||
}
|
||||
case ",":
|
||||
case "\n":
|
||||
{
|
||||
if (reader.getSeeked >= 1) ret = TagType.String;
|
||||
if (isNumber) ret = TagType.Int;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!reader.isDigit(val)) {
|
||||
if (isNumber) isNumber = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader.endSeek();
|
||||
if (!isNumber && isAlpha) ret = TagType.String;
|
||||
|
||||
reader.endSeek(); // Restore the original stream position
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Adjusting numeric and alphabetic checks
|
||||
var current = reader.next(); // Consume the character
|
||||
|
||||
buffer.write(current);
|
||||
|
||||
// Check if current character is a digit or numeric suffix
|
||||
if (!RegExp(r'^[0-9]$').hasMatch(current) &&
|
||||
!RegExp(r'^[sSbBiIlLfFdD]$').hasMatch(current)) {
|
||||
isNumeric = false; // Not purely numeric with possible 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 {
|
||||
|
@ -264,7 +269,7 @@ abstract class Tag {
|
|||
|
||||
bool shouldQuote(String value) {
|
||||
if (value == "") {
|
||||
return false;
|
||||
return true;
|
||||
} else {
|
||||
String letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:libac_dart/nbt/Stream.dart';
|
||||
|
||||
class list {
|
||||
List<dynamic> _list = [];
|
||||
|
|
|
@ -28,7 +28,7 @@ class string {
|
|||
String toString() => value ?? '';
|
||||
|
||||
// Conversion to bool
|
||||
bool toBool() => value != null && value.isNotEmpty;
|
||||
bool toBool() => value.isNotEmpty;
|
||||
|
||||
// Equality operator
|
||||
@override
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:libac_dart/consts.dart';
|
||||
|
||||
class PathHelper {
|
||||
String pth = "";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: libac_dart
|
||||
description: "Aria's Creations code library"
|
||||
version: 1.2.082424+1243
|
||||
version: 1.2.082924+0654
|
||||
homepage: "https://zontreck.com"
|
||||
|
||||
|
||||
|
|
BIN
test/EL-ReducedList.dat
Normal file
BIN
test/EL-ReducedList.dat
Normal file
Binary file not shown.
|
@ -108,4 +108,19 @@ void main() {
|
|||
expect(ID.MSB, 0);
|
||||
expect(ID.LSB, 0);
|
||||
});
|
||||
|
||||
test("Convert real-world NBT to SNBT and back to NBT", () async {
|
||||
String OriginFile = "${Directory.current.path}/test/EL-ReducedList.dat";
|
||||
String OutputSNBT = "${Directory.current.path}/build/el-test.snbt";
|
||||
String OutputNBT = "${Directory.current.path}/build/el-test.nbt";
|
||||
|
||||
CompoundTag tag = await NbtIo.read(OriginFile);
|
||||
await SnbtIo.writeToFile(OutputSNBT, tag);
|
||||
|
||||
expect(File(OutputSNBT).existsSync(), true);
|
||||
tag = await SnbtIo.readFromFile(OutputSNBT) as CompoundTag;
|
||||
|
||||
await NbtIo.write(OutputNBT, tag);
|
||||
expect(File(OutputNBT).existsSync(), true);
|
||||
}, timeout: Timeout(Duration(hours: 90)));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue