Skip to content

Releases: nemesissoft/Nemesis.TextParsers

Update packaging options

03 Jan 00:02
Compare
Choose a tag to compare

Source code generator for enum types

01 Jan 22:51
c1aa684
Compare
Choose a tag to compare

What's Changed

Full Changelog: 2.8.2...2.9.1

Code generator for enum types

With this feature it is enough to annotate enum with 2 attributes:

[Auto.AutoEnumTransformer(
    //1. optionally pass parser settings
    CaseInsensitive = true, AllowParsingNumerics = true, 
    //2. TransformerClassName can be left blank. In that case the name of enum is used with "Transformer" suffix
    TransformerClassName = "MonthCodeGenTransformer",
    //3. optionally pass namespace to generate the transformer class within. If not provided the namespace of the enum will be used
    TransformerClassNamespace = "ABC"
)]
//4. decorate enum with TransformerAttribute that points to automatically generated transformer
[Transformer(typeof(ABC.MonthCodeGenTransformer))]
public enum Month : byte
{
    None = 0,
    January = 1, February = 2, March = 3,
    April = 4, May = 5, June = 6,
    July = 7, August = 8, September = 9,
    October = 10, November = 11, December = 12
}

This in turn generates the following parser using best practices (some lines are ommited for brevity):

Source code for generated parser
public sealed class MonthCodeGenTransformer : TransformerBase<Nemesis.TextParsers.CodeGen.Sample.Month>
{
    public override string Format(Nemesis.TextParsers.CodeGen.Sample.Month element) => element switch
    {
        Nemesis.TextParsers.CodeGen.Sample.Month.None => nameof(Nemesis.TextParsers.CodeGen.Sample.Month.None),
        Nemesis.TextParsers.CodeGen.Sample.Month.January => nameof(Nemesis.TextParsers.CodeGen.Sample.Month.January),
        
        // ...

        Nemesis.TextParsers.CodeGen.Sample.Month.December => nameof(Nemesis.TextParsers.CodeGen.Sample.Month.December),
        _ => element.ToString("G"),
    };

    protected override Nemesis.TextParsers.CodeGen.Sample.Month ParseCore(in ReadOnlySpan<char> input) =>
        input.IsWhiteSpace() ? default : (Nemesis.TextParsers.CodeGen.Sample.Month)ParseElement(input);

    private static byte ParseElement(ReadOnlySpan<char> input)
    {
        if (input.IsEmpty || input.IsWhiteSpace()) return default;
        input = input.Trim();
        if (IsNumeric(input) && byte.TryParse(input
#if NETFRAMEWORK
    .ToString() //legacy frameworks do not support parsing from ReadOnlySpan<char>
#endif
            , out var number))
            return number;
        else
            return ParseName(input);


        static bool IsNumeric(ReadOnlySpan<char> input) =>
            input.Length > 0 && input[0] is var first &&
            (char.IsDigit(first) || first is '-' or '+');    
    }

