Get commented typing working

This commit is contained in:
Nathan McRae 2024-02-22 22:09:13 -08:00
parent 0a45f541a4
commit 7368ac816b
2 changed files with 219 additions and 61 deletions

View File

@ -3,9 +3,14 @@ using System.Text;
namespace NathanMcRae; namespace NathanMcRae;
public class Tsv<T> where T : SaneTsv.SaneTsvRecord public class Tsv<T> where T : SaneTsv.TsvRecord
{ {
public List<T> Records { get; set; } public virtual List<T> Records { get; set; }
}
public class CommentedTsv<T>: Tsv<T> where T : SaneTsv.TsvRecord
{
public override List<T> Records { get; set; }
public string FileComment { get; set; } = null; public string FileComment { get; set; } = null;
} }
@ -54,25 +59,34 @@ public class SaneTsv
} }
} }
public static Tsv<T> ParseSimpleTsv<T>(byte[] inputBuffer) where T : SaneTsvRecord, new() public static Tsv<T> ParseSimpleTsv<T>(byte[] inputBuffer) where T : TsvRecord, new()
{ {
return Parse<T>(inputBuffer, FormatType.SIMPLE_TSV); return Parse<T>(inputBuffer, FormatType.SIMPLE_TSV);
} }
public static Tsv<T> ParseTypedTsv<T>(byte[] inputBuffer) where T : SaneTsvRecord, new() public static Tsv<T> ParseTypedTsv<T>(byte[] inputBuffer) where T : TsvRecord, new()
{ {
return Parse<T>(inputBuffer, FormatType.TYPED_TSV); return Parse<T>(inputBuffer, FormatType.TYPED_TSV);
} }
public static Tsv<T> ParseCommentedTsv<T>(byte[] inputBuffer) where T : SaneTsvRecord, new() public static CommentedTsv<T> ParseCommentedTsv<T>(byte[] inputBuffer) where T : CommentedTsvRecord, new()
{ {
return Parse<T>(inputBuffer, FormatType.COMMENTED_TSV); // TODO: add the file comment?
return (CommentedTsv<T>)Parse<T>(inputBuffer, FormatType.COMMENTED_TSV);
} }
// TODO: Have parsing errors include line / column # // TODO: Have parsing errors include line / column #
protected static Tsv<T> Parse<T>(byte[] inputBuffer, FormatType format) where T : SaneTsvRecord, new() protected static Tsv<T> Parse<T>(byte[] inputBuffer, FormatType format) where T : TsvRecord, new()
{ {
var parsed = new Tsv<T>(); Tsv<T> parsed;
if (format == FormatType.COMMENTED_TSV)
{
parsed = new CommentedTsv<T>();
}
else
{
parsed = new Tsv<T>();
}
parsed.Records = new List<T>(); parsed.Records = new List<T>();
var headerTypes = new List<Type>(); var headerTypes = new List<Type>();
@ -82,7 +96,7 @@ public class SaneTsv
foreach (PropertyInfo property in typeof(T).GetProperties()) foreach (PropertyInfo property in typeof(T).GetProperties())
{ {
SaneTsvColumnAttribute attribute = (SaneTsvColumnAttribute)Attribute.GetCustomAttribute(property, typeof(SaneTsvColumnAttribute)); TypedTsvColumnAttribute attribute = (TypedTsvColumnAttribute)Attribute.GetCustomAttribute(property, typeof(TypedTsvColumnAttribute));
if (attribute == null) if (attribute == null)
{ {
continue; continue;
@ -248,10 +262,18 @@ public class SaneTsv
if (currentComment.Length > 0) if (currentComment.Length > 0)
{ {
parsed.FileComment = currentComment.ToString(); if (parsed is CommentedTsv<T> commentedParsed)
currentComment.Clear(); {
commentedParsed.FileComment = currentComment.ToString();
currentComment.Clear();
}
else
{
throw new Exception("Found a file comment, but parser wasn't expecting a comment");
}
} }
fields.Clear(); fields.Clear();
} }
else if (numFields != fields.Count) else if (numFields != fields.Count)
@ -333,11 +355,24 @@ public class SaneTsv
return parsed; return parsed;
} }
protected static T ParseCurrentRecord<T>(Type[] columnTypes, PropertyInfo[] properties, List<byte[]> fields, string comment, int line) where T : SaneTsvRecord, new() protected static T ParseCurrentCommentedRecord<T> (Type[] columnTypes, PropertyInfo[] properties, List<byte[]> fields, string comment, int line) where T : CommentedTsvRecord, new()
{
return (T)ParseCurrentRecord<T>(columnTypes, properties, fields, comment, line);
}
protected static T ParseCurrentRecord<T>(Type[] columnTypes, PropertyInfo[] properties, List<byte[]> fields, string comment, int line) where T : TsvRecord, new()
{ {
T record = new T(); T record = new T();
record.Comment = comment; if (record is CommentedTsvRecord commentedRecord)
{
commentedRecord.Comment = comment;
}
else if (comment != null)
{
throw new Exception($"Found comment for line {line}, but format does not support comments");
}
record.Line = line; record.Line = line;
for (int j = 0; j < fields.Count; j++) for (int j = 0; j < fields.Count; j++)
@ -705,7 +740,22 @@ public class SaneTsv
} }
} }
public static byte[] SerializeTypedTsv<T>(IList<T> data) where T : SaneTsvRecord public static byte[] SerializeSimpleTsv<T>(IList<T> data) where T : TsvRecord
{
return SerializeTsv<T>(data, FormatType.TYPED_TSV);
}
public static byte[] SerializeTypedTsv<T>(IList<T> data) where T : TsvRecord
{
return SerializeTsv<T>(data, FormatType.TYPED_TSV);
}
public static byte[] SerializeCommentedTsv<T>(IList<T> data, string fileComment) where T : CommentedTsvRecord
{
return SerializeTsv<T>(data, FormatType.COMMENTED_TSV);
}
protected static byte[] SerializeTsv<T>(IList<T> data, FormatType tsvFormat) where T : TsvRecord
{ {
var bytes = new List<byte>(); var bytes = new List<byte>();
@ -716,14 +766,20 @@ public class SaneTsv
foreach (PropertyInfo property in typeof(T).GetProperties()) foreach (PropertyInfo property in typeof(T).GetProperties())
{ {
SaneTsvColumnAttribute attribute = (SaneTsvColumnAttribute)Attribute.GetCustomAttribute(property, typeof(SaneTsvColumnAttribute)); TsvColumnAttribute attribute = (TsvColumnAttribute)Attribute.GetCustomAttribute(property, typeof(TsvColumnAttribute));
if (attribute == null) if (attribute == null)
{ {
continue; continue;
} }
headerNames.Add(attribute.ColumnName ?? property.Name); string headerName = attribute.ColumnName ?? property.Name;
headerTypes.Add(attribute.ColumnType ?? GetColumnFromType(property.PropertyType)); headerNames.Add(headerName);
Type headerType = attribute.ColumnType ?? GetColumnFromType(property.PropertyType);
if (tsvFormat == FormatType.SIMPLE_TSV && headerType != typeof(StringType))
{
throw new Exception($"Serializing Simple TSV requires all columns be of type string, but column '{headerName}' has type '{headerType}'");
}
headerTypes.Add(headerType);
headerPropertyInfos.Add(property); headerPropertyInfos.Add(property);
// TODO: Check that the property type and given column type are compatible // TODO: Check that the property type and given column type are compatible
columnCount++; columnCount++;
@ -990,42 +1046,64 @@ public class SaneTsv
} }
} }
public class SaneTsvRecord public class TsvRecord
{
public int? Line { get; set; }
public TsvRecord(int? line)
{
Line = line;
}
public TsvRecord() { }
}
public class CommentedTsvRecord : TsvRecord
{ {
public string Comment { get; set; } public string Comment { get; set; }
public int? Line { get; set; }
public SaneTsvRecord(string comment, int? line) public CommentedTsvRecord(string comment, int? line)
{ {
Comment = comment; Comment = comment;
Line = line; Line = line;
} }
public SaneTsvRecord() { } public CommentedTsvRecord() { }
} }
public class TestRecord : SaneTsvRecord public class TestRecord : CommentedTsvRecord
{ {
[SaneTsvColumn("my-column")] [TypedTsvColumn("my-column")]
public string MyColumn { get; set; } public string MyColumn { get; set; }
} }
// TODO: Add column ordering // TODO: Add column ordering
public class SaneTsvColumnAttribute : Attribute public class TsvColumnAttribute : Attribute
{ {
public string ColumnName { get; } public string ColumnName { get; }
public Type ColumnType { get; } public virtual Type ColumnType { get; }
public SaneTsvColumnAttribute() { } public TsvColumnAttribute()
public SaneTsvColumnAttribute(string columnName)
{ {
ColumnType = typeof(StringType);
}
public TsvColumnAttribute(string columnName)
{
ColumnType = typeof(StringType);
ColumnName = columnName; ColumnName = columnName;
} }
}
public SaneTsvColumnAttribute(string columnName, Type columnType) // TODO: Add column ordering
public class TypedTsvColumnAttribute : TsvColumnAttribute
{
public override Type ColumnType { get; }
public TypedTsvColumnAttribute() { }
public TypedTsvColumnAttribute(string columnName) : base(columnName) { }
public TypedTsvColumnAttribute(string columnName, Type columnType) : base(columnName)
{ {
ColumnName = columnName;
if (columnType.BaseType != typeof(ColumnType)) if (columnType.BaseType != typeof(ColumnType))
{ {
throw new Exception("Column type must inherit from SaneTsv.ColumnType"); throw new Exception("Column type must inherit from SaneTsv.ColumnType");
@ -1033,7 +1111,7 @@ public class SaneTsv
ColumnType = columnType; ColumnType = columnType;
} }
public SaneTsvColumnAttribute(Type columnType) public TypedTsvColumnAttribute(Type columnType)
{ {
if (columnType.BaseType != typeof(ColumnType)) if (columnType.BaseType != typeof(ColumnType))
{ {

View File

@ -3,39 +3,39 @@ using System.Text;
internal class Program internal class Program
{ {
public class TestRecord : SaneTsv.SaneTsvRecord public class TestRecord : SaneTsv.TsvRecord
{ {
[SaneTsv.SaneTsvColumn("string-test")] [SaneTsv.TypedTsvColumn("string-test")]
public string StringTest { get; set; } public string StringTest { get; set; }
[SaneTsv.SaneTsvColumn("bool-test")] [SaneTsv.TypedTsvColumn("bool-test")]
public bool BoolTest { get; set; } public bool BoolTest { get; set; }
[SaneTsv.SaneTsvColumn("float32-test")] [SaneTsv.TypedTsvColumn("float32-test")]
public float Float32Test { get; set; } public float Float32Test { get; set; }
[SaneTsv.SaneTsvColumn("float32-le-test", typeof(SaneTsv.Float32LEType))] [SaneTsv.TypedTsvColumn("float32-le-test", typeof(SaneTsv.Float32LEType))]
public float Float32LETest { get; set; } public float Float32LETest { get; set; }
[SaneTsv.SaneTsvColumn("float64-test")] [SaneTsv.TypedTsvColumn("float64-test")]
public double Float64Test { get; set; } public double Float64Test { get; set; }
[SaneTsv.SaneTsvColumn("float64-le-test", typeof(SaneTsv.Float64LEType))] [SaneTsv.TypedTsvColumn("float64-le-test", typeof(SaneTsv.Float64LEType))]
public double Float64LETest { get; set; } public double Float64LETest { get; set; }
[SaneTsv.SaneTsvColumn("uint32-test")] [SaneTsv.TypedTsvColumn("uint32-test")]
public UInt32 UInt32Test { get; set; } public UInt32 UInt32Test { get; set; }
[SaneTsv.SaneTsvColumn("uint64-test")] [SaneTsv.TypedTsvColumn("uint64-test")]
public UInt64 UInt64Test { get; set; } public UInt64 UInt64Test { get; set; }
[SaneTsv.SaneTsvColumn("int32-test")] [SaneTsv.TypedTsvColumn("int32-test")]
public Int32 Int32Test { get; set; } public Int32 Int32Test { get; set; }
[SaneTsv.SaneTsvColumn("int64-test")] [SaneTsv.TypedTsvColumn("int64-test")]
public Int64 Int64Test { get; set; } public Int64 Int64Test { get; set; }
[SaneTsv.SaneTsvColumn("binary-test")] [SaneTsv.TypedTsvColumn("binary-test")]
public byte[] BinaryTest { get; set; } 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) public TestRecord(string stringTest, bool boolTest, float float32Test, float float32LETest, double float64Test, double float64LETest, UInt32 uInt32Test, UInt64 uInt64Test, Int32 int32Test, Int64 int64Test, byte[] binaryTest)
@ -56,48 +56,48 @@ internal class Program
public TestRecord() { } public TestRecord() { }
} }
public class BoolTestRecord : SaneTsv.SaneTsvRecord public class BoolTestRecord : SaneTsv.CommentedTsvRecord
{ {
[SaneTsv.SaneTsvColumn("column1:ty#pe")] [SaneTsv.TypedTsvColumn("column1:ty#pe")]
public bool Column1 { get; set; } public bool Column1 { get; set; }
[SaneTsv.SaneTsvColumn] [SaneTsv.TypedTsvColumn]
public byte[] column2 { get; set; } public byte[] column2 { get; set; }
[SaneTsv.SaneTsvColumn("columnthree\nyep")] [SaneTsv.TypedTsvColumn("columnthree\nyep")]
public string Column3 { get; set; } public string Column3 { get; set; }
} }
public class BoolTestRecord2 : SaneTsv.SaneTsvRecord public class BoolTestRecord2 : SaneTsv.CommentedTsvRecord
{ {
[SaneTsv.SaneTsvColumn("column1:type")] [SaneTsv.TypedTsvColumn("column1:type")]
public bool Column1 { get; set; } public bool Column1 { get; set; }
[SaneTsv.SaneTsvColumn] [SaneTsv.TypedTsvColumn]
public byte[] column2 { get; set; } public byte[] column2 { get; set; }
[SaneTsv.SaneTsvColumn("columnthree\nyep")] [SaneTsv.TypedTsvColumn("columnthree\nyep")]
public string Column3 { get; set; } public string Column3 { get; set; }
} }
public class SerdeTestRecord : SaneTsv.SaneTsvRecord public class SerdeTestRecord : SaneTsv.CommentedTsvRecord
{ {
[SaneTsv.SaneTsvColumn("column1")] [SaneTsv.TypedTsvColumn("column1")]
public bool Column1 { get; set; } public bool Column1 { get; set; }
[SaneTsv.SaneTsvColumn] [SaneTsv.TypedTsvColumn]
public byte[] column2 { get; set; } public byte[] column2 { get; set; }
[SaneTsv.SaneTsvColumn("columnthree\nyep")] [SaneTsv.TypedTsvColumn("columnthree\nyep")]
public string Column3 { get; set; } public string Column3 { get; set; }
} }
public class FloatTestRecord : SaneTsv.SaneTsvRecord public class FloatTestRecord : SaneTsv.CommentedTsvRecord
{ {
[SaneTsv.SaneTsvColumn("somefloat")] [SaneTsv.TypedTsvColumn("somefloat")]
public double SomeFloat { get; set; } public double SomeFloat { get; set; }
[SaneTsv.SaneTsvColumn("binfloat", typeof(SaneTsv.Float64LEType))] [SaneTsv.TypedTsvColumn("binfloat", typeof(SaneTsv.Float64LEType))]
public double BinFloat { get; set; } public double BinFloat { get; set; }
} }
@ -148,7 +148,7 @@ internal class Program
"\nTRUE\tvalue\\\\t\0woo\tvaluetrhee" + "\nTRUE\tvalue\\\\t\0woo\tvaluetrhee" +
"\nFALSE\tnother\tno\\ther"; "\nFALSE\tnother\tno\\ther";
Tsv<BoolTestRecord2> parsed = SaneTsv.ParseCommentedTsv<BoolTestRecord2>(Encoding.UTF8.GetBytes(testString1)); CommentedTsv<BoolTestRecord2> parsed = SaneTsv.ParseCommentedTsv<BoolTestRecord2>(Encoding.UTF8.GetBytes(testString1));
} }
//{ //{
@ -188,8 +188,6 @@ internal class Program
} }
} }
Console.WriteLine("Done with tests");
{ {
string testName = "Serde test"; string testName = "Serde test";
@ -215,5 +213,87 @@ internal class Program
Console.WriteLine($"Failed {testName}"); Console.WriteLine($"Failed {testName}");
} }
} }
{
string testName = "Trying to parse a not commented record as a Commented TSV test";
// These should not compile:
//byte[] serialized = SaneTsv.SerializeCommentedTsv(data);
// Gives this error: error CS7036: There is no argument given that corresponds to the required parameter 'fileComment' of 'SaneTsv.SerializeCommentedTsv<T>(IList<T>, string)'
//Tsv<TestRecord> parsed = SaneTsv.ParseCommentedTsv<TestRecord>(serialized);
// Gives this error: error CS0311: The type 'Program.TestRecord' cannot be used as type parameter 'T' in the generic type or method 'SaneTsv.ParseCommentedTsv<T>(byte[])'. There is no implicit reference conversion from 'Program.TestRecord' to 'NathanMcRae.SaneTsv.CommentedTsvRecord'.
}
{
string testName = "Try to parsed a Commented TSV as a Simple TSV";
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";
try
{
Tsv<BoolTestRecord2> parsed = SaneTsv.ParseSimpleTsv<BoolTestRecord2>(Encoding.UTF8.GetBytes(testString1));
Console.WriteLine($"Failed {testName}");
}
catch (Exception e)
{
Console.WriteLine($"Passed {testName}");
}
}
{
string testName = "Try to parsed a Commented TSV as a Typed TSV";
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";
try
{
Tsv<BoolTestRecord2> parsed = SaneTsv.ParseTypedTsv<BoolTestRecord2>(Encoding.UTF8.GetBytes(testString1));
Console.WriteLine($"Failed {testName}");
}
catch (Exception e)
{
Console.WriteLine($"Passed {testName}");
}
}
{
string testName = "Try to parsed a Typed TSV as a Simple TSV";
string testString1 =
"column1:type:boolean\tcolumn2:binary\tcolumnthree\\nyep:string" +
"\nTRUE\tvalue\\\\t\0woo\tvaluetrhee" +
"\nFALSE\tnother\tno\\ther";
try
{
Tsv<BoolTestRecord2> parsed = SaneTsv.ParseSimpleTsv<BoolTestRecord2>(Encoding.UTF8.GetBytes(testString1));
Console.WriteLine($"Failed {testName}");
}
catch (Exception e)
{
Console.WriteLine($"Passed {testName}");
}
}
Console.WriteLine("Done with tests");
} }
} }