TECH
BLOG

UnityでもRoslynで構文解析やコード評価したい

2020
7

はじめに

RoslynはC#の構文解析やコード評価を行えるライブラリでC# 6以降のコンパイラでも使われています。Roslynを利用すると構文解析された結果を利用できるので文字列比較や正規表現と比べると解析漏れをなくせます。

RoslynのインストールはNuGetで使えますがUnityだと重複するdllがありそのままでは導入できません。それをUPMで簡単に導入する方法が分かったのでまとめてみました。

Unityでは以下のプロダクトでRoslynが使われています。

環境構築

Unity2018.3以上

  • Package Managerから Add package from git URL... できるバージョンでは com.unity.code-analysis を追加する
  • Package Managerから Add package from git URL... できないバージョンでは Packages/manifest.json に "com.unity.code-analysis": "0.1.2-preview", を追加する

インストールできると以下のようにPackage Managerに追加されます。

image.png

インストールだけではDLLを参照できないのでRoslynを使いたいスクリプトフォルダにアセンブリ定義を作り、以下の設定を変更します。

  • 一般 > リファレンスをオーバーライドをチェック
  • アセンブリ参照に Microsoft.CodeAnalysis で始まるdllを追加
  • エディタ拡張で使うならプラットフォームの Editor のみをチェック、アプリ内で使うならデフォルトの 任意のプラットフォーム のままでOK
image.png

Roslynのサンプル

Scripting API SamplesGetting Started C# Syntax Analysisを参考にサンプルを実行してみます。それぞれcode欄に実行または解析するコード、result欄にその結果を表示しています。また実行できるUnityプロジェクトは https://github.com/shiena/UnityRoslynSample にありメニューの Tools > Roslyn Sample を選択するとウインドウが開きます。

Evaluate a C# expression

コードを実行します。

string code = "1 + 2";
var result = CSharpScript.EvaluateAsync(code);

image.png

Evaluate a C# expression (strongly-typed)

ジェネリクスで結果の型を指定してコードを実行します。

string code = "1 + 2";
var result = CSharpScript.EvaluateAsync<int>(code);

image.png

Parameterize a script

クラス定義したパラメータをコードに適用して実行します。

public class Globals
{
   public int X;
   public int Y;
}
string code = "X+Y";
var globals = new Globals {X = 1, Y = 2};
var result = CSharpScript.EvaluateAsync<int>(code, globals: globals);

image.png

Query Methods

コードを解析してMainメソッドの最初の引数を出力します。

string code =
           @"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
   class Program
   {
       static void Main(string[] args)
       {
           Console.WriteLine(""Hello, World!"");
       }
   }
}";
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
var root = (CompilationUnitSyntax) tree.GetRoot();
var firstMember = root.Members[0];
var helloWorldDeclaration = (NamespaceDeclarationSyntax) firstMember;
var programDeclaration = (ClassDeclarationSyntax) helloWorldDeclaration.Members[0];
var mainDeclaration = (MethodDeclarationSyntax) programDeclaration.Members[0];
var argsParameter = mainDeclaration.ParameterList.Parameters[0];
var firstParameters = from methodDeclaration in root.DescendantNodes()
               .OfType<MethodDeclarationSyntax>()
           where methodDeclaration.Identifier.ValueText == "Main"
           select methodDeclaration.ParameterList.Parameters.First();
var argsParameter2 = firstParameters.Single();

image.png

SyntaxWalkers

コードを解析してSystemまたはSystem.以外で始まるusingを出力します。

class UsingCollector : CSharpSyntaxWalker
{
   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);
       }
   }
}
string code =
           @"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 { }
   }
   namespace Child2
   {
       using System.CodeDom;
       using Microsoft.CSharp;
       class Bar { }
   }
}";
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
var root = (CompilationUnitSyntax) tree.GetRoot();
var collector = new UsingCollector();
collector.Visit(root);

image.png

参考リンク

RELATED PROJECT

No items found.