    private static byte ParseName(ReadOnlySpan<char> input)
    {    
        if (IsEqual(input, nameof(Nemesis.TextParsers.CodeGen.Sample.Month.None)))
            return (byte)Nemesis.TextParsers.CodeGen.Sample.Month.None;            

        else if (IsEqual(input, nameof(Nemesis.TextParsers.CodeGen.Sample.Month.January)))
            return (byte)Nemesis.TextParsers.CodeGen.Sample.Month.January;            

        else if (IsEqual(input, nameof(Nemesis.TextParsers.CodeGen.Sample.Month.February)))
            return (byte)Nemesis.TextParsers.CodeGen.Sample.Month.February;            

        // ...         

        else if (IsEqual(input, nameof(Nemesis.TextParsers.CodeGen.Sample.Month.December)))
            return (byte)Nemesis.TextParsers.CodeGen.Sample.Month.December;            

        else throw new FormatException(@$"Enum of type 'Nemesis.TextParsers.CodeGen.Sample.Month' cannot be parsed from '{input.ToString()}'.
Valid values are: [None or January or February or March or April or May or June or July or August or September or October or November or December] or number within byte range. 
Ignore case option on.");        

        static bool IsEqual(ReadOnlySpan<char> input, string label) =>
            MemoryExtensions.Equals(input, label.AsSpan(), StringComparison.OrdinalIgnoreCase);
    }
}

Integrate with Github Actions

19 Dec 10:10
Compare
Choose a tag to compare
  • Add Github Actions for build. Remove integration with AppVeyor
  • Add architecture tests to check if settings can be properly handled
  • Add debuging option for code gen

Improve TupleHelper

User is no longer required to call for TupleHelper.ParseNext method. It's code was incorporated into ParseElement method. It's now enough to call:

var _helper = new TupleHelper(',', '∅', '\\', '(', ')');

var input = "(3.14, Ala ma kota)";
var enumerator = _helper.ParseStart(input, 2, "DoubleAndString");

var key = _helper.ParseElement(ref enumerator, TextTransformer.Default.GetTransformer<double>());
var value = _helper.ParseElement(ref enumerator, TextTransformer.Default.GetTransformer<string>(), 2, "DoubleAndString");

_helper.ParseEnd(ref enumerator, 2, "DoubleAndString");

All generated calls were updated accordingly

Full Changelog: v2.7.2...2.8.2

Add support for ReadOnlyObservableCollection

16 Jul 10:12
Compare
Choose a tag to compare
v2.7.2

Add support for ReadOnlyObservableCollection

Optimize build targets

14 Jul 13:23
Compare
Choose a tag to compare
v2.7.1

Optimize build targets

Upgrade to .NET 7

14 Jul 09:51
Compare
Choose a tag to compare

Migrate to NET 7.0
Remove support for net 5.0 (out of support)
Add central package management
Add Generic Math version of LightLinq
Pack readme inside nuget package

Modernize code base

30 May 09:50
Compare
Choose a tag to compare
v2.6.3

New API key for nuget + modernize code base

Add code gen package

01 Mar 21:26
Compare
Choose a tag to compare

Add code generation package that automatically generates necessary transformers

C# 9.0 Code generation

With introduction of new code-gen engine, you can opt to have your transformer generated automatically without any imperative code.

//1. use specially provided (via code-gen) Auto.AutoDeconstructable attribute
[Auto.AutoDeconstructable]
//2. provide deconstructable aspect options or leave this attribute out - default options will be engaged 
[DeconstructableSettings('_', '∅', '%', '〈', '〉')]
readonly partial /*3. partial modifier is VERY important - you need this cause generated code is placed in different file*/ struct StructPoint3d
{
    public double X { get; }
    public double Y { get; }
    public double Z { get; }

    //4. specify constructor and matching deconstructor 
    public StructPoint3d(double x, double y, double z) { X = x; Y = y; Z = z; }

    public void Deconstruct(out double x, out double y, out double z) { x = X; y = Y; z = Z; }
}

//5. sit back, relax and enjoy - code-gen will do the job for you :-)

This in turn might generate the following (parts of code ommited for brevity)

using /* ... */;
[Transformer(typeof(StructPoint3dTransformer))]
readonly partial struct StructPoint3d { }

sealed class StructPoint3dTransformer : TransformerBase<StructPoint3d>
{
    private readonly ITransformer<double> _transformer_x = TextTransformer.Default.GetTransformer<double>();
    /* specify remaining transformers... */
    private const int ARITY = 3;
    private readonly TupleHelper _helper = new TupleHelper('_', '∅', '%', '〈', '〉');

    protected override StructPoint3d ParseCore(in ReadOnlySpan<char> input)
    {
        var enumerator = _helper.ParseStart(input, ARITY);
        var t1 = _helper.ParseElement(ref enumerator, _transformer_x);        
        /* parse Y and Z... */
        _helper.ParseEnd(ref enumerator, ARITY);
        return new StructPoint3d(t1, t2, t3);
    }

