编译器管道功能区

.NET编译器平台(“Roslyn”)通过提供一个API层,是一个传统编译器管道镜像,向你这样的消费者揭示了C#和Visual Basic编译器的代码分析。

这条管道的每一部分,现在都是单独的组件。首先,在解析阶段,其中原始码被记号化和解析成不同语言的句法。第二,声明阶段,即从源代码和输入的metadata进行分析,以形成命名符号。下一个阶段,原始码中的标示符(identifier)被匹配成符号(symbol)。最后发布(emit)阶段,所有编译器构建的信息作为一个程序集被发布。

对应每一个阶段都会有一个对象模型,它允许在该阶段访问相关信息。解析阶段表现为句法树(syntax tree),声明阶段则是分层语法表(hierarchical symbol table),绑定阶段作为一个模型,用以展现编译器进行语义分析后的结果,发布阶段则作为API以产生IL字节码。

每个编译器将这些组件组合在一起,作为一个单一的端到端的(end-to-end)整体。

为了保证公开的编译器 API 足以创建世界一流的 IDE 功能,下一代 Visual Studio 将会使用这些增强 C#/VB 体验的语言服务来重建。举个例子,通过句法树来实现代码大纲和格式化功能、通过符号表实现对象浏览器和导航功能、通过语义模型实现重构和“转到定义”,以及使用上述所有模型(包括 emit API) 实现的“编辑”和“Continue” 功能。通过  “Rosyln” 最终用户体验版,这些体验可以在 Visual Studio 2013中感受到。该体验版是为了构建并测试基于.NET编译器平台( “Roslyn”) SDK 开发的应用,并将应用集成到 Visual Studio 中。你也可以用.NET编译器平台( “Roslyn”) API 创建独立于 Visual Studio 的应用,此类应用无需安装最终用户体验版。

API 层

.NET 编译器平台(“Roslyn”)由两个主要的API层组成,分别是编译器API和工作区API。

编译器API(Compiler APIs)

编译器层包含的对象模型与编译器管道每一部分的公开信息相对应,包括语法和语义两部分。编译器层还包含了对编译器单独调用的固定快照,其中包括程序集引用、编译器选项以及源代码文件。针对C#和Visual Basic语言有两种不同的API。两种API大小差不多,但是对每种语言又进行了高度的定制。该层不依赖于Visual Studio组件。

诊断 API

作为分析结果的一部分,编译器会产生一组诊断信息,涵盖了从句法、语义、定义赋值的错误到各种警告和诊断信息。编译器 API 层提供一些可扩展的 API 来公开诊断信息,并允许在编译过程中插入自定义的分析器,也可以象 StyleCop 或 FxCop 那样在编译器预定义信息之外生成自定义的诊断信息。以这种方式来生成诊断有个好处,即可以很方便的集成 MSBuild 或 Visual Studio 这些工具,这些工具依赖于诊断信息,以用于体验如基于策略停止生成、在编辑器中显示波浪线并提示代码修复。

脚本 API

作为编译器层的一部分,该团队还提供了 宿主(Hosting)/脚本 API 原型以执行代码片段和累积运行时下上文。  REPL 使用这些 API,不过到目前为止无论是 REPL 还是脚本 API 都不是 .NET 编译器平台项目的一部分。在重新引入这些组件前团队还需要审查这些设计。

工作区 API

工作区层包含工作区 API,是做代码分析和重构整个解决方案的起点。它协助你将解决方案中的项目信息组织成单一的对象模型, 你可以直接访问编译器层的对象模型,而无需解析文件、配置项或管理项目间的依赖关系。

此外,工作区层还提供了一组 API 可用于在如 Visual Studio IDE 宿主环境中实现代码分析与重构工具,包括:查找所有引用、代码格式化、代码生成API等等。

该层不依赖于 Visual Studio 组件。

句法方面(Working with Syntax)

编译器API所展示的最基本得数据结构是句法树。这些树展示了源代码的词汇和语法结构。它们有两个重要得目的:

1、允许工具—比如IDE、插件、代码分析工具以及重构—去看和处理用户项目源代码中的语法结构。

2、确保工具—比如重构和IDE—可以以一种自然得方式创建、更改和重排源代码,而不需要直接使用文本编辑器。通过创建和操作树,工具可以简单的创建和重排源代码。

句法树(Syntax Trees)

句法树是用于合辑、代码分析、绑定、重构、IDE特性以及代码生成得主要结构。如果没有被识别和归类为许多知名结构语言元素的其中一个,那么没有任何源代码可以被理解。

