5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

c# 文字列のパラメータ付数式を関数に変換して計算する

Last updated at Posted at 2018-11-27

formula2.gif

はじめに

お仕事で機器からの数十点の計測値を100ms周期で取り込み、それぞれ別の計算式で物理値に変換してチャートに表示するというのがありました。
計算式は機器の型式などで変更になるため文字列で以下のように定義ファイルに記述します。
"para1 / 360 * 100 + 1"
文字列はExpressionの式木を使って関数に変換し結果をバイナリで返します。
para1は計測値を示すパラメータでdouble型で与えられ、結果もdouble型で返します。
これ自体は数十ステップのプログラムで実現できたのですが、もう少し高機能にできないものかと、勉強を兼ねてプライベートで作ったのが本記事で紹介するコード(FormulaConverter)です。
計算式は以下のように記述します。
"Func<int,int,double>(x,y)=(int)x/2 + 5 - 10 * 2.3333d + (int)0.5f+Math.Pow(Math.Abs(y),2)"

特長としては、
・パラメータを渡せる
・カッコで計算順序を制御できる
・明示的な型変換(キャストができる
・System.Math関数が使える
・ラムダ式をコンパイルするのでそれなりに高速である
などです。
基本的に他のコードは流用していませんので完全フリーですが、極端に言うと掲載した計算結果しか確認していませんので使用する場合は自己責任でお願いします。

計算例

FormulaConverterとラムダ式で同等の式を作成し実行してみました。
以下のようなコードを自動生成して試しました。

.cs
//ForamulaConverter(関数定義なし)
dlg3 = FormulaConverter.GetDelegate("(int)para1/2 + 5 - 10 * 2.3333d + (int)0.5f");
var drtn3 = dlg3.DynamicInvoke(7);
//-----------
//ForamulaConverter(関数定義あり)
Delegate dlg15 = FormulaConverter.GetDelegate("Func<int,int,int>(x,y)=(2+x*2)/(y-2.5)*(x+5)");
var drtn15 = dlg15.DynamicInvoke(11, 22);
//ラムダ式(ForamulaConverter「関数定義あり」の同等コード)
Func<int, int, int> func15 = (x, y) => (int)((2 + x * 2) / (y - 2.5) * (x + 5));
var lrtn15 = func15(11, 22);

実行した結果です。

No. 計算式(文字列)定義 関数呼び出し 結果 ラムダ式の結果 コメント
1 "para1" dlg1.DynamicInvoke(1234); 1234 -
2 "(int)7/2 - 10 * 2.3333d" dlg2.DynamicInvoke(); -20.333 -
3 "(int)para1/2 + 5 - 10 * 2.3333d
+ (int)0.5f"
dlg3.DynamicInvoke(7); -15.333 -
4 "-~ 0x00001111" dlg4.DynamicInvoke(); 4370 -
5 "~ -0x00001111" dlg5.DynamicInvoke(); 4368 -
6 "0xffff0000 ^ 0" dlg6.DynamicInvoke(); 4294901760 -
7 "Func<double>()=(int)7/2
- 10 * 2.3333d"
dlg7.DynamicInvoke(); -20.333 -20.333 一致
8 "Func<int,int>(x)
=(int)x/2 + 5 - 10 * 2.3333d + (int)0.5f"
dlg8.DynamicInvoke(7); -15 -15 一致
9 "Func<double>()
=-~ 0x00001111"
dlg9.DynamicInvoke(); 4370 4370 一致
10 "Func<double>()
=~ -0x00001111"
dlg10.DynamicInvoke(); 4368 4368 一致
11 "Func<int,uint>(x)
=0xffff0000 ^ x"
dlg11.DynamicInvoke(0); 4294901760 4294901760 一致
12 "Func<int,double>(x)
=3.5d / x * (2 + 1.1f)"
dlg12.DynamicInvoke(2); 5.42500004172325 5.42499983310699 不一致
(誤差あり)
13 "Func<double>()=2 + 1.1f" dlg13.DynamicInvoke(); 3.10000002384186 3.09999990463257 不一致
(誤差あり)
14 "Func<float>()=2 + 1.1f" dlg14.DynamicInvoke(); 3.1 3.1 一致
15 "Func<int,int,int>(x,y)
=(2+x2)/(y-2.5)(x+5)"
dlg15.DynamicInvoke(11, 22); 19 19 一致
16 "Func<int,int>(x)
=x & 0xffff0000 | 0x00ff0000"
dlg16.DynamicInvoke(10); 16711680 16711680 一致
17 "Func<int,int,double>(x,y)
=(int)x/2 + 5 - 10 * 2.3333d
+ (int)0.5f
+Math.Pow(Math.Abs(y),2)"
dlg17.DynamicInvoke(7, -5); 9.667 9.667 一致
18 "Func<decimal,double>(x)=Math.Pow(Math.Abs(x),2)" dlg18.DynamicInvoke(-5m); 例外 コンパイルエラー 型に適合するPow関数が無い
19 "Func<double
,MidpointRounding,double>(x,y)
=Math.PI+Math.Round(x,2,y)"
dlg19.DynamicInvoke(Math.PI, MidpointRounding.ToEven) 6.28159265358979 6.28159265358979 一致
20 "Func<double>()=1|2&3^4+5-6*7/8%9" dlg20.DynamicInvoke(); 7 7 一致
21 "Func<sbyte,short,ushort,int,uint
,long,double>(x1,x2,x3,x4,x5,x6)
=x1+x2+x3+x4+x5+x6"
dlg21.DynamicInvoke((sbyte)-101, (short)-102, (ushort)103, -104, (uint)105, -106); -205 -205 「(sbyte)-101」など引数の型を明示的に定義すること

###速度
FormulaConverterとラムダ式、念のためハードコーディングした関数でそれぞれ100万回実行してみました。

.cs
//FormulaConverter定義
var dele = FormulaConverter.GetDelegate("Func<int,int,double>(x,y)=(int)x/2 + 5 - 10 * 2.3333d + (int)0.5f+Math.Pow(Math.Abs(y),2)");
//ラムダ式定義
Func<int, int, double> func = (x, y) => (double)((int)x / 2 + 5 - 10 * 2.3333d + (int)0.5f + Math.Pow(Math.Abs(y), 2));
//専用関数
double TestFunc(int x,int y)
{
    return (double)((int)x / 2 + 5 - 10 * 2.3333d + (int)0.5f + Math.Pow(Math.Abs(y), 2));
}

//FormulaConverter 100万回実行
    var rtn = (double)dele.DynamicInvoke(7, -5);
//ラムダ式 100万回実行
    var rtn = func(7, -5);
//専用関数 100万回実行
    var rtn = TestFunc(7, -5);
}