    public override string Format(StructPoint3d element)
    {
        Span<char> initialBuffer = stackalloc char[32];
        var accumulator = new ValueSequenceBuilder<char>(initialBuffer);
        try
        {
             _helper.StartFormat(ref accumulator);
             var (x, y, z) = element;
            _helper.FormatElement(_transformer_x, x, ref accumulator);
            /* format Y and Z... */
            _helper.EndFormat(ref accumulator);
            return accumulator.AsSpan().ToString();
        }
        finally { accumulator.Dispose(); }
    }
}

Code gen diagnositcs

Various diagnositcs exist to guide end user in creation of proper types that can be consumed by automatic generation. They might for example:

  1. check if types decorated with Auto* attributes are declared partial (prerequisite for additive code generation)
  2. validate settings passed via declarative syntax
  3. validate internal structure of type (i.e. check if constructor has matching Deconstruct method)
  4. check if external dependencies are included

Add code gen package

25 Feb 14:24
Compare
Choose a tag to compare

Add code generation package that automatically generates necessary transformers

C# 9.0 Code generation

With introduction of new code-gen engine, you can opt to have your transformer generated automatically without any imperative code.

//1. use specially provided (via code-gen) Auto.AutoDeconstructable attribute
[Auto.AutoDeconstructable]
//2. provide deconstructable aspect options or leave this attribute out - default options will be engaged 
[DeconstructableSettings('_', '∅', '%', '〈', '〉')]
readonly partial /*3. partial modifier is VERY important - you need this cause generated code is placed in different file*/ struct StructPoint3d
{
    public double X { get; }
    public double Y { get; }
    public double Z { get; }

    //4. specify constructor and matching deconstructor 
    public StructPoint3d(double x, double y, double z) { X = x; Y = y; Z = z; }

    public void Deconstruct(out double x, out double y, out double z) { x = X; y = Y; z = Z; }
}

//5. sit back, relax and enjoy - code-gen will do the job for you :-)

This in turn might generate the following (parts of code ommited for brevity)

using /* ... */;
[Transformer(typeof(StructPoint3dTransformer))]
readonly partial struct StructPoint3d { }

sealed class StructPoint3dTransformer : TransformerBase<StructPoint3d>
{
    private readonly ITransformer<double> _transformer_x = TextTransformer.Default.GetTransformer<double>();
    /* specify remaining transformers... */
    private const int ARITY = 3;
    private readonly TupleHelper _helper = new TupleHelper('_', '∅', '%', '〈', '〉');

    protected override StructPoint3d ParseCore(in ReadOnlySpan<char> input)
    {
        var enumerator = _helper.ParseStart(input, ARITY);
        var t1 = _helper.ParseElement(ref enumerator, _transformer_x);        
        /* parse Y and Z... */
        _helper.ParseEnd(ref enumerator, ARITY);
        return new StructPoint3d(t1, t2, t3);
    }

    public override string Format(StructPoint3d element)
    {
        Span<char> initialBuffer = stackalloc char[32];
        var accumulator = new ValueSequenceBuilder<char>(initialBuffer);
        try
        {
             _helper.StartFormat(ref accumulator);
             var (x, y, z) = element;
            _helper.FormatElement(_transformer_x, x, ref accumulator);
            /* format Y and Z... */
            _helper.EndFormat(ref accumulator);
            return accumulator.AsSpan().ToString();
        }
        finally { accumulator.Dispose(); }
    }
}

Code gen diagnositcs

Various diagnositcs exist to guide end user in creation of proper types that can be consumed by automatic generation. They might for example:

  1. check if types decorated with Auto* attributes are declared partial (prerequisite for additive code generation)
  2. validate settings passed via declarative syntax
  3. validate internal structure of type (i.e. check if constructor has matching Deconstruct method)
  4. check if external dependencies are included

Add code gen package

25 Feb 13:49
Compare
Choose a tag to compare
v2.6

Fix unit tests for code gen