句法树有三个关键属性。第一个属性是,句法树保存了完整的源信息。意味着句法树含有源文档中得每一条信息、每一个语法结构、每一个词汇记号,以及工作区、注释和预处理指令中的所有。例如,源中准确展示的每一条文字信息就像是输入进行去的一样。当程序未完成或有异常时,通过在句法树中展示跳过和丢失令牌,句法树可以展示源代码中的错误。

这一特点让语法树的第二个属性成为可能。从解析器得到的语法树与被解析的文本之间是完全可相互转换的。从任何一个语法树节点,都可以得到该节点子树的文本表示。这意味着,语法树可以用以构造和编辑源文本。创建树等于隐式创建等效文本,而编辑语法树,根据已存在树的变化做出一个新树,你才算是有效的编辑了文本。

语法树的第三个属性是:语法树是不变的且线程安全的。这意味着所获得的语法树是当前 代码状态的一个快照,且永远不会被改变。这允许多个用户在需要加锁或复制的情况下,以不同的线程在同一时间与同一棵语法树进行交互。因为树是固定不变的并且无法直接修改,通过创建额外的快照,工厂方法可以创建和更改语法树。通过重用底层的节点,这些树将十分高效,因此可以快速重建新版本且只需很少的额外内存。

语法树是名副其实的树形结构,其中非终止元素是其他元素的父元素。每一个语法树都是由节点、令牌和杂项构成。

句法节点(Syntax Nodes)

句法节点是句法树的主要元素。这些节点呈现了如声明、语句、子句和表达式。每一类句法节点都是通过继承自SyntaxNode的类来表示的。节点类集是不可扩展的。

句法树中所有的语法节点都是非终止节点,意思是它们可以一直有其他节点作为子节点。作为其他节点的子节点,每一个子节点都可以通过Parent属性获取父节点。因为节点和树是固定不变的,因此节点的父节点从来不会变。树的根节点的父节点是null。

每一个节点都有一个CHildNodes的方法,改方法返回一个源文档中基于自身位置的子节点序列。这个列表不包括任何令牌。每一个节点都有一个Descendant*的方法集合,比如DescendantNodes、DescendantTokens或DescendantTrivia,用于呈现所有该节点所在子树根的节点、令牌或杂项(trivia)的列表。

另外,通过强类型属性每一个语法节点子类可显示所有相同得子节点。例如,一个 BinaryExpressionSyntax节点类有三个标示二进制操作的额外属性:Left、OperatorToken和Right。Left和Right是ExpressionSyntax,OperatorToken类型是SyntaxToken。

一些语法节点有可选子节点。例如,IfStatementSyntax有一个可选的ElseClauseSyntax。如果没有子节点,该属性返回null。

句法令牌(Syntax Tokens)

句法令牌是语言语法的终端,是代码的最小语法单位。它们从来都不是其他节点或令牌的父辈。句法令牌由关键词、标示符、文本和标点符号组成。

出于效率的目的,SyntaxToken类型是CLR值类型。但是,不像句法节点,对于混合了属性(依赖于所要表示令牌的种类)的所有令牌只有一种结构。

例如,一个整型文本令牌表示一个数字值。此外,对于令牌所指的原始源文本,文本令牌有一个Value属性用来告诉你怎么准确解码整型值。该属性被记为对象类型,因为它可能是许多原始类型中得一种。

ValueText属性和Value属性一样,是告诉你同样的信息。但是这个属性被定义为String类型。在C#源文本中的一个标示符可能包含Unicode转义字符,但是转义序列句法本身不是标示符名称的构成部分。所以虽然令牌指向的原始文本包含有转义序列,但是ValueText属性却不是。相反,它包含被转义的Unicode字符标示符。

句法杂项(Syntax Trivia)

句法杂项是用来表示源文本中那些大量的对于理解代码来说是微不足道部分,比如空白字符、注释和预处理指令。

因为杂项并不是普通语言语法的一部分,而且可能出现在任何两个令牌之间,它们也不作为节点的孩子以包含在语法树中。然而,当实现像重构这种特性以及为了完全忠于原文时它们又很重要,它们又作为语法树的一部分存在。

你可以通过访问一个令牌的前导杂项(LeadingTrivia)或紧随杂项(TrailingTrivia)集合来访问杂项。当源文本被解析后,杂项序列将与令牌关联起来。通常,一个令牌拥有同一行上自身之后下一令牌之前的任何杂项。该行之后的任何杂项都与下一令牌关联。源文件的第一个令牌取得所有初始杂项,并且文件中最后的杂项序列被附加到文件结束令牌,否则宽度为零。

与句法节点和令牌不同,句法杂项没有父节点。不过,因为它们是句法树的一部分且每一个都与令牌关联,你可以通过 Token 属性来访问所关联的令牌。

