这篇文章分享如何用antlr解析odata filter条件表达式。
我最早接触antlr,是在刚开始工作后不久,那次需要用antlr实现一个功能:把gemfire的OQL(object query language)翻译成SQL语句,以便进行数据库操作。其实,简单讲,antlr就是一个非常方便的词法分析和语法分析的类库,基于这个类库,可以很容易的实现很多场景,比如计算器算术表达式的解析、各种编程语言的解析等。
印象很深刻的记得,大学编译原理的课程里面就有类似的两个练习,一个是实现计算器算术表达式的解析,一个是实现C-语言(C语言的简化版)的解析,当时肯定是需要自己手动实现,不能借助这些类库,那如何做的呢?一个很关键的点是状态机,在真正开始实现功能之前,需要根据具体问题的需求画一个状态机(个人觉得和状态图有些类似,或者说是状态图的一种形式),用状态机来描述哪些字符连一起可以构成哪种token,基于这个状态机就可以很方便的实现词法解析。其实,状态机在很多其它地方也有用途,比如:订单的状态变化,其实就可以用状态机来定义。
除了上面提到的场景,还有两个我们平时经常碰到的场景:json解析和html在线编辑器,它们都可以用antlr来实现。
具体odata filter条件表达式的定义可以参考odata官方文档,这里为了描述问题方便,简化基本规则如下:
- 最小的表达式符合模式 key operator value
- 表达式和表达式可以用逻辑运算符连接成一个新的表达式 expression AND expression
- 表达式的前后可以加括号以提高优先级 (expression OR expression) AND expression
根据上面的规则,下面列举几个例子:
- $filter=Name eq 'John' //查询所有name等于John的人
- $filter=Name eq 'John' AND age ge 30 //查询所有name等于John并且年龄大于等于30岁的人
- $filter=(firstName eq 'John' OR firstName eq 'Bill') AND lastName eq 'Smith' //查询所有名为John或Bill,姓为Smith的人
那么,如何解析上面定义的规则呢?
首先,有一种方案:利用关键字(比如eq, AND等)来split这个filter string,在比较简单的情况下也许这个方案可行,但是如果有表达式嵌套的情况(上面第三个例子),直接split string就会有些显得力不从心。
其实,我们可以看到odata filter条件表达式和计算器的算术表达式有些类似,它们都是非常典型的词法分析和语法分析案例,所以同样可以采用antlr来解析。
如果大家以前没有接触过antlr,网上有很多关于它的资料,大家可以自行网上搜索(包括antlr官网https://www.antlr.org/)。下面仅分享一些我使用antlr(antlr 4)解析odata filter条件表达式的经验总结:
- antlr的简单使用流程:定义grammar->生成对应语言(比如c#)的词法和语法分析代码->实现自己的Visitor遍历抽象语法树AST(abstract syntax tree)。
- 词法定义规则须大写打头,语法定义规则须小写打头。
- 从antlr 4.7开始,提供了对所有unicode的支持。关于这个,举一个实际的例子:由于.NET里面的正则表达式\w可以match很多国家的字符(具体有哪些,see https://docs.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#WordCharacter),如果我们需要odata filter条件表达式里面的key也支持\w可以match的字符,怎么做呢?那得益于antlr对Unicode的支持,可以像下面这样定义key:
fragment VALID_ID_CHAR : [\p{General_Category=Other_Letter}\p{General_Category=Lowercase_Letter}\p{General_Category=Uppercase_Letter}\p{General_Category=Titlecase_Letter}\p{General_Category=Modifier_Letter}\p{General_Category=Nonspacing_Mark}\p{General_Category=Connector_Punctuation}\p{General_Category=Decimal_Number}] ;
- 从antlr 4.5开始,c#的runtime换成了Antlr4.Runtime.Standard;之前的版本是用Sam Harwell提供的一个Runtime。参考https://github.com/antlr/antlr4/tree/master/runtime/CSharp。
- Intellij的antlr的插件提供了实时preview的功能,非常方便调试;VS的插件则没有这功能。
- 关于odata filter条件表达式的示例grammar文件,可以参考https://github.com/huazailmh/ODataFilterParser。
References
Antlr basics:
- https://github.com/antlr/antlrcs
- https://github.com/antlr/antlr4
- https://github.com/tunnelvisionlabs/antlr4cs
- https://github.com/antlr/grammars-v4
Unicode support:
- https://github.com/antlr/antlr4/blob/master/doc/unicode.md
- https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md#lexer-rule-elements
- http://unicode.org/reports/tr44/#General_Category_Values
- https://www.compart.com/en/unicode/category
- https://docs.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#WordCharacter