Miniscriptはいいぞ。

## 概要

Miniscriptはいいぞという啓蒙記事と、いまいち情報がないので組み込みのメモを記載するもの。

とは言うものの自分も調査している最中ではあるので、間違いがあったらコメントとかいただけると幸いです。

### Miniscriptってなに

ビットコイン絡みのスクリプト言語・・・ではありません。（同名ではあるけど）

まずは下記ページを見てください。
https://miniscript.org/

ちなみに同ページからゲーム開発の環境も提供されております。
主にゲームやアプリケーションへの組み込みに優れている言語って感じですね。

#### 特徴

言語仕様は以下ページにまとまってます。
https://miniscript.org/files/MiniScript-QuickRef.pdf

うわ、すげえ簡易にまとまってる！
ということで覚えるのはすごい楽です。

これならスクリプト言語として取り扱う際も覚えることが少なくて楽です。

まず関数の枠組みで記述する必要がないため、最初から書き下し前提で作ることが可能になっております。
必要な人は関数を自分で作ることもできます。（後述）

+= / -= みたいな記述はできないのですが、逆に明確な記述が可能になっており、かつ改行によって処理が分かれるというPythonみたいな特徴も持ってます。

その気になれば複数ファイル使って読み込んで本格的にゲームやアプリケーションも構築できるかと思いますが、言語自体はシンプルにまとまってます。

実装例：
https://miniscript.org/MiniMicro/index.html
（ファイルハンドリングもつけたゲーム開発・実行環境になってますね）

なお、ソースコードはオープンソースになっており、C++とC#で実装されております。
つまりdllが必要ないわけですね。コレは管理が楽。

#### これで何ができるのか

自分の場合は、Unityでの組み込み言語として利用すべく実装中です。
元々製作者もUnityでの組み込みを想定してたみたいですね。

参考資料：
http://luminaryapps.com/blog/miniscript-why/index.html

### Unityへの組み込み方法

すっげえ簡易的ですが、日本語資料がホント無いので記載しときます。

参考資料：
https://miniscript.org/files/MiniScript-Integration-Guide.pdf （公式の組み込みガイド）
https://netsis.jp/unity/unity-miniscript/ （先駆者）

#### 1.interpreterクラスのインスタンス作成・初期化

C#のソースコード一式をアセット内にぶちこんで、クラスを認識させます。
その後、使いたいGameObject上でinterpreterクラスのインスタンスを初期化します。
例として、Awakeあたりに情報を書く形にします。

```C#:MiniscriptTest.cs
    //自分で書いたソースコード抜粋してます
    public Interpreter interpreter;

    void Awake() {
        interpreter = new Interpreter();
        //ホストデータの指定。全体とかもできるのかな？
        interpreter.hostData = this;

        //標準出力先の指定
        interpreter.standardOutput = (string s) => Debug.Log(s);
        interpreter.implicitOutput = (string s) => Debug.Log($"implicit {s}");
        interpreter.errorOutput = (string s) => {

        //エラーログ出力。（Unity側のエラーとして動く）
        Debug.LogError(s);

        //ここで一応止めてはいるけどRun関数で動かしてるので意味はない気がする
        interpreter.Stop();
        };
    }
```

interpreterクラスはMiniscriptで持っているクラスになります。
よって、「using Miniscript;」の記述は必要になります。

#### 2.ソースコードの読み込み

今回はテキストアセットからstringを取得、それをinterpreterに食わせてコンパイルという形にしてます。
ほぼサンプル通りで実装して動くのを確認してる感じですね。

```C#:MiniscriptTest.cs

public string loadText;
public TextAsset textAsset;

void Start(){
    loadText = textAsset.text;
    RunScript(loadText);
}

public void RunScript(string sourceCode) {
    string extraSource = "ship.reset = function(); self.x=0; self.y=0; self.rot=0;end function\n";
    interpreter.Reset(extraSource + sourceCode);
    interpreter.Compile();
    ValMap data = new ValMap();
        
    data["x"] = new ValNumber(transform.localPosition.x);
    data["y"] = new ValNumber(transform.localPosition.y);
    data["rot"] = new ValNumber(transform.localRotation.z);

    interpreter.SetGlobalValue(globalVarName, data);
}
```
ここで重要なのは、interpreterクラスで実行している処理と、そのあとのバインド処理になります。
interpreterクラスのReset関数にてソースコードを読み込み、Compile関数にて処理を利用可能な状態にしている、という形です。

で、その後に「ValMap」というクラスを初期化してますが、これがMiniscript側で管理できる変数を保管するクラスみたいなんですね。
ここに参照先を指定して、言ってしまうと変数をバインドする形になります。

余談ですがグローバル変数みたいな形でも管理が出来る模様（SetGlobalValueってメソッドで行うみたいですね）

#### 3.ソースコードの実行と変数の反映