与句法令牌一样,杂项是值类型。单个SyntaxTrivia被用来描述各种各样的杂项。

区块

每个节点、令牌或者是杂项都能找到其在源文本中的位置和所包含的字符数。文本位置用 32 位整数来表示,它是以零为下标的 Unicode 字符索引。一个TextSpan对象是由开始位置和包含的字符数组成,两者都是整数形式。如果TextSpan长度为0,它则指向两个字符中间的位置。

每个节点有两个 TextSpan 类型的属性: Span 和 FullSpan。

Span 属性指的是从该节点的子树中第一个令牌开始到最后一个令牌结束的文本区块。这个区块不包含任何前导或紧随的杂项。

FullSpan 属性则包含了该节点的正常区块,再加上任何前导或紧随的杂项。

例如:

 if (x > 3)
      {
||        // this is bad
          |throw new Exception("Not right.");|  // better exception?||
      }

上面代码块中用单个垂直竖线(|)括起来的是声明节点的span。它是“throw new Exception("Not right.");”。完整的区块是被双垂直线(||)括起来的部分。它包括与span同样的字符以及与其相关的前导和紧随杂项。

种类 (Kinds )

节点、令牌或杂项都有个类型为 System.Int32的RawKind 属性,用来标识它们所表示的确切句法元素。这个值可转换为特定语言的枚举类型; C# 或 VB 语言都有个 SyntaxKind 枚举类型,列出了语法中所有可能的节点、令牌和杂项元素。通过调用CSharpSyntaxKind 或 VisualBasicSyntaxKind 扩展方法可以自动完成转换。

RawKind属性让共享相同节点类的句法节点更容易被区分开。对于令牌和杂项来说,该属性是区分一种元素与另一种元素的唯一途径。

例如, BinaryExpressionSynta 类有 Left、 OperatorToken 和 Right 三个子类。而 Kind 属性可以区分出它是 AddExpression、SubtractExpression 或者 MultiplyExpression 中的哪种句法节点。

错误(Errors)

甚至当源文本含有句法错误时,都能表明完整的句法树是可往返于源的。当解析器遇到无法确定该语言定义的句法代码时,它将使用两种技术中的一种来创建句法树。

第一种,假如解析器需要一种特殊标记,但是却找不到时,它将在句法树该特殊标记应该存在的地方插入一个丢失标记。丢失标记描述需要的实际标记,但是它是一个空区,并且它的IsMissing属性将返回真。

第二种,解析器可能跳过标记直到它找到了能够让它继续解析一个标记。这种情况下,被跳过的标记将附加上一个有SkippedTokens的杂项节点。

语义方面(Working with Semantics)

句法树表达的是源代码的词法和句法结构。虽然仅靠信息就足以描述源代码中的所有声明和逻辑,但不足以表示哪些东西正在被引用。

例如,许多同名的类型、字段、方法和本地变量分布在源代码的各处。虽然它们中的每个都是独一无二的,但要知道某个标示符真正指向的是哪一个就需要对语言规则的深入理解。

这些是源代码中的程序元素,而且程序也可以引用打包成程序集的编译好的类库。虽然程序集中不存在源代码,因此也就不存在句法节点或者树,但程序仍可以引用其中的元素。

在源代码的句法模型之外,语义模型封装了语言规则,让你有个简单的方法来对上面的情况作出区别。

合辑( compilation )

合辑 就是编译 C#或VB 程序所需的所有东西,包括所有引用的程序集、编译器选项及源文件。

由于这些信息保存在同一个地方,因此源代码中所包含的元素可以得到更加详细的说明。合辑用符号表示每一个声明的类型、成员或变量。它还提供多种方法,以帮助你找到相关符号,无论该符号是在源代码中声明的,还是作为元数据从程序集中导入的。

与句法树一样,合辑是不可变的。当你创建了合辑,你或者你想共享的其他人都不能改变它。不过,你可以从一个已存在的合辑中做出修改以此来创建一个新的合辑。比如,你可以创建一个 除了包含额外的源文件或程序集引用以外,其他所有的地方都和一个已存在合辑一样的新合辑,

符号(Symbols)

符号就是在源代码中声明的或者从程序集中导入的元数据的独特元素。每个命名空间、类型、方法、属性、字段、事件、参数或局部变量都可用符号表示。

在 Compilation 这个类型中有各种各样的方法和属性来帮你查找符号。比如你可以通过公用元数据名称来查找声明的类型的符号。你也可以以符号树的形式来访问整个符号表,这些符号以全局命名空间为根节点。

