Roslyn入門(一)-C#語法分析

演示環境

Visual Studio 2017html

.NET Compiler Platform SDKnode

簡介

今天,Visual Basic和C#編譯器是黑盒子:輸入文本而後輸出字節,編譯管道的中間階段沒有透明性。使用.NET編譯器平臺(之前稱爲「Roslyn」),工具和開發人員能夠利用編譯器使用的徹底相同的數據結構和算法來分析和理解代碼。 本篇文章,咱們將會慢慢熟悉語法API,經過語法API來查看解析器,語法樹,用於推理和構造它們的實用程序。git

理解語法樹

Trivia,Token和Node造成了一個徹底表明Visual Basic或C#代碼片斷中全部內容的樹github

SyntaxTree

它的實例表示整個解析樹。SyntaxTree是一個抽象類,具備特定於語言的派生類。要解析特定語言的語法,您須要使用CSharpSyntaxTree(或VisualBasicSyntaxTree)類上的解析方法。算法

SyntaxNode

它的實例表示的語法結構如聲明,語句,子句和表達式。api

SyntaxToken

它表明一個單獨的關鍵字,識別符,操做員或標點符號數據結構

SyntaxTrivia

它表示語法上可有可無的信息,例如令牌之間的空白,預處理指令和註釋。數據結構和算法

下圖示例:SyntaxNode: 藍色 | SyntaxToken: 綠色 | SyntaxTrivia: 紅色ide

語法解析樹

遍歷語法樹

  • 新建項目「CodeAnalysisDemo」
  • 引入Nuget
Microsoft.CodeAnalysis.CSharp
 
  Microsoft.CodeAnalysis.CSharp.Workspaces
  • 命名空間:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
  • 準備要分析的代碼
using System;

namespace UsingCollectorCS
{
   class Program
   {
       static void Main(string[] args)
       {
           Console.WriteLine("Hello World");
       }
   }

   class Student
   {
       public string Name { get; set; }
   }
}
  • 核心代碼
/// <summary>
       ///解析語法樹 /// </summary> /// <param name="code"></param> /// <returns></returns> public SyntaxNode GetRoot(string code) { var tree = CSharpSyntaxTree.ParseText(code); //SyntaxTree的根root var root = (CompilationUnitSyntax)tree.GetRoot(); //member var firstmember = root.Members[0]; //命名空間Namespace var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstmember; //類 class var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0]; //方法 Method var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0]; //參數 Parameter var argsParameter = mainDeclaration.ParameterList.Parameters[0]; //查詢方法,查詢方法名稱爲Main的第一個參數。 var firstParameters = from methodDeclaration in root.DescendantNodes() .OfType<MethodDeclarationSyntax>() where methodDeclaration.Identifier.ValueText == "Main" select methodDeclaration.ParameterList.Parameters.First(); var argsParameter2 = firstParameters.Single(); return root; } 
  • 入口Main方法
var code = @"using System;

                       namespace UsingCollectorCS
                       {
                           class Program
                           {
                               static void Main(string[] args)
                               {
                                   Console.WriteLine(""Hello World"");
                               }
                           }

                           class Student
                           {
                               public string Name { get; set; }
                           }
                       }";

           var tree = new AnalysisDemo().GetRoot(code);
  • Debug調試

通過對比可知如下部分工具

利用CSharpSyntaxTree.ParseText(code)獲取語法樹SyntaxTree

利用(CompilationUnitSyntax)tree.GetRoot()獲取語法樹的跟節點

利用 (NamespaceDeclarationSyntax)root.Members[0]可獲取命名空間

利用 (ClassDeclarationSyntax)helloWorldDeclaration.Members[0]可獲取類

利用 (MethodDeclarationSyntax)programDeclaration.Members[0]可獲取方法

利用linq查詢,可從**root.DescendantNodes()**節點內查詢方法/參數等成員。

SyntaxWalkers

一般,您須要在語法樹中查找特定類型的全部節點,例如,文件中的每一個屬性聲明。

經過擴展CSharpSyntaxWalker類並重寫VisitPropertyDeclaration方法,您能夠在不事先知道其結構的狀況下處理語法樹中的每一個屬性聲明。

CSharpSyntaxWalker是一種特殊的SyntaxVisitor,它以遞歸方式訪問節點及其每一個子節點。

咱們先來演示CSharpSyntaxWalker的兩個虛virtual方法VisitUsingDirective 和VisitPropertyDeclaration

  • 核心代碼以下:
/// <summary>
    /// 收集器 /// </summary> public class UsingCollector : CSharpSyntaxWalker { public readonly Dictionary<string, List<string>> models = new Dictionary<string, List<string>>(); public readonly List<UsingDirectiveSyntax> Usings = new List<UsingDirectiveSyntax>(); public override void VisitUsingDirective(UsingDirectiveSyntax node) { if (node.Name.ToString() != "System" && !node.Name.ToString().StartsWith("System.")) { this.Usings.Add(node); } } public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) { var classnode = node.Parent as ClassDeclarationSyntax; if (!models.ContainsKey(classnode.Identifier.ValueText)) { models.Add(classnode.Identifier.ValueText, new List<string>()); } models[classnode.Identifier.ValueText].Add(node.Identifier.ValueText); } } /// <summary> /// 演示CSharpSyntaxWalker /// </summary> /// <param name="code"></param> /// <returns></returns> public UsingCollector GetCollector(string code) { var tree = CSharpSyntaxTree.ParseText(code); var root = (CompilationUnitSyntax)tree.GetRoot(); var collector = new UsingCollector(); collector.Visit(root); return collector; } 
  • Main調用入口:
var code2 =
            @"using System;
                        using System.Collections.Generic;
                        using System.Linq;
                        using System.Text;
                        using Microsoft.CodeAnalysis;
                        using Microsoft.CodeAnalysis.CSharp;


            namespace TopLevel
                {
                    using Microsoft;
                    using System.ComponentModel;

                    namespace Child1
                    {
                        using Microsoft.Win32;
                        using System.Runtime.InteropServices;

                        class Foo {  
                            public string FChildA{get;set;}
                            public string FChildB{get;set;}
                        }
                    }

                    namespace Child2
                    {
                        using System.CodeDom;
                        using Microsoft.CSharp;

                        class Bar {
                             public string BChildA{get;set;}
                             public string BChildB{get;set;}
                        }
                    }
                }";

            var collector = new AnalysisDemo().GetCollector(code2);

            foreach (var directive in collector.Usings)
            {
                Console.WriteLine($"Name:{directive.Name}");
            }
            Console.WriteLine($"models:{JsonConvert.SerializeObject(collector.models)}");
  • 執行結果

咱們能夠得出結論

VisitUsingDirective 主要用於獲取Using命名空間

VisitPropertyDeclaration主要用於獲取屬性。

總結

本篇文章主要講了

  • 語法樹SyntaxTree,以及SyntaxNode,SyntaxToken,SyntaxTrivia。

  • 經過重寫CSharpSyntaxWalker的虛方法,能夠實現自定義獲取。

  • 附上官方截取的部分流程圖

Roslyn編譯管道功能區

編譯步驟

API圖層

Roslyn由兩個主要的API層組成 - 編譯器API和工做區API。

源碼

CsharpFanDemo

參考連接

Roslyn-Overview

Getting Started C# Syntax Analysis

從零開始學習 dotnet 編譯過程和 Roslyn 源碼分析

手把手教你寫 Roslyn 修改編譯

相關文章
相關標籤/搜索