20

私は電卓に取り組んでおり、文字列式を取り、それらを評価します。正規表現を使用して数学関数の式を検索し、引数を取得し、関数名を検索して評価する関数があります。私が問題を抱えているのは、引数の数がわかっている場合にのみこれを行うことができ、正規表現を正しく取得できないことです。(そして、文字と)文字の内容を文字で分割しただけで,は、その引数に他の関数呼び出しを含めることはできません。

関数一致パターンは次のとおりです。\b([a-z][a-z0-9_]*)\((..*)\)\b

1 つの引数でのみ機能します。ネストされた関数内のものを除くすべての引数に対してグループを作成できますか? たとえば、次のように一致し、次func1(2 * 7, func2(3, 5))のキャプチャ グループを作成します2 * 7func2(3, 5)

ここで、式を評価するために使用している関数:

    /// <summary>
    /// Attempts to evaluate and store the result of the given mathematical expression.
    /// </summary>
    public static bool Evaluate(string expr, ref double result)
    {
        expr = expr.ToLower();

        try
        {
            // Matches for result identifiers, constants/variables objects, and functions.
            MatchCollection results = Calculator.PatternResult.Matches(expr);
            MatchCollection objs = Calculator.PatternObjId.Matches(expr);
            MatchCollection funcs = Calculator.PatternFunc.Matches(expr);

            // Parse the expression for functions.
            foreach (Match match in funcs)
            {
                System.Windows.Forms.MessageBox.Show("Function found. - " + match.Groups[1].Value + "(" + match.Groups[2].Value + ")");

                int argCount = 0;
                List<string> args = new List<string>();
                List<double> argVals = new List<double>();
                string funcName = match.Groups[1].Value;

                // Ensure the function exists.
                if (_Functions.ContainsKey(funcName)) {
                    argCount = _Functions[funcName].ArgCount;
                } else {
                    Error("The function '"+funcName+"' does not exist.");
                    return false;
                }

                // Create the pattern for matching arguments.
                string argPattTmp = funcName + "\\(\\s*";

                for (int i = 0; i < argCount; ++i)
                    argPattTmp += "(..*)" + ((i == argCount - 1) ? ",":"") + "\\s*";
                argPattTmp += "\\)";

                // Get all of the argument strings.
                Regex argPatt = new Regex(argPattTmp);

                // Evaluate and store all argument values.
                foreach (Group argMatch in argPatt.Matches(match.Value.Trim())[0].Groups)
                {
                    string arg = argMatch.Value.Trim();
                    System.Windows.Forms.MessageBox.Show(arg);

                    if (arg.Length > 0)
                    {
                        double argVal = 0;

                        // Check if the argument is a double or expression.
                        try {
                            argVal = Convert.ToDouble(arg);
                        } catch {
                            // Attempt to evaluate the arguments expression.
                            System.Windows.Forms.MessageBox.Show("Argument is an expression: " + arg);

                            if (!Evaluate(arg, ref argVal)) {
                                Error("Invalid arguments were passed to the function '" + funcName + "'.");
                                return false;
                            }
                        }

                        // Store the value of the argument.
                        System.Windows.Forms.MessageBox.Show("ArgVal = " + argVal.ToString());
                        argVals.Add(argVal);
                    }
                    else
                    {
                        Error("Invalid arguments were passed to the function '" + funcName + "'.");
                        return false;
                    }
                }

                // Parse the function and replace with the result.
                double funcResult = RunFunction(funcName, argVals.ToArray());
                expr = new Regex("\\b"+match.Value+"\\b").Replace(expr, funcResult.ToString());
            }

            // Final evaluation.
            result = Program.Scripting.Eval(expr);
        }
        catch (Exception ex)
        {
            Error(ex.Message);
            return false;
        }

        return true;
    }

    ////////////////////////////////// ---- PATTERNS ---- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    /// <summary>
    /// The pattern used for function calls.
    /// </summary>
    public static Regex PatternFunc = new Regex(@"([a-z][a-z0-9_]*)\((..*)\)");

ご覧のとおり、引数に一致する Regex を構築する試みはかなりまずいものです。うまくいきません。

私がやろうとしているのは、式から2 * 7andを抽出することだけですが、引数の数が異なる関数でも機能する必要があります。正規表現を使用せずにこれを行う方法があれば、それも良いことです。func2(3, 5)func1(2 * 7, func2(3, 5))

4

