Skip to content

Commit

Permalink
FBD refinement. L5X API refinement. Documentations. Bug fixes. Other …
Browse files Browse the repository at this point in the history
…development
  • Loading branch information
tnunnink committed Nov 22, 2023
1 parent 90146ed commit 4e6f4d2
Show file tree
Hide file tree
Showing 51 changed files with 2,586 additions and 10,496 deletions.
190 changes: 97 additions & 93 deletions src/.idea/.idea.L5Sharp/.idea/workspace.xml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/L5Sharp.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PID/@EntryIndexedValue">PID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RAD/@EntryIndexedValue">LOG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=REAL/@EntryIndexedValue">REAL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RET/@EntryIndexedValue">RET</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>
Expand Down
162 changes: 85 additions & 77 deletions src/L5Sharp/Common/CrossReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,150 +2,158 @@
using System.Linq;
using System.Xml.Linq;
using JetBrains.Annotations;
using L5Sharp.Elements;
using L5Sharp.Enums;
using L5Sharp.Utilities;

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.
/// from another component. This class is meant to provide a uniform set of information for all types of references,
/// however, code references have some additional information that is useful for further identifying the target or
/// location of the reference.
/// </summary>
[PublicAPI]
public class CrossReference
{
private readonly XElement _element;

/// <summary>
/// Creates a new <see cref="CrossReference"/> with a referencing element, component name and type.
/// Creates a new <see cref="CrossReference"/> with a referencing element, component name and type, and optional instruction data.
/// </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 CrossReference(XElement element, string type, string name)
/// <param name="type">The type of the component that is being referenced.</param>
/// <param name="reference">The name of the component that is being referenced.</param>
/// <param name="instruction">The optional instruction name/key for the reference.
/// This is intended for code references as opposed to component references. Will be null if not applicable.</param>
/// <exception cref="ArgumentNullException"><c>element</c>, <c>type</c>, or <c>name</c> is <c>null</c>.</exception>
public CrossReference(XElement element, string type, string reference, Instruction? instruction = null)
{
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));

if (string.IsNullOrEmpty(type))
throw new ArgumentException("Type cannot be null or empty.", nameof(type));
if (string.IsNullOrEmpty(reference))
throw new ArgumentException("Name cannot be null or empty.", nameof(reference));

Type = type;
Reference = reference;
Instruction = instruction;
}

/// <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);
public ComponentKey Key => new(Type, Reference);

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

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

/// <summary>
/// The referencing <see cref="XElement"/> object
/// The <see cref="LogixElement"/> that is contains the reference to the component.
/// </summary>
public LogixElement Reference => GetReference();
/// <value>The <see cref="LogixElement"/> object that contains the component reference. This may be another
/// <c>Component</c>, a <c>Code</c> instance, or even a single <c>DiagramBlock</c> object.</value>
public LogixElement Element => _element.Deserialize();

/// <summary>
/// The location of the reference if this is a <see cref="LogixCode"/> reference type.
/// The type of the <c>LogixElement</c> that contains the reference to the component.
/// </summary>
/// <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;
/// <value>A <see cref="string"/> representing the name of the element type.</value>
/// <remarks>This helps further identify the reference element relative to other references.</remarks>
public string ElementType => _element.Name.LocalName;

/// <summary>
/// The type of the element referencing the component for this reference.
/// A unique identifier of the <c>LogixElement</c> that contains the reference to the component.
/// </summary>
public string ReferenceType => GetReference().L5XType;
/// <value>A <see cref="string"/> containing the name or number identifying the reference element.</value>
/// <remarks>
/// This will ultimately be either the name of the referencing component (for Tag references),
/// the number of the referencing rung or line of logic (for RLL and ST code), or the ID of the referencing
/// diagram block (for FBD/SFC code). This helps further identify the reference element relative to other references.
/// </remarks>
public string ElementId => _element.Attribute(L5XName.Name) is not null ? _element.Attribute(L5XName.Name)!.Value
: _element.Attribute(L5XName.Number) is not null ? _element.Attribute(L5XName.Number)!.Value
: _element.Attribute(L5XName.ID) is not null ? _element.Attribute(L5XName.ID)!.Value
: string.Empty;

