Completely rework SNBT Parser

(NOTE: ChatGPT Was used for regex only)
This commit is contained in:
zontreck 2024-08-29 06:55:12 -07:00
parent 7c87ef444f
commit 84192c69db
11 changed files with 150 additions and 107 deletions

View file

@ -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';

View file

@ -1,3 +1,3 @@
class Constants {
static const VERSION = "1.2.082424+1243";
static const VERSION = "1.2.082924+0654";
}

View file

@ -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) {

View file

@ -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();
}

View file

@ -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++) {

View file

@ -1,4 +1,3 @@
import 'package:libac_dart/nbt/Stream.dart';
class list {
List<dynamic> _list = [];

View file

@ -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

View file

@ -2,7 +2,6 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:libac_dart/consts.dart';
class PathHelper {
String pth = "";

View file

@ -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

Binary file not shown.

View file

@ -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)));
}