.NET Compiler Platform SDK

发布时间 2023-06-20 09:36:11作者: 摧残一生

.NET Compiler Platform SDK

.NET Compiler Platform 是什么?

通过学习该模型可以更快的了解Roslyn,或者说更快的了解c#编译器的相关知识。

编译器管道

编译器管道是什么

如上图所示,编译器管在每个阶段会进行不同的操作,这些操作我们可以理解为一个独立的组件或者模块,是一个黑盒结构。

  • Parser 分析模块

    转化为编译器能读懂的语言语法,遵循编译器的规则解析代码,这里会判断是否有编译问题。

  • Symbols 声明模块

    分析转换后的代码,将其进行拆分,将非元数据部分按照一定的规则进行重新组合,形成标识符。

  • Metadata Import 元数据导入模块

    将分析后的代码中数据部分进行保存,形成符号。

  • Binder 绑定模块

    将符号和标识符匹配起来。

  • IL Emitter 最终产物-IL代码

    通过Emitter编译为IL代码。

Compiler Api在编译过程的作用

Compiler Api在每个阶段都提供一个对象模型,让开发者可以在该阶段访问该模型来改变编译中的数据。

  • Syntax Tree API

    提供一个语法树(代码大纲和格式)

  • Symbol API

    分层符号表(对象浏览器和导航功能)

  • Binding and Flow Analysis APIs

    语义模型(重构和定义)

  • Emit API

    IL字节码的API

开发API

.NET编译器对外暴漏了多个API:编译器API,诊断API,脚本API和工作区API。

编译器API

涵盖了编译器的各个阶段,主要涉及对象模型(语法模型和语义模型),还包含最原始的程序集引用,编译器选项和源代码文件,该部分不可变。

诊断API

在编译器分析过程中,会产生一组诊断信息,包括对于语法,语义的警告或者诊断性信息。此时,用户可通过诊断API获得诊断信息,也可将用户定义的分析器插入编译过程,例如StyleCOp等。

脚本API

按照脚本语言的方式进行交互式执行代码

  • REPL(读取-评估-打印-循环)

    Read(读取用户输入)-Eval(执行输入内容)-Print(打印输出结果)-Loop(循环)

工作区API

可直接访问编译器层对象模型。

Syntax Tree(语法树)

每个语法树都由节点,标记和其他项组成,用于编译,代码分析,绑定,重构,IDE功能和代码生成的主要结构。

  • 完全保真的保存所有源信息。
  • 生成分析源的确切文本。
  • 只读且线程安全。
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

语法节点

表示了声明,语句,子句和表达式等语法结构,每个类别都派生自Microsoft.CodeAnalysis.SyntaxNode来表示,不可扩展。

语法节点为语法树的非终端节点,终端为语法标记,可通过SyntaxNode.Parent访问父节点,根的父节点为null。

语法标记

语法树的终端,表示代码自小的语法片段。包含关键字,标识符,文本和标点,都采用同一结构,但是属性不同,取决于表示的标记类型。

语法其他项

包含了空格,注释和预处理的指令,Microsoft.CodeAnalysis.SyntaxTrivia类型描述琐碎的内容。

可通过SyntaxToken.LeadingTrivia 或 SyntaxToken.TrailingTrivia 集合来访问语法的其他项。 源文件中的第一个标记可获取所有初始琐碎内容,而最后一个琐碎内容序列附加到 文件尾标记,否则其宽度为零。

无父级,每个其他项都与一个标记关联,可使用SyntaxTrivia.Token属性访问关联的标记。

范围

每个节点、标记或其他项都知道其在源文本内的位置和包含的字符数。 文本位置表示为一个 32 位整数,是一个从零开始的 char 索引。 TextSpan 对象表示开始位置和字符计数,都表示为整数。

每个节点具有两个TextSpan属性:Span和FullSpan

  • Span

    从节点子树中第一个标记的开头到最后一个标记末尾的文本范围。 此范围不包括任何其他项内容。

  • FullSpan

    文本范围包括节点的正常范围,加上语法其他项的内容。

种类

Syntax Node.RawKing属性,标识所表示的确切语法元素。此数值可强转换为枚举类型(Microsoft.CodeAnalysis.CSharp.SyntaxKing)。

RawKind属性可轻松消除共享同一节点的语法节点类型的歧义。

错误

主要是语法错误,当分析程序遇到不符合语言定义的代码时,会创建语法树:

  1. 需要特定种类的标记,未找到,会在所需标记的位置插入缺失的标记,SyntaxNode.IsMissing属性返回true。
  2. 跳过一些标记,知道发现可继续分析的标记,跳过的内容为SkippedTokensTrivia类型的其他项目的内容节点。

语法分析

查询符号

//CSharpCompilation.AddReferences 方法将引用添加到编译。
//MetadataReference.CreateFromFile 方法加载程序集作为引用。
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
var compilation = CSharpCompilation.Create("HelloWorld").AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))	.AddSyntaxTrees(tree);

查询语义模型

SemanticModel model = compilation.GetSemanticModel(tree);

绑定名称

// 使用解析树找到了"using System;"
UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;
// Use the semantic model for symbol information:
SymbolInfo nameInfo = model.GetSymbolInfo(systemName);
// Microsoft.CodeAnalysis.TypeInfo结构包括 TypeInfo.Type 属性,此属性可启用对关于文本类型的语义信息的访问。
var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;
// 使用 LINQ 查询语法生成完整查询,然后在控制台中显示所有方法名称
foreach (string name in (from method in stringTypeSymbol?.GetMembers().OfType<IMethodSymbol>()
 		where
			SymbolEqualityComparer.Default.Equals(method.ReturnType, stringTypeSymbol)
                         &&
			method.DeclaredAccessibility == Accessibility.Public select method.Name).Distinct())
{
 Console.WriteLine(name);
}

源生成器

编译用户代码时检查用户代码,动态的创建新的源文件并将这些文件添加到用户的编译中。

目前 .NET Standard 2.0 程序集只能用作源生成器

运行时反射

应用启动时对用户代码进行一定分析,并使用这些数据生成内容。

例如:ASP.NET Core在Web服务首次运行时可以使用反射来发现已定义的构造