符号也包含编译器从源代码或者元数据中得到的附加信息,例如其他被引用的符号。每种符号都是用从ISymbol派生的接口来表示,每个接口都有自己的方法和属性来详细说明编译器收集到的信息。其中的许多属性直接引用其他符号。比如,IMethodSymbol 类的 ReturnType 属性,告诉你方法声明所引用的实际类型符号。

符号是命名空间、类型和成员在源代码和元数据之间的通用表示。例如,在源代码中声明的方法和从元数据导入的方法,均表示为有相同属性的 IMethodSymbol。

用System.Reflection API表示的符号在概念上与CLT类型系统相似,但它们是模型而不仅仅是类型,因此要更丰富些。命名空间、本地变量和标签都是符号。此外,符号表现为语言概念而非 CLR 概念。它们有很多重叠的地方,但也有很多有意义的区别。比如,在 C# 或 Visual Basic 中的迭代器方法(iterator)是单一符号。但是当迭代器方法转换为 CLR 元数据,它是一个类型和多个方法。

语义模型

语义模型 展示了单个源文件的所有语义信息。使用它你能发现如下内容:

  • 符号被引用在源中特定位置。

  • 任何类型的表达式组合。

  • 包含错误和警告的所有诊断。

  • 变量如何流入和流出源。

  • 更多猜测性问题的答案。

工作区方面(Working with a Workspace)

工作区层是对整个解决方案做代码分析和重构的起点。在该层内,工作区API将协助你组织一个解决方案中有关项目的所有信息到一个单独对象模型中,提供你直接访问如源文本、句法树、语义模型和合辑的编译器层对象模型,而不需要解析文件、配置选项或管理内部项目依赖。

宿主环境,比如IDE,提供了一个工作区让你打开解决方案。也可以简单的通过载入一个解决方案文件在IDE外部来使用该模型。

工作区(Workspace)

工作区作为项目的一个集合,是解决方案的活跃展现,每一个都是文档的集合。典型的,工作区会被绑定一个宿主环境,作为一种用户类型或操作性能是经常变化的。

工作区提供了访问解决方案的当前模型。当宿主环境发生变化时,工作区就好触发相应的事件,并且更新CurrentSolution属性。例如,当一个文本编辑器中的用户类型与源文档中的其中一个相关联时,工作区将用事件像整个解决方案的模型发送已变更信号,且告知是哪一个文档被修改。你可以从正确性、高亮内容的意义或对代码的更改提出建议来分析新模型,从而对这些变化做出反应。

你也可以创建单独的工作区,前提是断开宿主环境,或者用在一个没有宿主环境的应用中。

解决方案(Solutions),项目(Projects),文档(Documents)

虽然每按一次键都可能改变工作区,你也可以在隔离的解决方案模型中工作。

解决方案是工程和文档的固定模型。这意味着模型无需加锁或复制就可以被共享。在你从工作区的CurrentSolution属性中获取一个解决方案实例后,该实例就不会再变了。不过,像语法树和合辑,你可以在已存在的解决方案和特定修改上通过构造新实例来更改解决方案。要在工作区中看到你做的更改,你必须明确的将更改的解决方案应用到工作区。

项目是整体不变的解决方案模型的一部分。它呈现了所有的源代码文档、解析和合辑选项以及程序集和项目到项目的引用。从一个项目中,你可以访问相应的合辑而无需判断项目依赖项或解析任何源文件。

文档也是整体不变的解决方案模型的一部分。文档呈现了一个单个源文件,从中你可以访问文件、语法树和语义模型的文本。下面的图显示了与宿主环境、工具和怎样做的更改有关的工作区。

总结

.NET编译器平台(“Roslyn”)公开了一组编译器API和工作区API,它们提供了关于你的源代码的丰富信息,并且完全忠于C#和Visual Basic语言。让编译器作为平台的过渡,为集中创建代码工具和应用程序大大降低了进入门槛。它创造了许多革新,如:meta-Programming、代码生成和转换,交互使用C#和VB语言,和某些特殊领域的嵌入式C#和VB语言。

出处:https://www.oschina.net/translate/roslyn-intro?print