測定結果です。
まあ想定内ですがラムダ式が一番早いみたいです。

FormulaConverter: 855msec
Lambda Function: 69msec
Test Function: 92msec

###まとめ(制約など)
・文字列の解析処理を簡略化しているため"1++1"はエラーにならず"1+ +1"と同じ結果になります。
また、"-1"のようなリテラル値はExpression.Constant(-1)ではなく、Expression.Constant(1)を Expression.Negateに変換します。
・型は数値型とSystem.Mathなどをサポートしています。
・Math関数はMath.PIとかMidpointRounding.ToEvenとか使えるようにしました。引数にも渡せます。enumのORは試していません。
・float型を使ったときラムダ式と違いがでます。
・当初エラー処理はほとんどやっていなかったのですが例外メッセージを追加しています。

###追加、変更など
2018/11/28
投稿したばかりなのですがやっぱりMath.PIなど使えないといやですよね。
という訳で、やっちゃえリフレクション、Math.PIなどのフィールド値を使えるようにしました。No.19が追加のサンプルです。
2018/11/30
演算子の優先順序が思いっきり違っていたので%演算子の追加と合わせて修正しました。
2018/12/2
一応数値型全てに対応しました。No20と21を追加しています。
またこれを機に型の変換優先度などを定義した間に合わせの辞書は廃止しました。
2018/12/15
Math関数以外の対応と"1/-5"のように割り算と符号の組み合わせが記述できない不具合を対策しました。
合わせてフォーマットエラーについて極力意図した例外を発行するようにコードを見直しました。
2019/4/9
コードが長いので最後尾に移動しました。内容は変わっていません。
2020/10/7
適用例(画像)を追加しました。

