Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docfx/articles/compiler/ATTRIBUTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,50 @@ CLASS PUBLIC MyClass
END_CLASS
~~~

### Generic extension attributes

ixc allows to declare generic attributes in ST that will add a genetic notation to transpiled types.
The use of generics is an advanced scenario aimed at simplifying some tasks where templating is needed.
This feature was explicitly crafted for data exchange scenarios, and it does not support the entire range of use of generics in C#.


#### Use

Any class can be annotated with the following attribute:

~~~iecst
{#ix-generic:<TOnline, TPlain> where TOnline : ITwinObject}
CLASS PUBLIC Extender

END_CLASS
~~~

That will create the following type declaration in twin type:

~~~C#
public partial class Extender<TOnline, TPlain> : AXSharp.Connector.ITwinObject where TOnline : ITwinObject
~~~


When deriving from a class with generic annotation following additional annotation should be used for a generic member of the class:

~~~iecst
CLASS PUBLIC Extendee2 EXTENDS Extender
VAR PUBLIC
{#ix-generic:TOnline}
{#ix-generic:TPlain as POCO}
SomeType : SomeType;
END_VAR
END_CLASS
~~~

Where TOnline generic type will be substituted with `SomeType`. The `as POCO` will transpale `TPlain` generic attribute as the corresponding plain (aka POCO) type.

The previous example will transpile as follows.
~~~C#
public partial class Extendee : Generics.Extender<Generics.SomeType, Pocos.Generics.SomeType>
~~~

### See also

#### [RenderIgnore](../blazor/RENDERABLECONTENT.md#renderignore-and-custom-labels)
Original file line number Diff line number Diff line change
Expand Up @@ -56,45 +56,72 @@ public void CreateFile(IFileSyntax fileSyntax, IxNodeVisitor visitor)
}


private string ReplaceGenericSignature(IClassDeclaration? classDeclaration)
{
if (classDeclaration == null)
return string.Empty;

var generics = new List<string>();
var genericSignature = classDeclaration?.ExtendedType?.GetGenericAttributes()?.Product;
if(string.IsNullOrEmpty(genericSignature))
{
return string.Empty;
}

foreach (var genericType in classDeclaration?.ExtendedType?.GetGenericAttributes().GenericTypes)
{
var fieldDeclaresGenericType = classDeclaration.Fields
.FirstOrDefault(p => p.GetGenericAttributes().Any(a => a.GenericTypeAssignment.type == genericType));

if (fieldDeclaresGenericType != null)
{
foreach (var attribute in fieldDeclaresGenericType?.GetGenericAttributes())
{
if (attribute.GenericTypeAssignment.isPoco)
{
genericSignature = genericSignature.Replace(genericType, $"Pocos.{fieldDeclaresGenericType?.Type.FullyQualifiedName}");
}
else
{
genericSignature = genericSignature.Replace(genericType, fieldDeclaresGenericType?.Type.FullyQualifiedName);
}
}
}
}

return genericSignature;
}

/// <inheritdoc />
public void CreateClassDeclaration(IClassDeclarationSyntax classDeclarationSyntax,
IClassDeclaration classDeclaration,
IxNodeVisitor visitor)
{
classDeclarationSyntax.UsingDirectives.ToList().ForEach(p => p.Visit(visitor, this));

var generic = classDeclaration.GetGenericAttributes();

AddToSource(classDeclaration.Pragmas.AddAttributes());
AddToSource($"{classDeclaration.AccessModifier.Transform()}partial class {classDeclaration.Name}");
AddToSource($"{classDeclaration.AccessModifier.Transform()}partial class {classDeclaration.Name}{generic?.Product}");
AddToSource(":");

var isExtended = false;
var extendedType = classDeclaration.ExtendedTypeAccesses.FirstOrDefault();
if (Compilation.GetSemanticTree().Types
.Any(p => p.FullyQualifiedName == extendedType?.Type.FullyQualifiedName))
{

AddToSource($"{extendedType.Type.FullyQualifiedName}");
AddToSource($"{extendedType.Type.FullyQualifiedName}{ReplaceGenericSignature(classDeclaration)}");
isExtended = true;
}
else
{
AddToSource(typeof(ITwinObject).n()!);
}

//var isExtended = false;
//if (Compilation.GetSemanticTree().Types
// .Any(p => p.FullyQualifiedName == classDeclaration.ExtendedType?.Type.FullyQualifiedName))
//{
// AddToSource($"{classDeclarationSyntax.BaseClassName.FullyQualifiedIdentifier}");
// isExtended = true;
//}
//else
//{
// AddToSource(typeof(ITwinObject).n()!);
//}

AddToSource(classDeclarationSyntax.ImplementsList != null ? ", " : string.Empty);
classDeclarationSyntax.ImplementsList?.Visit(visitor, this);

AddToSource(generic?.GenericConstrains);

AddToSource("\n{");

AddToSource(CsOnlinerMemberBuilder.Create(visitor, classDeclaration, this).Output);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using AX.ST.Semantic.Model.Declarations;
using AX.ST.Semantic.Model.Declarations.Types;
using AX.ST.Semantic.Pragmas;
using AXSharp.Compiler.Cs.Pragmas.PragmaParser;

namespace AXSharp.Compiler.Cs;

Expand All @@ -17,9 +18,10 @@ namespace AXSharp.Compiler.Cs;
/// </summary>
public static class PragmaExtensions
{
private const string PRAGMA_ATTRIBUTE_SIGNATURE = "#ix-attr:";
private const string PRAGMA_DECLARE_PROPERTY_SIGNATURE = "#ix-prop:";
private const string PRAGMA_PROPERTY_SET_SIGNATURE = "#ix-set:";
public const string PRAGMA_ATTRIBUTE_SIGNATURE = "#ix-attr:";
public const string PRAGMA_DECLARE_PROPERTY_SIGNATURE = "#ix-prop:";
public const string PRAGMA_PROPERTY_SET_SIGNATURE = "#ix-set:";
public const string PRAGMA_PROPERTY_GENERIC_ATTRIBUTES = "#ix-generic:";

private static readonly int pragma_attribute_signature_length = PRAGMA_ATTRIBUTE_SIGNATURE.Length;
private static readonly int pragma_declare_property_signature_length = PRAGMA_DECLARE_PROPERTY_SIGNATURE.Length;
Expand All @@ -33,7 +35,8 @@ public static class PragmaExtensions
public static string AddAttributes(this IEnumerable<IPragma> pragmas)
{
return string.Join("\r\n",
pragmas.Where(p => p.Content.StartsWith(PRAGMA_ATTRIBUTE_SIGNATURE)).Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p)));
pragmas.Where(p => p.Content.StartsWith(PRAGMA_ATTRIBUTE_SIGNATURE))
.Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p).Product));
}

