C编译器03-普拉特算法

上一篇到了转换器,能把表达式转换为不考虑优先级的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算法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小乌鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值