Skip to content

Commit

Permalink
FBD development and updates to the L5X primary API
Browse files Browse the repository at this point in the history
  • Loading branch information
tnunnink committed Nov 3, 2023
1 parent f571847 commit 002b4d4
Show file tree
Hide file tree
Showing 66 changed files with 2,396 additions and 1,213 deletions.
135 changes: 73 additions & 62 deletions src/.idea/.idea.L5Sharp/.idea/workspace.xml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/L5Sharp.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ACOS/@EntryIndexedValue">ACOS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ACS/@EntryIndexedValue">ACS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AND/@EntryIndexedValue">AND</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AOI/@EntryIndexedValue">AOI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ASIN/@EntryIndexedValue">ASIN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ASN/@EntryIndexedValue">ASN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ATAN/@EntryIndexedValue">ATAN</s:String>
Expand All @@ -22,6 +23,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=INT/@EntryIndexedValue">INT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IO/@EntryIndexedValue">IO</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=JSR/@EntryIndexedValue">JSR</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LEN/@EntryIndexedValue">LEN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LINT/@EntryIndexedValue">LINT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LN/@EntryIndexedValue">LN</s:String>
Expand All @@ -37,6 +39,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=REAL/@EntryIndexedValue">REAL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RLL/@EntryIndexedValue">RLL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPI/@EntryIndexedValue">RPI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SBR/@EntryIndexedValue">SBR</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SFC/@EntryIndexedValue">SFC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SIN/@EntryIndexedValue">LOG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SINT/@EntryIndexedValue">SINT</s:String>
Expand Down
34 changes: 26 additions & 8 deletions src/L5Sharp/Common/Argument.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using L5Sharp.Types;
using L5Sharp.Types.Atomics;

Expand All @@ -20,7 +22,7 @@ private Argument(object value)
{
_value = value ?? throw new ArgumentNullException(nameof(value));
}

/// <summary>
/// Indicates whether the argument is an immediate atomic value.
/// </summary>
Expand All @@ -46,20 +48,36 @@ private Argument(object value)
/// </summary>
/// <value><c>true</c> if the underlying value is a <see cref="TagName"/> object; Otherwise, <c>false</c>.</value>
public bool IsTag => _value is TagName;

/// <summary>
/// Indicates whether the argument is a literal string value with the single quote identifiers.
/// </summary>
/// <value><c>true</c> if the underlying value is an <see cref="string"/> object; Otherwise, <c>false</c>.</value>
public bool IsString => _value is string;

/// <summary>
/// The collection of <see cref="TagName"/> values found in the argument.
/// </summary>
/// <value>A <see cref="IEnumerable{T}"/> of <see cref="TagName"/> values.</value>
/// <remarks>
/// Since an argument could represent a complex expression, it may contain more than one tag name value.
/// We need a way to get all tag names from a single argument whether it's a single tag name or expression or
/// multiple tag names.
/// </remarks>
public IEnumerable<TagName> Tags => _value switch
{
TagName tagName => new[] { tagName },
NeutralText text => text.Tags(),
_ => Enumerable.Empty<TagName>()
};

/// <summary>
/// Represents an unknown argument that can be found in certain instruction text.
/// </summary>
/// <value>A <see cref="Argument"/> representing an unknown parameter.</value>
/// <remarks>This is literally the '?' character, as often seen in the TIMER instruction arguments.</remarks>
public static Argument Unknown => new("?");

