c语言怎么生成代码 (c语言源代码生成器)

我们将实现 IIncrementalGenerator ,以便它生成我们所需的代码。

语法树

IIncrementalGenerator 只有一个方法需要实现,BindablePropSG.cs应该是这样的:

namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {

            var fieldGroups = context.SyntaxProvider
                .CreateSyntaxProvider(
                    // 一个告诉我们感兴趣的代码片段的函数
                    // 对于语法树中的每一个节点,都会应用这个函数来判断语法节点是否会在Transform阶段被处理
                    predicate: IsBindableProp,
                    // Transform 是一个从 predicate 阶段接收输出的函数
                    // 它的输入是上一阶段接受的节点
                    // 在这个阶段,我们只需要提取所有必要的信息来生成代码(字段名、数据类型等)
                    transform: Transform
                )
                .Where(item => item is not (null, null))
                .Collect();
            
            // fieldGroups 是所有具有 BindableProp 属性的字段
            // Execute 是一个函数。 它将从 fieldGroups 生成代码
            context.RegisterSourceOutput(fieldGroups, Execute);
        }
    }
}

在继续之前,让我简要解释一下生成器发生了什么。

c语言编写代码的流程,c语言怎么生成代码

编译器会将源代码文件解析为语法树,然后将其传递给我们的生成器。 然后生成器分析并创建新代码。 然后,将它们返回给编译器。

c语言编写代码的流程,c语言怎么生成代码

C# 文件语法树

我使用的是 Visual Studio 2022。如果找不到 Syntax Visualizer 窗口,请查看 Visual Installer 以安装 .NET 平台编译器 SDK。

c语言编写代码的流程,c语言怎么生成代码

我希望以上面的说明能让您了解发生了什么。 让我详细展示一下是如何实现所有功能的。

// BindableProp.cs:
namespace BindableProps
{
    [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
    public sealed class BindableProp : Attribute
    {
        public int DefaultBindingMode { get; set; }

        public string ValidateValueDelegate { get; set; }

        public string PropertyChangedDelegate { get; set; }

        public string PropertyChangingDelegate { get; set; }

        public string CoerceValueDelegate { get; set; }

        public string CreateDefaultValueDelegate { get; set; }


        public BindableProp()
        {

        }
    }
}
// BindablePropsSG/Generators/BindablePropSG.cs

namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        // 寻找标有 BindableProp 属性的代码
        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            if (node is not AttributeSyntax attributeSyntax)
            {
                return false;
            }

            var name = SyntaxUtil.ExtractName(attributeSyntax?.Name);

            return name is "BindableProp" or "BindablePropAttribute";
        }
    }
}

c语言编写代码的流程,c语言怎么生成代码

语法树中的 BindableProp 属性

// Utils/SyntaxUtil.cs

namespace BindablePropsSG.Utils
{
    public class SyntaxUtil
    {
        public static string? ExtractName(NameSyntax? name)
        {
            return name switch
            {
                SimpleNameSyntax ins => ins.Identifier.Text,
                QualifiedNameSyntax qns => qns.Right.Identifier.Text,
                _ => null
            };
        }
    }
}

现在我们拿到了所有 BindableProp 语法节点。 让我们提取必要的信息。

namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            // ...
        }

        // 注意,我们返回一个元组
        // 对于那些不知道元组是什么的人,只要认为我返回一个包含 2 个项目的数组
        private (FieldDeclarationSyntax?, IFieldSymbol?) Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
        {
            // context对象包含很多东西,您可以调试和探索适合您需要的内容
            // 就我而言,FieldDeclarationSyntax 和 IFieldSymbol 足以

            // 这是从 IsBindableProp 筛选出来的语法节点
            var attributeSyntax = (AttributeSyntax)context.Node;

            // Attribute --> AttributeList --> Field
            // 通过上面的语法树图像,您将看到对应信息
            if (attributeSyntax.Parent?.Parent is not FieldDeclarationSyntax fieldSyntax)
                return (null, null);

            var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(fieldSyntax.Declaration.Variables.FirstOrDefault()!) as IFieldSymbol;

            return (fieldSyntax, fieldSymbol);
        }
    }
}

