1. 程式人生 > >Rosyln入門(一)-C#語法分析

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

演示環境

簡介

今天,Visual Basic和C#編譯器是黑盒子:輸入文字然後輸出位元組,編譯管道的中間階段沒有透明性。使用.NET編譯器平臺(以前稱為“Roslyn”),工具和開發人員可以利用編譯器使用的完全相同的資料結構和演算法來分析和理解程式碼。 本篇文章,我們將會慢慢熟悉語法API,通過語法API來檢視解析器,語法樹,用於推理和構造它們的實用程式。

理解語法樹

Trivia,Token和Node形成了一個完全代表Visual Basic或C#程式碼片段中所有內容的樹

SyntaxTree

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

SyntaxNode

它的例項表示的語法結構如宣告,語句,子句和表示式。

SyntaxToken

它代表一個單獨的關鍵字,識別符,操作員或標點符號

SyntaxTrivia

它表示語法上無關緊要的資訊,例如令牌之間的空白,預處理指令和註釋。

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

語法解析樹

遍歷語法樹

  • 新建專案“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。 

原始碼

參考連結