/// <summary>
/// Represents an empty argument.
/// </summary>
Expand All @@ -84,7 +102,7 @@ public static Argument Parse(string? value)
{
//Empty value - lets not crash on empty or invalid arguments.
if (string.IsNullOrEmpty(value)) return Empty;

//Unknown value - Can be found in TON instructions.
if (value == "?") return Unknown;

Expand Down Expand Up @@ -194,21 +212,21 @@ public static Argument Parse(string? value)
/// </summary>
/// <param name="argument">The <see cref="Argument"/> object to convert.</param>
/// <returns>A <see cref="TagName"/> object representing the value of the argument.</returns>
public static explicit operator TagName(Argument argument) => (TagName) argument._value;
public static explicit operator TagName(Argument argument) => (TagName)argument._value;

/// <summary>
/// Explicitly converts the provided <see cref="Argument"/> to an <see cref="AtomicType"/>.
/// </summary>
/// <param name="argument">The <see cref="Argument"/> object to convert.</param>
/// <returns>A <see cref="AtomicType"/> object representing the value of the argument.</returns>
public static explicit operator AtomicType(Argument argument) => (AtomicType) argument._value;
public static explicit operator AtomicType(Argument argument) => (AtomicType)argument._value;

/// <summary>
/// Explicitly converts the provided <see cref="Argument"/> to an <see cref="NeutralText"/>.
/// </summary>
/// <param name="argument">The <see cref="Argument"/> object to convert.</param>
/// <returns>A <see cref="NeutralText"/> object representing the value of the argument.</returns>
public static explicit operator NeutralText(Argument argument) => (NeutralText) argument._value;
public static explicit operator NeutralText(Argument argument) => (NeutralText)argument._value;

/// <inheritdoc />
public override bool Equals(object? obj) => _value.Equals(obj);
Expand Down
34 changes: 33 additions & 1 deletion src/L5Sharp/Common/ComponentKey.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using L5Sharp.Utilities;

namespace L5Sharp.Common;

Expand All @@ -14,7 +15,7 @@ namespace L5Sharp.Common;
{
private readonly string _type;
private readonly string _name;

/// <summary>
/// Creates a new <see cref="ComponentKey"/> value type with the provided parameters.
/// </summary>
Expand All @@ -33,6 +34,21 @@ public ComponentKey(string type, string name)
/// <returns><c>true</c> if the component key has the provided name; Otherwise, <c>false</c>.</returns>
public bool HasName(string name) => string.Equals(_name, name, StringComparison.OrdinalIgnoreCase);

/// <summary>
/// Determines if this key is of the specified component type name.
/// </summary>
/// <param name="type">The component type to check.</param>
/// <returns><c>true</c> if the component key is of the provided type name; Otherwise, <c>false</c>.</returns>
public bool IsType(string type) => _type.IsEquivalent(type);

/// <summary>
/// Determines if this key is of the specified component type parameter.
/// </summary>
/// <typeparam name="TComponent">The component type to check</typeparam>
/// <returns><c>true</c> if the component key is of the provided type parameter; Otherwise, <c>false</c>.</returns>
public bool IsType<TComponent>() where TComponent : LogixComponent =>
_type.IsEquivalent(typeof(TComponent).L5XType());

/// <inheritdoc />
public bool Equals(ComponentKey other) =>
StringComparer.OrdinalIgnoreCase.Equals(_type, other._type) &&
Expand All @@ -46,6 +62,22 @@ public override int GetHashCode() =>
StringComparer.OrdinalIgnoreCase.GetHashCode(_type) ^
StringComparer.OrdinalIgnoreCase.GetHashCode(_name);

/// <summary>
/// Determines if two objects are equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns><c>true</c> if the objects have the same type and name property; Otherwise, <c>false</c>.</returns>
public static bool operator ==(ComponentKey left, ComponentKey right) => Equals(left, right);

/// <summary>
/// Determines if two objects are not equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns><c>true</c> if the objects have the same type and name property; Otherwise, <c>false</c>.</returns>
public static bool operator !=(ComponentKey left, ComponentKey right) => !Equals(left, right);

/// <inheritdoc />
public override string ToString() => $"[{_type}]{_name}";
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,79 @@
using System;
using System.Linq;
using System.Xml.Linq;
using L5Sharp.Common;
using JetBrains.Annotations;
using L5Sharp.Elements;
using L5Sharp.Enums;
using L5Sharp.Utilities;

namespace L5Sharp;
namespace L5Sharp.Common;

/// <summary>
/// Represents a reference to a component within a Logix project. This could be a code reference or a reference
/// from another component. This class is meant to provide a uniform set of information for all types of references.
/// </summary>
public class LogixReference
[PublicAPI]
public class CrossReference
{
private readonly XElement _element;
private readonly string _name;
private readonly string _type;

/// <summary>
/// Creates a new <see cref="LogixReference"/> with a referencing element, component name and type.
/// Creates a new <see cref="CrossReference"/> with a referencing element, component name and type.
/// </summary>
/// <param name="element">The referencing <see cref="XElement"/> object.</param>
/// <param name="name"></param>
/// <param name="type"></param>
/// <exception cref="ArgumentNullException">Any provided parameter is <c>null</c>.</exception>
public LogixReference(XElement element, string name, string type)
public CrossReference(XElement element, string type, string name)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentException("Value cannot be null or empty.", nameof(name));
if (string.IsNullOrEmpty(type)) throw new ArgumentException("Value cannot be null or empty.", nameof(type));
if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name cannot be null or empty.", nameof(name));
if (string.IsNullOrEmpty(type)) throw new ArgumentException("Type cannot be null or empty.", nameof(type));
ComponentType = type;
ComponentName = name;
_element = element ?? throw new ArgumentNullException(nameof(element));
_name = name;
_type = type;
}

/// <summary>
///
/// Creates a new <see cref="CrossReference"/> with a referencing element, component name and type.
/// </summary>
public ComponentKey Key => new(_type, _name);
/// <param name="element">The referencing <see cref="XElement"/> object.</param>
/// <param name="name"></param>
/// <param name="type"></param>
/// <param name="instruction"></param>
/// <exception cref="ArgumentNullException">Any provided parameter is <c>null</c>.</exception>
public CrossReference(XElement element, string type, string name, Instruction instruction)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name cannot be null or empty.", nameof(name));
if (string.IsNullOrEmpty(type)) throw new ArgumentException("Type cannot be null or empty.", nameof(type));
ComponentType = type;
ComponentName = name;
Instruction = instruction;
_element = element ?? throw new ArgumentNullException(nameof(element));
}

/// <summary>
/// The corresponding <see cref="Common.ComponentKey"/> of the reference, indicating both the component type and name
/// this element is in reference to.
/// </summary>
public ComponentKey ComponentKey => new(ComponentType, ComponentName);

/// <summary>
/// The type of the component the element is in reference to.
/// </summary>
/// <value>A <see cref="string"/> indicating the type of the component.</value>
public string ComponentType { get; }

/// <summary>
/// The name of the component the element is in reference to.
/// </summary>
/// <value>A <see cref="string"/> indicating the name of the component.</value>
public string ComponentName { get; }

/// <summary>
/// The specific instruction value the element is in reference to.
/// </summary>
/// <value>An <see cref="Common.Instruction"/> object if found; Otherwise, <c>null</c>.</value>
private Instruction? Instruction { get; }

/// <summary>
/// The referencing <see cref="XElement"/> object
Expand All @@ -47,21 +83,21 @@ public LogixReference(XElement element, string name, string type)
/// <summary>
/// The location of the reference if this is a <see cref="LogixCode"/> reference type.
/// </summary>
/// <value>A <see cref="string"/> containing the <c>Rung</c>, <c>Line</c>, or <c>DiagramElement</c> location for
/// <value>A <see cref="string"/> containing the <c>Rung</c>, <c>Line</c>, or <c>DiagramBlock</c> location for
/// the reference. If this reference has no relevant location, an empty string is returned.
/// </value>
public string ReferenceId => GetIdentifier() ?? string.Empty;

/// <summary>
/// The type of the element referencing the component for this reference.
/// </summary>
public string ReferenceType => GetReference().GetType().L5XType();
public string ReferenceType => GetReference().L5XType;

/// <summary>
/// The <see cref="Scope"/> type that the reference is contained within.
/// The <see cref="Enums.Scope"/> type that the reference is contained within.
/// </summary>
/// <value>A <see cref="Scope"/> indicating scope of the reference.</value>
public Scope ScopeType => Scope.ScopeType(_element);
/// <value>A <see cref="Enums.Scope"/> indicating scope of the reference.</value>
public Scope Scope => Scope.ScopeType(_element);

/// <summary>
/// The name of the scoped program, instruction, or controller that the reference is contained within.
Expand All @@ -70,7 +106,7 @@ public LogixReference(XElement element, string name, string type)
/// A <see cref="string"/> representing the name of program, controller, or instruction the reference
/// is contained within.
/// </value>
public string ScopeName => Scope.ScopeName(_element);
public string Container => Scope.ScopeName(_element);

/// <summary>
/// The name of the <c>Routine</c> that the reference is contained within, it is a <see cref="LogixCode"/>
Expand All @@ -86,17 +122,18 @@ public LogixReference(XElement element, string name, string type)
public string TaskName => GetTaskName() ?? string.Empty;

/// <summary>
/// Determines if this <see cref="LogixReference"/> is the same as another <see cref="LogixReference"/>.
/// Determines if this <see cref="CrossReference"/> is the same as another <see cref="CrossReference"/>.
/// </summary>
/// <param name="other">The other logix reference to compare to.</param>
/// <returns><c>true</c> if the provided reference has the same scope, routine, location, and reference type,
/// indicating that they are referring to the same code or component element in the L5X structure;
/// Otherwise, <c>false</c>.
/// </returns>
public bool IsSame(LogixReference other)
public bool IsSame(CrossReference other)
{
return ScopeType == other.ScopeType &&
ScopeName.IsEquivalent(other.ScopeName) &&
return ComponentKey == other.ComponentKey &&
Scope == other.Scope &&
Container.IsEquivalent(other.Container) &&
RoutineName.IsEquivalent(other.RoutineName) &&
ReferenceId.IsEquivalent(other.ReferenceId) &&
ReferenceType.IsEquivalent(other.ReferenceType);
Expand All @@ -110,16 +147,16 @@ public bool IsSame(LogixReference other)
return GetReference() switch
{
LogixComponent component => component.Name,
LogixCode code => code.Location,
DiagramElement diagram => $"{diagram.Location} ",
LogixCode code => code.Identifier,
DiagramBlock diagram => $"{diagram.Location} ",
_ => null
};
}

/// <summary>
/// Deserialized the element into the appropriate <see cref="LogixElement"/> type.
/// </summary>
private LogixElement GetReference() => LogixSerializer.Deserialize(_element);
private LogixElement GetReference() => _element.Deserialize();

/// <summary>
/// Uses the underlying element hierarchy to determine the name of the <c>Task</c> that the reference is contained.
Expand All @@ -132,7 +169,7 @@ public bool IsSame(LogixReference other)
.Ancestors(L5XName.RSLogix5000Content)
.Descendants(L5XName.Task)
.FirstOrDefault(e => e.Descendants(L5XName.ScheduledProgram)
.Any(p => p.Attribute(L5XName.Name)?.Value == ScopeName))
.Any(p => p.Attribute(L5XName.Name)?.Value == Container))
?.LogixName();
}
}
Loading

0 comments on commit 002b4d4

Please sign in to comment.