5 に答える 5

37

より複雑な機能を処理するための単純なソリューションと、より高度なソリューション ( editの後に追加) の両方があります。

投稿した例を実現するには、これを 2 つの手順で行うことをお勧めします。最初の手順は、パラメーターを抽出することです (正規表現は最後に説明されています)。

\b[^()]+\((.*)\)$

次に、パラメーターを解析します。

シンプルなソリューション

以下を使用してパラメーターを抽出します。

([^,]+\(.+?\))|([^,]+)

C# コードの例を次に示します (すべてのアサートがパスします)。

string extractFuncRegex = @"\b[^()]+\((.*)\)$";
string extractArgsRegex = @"([^,]+\(.+?\))|([^,]+)";

//Your test string
string test = @"func1(2 * 7, func2(3, 5))";

var match = Regex.Match( test, extractFuncRegex );
string innerArgs = match.Groups[1].Value;
Assert.AreEqual( innerArgs, @"2 * 7, func2(3, 5)" );
var matches = Regex.Matches( innerArgs, extractArgsRegex );            
Assert.AreEqual( matches[0].Value, "2 * 7" );
Assert.AreEqual( matches[1].Value.Trim(), "func2(3, 5)" );

正規表現の説明。単一の文字列としての引数の抽出:

\b[^()]+\((.*)\)$

どこ:

  • [^()]+開き括弧、閉じ括弧ではない文字。
  • \((.*)\)括弧内のすべて

引数の抽出:

([^,]+\(.+?\))|([^,]+)

どこ:

  • ([^,]+\(.+?\))コンマではない文字の後に括弧内の文字が続きます。これにより、func 引数が取得されます。+? に注意してください。一致が遅延し、最初に一致する ) で停止するようにします。
  • |([^,]+)前の文字が一致しない場合は、コンマではない連続した文字に一致します。これらの試合はグループに分けられます。

より高度なソリューション

現在、そのアプローチにはいくつかの明らかな制限があります。たとえば、最初の閉じ括弧に一致するため、ネストされた関数をうまく処理できません。より包括的なソリューション (必要な場合) については、バランシング グループ定義を使用する必要があります(この編集の前に述べたように)。私たちの目的のために、バランスグループの定義により、開き括弧のインスタンスを追跡し、閉じ括弧のインスタンスを差し引くことができます。本質的に、最後の閉じ括弧が見つかるまで、開き括弧と閉じ括弧は検索のバランス部分で互いに打ち消し合います。つまり、対戦は、ブラケットのバランスが取れて最後の終了ブラケットが見つかるまで続行されます。

したがって、parms を抽出する正規表現は次のようになります (func 抽出は同じままでかまいません)。

(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*\)))*)+

実際の動作を示すいくつかのテスト ケースを次に示します。

string extractFuncRegex = @"\b[^()]+\((.*)\)$";
string extractArgsRegex = @"(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*\)))*)+";

//Your test string
string test = @"func1(2 * 7, func2(3, 5))";

var match = Regex.Match( test, extractFuncRegex );
string innerArgs = match.Groups[1].Value;
Assert.AreEqual( innerArgs, @"2 * 7, func2(3, 5)" );
var matches = Regex.Matches( innerArgs, extractArgsRegex );
Assert.AreEqual( matches[0].Value, "2 * 7" );
Assert.AreEqual( matches[1].Value.Trim(), "func2(3, 5)" );

//A more advanced test string
test = @"someFunc(a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2)";
match = Regex.Match( test, extractFuncRegex );
innerArgs = match.Groups[1].Value;
Assert.AreEqual( innerArgs, @"a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2" );
matches = Regex.Matches( innerArgs, extractArgsRegex );
Assert.AreEqual( matches[0].Value, "a" );
Assert.AreEqual( matches[1].Value.Trim(), "b" );            
Assert.AreEqual( matches[2].Value.Trim(), "func1(a,b+c)" );
Assert.AreEqual( matches[3].Value.Trim(), "func2(a*b,func3(a+b,c))" );
Assert.AreEqual( matches[4].Value.Trim(), "func4(e)+func5(f)" );
Assert.AreEqual( matches[5].Value.Trim(), "func6(func7(g,h)+func8(i,(a)=>a+2))" );
Assert.AreEqual( matches[6].Value.Trim(), "g+2" );

特に、メソッドが非常に高度になっていることに注意してください。