要为 BindableProperty.Create 函数生成代码。 这个需要:

  • 字段名称
  • 他的数据类型
  • 类的名称
  • 默认值
  • 可选的绑定模式和一些委托函数

对于必选的参数,FieldDeclarationSyntax 和 IFieldSymbol 就足够了。 对于可选参数,让开发人员将它们输入到 BindableProp 的属性中。

// 这是开发人员使用所有设置时的示例

public partial class TextInput : ContentView
{
    // 使用一些设置创建属性
    [BindableProp(DefaultBindingMode = ((int)BindingMode.TwoWay))]
    string text = "From every time";

    // 完整设置
    [BindableProp(
        DefaultBindingMode = ((int)BindingMode.OneWay),
        ValidateValueDelegate = nameof(ValidateValue),
        PropertyChangedDelegate = nameof(PropertyChangedDelegate),
        PropertyChangingDelegate = nameof(PropertyChangingDelegate),
        CoerceValueDelegate = nameof(CoerceValueDelegate),
        CreateDefaultValueDelegate = nameof(CreateDefaultValueDelegate)
        )]
    string placeHolder = "Always!";

    static bool ValidateValue(BindableObject bindable, object value)
    {
        return true;
    }

    static void PropertyChangedDelegate(BindableObject bindable, object oldValue, object newValue)
    {
        // Do something
    }

    static void PropertyChangingDelegate(BindableObject bindable, object oldValue, object newValue)
    {
        // Do something
    }

    static object CoerceValueDelegate(BindableObject bindable, object value)
    {
        // Do something
        return 0;
    }

    static object CreateDefaultValueDelegate(BindableObject bindable)
    {
        // Do something
        return string.Empty;
    }
}

我们收集了足够的原料。 现在,让我们把它们放在一起。

namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            // ...
        }

        private (FieldDeclarationSyntax?, IFieldSymbol?) Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
        {
            // ...
        }

        private void Execute(SourceProductionContext context, ImmutableArray<(FieldDeclarationSyntax?, IFieldSymbol?)> fieldSyntaxesAndSymbols)
        {
            // 转换后,得到一个元组

            if (fieldSyntaxesAndSymbols.IsDefaultOrEmpty)
                return;

            // 这些字段可能来自不同或相同的类
            // 我们需要按类别对它们进行分组
            var groupList = fieldSyntaxesAndSymbols.GroupBy<(FieldDeclarationSyntax, IFieldSymbol), ClassDeclarationSyntax>(
                    fieldGroup => (ClassDeclarationSyntax)fieldGroup.Item1!.Parent!
                );
            
            // 如果上面的代码让你头疼,我很抱歉
            // 几个月前,我写这个时也有同样的感觉

            // 你只需要知道
            // groupList 是map列表
            // 它的形式类似于下面的代码:
            // [ 
            //   { classDeclaration1, [tuple1, tuple2, ...] },
            //   { classDeclaration2, [tuple1, tuple2, ...] },
            //   ...
            // ]
            foreach (var group in groupList)
            {
                string sourceCode = ProcessClass(group.Key, group.ToList());
                // 全名包括命名空间和类名
                // 例如 MyMauiProject.Controls.Textlnput
                var className = SyntaxUtil.GetClassFullname(group.Key);

                // 注意,输出文件应该包含'g'字母
                // 这样您的 IDE 就可以知道这是生成的代码
                context.AddSource(#34;{className}.g.cs", sourceCode);
            }
        }
    }
}
//  BindablePropsSG 项目, Utils/SyntaxUtil.cs

namespace BindablePropsSG.Utils
{
    public class SyntaxUtil
    {
        public static string? ExtractName(NameSyntax? name)
        {
            // ...
        }

        public static string GetClassFullname(TypeDeclarationSyntax source)
        {
            var namespaces = new LinkedList<BaseNamespaceDeclarationSyntax>();
            var types = new LinkedList<TypeDeclarationSyntax>();

            for (var parent = source.Parent; parent is object; parent = parent.Parent)
            {
                if (parent is BaseNamespaceDeclarationSyntax @namespace)
                {
                    namespaces.AddFirst(@namespace);
                }
                else if (parent is TypeDeclarationSyntax type)
                {
                    types.AddFirst(type);
                }
            }

            var result = new StringBuilder();

            for (var item = namespaces.First; item is object; item = item.Next)
            {
                result.Append(item.Value.Name).Append(".");
            }

            for (var item = types.First; item is object; item = item.Next)
            {
                var type = item.Value;
                AppendName(result, type);
                result.Append(".");
            }

            AppendName(result, source);

            return result.ToString();
        }

