最近做数据清洗打算做一个表达式解析模块希望能支持嵌套的模块,类似于nifi的recordpath模块,例:
上下文数据:{"name": "zs","age":18}
表达式:replace(/name,replace(/name,zs,s),replace(/age,8,replace(/age,1,22228888)))
预期结果:z1222288888
最初直接引入nifi的recordpath模块调用发现可以使用,但是引入了非常多nifi的基础类,与项目结合度比较低,并且数据类型要多一层转换(映射成nifi定义的基础类型执行后再映射回来),遂看了下nifi的实现方式,是使用的Antlr3,网上查了下Antlr3的资料非常的少,发现大部分资料都是使用的Antlr4,在网上找到好多计算器的例子,参考着搞了好几天,走了不少弯路,最后发现实际上非常简单,g4文件只有5行就可以,Antlr4真的非常强大,下面是g4及Visitor代码:
Govern.g4
grammar Govern;
expr: expr'('expr (','expr)*')' # func
| COL # col
| ELEMENT # element
;
ELEMENT : [A-Za-z0-9]+ ; // 匹配数字及字母
COL : '/'[A-Za-z0-9]+ ; // 匹配列名
Visitor实现,由于目前只是做一个demo,所以只做了replace方法的实现所以硬编码了replace方法,replace方法只调用了string的replace方法,固不贴代码了,可以根据需要拓展更多方法
public class DoFuncGovernVisitor extends GovernBaseVisitor<Object> {
private Map<String, Object> context;
@Override
public Object visitFunc(GovernParser.FuncContext ctx) {
List<Object> exprList = new ArrayList<>();
for (GovernParser.ExprContext exprContext : ctx.expr()) {
exprList.add(exprContext.accept(this));
}
return getFunc(exprList.get(0).toString()).doFunc(exprList.stream().skip(1).toArray());
}
@Override
public Object visitElement(GovernParser.ElementContext ctx) {
return ctx.getText();
}
@Override
public Object visitCol(GovernParser.ColContext ctx) {
return context.get(ctx.getText().substring(1));
}
private Func<?> getFunc(String funcName) {
System.out.println(funcName);
return new Replace();
}
public void setContext(Map<String, Object> context) {
this.context = context;
}
}
main测试
public static void main(String[] args) {
String query = "replace(name,replace(name,zs,s),replace(age,8,replace(age,1,22228888)))";
GovernLexer lexer = new GovernLexer(CharStreams.fromString(query));
GovernParser parser = new GovernParser(new CommonTokenStream(lexer));
DoFuncGovernVisitor visitor = new DoFuncGovernVisitor();
Map<String, Object> column = new HashMap<>();
column.put("name", "zs");
column.put("age", 18);
visitor.setContext(column);
System.out.println(visitor.visit(parser.expr()));
}
执行结果