コード

FormulaConverter.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;

namespace Formula
{
    public class FormulaConverter
    {
        private static readonly Dictionary<string, Type> ALIAS_MAP = new Dictionary<string, Type>()
        {
            {"sbyte",typeof(SByte)},{"byte",typeof(Byte)},{"short",typeof(Int16)},{"ushort",typeof(UInt16)},
            { "int",typeof(Int32)},{"uint",typeof(UInt32)},{"long",typeof(Int64)},{"ulong",typeof(UInt64)},
            { "decimal",typeof(Decimal)},{"float",typeof(Single)},{"double",typeof(Double)}

        };

        /// <summary>
        /// 算術式を記述した文字列からデリゲート関数のインスタンスを作成する
        /// </summary>
        /// <param name="formula">
        /// 記述例:
        /// 関数定義なし:"(int)para1/4 + 2" → delegate(5)=3
        ///             パラメータ名はpara1,para2と記述する。パラメータの型はdoubleと見なす。
        /// 関数定義あり:"Func<int,int>(x)=x/4 + 2"
        /// </param>
        /// <returns>デリゲート</returns>
        public static Delegate GetDelegate(string formula)
        {
            FormStructure funcDef = new FormStructure(formula);
            int leftPosi;
            while ((leftPosi = funcDef.FormulaParts.FindLastIndex(x => x.Part.Equals("("))) >= 0)
            {
                if (leftPosi >= 0)
                {
                    int rightPosi = funcDef.FormulaParts.Select((part, ix) => new { Value = part, Index = ix })
                            .First(x => x.Value.Part.Equals(")") && x.Index > leftPosi).Index;
                    if (rightPosi >= 0)
                    {
                        List<Token> innerParts = funcDef.FormulaParts.GetRange(leftPosi + 1, rightPosi - leftPosi);
                        funcDef.FormulaParts.RemoveRange(leftPosi, rightPosi - leftPosi + 1);
                        //関数の引数だった場合引数の数を知るためカッコの中をList化する
                        int commaIndex;
                        List<Token> argexp = new List<Token>();
                        if (rightPosi > (leftPosi + 1))
                        {
                            while ((commaIndex = innerParts.FindIndex(s => s.Part.Equals(",") || s.Part.Equals(")"))) >= 0)
                            {
                                argexp.Add(ParseInnerParts(innerParts.GetRange(0, commaIndex)));
                                innerParts.RemoveRange(0, commaIndex + 1);
                            }
                        }
                        funcDef.FormulaParts.Insert(leftPosi, new Token(argexp));
                    }
                    else
                        throw new FormatException(formula + ":Missing right parenthesis.");
                }
            }
            if (funcDef.FormulaParts.Count == 1)
            {
                List<Token> rootLst = funcDef.FormulaParts[0].ToBetListValue;
                Expression rootExpr = rootLst[0].ToBeExprValue;
                if (funcDef.RtnType != null)
                {
                    if (funcDef.RtnType != rootExpr.Type)
                        rootExpr = ConvertExpression(rootExpr, funcDef.RtnType);
                }
                else
                {
                    funcDef.RtnType = rootExpr.Type;
                }
                return funcDef.GetDelegete(rootExpr);
            }
            throw new FormatException(formula + ":Can not parse the formula.");
        }

        //かっこで括られた式の最小単位を解析する
        private static Token ParseInnerParts(List<Token> subParts)
        {
            //関数
            MethodExpression(subParts);
            //キャスト
            ConvExpression(subParts);
            //単項演算
            PreExpression(subParts, "+-~", Expression.UnaryPlus, Expression.Negate, Expression.Not);
            //2項算術演算 
            JoinExpression(subParts, "*/%", Expression.Multiply, Expression.Divide, Expression.Modulo);            
            JoinExpression(subParts, "+-", Expression.Add, Expression.Subtract);
            //2項論理演算 
            JoinExpression(subParts, "&^|", Expression.And, Expression.ExclusiveOr, Expression.Or);
            if (subParts.Count != 1)
                throw new FormatException("Can not parce part of the formula.");
            return subParts[0];
        }

