你可能需要考虑定义自己的解析器,然后遍历
AST以获取所需值。有许多工具可以做到这一点(例如
flex或
bison)。但在.net世界中,
Irony可能是一个可行的选择:它在
.net standard 2.0
中可用,我没有在
.net core 2.1
控制台测试项目中遇到任何问题。
首先,你通常需要定义语法。幸运的是,微软很好地
为我们提供了EBNF参考资料,所以我们所要做的就是将其适应于Irony。我最终实现了上述语法的子集,似乎可以满足你的示例语句(并且还超出了一些,随意删减)。
using Irony.Parsing;
namespace irony_playground
{
[Language("OData", "1.0", "OData Filter")]
public class OData: Grammar
{
public OData()
{
var identifier = new RegexBasedTerminal("identifier", "[a-zA-Z_][a-zA-Z_0-9]*");
var string_literal = new StringLiteral("string_literal", "'");
var integer_literal = new NumberLiteral("integer_literal", NumberOptions.IntOnly);
var float_literal = new NumberLiteral("float_literal", NumberOptions.AllowSign|NumberOptions.AllowSign)
| new RegexBasedTerminal("float_literal", "(NaN)|-?(INF)");
var boolean_literal = new RegexBasedTerminal("boolean_literal", "(true)|(false)");
var filter_expression = new NonTerminal("filter_expression");
var boolean_expression = new NonTerminal("boolean_expression");
var collection_filter_expression = new NonTerminal("collection_filter_expression");
var logical_expression = new NonTerminal("logical_expression");
var comparison_expression = new NonTerminal("comparison_expression");
var variable = new NonTerminal("variable");
var field_path = new NonTerminal("field_path");
var lambda_expression = new NonTerminal("lambda_expression");
var comparison_operator = new NonTerminal("comparison_operator");
var constant = new NonTerminal("constant");
Root = filter_expression;
filter_expression.Rule = boolean_expression;
boolean_expression.Rule = collection_filter_expression
| logical_expression
| comparison_expression
| boolean_literal
| "(" + boolean_expression + ")"
| variable;
variable.Rule = identifier | field_path;
field_path.Rule = MakeStarRule(field_path, ToTerm("/"), identifier);
collection_filter_expression.Rule =
field_path + "/all(" + lambda_expression + ")"
| field_path + "/any(" + lambda_expression + ")"
| field_path + "/any()";
lambda_expression.Rule = identifier + ":" + boolean_expression;
logical_expression.Rule =
boolean_expression + (ToTerm("and", "and") | ToTerm("or", "or")) + boolean_expression
| ToTerm("not", "not") + boolean_expression;
comparison_expression.Rule =
variable + comparison_operator + constant |
constant + comparison_operator + variable;
constant.Rule =
string_literal
| integer_literal
| float_literal
| boolean_literal
| ToTerm("null");
comparison_operator.Rule = ToTerm("gt") | "lt" | "ge" | "le" | "eq" | "ne";
RegisterBracePair("(", ")");
}
}
}
一些提示:Irony附带了Grammar Explorer工具,允许您加载语法dll并进行调试,因此建议将您的类放在自己的项目中。然后,您就可以更轻松地理解这些概念了:
![输入图像描述](https://istack.dev59.com/caUtE.webp)
在您满意语法之后,您需要从您的项目中引用它并解析输入字符串:
class Program
{
static void Main(string[] args)
{
var g = new OData();
var l = new LanguageData(g);
var r = new Parser(l);
var p = r.Parse("((Name eq 'John' or Name eq 'Grace Paul') and (Department eq 'Finance and Accounting'))");
}
}
然后,您需要遍历生成的树,并根据语法节点类型应用自定义逻辑。如何实现可以在这个SO答案中找到一个例子。
根据您的要求,您可能会发现这对于您的目的来说是一种过度设计,或者实际上可能会发现它给了您恰当的控制级别。