从中可以很容易理解:运算符(BinOp)包含+、-、*和/,而一个二元运算的表达式(BinExpr)由两个表达式(Expr)和一个运算符(BinOp)组成,而二元运算的表达式又是表达式(Expr)的一种。通过这样的语法定义,就可以使用Irony的Parser产生语法树了:
var language = new LanguageData(new ExpressionGrammar()); var parser = new Parser(language); var syntaxTree = parser.Parse(input);怎么样,是不是非常方便?
在迁移Irony项目的同时,我还将Irony的测试工具Irony Grammar Explorer分离出来成为了一个单独的Github Repo。在你定义了上面的ExpressionGrammar类之后,编译你的程序集,然后就可以使用Irony Grammar Explorer进行测试了。比如,使用Irony Grammar Explorer打开Apworks.Querying.Parsers.Irony程序集,它将自动扫描程序集中所有的Grammar定义,然后让用户对各种Grammar进行测试。值得一提的是,在测试界面,Irony Grammar Explorer还能根据语法定义,自动产生语法高亮:
点击右边的语法树中的节点,即可定位到输入字符串的相应部分。比较有趣的一点是,在Irony Grammar Explorer的Github Repo里,还包含了一个语法定义的案例库:IronyExplorer.Samples,它包含了很多流行编程语言的语法定义。比如,下面是C# 3.5语言的语法测试效果:
有关Irony Grammar Explorer的其它功能,我就不一一介绍了,大家可以自己实践一下。总的来说,Irony可以帮助大家快速方便地实现语法解析器,而且功能也能够满足绝大多数需求,针对.NET Core的支持,也使得Irony能够直接被应用在跨平台的.NET应用程序中,并支持Docker部署。接下来的问题就更有趣了:我已经定义了自己的语法,并使用Irony Grammar Explorer通过了测试,接下来,我如何在我的应用程序中运用这个语法?换个方式问:我拿到了语法树后,该怎么办呢?
语法树的处理虽然我们能够将字符串文本解析成一棵语法树,能够通过语法树来体现一个字符串中各个部分的含义,以及它们之间的关系,但是如何能够让计算机来读懂这棵树,并执行相应的任务呢?这就涉及到语法树的处理问题。参考编译原理,词法分析和语法分析已经由Irony完成,接下来的语义分析,就需要我们自己写代码了。
在Irony Repo的案例代码中,我们的目的是能够解析一个四则运算表达式,并计算出结果,于是,我们定义了下面的对象模型:
因此,只需要将解析的语法树转换成上面的对象模型,也就能够通过Evaluation.Value属性,得到计算的最终结果。从代码上看,向对象模型的转换,是通过递归的方式遍历语法树实现的:
private Evaluation PerformEvaluate(ParseTreeNode node) { switch (node.Term.Name) { case "BinaryExpression": var leftNode = node.ChildNodes[0]; var opNode = node.ChildNodes[1]; var rightNode = node.ChildNodes[2]; Evaluation left = PerformEvaluate(leftNode); Evaluation right = PerformEvaluate(rightNode); BinaryOperation op = BinaryOperation.Add; switch (opNode.Term.Name) { case "+": op = BinaryOperation.Add; break; case "-": op = BinaryOperation.Sub; break; case "*": op = BinaryOperation.Mul; break; case "/": op = BinaryOperation.Div; break; } return new BinaryEvaluation(left, right, op); case "Number": var value = Convert.ToSingle(node.Token.Text); return new ConstantEvaluation(value); } throw new InvalidOperationException($"Unrecognizable term {node.Term.Name}."); }以上完整代码请参考Evaluator的实现。整个案例及使用方式可以点击查看。可以看到,使用Irony来实现一个四则混合运算的计算器还是非常方便的。
在Apworks中,我们需要的是能够将一个表达查询语义的语法树,转换成Lambda表达式,以便于后台数据库引擎能够直接执行Lambda表达式完成查询。通过数据库引擎执行Lambda表达式的优势是非常明显的,比如Entity Framework Core可以通过Lambda表达式生成高效的SQL语句并在数据库服务器上执行,性能方面也能兼顾得非常好。
类似的,我们使用.NET Expression的对象模型,通过遍历查询语句的语法树来生成表达式模型,最后转换成Lambda表达式即可。具体过程就不再赘述了,请参考Apworks的源代码。现在我们来看看实际效果。
假设我们的测试数据如下:
Customers.Add(new Customer { Id = 1, Email = "jim@example.com", Name = "jim", DateRegistered = DateTime.Now.AddDays(-1) }); Customers.Add(new Customer { Id = 2, Email = "tom@example.com", Name = "tom", DateRegistered = DateTime.Now.AddDays(-2) }); Customers.Add(new Customer { Id = 3, Email = "alex@example.com", Name = "alex", DateRegistered = DateTime.Now.AddDays(-3) }); Customers.Add(new Customer { Id = 4, Email = "carol@example.com", Name = "carol", DateRegistered = DateTime.Now.AddDays(-4) }); Customers.Add(new Customer { Id = 5, Email = "david@example.com", Name = "david", DateRegistered = DateTime.Now.AddDays(-5) }); Customers.Add(new Customer { Id = 6, Email = "frank@example.com", Name = "frank", DateRegistered = DateTime.Now.AddDays(-6) }); Customers.Add(new Customer { Id = 7, Email = "peter@example.com", Name = "peter", DateRegistered = DateTime.Now.AddDays(-7) }); Customers.Add(new Customer { Id = 8, Email = "paul@example.com", Name = "paul", DateRegistered = DateTime.Now.AddDays(1) }); Customers.Add(new Customer { Id = 9, Email = "winter@example.com", Name = "winter", DateRegistered = DateTime.Now.AddDays(2) }); Customers.Add(new Customer { Id = 10, Email = "julie@example.com", Name = "julie", DateRegistered = DateTime.Now.AddDays(3) }); Customers.Add(new Customer { Id = 11, Email = "jim@example.com", Name = "jim", DateRegistered = DateTime.Now.AddDays(4) }); Customers.Add(new Customer { Id = 12, Email = "brian@example.com", Name = "brian", DateRegistered = DateTime.Now.AddDays(5) }); Customers.Add(new Customer { Id = 13, Email = "david@example.com", Name = "david", DateRegistered = DateTime.Now.AddDays(6) }); Customers.Add(new Customer { Id = 14, Email = "daniel@example.com", Name = "daniel", DateRegistered = DateTime.Now.AddDays(7) }); Customers.Add(new Customer { Id = 15, Email = "jill@example.com", Name = "jill", DateRegistered = DateTime.Now.AddDays(8) });下面调试单元测试,并查看所产生的Lambda表达式,可以看到,Lambda表达式正确产生,测试顺利通过:
总结