构建 语法树 来解析 数学表达式
本文主要讲如何把一个数学表达式转化成语法树,并通过语法树来解出结果。
引入
这个主要是用来学习语法树的构建,数学表达式只是一个引子,本项目可以引申到 HTTP 请求报文解析,SQL语句的解析的诸如此类的拥有固定规则的字符串的解析。
思考
我们想想,对于 1 + 2 * 3
这个表达式,它的值是7。但是如果你拿到的是一串字符串,那么你要如何用C ++这样的语言来解析呢?首先,这是一个所谓的 “中缀” 符号。还有前缀和后缀表示法。术语“中缀”,“前缀”和“后缀”指的是与操作数相关的运算符的位置:
- 前缀:运算符 操作数1 操作数2(例如:+ 1 2)
- 中缀:操作数1 运算符 操作数2(例如:1 + 2)
- 后缀:操作数1 操作数2 运算符(例如:1 2 +)
但是很明显,我们没有办法使用程序通过中序遍历做什么,因为表达式里通常包含优先级的运算,这使得中序遍历并不能提前做什么操作。
因此,我们必须借助别的工具来辅助,常用的方式有两种
- 逆波兰表示法(RPN)
- 抽象语法树(AST)
但是,对于数学表达式来说,两种方法都可以做到,但是对于别的句式进行解析,逆波兰表达式就显得不那么方便了。因此我们这里通过建立抽象语法树来对数学表达式进行解析。
语法定义
首先我们定义出一种递归的语法
EXP -> EXP + EXP | EXP - EXP | EXP * EXP | EXP / EXP |
- EXP | ( EXP ) | number | sin( EXP ) | cos( EXP )
但是很显然,这种方式并不能体现表达式的优先级,因此我们改进语法:
EXP -> EXP + TERM |
EXP - TERM |
TERM
TERM -> TERM * FACTOR |
TERM / FACTOR |
FACTOR
FACTOR -> ( EXP ) | - EXP | number |
sin( EXP ) | cos( EXP )
现在这种语法是可以表示出表达式的优先级,但是还有一个问题,这种语法是一个左递归的语法,因此我们还需要对其进行改进:
EXP -> TERM EXP1
EXP1 -> + TERM EXP1 |
- TERM EXP1 |
null
TERM -> FACTOR TERM1
TERM1 -> * FACTOR TERM1 |
/ FACTOR TERM1 |
null
FACTOR -> ( EXP ) | - EXP | number |
sin( EXP ) | cos( EXP )
代码实现
我们这里将使用 Java 进行编写示例,但是你可以把它翻译成任何语言的代码
Parser类定义:
public class Parser {
private Token m_crtToken;
private final String m_Text;
private int m_Index;
private Parser(String str) {
...}
public static ASTNode parse(String expr) {
...}
private ASTNode Expression() {
...}
private ASTNode Expression1() {
...}
private ASTNode Term() {
...}
private ASTNode Term1() {
...}
private ASTNode Factor() {
...}
private void Match(char expected) {
...}
private void SkipWhitespaces() {
...}
private void GetNextToken() {
...}
private double GetNumber() {
...}
private boolean isSpace(char ch) {
...}
private boolean isDigit(char ch) {
...}
}
其中
- ASTNode 为二叉树的一个节点
- SkipWhitespaces() 为扫描字符串时跳过所有空格的函数
- Match(char) 为下一个字符是否与传入字符匹配
- GetNextToken() 为获取下一个元素类型
完整代码
Parser.java
// Parser.java
package expr_parser;
public class Parser {
private Token m_crtToken;
private final String m_Text;
private int m_Index;
public Parser(String str) {
m_Text = str + "#";
m_Index = 0;
m_crtToken = new Token();
}
public static ASTNode parse(String expr) throws ParserException {
Parser parser = new Parser(expr);
parser.GetNextToken();
return parser.Expression();
}
private ASTNode Expression() throws ParserException {
ASTNode t_node = Term();
ASTNode e1_node = Expression1();
return new ASTNode(ASTNodeType.OPERATOR_PLUS, 0, t_node, e1_node);
}
private ASTNode Expression1() throws ParserException {
ASTNode t_node;
ASTNode e1_node;
switch (m_crtToken.type) {
case PLUS:
GetNextToken();
t_node = Term();
e1_node = Expression1();
return new ASTNode(ASTNodeType.OPERATOR_PLUS