构建 Antlr4 AST的一点心得

731 阅读2分钟

项目上需要用到实现自定义的一套 filter dsl, 来增强 es 搜索的能力. 采用了Antlr4作为语法解析工具, Antlr4 需要我们提供一套语法规则来帮助构建语法树, 那我们想要实现的效果大概有以下几个点:

  • 语句之间的逻辑关系支持 "与" 和 "或", 可以支持"&&", "and" "||", "or" 的格式
  • 支持的条件判断有"eq" , "ne", "gt", "ge", "lt", 'le", "in", "has".
  • 支持的数据的类型有两种, 字符串"String"和数字"Number"以及两者的数组类型.
  • 同时需要支持 "( )" 的无限嵌套形式.

下面是个参考示例:

" 'sku.name' has '香水' and ('sku.price' le 120.5 || 'shop.address' in ['湖南', '广州'])"

意思很清晰就是: 商品名包含香水, 价格小于120.5或店铺地址在湖南或者广州.

这种语法其实算比较简单的了,规则不多也比较容易理解, 要说难点的话可能就是如何优雅地处理类似 '(xxx (xxx) (xxx) ) (xxx)' 这样的结构.下面也是我自己摸索出来的tips 可以帮助更快的去构建语法树.

  1. 列出一些示例, 基本涵盖我们需要支持的所有规则, 可以先从简单的开始
  • 简单语句: " 'sku.name' eq '手表' "
  • 复合语句: '' 'address' in ['CN', 'US'] and 'price' gt 10"
  • 嵌套语句: " ('address' in ['CN', 'US'] and 'price' gt 10) || ('sku.name' eq '手表' && 'shop.name' has '中国')"
  1. 模式拆解. 不难看出,这个语法的组成模式就是表达式之间的逻辑组合, 抽象成 expr bool expr , expr是最小的语句单元, 可以得到true或者false的结果, bool是'&&' '||'. 那么对于嵌套的语句呢? 一个嵌套语句是多个嵌套语句或者expr的组合, 也可以得到true或者false的结果. 那么使用一个更上层的符号stmt来表示, stmt的child可以是expr或者stmt.

  2. 针对列出来的语句, 尝试动手画出来, 通过不断的尝试和优化找到合理的语法树结构,例如

  1. 使用antlr的语法进行描述. 在有了合理的语法树之后,再将它转换成对应的描述方式就比较容易了. 下面是我的g4文件
grammar EsFilter;

filter : stmt? EOF;

stmt
: expr
| '(' stmt ')'
| stmt Condition stmt
;

expr: val Op val;

val: const | arr ;

Op
  : 'eq'
  | 'ne'
  | 'gt'
  | 'ge'
  | 'lt'
  | 'le'
  | 'has'
  | 'in'
  ;

Condition
  : '&&'
  | 'and'
  | '||'
  | 'or'
  ;

const
  : NUMBER
  | STRING
  ;

arr : '[' const (',' const )* ']';

NUMBER
   : '-'? INT ('.' [0-9]+)? EXP?
   ;

fragment INT
   : '0' | [0]* [1-9] [0-9]*
   ;

fragment EXP
  : [Ee] [+-]? INT
  ;

//STRING : ''' (ESC | SAFECODEPOINT)* ''';
STRING : ''' .*? ''';

WS : ('\t' | ' ' | '\n')+ -> skip;