tnunnink / L5Sharp

A library for intuitively interacting with Rockwell's L5X import/export files.
MIT License
55 stars 6 forks source link

[Bug] Missing support to DT/LDT Atomic type #33

Open frolra opened 2 months ago

frolra commented 2 months ago

When I try to import my L5X file, the DataType read process throws me this exception:

The name 'DT' does not represent a known L5Sharp.Core.AtomicData type. _at L5Sharp.Core.AtomicData.Parse(String name, String value) at L5Sharp.Core.LogixSerializer.DeserializeAtomic(XElement element) at L5Sharp.Core.LogixSerializer.DeserializeData(XElement element) at L5Sharp.Core.LogixSerializer.Deserialize(XElement element) at L5Sharp.Core.LogixSerializer.Deserialize[TElement](XElement element) at L5Sharp.Core.LogixElement.GetData() at L5Sharp.Core.Member.GetData() at L5Sharp.Core.Member.get_Value() at L5Sharp.Core.Tag.get_Value() at L5Sharp.Core.Tag.getDataType()

I Checked at source code and missing this type definition. From Logix DT is Date/Time format and basically is a Unsigned integer 64 bits image

tnunnink commented 2 months ago

Thanks. I'll look into adding support for it.

frolra commented 2 months ago

I started to create a DT class from ULINT if can help you :) `using System;

namespace L5Sharp.Core;

///

/// Represents a DT Logix atomic data type, or a type analogous to a . /// public sealed class DT : AtomicData, IComparable, IConvertible, ILogixParsable
{ /// /// The underlying primitive value which is set upon construction and not changed. /// private readonly DateTime _value;

/// <summary>
/// Creates a new default <see cref="DT"/> type.
/// </summary>
public DT()
{
}

/// <summary>
/// Creates a new <see cref="DT"/> with the provided value.
/// </summary>
/// <param name="value">The value to initialize the type with.</param>
public DT(DateTime value)
{
    _value = value;
}

/// <summary>
/// Creates a new <see cref="DT"/> value with the provided radix format.
/// </summary>
/// <param name="radix">The <see cref="Core.Radix"/> number format of the value.</param>
public DT(Radix radix) : base(radix)
{
}

/// <summary>
/// Creates a new <see cref="DT"/> with the provided value.
/// </summary>
/// <param name="value">The value to initialize the type with.</param>
/// <param name="radix">The optional radix format of the value.</param>
public DT(DateTime value, Radix radix) : base(radix)
{
    _value = value;
}

/// <inheritdoc />
public override string Name => nameof(DT);

/// <inheritdoc />
public int CompareTo(object? obj)
{
    return obj switch
    {
        null => 1,
        DT typed => _value.CompareTo(typed._value),
        AtomicData atomic => _value.CompareTo((DateTime)Convert.ChangeType(atomic, typeof(DateTime))),
        ValueType value => _value.CompareTo((DateTime)Convert.ChangeType(value, typeof(DateTime))),
        _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.")
    };
}

/// <inheritdoc />
public override bool Equals(object? obj)
{
    return obj switch
    {
        DT value => value._value == _value,
        AtomicData atomic => _value.Equals((DateTime)Convert.ChangeType(atomic, typeof(DateTime))),
        ValueType value => _value.Equals(Convert.ChangeType(value, typeof(DateTime))),
        _ => false
    };
}

/// <inheritdoc />
public override int GetHashCode() => _value.GetHashCode();

/// <summary>
/// Parses a string into a <see cref="DT"/> value.
/// </summary>
/// <param name="value">The string to parse.</param>
/// <returns>A <see cref="DT"/> representing the parsed value.</returns>
/// <exception cref="FormatException">The <see cref="Radix"/> format can not be inferred from <c>value</c>.</exception>
public new static DT Parse(string value)
{
    if (DateTime.TryParse(value, out var result))
        return new DT(result);

    var radix = Radix.Infer(value);
    var atomic = radix.ParseValue(value);
    var converted = (DateTime)Convert.ChangeType(atomic, typeof(DateTime));
    return new DT(converted, radix);
}

/// <summary>
/// Tries to parse a string into a <see cref="DT"/> value.
/// </summary>
/// <param name="value">The string to parse.</param>
/// <returns>The parsed <see cref="DT"/> value if successful; Otherwise, <c>null</c>.</returns>
public new static DT? TryParse(string? value)
{
    if (value is null || value.IsEmpty())
        return default;

    if (DateTime.TryParse(value, out var primitive))
        return new DT(primitive);

    if (!Radix.TryInfer(value, out var radix))
        return default;

    var parsed = radix.ParseValue(value);
    var converted = (DateTime)Convert.ChangeType(parsed, typeof(DateTime));
    return new DT(converted, radix);
}

// Contains the implicit .NET conversions for the type.

#region Conversions

/// <summary>
/// Converts the provided <see cref="DateTime"/> to a <see cref="DT"/> value.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <returns>A <see cref="DT"/> value.</returns>
public static implicit operator DT(DateTime value) => new(value);

/// <summary>
/// Converts the provided <see cref="DT"/> to a <see cref="DateTime"/> value.
/// </summary>
/// <param name="atomic">The value to convert.</param>
/// <returns>A <see cref="DateTime"/> type value.</returns>
public static implicit operator DateTime(DT atomic) => atomic._value;

/// <summary>
/// Implicitly converts a <see cref="string"/> to a <see cref="DT"/> value.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <returns>A new <see cref="DT"/> value.</returns>
public static implicit operator DT(string value) => Parse(value);

/// <summary>
/// Implicitly converts the provided <see cref="DT"/> to a <see cref="string"/> value.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <returns>A new <see cref="string"/> value.</returns>
public static implicit operator string(DT value) => value.ToString();

#endregion

// Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each
// atomic type to avoid polluting the API, and to have the implementation as performant as possible.
// To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type.

#region Convertible

/// <inheritdoc />
TypeCode IConvertible.GetTypeCode() => TypeCode.Object;

/// <inheritdoc />
bool IConvertible.ToBoolean(IFormatProvider? provider) => _value.Ticks != 0;

/// <inheritdoc />
byte IConvertible.ToByte(IFormatProvider? provider) => (byte)_value.Ticks;

/// <inheritdoc />
char IConvertible.ToChar(IFormatProvider? provider) => (char)_value.Ticks;

/// <inheritdoc />
DateTime IConvertible.ToDateTime(IFormatProvider? provider) =>
    throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported.");

/// <inheritdoc />
decimal IConvertible.ToDecimal(IFormatProvider? provider) =>
    throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported.");

/// <inheritdoc />
double IConvertible.ToDouble(IFormatProvider? provider) => (double)_value.Ticks ;

/// <inheritdoc />
short IConvertible.ToInt16(IFormatProvider? provider) => (short)_value.Ticks;

/// <inheritdoc />
int IConvertible.ToInt32(IFormatProvider? provider) => (int)_value.Ticks;

/// <inheritdoc />
long IConvertible.ToInt64(IFormatProvider? provider) => _value.Ticks;

/// <inheritdoc />
sbyte IConvertible.ToSByte(IFormatProvider? provider) => (sbyte)_value.Ticks;

/// <inheritdoc />
float IConvertible.ToSingle(IFormatProvider? provider) => _value.Ticks;

/// <inheritdoc />
string IConvertible.ToString(IFormatProvider? provider) => ToString();

/// <inheritdoc />
object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
{
    var convertible = (IConvertible)this;

    return Type.GetTypeCode(conversionType) switch
    {
        TypeCode.Boolean => convertible.ToBoolean(provider),
        TypeCode.Byte => convertible.ToByte(provider),
        TypeCode.Char => convertible.ToChar(provider),
        TypeCode.DateTime => convertible.ToDateTime(provider),
        TypeCode.Decimal => convertible.ToDecimal(provider),
        TypeCode.Double => convertible.ToDouble(provider),
        TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)),
        TypeCode.Int16 => convertible.ToInt16(provider),
        TypeCode.Int32 => convertible.ToInt32(provider),
        TypeCode.Int64 => convertible.ToInt64(provider),
        TypeCode.Object => ToAtomic(conversionType),
        TypeCode.SByte => convertible.ToSByte(provider),
        TypeCode.Single => convertible.ToSingle(provider),
        TypeCode.String => ToString(),
        TypeCode.UInt16 => convertible.ToUInt16(provider),
        TypeCode.UInt32 => convertible.ToUInt32(provider),
        TypeCode.UInt64 => convertible.ToUInt64(provider),
        TypeCode.DBNull => throw new InvalidCastException(
            "Conversion for type code 'DbNull' not supported by AtomicType."),
        _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.")
    };
}

/// <inheritdoc />
ushort IConvertible.ToUInt16(IFormatProvider? provider) => (ushort)_value.Ticks;

/// <inheritdoc />
uint IConvertible.ToUInt32(IFormatProvider? provider) => (uint)_value.Ticks;

/// <inheritdoc />
ulong IConvertible.ToUInt64(IFormatProvider? provider) => (ulong)_value.Ticks;

/// <summary>
/// Converts the current atomic type to the specified atomic type.
/// </summary>
/// <param name="conversionType">The atomic type to convert to.</param>
/// <returns>A <see cref="object"/> representing the converted atomic type value.</returns>
/// <exception cref="InvalidCastException">The specified type is not a valid atomic type.</exception>
private object ToAtomic(Type conversionType)
{
    if (conversionType == typeof(BOOL))
        return new BOOL(_value.Ticks != 0);
    if (conversionType == typeof(SINT))
        return new SINT((sbyte)_value.Ticks);
    if (conversionType == typeof(INT))
        return new INT((short)_value.Ticks);
    if (conversionType == typeof(DINT))
        return new DINT((int)_value.Ticks);
    if (conversionType == typeof(LINT))
        return new LINT(_value.Ticks);
    if (conversionType == typeof(REAL))
        return new REAL(_value.Ticks);
    if (conversionType == typeof(LREAL))
        return new LREAL(_value.Ticks);
    if (conversionType == typeof(USINT))
        return new USINT((byte)_value.Ticks);
    if (conversionType == typeof(UINT))
        return new UINT((ushort)_value.Ticks);
    if (conversionType == typeof(UDINT))
        return new UDINT((uint)_value.Ticks);
    if (conversionType == typeof(ULINT))
        return new ULINT((ulong)_value.Ticks);

    throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}.");
}

#endregion

}`

tnunnink commented 2 months ago

Do you want to submit a PR?

frolra commented 2 months ago

Yep, i will review more deeply the code and i submit the PR 👍🏼