Implemented generating SNBT from tags

This commit is contained in:
ForeverZer0 2021-08-31 01:32:36 -04:00
parent 5b6fcd2d4c
commit 16369b68e3
16 changed files with 267 additions and 1 deletions

View File

@ -0,0 +1,30 @@
using Xunit;
using Xunit.Abstractions;
namespace SharpNBT.Tests
{
public class StringifiedTest
{
private readonly ITestOutputHelper output;
public StringifiedTest(ITestOutputHelper output)
{
this.output = output;
}
[Fact]
public void BigOutput()
{
var tag = TestHelper.GetTag("bigtest.nbt", CompressionType.GZip);
output.WriteLine(tag.Stringify(true));
}
[Fact]
public void HelloWorldOutput()
{
var tag = TestHelper.GetTag("hello_world.nbt", CompressionType.None);
output.WriteLine(tag.Stringify(true));
}
}
}

95
SharpNBT/SNBT/Lexer.cs Normal file
View File

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace SharpNBT.SNBT
{
internal enum TokenType
{
None,
CompoundBegin,
CompoundEnd,
Identifier,
String,
Separator,
Comma,
ByteArray,
IntArray,
LongArray,
ListArray,
EndArray,
Float,
Double,
Byte,
Short,
Long,
Int,
WhiteSpace,
Char
}
internal sealed class LexerRule
{
internal delegate string PostProcessHandler(string input);
private readonly Regex regex;
public TokenType Type { get; }
public LexerRule(TokenType type, string pattern) : this(type, new Regex(pattern, RegexOptions.Multiline | RegexOptions.CultureInvariant))
{
}
public LexerRule(TokenType type, Regex regex)
{
Type = type;
this.regex = regex ?? throw new ArgumentNullException(nameof(regex));
}
public LexerRule(TokenType type, Regex regex, PostProcessHandler handler)
{
}
}
internal class Lexer
{
private static readonly List<LexerRule> rules;
static Lexer()
{
rules = new List<LexerRule>
{
new LexerRule(TokenType.CompoundBegin, @"\{[\s]*"),
new LexerRule(TokenType.CompoundEnd, @"[\s]*\}"),
new LexerRule(TokenType.Identifier, "\".+?\"(?=:)"),
new LexerRule(TokenType.Identifier, "'.+?'(?=:) "),
new LexerRule(TokenType.Identifier, "A-Za-z0-9_-]+?(?=:) "),
new LexerRule(TokenType.String, "\".*?\""),
new LexerRule(TokenType.String, "'.*?'"),
new LexerRule(TokenType.Separator, @"[\s]*:[\s]*"),
new LexerRule(TokenType.Comma, @"[\s]*,[\s]*"),
new LexerRule(TokenType.ByteArray, @"\[B;[\s]*?"),
new LexerRule(TokenType.IntArray, @"\[I;[\s]*?"),
new LexerRule(TokenType.LongArray, @"\[L;[\s]*?"),
new LexerRule(TokenType.ListArray, @"\[[\s]*?"),
new LexerRule(TokenType.EndArray, @"[\s]*\]"),
new LexerRule(TokenType.Float, @"-?[0-9]*\.[0-9]+[Ff]"),
new LexerRule(TokenType.Double, @"-?[0-9]*\.[0-9]+[Dd]?"),
new LexerRule(TokenType.Byte, "-?([0-9]+)[Bb]"),
new LexerRule(TokenType.Short, "-?([0-9]+)[Ss]"),
new LexerRule(TokenType.Long, "-?([0-9]+)[Ll]"),
new LexerRule(TokenType.Int, "-?([0-9]+)"),
new LexerRule(TokenType.WhiteSpace, @"[\s]+"),
new LexerRule(TokenType.String, @"[\S]+"),
new LexerRule(TokenType.Char, ".")
};
}
public Lexer()
{
}
}
}

View File

@ -57,5 +57,12 @@ namespace SharpNBT
var word = Count == 1 ? Strings.WordElement : Strings.WordElements; var word = Count == 1 ? Strings.WordElement : Strings.WordElements;
return $"TAG_Byte_Array({PrettyName}): [{Count} {word}]"; return $"TAG_Byte_Array({PrettyName}): [{Count} {word}]";
} }
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}[B;{string.Join(',', this)}]";
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace SharpNBT namespace SharpNBT
@ -69,5 +70,12 @@ namespace SharpNBT
/// <returns>The tag represented as a <see cref="sbyte"/>.</returns> /// <returns>The tag represented as a <see cref="sbyte"/>.</returns>
[CLSCompliant(false)] [CLSCompliant(false)]
public static implicit operator sbyte(ByteTag tag) => unchecked((sbyte)tag.Value); public static implicit operator sbyte(ByteTag tag) => unchecked((sbyte)tag.Value);
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value}B";
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -112,5 +113,31 @@ namespace SharpNBT
tag.PrettyPrinted(buffer, level + 1, indent); tag.PrettyPrinted(buffer, level + 1, indent);
buffer.AppendLine(space + "}"); buffer.AppendLine(space + "}");
} }
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify()
{
var strings = new string[Count];
for (var i = 0; i < strings.Length; i++)
strings[i] = this[i].Stringify();
return $"{StringifyName}{{{string.Join(',', strings)}}}";
}
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <param name="topLevel">Flag indicating if this is the top-level tag that should be wrapped in braces.</param>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public string Stringify(bool topLevel)
{
var str = Stringify();
return topLevel ? $"{{{str}}}" : str;
}
} }
} }

View File