/// <summary>
/// The name of the <c>Task</c> that the reference is contained within if applicable.
/// </summary>
/// <value>A <see cref="string"/> representing the containing task if found; Otherwise, an empty string.</value>
/// <remarks>
/// This could potentially be helpful for analyzing references to tags that are used across multiple
/// Task components.
/// </remarks>
public string Task => Scope.Task(_element);

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

public Scope Scope => Scope.Type(_element);
/// <summary>
/// The name of the scoped program, instruction, or controller that the reference is contained within.
/// </summary>
/// <value>
/// A <see cref="string"/> representing the name of program, controller, or instruction the reference
/// is contained within.
/// </value>
public string Container => Scope.ScopeName(_element);

public string Container => Scope.Container(_element);
/// <summary>
/// The name of the <c>Routine</c> that the reference is contained within, it is a <see cref="LogixCode"/>
/// type element.
/// </summary>
/// <value>A <see cref="string"/> representing the containing routine if found; Otherwise, an empty string.</value>
public string RoutineName => _element.Ancestors(L5XName.Routine).FirstOrDefault()?.LogixName() ?? string.Empty;

/// <summary>
/// The name of the <c>Task</c> that the reference is contained within if applicable.
/// </summary>
/// <value>A <see cref="string"/> representing the containing task if found; Otherwise, an empty string.</value>
public string TaskName => GetTaskName() ?? string.Empty;
public string Routine => _element.Ancestors(L5XName.Routine).FirstOrDefault()?.LogixName() ?? string.Empty;

/// <summary>
/// Determines if this <see cref="CrossReference"/> is the same as another <see cref="CrossReference"/>.
/// The instruction object containing the reference to the component if this reference is a logic or code reference.
/// </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(CrossReference other)
/// <value>If the reference is a code reference, then the <see cref="Common.Instruction"/> object the reference
/// was found; Otherwise, <c>null</c>.</value>
/// <remarks>
/// The helps further identify where in the logix element the reference is located. Having the associated
/// instruction can help for searching or filtering references and finding other references sharing the common
/// instruction.
/// </remarks>
public Instruction? Instruction { get; }

/// <inheritdoc />
public override bool Equals(object? obj)
{
return ComponentKey == other.ComponentKey &&
if (ReferenceEquals(this, obj))
return true;

if (obj is not CrossReference other)
return false;

return Key == other.Key &&
Scope == other.Scope &&
Container.IsEquivalent(other.Container) &&
RoutineName.IsEquivalent(other.RoutineName) &&
ReferenceId.IsEquivalent(other.ReferenceId) &&
ReferenceType.IsEquivalent(other.ReferenceType);
Routine.IsEquivalent(other.Routine) &&
ElementId.IsEquivalent(other.ElementId) &&
ElementType.IsEquivalent(other.ElementType);
}

/// <summary>
/// Determines the string that identifies this reference element within the context or scope of the L5X.
/// </summary>
private string? GetIdentifier()
/// <inheritdoc />
public override int GetHashCode()
{
return GetReference() switch
{
LogixComponent component => component.Name,
LogixCode code => code.Identifier,
DiagramBlock diagram => $"{diagram.Location} ",
_ => null
};
return HashCode.Combine(Key, Scope, Container, Routine, ElementId, ElementType);
}

/// <summary>
/// Deserialized the element into the appropriate <see cref="LogixElement"/> type.
/// </summary>
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.
/// This would only apply to code or scoped tag reference types. Controller scoped items are not contained
/// within a task
/// </summary>
private string? GetTaskName()
{
return _element
.Ancestors(L5XName.RSLogix5000Content)
.Descendants(L5XName.Task)
.FirstOrDefault(e => e.Descendants(L5XName.ScheduledProgram)
.Any(p => p.Attribute(L5XName.Name)?.Value == Container))
?.LogixName();
}
/// <inheritdoc />
public override string ToString() => Key.ToString();
}
4 changes: 2 additions & 2 deletions src/L5Sharp/Common/TagName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public TagName(string tagName)
/// <remarks>
/// <para>
/// The root portion of a given tag name is simply the beginning part of the tag name up to the first
/// member separator character ('.' or '['). For Module defined tags, this includes the colon separator.
/// member separator character '.' or '['. For Module defined tags, this includes the colon separator.
/// </para>
/// <para>
/// This value can be swapped out easily using <see cref="Rename"/> to return a new <see cref="TagName"/> with the
Expand Down Expand Up @@ -222,7 +222,7 @@ public bool Contains(TagName tagName)
/// <param name="second">A tag name object to compare.</param>
/// <param name="comparer">The equality comparer to use for comparison.</param>
/// <returns><c>true</c> if the tag name are equal according too the provided comparer; otherwise, false.</returns>
/// <remarks>Use the prebuilt <see cref="TagNameComparer"/> class for several predefined comparers.</remarks>
/// <remarks>Use the prebuilt <see cref="TagNameComparer"/> class for several predefined comparer objects.</remarks>
public static bool Equals(TagName first, TagName second, IEqualityComparer<TagName> comparer) =>
comparer.Equals(first, second);

