cucu: a compiler u can understand (part 2)

2023-11-09

原文地址: http://blog.csdn.net/roger__wong/article/details/8502477

原文地址:http://zserge.com/blog/cucu-part2.html

到目前为止,我们已经定义了我们语言的语法并编写了一个词法分析器。在本篇文章中,我们将为我们的语言写解析器。但在开始之前,我们先需要一些辅助函数:

int peek(char *s) {
    return (strcmp(tok, s) == 0);
}

int accept(char *s) {
    if (peek(s)) {
        readtok();
        return 1;
    }
    return 0;
}

int expect(char *s) {
    if (accept(s) == 0) {
        error("Error: expected '%s'\n", s);
    }
}

peek() 函数若下一个符号与传入的字符串相等,则返回非零值。 accept()函数读取下一个符号,如果其与传入参数相同,否则返回0。expect() h帮助我们检查语言的语法。

较为困难的部分

从语言的语法中我们可以得知,语句和表达式是相互掺杂在一起的。因此这就意味着一旦我们开始写解析器,我们必须时刻记住这些递归生成规则。让我们从顶至底来进行分析。下面是最高层的函数compiler():

static int typename();
static void statement();

static void compile() {
    while (tok[0] != 0) { /* until EOF */
        if (typename() == 0) {
            error("Error: type name expected\n");
        }
        DEBUG("identifier: %s\n", tok);
        readtok();
        if (accept(";")) {
            DEBUG("variable definition\n");
            continue;
        } 
        expect("(");
        int argc = 0;
        for (;;) {
            argc++;
            typename();
            DEBUG("function argument: %s\n", tok);
            readtok();
            if (peek(")")) {
                break;
            }
            expect(",");
        }
        expect(")");
        if (accept(";") == 0) {
            DEBUG("function body\n");
            statement();
        }
    }
}

这个函数首先尝试读取类型名,其次是标识符。如果在此之后紧跟一个分号,则说明是一个变量声明。如果跟着括号,说明是一个函数。如果是函数,就接着去逐一搜索参数,再次之后如果没有分号,则说明是一个函数定义(有函数体),否则就只是一个函数声明(只有函数名和类型)。

这里, typename() 是一个用来让我们跳过类型名的函数。 我们指接受int类型、char类型以及其指针(char*):

static int typename() {
    if (peek("int") || peek("char")) {
        readtok();
        while (accept("*"));
        return 1;
    }
    return 0;
}

最有趣的大概就是 statement() 函数了。它可以分析一个单独的语句,而这个语句可以是一个块、一个局部变量的定义/声明、一个return语句等。

现在让我们来看看它的样子:

static void statement() {
    if (accept("{")) {
        while (accept("}") == 0) {
            statement();
        }
    } else if (typename()) {
        DEBUG("local variable: %s\n", tok);
        readtok();
        if (accept("=")) {
            expr();
            DEBUG(" :=\n");
        }
        expect(";");
    } else if (accept("if")) {
        /* TODO */
    } else if (accept("while")) {
        /* TODO */
    } else if (accept("return")) {
        if (peek(";") == 0) {
            expr();
        }
        expect(";");
        DEBUG("RET\n");
    } else {
        expr();
        expect(";");
    }
}

如果遇到的是一个“块”,即{...}的部分,就继续尝试在块中解析语句直到块结束。如果以变量名开头,则说明是一个局部变量的定义。条件语句(if/then/else)和循环语句在这里没有列出,留给读者去思考根据我们的语法,这些部分应当如何去实现。

当然,大部分语句里都包含着表达式,因此我们需要写一个函数取分析表达式。表达式解析器是一个向下递归的解析器,因此很多的表达式解析函数会互相调用直到找到主表达式为止。所谓的主表达式,根据我们的语法,是指一个数字(常量)或者一个标识符(变量或者函数)。

static void prim_expr() {
    if (isdigit(tok[0])) {
        DEBUG(" const-%s ", tok);
    } else if (isalpha(tok[0])) {
        DEBUG(" var-%s ", tok);
    } else if (accept("(")) {
        expr();
        expect(")");
    } else {
        error("Unexpected primary expression: %s\n", tok);
    }
    readtok();
}

