Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
frolra opened this issue Sep 24, 2024 · 4 comments
Open

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

frolra opened this issue Sep 24, 2024 · 4 comments

Comments

@frolra
Copy link

frolra commented Sep 24, 2024

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.get_DataType()

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
Copy link
Owner

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

@frolra
Copy link
Author

frolra commented Sep 24, 2024

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
Copy link
Owner

Do you want to submit a PR?

@frolra
Copy link
Author

frolra commented Sep 24, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants