Skip to content

Commit d7c784b

Browse files
authored
Merge pull request #511 from Inxton/510-various-additions
2 parents 4f23f3a + 5861c7a commit d7c784b

47 files changed

Lines changed: 914 additions & 49 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Directory.Packages.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<PackageVersion Include="Octokit.Extensions" Version="1.0.7" />
2222
<PackageVersion Include="Cake.Frosting" Version="5.0.0" />
2323
<PackageVersion Include="Cake.Powershell" Version="4.0.0" />
24-
<PackageVersion Include="Google.Protobuf" Version="3.28.3" />
24+
<PackageVersion Include="Google.Protobuf" Version="3.32.0" />
2525
<PackageVersion Include="CliWrap" Version="3.6.7" />
2626
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
2727
<PackageVersion Include="NuGet.Configuration" Version="6.12.1" />
@@ -45,7 +45,7 @@
4545
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
4646
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
4747
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
48-
<PackageVersion Include="Siemens.Simatic.S7.Webserver.API" Version="3.3.3" />
48+
<PackageVersion Include="Siemens.Simatic.S7.Webserver.API" Version="3.3.24" />
4949
<PackageVersion Include="nunit" Version="4.3.2" />
5050
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
5151
<PackageVersion Include="NSubstitute" Version="5.3.0" />
@@ -57,7 +57,7 @@
5757
<PackageVersion Include="MSTest.TestFramework" Version="3.6.3" />
5858
<PackageVersion Include="Moq" Version="4.20.72" />
5959
<PackageVersion Include="System.Interactive" Version="6.0.1" />
60-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
60+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
6161
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="10.0.0" />
6262
<PackageVersion Include="System.Collections.Immutable" Version="10.0.0" />
6363
<PackageVersion Include="System.IO.Packaging" Version="10.0.0" />

docfx/articles/compiler/ADDED_MEMBERS.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,109 @@ When the application tries to write to the variable it first validates that the
176176

177177
AttributeToolTip allows you to describe the variable or an object. These can be then used to give short hints to the user in the application. This attribute can be localized.
178178

179+
## Typed Enum Accessor Properties
180+
181+
### Overview
182+
183+
For properties that store enumeration values (either PLC enums or named value types with integral backing types), the AXSharp compiler automatically generates typed enum accessor properties. These properties provide a convenient way to access the enum value cast to a strongly-typed C# enum, without requiring explicit casting.
184+
185+
### When Generated
186+
187+
Typed enum accessor properties are generated for:
188+
- Properties based on PLC `ENUM` types
189+
- Properties based on **named value types with integral backing types** (BYTE, USINT, SINT, INT, UINT, WORD, DINT, DWORD, UDINT, LINT, LWORD, ULINT)
190+
191+
### Naming Convention
192+
193+
For a property named `Status` of enum type `Color`, the compiler generates an accessor property named `StatusEnum`.
194+
195+
### Example
196+
197+
**PLC code:**
198+
~~~iecst
199+
{S7.extern=ReadWrite}
200+
TYPE Color : DINT
201+
Black := 0;
202+
White := 1;
203+
Red := 2;
204+
END_TYPE
205+
206+
CLASS PUBLIC MyClass
207+
VAR PUBLIC
208+
Status : Color;
209+
END_VAR
210+
END_CLASS
211+
~~~
212+
213+
**Generated C# code:**
214+
~~~C#
215+
public partial class MyClass : AXSharp.Connector.ITwinObject
216+
{
217+
[AXSharp.Connector.EnumeratorDiscriminatorAttribute(typeof(Color))]
218+
public OnlinerInt Status { get; }
219+
220+
public Color StatusEnum { get => (Color)Status.LastValue; }
221+
}
222+
~~~
223+
224+
### Usage
225+
226+
Instead of manually casting, you can use the generated typed accessor:
227+
228+
~~~C#
229+
// Without typed accessor - requires casting
230+
var statusValue = (Color)myObject.Status.LastValue;
231+
232+
// With typed accessor - automatic casting
233+
var statusValue = myObject.StatusEnum;
234+
~~~
235+
236+
### Important: Reading the Backing Property First
237+
238+
**The typed enum accessor properties read from the `LastValue` of the backing property.** This means that **the backing property must be read from the PLC first** for the accessor to return a valid value.
239+
240+
The accessor does **not** perform any I/O operation itself—it simply casts the last read value.
241+
242+
#### Example Scenario
243+
244+
~~~C#
245+
// INCORRECT - StatusEnum will return default/zero value
246+
var status = myObject.StatusEnum; // Invalid! No read was performed yet
247+
248+
// CORRECT - Read the backing property first
249+
await myObject.Status.GetAsync(); // Read from PLC
250+
var status = myObject.StatusEnum; // Now valid with the read value
251+
252+
// CORRECT - Bulk read on the object
253+
myObject.Read(); // Cyclic read of all properties
254+
var status = myObject.StatusEnum; // Valid after read/polling
255+
~~~
256+
257+
#### Polling/Cyclic Scenario
258+
259+
When using a read loop that executes periodically (polling):
260+
261+
~~~C#
262+
// Polling loop
263+
while (isRunning)
264+
{
265+
myObject.Read(); // Reads all properties including Status
266+
267+
// Now all typed accessors have valid data
268+
var status = myObject.StatusEnum;
269+
var color = (Color)status;
270+
271+
await Task.Delay(100); // Update interval
272+
}
273+
~~~
274+
275+
### EnumeratorDiscriminatorAttribute
276+
277+
All properties with typed enum accessors are marked with `[EnumeratorDiscriminatorAttribute]`. This attribute identifies the corresponding enum type and is used internally by the framework for serialization and UI support.
278+
279+
You can also see this marking on POCO (Plain Old CLR Object) versions of your types, where it helps maintain enum type information:
280+
281+
~~~C#
282+
[AXSharp.Connector.EnumeratorDiscriminatorAttribute(typeof(Color))]
283+
public Color Status { get; set; }
284+
~~~

