C#으로 이해하는 자료구조 전자책
Roslyn - C# 컴파일러 API 에 대하여 (1)

[제목] Roslyn - C# 컴파일러 API 에 대하여 (1)

Roslyn은 C# 6.0 컴파일러를 제공함과 더불어 컴파일러의 각 단계별 기능들을 개발자가 직접 활용할 수 있도록  Compiler API 도 제공하고 있다. 이 Compiler API를 이용하면 개발자는 컴파일러 내에서 일어나는 기능들을 활용한 다양한 응용 프로그램을 만들 수 있다. 이번 아티클에서는 C# 코드를 문자열로 받아들여 이를 파싱해서 구문 분석(Syntax Analysis)을 실행하는 API를 살펴보자.

먼저 Roslyn을 사용하기 전에, 아직 개발단계인 Roslyn을 설치하는 방법에 대해 간단히 집고 넘어가자...
로즐린 사용을 위해서는 최근(6/3/2014) 발표된 Visual Studio 14 CTP  를 사용하면 되는데 여기에는 Roslyn이 포함되어 있으므로 별도의 설치가 필요없다. 하지만, VS 14은 아직 Side by Side Installation을 지원하지 않으므로, 기존 설치된 VS 에 영향을 미칠 수 있다. 따라서, 이전 버젼의 VS 를 가지고 있는 대부분의 개발자들은 별도의 머신이나 Virtual Machine을 사용해야 한다. 이외의 다른 방식으로 Roslyn 소스를 받아 빌드하거나 Preview를 다운하거나 등등의 방법이 있는데, 가장 간단한(?) 방법은 아마 NuGet을 사용하여 Preview를 다운받는 방식인 것 같다.

NuGet을 사용방식은 다음 스텝을 따르면 된다.
1) VS 2013 실행하여 새 프로젝트 생성
2) Tools - NuGet Package Manager - Package Manage Console 메뉴 선택
3) Package Manage Console에서 Install-Package 명령 실행
     PM> Install-Package Microsoft.CodeAnalysis -Pre

설치가 완료된 후, 아래와 같이 using문을 사용하면 Compiler API를 사용할 수 있다.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;   

C# 컴파일러가 C# 코드를 읽어 들어 가장 먼저 하는 것은 코드를 토큰 단위로 쪼개 파싱하여 결과물로 Syntax Tree를 만드는 일이다. 개발자는 Roslyn의 컴파일러 API를 이용해 실제 C# 컴파일러가 만들어 내는 것과 동일한 파싱 결과를 구할 수 있다.

Compiler API에서 Syntax Tree를 표현하는 클래스로 SyntaxTree 라는 abstract class가 있다. 프로그래밍 언어마다 다른 SyntaxTree를 사용하는데, C#은 (SyntaxTree 클래스에서 파생된) CSharpSyntaxTree 클래스를 사용하고 VB.NET은 VisualBasicSyntaxTree를 사용한다.

CSharpSyntaxTree 클래스의 정적 메서드 ParseText() 혹은 ParseFile() 을 사용하면, C# 코드를 파싱하여 그 결과물인 SyntaxTree 객체를 구할 수 있다. Syntax Tree 는 코멘트, 공백 등을 포함한 소스 코드의 모든 내용을 갖고 있으므로, 이 트리로부터 소스코드를 복원하는 것도 가능하다.

SyntaxTree tree = CSharpSyntaxTree.ParseText(@"
namespace NS1
{ 
    class Class1
    { 
        public int ID { get; set; }
        public string Name { get; set; }
        public void Run(int id) { }        
    } 
}");

SyntaxTree를 구성하는 노드들은 다양한 종류가 있는데, 모두 SyntaxNode 클래스로부터 파생된 클래스들이다. (이 두개의 핵심 클래스(SyntaxTree, SyntaxNode) 이외에 키워드, 연산자, 구두법등을 나타내는 SyntaxToken 구조체와 코멘트, 공백등을 표현하는 SyntaxTrivia 구조체가 Syntax Analysis의 주요 Type이다)

위의 예에서 처럼 tree 라는 SyntaxTree 객체를 얻은 후, 트리구조를 Navigation 하면서 하위 노드들의 내용을 살펴볼 수 있다. 먼저 SyntaxTree의 루트노드는 GetRoot() 메서드를 사용하여 구한다.

// 루트노드
var root = (CompilationUnitSyntax)tree.GetRoot();

예제 C# 코드에서 루트노드 밑에 바로 namespace가 있으므로 root의 Members 컬렉션에서 namespace를 읽어 온다. 이어 namespace 밑에 class 한 개가 있으므로 Members 컬렉션에서 Class1을 읽어 온다. 여기서 NamespaceDeclarationSyntax, CSharpSyntaxNode 등은 모두 SyntaxNode 클래스로부터 파생된 노드 클래스이다.

// namespace 노드
NamespaceDeclarationSyntax nodeNS = (NamespaceDeclarationSyntax)root.Members[0];
// class 노드
CSharpSyntaxNode nodeClass = nodeNS.Members[0];

Class1 노드를 구한 후, 메서드와 속성 이름만을 출력해 보기 위해, Class 노드의 자식노드들을 ChildNodes() 메소드로 차례로 가져와서 각 노드가 속성(PropertyDeclarationSyntax) 혹은 메서드(MethodDeclarationSyntax) 노드인지를 체크하여 각각 출력한다.

// 클래스 멤버들
foreach (MemberDeclarationSyntax node in nodeClass.ChildNodes())
{    
    if (node is PropertyDeclarationSyntax) //속성
    {
        var prop = (PropertyDeclarationSyntax)node;
        Console.WriteLine("Property: {0}", prop.Identifier.ValueText);
    }
    else if (node is MethodDeclarationSyntax) // 메서드
    {
        var mth = (MethodDeclarationSyntax)node;
        Console.WriteLine("Method: {0}", mth.Identifier.ValueText);
    }                
}

위의 C# 코드는 비교적 간단한 예이지만, 만약 코드가 복잡한 경우에 트리/컬렉션에 대해 LINQ 쿼리를 사용하는 것이 효율적일 때가 있다. 즉, 만약 소스코드중 Run이라는 메서드를 찾아 이 메서드의 파라미터들과 메서드 본문 전체를 출력하기 위해서는 아래와 같이 LINQ 쿼리를 사용해서 직접 Run() 메서드를 찾아 그 내용을 출력할 수 있다.

var meth = root.DescendantNodes().OfType().Single(p => p.Identifier.ValueText == "Run");
foreach (var p in meth.ParameterList.Parameters)
{
    Console.WriteLine("{0} {1}", p.Type, p.Identifier.ValueText);
}
Console.WriteLine("Method Body:\r\n{0}", meth.Body);

이러한 Compiler API - Syntax Analysis 기능은 여러가지로 활용될 수 있는데, 몇 가지 예를 들면 소스코드들 중에 public 클래스 멤버들만 요약 정리한다던지, C# 소스코드 Beautifier 등의 유틸러티를 개발하는 경우, C# 코드를 읽어 직접 Help Document를 만드는 경우 등등에 유용하게 사용될 수 있다.

Roslyn - C# 컴파일러 API (1)
Roslyn - C# 컴파일러 API (2)
Roslyn - C# 컴파일러 API (3)



본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.