        //関数
        private static void MethodExpression(List<Token> parts)
        {            
            int posi = 0;
            while (posi < parts.Count)
            {
                if (parts[posi].Part is MethodInfo candMethodInfo)
                {
                    Expression[] paraArg = parts[posi + 1].ToBetListValue.Select(x => x.ToBeExprValue).ToArray();
                    Type[] argTypes = Array.ConvertAll(paraArg, x => x.Type);
                    //該当のMethodInfoを取得
                    var classMethod = candMethodInfo.DeclaringType.GetMethod(candMethodInfo.Name, argTypes);
                    if (classMethod == null)
                    {
                        //パラメータの型が適合するオーバーロードメソッドが見つからない
                        var argStr = string.Join(",", argTypes.Select(x => x.Name));
                        throw new FormatException(candMethodInfo.DeclaringType.Name+"."+candMethodInfo.Name+"(" + argStr+ "): No Method to match the types.");
                    }
                    ParameterInfo[] parameters = classMethod.GetParameters();
                    paraArg = paraArg.Select((value, index) => new { Value = value, Index = index })
                        .Select(expr =>
                        {
                            if (expr.Value.Type != parameters[expr.Index].ParameterType)
                                return ConvertExpression(expr.Value, parameters[expr.Index].ParameterType);
                            else
                                return expr.Value;
                        }).ToArray();
                    parts[posi] = new Token(Expression.Call(classMethod, paraArg));
                    parts.RemoveRange(posi + 1, 1);
                }
                posi++;
            }
        }

        //キャスト調査
        private static void ConvExpression(List<Token> subParts)
        {
            int posi = 0;
            while (posi < subParts.Count)
            {
                if (posi >= 0 && posi < (subParts.Count - 1))
                {
                    if (subParts[posi].Part is Type castType)
                    {
                        subParts[posi] = new Token(ConvertExpression(subParts[posi + 1].ToBeExprValue, castType));
                        subParts.RemoveRange(posi + 1, 1);
                    }
                }
                posi++;
            }
        }

        //2項算術式のExpression作成 
        private static void JoinExpression(List<Token> subParts, string opes, params Func<Expression, Expression, Expression>[] joinFuncs)
        {
            int partsCount = 0;
            while (partsCount < subParts.Count)
            {
                if (subParts[partsCount].Part is string str)
                {
                    int opeIdx = opes.IndexOf(str);
                    if (opeIdx >= 0)
                    {
                        if (partsCount > 0 && (partsCount + 1) < subParts.Count)
                        {
                            Expression rightExpr = subParts[partsCount + 1].ToBeExprValue;
                            Expression leftExpr = subParts[partsCount - 1].ToBeExprValue;
                            Type imType;
                            if (opes[opeIdx] == '&' || opes[opeIdx] == '|' || opes[opeIdx] == '^')
                                imType = ConvExpressionImplictType(GetImplictTypeOr, leftExpr, rightExpr);                              
                            else
                                imType = ConvExpressionImplictType(GetImplictTypeAdd, leftExpr, rightExpr);
                            leftExpr = ConvertExpression(leftExpr, imType);
                            rightExpr = ConvertExpression(rightExpr, imType);
                            Expression join = joinFuncs[opeIdx](leftExpr, rightExpr);
                            subParts[partsCount - 1] = new Token(join);
                            subParts.RemoveRange(partsCount, 2);
                        }
                        else
                            throw new FormatException("Operands for binary operator are missing.");
                    }
                    else
                        partsCount++;
                }
                else
                    partsCount++;
            }
            return;
        }

