深入解析DoctorWkt/acwj项目:更多运算符的实现

深入解析DoctorWkt/acwj项目:更多运算符的实现

acwj A Compiler Writing Journey acwj 项目地址: https://gitcode.com/gh_mirrors/ac/acwj

本文是DoctorWkt/acwj项目系列教程的第21部分,我们将深入探讨如何在该项目中实现更多C语言运算符。通过本文,你将了解编译器如何处理各种运算符,包括自增/自减、位运算和逻辑运算等。

运算符概述

在本部分实现中,我们主要关注以下几类运算符:

  1. 自增/自减运算符++--,包括前缀和后缀形式
  2. 一元运算符-(负号)、~(按位取反)和!(逻辑非)
  3. 二元运算符^(异或)、&(按位与)、|(按位或)、<<(左移)和>>(右移)
  4. 隐式非零运算符:在条件语句中自动将表达式结果转换为布尔值

词法分析扩展

首先需要为这些新运算符定义对应的token类型:

enum {
  // 二元运算符
  T_LOGOR, T_LOGAND, T_OR, T_XOR, T_AMPER,
  T_LSHIFT, T_RSHIFT,
  
  // 其他运算符
  T_INC, T_DEC, T_INVERT, T_LOGNOT,
  ...
};

词法分析器需要能够识别这些运算符,特别是要注意区分相似的符号组合,如<<<<=等。

语法分析与AST节点

在语法分析阶段,我们需要将这些运算符映射到AST节点类型:

enum {
  // 二元运算符节点
  A_LOGOR, A_LOGAND, A_OR, A_XOR, A_AND,
  A_LSHIFT, A_RSHIFT,
  
  // 一元运算符节点
  A_PREINC, A_PREDEC, A_POSTINC, A_POSTDEC,
  A_NEGATE, A_INVERT, A_LOGNOT,
  ...
};

同时需要定义运算符的优先级,确保表达式按照正确的顺序求值:

static int OpPrec[] = {
  0, 10, 20, 30,                // T_EOF, T_ASSIGN, T_LOGOR, T_LOGAND
  40, 50, 60,                   // T_OR, T_XOR, T_AMPER
  ...
};

前缀与后缀表达式处理

根据C语言的BNF文法,我们需要分别处理前缀和后缀表达式:

  1. 前缀表达式:包括一元运算符和前置自增/自减
  2. 后缀表达式:主要包括后置自增/自减

在实现中,我们通过prefix()postfix()函数来处理这两种情况。特别需要注意的是:

  • 前缀&运算符需要将表达式视为左值
  • 前缀-~!运算符需要将表达式视为右值
  • 自增/自减运算符要求操作数必须是左值

布尔值转换

C语言允许在条件语句中使用非布尔表达式,编译器需要自动将其转换为布尔值。我们通过引入A_TOBOOLAST节点类型来实现这一功能:

// 在解析条件语句时
condAST = binexpr(0);
if (condAST->op < A_EQ || condAST->op > A_GE)
  condAST = mkastunary(A_TOBOOL, condAST->type, condAST, 0);

代码生成

在代码生成阶段,我们需要为每种新运算符生成对应的x86-64汇编指令:

  1. 位运算:直接使用对应的汇编指令

    int cgand(int r1, int r2) {
      fprintf(Outfile, "\tandq\t%s, %s\n", reglist[r1], reglist[r2]);
      free_register(r1); return (r2);
    }
    
  2. 移位运算:需要先将移位量存入%cl寄存器

    int cgshl(int r1, int r2) {
      fprintf(Outfile, "\tmovb\t%s, %%cl\n", breglist[r2]);
      fprintf(Outfile, "\tshlq\t%%cl, %s\n", reglist[r1]);
      free_register(r2); return (r1);
    }
    
  3. 布尔运算:使用test指令结合条件设置指令

    int cglognot(int r) {
      fprintf(Outfile, "\ttest\t%s, %s\n", reglist[r], reglist[r]);
      fprintf(Outfile, "\tsete\t%s\n", breglist[r]);
      fprintf(Outfile, "\tmovzbq\t%s, %s\n", breglist[r], reglist[r]);
      return (r);
    }
    
  4. 自增/自减运算:需要同时处理内存加载和值修改

    // 以char类型为例
    if (op == A_PREINC)
      fprintf(Outfile, "\tincb\t%s(\%%rip)\n", Gsym[id].name);
    fprintf(Outfile, "\tmovzbq\t%s(%%rip), %s\n", Gsym[id].name, reglist[r]);
    if (op == A_POSTINC)
      fprintf(Outfile, "\tincb\t%s(\%%rip)\n", Gsym[id].name);
    

实现思考

在实现这些运算符时,有几个关键设计决策值得注意:

  1. AST节点设计:选择创建新的AST节点类型而不是修改现有节点结构,保持了代码的清晰性
  2. 运算符优先级:参考C语言标准确保正确的求值顺序
  3. 类型处理:特别是对有符号和无符号类型的正确处理
  4. 左值/右值区分:确保运算符应用于正确的表达式类型

总结

通过本部分的实现,我们的编译器现在支持了C语言中大多数常用的运算符。这些实现不仅增加了语言的功能性,也为后续更复杂的语言特性打下了基础。在实现过程中,我们特别注重:

  1. 保持代码结构的清晰和可扩展性
  2. 正确处理各种边界情况
  3. 生成高效的机器代码
  4. 为未来的扩展预留空间

这些运算符的实现是编译器开发中的重要里程碑,它们使得我们的编译器能够处理更加复杂和实用的C语言代码。

acwj A Compiler Writing Journey acwj 项目地址: https://gitcode.com/gh_mirrors/ac/acwj

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

凤红令Nathania

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

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

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

打赏作者

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

抵扣说明:

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

余额充值