static void postfix_expr() {
    prim_expr();
    if (accept("[")) {
        expr();
        expect("]");
        DEBUG(" [] ");
    } else if (accept("(")) {
        if (accept(")") == 0) {
            expr();
            DEBUG(" FUNC-ARG\n");
            while (accept(",")) {
                expr();
                DEBUG(" FUNC-ARG\n");
            }
            expect(")");
        }
        DEBUG(" FUNC-CALL\n");
    }
}

static void add_expr() {
    postfix_expr();
    while (peek("+") || peek("-")) {
        if (accept("+")) {
            postfix_expr();
            DEBUG(" + ");
        } else if (accept("-")) {
            postfix_expr();
            DEBUG(" - ");
        }
    }
}

static void shift_expr() {
    add_expr();
    while (peek("<<") || peek(">>")) {
        if (accept("<<")) {
            add_expr();
            DEBUG(" << ");
        } else if (accept(">>")) {
            add_expr();
            DEBUG(" >> ");
        }
    }
}

static void rel_expr() {
    shift_expr();
    while (peek("<")) {
        if (accept("<")) {
            shift_expr();
            DEBUG(" < ");
        }
    }
}

static void eq_expr() {
    rel_expr();
    while (peek("==") || peek("!=")) {
        if (accept("==")) {
            rel_expr();
            DEBUG(" == ");
        } else if (accept("!=")) {
            rel_expr();
            DEBUG("!=");
        }
    }
}

static void bitwise_expr() {
    eq_expr();
    while (peek("|") || peek("&")) {
        if (accept("|")) {
            eq_expr();
            DEBUG(" OR ");
        } else if (accept("&")) {
            eq_expr();
            DEBUG(" AND ");
        }
    }
}

static void expr() {
    bitwise_expr();
    if (accept("=")) {
        expr();
        DEBUG(" := ");
    }
}

上面是一大段的代码,但是不需要感到头疼,因为它们都简单的很。每一个分析表达式的函数首先都尝试调用一个更高优先级的表达式分析函数。接着,如果找到了这个函数期望的符号,则它继续调用高优先级的函数。然后当它分析完了一个二元表达式(如x+y、x&y、x==y)的两部分之后,就将值返回。有些表达式可以链式连接(如a+b+c+d),因此需要循环的分析它们。

我们在分析每一个表达式的时候都会输出一些调试信息,这些信息会给我们带来一些有趣的结果。例如,若我们分析以下代码片段:

int main(int argc, char **argv) {
    int i = 2 + 3;
    char *s;
    func(i+2, i == 2 + 2, s[i+2]);
    return i & 34 + 2;
}

我们将会得到如下的输出:

identifier: main
function argument: argc
function argument: argv
function body
local variable: i
 const-2  const-3  +  :=
local variable: s
 var-func  var-i  const-2  +  FUNC-ARG
 var-i  const-2  const-2  +  ==  FUNC-ARG
 var-s  var-i  const-2  +  []  FUNC-ARG
 FUNC-CALL
 var-i  const-34  const-2  +  AND RET

所有的表达式都会被写成逆波兰式(比如2+3变成23+)。而这对于有堆栈的计算机来说,是更为方便合理的形式,当操作数在栈顶的时候,函数能够执行出栈操作并取得操作数,之后将结果压栈。

虽然对于现在的以寄存器为基础的CPU,这或许不是一个最优的方法,但这个方法很简单并且能够满足我们编译器的需要。


symbols

现在,我们已经完成了很多工作了,我们使用不到300行的代码写了一个词法分析器和解析器。接下来我们要做的事情是添加以下函数,以便让这些符号(比如变量名、函数名)能够正确的工作。一个编译器应该有一个符号表以便能够很快的找到这些符号的地址,所以当你在代码中写“i=0"的时候,实际上你是将0这个值放入了内存的0x1234的位置(假设变量i在内存的位置就是0x1234)。相似的,当我们调用函数”func()"时,实际上做的是跳转到0x5678继续执行而已(假设func这个符号的的值是0x5678)。

我们需要一个数据结构来存放符号:

struct sym {
    char type;
    int addr;
    char name[];
};

这里type 有不同的含义。 我们用一个单独的字母来标示不同的类型:

  • L - 局部变量。 addr 存储变量在堆栈里的地址
  • A - 函数参数。 addr 存储参数在堆栈里的地址
  • U - 未定义的全局变量。 addr 存储其在内存中的绝对地址。
  • D - 定义过的全局变量。 其余同上。