Expand Down
2 changes: 1 addition & 1 deletion src/L5Sharp/Components/DataType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public override IEnumerable<LogixComponent> Dependencies()

foreach (var member in Members)
{
var dataType = L5X.FindComponent<DataType>(member.DataType);
var dataType = L5X.Find<DataType>(member.DataType);
if (dataType is null) continue;
dependencies.Add(dataType);
dependencies.AddRange(dataType.Dependencies());
Expand Down
7 changes: 6 additions & 1 deletion src/L5Sharp/Components/Tag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ public string? Unit
/// <seealso cref="Parent"/>
public Tag Root { get; }

/// <summary>
///
/// </summary>
public Tag? Alias => AliasFor is not null ? L5X?.Find(AliasFor) : default;

/// <summary>
/// The full tag name path of the <see cref="Tag"/>.
/// </summary>
Expand Down Expand Up @@ -288,7 +293,7 @@ public Tag this[TagName tagName]
get
{
if (tagName is null) throw new ArgumentNullException(nameof(tagName));
if (tagName.IsEmpty) throw new ArgumentException("Can not retrieve member from empty tag name.");
if (tagName.IsEmpty) return this;

var member = Value.Member(tagName.Root);
if (member is null)
Expand Down
25 changes: 20 additions & 5 deletions src/L5Sharp/Elements/AOI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ public string? Name
}

/// <summary>
/// The backing tag name for the <c>AoiBlock</c> instance.
/// The backing tag name for the AOI block element.
/// </summary>
/// <value>A <see cref="string"/> containing the tag name if it exists; Otherwise, <c>null</c>.</value>
public string? Operand
public TagName? Operand
{
get => GetValue<string>();
get => GetValue<TagName>();
set => SetValue(value);
}

Expand All @@ -62,7 +62,13 @@ public string? Operand
/// <value>A <see cref="IEnumerable{T}"/> containing the names of the pins if found. If not found then an
/// empty collection.</value>
/// <remarks>To update the property, you must assign a new collection of pin names.</remarks>
public IEnumerable<TagName> VisiblePins => throw new NotImplementedException();
public Params? VisiblePins
{
get => Element.Attribute(L5XName.VisiblePins) is not null
? new Params(Element.Attribute(L5XName.VisiblePins)!)
: default;
set => SetValue(value is not null ? string.Join(" ", value) : null);
}

/// <summary>
/// The collection of input/output parameters for the <c>AoiBlock</c> instance.
Expand All @@ -88,7 +94,7 @@ public IEnumerable<KeyValuePair<string, string>> Parameters
/// <inheritdoc />
public override IEnumerable<CrossReference> References()
{
if (Operand is not null && Operand.IsTag())
if (Operand is not null)
yield return new CrossReference(Element, Operand, L5XName.Tag);

if (Name is not null)
Expand All @@ -98,4 +104,13 @@ public override IEnumerable<CrossReference> References()
if (parameter.Value.IsTag())
yield return new CrossReference(Element, parameter.Value, L5XName.Tag);
}

/// <inheritdoc />
protected override IEnumerable<Argument> GetArguments(KeyValuePair<uint, string?> endpoint)
{
//todo we need to think about how could this be an invalid call (i.e. why check pins)
yield return Operand is not null && endpoint.Value is not null && VisiblePins?.Contains(endpoint.Value) == true
? TagName.Concat(Operand, endpoint.Value)
: Argument.Empty;
}
}
Loading

0 comments on commit 4e6f4d2

Please sign in to comment.