@@ -205,6 +205,121 @@ public sealed class AbLegacyAddressTests
|
||||
AbLegacyPlcFamilyProfile.LogixPccc.SupportsFunctionFiles.ShouldBeFalse();
|
||||
}
|
||||
|
||||
// ---- Indirect / indexed addressing (Issue #247) ----
|
||||
//
|
||||
// PLC-5 / SLC permit `N7:[N7:0]` (word number sourced from another address) and
|
||||
// `N[N7:0]:5` (file number sourced from another address). Recursion is capped at 1 — the
|
||||
// inner address must itself be a plain direct PCCC reference.
|
||||
|
||||
[Fact]
|
||||
public void TryParse_accepts_indirect_word_source()
|
||||
{
|
||||
var a = AbLegacyAddress.TryParse("N7:[N7:0]");
|
||||
a.ShouldNotBeNull();
|
||||
a.FileLetter.ShouldBe("N");
|
||||
a.FileNumber.ShouldBe(7);
|
||||
a.IndirectFileSource.ShouldBeNull();
|
||||
a.IndirectWordSource.ShouldNotBeNull();
|
||||
a.IndirectWordSource!.FileLetter.ShouldBe("N");
|
||||
a.IndirectWordSource.FileNumber.ShouldBe(7);
|
||||
a.IndirectWordSource.WordNumber.ShouldBe(0);
|
||||
a.IsIndirect.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_accepts_indirect_file_source()
|
||||
{
|
||||
var a = AbLegacyAddress.TryParse("N[N7:0]:5");
|
||||
a.ShouldNotBeNull();
|
||||
a.FileLetter.ShouldBe("N");
|
||||
a.FileNumber.ShouldBeNull();
|
||||
a.WordNumber.ShouldBe(5);
|
||||
a.IndirectFileSource.ShouldNotBeNull();
|
||||
a.IndirectFileSource!.FileLetter.ShouldBe("N");
|
||||
a.IndirectFileSource.FileNumber.ShouldBe(7);
|
||||
a.IndirectFileSource.WordNumber.ShouldBe(0);
|
||||
a.IndirectWordSource.ShouldBeNull();
|
||||
a.IsIndirect.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_accepts_both_indirect_file_and_word()
|
||||
{
|
||||
var a = AbLegacyAddress.TryParse("N[N7:0]:[N7:1]");
|
||||
a.ShouldNotBeNull();
|
||||
a.IndirectFileSource.ShouldNotBeNull();
|
||||
a.IndirectWordSource.ShouldNotBeNull();
|
||||
a.IndirectWordSource!.WordNumber.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("N[N[N7:0]:0]:5")] // depth-2 file source
|
||||
[InlineData("N7:[N[N7:0]:0]")] // depth-2 word source
|
||||
[InlineData("N7:[N7:[N7:0]]")] // depth-2 word source (nested word)
|
||||
public void TryParse_rejects_depth_greater_than_one(string input)
|
||||
{
|
||||
AbLegacyAddress.TryParse(input).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("N7:[")] // unbalanced bracket
|
||||
[InlineData("N7:]")] // unbalanced bracket
|
||||
[InlineData("N[:5")] // empty inner file source
|
||||
[InlineData("N7:[]")] // empty inner word source
|
||||
[InlineData("N[X9:0]:5")] // unknown file letter inside
|
||||
public void TryParse_rejects_malformed_indirect(string input)
|
||||
{
|
||||
AbLegacyAddress.TryParse(input).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToLibplctagName_reemits_indirect_word_source()
|
||||
{
|
||||
var a = AbLegacyAddress.TryParse("N7:[N7:0]");
|
||||
a.ShouldNotBeNull();
|
||||
a.ToLibplctagName().ShouldBe("N7:[N7:0]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToLibplctagName_reemits_indirect_file_source()
|
||||
{
|
||||
var a = AbLegacyAddress.TryParse("N[N7:0]:5");
|
||||
a.ShouldNotBeNull();
|
||||
a.ToLibplctagName().ShouldBe("N[N7:0]:5");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_indirect_with_bit_outside_brackets()
|
||||
{
|
||||
// Outer bit applies to the resolved word; inner address is still depth-1.
|
||||
var a = AbLegacyAddress.TryParse("N7:[N7:0]/3");
|
||||
a.ShouldNotBeNull();
|
||||
a.BitIndex.ShouldBe(3);
|
||||
a.IndirectWordSource.ShouldNotBeNull();
|
||||
a.ToLibplctagName().ShouldBe("N7:[N7:0]/3");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_Plc5_indirect_inner_address_obeys_octal()
|
||||
{
|
||||
// Inner I:/O: indices on PLC-5 must obey octal rules even when nested in brackets.
|
||||
var a = AbLegacyAddress.TryParse("N7:[I:010/10]", AbLegacyPlcFamily.Plc5);
|
||||
a.ShouldNotBeNull();
|
||||
a.IndirectWordSource.ShouldNotBeNull();
|
||||
a.IndirectWordSource!.WordNumber.ShouldBe(8); // octal 010 → 8
|
||||
a.IndirectWordSource.BitIndex.ShouldBe(8); // octal 10 → 8
|
||||
|
||||
// Octal-illegal digit '8' inside an inner I: address is rejected on PLC-5.
|
||||
AbLegacyAddress.TryParse("N7:[I:8/0]", AbLegacyPlcFamily.Plc5).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_indirect_inner_cannot_itself_be_indirect()
|
||||
{
|
||||
AbLegacyAddress.TryParse("N7:[N7:[N7:0]]").ShouldBeNull();
|
||||
AbLegacyAddress.TryParse("N[N[N7:0]:5]:5").ShouldBeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("RTC", "HR", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Int32)]
|
||||
[InlineData("RTC", "EN", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Boolean)]
|
||||
|
||||
Reference in New Issue
Block a user