括弧付きのブール式を含む C# の文字列を検証したいと考えています。文字列には、1 ~ 9 の数字、丸括弧、「OR」、「AND」のみを含める必要があります。
良い文字列の例:
「1と2」
「2または4」
「4 AND (3 OR 5)」
「2」
等々...
正規表現がこのタスクに十分柔軟かどうかはわかりません。C# でこれを達成する簡単な方法はありますか?
括弧付きのブール式を含む C# の文字列を検証したいと考えています。文字列には、1 ~ 9 の数字、丸括弧、「OR」、「AND」のみを含める必要があります。
良い文字列の例:
「1と2」
「2または4」
「4 AND (3 OR 5)」
「2」
等々...
正規表現がこのタスクに十分柔軟かどうかはわかりません。C# でこれを達成する簡単な方法はありますか?
単純なパーサーでこれを行う方がおそらく簡単です。しかし、バランシング グループを使用して .NET 正規表現でこれを行うことができます。文字列から角かっこを削除すると、常に.^\d+(?:\s+(?:AND|OR)\s+\d+)*\z
したがって、バランシング グループを使用して、ブラケットのバランスが取れていることを確認するだけです (適切な場所に適切な形で配置されます)。
上記の式を少し書き換えます。
(?x)^
OPENING
\d+
CLOSING
(?:
\s+(?:AND|OR)\s+
OPENING
\d+
CLOSING
)*
BALANCED
\z
((?x)
正規表現エンジンがパターン内のすべての空白とコメントを無視するようにするため、読みやすくなります。)
OPENING
任意の数 (0 を含む) の左角かっこに一致する場所:
\s* (?: (?<open> \( ) \s* )*
CLOSING
また、バランシング グループのバランスがとれていることを確認しながら、任意の数の閉じ括弧に一致します。
\s* (?: (?<-open> \) ) \s* )*
バランスチェックをBALANCED
実行し、閉じたブラケットよりも開いたブラケットの方が多い場合は失敗します。
(?(open)(?!))
式を与える:
(?x)^
\s* (?: (?<open> \( ) \s* )*
\d+
\s* (?: (?<-open> \) ) \s* )*
(?:
\s+(?:AND|OR)\s+
\s* (?: (?<open> \( ) \s* )*
\d+
\s* (?: (?<-open> \) ) \s* )*
)*
(?(open)(?!))
\z
ランダムなスペースを許可したくない場合は、すべて削除します\s*
。
IdeOneでデモを参照してください。出力:
matched: '2'
matched: '1 AND 2'
matched: '12 OR 234'
matched: '(1) AND (2)'
matched: '(((1)) AND (2))'
matched: '1 AND 2 AND 3'
matched: '1 AND (2 OR (3 AND 4))'
matched: '1 AND (2 OR 3) AND 4'
matched: ' ( 1 AND ( 2 OR ( 3 AND 4 ) )'
matched: '((1 AND 7) OR 6) AND ((2 AND 5) OR (3 AND 4))'
matched: '(1)'
matched: '(((1)))'
failed: '1 2'
failed: '1(2)'
failed: '(1)(2)'
failed: 'AND'
failed: '1 AND'
failed: '(1 AND 2'
failed: '1 AND 2)'
failed: '1 (AND) 2'
failed: '(1 AND 2))'
failed: '(1) AND 2)'
failed: '(1)() AND (2)'
failed: '((1 AND 7) OR 6) AND (2 AND 5) OR (3 AND 4))'
failed: '((1 AND 7) OR 6) AND ((2 AND 5 OR (3 AND 4))'
failed: ''
入力文字列を検証するだけの場合は、単純なパーサーを作成できます。各メソッドは、特定の種類の入力 (数字、括弧、演算子) を消費し、一致後に残りの文字列を返します。一致するものがない場合、例外がスローされます。
public class ParseException : Exception { }
public static class ExprValidator
{
public static bool Validate(string str)
{
try
{
string term = Term(str);
string stripTrailing = Whitespace(term);
return stripTrailing.Length == 0;
}
catch(ParseException) { return false; }
}
static string Term(string str)
{
if(str == string.Empty) return str;
char current = str[0];
if(current == '(')
{
string term = LBracket(str);
string rBracket = Term(term);
string temp = Whitespace(rBracket);
return RBracket(temp);
}
else if(Char.IsDigit(current))
{
string rest = Digit(str);
try
{
//possibly match op term
string op = Op(rest);
return Term(op);
}
catch(ParseException) { return rest; }
}
else if(Char.IsWhiteSpace(current))
{
string temp = Whitespace(str);
return Term(temp);
}
else throw new ParseException();
}
static string Op(string str)
{
string t1 = Whitespace_(str);
string op = MatchOp(t1);
return Whitespace_(op);
}
static string MatchOp(string str)
{
if(str.StartsWith("AND")) return str.Substring(3);
else if(str.StartsWith("OR")) return str.Substring(2);
else throw new ParseException();
}
static string LBracket(string str)
{
return MatchChar('(')(str);
}
static string RBracket(string str)
{
return MatchChar(')')(str);
}
static string Digit(string str)
{
return MatchChar(Char.IsDigit)(str);
}
static string Whitespace(string str)
{
if(str == string.Empty) return str;
int i = 0;
while(i < str.Length && Char.IsWhiteSpace(str[i])) { i++; }
return str.Substring(i);
}
//match at least one whitespace character
static string Whitespace_(string str)
{
string stripFirst = MatchChar(Char.IsWhiteSpace)(str);
return Whitespace(stripFirst);
}
static Func<string, string> MatchChar(char c)
{
return MatchChar(chr => chr == c);
}
static Func<string, string> MatchChar(Func<char, bool> pred)
{
return input => {
if(input == string.Empty) throw new ParseException();
else if(pred(input[0])) return input.Substring(1);
else throw new ParseException();
};
}
}
非常に簡単に:
最初の段階で、単純な文字列比較で語彙 (数字、括弧、または演算子) を判別する必要があります。
第 2 段階では、閉じ括弧 (bracketPairs) の数の変数を定義する必要があります。これは、各語彙について次のアルゴリズムで計算できます。
現在の語彙素が '(' の場合、bracketPairs++;
現在の語彙素が ')' の場合、bracketPairs--.
それ以外の場合は、bracketPairs を変更しないでください。
最後に、すべての語彙が既知で、bracketPairs == 0 の場合、入力式は有効です。
AST を構築する必要がある場合、タスクはもう少し複雑です。
あなたが望むのは「バランスの取れたグループ」です。それらを使用すると、すべてのブレース定義を取得できます。次に、単純な文字列の解析が必要です
http://blog.stevenlevithan.com/archives/balancing-groups
http://msdn.microsoft.com/en-us/library/bs2twtah.aspx#balancing_group_definition