diff --git a/SharpNBT/SNBT/StringNbt.cs b/SharpNBT/SNBT/StringNbt.cs index c35f190..3619ff9 100644 --- a/SharpNBT/SNBT/StringNbt.cs +++ b/SharpNBT/SNBT/StringNbt.cs @@ -24,13 +24,54 @@ public static class StringNbt public static CompoundTag Parse(string source) { var bytes = Encoding.UTF8.GetBytes(source); - var scanner = new Scanner(bytes, Encoding.UTF8); - + return Parse(bytes, Encoding.UTF8); + } + + /// + /// Parse the given text into a . + /// + /// A string containing the SNBT code to parse. + /// The instance described in the source text. + /// When is . + /// When is invalid SNBT code. + public static ListTag ParseList(string source) + { + var bytes = Encoding.UTF8.GetBytes(source); + return ParseList(bytes, Encoding.UTF8); + } + + /// + /// Parse the given text into a . + /// + /// A string containing the SNBT code to parse. + /// Encoding of the . + /// The instance described in the source text. + /// When is . + /// When is invalid SNBT code. + public static CompoundTag Parse(ReadOnlySpan source, Encoding? encoding = null) + { + var scanner = new Scanner(source, encoding ?? Encoding.UTF8); scanner.MoveNext(true, true); scanner.AssertChar('{'); return ParseCompound(null, ref scanner); } - + + /// + /// Parse the given text into a . + /// + /// A string containing the SNBT code to parse. + /// Encoding of the . + /// The instance described in the source text. + /// When is . + /// When is invalid SNBT code. + public static ListTag ParseList(ReadOnlySpan source, Encoding? encoding = null) + { + var scanner = new Scanner(source, encoding ?? Encoding.UTF8); + scanner.MoveNext(true, true); + scanner.AssertChar('['); + return ParseList(null, ref scanner); + } + private static CompoundTag ParseCompound(string? name, ref Scanner scanner) { scanner.MoveNext(true, true); @@ -40,23 +81,20 @@ public static class StringNbt if (scanner.Current == '}') return result; - while (true) + while (!scanner.IsEndOfInput) { // Read the name of the tag var childName = ParseString(ref scanner, out _); // Move to and asser the next significant character is a deliminator. - // scanner.MoveNext(true, true); + scanner.MoveNext(true, true); scanner.AssertChar(':'); // Move to and parse the tag value scanner.MoveNext(true, true); var tag = ParseTag(childName, ref scanner); result.Add(tag); - // scanner.MoveNext(true, true); - - if (char.IsWhiteSpace(scanner.Current)) - scanner.MoveNext(true, true); + scanner.MoveNext(true, true); // Comma encountered, read another tag. if (scanner.Current == ',') @@ -68,7 +106,7 @@ public static class StringNbt // Closing brace encountered, break loop. if (scanner.Current == '}') { - scanner.MoveNext(true, false); + // scanner.MoveNext(true, false); break; } @@ -79,6 +117,15 @@ public static class StringNbt return result; } + /// + /// Parses the next logical chunk of data as a string. + /// + /// A reference to the context. + /// + /// Flag indicating if the value was enclosed in a pair of matching single/double quotes, otherwise + /// if it was read as a literal span of characters from the input. + /// + /// The scanned string. private static string ParseString(ref Scanner scanner, out bool quoted) { var quote = scanner.Current; @@ -105,7 +152,6 @@ public static class StringNbt if (scanner.Current == quote) { closed = true; - scanner.Position++; break; } @@ -129,8 +175,12 @@ public static class StringNbt var start = scanner.Position; for (var length = 0; scanner.MoveNext(false, true); length++) { - if (!AllowedInUnquoted(scanner.Current)) - return new string(scanner.Source.Slice(start, length + 1)); + if (AllowedInUnquoted(scanner.Current)) + continue; + + // Step back one character so not to consume the one that failed the permission check. + scanner.Position--; + return new string(scanner.Source.Slice(start, length + 1)); } return string.Empty; @@ -179,11 +229,10 @@ public static class StringNbt return new IntTag(name, hex); } - // When all else fails, assume it is an unquoted string + // When all else fails, is could only have been an unquoted string return new StringTag(name, value); } - - + private static bool TryParseNumber(string? name, string value, char suffix, out Tag tag) { // A much less complicated char.ToLower() @@ -239,34 +288,31 @@ public static class StringNbt if (scanner.Current == ']') return new ListTag(name, TagType.End); - if (scanner.Peek() == ';') + // No type-prefix, must be a ListTag + if (scanner.Peek() != ';') + return ParseList(name, ref scanner); + + // This is an array of integral values + var prefix = scanner.Current; + scanner.Position += 2; + return prefix switch { - // This is an array of integer values - var prefix = scanner.Current; - scanner.Position += 2; - return prefix switch - { - 'B' => new ByteArrayTag(name, ParseArrayValues(ref scanner)), - 'I' => new IntArrayTag(name, ParseArrayValues(ref scanner)), - 'L' => new LongArrayTag(name, ParseArrayValues(ref scanner)), - _ => throw scanner.SyntaxError($"Invalid type specifier. Expected 'B', 'I', or 'L', got '{prefix}'.") - }; - } - - // No prefix, so this must be a list of tags if valid - return ParseList(name, ref scanner); + 'B' => new ByteArrayTag(name, ParseArrayValues(ref scanner)), + 'I' => new IntArrayTag(name, ParseArrayValues(ref scanner)), + 'L' => new LongArrayTag(name, ParseArrayValues(ref scanner)), + _ => throw scanner.SyntaxError($"Invalid type specifier. Expected 'B', 'I', or 'L', got '{prefix}'.") + }; } - private static Tag ParseList(string? name, ref Scanner scanner) + private static ListTag ParseList(string? name, ref Scanner scanner) { var list = new List(); while (true) { var child = ParseTag(null, ref scanner); list.Add(child); - - if (char.IsWhiteSpace(scanner.Current)) - scanner.MoveNext(true, true); + + scanner.MoveNext(true, true); // Comma encountered, read another tag. if (scanner.Current == ',') @@ -278,7 +324,6 @@ public static class StringNbt // Closing brace encountered, break loop. if (scanner.Current == ']') { - scanner.MoveNext(true, false); break; } @@ -295,7 +340,6 @@ public static class StringNbt // Early-out for [] if (scanner.Current == ']') { - scanner.Position++; return Array.Empty(); } @@ -317,8 +361,7 @@ public static class StringNbt var values = new T[strings.Length]; for (var i = 0; i < values.Length; i++) values[i] = T.Parse(strings[i], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); - - scanner.Position++; // Consume the closing ']' + return values; }