导航
前言
做类似 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入门