/// <summary>
Expand All @@ -44,7 +47,8 @@ public static string AddAttributes(this IEnumerable<IPragma> pragmas)
public static string DeclareProperties(this ITypeDeclaration typeDeclaration)
{
return string.Join("\r\n",
typeDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_DECLARE_PROPERTY_SIGNATURE)).Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, typeDeclaration)));
typeDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_DECLARE_PROPERTY_SIGNATURE))
.Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, typeDeclaration).Product));
}

/// <summary>
Expand All @@ -55,7 +59,8 @@ public static string DeclareProperties(this ITypeDeclaration typeDeclaration)
public static string DeclareProperties(this IConfigurationDeclaration configDeclaration)
{
return string.Join("\r\n",
configDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_DECLARE_PROPERTY_SIGNATURE)).Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, configDeclaration)));
configDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_DECLARE_PROPERTY_SIGNATURE))
.Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, configDeclaration).Product));
}

/// <summary>
Expand All @@ -66,7 +71,21 @@ public static string DeclareProperties(this IConfigurationDeclaration configDecl
public static string SetProperties(this IFieldDeclaration fieldDeclaration)
{
return string.Join("\r\n",
fieldDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_SET_SIGNATURE)).Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, fieldDeclaration)));
fieldDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_SET_SIGNATURE)).Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, fieldDeclaration).Product));
}

public static VisitorProduct GetGenericAttributes(this ITypeDeclaration typeDeclaration)
{
return typeDeclaration.Pragmas
.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_GENERIC_ATTRIBUTES))
.Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, typeDeclaration)).FirstOrDefault();
}

public static IEnumerable<VisitorProduct> GetGenericAttributes(this IFieldDeclaration typeDeclaration)
{
return typeDeclaration.Pragmas
.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_GENERIC_ATTRIBUTES))
.Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, typeDeclaration));
}

/// <summary>
Expand All @@ -77,7 +96,8 @@ public static string SetProperties(this IFieldDeclaration fieldDeclaration)
public static string SetProperties(this IVariableDeclaration variableDeclaration)
{
return string.Join("\r\n",
variableDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_SET_SIGNATURE)).Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, variableDeclaration)));
variableDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_SET_SIGNATURE))
.Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p, variableDeclaration).Product));
}