```C#:MiniscriptTest.cs
    void Update(){
        try{
            //interpreter.Restart();
            if( !interpreter.Running())
                interpreter.Restart();
            interpreter.RunUntilDone(1.0f);

        }catch (MiniscriptException err) {
#if UNITY_EDITOR
            Debug.Log("Script error: " + err.Description());
#endif
        }

        UpdateFromScript();
    }
	
	public void UpdateFromScript() {
		ValMap data = null;
		try {
			data = interpreter.GetGlobalValue(globalVarName) as ValMap;
		} catch (UndefinedIdentifierException) {
			Debug.LogWarning(globalVarName + " not found in global context.");
		}
		if (data == null) return;
		
		Transform t = transform;
		Vector3 pos = t.localPosition;
		
		Value xval = data["x"];
		if (xval != null) pos.x = xval.FloatValue();
		Value yval = data["y"];
		if (yval != null) pos.y = yval.FloatValue();

		t.localPosition = pos;
		
		Value rotVal = data["rot"];
		if (rotVal != null) t.localRotation = Quaternion.Euler(0, 0, (float)rotVal.FloatValue());
	}
```

ここでやってることは、スクリプトの実行と、Valueクラスからデータを参照し、値をバインドした変数に代入しているってことです。

スクリプトの実行は、RunUntilDoneメソッドで行います。
これは、指定秒数までを許容し、それ以上だと途中で打ち切るみたいです。（オプションで指定秒数まで待つことも可能）
自分は処理してもらうのが重要なので秒数の枠を結構大きくしてます。
あと自分の場合は毎フレームで処理してもらうのが重要なので、常時Running()状態にしてます。
（つまりコンパイルして動かす準備ができたってことみたいですね。Running()がFalseだと動かないので、Restart()メソッドを実行する必要がある）

ValueクラスはMiniscriptで持っているクラス名、このクラスからFloatValue()メソッド等の変数として取得出来るメソッドに変換して、元の値に格納していく形になります。

### おまけ：組み込み関数の作成

おまけと言いつつ、これが簡単にできるからこそMiniscriptがすごいなあと思うわけです。
組み込み関数とは読んで字の如くなんですが、スクリプトを動かしている親プログラム（今回はUnity）で処理を行ってスクリプト側に値を返すことができる関数のことです。

```C#:MiniscriptTest.cs
    public Intrinsic f;

    //新関数定義
    f = Intrinsic.Create("my_abs");
    f.AddParam("x", 0);
    f.code = (context, partialResult) => {
    return new Intrinsic.Result(
        context.GetVar("x").FloatValue()
        *context.GetVar("x").FloatValue()
    );
```
今回実装しているのは値を二乗する組み込み関数ですが・・・
・・・えっと、こんだけです。
こんだけで組み込み関数が作成できちゃうわけですね。
上記のinterpreterクラスとの連携すら必要ないです。

Intrinsicクラスでは組み込み関数の情報を保持しているらしく、多分Miniscriptのソースコード側でinterpreterと連携しているんじゃないかなあ、と思います。

AddParamメソッドで利用する引数を指定します。
で、codeプロパティにdelegateの仕組みを利用してコードを追加するわけですね。
スクリプト内から値を取得する場合、
戻り値はIntrinsic.Resultに値をぶちこめば問題はないです。
ちなみに戻り値をIntrinsic.Result.Nullにすると戻り値なしで処理できます。（親プログラム側だけで処理したい場合に使う）

#### 20200901追記

上記の組み込み関数では「ソースコードを定義したクラスでのみ」動作します。
これはどういうことかというと、以下の通り。

* 処理対象（どのクラスを処理しているときにインタプリタを実行するか）を定義可能
* 処理対象を取得しない場合、定義しているソースコードが利用している変数を持つクラス・・・の新しくインスタンスが定義されたものになる。実質不定っぽい。
* 処理対象を指定する場合、組み込み関数内でinterpreterのプロパティである「hostData」を用いて、どのインスタンス上で動かすかを指定可能。
* 記述については公式のPDF「MiniScript-Integration-Guide.pdf」に記載がある。

つまり組み込み関数を使う際は情報の取得元を意識して使うとうまくいくという感じですかねえ。
ちなみに同じことが通常の運用でも言えて、上記のhostDataを定義しつつ動かすという形にすれば同じクラスを持つGameObjectを大量に生成した場合も問題なく動くことを確認済みです。

## まとめ

* Miniscript言語の紹介
* すげえ仕様が小さいので覚えやすいよ
* アプリケーションへの組み込み・拡張が容易だよ

ということで・・・Miniscriptはいいぞ。

※実行効率とかは載せてませんが、それはこれから調べる形ですね。
なにぶん今自分のプロジェクトで導入しているのがC#ですし、ある程度遅いのは織り込み済みなので。