Standardized positioning of scanner after reading each data element

This commit is contained in:
Eric Freed 2023-08-27 01:33:28 -04:00
parent f46d240767
commit 05126cee2a
1 changed files with 82 additions and 39 deletions

View File

@ -24,13 +24,54 @@ public static class StringNbt
public static CompoundTag Parse(string source) public static CompoundTag Parse(string source)
{ {
var bytes = Encoding.UTF8.GetBytes(source); var bytes = Encoding.UTF8.GetBytes(source);
var scanner = new Scanner(bytes, Encoding.UTF8); return Parse(bytes, Encoding.UTF8);
}
/// <summary>
/// Parse the given <paramref name="source"/> text into a <see cref="ListTag"/>.
/// </summary>
/// <param name="source">A string containing the SNBT code to parse.</param>
/// <returns>The <see cref="CompoundTag"/> instance described in the source text.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="SyntaxErrorException">When <paramref name="source"/> is invalid SNBT code.</exception>
public static ListTag ParseList(string source)
{
var bytes = Encoding.UTF8.GetBytes(source);
return ParseList(bytes, Encoding.UTF8);
}
/// <summary>
/// Parse the given <paramref name="source"/> text into a <see cref="CompoundTag"/>.
/// </summary>
/// <param name="source">A string containing the SNBT code to parse.</param>
/// <param name="encoding">Encoding of the <paramref name="source"/>.</param>
/// <returns>The <see cref="CompoundTag"/> instance described in the source text.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="SyntaxErrorException">When <paramref name="source"/> is invalid SNBT code.</exception>
public static CompoundTag Parse(ReadOnlySpan<byte> source, Encoding? encoding = null)
{
var scanner = new Scanner(source, encoding ?? Encoding.UTF8);
scanner.MoveNext(true, true); scanner.MoveNext(true, true);
scanner.AssertChar('{'); scanner.AssertChar('{');
return ParseCompound(null, ref scanner); return ParseCompound(null, ref scanner);
} }
/// <summary>
/// Parse the given <paramref name="source"/> text into a <see cref="ListTag"/>.
/// </summary>
/// <param name="source">A string containing the SNBT code to parse.</param>
/// <param name="encoding">Encoding of the <paramref name="source"/>.</param>
/// <returns>The <see cref="CompoundTag"/> instance described in the source text.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="SyntaxErrorException">When <paramref name="source"/> is invalid SNBT code.</exception>
public static ListTag ParseList(ReadOnlySpan<byte> 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) private static CompoundTag ParseCompound(string? name, ref Scanner scanner)
{ {
scanner.MoveNext(true, true); scanner.MoveNext(true, true);
@ -40,23 +81,20 @@ public static class StringNbt
if (scanner.Current == '}') if (scanner.Current == '}')
return result; return result;
while (true) while (!scanner.IsEndOfInput)
{ {
// Read the name of the tag // Read the name of the tag
var childName = ParseString(ref scanner, out _); var childName = ParseString(ref scanner, out _);
// Move to and asser the next significant character is a deliminator. // Move to and asser the next significant character is a deliminator.
// scanner.MoveNext(true, true); scanner.MoveNext(true, true);
scanner.AssertChar(':'); scanner.AssertChar(':');
// Move to and parse the tag value // Move to and parse the tag value
scanner.MoveNext(true, true); scanner.MoveNext(true, true);
var tag = ParseTag(childName, ref scanner); var tag = ParseTag(childName, ref scanner);
result.Add(tag); result.Add(tag);
// scanner.MoveNext(true, true); scanner.MoveNext(true, true);
if (char.IsWhiteSpace(scanner.Current))
scanner.MoveNext(true, true);
// Comma encountered, read another tag. // Comma encountered, read another tag.
if (scanner.Current == ',') if (scanner.Current == ',')
@ -68,7 +106,7 @@ public static class StringNbt
// Closing brace encountered, break loop. // Closing brace encountered, break loop.
if (scanner.Current == '}') if (scanner.Current == '}')
{ {
scanner.MoveNext(true, false); // scanner.MoveNext(true, false);
break; break;
} }
@ -79,6 +117,15 @@ public static class StringNbt
return result; return result;
} }
/// <summary>
/// Parses the next logical chunk of data as a string.
/// </summary>
/// <param name="scanner">A reference to the <see cref="Scanner"/> context.</param>
/// <param name="quoted">
/// Flag indicating if the value was enclosed in a pair of matching single/double quotes, otherwise
/// <see langword="false"/> if it was read as a literal span of characters from the input.
/// </param>
/// <returns>The scanned string.</returns>
private static string ParseString(ref Scanner scanner, out bool quoted) private static string ParseString(ref Scanner scanner, out bool quoted)
{ {
var quote = scanner.Current; var quote = scanner.Current;
@ -105,7 +152,6 @@ public static class StringNbt
if (scanner.Current == quote) if (scanner.Current == quote)
{ {
closed = true; closed = true;
scanner.Position++;
break; break;
} }
@ -129,8 +175,12 @@ public static class StringNbt
var start = scanner.Position; var start = scanner.Position;
for (var length = 0; scanner.MoveNext(false, true); length++) for (var length = 0; scanner.MoveNext(false, true); length++)
{ {
if (!AllowedInUnquoted(scanner.Current)) if (AllowedInUnquoted(scanner.Current))
return new string(scanner.Source.Slice(start, length + 1)); 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; return string.Empty;
@ -179,11 +229,10 @@ public static class StringNbt
return new IntTag(name, hex); 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); return new StringTag(name, value);
} }
private static bool TryParseNumber(string? name, string value, char suffix, out Tag tag) private static bool TryParseNumber(string? name, string value, char suffix, out Tag tag)
{ {
// A much less complicated char.ToLower() // A much less complicated char.ToLower()
@ -239,34 +288,31 @@ public static class StringNbt
if (scanner.Current == ']') if (scanner.Current == ']')
return new ListTag(name, TagType.End); 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 'B' => new ByteArrayTag(name, ParseArrayValues<byte>(ref scanner)),
var prefix = scanner.Current; 'I' => new IntArrayTag(name, ParseArrayValues<int>(ref scanner)),
scanner.Position += 2; 'L' => new LongArrayTag(name, ParseArrayValues<long>(ref scanner)),
return prefix switch _ => throw scanner.SyntaxError($"Invalid type specifier. Expected 'B', 'I', or 'L', got '{prefix}'.")
{ };
'B' => new ByteArrayTag(name, ParseArrayValues<byte>(ref scanner)),
'I' => new IntArrayTag(name, ParseArrayValues<int>(ref scanner)),
'L' => new LongArrayTag(name, ParseArrayValues<long>(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);
} }
private static Tag ParseList(string? name, ref Scanner scanner) private static ListTag ParseList(string? name, ref Scanner scanner)
{ {
var list = new List<Tag>(); var list = new List<Tag>();
while (true) while (true)
{ {
var child = ParseTag(null, ref scanner); var child = ParseTag(null, ref scanner);
list.Add(child); list.Add(child);
if (char.IsWhiteSpace(scanner.Current)) scanner.MoveNext(true, true);
scanner.MoveNext(true, true);
// Comma encountered, read another tag. // Comma encountered, read another tag.
if (scanner.Current == ',') if (scanner.Current == ',')
@ -278,7 +324,6 @@ public static class StringNbt
// Closing brace encountered, break loop. // Closing brace encountered, break loop.
if (scanner.Current == ']') if (scanner.Current == ']')
{ {
scanner.MoveNext(true, false);
break; break;
} }
@ -295,7 +340,6 @@ public static class StringNbt
// Early-out for [] // Early-out for []
if (scanner.Current == ']') if (scanner.Current == ']')
{ {
scanner.Position++;
return Array.Empty<T>(); return Array.Empty<T>();
} }
@ -317,8 +361,7 @@ public static class StringNbt
var values = new T[strings.Length]; var values = new T[strings.Length];
for (var i = 0; i < values.Length; i++) for (var i = 0; i < values.Length; i++)
values[i] = T.Parse(strings[i], NumberStyles.Integer, NumberFormatInfo.InvariantInfo); values[i] = T.Parse(strings[i], NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
scanner.Position++; // Consume the closing ']'
return values; return values;
} }