diff --git a/SaneTsv/SaneTsv.cs b/SaneTsv/SaneTsv.cs index cb5a610..f2fc0dc 100644 --- a/SaneTsv/SaneTsv.cs +++ b/SaneTsv/SaneTsv.cs @@ -1,7 +1,14 @@ -using System.Text; +using System.Reflection; +using System.Text; namespace NathanMcRae; +public class Tsv where T : SaneTsv.SaneTsvRecord +{ + public List Records { get; set; } + public string FileComment { get; set; } = null; +} + /// /// Sane Tab-Separated Values /// @@ -34,8 +41,6 @@ public class SaneTsv // TODO: We need to be able to update all these in tandem somehow public string[] ColumnNames { get; protected set; } public Type[] ColumnTypes { get; protected set; } - public List Records { get; protected set; } - public string FileComment { get; protected set; } = null; protected static bool? _littleEndian = null; public static bool LittleEndian { @@ -49,28 +54,46 @@ public class SaneTsv } } - public static SaneTsv ParseSimpleTsv(byte[] inputBuffer) + public static Tsv ParseSimpleTsv(byte[] inputBuffer) where T : SaneTsvRecord, new() { - return Parse(inputBuffer, FormatType.SIMPLE_TSV); + return Parse(inputBuffer, FormatType.SIMPLE_TSV); } - public static SaneTsv ParseTypedTsv(byte[] inputBuffer) + public static Tsv ParseTypedTsv(byte[] inputBuffer) where T : SaneTsvRecord, new() { - return Parse(inputBuffer, FormatType.TYPED_TSV); + return Parse(inputBuffer, FormatType.TYPED_TSV); } - public static SaneTsv ParseCommentedTsv(byte[] inputBuffer) + public static Tsv ParseCommentedTsv(byte[] inputBuffer) where T : SaneTsvRecord, new() { - return Parse(inputBuffer, FormatType.COMMENTED_TSV); + return Parse(inputBuffer, FormatType.COMMENTED_TSV); } // TODO: Have parsing errors include line / column # - protected static SaneTsv Parse(byte[] inputBuffer, FormatType format) + protected static Tsv Parse(byte[] inputBuffer, FormatType format) where T : SaneTsvRecord, new() { - var parsed = new SaneTsv(); - parsed.ColumnNames = new string[] { }; - parsed.ColumnTypes = new Type[] { }; - parsed.Records = new List(); + var parsed = new Tsv(); + parsed.Records = new List(); + + var headerTypes = new List(); + var headerNames = new List(); + var headerPropertyInfos = new List(); + int columnCount = 0; + + foreach (PropertyInfo property in typeof(T).GetProperties()) + { + SaneTsvColumnAttribute attribute = (SaneTsvColumnAttribute)Attribute.GetCustomAttribute(property, typeof(SaneTsvColumnAttribute)); + if (attribute == null) + { + continue; + } + + headerNames.Add(attribute.ColumnName ?? property.Name); + headerTypes.Add(attribute.ColumnType ?? GetColumnFromType(property.PropertyType)); + headerPropertyInfos.Add(property); + // TODO: Check that the property type and given column type are compatible + columnCount++; + } var fieldBytes = new List(); var fields = new List(); @@ -129,9 +152,6 @@ public class SaneTsv numFields = fields.Count; - parsed.ColumnNames = new string[numFields]; - parsed.ColumnTypes = new Type[numFields]; - int numTypesBlank = 0; for (int j = 0; j < fields.Count; j++) @@ -213,9 +233,17 @@ public class SaneTsv } // TODO: Check column name uniqueness + // TODO: Allow lax parsing (only worry about parsing columns that are given in the specifying type - parsed.ColumnNames[j] = columnName; - parsed.ColumnTypes[j] = type; + if (headerNames[j] != columnName) + { + throw new Exception($"Column {j} has name {columnName}, but expected {headerNames[j]}"); + } + + if (headerTypes[j] != type) + { + throw new Exception($"Column {j} has type {type}, but expected {headerTypes[j]}"); + } } if (currentComment.Length > 0) @@ -238,7 +266,7 @@ public class SaneTsv comment = currentComment.ToString(); currentComment.Clear(); } - parsed.Records.Add(new SaneTsvRecord(parsed.ColumnNames, ParseCurrentRecord(parsed, fields, line), comment, line)); + parsed.Records.Add(ParseCurrentRecord(headerTypes.ToArray(), headerPropertyInfos.ToArray(), fields, comment, line)); fields.Clear(); } @@ -298,28 +326,33 @@ public class SaneTsv comment = currentComment.ToString(); currentComment.Clear(); } - parsed.Records.Add(new SaneTsvRecord(parsed.ColumnNames, ParseCurrentRecord(parsed, fields, line), comment, line)); + parsed.Records.Add(ParseCurrentRecord(headerTypes.ToArray(), headerPropertyInfos.ToArray(), fields, comment, line)); fields.Clear(); } return parsed; } - /// - /// Note: this modifies 'parsed' - /// - protected static object[] ParseCurrentRecord(SaneTsv parsed, List fields, int line) + protected static T ParseCurrentRecord(Type[] columnTypes, PropertyInfo[] properties, List fields, string comment, int line) where T : SaneTsvRecord, new() { - var parsedFields = new object[fields.Count]; + T record = new T(); + + record.Comment = comment; + record.Line = line; + for (int j = 0; j < fields.Count; j++) { // All other types require the content to be UTF-8. Binary fields can ignore that. - if (parsed.ColumnTypes[j] == typeof(BinaryType)) + if (columnTypes[j] == typeof(BinaryType)) { - parsedFields[j] = fields[j]; + // TODO: Use faster method for property setting + // e.g. https://blog.marcgravell.com/2012/01/playing-with-your-member.html + // or https://stackoverflow.com/questions/1027980/improving-performance-reflection-what-alternatives-should-i-consider + // or https://stackoverflow.com/questions/12767091/why-are-propertyinfo-setvalue-and-getvalue-so-slow + properties[j].SetValue(record, fields[j]); continue; } - else if (parsed.ColumnTypes[j] == typeof(Float32LEType)) + else if (columnTypes[j] == typeof(Float32LEType)) { byte[] floatBytes; if (!LittleEndian) @@ -334,11 +367,11 @@ public class SaneTsv { floatBytes = fields[j]; } - parsedFields[j] = BitConverter.ToSingle(floatBytes, 0); + properties[j].SetValue(record, BitConverter.ToSingle(floatBytes, 0)); continue; } - else if (parsed.ColumnTypes[j] == typeof(Float64LEType)) + else if (columnTypes[j] == typeof(Float64LEType)) { byte[] floatBytes; if (!LittleEndian) @@ -353,7 +386,7 @@ public class SaneTsv { floatBytes = fields[j]; } - parsedFields[j] = BitConverter.ToDouble(floatBytes, 0); + properties[j].SetValue(record, BitConverter.ToDouble(floatBytes, 0)); continue; } @@ -370,11 +403,11 @@ public class SaneTsv // TODO: Add checking for numeric types format - if (parsed.ColumnTypes[j] == typeof(StringType)) + if (columnTypes[j] == typeof(StringType)) { - parsedFields[j] = fieldString; + properties[j].SetValue(record, fieldString); } - else if (parsed.ColumnTypes[j] == typeof(BooleanType)) + else if (columnTypes[j] == typeof(BooleanType)) { bool parsedBool; if (fieldString == "TRUE") @@ -390,9 +423,9 @@ public class SaneTsv throw new Exception($"Field {j} on line {line} is not valid boolean. Must be 'TRUE' or 'FALSE' exactly"); } - parsedFields[j] = parsedBool; + properties[j].SetValue(record, parsedBool); } - else if (parsed.ColumnTypes[j] == typeof(Float32Type)) + else if (columnTypes[j] == typeof(Float32Type)) { float parsedFloat; if (!float.TryParse(fieldString, out parsedFloat)) @@ -411,9 +444,9 @@ public class SaneTsv } } - parsedFields[j] = parsedFloat; + properties[j].SetValue(record, parsedFloat); } - else if (parsed.ColumnTypes[j] == typeof(Float64Type)) + else if (columnTypes[j] == typeof(Float64Type)) { double parsedDouble; if (!double.TryParse(fieldString, out parsedDouble)) @@ -432,51 +465,51 @@ public class SaneTsv } } - parsedFields[j] = parsedDouble; + properties[j].SetValue(record, parsedDouble); } - else if (parsed.ColumnTypes[j] == typeof(UInt32Type)) + else if (columnTypes[j] == typeof(UInt32Type)) { if (!UInt32.TryParse(fieldString, out UInt32 parsedUInt32)) { throw new Exception($"Field {j} on line {line} is not valid UInt32"); } - parsedFields[j] = parsedUInt32; + properties[j].SetValue(record, parsedUInt32); } - else if (parsed.ColumnTypes[j] == typeof(UInt64Type)) + else if (columnTypes[j] == typeof(UInt64Type)) { if (!UInt64.TryParse(fieldString, out UInt64 parsedUInt64)) { throw new Exception($"Field {j} on line {line} is not valid UInt64"); } - parsedFields[j] = parsedUInt64; + properties[j].SetValue(record, parsedUInt64); } - else if (parsed.ColumnTypes[j] == typeof(Int32Type)) + else if (columnTypes[j] == typeof(Int32Type)) { if (!Int32.TryParse(fieldString, out Int32 parsedInt32)) { throw new Exception($"Field {j} on line {line} is not valid Int32"); } - parsedFields[j] = parsedInt32; + properties[j].SetValue(record, parsedInt32); } - else if (parsed.ColumnTypes[j] == typeof(Int64Type)) + else if (columnTypes[j] == typeof(Int64Type)) { if (!Int64.TryParse(fieldString, out Int64 parsedInt64)) { throw new Exception($"Field {j} on line {line} is not valid Int64"); } - parsedFields[j] = parsedInt64; + properties[j].SetValue(record, parsedInt64); } else { - throw new Exception($"Unexpected type {parsed.ColumnTypes[j]}"); + throw new Exception($"Unexpected type {columnTypes[j]}"); } } - return parsedFields; + return record; } public static byte[] SerializeSimpleTsv(IList header, IList> data) @@ -672,13 +705,28 @@ public class SaneTsv } } - public static byte[] SerializeTypedTsv(IList headerTypes, IList headerNames, IList> data) + public static byte[] SerializeTypedTsv(IList data) where T : SaneTsvRecord { var bytes = new List(); - if (headerNames.Count != headerTypes.Count) + var headerTypes = new List(); + var headerNames = new List(); + var headerPropertyInfos = new List(); + int columnCount = 0; + + foreach (PropertyInfo property in typeof(T).GetProperties()) { - throw new ArgumentException($"headerTypes length ({headerTypes.Count}) does not match headerNames length ({headerNames.Count})"); + SaneTsvColumnAttribute attribute = (SaneTsvColumnAttribute)Attribute.GetCustomAttribute(property, typeof(SaneTsvColumnAttribute)); + if (attribute == null) + { + continue; + } + + headerNames.Add(attribute.ColumnName ?? property.Name); + headerTypes.Add(attribute.ColumnType ?? GetColumnFromType(property.PropertyType)); + headerPropertyInfos.Add(property); + // TODO: Check that the property type and given column type are compatible + columnCount++; } // Serialize header @@ -745,8 +793,10 @@ public class SaneTsv // Serialize data for (int i = 0; i < data.Count; i++) { - for (int j = 0; j < data[i].Count; j++) + for (int j = 0; j < columnCount; j++) { + object datum = headerPropertyInfos[j].GetValue(data[i]); + try { byte[] fieldEncoded = null; @@ -755,16 +805,16 @@ public class SaneTsv if (headerTypes[j] == typeof(StringType)) { - fieldEncoded = Encoding.UTF8.GetBytes((string)data[i][j]); + fieldEncoded = Encoding.UTF8.GetBytes((string)datum); } else if (headerTypes[j] == typeof(BooleanType)) { - bytes.AddRange((bool)data[i][j] ? TrueEncoded : FalseEncoded); + bytes.AddRange((bool)datum ? TrueEncoded : FalseEncoded); skipEscaping = true; } else if (headerTypes[j] == typeof(Float32Type)) { - if (data[i][j] is float f) + if (datum is float f) { if (float.IsNegativeInfinity(f)) { @@ -777,7 +827,7 @@ public class SaneTsv else { // See https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#round-trip-format-specifier-r - bytes.AddRange(Encoding.UTF8.GetBytes(((float)data[i][j]).ToString("G9"))); + bytes.AddRange(Encoding.UTF8.GetBytes(((float)datum).ToString("G9"))); } } else @@ -790,11 +840,11 @@ public class SaneTsv { if (LittleEndian) { - fieldEncoded = BitConverter.GetBytes((float)data[i][j]); + fieldEncoded = BitConverter.GetBytes((float)datum); } else { - byte[] floatBytes = BitConverter.GetBytes((float)data[i][j]); + byte[] floatBytes = BitConverter.GetBytes((float)datum); fieldEncoded = new byte[sizeof(float)]; for (int k = 0; k < sizeof(float); k++) { @@ -804,7 +854,7 @@ public class SaneTsv } else if (headerTypes[j] == typeof(Float64Type)) { - if (data[i][j] is double d) + if (datum is double d) { if (double.IsNegativeInfinity(d)) { @@ -830,11 +880,11 @@ public class SaneTsv { if (LittleEndian) { - fieldEncoded = BitConverter.GetBytes((double)data[i][j]); + fieldEncoded = BitConverter.GetBytes((double)datum); } else { - byte[] doubleBytes = BitConverter.GetBytes((double)data[i][j]); + byte[] doubleBytes = BitConverter.GetBytes((double)datum); fieldEncoded = new byte[sizeof(double)]; for (int k = 0; k < sizeof(double); k++) { @@ -844,27 +894,27 @@ public class SaneTsv } else if (headerTypes[j] == typeof(UInt32Type)) { - bytes.AddRange(Encoding.UTF8.GetBytes(((UInt32)(int)data[i][j]).ToString())); + bytes.AddRange(Encoding.UTF8.GetBytes(((UInt32)datum).ToString())); skipEscaping = true; } else if (headerTypes[j] == typeof(UInt64Type)) { - bytes.AddRange(Encoding.UTF8.GetBytes(((UInt64)(int)data[i][j]).ToString())); + bytes.AddRange(Encoding.UTF8.GetBytes(((UInt64)datum).ToString())); skipEscaping = true; } else if (headerTypes[j] == typeof(Int32Type)) { - bytes.AddRange(Encoding.UTF8.GetBytes(((Int32)(int)data[i][j]).ToString())); + bytes.AddRange(Encoding.UTF8.GetBytes(((Int32)datum).ToString())); skipEscaping = true; } else if (headerTypes[j] == typeof(Int64Type)) { - bytes.AddRange(Encoding.UTF8.GetBytes(((Int64)(int)data[i][j]).ToString())); + bytes.AddRange(Encoding.UTF8.GetBytes(((Int64)datum).ToString())); skipEscaping = true; } else if (headerTypes[j] == typeof(BinaryType)) { - fieldEncoded = (byte[])data[i][j]; + fieldEncoded = (byte[])datum; } else { @@ -902,7 +952,7 @@ public class SaneTsv } } - if (j < data[i].Count - 1) + if (j < columnCount - 1) { bytes.Add((byte)'\t'); } @@ -921,20 +971,17 @@ public class SaneTsv return bytes.ToArray(); } - public SaneTsvRecord this[int i] => Records[i]; - - public class SaneTsvRecord + public class SimpleTsvRecord { public string[] ColumnNames { get; } public string Comment { get; } - public object[] Fields { get; } - public int Line { get; } + public string[] Fields { get; } + public int? Line { get; } - public object this[string columnName] => Fields[Array.IndexOf(ColumnNames, columnName)]; + public string this[string columnName] => Fields[Array.IndexOf(ColumnNames, columnName)]; + public string this[int columnIndex] => Fields[columnIndex]; - public object this[int columnIndex] => Fields[columnIndex]; - - public SaneTsvRecord(string[] columnNames, object[] fields, string comment, int line) + public SimpleTsvRecord(string[] columnNames, string[] fields, string comment, int line) { ColumnNames = columnNames; Fields = fields; @@ -942,4 +989,57 @@ public class SaneTsv Line = line; } } + + public class SaneTsvRecord + { + public string Comment { get; set; } + public int? Line { get; set; } + + public SaneTsvRecord(string comment, int? line) + { + Comment = comment; + Line = line; + } + + public SaneTsvRecord() { } + } + + public class TestRecord : SaneTsvRecord + { + [SaneTsvColumn("my-column")] + public string MyColumn { get; set; } + } + + // TODO: Add column ordering + public class SaneTsvColumnAttribute : Attribute + { + public string ColumnName { get; } + public Type ColumnType { get; } + + public SaneTsvColumnAttribute() { } + + public SaneTsvColumnAttribute(string columnName) + { + ColumnName = columnName; + } + + public SaneTsvColumnAttribute(string columnName, Type columnType) + { + ColumnName = columnName; + if (columnType.BaseType != typeof(ColumnType)) + { + throw new Exception("Column type must inherit from SaneTsv.ColumnType"); + } + ColumnType = columnType; + } + + public SaneTsvColumnAttribute(Type columnType) + { + if (columnType.BaseType != typeof(ColumnType)) + { + throw new Exception("Column type must inherit from SaneTsv.ColumnType"); + } + ColumnType = columnType; + } + } } diff --git a/SaneTsv/SaneTsvTest/Program.cs b/SaneTsv/SaneTsvTest/Program.cs index fa689e7..fd0b7e1 100644 --- a/SaneTsv/SaneTsvTest/Program.cs +++ b/SaneTsv/SaneTsvTest/Program.cs @@ -1,145 +1,219 @@ using NathanMcRae; using System.Text; +internal class Program { - string testName = "Bool test"; - string testString1 = "column1:ty\\#pe:boolean\tcolumn2:binary\tcolumnthree\\nyep:string" + - "\nTRUE\tvalue\\\\t\0woo\tvaluetrhee" + - "\nFALSE\tnother\tno\\ther"; + public class TestRecord : SaneTsv.SaneTsvRecord + { + [SaneTsv.SaneTsvColumn("string-test")] + public string StringTest { get; set; } - SaneTsv parsed = SaneTsv.ParseTypedTsv(Encoding.UTF8.GetBytes(testString1)); - if (parsed.Records[0]["column1:ty#pe"] is bool result && result) - { - Console.WriteLine($"Passed {testName}"); + [SaneTsv.SaneTsvColumn("bool-test")] + public bool BoolTest { get; set; } + + [SaneTsv.SaneTsvColumn("float32-test")] + public float Float32Test { get; set; } + + [SaneTsv.SaneTsvColumn("float32-le-test", typeof(SaneTsv.Float32LEType))] + public float Float32LETest { get; set; } + + [SaneTsv.SaneTsvColumn("float64-test")] + public double Float64Test { get; set; } + + [SaneTsv.SaneTsvColumn("float64-le-test", typeof(SaneTsv.Float64LEType))] + public double Float64LETest { get; set; } + + [SaneTsv.SaneTsvColumn("uint32-test")] + public UInt32 UInt32Test { get; set; } + + [SaneTsv.SaneTsvColumn("uint64-test")] + public UInt64 UInt64Test { get; set; } + + [SaneTsv.SaneTsvColumn("int32-test")] + public Int32 Int32Test { get; set; } + + [SaneTsv.SaneTsvColumn("int64-test")] + public Int64 Int64Test { get; set; } + + [SaneTsv.SaneTsvColumn("binary-test")] + public byte[] BinaryTest { get; set; } + + public TestRecord(string stringTest, bool boolTest, float float32Test, float float32LETest, double float64Test, double float64LETest, UInt32 uInt32Test, UInt64 uInt64Test, Int32 int32Test, Int64 int64Test, byte[] binaryTest) + { + StringTest = stringTest; + BoolTest = boolTest; + Float32Test = float32Test; + Float32LETest = float32LETest; + Float64Test = float64Test; + Float64LETest = float64LETest; + UInt32Test = uInt32Test; + UInt64Test = uInt64Test; + Int32Test = int32Test; + Int64Test = int64Test; + BinaryTest = binaryTest; + } + + public TestRecord() { } } - else + + public class BoolTestRecord : SaneTsv.SaneTsvRecord { - Console.WriteLine($"Failed {testName}"); + [SaneTsv.SaneTsvColumn("column1:ty#pe")] + public bool Column1 { get; set; } + + [SaneTsv.SaneTsvColumn] + public byte[] column2 { get; set; } + + [SaneTsv.SaneTsvColumn("columnthree\nyep")] + public string Column3 { get; set; } + } + + public class BoolTestRecord2 : SaneTsv.SaneTsvRecord + { + [SaneTsv.SaneTsvColumn("column1:type")] + public bool Column1 { get; set; } + + [SaneTsv.SaneTsvColumn] + public byte[] column2 { get; set; } + + [SaneTsv.SaneTsvColumn("columnthree\nyep")] + public string Column3 { get; set; } + } + + public class SerdeTestRecord : SaneTsv.SaneTsvRecord + { + [SaneTsv.SaneTsvColumn("column1")] + public bool Column1 { get; set; } + + [SaneTsv.SaneTsvColumn] + public byte[] column2 { get; set; } + + [SaneTsv.SaneTsvColumn("columnthree\nyep")] + public string Column3 { get; set; } + } + + public class FloatTestRecord : SaneTsv.SaneTsvRecord + { + [SaneTsv.SaneTsvColumn("somefloat")] + public double SomeFloat { get; set; } + + [SaneTsv.SaneTsvColumn("binfloat", typeof(SaneTsv.Float64LEType))] + public double BinFloat { get; set; } + } + + private static void Main(string[] args) + { + { + string testName = "Bool test"; + string testString1 = "column1:ty\\#pe:boolean\tcolumn2:binary\tcolumnthree\\nyep:string" + + "\nTRUE\tvalue\\\\t\0woo\tvaluetrhee" + + "\nFALSE\tnother\tno\\ther"; + + Tsv parsed = SaneTsv.ParseTypedTsv(Encoding.UTF8.GetBytes(testString1)); + if (parsed.Records[0].Column1) + { + Console.WriteLine($"Passed {testName}"); + } + else + { + Console.WriteLine($"Failed {testName}"); + } + } + + { + string testName = "Bad bool test"; + try + { + string testString1 = "column1:type:boolean\tcolumn2:binary\tcolumnthree\\nyep:string" + + "\nTUE\tvalue\\\\t\0woo\tvaluetrhee" + + "\nFALSE\tnother\tno\\ther"; + + Tsv parsed = SaneTsv.ParseTypedTsv(Encoding.UTF8.GetBytes(testString1)); + Console.WriteLine($"Failed {testName}"); + } + catch (Exception) + { + Console.WriteLine($"Passed {testName}"); + } + + } + + { + string testName = "Comment test"; + string testString1 = "#This is a file comment\n" + + "#One more file comment line\n" + + "column1:type:boolean\tcolumn2:binary\tcolumnthree\\nyep:string" + + "\n#This is a comment" + + "\n#Another comment line" + + "\nTRUE\tvalue\\\\t\0woo\tvaluetrhee" + + "\nFALSE\tnother\tno\\ther"; + + Tsv parsed = SaneTsv.ParseCommentedTsv(Encoding.UTF8.GetBytes(testString1)); + } + + //{ + // string testName = "Serde test"; + // string testString1 = "column1\tcolumn2\tcolumnthree\\nyep" + + // "\nTRUE\tvalue\\\\twoo\tvaluetrhee" + + // "\nFALSE\tnother\tno\\ther"; + + // Tsv parsed = SaneTsv.ParseSimpleTsv(Encoding.UTF8.GetBytes(testString1)); + // string serialized = Encoding.UTF8.GetString(SaneTsv.SerializeSimpleTsv(parsed.ColumnNames, parsed.Records.Select(r => r.Fields.Select(f => f.ToString()).ToArray()).ToArray())); + + // if (testString1 == serialized) + // { + // Console.WriteLine($"Passed {testName}"); + // } + // else + // { + // Console.WriteLine($"Failed {testName}"); + // } + //} + + { + string testName = "Float binary test"; + var bytes = new List(); + bytes.AddRange(Encoding.UTF8.GetBytes("somefloat:float64\tbinfloat:float64-le" + + "\n1.5\t")); bytes.AddRange(BitConverter.GetBytes(1.5)); + bytes.AddRange(Encoding.UTF8.GetBytes("\n-8.0000005E-14\t")); bytes.AddRange(BitConverter.GetBytes(-8.0000005E-14)); + + Tsv parsed = SaneTsv.ParseTypedTsv(bytes.ToArray()); + if (parsed.Records[0].BinFloat == parsed.Records[0].SomeFloat) + { + Console.WriteLine($"Passed {testName}"); + } + else + { + Console.WriteLine($"Failed {testName}"); + } + } + + Console.WriteLine("Done with tests"); + + { + string testName = "Serde test"; + + TestRecord[] data = + { + new TestRecord("test", true, 44.5f, 44.5f, -88e-3, -88e-3, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3 }), + new TestRecord("test2", false, 44.5000005f, 44.5000005f, -88e-30, -88e-30, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3, 4 }), + new TestRecord("test2", false, float.NaN, float.NaN, double.NaN, double.NaN, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3, 4 }), + new TestRecord("test2", false, float.NegativeInfinity, float.NegativeInfinity, double.NegativeInfinity, double.NegativeInfinity, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3, 4 }), + new TestRecord("test2", false, float.PositiveInfinity, float.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3, 4 }), + }; + + byte[] serialized = SaneTsv.SerializeTypedTsv(data); + + Tsv parsed = SaneTsv.ParseTypedTsv(serialized); + + if ((float)parsed.Records[1].Float32Test == 44.5000005f) + { + Console.WriteLine($"Passed {testName}"); + } + else + { + Console.WriteLine($"Failed {testName}"); + } + } } } - -{ - string testName = "Bad bool test"; - try - { - string testString1 = "column1:type:boolean\tcolumn2:binary\tcolumnthree\\nyep:string" + - "\nTUE\tvalue\\\\t\0woo\tvaluetrhee" + - "\nFALSE\tnother\tno\\ther"; - - SaneTsv parsed = SaneTsv.ParseTypedTsv(Encoding.UTF8.GetBytes(testString1)); - Console.WriteLine($"Failed {testName}"); - } - catch (Exception) - { - Console.WriteLine($"Passed {testName}"); - } - -} - -{ - string testName = "Comment test"; - string testString1 = "#This is a file comment\n" + - "#One more file comment line\n" + - "column1:type:boolean\tcolumn2:binary\tcolumnthree\\nyep:string" + - "\n#This is a comment" + - "\n#Another comment line" + - "\nTRUE\tvalue\\\\t\0woo\tvaluetrhee" + - "\nFALSE\tnother\tno\\ther"; - - SaneTsv parsed = SaneTsv.ParseCommentedTsv(Encoding.UTF8.GetBytes(testString1)); -} - -{ - string testName = "Serde test"; - string testString1 = "column1\tcolumn2\tcolumnthree\\nyep" + - "\nTRUE\tvalue\\\\twoo\tvaluetrhee" + - "\nFALSE\tnother\tno\\ther"; - - SaneTsv parsed = SaneTsv.ParseSimpleTsv(Encoding.UTF8.GetBytes(testString1)); - string serialized = Encoding.UTF8.GetString(SaneTsv.SerializeSimpleTsv(parsed.ColumnNames, parsed.Records.Select(r => r.Fields.Select(f => f.ToString()).ToArray()).ToArray())); - - if (testString1 == serialized) - { - Console.WriteLine($"Passed {testName}"); - } - else - { - Console.WriteLine($"Failed {testName}"); - } -} - -{ - string testName = "Float binary test"; - var bytes = new List(); - bytes.AddRange(Encoding.UTF8.GetBytes("somefloat:float64\tbinfloat:float64-le" + - "\n1.5\t")); bytes.AddRange(BitConverter.GetBytes(1.5)); - bytes.AddRange(Encoding.UTF8.GetBytes("\n-8.0000005E-14\t")); bytes.AddRange(BitConverter.GetBytes(-8.0000005E-14)); - - SaneTsv parsed = SaneTsv.ParseTypedTsv(bytes.ToArray()); - if ((double)parsed.Records[0]["binfloat"] == (double)parsed.Records[0]["somefloat"]) - { - Console.WriteLine($"Passed {testName}"); - } - else - { - Console.WriteLine($"Failed {testName}"); - } -} - -Console.WriteLine("Done with tests"); - -{ - string testName = "Serde test"; - string[] headerNames = - { - "string-test", - "bool-test", - "float32-test", - "float32-le-test", - "float64-test", - "float64-le-test", - "uint32-test", - "uint64-test", - "int32-test", - "int64-test", - "binary-test", - }; - - Type[] headerTypes = - { - typeof(SaneTsv.StringType), - typeof(SaneTsv.BooleanType), - typeof(SaneTsv.Float32Type), - typeof(SaneTsv.Float32LEType), - typeof(SaneTsv.Float64Type), - typeof(SaneTsv.Float64LEType), - typeof(SaneTsv.UInt32Type), - typeof(SaneTsv.UInt64Type), - typeof(SaneTsv.Int32Type), - typeof(SaneTsv.Int64Type), - typeof(SaneTsv.BinaryType), - }; - - object[][] data = - { - new object[] { "test", true, 44.5f, 44.5f, -88e-3, -88e-3, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3 } }, - new object[] { "test2", false, 44.5000005f, 44.5000005f, -88e-30, -88e-30, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3, 4 } }, - new object[] { "test2", false, float.NaN, float.NaN, double.NaN, double.NaN, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3, 4 } }, - new object[] { "test2", false, float.NegativeInfinity, float.NegativeInfinity, double.NegativeInfinity, double.NegativeInfinity, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3, 4 } }, - new object[] { "test2", false, float.PositiveInfinity, float.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, 7773, 88888888, -7773, -88888888, new byte[] { 0, 1, 2, 3, 4 } }, - }; - - byte[] serialized = SaneTsv.SerializeTypedTsv(headerTypes, headerNames, data); - - SaneTsv parsed = SaneTsv.ParseTypedTsv(serialized); - - if ((float)parsed.Records[1]["float32-test"] == 44.5000005) - { - Console.WriteLine($"Passed {testName}"); - } - else - { - Console.WriteLine($"Failed {testName}"); - } -} -// TODO: Check qNaN, sNaN, +inf, -inf values for float types