cucu: a compiler u can understand (part 2)

来源:互联网 发布:虚拟服务器端口设置 编辑:程序博客网 时间:2024/06/01 08:12
原文地址: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: mainfunction argument: argcfunction argument: argvfunction bodylocal 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部分去查阅。



原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 lol碰到嘴臭的怎么办 小婴儿脾气大怎么办呢? 玩游戏输入法会跳出出来怎么办 逆水寒fps太低怎么办 我dcj没地速怎么办 电焊看久眼睛疼怎么办 装修忘了窗帘盒怎么办? 纹眉导致眼肿了怎么办 哭泣引起的眼肿怎么办 在酒店忘记拉窗帘了怎么办 湿气重喉咙有痰怎么办 眼睛上火了肿了怎么办 陌陌直播没人看怎么办 陌陌直播没人气怎么办 我真的爱上你了怎么办 弯腰时间久了腰疼怎么办 斗鱼pk输的怎么办 领导当着人骂我怎么办 被老板骂了应该怎么办 三星s7关机键掉了怎么办 主播遇到黑粉怎么办 在工作单位突然死亡怎么办 孕7月半夜脚抽筋怎么办 上单对上两个射手怎么办 游戏本玩游戏掉帧怎么办 手机开直播很卡怎么办 小孩小鸡被虫子咬了怎么办 小鸡仔不吃食了怎么办 小鸡的腿瘸了怎么办 在境外住酒店钱被偷了怎么办 一加6屏幕辣眼睛怎么办 棉质衣服皱了怎么办 洗完衣服皱了怎么办 穿衬衫袖子很皱怎么办 洗完衣服有褶皱怎么办 麻料裤子容易皱怎么办 苹果手机邮件删了怎么办 飞猪12306登录不上怎么办 邮箱被别人绑定12306怎么办 白名单一个地址也没怎么办 12306忘记用户名和密码怎么办