        //前置Expression作成
        private static void PreExpression(List<Token> subParts, string opes, params Func<Expression, Expression>[] prefixFuncs)
        {
            string fowardopes = "+-*/&|^~";
            string signs = "+-";
            if (subParts.Count >= 2)
            {
                int posi = subParts.Count - 2;
                while (posi >= 0)
                {
                    if (subParts[posi].Part is string opestr)
                    {
                        int opeIdx = opes.IndexOf(opestr);
                        if (opeIdx >= 0)
                        {
                            if (signs.IndexOf(opestr) >= 0)
                            {
                                //+-
                                string fowardOpe = string.Empty;
                                if(posi > 0)
                                {
                                    if (subParts[posi - 1].Part is string fstr)
                                        fowardOpe = fstr;

                                }
                                if(posi == 0 || (fowardOpe != string.Empty && fowardopes.IndexOf(fowardOpe) >= 0))
                                {
                                    Expression expr = subParts[posi + 1].ToBeExprValue;
                                    Type imType = ConvExpressionImplictType(GetImplictTypeSign, expr);
                                    expr = ConvertExpression(expr, imType);
                                    Expression nexp = prefixFuncs[opeIdx](expr);
                                    subParts[posi] = new Token(nexp);
                                    subParts.RemoveRange(posi + 1, 1);
                                    if (signs.IndexOf(fowardOpe) >= 0)
                                        //左側の2項演算子の+-はスキップする
                                        posi--;
                                }
                            }
                            else
                            {
                                //~(not)
                                Expression nexp = prefixFuncs[opeIdx](subParts[posi + 1].ToBeExprValue);
                                subParts[posi] = new Token(nexp);
                                subParts.RemoveRange(posi + 1, 1);
                            }
                        }
                    }
                    posi--;
                }
            }
        }

        //文字列をExpression,キャストType,代表MethodInfoに変換
        private static object TryConvElement(object part)
        {
            if (part is Expression exp)
                return exp; 
            else if (part is string str)
            {
                Type castType = GetFieldType(str);
                if (castType != null)
                    return castType;
                MethodInfo oneMethod = TryGetOneMethodInfo(str);
                if(oneMethod != null)
                    return oneMethod;
                if (Regex.IsMatch(str, "^0[xX]"))
                {
                    if (uint.TryParse(str.Substring(2), NumberStyles.HexNumber, null, out var uintValue))
                        return Expression.Constant(uintValue);
                    else
                        return Expression.Constant(ulong.Parse(str.Substring(2), NumberStyles.HexNumber));
                }               
                else
                {
                    object fieldValue = GetFieldValue(str);
                    if (fieldValue != null)
                        return Expression.Constant(fieldValue);
                    else if (Regex.IsMatch(str, "[dD]$"))
                        return Expression.Constant(double.Parse(str.Substring(0, str.Length - 1)));
                    else if (Regex.IsMatch(str, "[fF]$"))
                        return Expression.Constant(float.Parse(str.Substring(0, str.Length - 1)));
                    else if (Regex.IsMatch(str, "[mM]$"))
                        return Expression.Constant(decimal.Parse(str.Substring(0, str.Length - 1)));
                    else
                    {
                        if (int.TryParse(str, out var iValue))
                            return Expression.Constant(iValue);
                        else if (long.TryParse(str, out var lValue))
                            return Expression.Constant(lValue);
                        else if(double.TryParse(str, out var dValue))
                            return Expression.Constant(dValue);
                    }
                }
            }
            return part;
        }

        //型が異なるときExpressionを指定した型に変更する。無効な型はここでエラーとなる。
        private static Expression ConvertExpression(Expression expr,Type type)
        {
            try
            {
                if (type != null && type != expr.Type)
                    return Expression.Convert(expr, type);
            }
            catch
            {
                throw new FormatException("Formula type conversion error : " + expr.ToString() + "(" + expr.Type.Name + ") to " + type.Name );
            }
            return expr;
        }

        //クラス名または形名だった場合Typeを求める
        private static Type GetFieldClassType(string candClassName)
        {
            Type classType = Type.GetType(candClassName);
            if (classType != null)
                return classType;
            return Type.GetType("System." + candClassName);
        }