someFunc(a,b,func1(a,b+c),func2(a*b,func3(a+b,c)),func4(e)+func5(f),func6(func7(g,h)+func8(i,(a)=>a+2)),g+2)

したがって、正規表現をもう一度見てください。

(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*\)))*)+

要約すると、コンマや括弧ではない文字で始まります。次に、引数に角かっこがある場合、バランスが取れるまで角かっこを一致させて減算します。次に、引数に他の関数がある場合に備えて、その一致を繰り返そうとします。次に、次の引数 (コンマの後) に進みます。詳細に:

  • [^,()]+「,()」以外のすべてに一致します
  • ?:は非キャプチャ グループを意味します。つまり、グループ内の括弧内に一致を保存しません。
  • \(開き括弧から開始することを意味します。
  • ?>アトミックグループ化を意味します-基本的に、これはバックトラック位置を覚えていないことを意味します. これは、さまざまな組み合わせを試すためのステップバックが少なくなるため、パフォーマンスの向上にも役立ちます。
  • [^()]+|開き括弧または閉じ括弧以外を意味します。この後に | が続きます。(また)
  • \((?<open>)|これは良いことで、match '(' または
  • (?<-open>)これは、')' に一致し、'(' のバランスを取るというより良い方法です。これは、一致のこの部分 (最初のブラケットの後のすべて) が、すべての内部ブラケットが一致するまで続くことを意味します。バランス式がないと、一致は最初の閉じ括弧で終了します. 核心は、エンジンがこの ')' を最後の ')' と照合せず、代わりに一致する '(' から差し引くことです. -open は失敗するため、最後の ')' を一致させることができます。
  • 正規表現の残りの部分には、グループの閉じ括弧と繰り返し ( および +) が含まれます。これらはそれぞれ次のとおりです: 内側の括弧の一致を 0 回以上繰り返し、完全な括弧の検索を 0 回以上繰り返します (0 は括弧なしの引数を許可します)。完全一致を 1 回以上繰り返します (foo(1)+foo(2) が許可されます)。

最後の装飾:

(?(open)(?!))正規表現に追加する場合:

(?:[^,()]+((?:\((?>[^()]+|\((?<open>)|\)(?<-open>))*(?(open)(?!))\)))*)+

(?!) は、open が何か (減算されていないもの) をキャプチャした場合、常に失敗します。つまり、閉じ括弧のない開き括弧がある場合、常に失敗します。これは、バランシングが失敗したかどうかをテストする便利な方法です。

いくつかのメモ:

  • 最後の文字が ')' の場合、 \b は一致しません。これは、単語の文字ではなく、\bが単語の文字境界をテストするため、正規表現が一致しないためです。
  • 正規表現は強力ですが、専門家の中の専門家でない限り、式をシンプルに保つのが最善です。そうしないと、メンテナンスが難しく、他の人が理解するのが難しくなります。そのため、問題をサブ問題とより単純な式に分割し、言語が得意とする非検索/一致操作の一部を言語に実行させることが最善の場合があります。そのため、慣れている場所に応じて、単純な正規表現をより複雑なコードと組み合わせたり、その逆を行ったりすることができます。
  • これはいくつかの非常に複雑な関数に一致しますが、関数の字句解析ではありません。
  • 引数に文字列を含めることができ、文字列自体に角かっこを含めることができる場合 (例: "go(...")、比較から文字列を除外するように正規表現を変更する必要があります。コメントも同様です。
  • グループ定義のバランスをとるためのいくつかのリンク:ここここここおよびここ

それが役立つことを願っています。

于 2013-09-20T03:25:03.757 に答える
0

正規表現では、この問題を完全に解決することはできません...

(括弧がネストされているため、コードを変更してに対してカウントする必要があります)。に遭遇した場合は(、その位置に注意して先を見越して、見つけた余分な ものごとにカウンターを増やし、見つけたごとにカウンターを減らす必要があります。カウンターが 0 のときに、関数パラメーター ブロックの末尾である が見つかった場合、括弧内のテキストを解析できます。関数パラメーターを取得するために、カウンターが 0 のときにテキストを分割することもできます。()),

カウンターが 0 のときに文字列の末尾に到達すると、"(" without ")"エラーが発生します。

次に、開き括弧と閉じ括弧、およびコンマの間のテキスト ブロックを取得し、パラメーターごとに上記の手順を繰り返します。

于 2013-09-20T00:30:32.517 に答える