RoslynはC#の構文解析やコード評価を行えるライブラリでC# 6以降のコンパイラでも使われています。Roslynを利用すると構文解析された結果を利用できるので文字列比較や正規表現と比べると解析漏れをなくせます。
RoslynのインストールはNuGetで使えますがUnityだと重複するdllがありそのままでは導入できません。それをUPMで簡単に導入する方法が分かったのでまとめてみました。
Unityでは以下のプロダクトでRoslynが使われています。
Unity2018.3以上
インストールできると以下のようにPackage Managerに追加されます。
インストールだけではDLLを参照できないのでRoslynを使いたいスクリプトフォルダにアセンブリ定義を作り、以下の設定を変更します。
Scripting API SamplesとGetting Started C# Syntax Analysisを参考にサンプルを実行してみます。それぞれcode欄に実行または解析するコード、result欄にその結果を表示しています。また実行できるUnityプロジェクトは https://github.com/shiena/UnityRoslynSample にありメニューの Tools > Roslyn Sample を選択するとウインドウが開きます。
コードを実行します。
string code = "1 + 2";
var result = CSharpScript.EvaluateAsync(code);
ジェネリクスで結果の型を指定してコードを実行します。
string code = "1 + 2";
var result = CSharpScript.EvaluateAsync<int>(code);
クラス定義したパラメータをコードに適用して実行します。
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);
コードを解析して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();
コードを解析して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);