        //フィールド情報だった場合FieldInfoを求める
        private static FieldInfo GetFieldInfo(string str)
        {
            int periodIdx = str.LastIndexOf(".");
            if(periodIdx >= 0)
            {
                Type classType = GetFieldClassType(str.Substring(0, periodIdx));
                if (classType != null)
                {
                    string candFiledName = str.Substring(periodIdx + 1);
                    FieldInfo[] fields = classType.GetFields();
                    FieldInfo filed = Array.Find(fields, x => x.Name == candFiledName);
                    return filed;
                }
            }            
            return null;
        }

        //フィールド変数だった場合値を求める
        private static object GetFieldValue(string str)
        {
            FieldInfo field = GetFieldInfo(str);
            if (field != null)
                return field.GetValue(field.DeclaringType);
            else
                return null;
        }

        //型名だった場合型を求める
        private static Type GetFieldType(string str)
        {
            if(ALIAS_MAP.ContainsKey(str))
                return ALIAS_MAP[str];
            else
                return GetFieldClassType(str);
        }

        //代表のMethodInfoを取得する
        private static MethodInfo TryGetOneMethodInfo(string str)
        {
            int periodIdx = str.LastIndexOf(".");
            if (periodIdx >= 0)
            {                
                Type candClassType = GetFieldClassType(str.Substring(0, periodIdx));
                if (candClassType != null)
                {
                    string methodName = str.Substring(periodIdx+1);
                    MethodInfo[] classMethos = candClassType.GetMethods();
                    //return Array.Find(classMethos, (xx) => (candClassType.Name + "." + xx.Name) == str);
                    return Array.Find(classMethos, (xx) => candClassType == xx.ReflectedType && methodName == xx.Name);
                }
            }
            return null;
        }

        //算術演算の暗黙的な型を求める
        private static Type GetImplictTypeAdd<T>(T[] values)
        {
            return ((dynamic)values[0] + (dynamic)values[1]).GetType();
        }

        //論理演算の暗黙的な型を求める
        private static Type GetImplictTypeOr<T>(T[] values)
        {
            return ((dynamic)values[0] | (dynamic)values[1]).GetType();
        }

        //Singed型を求める
        private static Type GetImplictTypeSign<T>(T[] values)
        {
            return (-(dynamic)values[0]).GetType();
        }

        //2項演算でExpressionの型を演算時の型に変換する
        private static Type ConvExpressionImplictType(Func<object[], Type> getImplictType, params Expression[] exprs)
        {
            try
            {
                object[] values = exprs.Select(expr => TypeDescriptor.GetConverter(expr.Type).ConvertFromString("1")).ToArray();
                return getImplictType(values);
            }
            catch
            {
                string[] args = exprs.Select(expr => expr.ToString() + "(" + expr.Type.Name + ")").ToArray();
                throw new FormatException("Formula conversion error : " + String.Join(",", args));
            }
        }

        //パラメータ情報
        private class FormulaParameterInfo
        {
            public string Name { get; set; }
            public Type DeclType { get; set; }
            public ParameterExpression DeclExpression { get; set; }
            public FormulaParameterInfo(string name, string typeName)
            {
                Name = name;
                DeclType = GetFieldType(typeName);
                DeclExpression = Expression.Parameter(DeclType);
            }
            public FormulaParameterInfo(string name) : this(name, "double") { }
        }

        //式の要素クラス
        private class Token
        {
            private List<Token> _list = null;

            public Token(object part)
            {
                if(part is List<Token> lst)
                {
                    _list = lst;
                    if (_list.Count == 1)
                        Part = _list[0].Part;
                    else
                        Part = _list;
                }
                else
                    Part = part;
            }

            //要素オブジェクト
            public object Part { get; private set; } = null;

            //文字列
            public string ToBeStringValue
            {
                get
                {
                    if (Part is string str)
                        return str;
                    else
                        throw new FormatException();
                }
            }

            //引数のリスト
            public List<Token> ToBetListValue
            {
                get
                {
                    if (_list != null)
                        return _list;
                    else
                        throw new FormatException("Paramete arguments expected.");
                }
            }

            //Expression
            public Expression ToBeExprValue
            {
                get
                {
                    if (Part is Expression expr)
                        return expr;
                    else
                        throw new FormatException("Expression expected.");
                }
            }
        }