揭示编译器API的更多相关文章

  1. Java SE 6 新特性: 编译器 API

    新 API 功能简介 JDK 6 提供了在运行时调用编译器的 API,后面我们将假设把此 API 应用在 JSP 技术中.在传统的 JSP 技术中,服务器处理 JSP 通常需要进行下面 6 个步骤: ...

  2. Java编译器API简介

    今天给大家分享的是Java编译器API简介,文章部分内容摘自[优锐课]学习笔记. Java编译器API Java编译器API是Java模块(称为java.compiler)的一部分.该模块包括语言模型 ...

  3. Roslyn 编译器Api妙用:动态生成类并实现接口

    在上一篇文章中有讲到使用反射手写IL代码动态生成类并实现接口. 反射的妙用:C#通过反射动态生成类型继承接口并实现 有位网友推荐使用 Roslyn 去脚本化动态生成,今天这篇文章就主要讲怎么使用 Ro ...

  4. Java的脚本机制、编译器API

    学习 xxl-job 定时任务时了解到基于 JVM 的 Grovvy 脚本语言.搭建 Jenkins 时知道了编译API 1. Java 脚本机制 Java 的脚本 API 可以让我们调用 JavaS ...

  5. .NET 编译器(”Roslyn“)介绍

    介绍 一般来说,编译器是一个黑箱,源代码从一端进入,然后箱子中发生一些奇妙的变化,最后从另一端出来目标文件或程序集.编译器施展它们的魔法,它们必须对所处理的代码进行深入的理解,不过相关知识不是每个人都 ...

  6. Roslyn介绍

    介绍 一般来说,编译器是一个黑箱,源代码从一端进入,然后箱子中发生一些奇妙的变化,最后从另一端出来目标文件或程序集.编译器施展它们的魔法,它们必须对所处理的代码进行深入的理解,不过相关知识不是每个人都 ...

  7. Atitit.java eval功能的实现  Compiler API

    Atitit.java eval功能的实现  Compiler API 输出echo2 输出目录配置2 针对编译器,JDK 设计了两个接口,分别是 JavaCompiler 和JavaCompiler ...

  8. .NET程序的性能要领和优化建议

    前几天在老赵的博客上看到,Bill Chiles (Roslyn 编译器的Program Manager)写了一篇文章叫做<Essential Performance Facts and .NE ...

  9. (转载)JAVA动态编译--字节代码的操纵

    在一般的Java应用开发过程中,开发人员使用Java的方式比较简单.打开惯用的IDE,编写Java源代码,再利用IDE提供的功能直接运行Java 程序就可以了.这种开发模式背后的过程是:开发人员编写的 ...

随机推荐

  1. DAY 5 上午

    或者跑一个dp dp[i]表示总花费不超过i的情况下的最短路 dij套dp o(nk)个点 对于每一个点u,建立k+1个点表示到点u花费费用为i 比如u-->v长度为c u,0-->v,c ...

  2. Java面试中hashCode()与equals(Object obj)方法关系的准确回答

    原文地址: https://blog.csdn.net/qq_19260029/article/details/77917925 hashCode()与equals(Object obj)都是Java ...

  3. 测开之路九十九:js函数、事件、window窗体对象

    函数:function 函数名(参数列表) 事件 单击:onclick()表单提交:onsubmit()鼠标经过:onmouseover()值改表时:onchange() window窗体对象转跳:w ...

  4. 【Unity Shader】---准确认识SubShader语义块结构、渲染状态设定、Tags标签

    一[SubShader] 每个UnityShader文件可以包含多个SubShader语义块,但至少要有一个.当Unity需要加载这个UnityShader时,Unity会扫描所有的SubShader ...

  5. mooc-IDEA live template--006

    十二.IntelliJ IDEA -live template 以定时器为例: 1.创建一个Template Group... 2.在创建的Template Group下面,创建一个Live Temp ...

  6. vuer-cli 安装笔记

    电脑上装 的软件全卸载了.需要 重装 .整理了一下vue-cli脚手架搭建 1 先下载git 2 再下载node 3安装淘宝镜像 (https://npm.taobao.org/) 4 安装webpa ...

  7. 今天起,重新开头学习Java - 一、安装环境

    先拜领路人 https://blog.csdn.net/u011541946/article/category/6951961/3? 一.安装JDK 1. 下载 www.java.com JDK是Ja ...

  8. Console.Out 属性和 XmlDocument.Save 方法 (String)

    Console.Out 属性 默认情况下,此属性设置为标准输出流. 此属性可以设置为另一个流SetOut方法. 请注意,调用Console.Out.WriteLine方法是等效于调用相应WriteLi ...

  9. The second curriculum design experiment report in spring 2019

    2019年第二次课程设计实验报告 一.实验项目名称 贪吃蛇 二.实验项目功能描述 1.小蛇的移动 玩家可以通过 W A S D控制小蛇的上左下右移动,通过函数改变小蛇部位的位置 2.判断游戏失败 当小 ...

  10. java 获取某路径下的子文件/子路径

    /** * 获取某路径下的子文件 * */ public static List<String> getSubFile(String path){ List<String> s ...