使用 ANTLR 实现公式解析
- Java
- 2023-06-09
- 2174热度
- 0评论
导航
前言
做类似 BI 的产品时,会遇到公式解析场景。用户(前端)输入一个公式字符串,后端要解析为一个具体的函数对象。
我们使用 ANTLR 来做公式解析。
关于 ANTLR 的基本用法,及它在项目中的基本配置,参见:ANTLR4入门
本文并不是手把手教程,只讲核心点,其他不赘述。
定义函数类
我们会有一个 Function 的抽象类/接口,其他所有函数都继承 Function。函数可以套函数。
根据实际业务需求来定义,这里不是本文重点,略。
我们要将公式字符串解析为 Function 对象。举个例子,对于表达式字符串“a+b+c”,可以表示为一个 ADD 函数,有三个节点(参数):a、b、c。
分析公式表达式
一个公式表达式中,可能出现以下元素。
值类型:
-
表格列
-
整数
-
浮点型数字
-
字符串
函数类型:
-
加减乘除算术运算
-
自定义的函数 比如:sum()、count()等
自定义的关键字:
-
distinct (实际抽象为函数)
写语法和词法
Formula.g4
grammar Formula; expr : expr '/' expr # Divide // 除法 | expr '*' expr # Multiply // 乘法 | expr '-' expr # Subtract // 减法 | expr '+' expr # Add //加法 | FLOAT # Float // 浮点型 | INTEGER # Integer //整数 | '(-'expr')' # Negative // 负运算 | '('expr')' # Parens // 括号运算 | ABS'('expr')' # Abs // 绝对值 | MAX'('expr (','expr)*')' # Max // 最大值 | MIN'('expr (','expr)*')' # Min // 最小值 | SUM'('expr (','expr)*')' # Sum //求和 | '${' MUL_IDENTIFIER '}' # Column // 表格列 ; # 词法 //函数名 ABS :[aA][bB][sS]; //绝对值 MAX :[mM][aA][xX]; //返回一组值中的最大值。MAX(number1, [number2], ...) MIN :[mM][iI][nN]; //返回一组值中的最小值。MIN(number1, [number2], ...) SUM :[sS][uU][mM]; //返回一组值的和。SUM(number1, [number2], ...) INTEGER :[0-9]+; //整数,包含正整数、负整数、零 FLOAT :[0-9]* '.' INTEGER+; //浮点数,包含正浮点数,负浮点数、零(0.0) STRING // 字符串类型 : '\'' ( ~('\''|'\\') | ('\\' .) )* '\'' ; WHITE_SPACE : [ \t\r]+ -> skip ; //空白定义,可以是空格、制表符,换行符 MUL_IDENTIFIER : IDENTIFIER '.' IDENTIFIER ; IDENTIFIER //数字、字母以及下划线表示的标识符 : (LETTER | DIGIT | '_')+ ; DIGIT //数字 : [0-9] ; LETTER //字母 : [A-Z] | [a-z] ;
实现 Visitor
编译之后,我们来写 FormulaVisitorImpl。
public class FormulaVisitorImpl extends FormulaBaseVisitor<Function> { /** * 访问 ➕ 节点调用 * 返回值为我们的 Function 类型 实现比较简单 */ @Override public Function visitAdd(FormulaParser.AddContext ctx) { AddFunction addFunction = new AddFunction(); List<Function> argList = new ArrayList<>(); argList.add(visit(ctx.expr(0))); argList.add(visit(ctx.expr(1))); addFunction.setArgList(argList); return addFunction; } /** * 访问整数节点调用 */ @Override public Function visitInteger(FormulaParser.IntegerContext ctx) { ConstantFunction constantFunction = new ConstantFunction(); constantFunction.setValueType(ValueTypeEnum.NUMBER); constantFunction.setValue(Integer.valueOf(ctx.getText())); return constantFunction; } }
继续写其他的 Visit 逻辑。
完成解析
ANTLRInputStream stream = new ANTLRInputStream("1 + 1"); FormulaLexer lexer = new FormulaLexer(stream); CommonTokenStream tkn = new CommonTokenStream(lexer); FormulaParser parser = new FormulaParser(tkn); ParseTree tree = parser.expr(); FormulaVisitorImpl visitor = new FormulaVisitorImpl(); Function ans = visitor.visit(tree); System.out.println(ans.toExpression());
本文比较简略,仅为思路整理。ANTLR 的细节用法(手把手教程),请看上一篇文章:ANTLR4入门