        //関数定義情報
        private class FormStructure
        {
            private static readonly string[] DEFAULT_PARA_NAMES =
            {
                "para1",  "para2", "para3", "para4", "para5", "para6", "para7"
            };
            private static readonly Type[] LAMBDA_DEFS =
            {
                typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>),
                typeof(Func<,,,,>), typeof(Func<,,,,,>), typeof(Func<,,,,,,>), typeof(Func<,,,,,,,>)
            };
            public List<Token> FormulaParts;
            public Type RtnType;
            private FormulaParameterInfo[] Parameters;

            //コンストラクタ
            public FormStructure(string formula)
            {
                string[] paraNames = null;
                string[] typeNames = null;
                //スペースなどを取り除く→ (1+ +1)は(1++1)となるので必要があればこの前で文字チェックする
                string formStr = new Regex("[ \t\n]").Replace(formula, string.Empty);
                //関数定義部が存在するか
                if (formStr.IndexOf('=') >= 0)
                {
                    string[] formParts = formStr.Split('=');
                    if (formParts.Length != 2)
                        throw new FormatException();
                    ParseFuncDef(formParts[0], out paraNames, out typeNames);
                    formStr = formParts[1];
                }
                formStr = "(" + formStr + ")";
                //式を分解する
                List < object > parts = Regex.Split(formStr, "([,\\+\\-\\*\\/\\(\\)&\\|%\\^~])")
                    .Where(x => x != string.Empty).ToList<object>();
                if (paraNames == null)
                {
                    //関数定義部が存在しない場合式からパラメータを抽出する 
                    paraNames = DEFAULT_PARA_NAMES.Where(x => parts.IndexOf(x) >= 0).ToArray();
                    Parameters = paraNames.Select(x => new FormulaParameterInfo(x)).ToArray();
                    RtnType = null;   //戻り値の型は不明とする
                }
                else
                {
                    //関数定義部がある場合
                    Parameters = paraNames.Select((value, index) => new { Value = value, Index = index })
                        .Select(x => new FormulaParameterInfo(x.Value, typeNames[x.Index])).ToArray();
                    RtnType = GetFieldType(typeNames[typeNames.Length - 1]);
                }
                //パラメータ記述部分をParameterExpressionに変換
                FormulaParts = parts.ConvertAll(x =>
                {
                    ParameterExpression pExpr = Array.Find(Parameters, para => para.Name == (string)x)?.DeclExpression;
                    if (pExpr != null)
                        return new Token(pExpr);
                    else
                        return new Token(TryConvElement(x));
                });
            }

            //関数定義部からパラメータ名と型を抽出する
            private void ParseFuncDef(string defStr, out string[] paraNames, out string[] typeNames)
            {
                Regex reg = new Regex("^Func\\<(?<types>.*?)\\>\\((?<paras>.*)\\)$");
                Match m = reg.Match(defStr);
                typeNames = m.Groups["types"].Value.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                paraNames = m.Groups["paras"].Value.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                //型の数=パラメータの数+1
                if (typeNames.Length == paraNames.Length + 1)
                {
                    //型名とパラメータ名の合理性チェック
                    if (typeNames.All(name => GetFieldType(name) != null))
                    {
                        Regex regVar = new Regex("^[a-zA-Z][a-zA-Z0-9_]*$");
                        if (paraNames.All(value => regVar.IsMatch(value)))
                            return;
                    }
                }
                throw new FormatException();
            }

            //演算関数を作成する
            public Delegate GetDelegete(Expression rootExp)
            {
                ParameterExpression[] paramExpressions = Parameters.Select(p => p.DeclExpression).ToArray();
                List<Type> declTypes = Parameters.Select(p => p.DeclType).ToList();
                declTypes.Add(RtnType);
                Type lambdaDef = LAMBDA_DEFS[paramExpressions.Length];
                Type genericType = lambdaDef.MakeGenericType(declTypes.ToArray());
                ParameterExpression valueExpression = Expression.Variable(genericType);
                return Expression.Lambda(
                Expression.Block(
                new[] { valueExpression },
                rootExp
                     ),
                     paramExpressions
                    ).Compile();
            }
        }
    }
}
5
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?