docfx/articles/compiler/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Entry.Plc.weather.GeoLocation.Write();
8080
~~~
8181

8282
- [Attributes](ATTRIBUTES.md)
83-
- [Added members](ADDED_MEMBERS.md)
83+
- [Added members](ADDED_MEMBERS.md) - including typed enum accessor properties
8484
- [Config file](CONFIG_FILE.md)
8585
- [Packaging and dependency management](PACKAGING.md)
8686

src/AXSharp.blazor/src/AXSharp.Presentation.Blazor.Controls/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/AXSharp.compiler/src/AXSharp.Cs.Compiler/Helpers/CsHelpers.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,29 @@
77

88
using System.Text;
99
using AX.ST.Semantic.Model.Declarations;
10+
using AX.ST.Semantic.Model.Declarations.Types;
1011
using AX.ST.Syntax.Tree;
1112
using AXSharp.Connector;
1213

1314
namespace AXSharp.Compiler.Cs.Helpers;
1415

1516
internal static class CsHelpers
1617
{
18+
private static readonly HashSet<string> IntegralIecTypes = new(StringComparer.OrdinalIgnoreCase)
19+
{
20+
"BYTE", "USINT", "SINT", "INT", "UINT", "WORD",
21+
"DINT", "DWORD", "UDINT", "LINT", "LWORD", "ULINT"
22+
};
23+
24+
/// <summary>
25+
/// Determines whether the backing type of a named value type is integral
26+
/// and thus valid as a C# enum base type.
27+
/// </summary>
28+
public static bool HasIntegralBackingType(this INamedValueTypeDeclaration namedValueType)
29+
{
30+
return IntegralIecTypes.Contains(namedValueType.ValueTypeAccess.Type.Name.Trim());
31+
}
32+
1733
public static string Transform(this IAccessModifierSyntax syntax)
1834
{
1935
return $"{syntax.ModifierKeyword.Text.ToLower()} ";

src/AXSharp.compiler/src/AXSharp.Cs.Compiler/Onliner/CsOnlinerMemberBuilder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public void CreateFieldDeclaration(IFieldDeclaration fieldDeclaration, IxNodeVis
6565
AddToSource("OnlinerInt");
6666
AddToSource($" {fieldDeclaration.Name}");
6767
AddToSource("{get;}");
68+
AddToSource($"{fieldDeclaration.AccessModifier.Transform()} {@enum.GetQualifiedName()} {fieldDeclaration.Name}Enum {{ get => ({@enum.GetQualifiedName()}){fieldDeclaration.Name}.LastValue; }}");
6869
break;
6970
case INamedValueTypeDeclaration namedValue:
7071
AddToSource(
@@ -74,6 +75,8 @@ public void CreateFieldDeclaration(IFieldDeclaration fieldDeclaration, IxNodeVis
7475
//fieldDeclaration.Type.Accept(visitor, this);
7576
AddToSource($" {fieldDeclaration.Name}");
7677
AddToSource("{get;}");
78+
if (namedValue.HasIntegralBackingType())
79+
AddToSource($"{fieldDeclaration.AccessModifier.Transform()} {namedValue.GetQualifiedName()} {fieldDeclaration.Name}Enum {{ get => ({namedValue.GetQualifiedName()}){fieldDeclaration.Name}.LastValue; }}");
7780
break;
7881
case IArrayTypeDeclaration array:
7982
var arrayEligibility = array.IsEligibleForTranspile(SourceBuilder, warnMissingOrInconsistent: true);
@@ -159,6 +162,7 @@ public void CreateVariableDeclaration(IVariableDeclaration semantics, IxNodeVisi
159162
AddToSource("OnlinerInt");
160163
AddToSource($" {semantics.Name}");
161164
AddToSource("{get;}");
165+
AddToSource($"public {@enum.GetQualifiedName()} {semantics.Name}Enum {{ get => ({@enum.GetQualifiedName()}){semantics.Name}.LastValue; }}");
162166
break;
163167
case INamedValueTypeDeclaration namedValue:
164168
AddToSource(
@@ -168,6 +172,8 @@ public void CreateVariableDeclaration(IVariableDeclaration semantics, IxNodeVisi
168172
//semantics.Type.Accept(visitor, this);
169173
AddToSource($" {semantics.Name}");
170174
AddToSource("{get;}");
175+
if (namedValue.HasIntegralBackingType())
176+
AddToSource($"public {namedValue.GetQualifiedName()} {semantics.Name}Enum {{ get => ({namedValue.GetQualifiedName()}){semantics.Name}.LastValue; }}");
171177
break;
172178
case IArrayTypeDeclaration array:
173179
var arrayEligible = array.IsEligibleForTranspile(SourceBuilder, warnMissingOrInconsistent: true);

src/AXSharp.compiler/src/AXSharp.Cs.Compiler/Plain/CsPlainSourceBuilder.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// AXSharp.Compiler.Cs
1+
// AXSharp.Compiler.Cs
22
// Copyright (c) 2023 MTS spol. s r.o., and Contributors. All Rights Reserved.
33
// Contributors: https://github.com/inxton/axsharp/graphs/contributors
44
// See the LICENSE file in the repository root for more information.
@@ -145,9 +145,14 @@ public void CreateFieldDeclaration(IFieldDeclaration fieldDeclaration, IxNodeVis
145145
AddPropertyDeclaration(fieldDeclaration, fieldDeclaration, visitor);
146146
AddToSource(" = string.Empty;");
147147
break;
148-
case INamedValueTypeDeclaration namedValueType:
148+
case IEnumTypeDeclaration @enum:
149+
AddToSource($"[AXSharp.Connector.EnumeratorDiscriminatorAttribute(typeof(global::{@enum.GetQualifiedName()}))]");
149150
AddPropertyDeclaration(fieldDeclaration, fieldDeclaration, visitor);
150151
break;
152+
case INamedValueTypeDeclaration namedValueType:
153+
AddToSource($"[AXSharp.Connector.EnumeratorDiscriminatorAttribute(typeof(global::{namedValueType.GetQualifiedName()}))]");
154+
AddPropertyDeclaration(fieldDeclaration, fieldDeclaration, visitor);
155+
break;
151156
case IScalarTypeDeclaration scalar:
152157
AddPropertyDeclaration(fieldDeclaration, fieldDeclaration, visitor);
153158
AddToSource(scalar.CreateScalarInitializer(this.Project?.CompilerOptions?.TargetPlatfromMoniker));
@@ -312,7 +317,12 @@ public void CreateVariableDeclaration(IVariableDeclaration fieldDeclaration, IxN
312317
AddPropertyDeclaration(fieldDeclaration, fieldDeclaration, visitor);
313318
AddToSource(" = string.Empty;");
314319
break;
320+
case IEnumTypeDeclaration @enum:
321+
AddToSource($"[AXSharp.Connector.EnumeratorDiscriminatorAttribute(typeof({@enum.GetQualifiedName()}))]");
322+
AddPropertyDeclaration(fieldDeclaration, fieldDeclaration, visitor);
323+
break;
315324
case INamedValueTypeDeclaration namedValueType:
325+
AddToSource($"[AXSharp.Connector.EnumeratorDiscriminatorAttribute(typeof(global::{namedValueType.GetQualifiedName()}))]");
316326
AddPropertyDeclaration(fieldDeclaration, fieldDeclaration.Type, visitor);
317327
break;
318328
case IScalarTypeDeclaration scalar:
@@ -439,4 +449,4 @@ private static string ShortedQualifiedIfPossible(IDeclaration semantics)
439449
? semantics.Type.FullyQualifiedName.Remove(0, semantics.ContainingNamespace.FullyQualifiedName.Length + 1)
440450
: semantics.Type.FullyQualifiedName;
441451
}
442-
}
452+
}

src/AXSharp.compiler/src/AXSharp.Cs.Compiler/Pragmas/PragmaExtensions.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// AXSharp.Compiler.Cs
1+
// AXSharp.Compiler.Cs
22
// Copyright (c) 2023 MTS spol. s r.o., and Contributors. All Rights Reserved.
33
// Contributors: https://github.com/inxton/axsharp/graphs/contributors
44
// See the LICENSE file in the repository root for more information.
@@ -147,13 +147,27 @@ public static string SetProperties(this ITypeDeclaration typeDeclaration)
147147
/// <returns></returns>
148148
public static string GetPropertyValue(this IDeclaration declaration, string propertyName, string memberName = "")
149149
{
150-
var propertyValue = declaration.Pragmas.FirstOrDefault(p =>
151-
p.Content.Replace(" ", string.Empty).StartsWith($"{PRAGMA_PROPERTY_SET_SIGNATURE}{propertyName}"))
152-
?.Content.Split('=');
150+
try
151+
{
152+
// This is only to check for malformed pragmas, if there is any, exception will be thrown and caught in catch block and empty string will be returned.
153+
string.Join("\r\n",
154+
declaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_SET_SIGNATURE))
155+
.Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p).Product));
153156

154-
if (propertyValue is { Length: > 0 }) return propertyValue[1].Replace("\"", string.Empty).Trim();
157+
var propertyValue = declaration.Pragmas.FirstOrDefault(p =>
158+
p.Content.Replace(" ", string.Empty).StartsWith($"{PRAGMA_PROPERTY_SET_SIGNATURE}{propertyName}"))
159+
?.Content.Split('=');
155160

156-
return memberName;
161+
if (propertyValue is { Length: > 0 }) return propertyValue[1].Replace("\"", string.Empty).Trim();
162+
163+
return memberName;
164+
}
165+
catch (MalformedPragmaException ex)
166+
{
167+
// swallow
168+
}
169+
170+
return string.Empty;
157171
}
158172

159173
/// <summary>

src/AXSharp.compiler/src/AXSharp.Cs.Compiler/Pragmas/PragmaParser/PragmaCompiler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// AXSharp.Compiler.Cs
1+
// AXSharp.Compiler.Cs
22
// Copyright (c) 2023 MTS spol. s r.o., and Contributors. All Rights Reserved.
33
// Contributors: https://github.com/inxton/axsharp/graphs/contributors
44
// See the LICENSE file in the repository root for more information.
@@ -60,7 +60,7 @@ private static VisitorProduct Compile(IPragma pragma, Parser parser)
6060
if (pragma.Location != null)
6161
{
6262
diagMessage =
63-
$"[Error]: {pragma.Location.GetLineSpan().Filename}:{pragma.Location.GetLineSpan().StartLinePosition.Line}, {pragma.Location.GetLineSpan().StartLinePosition.Character} {malformedPragmaException.Message}";
63+
$"[Error]: {pragma.Location.GetLineSpan().Filename}:{pragma.Location.GetLineSpan().StartLinePosition.Line + 1}, {pragma.Location.GetLineSpan().StartLinePosition.Character} {malformedPragmaException.Message}";
6464
}
6565

6666
Log.Logger.Error(diagMessage);

src/AXSharp.compiler/src/AXSharp.Cs.Compiler/Pragmas/PragmaParser/PragmaGrammar.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// AXSharp.Compiler.Cs
1+
// AXSharp.Compiler.Cs
22
// Copyright (c) 2023 MTS spol. s r.o., and Contributors. All Rights Reserved.
33
// Contributors: https://github.com/inxton/axsharp/graphs/contributors
44
// See the LICENSE file in the repository root for more information.
@@ -130,7 +130,7 @@ public PragmaGrammar()
130130
AddedPropertySetter.Rule =
131131
ix_set + colon + AddedPropertyIdentifier + assing + AddedPropertyInitializer;
132132

133-
Pragmas.Rule = AddedPropertyDeclaration | DeclarationAttribute | AddedPropertySetter | GenericAttribute;
133+
Pragmas.Rule = AddedPropertyDeclaration | AddedPropertySetter | DeclarationAttribute | GenericAttribute;
134134

135135
Root = Pragmas;
136136

@@ -145,4 +145,4 @@ public override void BuildAst(LanguageData language, ParseTree parseTree)
145145
var astBuilder = new AstBuilder(astContext);
146146
astBuilder.BuildAst(parseTree);
147147
}
148-
}
148+
}

0 commit comments

Comments
 (0)