@ -37,5 +37,12 @@ namespace SharpNBT
/// <param name="tag">The tag to convert.</param> /// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="double"/>.</returns> /// <returns>The tag represented as a <see cref="double"/>.</returns>
public static implicit operator double(DoubleTag tag) => tag.Value; public static implicit operator double(DoubleTag tag) => tag.Value;
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value:0.0}D";
} }
} }

View File

@ -24,5 +24,12 @@ namespace SharpNBT
{ {
// Do nothing // Do nothing
} }
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => string.Empty;
} }
} }

View File

@ -36,5 +36,12 @@ namespace SharpNBT
/// <param name="tag">The tag to convert.</param> /// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="float"/>.</returns> /// <returns>The tag represented as a <see cref="float"/>.</returns>
public static implicit operator float(FloatTag tag) => tag.Value; public static implicit operator float(FloatTag tag) => tag.Value;
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value:0.0}F";
} }
} }

View File

@ -61,5 +61,12 @@ namespace SharpNBT
var word = Count == 1 ? Strings.WordElement : Strings.WordElements; var word = Count == 1 ? Strings.WordElement : Strings.WordElements;
return $"TAG_Int_Array({PrettyName}): [{Count} {word}]"; return $"TAG_Int_Array({PrettyName}): [{Count} {word}]";
} }
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}[I;{string.Join(',', this)}]";
} }
} }

View File

@ -64,5 +64,12 @@ namespace SharpNBT
/// <returns>The tag represented as a <see cref="uint"/>.</returns> /// <returns>The tag represented as a <see cref="uint"/>.</returns>
[CLSCompliant(false)] [CLSCompliant(false)]
public static implicit operator uint(IntTag tag) => unchecked((uint)tag.Value); public static implicit operator uint(IntTag tag) => unchecked((uint)tag.Value);
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value}";
} }
} }

View File

@ -72,7 +72,7 @@ namespace SharpNBT
tag.PrettyPrinted(buffer, level + 1, indent); tag.PrettyPrinted(buffer, level + 1, indent);
buffer.AppendLine(space + "}"); buffer.AppendLine(space + "}");
} }
/// <summary> /// <summary>
/// Retrieves a "pretty-printed" multiline string representing the complete tree structure of the tag. /// Retrieves a "pretty-printed" multiline string representing the complete tree structure of the tag.
/// </summary> /// </summary>
@ -85,5 +85,19 @@ namespace SharpNBT
PrettyPrinted(buffer, 0, indent); PrettyPrinted(buffer, 0, indent);
return buffer.ToString(); return buffer.ToString();
} }
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify()
{
var strings = new string[Count];
for (var i = 0; i < strings.Length; i++)
strings[i] = this[i].Stringify();
return $"{StringifyName}[{string.Join(',', strings)}]";
}
} }
} }

View File

@ -60,5 +60,12 @@ namespace SharpNBT
var word = Count == 1 ? Strings.WordElement : Strings.WordElements; var word = Count == 1 ? Strings.WordElement : Strings.WordElements;
return $"TAG_Long_Array({PrettyName}): [{Count} {word}]"; return $"TAG_Long_Array({PrettyName}): [{Count} {word}]";
} }
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}[L;{string.Join(',', this)}]";
} }
} }

View File

@ -64,5 +64,12 @@ namespace SharpNBT
/// <returns>The tag represented as a <see cref="ulong"/>.</returns> /// <returns>The tag represented as a <see cref="ulong"/>.</returns>
[CLSCompliant(false)] [CLSCompliant(false)]
public static implicit operator ulong(LongTag tag) => unchecked((ulong)tag.Value); public static implicit operator ulong(LongTag tag) => unchecked((ulong)tag.Value);
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value}L";
} }
} }

View File

@ -64,5 +64,12 @@ namespace SharpNBT
/// <returns>The tag represented as a <see cref="ushort"/>.</returns> /// <returns>The tag represented as a <see cref="ushort"/>.</returns>
[CLSCompliant(false)] [CLSCompliant(false)]
public static implicit operator ushort(ShortTag tag) => unchecked((ushort)tag.Value); public static implicit operator ushort(ShortTag tag) => unchecked((ushort)tag.Value);
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value}S";
} }
} }

View File

@ -37,5 +37,12 @@ namespace SharpNBT
/// <param name="tag">The tag to convert.</param> /// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="string"/>.</returns> /// <returns>The tag represented as a <see cref="string"/>.</returns>
public static implicit operator string(StringTag tag) => tag.Value; public static implicit operator string(StringTag tag) => tag.Value;
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}\"{Value}\"";
} }
} }

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Runtime.Serialization.Json; using System.Runtime.Serialization.Json;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using JetBrains.Annotations; using JetBrains.Annotations;
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]
@ -208,6 +209,27 @@ namespace SharpNBT
/// <param name="right">Second value to compare.</param> /// <param name="right">Second value to compare.</param>
/// <returns>Result of comparison.</returns> /// <returns>Result of comparison.</returns>
public static bool operator !=(Tag left, Tag right) => !Equals(left, right); public static bool operator !=(Tag left, Tag right) => !Equals(left, right);
/// <summary>
/// Gets the <i>string</i> representation of this NBT tag (SNBT).
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public abstract string Stringify();
/// <summary>
/// Gets the name in a formatted properly for SNBT.
/// </summary>
[NotNull]
protected internal string StringifyName
{
get
{
if (string.IsNullOrEmpty(Name))
return string.Empty;
return Regex.IsMatch(Name, @"^[A-Ba-z0-9_-]+$") ? $"{Name}: " : $"\"{Name}\": ";
}
}
} }
/// <summary> /// <summary>