chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||||
|
||||
/// <summary>
|
||||
/// Parsed TwinCAT symbolic tag path. Handles global-variable-list (<c>GVL.Counter</c>),
|
||||
/// program-variable (<c>MAIN.bStart</c>), structured member access
|
||||
/// (<c>Motor1.Status.Running</c>), array subscripts (<c>Data[5]</c>), multi-dim arrays
|
||||
/// (<c>Matrix[1,2]</c>), and bit-access (<c>Flags.0</c>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>TwinCAT's symbolic syntax mirrors IEC 61131-3 structured-text identifiers — so the
|
||||
/// grammar maps cleanly onto the AbCip Logix path parser, but without Logix's
|
||||
/// <c>Program:</c> scope prefix. The leading segment is the namespace (POU name /
|
||||
/// GVL name) and subsequent segments walk into struct/array members.</para>
|
||||
/// </remarks>
|
||||
public sealed record TwinCATSymbolPath(
|
||||
IReadOnlyList<TwinCATSymbolSegment> Segments,
|
||||
int? BitIndex)
|
||||
{
|
||||
public string ToAdsSymbolName()
|
||||
{
|
||||
var buf = new System.Text.StringBuilder();
|
||||
for (var i = 0; i < Segments.Count; i++)
|
||||
{
|
||||
if (i > 0) buf.Append('.');
|
||||
var seg = Segments[i];
|
||||
buf.Append(seg.Name);
|
||||
if (seg.Subscripts.Count > 0)
|
||||
buf.Append('[').Append(string.Join(",", seg.Subscripts)).Append(']');
|
||||
}
|
||||
if (BitIndex is not null) buf.Append('.').Append(BitIndex.Value);
|
||||
return buf.ToString();
|
||||
}
|
||||
|
||||
public static TwinCATSymbolPath? TryParse(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
var src = value.Trim();
|
||||
|
||||
var parts = new List<string>();
|
||||
var depth = 0;
|
||||
var start = 0;
|
||||
for (var i = 0; i < src.Length; i++)
|
||||
{
|
||||
var c = src[i];
|
||||
if (c == '[') depth++;
|
||||
else if (c == ']') depth--;
|
||||
else if (c == '.' && depth == 0)
|
||||
{
|
||||
parts.Add(src[start..i]);
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
parts.Add(src[start..]);
|
||||
if (depth != 0 || parts.Any(string.IsNullOrEmpty)) return null;
|
||||
|
||||
int? bitIndex = null;
|
||||
if (parts.Count >= 2 && int.TryParse(parts[^1], out var maybeBit)
|
||||
&& maybeBit is >= 0 and <= 31
|
||||
&& !parts[^1].Contains('['))
|
||||
{
|
||||
bitIndex = maybeBit;
|
||||
parts.RemoveAt(parts.Count - 1);
|
||||
}
|
||||
|
||||
var segments = new List<TwinCATSymbolSegment>(parts.Count);
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var bracketIdx = part.IndexOf('[');
|
||||
if (bracketIdx < 0)
|
||||
{
|
||||
if (!IsValidIdent(part)) return null;
|
||||
segments.Add(new TwinCATSymbolSegment(part, []));
|
||||
continue;
|
||||
}
|
||||
if (!part.EndsWith(']')) return null;
|
||||
var name = part[..bracketIdx];
|
||||
if (!IsValidIdent(name)) return null;
|
||||
var inner = part[(bracketIdx + 1)..^1];
|
||||
var subs = new List<int>();
|
||||
foreach (var tok in inner.Split(','))
|
||||
{
|
||||
if (!int.TryParse(tok, out var n) || n < 0) return null;
|
||||
subs.Add(n);
|
||||
}
|
||||
if (subs.Count == 0) return null;
|
||||
segments.Add(new TwinCATSymbolSegment(name, subs));
|
||||
}
|
||||
if (segments.Count == 0) return null;
|
||||
|
||||
return new TwinCATSymbolPath(segments, bitIndex);
|
||||
}
|
||||
|
||||
private static bool IsValidIdent(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return false;
|
||||
if (!char.IsLetter(s[0]) && s[0] != '_') return false;
|
||||
for (var i = 1; i < s.Length; i++)
|
||||
if (!char.IsLetterOrDigit(s[i]) && s[i] != '_') return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record TwinCATSymbolSegment(string Name, IReadOnlyList<int> Subscripts);
|
||||
Reference in New Issue
Block a user