Lua Formula(MoonSharp版)

以前作った[Lua Formula(xLua版)](https://qiita.com/riafeed/items/8d043d01f6a1f4ca2611)のMoonSharp版を作ってみた。
仕様の違いからxLua版とは同じようにはいかなかった。


```C#
using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Unity.VisualScripting;
using UnityEngine;

/// <summary>
/// Luaコードの実行
/// </summary>
[UnitCategory("Lua Formula")]
[UnitTitle("Lua Formula")]
public class LuaExecute : Unit
{
    [DoNotSerialize]
    [PortLabelHidden]
    public ControlInput InputFlow { get; private set; }

    [DoNotSerialize]
    [PortLabelHidden]
    public ControlOutput OutputFlow { get; private set; }

    [Serialize]
    [Inspectable(order = int.MaxValue)]
    [InspectorExpandTooltip]
    public bool cacheArguments { get; set; }

    [DoNotSerialize]
    [Inspectable, UnitHeaderInspectable("Inputs")]
    public virtual int inputCount
    {
        get
        {
            return _inputCount;
        }
        set
        {
            _inputCount = Mathf.Clamp(value, minInputCount, maxInputCount);
        }
    }

    [DoNotSerialize]
    public ReadOnlyCollection<ValueInput> multiInputs { get; protected set; }

    [SerializeAs(nameof(inputCount))]
    private int _inputCount = 0;

    [DoNotSerialize]
    protected virtual int maxInputCount => 9;

    [DoNotSerialize]
    protected virtual int minInputCount => 0;

    [Inspectable, InspectorTextArea, UnitHeaderInspectable]
    public string LuaCode { get; private set; }

    [DoNotSerialize]
    public ValueOutput Result { get; private set; }

    [DoNotSerialize]
    private object result;

    private Script script = null;

    protected override void Definition()
    {
        InputFlow = ControlInput(nameof(InputFlow), ExecuteWait);
        OutputFlow = ControlOutput(nameof(OutputFlow));

        var mi = new List<ValueInput>();
        multiInputs = mi.AsReadOnly();

        for (var i = 0; i < inputCount; i++)
        {
            mi.Add(ValueInput<object>(((char)('a' + i)).ToString()));
        }

        foreach (var input in multiInputs)
        {
            input.AllowsNull();
        }

        Result = ValueOutput(nameof(Result), _ => result);

        Succession(InputFlow, OutputFlow);
    }

    public ControlOutput ExecuteWait(Flow flow)
    {
        if(script == null)
        {
            script = new Script();
            UserData.RegistrationPolicy = InteropRegistrationPolicy.Automatic;
        }

        for (var i = 0; i < inputCount; i++)
        {
            if (multiInputs[i].hasValidConnection)
            {
                var obj = flow.GetValue<object>(multiInputs[i]);
                script.Globals[((char)('A' + i)).ToString()] = obj;
            }
        }
        
        DynValue luaFunction = script.LoadString(LuaCode, null, "Chunk");
        DynValue ret = luaFunction.Function.Call();
        result = GetValue(ret);

        if (!cacheArguments)
        {
            for (var i = 0; i < maxInputCount; i++)
            {
                script.Globals[(((char)('A' + i)).ToString())] = null;
            }
        }

        return OutputFlow;
    }

    //DynValをC#で扱えるデータ型に変換
    public static object GetValue(DynValue val)
    {
        object result;

        if (val == null) return null;

        DataType valType = val.Type;

        switch (valType)
        {
            case DataType.Boolean:
                result = val.Boolean;
                break;

            case DataType.Number:
                if (val.Number % 1 == 0)
                {
                    //小数点以下の値がないい場合はLong型
                    result = (long)val.Number;
                }
                else
                {
                    result = val.Number;
                }
                break;

            case DataType.String:
                result = val.String;
                break;

            case DataType.Tuple:
                int len = val.Tuple.Length;
                result = new List<object>();
                for (int i = 0; i < len; i++)
                {
                    ((List<object>)result).Add(GetValue(val.Tuple[i]));
                }
                break;

            case DataType.Table:
                Dictionary<string, object> dic = null;
                List<object> list = null;
                bool isDic = false;

                foreach (var key in val.Table.Keys)
                {
                    if (key.Type == DataType.String)
                    {
                        //1つでも文字列型のキーが存在するテーブルは辞書扱い(配列分は切り捨て)
                        if (dic == null) dic = new Dictionary<string, object>();
                        isDic = true;
                        dic.Add(key.String, GetValue(val.Table.Get(key.String)));
                    }
                    else if (!isDic && key.Type == DataType.Number)
                    {
                        if (list == null)
                        {
                            list = new List<object>(val.Table.Length);
                            for (int i = 0; i < val.Table.Length; i++) list.Add(null);
                        }
                        list[(int)key.Number - 1] = GetValue(val.Table.Get((int)key.Number));
                    }
                }
                result = isDic ? dic : list;
                break;

            case DataType.UserData:
                result = val.UserData.Object;
                if (result == null) result = val.UserData.Descriptor.Type;
                break;

            case DataType.Function:
                result = val.Function;
                break;

            case DataType.ClrFunction:
                result = val.Callback;
                break;

            case DataType.Nil:
            case DataType.Void:
                result = null;
                break;

            default:
                result = val;
                break;
        }

        return result;
    }
}
```