上一篇到了转换器,能把表达式转换为不考虑优先级的AST树。这篇就要考虑表达式优先级,得到按优先级的AST树执行。
先为每个操作符定义优先级,然后op_precedence实现令牌类型转换优先级方法方便使用
//定义每个操作的优先级
// Operator precedence for each token
static int OpPrec[] = { 0, 10, 10, 20, 20, 0 };
//得到令牌类型的优先级
// Check that we have a binary operator and
// return its precedence.
static int op_precedence(int tokentype) {
int prec = OpPrec[tokentype];
if (prec == 0) {
fprintf(stderr, "syntax error on line %d, token %d\n", Line, tokentype);
exit(1);
}
return (prec);
}
// Structure and enum definitions
// Copyright (c) 2019 Warren Toomey, GPL3
// Token types
enum {
T_EOF, T_PLUS, T_MINUS, T_STAR, T_SLASH, T_INTLIT
};
// Token structure
struct token {
int token; // Token type, from the enum list above
int intvalue; // For T_INTLIT, the integer value
};
// AST node types
enum {
A_ADD, A_SUBTRACT, A_MULTIPLY, A_DIVIDE, A_INTLIT
};
然后就是用pratt算法递归沉降按优先级得到AST树,费解的就是普拉特算法,拿一个公式解释执行过程
//2*3+4*5
/*
先读入2
2
--------------------------------------
*优先级大于2进while循环
---------------------------------------
*
/ \
2 未知的子树,递归调用binexpr传入+优先级
----------------------------------------
3的优先级小于*,第一轮递归没进while循环自己返回3,left=*的节点,
此时token在3,tokentype是3的优先级大于传的0接着while循环
----------------------------------------
*
/ \
2 3
-----------------------------------------
第二轮while循环读到+,然后传入+优先级递归binexpr得到右节点
-----------------------------------------------
*
/ \
* 递归方法得到的右树
/ \
2 3
----------------------------------------------------
此论递归先读到4为left节点,然后*大于4优先级进入while循环,把4当左节点*当中间节点和再递归的右节点返回
-----------------------------------------------------
*
/ \
* *
/ \ / \
2 34 递归的右节点
-----------------------------------------------------
然后读到5结束就得到下面的这个AST树
+
/ \
/ \
/ \
* *
/ \ / \
2 3 4 5
*/
/*
普拉特算法
prefix:表达式前缀
infix:表达式操作符
Pratt Parsing是一种循环与递归相结合的算法。
解析一个表达式(parseExp)的过程大概是这样:
吃一个token作为prefix,调用这种token对应的prefixParselet,
得到left(已经构建好的表达式节点)prefixParselet递归调用parseExp,
解析自己需要的部分,
构建表达式节点while循环瞥一眼token作为infix,
仅当它的优先级足够高,才能继续处理。
否则函数return left吃掉infix token,
调用这种token对应的infixParselet,
将left传给它infixParselet递归调用parseExp,
解析自己需要的部分,构建表达式节点得到新的left
*/
//返回AST树,从最低优先级0开始
// Return an AST tree whose root is a binary operator.
// Parameter ptp is the previous token's precedence.
struct ASTnode *binexpr(int ptp) {
struct ASTnode *left, *right;
int tokentype;
//得到左节点
// Get the integer literal on the left.
// Fetch the next token at the same time.
left = primary();
// If no tokens left, return just the left node
tokentype = Token.token;
//如果结束就返回
if (tokentype == T_EOF)
return (left);
//扫描的令牌优先级大于传入优先级就循环找
// While the precedence of this token is
// more than that of the previous token precedence
while (op_precedence(tokentype) > ptp) {
//扫描下一个令牌
// Fetch in the next integer literal
scan(&Token);
// Recursively call binexpr() with the
// precedence of our token to build a sub-tree
right = binexpr(OpPrec[tokentype]);
// Join that sub-tree with ours. Convert the token
// into an AST operation at the same time.
left = mkastnode(arithop(tokentype), left, right, 0);
// Update the details of the current token.
// If no tokens left, return just the left node
tokentype = Token.token;
if (tokentype == T_EOF)
return (left);
}
// Return the tree we have when the precedence
// is the same or lower
return (left);
}
执行考虑优先级的AST树得到结果
// Main program: check arguments and print a usage
// if we don't have an argument. Open up the input
// file and call scanfile() to scan the tokens in it.
void main(int argc, char *argv[]) {
struct ASTnode *n;
if (argc != 2)
usage(argv[0]);
init();
if ((Infile = fopen(argv[1], "r")) == NULL) {
fprintf(stderr, "Unable to open %s: %s\n", argv[1], strerror(errno));
exit(1);
}
//到开始
scan(&Token); // Get the first token from the input
//得到考虑优先级的AST树
n = binexpr(0); // Parse the expression in the file
//执行树
printf("%d\n", interpretAST(n)); // Calculate the final result
exit(0);
}
编译测试
[zhanglianzhu@zlzlinux 03_Precedence]$
[zhanglianzhu@zlzlinux 03_Precedence]$ make
cc -o parser -g expr.c interp.c main.c scan.c tree.c
[zhanglianzhu@zlzlinux 03_Precedence]$ ls
data.h expr2.c input02 input05 Makefile scan.c
decl.h expr.c input03 interp.c parser tree.c
defs.h input01 input04 main.c Readme.md
[zhanglianzhu@zlzlinux 03_Precedence]$ cat inpu01
cat: inpu01: 没有那个文件或目录
[zhanglianzhu@zlzlinux 03_Precedence]$ cat input01
2*3+4*5
[zhanglianzhu@zlzlinux 03_Precedence]$ ./parser input01
26
[zhanglianzhu@zlzlinux 03_Precedence]$
以上就是C编译器学习第三篇,重要理解pratt算法