So far, I've added two functions: sym_find(char *s) to find symbol by its name, andsym_declare() to add a new symbol.

到此为止,我们还需要增加两个函数:: sym_find(char *s) 来根据符号名查找符号, sym_declare() 来加入一个新的符号。

现在我们已经可以去设计后端的架构了,详情见下篇文章。

如果你忘了前面的信息你可以到part1部分去查阅。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

cucu: a compiler u can understand (part 2) 的相关文章

  • 电子科技大学编译原理复习笔记(八):语义分析

  • assemble language学习(-)

    不容易 终于将第一个简单的arm assemble Language程序跑通了 1 创建project 选择stm32407ve 2 添加汇编启动文件start s STACK TOP EQU 0x20002000 AREA RESET C
  • LLVM Language Reference Manual

    摘要 该文档是LLVM汇编语言的参考指南 LLVM是基于表示的静态单赋值 SSA 该表示提供类型安全 低层级操作 灵活性 及简洁表示所有高层级语言的能力 这是贯穿各方面LLVM编译策略的通用代码表示 简介 LLVM代码表示用于三个不同形式
  • 编译原理实验 实验一 词法分析设计 Java实现

    一 实验目的 通过本实验的编程实践 使学生了解词法分析的任务 掌握词法分析程序设计的原理和构造方法 使学生对编译的基本概念 原理和方法有完整的和清楚的理解 并能正确地 熟练地运用 二 实验内容 用 VC VB JAVA 语言实现对 C 语言
  • 编译原理书籍推荐

    大学课程为什么要开设编译原理呢 这门课程关注的是编译器方面的产生原理和技术问题 似乎和计算机的基础领域不沾边 可是编译原理却一直作为大学本科的必修课程 同时也成为了研究生入学考试的必考内容 编译原理及技术从本质上来讲就是一个算法问题而已 当
  • Flex&Bison 简单入门

    Flex Bison 简单入门 Ref flex与bison 中文版 1 Flex Bison安装 安装flex sudo apt install flex 安装bison sudo apt install bison 安装gcc 若缺少
  • 编译原理笔记

    目录 序章 编译原理 编译器 程序设计语言 第一章 概述 机器语言 第一代语言 特点 汇编语言 高级程序设计语言 鼻祖 时期 特点 翻译程序 汇编语言 解释语言 编译程序 编译过程 词法分析 语法分析 语义分析 中间代码生成 之前三步都是编
  • 【编译原理】- 递归下降的语法分析器的实现

    目录 一 实验题目 二 分析与设计 三 源代码 一 实验题目 编写识别由下列文法G E 所定义的表达式的递归下降语法分析器 E E T E T T T T F T F F F E i 输入 含有十进制数或十六进制数的表达式 如 75 1ah
  • 用 Rust 编写一个简单的词法分析器

    词法分析是编译器和解释器的第一个环节 相对而言也比较简单 词法分析器 有时也称为单元生成器 tokenizer 或者扫描器 scanner 将源代码转换为词法单元 这个过程称为词法分析 本文代码设计复刻自 用Go语言自制解释器 词法分析器一
  • gcc常用选项及常见的文件格式,扩展名

    gcc常用选项 编译过程 预处理 编译 汇编 链接 gcc的选项 必须分开给出 x 语言名 指出后面文件的语言 c 编译 汇编源文件 生成目标文件 S 编译不汇编 生成汇编文件 E 预处理 输出送到标准输出 o 指定输出的文件名 pipe
  • 吉首大学_编译原理实验题_基于预测方法的语法分析程序的设计【通过代码】

    一 实验要求 实验二 基于预测方法的语法分析程序的设计 一 实验目的 了解预测分析器的基本构成及用自顶向下的预测法对表达式进行语法分析的方法 掌握预测语法分析程序的手工构造方法 二 实验内容 1 了解编译程序的基于预测方法的语法分析过程 2
  • C++ 实现自动产生LR1分析器的产生器

    C 实现自动产生LR1分析器的产生器 1 介绍 2 总体思路 2 1 拓广文法 2 2 计算First集合 2 3 计算每个闭包的项目集以及GO函数 2 4 计算分析表的动作函数ACTION和状态转换函数GOTO 2 5 对语句进行语法分析
  • 简单的递归下降语法分析程序

    简单递归分析程序 其代码如下 include
  • 编译原理基础知识+笔记(1)

    一 编译原理概述 1 翻译程序 把某一种语言程序 称为源语言程序 等价地转换成另一种语言程序 称为目标语言程序 的程序 2 编译程序 把某一种高级语言程序等价地转换成另一种低级语言程序 如汇编语言或机器语言程序 的程序 又分为 诊断编译程序
  • cucu: a compiler you can understand (part 1)

    原文地址 http blog csdn net roger wong article details 8498591 译者序 最近在学习一些编译器的基本知识 就找到了这篇英文的博客 在csdn搜了一下貌似没有人翻译 所以我干脆翻译了算了 反
  • 编译原理实验二:Bison

    编译原理实验二 Bison 实验要求 1 了解Bision基础知识 如何将文法产生式转换为Bison语句 2 阅读 src common SyntaxTree c 对应头文件 include SyntaxTree h 理解分析树生成的过程
  • 编译原理实验一:词法分析

    实验一 词法分析程序 一 实验目的 通过设计编制调试一个具体的词法分析程序 加深对词法分析原理的理解 并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法 编制一个读单词过程 从输入的源程序中 识别出各个具有独立意义的
  • 编译原理绪论

    1 美图 5 编译过程一语法分析 任务 在词法分析的基础上 根据语言的语法规则把单词符号串分解成各类语法单位 语法范畴 依循的原则 语法规则 描述工具 上下文无关文法 6 编译过程一中间代码产生 任务 对各类不同语法范畴按语言的语义进行初步
  • 编译原理(4)LR(0)语法分析程序设计(Python实现)

    1 实验要求 1 已知文法G S 0 S E 1 E aA 2 E bB 3 A cA 4 A d 5 B cB 6 B d 手工建立文法G S 的LR 0 的项目集规范族DFA和LR 0 分析表 2 根据清华大学版 编译原理 第3版 教材
  • Compiler- volatile关键字

    为了直观的感受编译器为程序所做的编译优化 我们通过以下的C 程序来进行演示 只能体现编译优化的一小部分hh 请大家预测一下下面代码的输出结果 include