/// <summary>
Expand All @@ -88,7 +108,8 @@ public static string SetProperties(this IVariableDeclaration variableDeclaration
public static string SetProperties(this ITypeDeclaration typeDeclaration)
{
return string.Join("\r\n",
typeDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_SET_SIGNATURE)).Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p)));
typeDeclaration.Pragmas.Where(p => p.Content.StartsWith(PRAGMA_PROPERTY_SET_SIGNATURE))
.Select(p => Pragmas.PragmaParser.PragmaCompiler.Compile(p).Product));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ public override void AcceptVisitor(IAstVisitor visitor)
{
if (Type.ToUpperInvariant() == "STRING" || Type.ToUpperInvariant() == "WSTRING")
{
v.Product = $"private {Type} _{Identifier};" +
v.Product.Product = $"private {Type} _{Identifier};" +
$"\n{AccessQualifier} {Type} {Identifier} " +
$"{{ " +
$"get => string.IsNullOrEmpty(_{Identifier}) ? SymbolTail : this.Translate(_{Identifier}).Interpolate(this); set => _{Identifier} = value; " +
$"}}";
}
else
{
v.Product = $"{AccessQualifier} {Type} {Identifier} {{ get; set; }}";
v.Product.Product = $"{AccessQualifier} {Type} {Identifier} {{ get; set; }}";
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public override void AcceptVisitor(IAstVisitor visitor)
{

if (visitor is PragmaVisitor v)
v.Product = MemberName != null
v.Product.Product = MemberName != null
? $"{MemberName}.{PropertyName} = {InitValue};"
: $"{PropertyName} = {InitValue};";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public override void AcceptVisitor(IAstVisitor visitor)
var v = visitor as PragmaVisitor;
if (AttributeLiteral != null)
if (v != null)
v.Product = AttributeLiteral;
v.Product.Product = AttributeLiteral;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Irony.Ast;
using Irony.Interpreter.Ast;
using Irony.Parsing;

namespace AXSharp.Compiler.Cs.Pragmas.PragmaParser;

public class GenericAttributeAstNode : AstNode
{
public override void Init(AstContext context, ParseTreeNode treeNode)
{
Children = treeNode.ChildNodes.Select(p => p.AstNode as AstNode).ToList();
}

public List<AstNode?> Children { get; set; }

public override void AcceptVisitor(IAstVisitor visitor)
{
this.Children.ForEach(p =>
{
if (p != null)
{
p.AcceptVisitor(visitor);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Irony.Ast;
using Irony.Interpreter.Ast;
using Irony.Parsing;

namespace AXSharp.Compiler.Cs.Pragmas.PragmaParser;

internal class GenericDeclarationAstNode : AstNode
{
public string GenericTypeDeclaration { get; private set; }
public string? GenericConstraints { get; private set; }
public IEnumerable<string> GenericTypes { get; private set; }

public override void Init(AstContext context, ParseTreeNode treeNode)
{
GenericTypeDeclaration = string.Join(",",treeNode.ChildNodes[1].ChildNodes.Select(p => p.Token.Text));

GenericTypes = treeNode.ChildNodes[1].ChildNodes.Select(p => p.Token.Text);

if (treeNode.ChildNodes.Count >= 3)
{

foreach (var node in treeNode.ChildNodes[3].ChildNodes)
{
GenericConstraints = $"{GenericConstraints} where {node.ChildNodes[1].Token.Text} : {node.ChildNodes[3].Token.Text}";
}
}
}

public override void AcceptVisitor(IAstVisitor visitor)
{
if (visitor is PragmaVisitor v)
{
v.Product.Product = $"<{GenericTypeDeclaration}>";
v.Product.GenericConstrains = GenericConstraints;
v.Product.GenericTypes = GenericTypes;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Irony.Ast;
using Irony.Interpreter.Ast;
using Irony.Parsing;

namespace AXSharp.Compiler.Cs.Pragmas.PragmaParser;

internal class GenericTypeAssignmentAstNode : AstNode
{
public (string type, bool isPoco) GenericTypeDeclaration { get; private set; }
public string? GenericConstraints { get; private set; }
public IEnumerable<string> GenericTypes { get; private set; }

public override void Init(AstContext context, ParseTreeNode treeNode)
{
var isPoco = false;
if (treeNode.ChildNodes.Count > 1 && treeNode.ChildNodes[1].ChildNodes.Count > 1)
{
isPoco = treeNode.ChildNodes[1].ChildNodes[1]?.Token?.Text.Trim() == "POCO";
}

GenericTypeDeclaration = (treeNode.ChildNodes[0].Token.Text.Trim(), isPoco);
}

public override void AcceptVisitor(IAstVisitor visitor)
{
if (visitor is PragmaVisitor v)
{
v.Product.GenericTypeAssignment = GenericTypeDeclaration;
}
}
}
Loading