diff --git a/src/L5Sharp.Core/Common/Instruction.cs b/src/L5Sharp.Core/Common/Instruction.cs index 74537a70..7f1717c5 100644 --- a/src/L5Sharp.Core/Common/Instruction.cs +++ b/src/L5Sharp.Core/Common/Instruction.cs @@ -1801,7 +1801,7 @@ private Argument[] ParseArguments() var signature = _text.Substring(start, length); - return Regex.Split(signature, ArgumentSplitPattern).Select(a => new Argument(a)).ToArray(); + return Regex.Split(signature, ArgumentSplitPattern).Select(a => new Argument(a.Trim())).ToArray(); } /// diff --git a/src/L5Sharp.Core/Enums/ArgumentType.cs b/src/L5Sharp.Core/Enums/ArgumentType.cs index f02edb55..fc4a4d25 100644 --- a/src/L5Sharp.Core/Enums/ArgumentType.cs +++ b/src/L5Sharp.Core/Enums/ArgumentType.cs @@ -26,12 +26,15 @@ private ArgumentType(string name, string value) : base(name, value) public static ArgumentType Of(string value) { if (string.IsNullOrEmpty(value)) return Empty; - if (value.StartsWith('\'') && value.EndsWith('\'')) return String; - if (Radix.TryInfer(value, out _)) return Atomic; + + var trimmed = value.Trim(); + + if (trimmed.StartsWith('\'') && trimmed.EndsWith('\'')) return String; + if (Radix.TryInfer(trimmed, out _)) return Atomic; // We can use the tag name patter match because system components should pass this as well - if (TagName.IsTag(value)) return Reference; + if (TagName.IsTag(trimmed)) return Reference; // todo I think we need to include bitwise operators (AND, OR, XOR, etc.) - if (value.IndexOfAny(['=', '>', '<', '+', '-', '*', '/', '(', ')']) >= 0) return Expression; + if (trimmed.IndexOfAny(['=', '>', '<', '+', '-', '*', '/', '(', ')']) >= 0) return Expression; return Unknown; } diff --git a/tests/L5Sharp.Tests.Core/Code/RungTests.cs b/tests/L5Sharp.Tests.Core/Code/RungTests.cs index 71725967..870e18c6 100644 --- a/tests/L5Sharp.Tests.Core/Code/RungTests.cs +++ b/tests/L5Sharp.Tests.Core/Code/RungTests.cs @@ -204,4 +204,54 @@ public Task SetTextToNullThenCommentThenTextAgainShouldBeValid() return VerifyXml(xml); } + + [TestCase("MOV(Flow_Rate, Flow_Rate_Scaled);", new[] { "Flow_Rate", "Flow_Rate_Scaled" })] + [TestCase("ADD(Tank_Level.PV, Tank_Level.Offset, Tank_Level.Corrected);", new[] { "Tank_Level.PV", "Tank_Level.Offset", "Tank_Level.Corrected" })] + [TestCase("MOV(Motor1.Status, PanelLights.Green);", new[] { "Motor1.Status", "PanelLights.Green" })] + [TestCase("TON(Motor1_Timer, Timer_Preset, Timer_Accum);", new[] { "Motor1_Timer", "Timer_Preset", "Timer_Accum" })] + [TestCase("MOV(5000, Motor1_Timer.PRE);", new[] { "Motor1_Timer.PRE" })] + public void Tags_RungWithMultipleArgs_ShouldCountAllTagReferences(string text, string[] expectedTags) + { + var rung = new Rung(text); + + var tags = rung.Tags().ToList(); + + tags.Should().HaveCount(expectedTags.Length); + foreach (var expected in expectedTags) + { + tags.Should().Contain(t => (string)t == expected); + } + } + + [TestCase("XIC(Motor1_Start)OTE(Motor1_Start);", new[] { "Motor1_Start" }, new[] { 2 })] + [TestCase("XIC(Motor1_Start)XIO(Motor1_Stop)OTE(Motor1_Start);", new[] { "Motor1_Start", "Motor1_Stop" }, new[] { 2, 1 })] + [TestCase("XIC(Motor1_Status.Running)OTE(Motor1_Status.Running);", new[] { "Motor1_Status.Running" }, new[] { 2 })] + public void Tags_RungWithDuplicateTagInMultipleInstructions_ShouldCountEachOccurrence( + string text, string[] tagNames, int[] expectedCounts) + { + var rung = new Rung(text); + + var tags = rung.Tags().ToList(); + + tags.Should().HaveCount(expectedCounts.Sum()); + for (var i = 0; i < tagNames.Length; i++) + { + tags.Count(t => (string)t == tagNames[i]).Should().Be(expectedCounts[i]); + } + } + + [Test] + public void Tags_RungWithBranchAndNestedInstructions_ShouldCountAllReferences() + { + var rung = new Rung("[XIC(Start),XIO(Stop)]OTE(Motor);MOV(Motor_Status, Log);"); + + var tags = rung.Tags().ToList(); + + tags.Should().HaveCount(5); + tags.Count(t => (string)t == "Start").Should().Be(1); + tags.Count(t => (string)t == "Stop").Should().Be(1); + tags.Count(t => (string)t == "Motor").Should().Be(1); + tags.Count(t => (string)t == "Motor_Status").Should().Be(1); + tags.Count(t => (string)t == "Log").Should().Be(1); + } } \ No newline at end of file diff --git a/tests/L5Sharp.Tests.Core/Common/ArgumentTests.cs b/tests/L5Sharp.Tests.Core/Common/ArgumentTests.cs index 19eabd3e..15d38b7a 100644 --- a/tests/L5Sharp.Tests.Core/Common/ArgumentTests.cs +++ b/tests/L5Sharp.Tests.Core/Common/ArgumentTests.cs @@ -162,4 +162,14 @@ public void ToNeutralText_WhenCalled_ShouldBeExpected() text.Should().NotBeNull(); text.ToString().Should().Be("SomeTag > 100"); } + + [TestCase(" Motor1.Status")] + [TestCase(" MyTag")] + public void New_TagWithLeadingWhitespace_ShouldBeReference(string value) + { + Argument argument = value; + + argument.Type.Should().Be(ArgumentType.Reference); + argument.IsReference.Should().BeTrue(); + } } \ No newline at end of file diff --git a/tests/L5Sharp.Tests.Core/Common/InstructionTests.cs b/tests/L5Sharp.Tests.Core/Common/InstructionTests.cs index ad74e17e..d26f37a4 100644 --- a/tests/L5Sharp.Tests.Core/Common/InstructionTests.cs +++ b/tests/L5Sharp.Tests.Core/Common/InstructionTests.cs @@ -356,5 +356,17 @@ public void ToString_WhenCalled_ShouldBeText() text.Should().Be("OTE(MyTag)"); } + + [TestCase("MOV(Motor1.Status, PanelLights.Green)", 2)] + [TestCase("ADD(Tank_Level.PV, Tank_Level.Offset, Tank_Level.Corrected)", 3)] + public void Parse_CommaSeparatedArgsWithWhitespace_ShouldParseAllAsReferences(string text, int expectedCount) + { + var instructions = Instruction.Parse(text).ToList(); + + var arguments = instructions[0].Arguments.ToList(); + + arguments.Should().HaveCount(expectedCount); + arguments.Should().AllSatisfy(a => a.IsReference.Should().BeTrue()); + } } } \ No newline at end of file diff --git a/tests/L5Sharp.Tests.Core/Common/TagNameTests.cs b/tests/L5Sharp.Tests.Core/Common/TagNameTests.cs index d7f024f3..07dbc6b6 100644 --- a/tests/L5Sharp.Tests.Core/Common/TagNameTests.cs +++ b/tests/L5Sharp.Tests.Core/Common/TagNameTests.cs @@ -688,5 +688,19 @@ public void CompareTo_Null_ShouldBeOne() result.Should().Be(1); } + + [TestCase("MyTag")] + [TestCase("Motor1.Status")] + public void IsTag_ValidTagName_ShouldReturnTrue(string value) + { + TagName.IsTag(value).Should().BeTrue(); + } + + [TestCase(" MyTag")] + [TestCase(" Motor1.Status")] + public void IsTag_TagWithLeadingSpace_ShouldReturnFalse(string value) + { + TagName.IsTag(value).Should().BeFalse(); + } } } \ No newline at end of file diff --git a/tests/L5Sharp.Tests.Core/Enums/ArgumentTypeTests.cs b/tests/L5Sharp.Tests.Core/Enums/ArgumentTypeTests.cs index 18849a28..3169aea0 100644 --- a/tests/L5Sharp.Tests.Core/Enums/ArgumentTypeTests.cs +++ b/tests/L5Sharp.Tests.Core/Enums/ArgumentTypeTests.cs @@ -104,4 +104,13 @@ public void Of_ValidTagName_ShouldBeTag() type.Should().Be(ArgumentType.Reference); } + + [TestCase(" MyTag")] + [TestCase(" Motor1.Status")] + public void Of_TagWithLeadingSpace_ShouldBeReference(string value) + { + var type = ArgumentType.Of(value); + + type.Should().Be(ArgumentType.Reference); + } } \ No newline at end of file diff --git a/tests/L5Sharp.Tests.Core/L5XReferencesTests.cs b/tests/L5Sharp.Tests.Core/L5XReferencesTests.cs index b5d463a7..b436266f 100644 --- a/tests/L5Sharp.Tests.Core/L5XReferencesTests.cs +++ b/tests/L5Sharp.Tests.Core/L5XReferencesTests.cs @@ -22,6 +22,52 @@ public void TagReferences_ProjectWithSimpleRungReferences_ShouldBeExpectedCount( references.Should().HaveCount(3); } + [Test] + public void TagReferences_RungWithCommaSeparatedArgs_ShouldCountAllOccurrences() + { + var project = L5X.New("MyProject", "1756-L84E", "34.1") + .Add(Tag.Named("Source").WithValue(100).Build()) + .Add(Tag.Named("Source.Member").WithValue(200).Build()) + .Add(Routine.Rll("MyRoutine").InProgram("MyProgram") + .WithRung("MOV(Source, Source.Member);") + .Build()); + + var sourceRefs = project.Tags.Get("Source").References().ToList(); + var memberRefs = project.Tags.Get("Source.Member").References().ToList(); + + sourceRefs.Should().HaveCount(1); + memberRefs.Should().HaveCount(1); + } + + [Test] + public void TagReferences_SameTagUsedTwiceInDifferentInstructions_ShouldCountBoth() + { + var project = L5X.New("MyProject", "1756-L84E", "34.1") + .Add(Tag.Named("MyTag").WithValue(123).Build()) + .Add(Routine.Rll("MyRoutine").InProgram("MyProgram") + .WithRung("XIC(MyTag)OTE(MyTag);") + .Build()); + + var references = project.Tags.Get("MyTag").References().ToList(); + + references.Should().HaveCount(2); + } + + [Test] + public void TagReferences_SameTagUsedTwiceInSameInstruction_ShouldCountBoth() + { + var project = L5X.New("MyProject", "1756-L84E", "34.1") + .Add(Tag.Named("MyTag").WithValue(100).Build()) + .Add(Tag.Named("Dest").WithValue(0).Build()) + .Add(Routine.Rll("MyRoutine").InProgram("MyProgram") + .WithRung("ADD(MyTag, MyTag, Dest);") + .Build()); + + var references = project.Tags.Get("MyTag").References().ToList(); + + references.Should().HaveCount(2); + } + #region TestFile [Test]