随机推荐

  • 爬虫工作中代理失效了怎么处理?

    Hey 亲爱的爬虫小伙伴们 是不是经常在爬虫的工作中遇到代理IP失效的问题 别着急 今天我来分享一些应对代理失效的妙招 这些方法简单易行 让你爬虫顺利进行 一 为什么代理会失效 在爬虫过程中 使用代理IP是常见的手段 它可以帮助我们隐藏真实
  • Could not find a declaration file for module 'vue-xxx'.

    我尝试添加到项目中的任何第三方Vue js库都会引发以下错误 Could not find a declaration file for module vue xxx Could not find a declaration file fo
  • MySQL 中的共享锁、排他锁与意向锁

    共享锁 Share Lock 共享锁又称读锁 简称 S 锁 一个事务获取了一个数据行的共享锁 其他事务能获得该行对应的共享锁 但不能获得排他锁 即一个事务在读取一个数据行的时候 其他事务可以并发读取数据 但不能对该数据行进行增删改 直到已释
  • 【Mybatis】Mybatis的介绍以及使用

    Mybatis的介绍以及使用 https www cnblogs com kenhome p 7764398 html resultMap的用法以及关联结果集映射 https blog csdn net qq 42780864 articl
  • N1盒子刷机经验分享

    小白入坑N1经验分享 n1的玩法很多 价格也很实惠 所以前几天也入手了一个 但是对这个小盒子是一无所知 完全摸不着方向 整天在恩山逛 恩山大佬很多 干货也很多 因此我折腾了几天 反复看了几个精品帖 算是有了点体会 但是 大佬们分享的帖子虽然
  • catalina 无法验证macos_macOS 10.15 Catalina无法打开app,提示“因为无法确认开发者身份”问题的解决方法......

    概述 本文最后更新 2020年5月4日 不少用户升级到macOS Catalina 10 15之后 遇到了网上下载的app无法运行的问题 出现以下几种提示 无法打开 xxx 因为无法确认开发者的身份 xxx 已损坏 无法打开 您应该将它移到
  • Java比较器

    一 Java比较器的概述 1 为什么要使用比较器 当java涉及到数组排序时 就会使用到比较器 import java util Arrays public class ComparableTest1 public static void
  • Java常见问题(1)navicat连接mysql报2059错误

    一 navicat连接mysql8后出现2059报错原因 使用navicat连接mysql数据库的时候 弹出2059错误 如下图所示 出现的原因是mysql8安装选择了强加密规则caching sha2 password 而mysql8之前
  • 买了一年CSDN年VIP,用着实在太爽!

    买一年CSDN的年VIP有多爽及使用攻略 一 前言 这段时间 一旦打开CSDN就不断的弹出618活动 在电脑网上打开 一股白嫖之的气息吹来 让人直接忍不住剁手 最后经过近5天的挣扎 我还是受不了CSDN的蛊惑 618不买衣服不买裤子 不买键
  • 链接器工具错误 LNK2005———— 符号 被定义了多次。

    出错函数为void BinaryTree test 如下 1 该函数在BInarySearchTree h中声明如下 void BinaryTree test 2 该函数在BInarySearchTree cpp中定义 如下 void Bi
  • 2021美赛A题

    2021百万 问题A 真菌 碳循环描述了整个地球化学过程中碳的交换过程 是地球生命的重要组成部分 碳循环的一部分包括 化合物的分解 使碳得以更新并以其他形式使用 一键 这一过程的组成部分是植物材料和木质纤维的分解 分解木质纤维的一些关键因素
  • 学习笔记(Putty使用指南)

    下载 Putty是用来远程连接服务器的 支持SSH Telnet Serial等协议的连接 其中最常用的是SSH 下载链接 链接 https pan baidu com s 1RpJCgizhzxBvc7VVdYxDFg 提取码 4v23
  • valgrind massif 分析内存问题

    旧博文 搬到 csdn 原文 http rebootcat com 2020 06 16 valgrind massif memory analysing Valgrind Massif valgrind 是什么 这里直接引用其他人的博客
  • 戴尔服务器重装系统的方法,Dell服务器安装操作系统四种方法.doc

    Dell服务器安装操作系统四种方法 doc Dell服务器安装操作系统四种方法PowerEdge服务器手动安装操作系统 适合有软驱软盘 硬盘有数据要保留的用户 Dell PowerEdge 1950 服务器 Windows 2003安装手册
  • 优美的讲解equals和==的区别

    初步了解在JVM中的内存分配知识 在JVM中 内存分为堆内存跟栈内存 他们二者的区别是 当我们创建一个对象 new Object 时 就会调用对象的构造函数来开辟空间 将对象数据存储到堆内存中 与此同时在栈内存中生成对应的引用 当我们在后续
  • 【Python】Stegano包:一个纯Python的隐写模块,提供不同的隐写和隐写分析方法

    文章目录 一 介绍 二 安装 三 使用 Stegano 作为 Python 模块 3 1 LSB method 3 2 LSB method with sets 3 3 图片的描述字段 一 介绍 隐写术是一门以这样的方式编写隐藏消息的艺术和
  • Type ‘string‘ is not assignable to type ‘“xx1“

    新手常遇typescript类型约束报错 是宽泛的字符串约束和具体值的类型约束的问题 使用as const 解决 原理详细如下 https blog csdn net weixin 43263355 article details 1209
  • java 对象多属性排序_java – 按多个属性对对象排序

    我一直在研究一些需要我按三个属性 软件 str 颜色 str 和体积 int 对物体 软饮料 进行分类的东西 我已经研究过 并找到了通过名称 颜色和体积分别订购它们的方法 但有没有办法按三种方式订购它们 我的意思是 例如 假设有四个Soft
  • 用OpenCV-Python制作灯光秀短视频

    老猿Python博文目录 https blog csdn net LaoYuanPython 用OpenCV Python读取摄像头写入视频文件 一 引言 在 https blog csdn net LaoYuanPython articl
  • cucu: a compiler u can understand (part 2)

    原文地址 http blog csdn net roger wong article details 8502477 原文地址 http zserge com blog cucu part2 html 到目前为止 我们已经定义了我们语言的语