        static void AppendName(StringBuilder builder, TypeDeclarationSyntax type)
        {
            builder.Append(type.Identifier.Text);
            var typeArguments = type.TypeParameterList?.ChildNodes()
                .Count(node => node is TypeParameterSyntax) ?? 0;
            if (typeArguments != 0)
                builder.Append(".").Append(typeArguments);
        }

        public static SyntaxNode? FindSyntaxBySymbol(SyntaxNode syntaxNode, ISymbol symbol)
        {
            var span = symbol.Locations.FirstOrDefault()!.SourceSpan;
            var syntax = syntaxNode.FindNode(span);

            return syntax;
        }
    }
}
namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            // ...
        }

        private (FieldDeclarationSyntax?, IFieldSymbol?) Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
        {
            // ...
        }

        private void Execute(SourceProductionContext context, ImmutableArray<(FieldDeclarationSyntax?, IFieldSymbol?)> fieldSyntaxesAndSymbols)
        {
            // ...
        }

        private string ProcessClass(ClassDeclarationSyntax classSyntax, List<(FieldDeclarationSyntax, IFieldSymbol)> fieldGroup)
        {
            if (classSyntax is null)
            {
                return string.Empty;
            }

            // 导入所有的包
            var usingDirectives = classSyntax.SyntaxTree.GetCompilationUnitRoot().Usings;

            // 如果类没有命名空间,我们将使用'global'
            var namespaceSyntax = classSyntax.Parent as BaseNamespaceDeclarationSyntax;
            var namespaceName = namespaceSyntax?.Name?.ToString() ?? "global";

            // 放在一起
            // 制作类的部分
            var source = new StringBuilder($@"
// <auto-generated/>
{usingDirectives}

namespace {namespaceName}
{{
    public partial class {classSyntax.Identifier}
    {{
");

            // 来到字段部分
            // 为每个字段创建属性 
            foreach (var (fieldSynTax, fieldSymbol) in fieldGroup)
            {
                ProcessField(source, classSyntax, fieldSynTax, fieldSymbol);
            }

            // 不要忘记右括号
            source.Append(@#34;
    }}
}}
");
            return source.ToString();
        }
    }
}
namespace BindablePropsSG.Generators
{
    [Generator]
    public class BindablePropSG : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
          // ...           
        }

        private bool IsBindableProp(SyntaxNode node, CancellationToken _)
        {
            // ...
        }

        private (FieldDeclarationSyntax?, IFieldSymbol?) Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
        {
            // ...
        }

        private void Execute(SourceProductionContext context, ImmutableArray<(FieldDeclarationSyntax?, IFieldSymbol?)> fieldSyntaxesAndSymbols)
        {
            // ...
        }

        private string ProcessClass(ClassDeclarationSyntax classSyntax, List<(FieldDeclarationSyntax, IFieldSymbol)> fieldGroup)
        {
            // ...
        }

        private void ProcessField(StringBuilder source, ClassDeclarationSyntax classSyntax, FieldDeclarationSyntax fieldSyntax, IFieldSymbol fieldSymbol)
        {
            var fieldName = fieldSymbol.Name;
            // C#中的命名约定,字段名是驼峰式,属性名是pascal形式
            // StringUtil.PascalCaseOf 位于 Utils/StringUtil.cs 中。 我稍后会展示它
            var propName = StringUtil.PascalCaseOf(fieldName);

            if (propName.Length == 0 || propName == fieldName)
            {
                return;
            }

            var fieldType = fieldSyntax.Declaration.Type;

            var className = classSyntax.Identifier;

            var defaultFieldValue = GetFieldDefaultValue(fieldSyntax) ?? "default";

            var attributeSyntax = GetAttributeByName(fieldSyntax, "BindableProp");

            var attributeArguments = attributeSyntax?.ArgumentList?.Arguments;

            // 得到可选参数
            var defaultBindingMode = GetAttributeParam(attributeArguments, "DefaultBindingMode") ?? "0";

            var validateValueDelegate = GetAttributeParam(attributeArguments, "ValidateValueDelegate") ?? "null";

            // 如果没有PropertyChangedDelegate
            // 我们给他一个默认的
            // 这将有助于属性与 MVVM 一起工作
            var propertyChangedDelegate = GetAttributeParam(
                attributeArguments, "PropertyChangedDelegate"
                ) ?? @#34;(bindable, oldValue, newValue) => 
                        (({className})bindable).{propName} = ({fieldType})newValue";

            var propertyChangingDelegate = GetAttributeParam(attributeArguments, "PropertyChangingDelegate") ?? "null";

            var coerceValueDelegate = GetAttributeParam(attributeArguments, "CoerceValueDelegate") ?? "null";

            var createDefaultValueDelegate = GetAttributeParam(attributeArguments, "CreateDefaultValueDelegate") ?? "null";

            // 检索所有必需和可选参数后,我们只需编写新的源代码
            source.Append($@"
        public static readonly BindableProperty {propName}Property = BindableProperty.Create(
            nameof({propName}),
            typeof({fieldType}),
            typeof({className}),
            {defaultFieldValue},
            (BindingMode){defaultBindingMode},
            {validateValueDelegate},
            {propertyChangedDelegate},
            {propertyChangingDelegate},
            {coerceValueDelegate},
            {createDefaultValueDelegate}
        );

        public {fieldType} {propName}
        {{
            get => {fieldName};
            set 
            {{ 
                OnPropertyChanging(nameof({propName}));

                {fieldName} = value;
                SetValue({className}.{propName}Property, {fieldName});

                OnPropertyChanged(nameof({propName}));
            }}
        }}
");
        }

        string? GetAttributeParam(SeparatedSyntaxList<AttributeArgumentSyntax>? attributeArguments, string paramName)
        {
            var paramSyntax = attributeArguments?.FirstOrDefault(
                attrArg => attrArg?.NameEquals?.Name.Identifier.Text == paramName
            );

            if (paramSyntax?.Expression is InvocationExpressionSyntax invocationExpressionSyntax)
            {
                return invocationExpressionSyntax.ArgumentList.Arguments.FirstOrDefault()?.ToString();
            }
            else if (paramSyntax?.Expression is LiteralExpressionSyntax literalExpressionSyntax)
            {
                return literalExpressionSyntax.Token.Value?.ToString();
            }

            return paramSyntax?.Expression.ToString();
        }

        string? GetFieldDefaultValue(FieldDeclarationSyntax fieldSyntax)
        {
            var variableDeclaration = fieldSyntax.DescendantNodesAndSelf()
                .OfType<VariableDeclarationSyntax>()
                .FirstOrDefault();
            var variableDeclarator = variableDeclaration?.Variables.FirstOrDefault();
            var initializer = variableDeclarator?.Initializer;
            return initializer?.Value?.ToString();
        }

        AttributeSyntax? GetAttributeByName(FieldDeclarationSyntax fieldSyntax, string attributeName)
        {
            var attributeSyntax = fieldSyntax.AttributeLists
                .FirstOrDefault(attrList =>
                {
                    var attr = attrList.Attributes.FirstOrDefault();
                    return attr is not null && SyntaxUtil.ExtractName(attr.Name) == attributeName;
                })
                ?.Attributes
                .FirstOrDefault();

            return attributeSyntax;
        }
    }
}
// Utils/StringUtil.cs

namespace BindablePropsSG.Utils
{
    public class StringUtil
    {
        public static string PascalCaseOf(string fieldName)
        {
            fieldName = fieldName.TrimStart('_');
            if (fieldName.Length == 0)
                return string.Empty;

            if (fieldName.Length == 1)
                return fieldName.ToUpper();

            return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
        }
    }
}

回顾

多么多的代码! 我试图以尽可能清晰的方式编写它们。 但是,可能仍然有许多您从未听说过的类和方法。 所以我来总结一下。

1.遍历抛出Syntax Tree,找到BindableProp属性节点

2.从我们找到的节点中,获取FieldDeclarationSyntax和IFieldSymbol

3.根据所属类别对它们进行分组

4.生成我们需要的代码并保存到文件中

在 MAUI 项目中使用时的样子

您可以尝试从单元测试项目运行或调试代码。