跳至主要內容

重庆大学编译原理2022教改项目

AI悦创原创Python 一对一教学重庆大学编译原理一对一教学Python 一对一教学重庆大学编译原理一对一教学大约 8 分钟...约 2489 字

1. 实验方案

本次实验将实现一个由 SysY (精简版 C 语言,来自 https://compiler.educg.net/open in new window) 翻译至 RISC-V 汇编的编译器,生成的汇编通过 GCC 的汇编器翻译至二进制,最终运行在模拟器 qemu-riscv 上

实验至少包含四个部分: 词法和语法分析、语义分析和中间代码生成、以及目标代码生成,每个部分都依赖前一个部分的结果,逐步构建一个完整编译器

实验一:词法分析和语法分析,将读取源文件中代码并进行分析,输出一颗语法树

实验二:接受一颗语法树,进行语义分析、中间代码生成,输出中间表示 IR (Intermediate Representation)

实验三:根据 IR 翻译成为汇编

实验四(可选):IR 和汇编层面的优化

2. 实验开始之前

本次实验将从零开始,写一个完整的编译器,编译 SysY 语言至 risc-v 汇编,并在 qemu-riscv 上跑起来

在开始之前,请不要抱有畏惧心理,我们准备好的了详细的文档、代码框架和构建方案、自动化测试程序

还有几个小建议:

  1. 在编写代码时,注意代码规范,避免出现低级但难以排查的错误甚至是奇奇怪怪的链接错误,请阅读 C++ coding styleopen in new window 和助教为你准备的 如果不遵守可能会出现各种奇奇怪怪问题的编码小技巧open in new window
  2. 在遇到 bug 时,请相信计算机一定不会出错,一定是代码的问题
  3. 在需要查找资料来解决问题时,请至少使用 Bingopen in new window 而不是百度,尽量查阅 Googleopen in new window, wikipediaopen in new window, stackoverflowopen in new window 等网站的资料,因为与编译原理相关的中文资料可能会非常少,英文资料相对丰富(以助教的经验来说 CSDN 不太能解决问题
  4. 上网查资料并不能解决所有的问题,他们的回答甚至有可能时错的!正确的做法应当是阅读官方文档,官方手册包含了查找对象的所有信息,关于查找对象的一切问题都可以在官方手册中找到答案。如果你需要了解如何使用 GDB,你应该阅读Debugging with GDBopen in new window,或者是你需要了解 riscv 的指令集定义、汇编规则,你应该到 riscv 官网中的技术手册open in new window 中寻找答案
  5. 在向助教和同学提问之前,请学习 提问的智慧open in new window,以更高效的解决问题

3. 实验一

3.1 实验目标

实验一将实现编译器前端的词法分析和语法分析部分,目标是分析输入的 源文件 得到一颗 抽象语法树

3.2 实验步骤

3.2.1 实验一标准输出

这是一段最简单的 SysY 程序

int main() {
    return 3;
}

实验一将把他解析为一颗语法分析树,我们用 json 来输出语法树,标准如下:

{
   "name" : "CompUnit",
   "subtree" : [
      {
         "name" : "FuncDef",
         "subtree" : [
            {
               "name" : "FuncType",
               "subtree" : [
                  {
                     "name" : "Terminal",
                     "type" : "INTTK",
                     "value" : "int"
                  }
               ]
            },
            {
               "name" : "Terminal",
               "type" : "IDENFR",
               "value" : "main"
            },
            {
               "name" : "Terminal",
               "type" : "LPARENT",
               "value" : "("
            },
            {
               "name" : "Terminal",
               "type" : "RPARENT",
               "value" : ")"
            },
            {
               "name" : "Block",
               "subtree" : [
                  {
                     "name" : "Terminal",
                     "type" : "LBRACE",
                     "value" : "{"
                  },
                  {
                     "name" : "BlockItem",
                     "subtree" : [
                        {
                           "name" : "Stmt",
                           "subtree" : [
                              {
                                 "name" : "Terminal",
                                 "type" : "RETURNTK",
                                 "value" : "return"
                              },
                              {
                                 "name" : "Exp",
                                 "subtree" : [
                                    {
                                       "name" : "AddExp",
                                       "subtree" : [
                                          {
                                             "name" : "MulExp",
                                             "subtree" : [
                                                {
                                                   "name" : "UnaryExp",
                                                   "subtree" : [
                                                      {
                                                         "name" : "PrimaryExp",
                                                         "subtree" : [
                                                            {
                                                               "name" : "Number",
                                                               "subtree" : [
                                                                  {
                                                                     "name" : "Terminal",
                                                                     "type" : "INTLTR",
                                                                     "value" : "3"
                                                                  }
                                                               ]
                                                            }
                                                         ]
                                                      }
                                                   ]
                                                }
                                             ]
                                          }
                                       ]
                                    }
                                 ]
                              },
                              {
                                 "name" : "Terminal",
                                 "type" : "SEMICN",
                                 "value" : ";"
                              }
                           ]
                        }
                     ]
                  },
                  {
                     "name" : "Terminal",
                     "type" : "RBRACE",
                     "value" : "}"
                  }
               ]
            }
         ]
      }
   ]
}

3.3 词法分析

词法分析的目的是读入外部的字符流(源程序)对其进行扫描,把它们组成有意义的词素序列,对于每个词素,词法分析器都会产生词法单元(Token) 作为输出

1. Token

Token 的定义在 token.h 中,同时 Token 类型的枚举类 TokenType 也定义在其中

struct Token {
    TokenType type;
    string value;
};

enum class TokenType{
    IDENFR,         // identifier    
    INTLTR,            // int literal
    FLOATLTR,        // float literal
    CONSTTK,        // const
    VOIDTK,            // void
    ... 
}

其中 string value 是 Token 所代表的字符串, TokenType type 是指 Token 的类型

2. DFA

在词法分析中,我们使用确定有限状态自动机 (deterministic finite automaton, DFA) 来进行分词,对于一个给定的属于该自动机的状态和一个属于该自动机字母表 Σ 的字符,它都能根据事先给定的 转移函数 转移到下一个状态,某些转移函数会进行输出

我们需要为词法分析设计这样一个 DFA:它可以接收输入字符,进行状态改变,并在某些转移过程中输出累计接受到的字符所组成的字符串

该 DFA 中应存在五种状态,我们用枚举类 State 来表示

enum class State {
    Empty,              // space, \n, \r ...
    Ident,              // a keyword or identifier, like 'int' 'a0' 'else' ...
    IntLiteral,         // int literal, like '1' '1900', only in decimal
    FloatLiteral,       // float literal, like '0.1'
    op                  // operators and '{', '[', '(', ',' ...
};

我们将 DFA 及其行为的抽象为类和类方法,定义在 lexical.h 中

struct DFA {
    /**
     * @brief take a char as input, change state to next state, and output a Token if necessary
     * @param[in] input: the input character
     * @param[out] buf: the output Token buffer
     * @return  return true if a Token is produced, the buf is valid then
     */
    bool next(char input, Token& buf);

    /**
     * @brief reset the DFA state to begin
     */
    void reset();

private:
    State cur_state;    // record current state of the DFA
    string cur_str;    // record input characters
};

其中 State cur_state 记录 DFA 当前的状态,string cur_str 记录 DFA 已经接受的字符串

每次字符输入都应调用 bool next(char input, Token& buf); 该函数是实现 DFA 的核心,即 转移函数 ,其根据自身当前状态和输入来决定转移后的状态,如果产生 Token 则返回 true

TODO: *实现 DFA 中的 bool next(char input, Token& buf) 函数和 void reset(); 函数*

3. Scanner

Scanner 是扫描器,其职责是将字符串输入转化为 Token 串,词法分析实际上就是实现一个 Scanner

Scanner 的定义在 lexical.h 中

struct Scanner {
    /**
     * @brief constructor
     * @param[in] filename: the input file  
     */
    Scanner(std::string filename); 

    /**
     * @brief run the scanner, analysis the input file and result a token stream
     * @return std::vector<Token>: the result token stream
     */
    std::vector<Token> run();

private:
    std::ifstream fin;  // the input file
};

其中 std::ifstream fin 是源文件打开得到的文件流

std::vector<Token> run(); 将执行分析过程,从文件流中读取字符,实例化一个 DFA 对象来获取 Token,但是我们的 DFA 只能识别代码部分,源文件中的注释输入 DFA 后并不能被正确的处理,我们需要在使用 DFA 接受字符串前对字符串进行预处理,所以 run() 的伪代码如下:

vector<Token> ret;
Token tk;
DFA dfa;
string s = preproccess(fin);    // delete comments
for(auto c: s) {
        if(dfa.next(c, tk)){
            ret.push_back(tk);
        }
}

TODO: 实现 Scanner 的 std::vector run() 函数

我们用一段最简单的 SysY 程序来展示词法分析的结果

int main() {
    return 3;
}

结果如下:

TokenTypeValue
INTTKint
IDENFRmain
LPARENT(
RPARENT)
LBRACE{
RETURNTKreturn
INTLTR3
SEMICN;
RBRACE}

词法分析部分到此结束

3.4 语法分析

1. SysY 文法

我们对 SysY 文法进行了一定的限制以减少难度,主要改变是同学们不需要支持二维以上的数组解析、不需要支持各种形式的浮点数字面量解析(不需要支持即我们在测试中不会出现这样的用例),并对左递归文法做了处理。新的文法请参考 文法定义。请注意,实现必须以该文法为准

2. 抽象语法树

抽象语法树(abstract syntax tree, AST) 是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构

我们知道文法中的一个 产生式 可以化为树的形式,如 FuncDef -> FuncType Ident '(' [FuncFParams] ')' Block 可以展开为以 FuncDef 为父节点,FuncType Ident '(' [FuncFParams] ')' Block 为从左至右子节点的一颗多叉树

为了之后语义分析的实现方便,我们为每一个非终结符定义一个类,以便不同的树节点可以拥有他们自己的成员变量,他们拥有共同的基类 AstNode,定义在 abstract_syntax_tree.h 中

struct AstNode{
    NodeType type;  // the node type
    AstNode* parent;    // the parent node
    vector<AstNode*> children;     // children of node

    /**
     * @brief Get the json output object
     * @param root: a Json::Value buffer, should be initialized before calling this function
     */
    void get_json_output(Json::Value& root) const;
}

struct CompUnit: AstNode {
struct Decl: AstNode{
struct FuncDef: AstNode{
struct ConstDecl: AstNode {
...

其中 NodeType 代表了树节点的类型,每个非终结符有自己的 NodeType,所有的终结符认为式 Terminal 类型,NodeType 枚举类定义在abstract_syntax_tree.h 中

公众号:AI悦创【二维码】

AI悦创·编程一对一

AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Web、Linux」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh

C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh

方法一:QQopen in new window

方法二:微信:Jiabcdefh

上次编辑于:
贡献者: AndersonHJB
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度