一个简单编译器的解析:write-a-C-interpreter

本文介绍了GitHub上一个小型C语言编译器的实现,包括词法分析、语法分析及代码生成。文章详细讲解了next()、program()、expression()等关键函数的功能,以及表达式、变量、函数和枚举的解析。该编译器支持基本的C语言特性,如if、while,但不支持struct,使用虚拟机执行指令。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本文分析代码来自 github:https://github.com/lotabout/write-a-C-interpreter/blob/master/xc.c。并非亲自实现。这个一个小型的C语言编译器,或者称为解释器,是 c 语言的极度简化版。不过包含了词法分析、语法分析以及代码生成等功能。该代码在进行语法分析的同时进行词法分析。本文大部分参照原作者内容,不过增加了自己的解释。

正文

先总体介绍改代码中包含的函数

next(): 词法分析的入口

program():语法分析的入口

global_declaration: 全局声明,包括 全局变量、函数以及 enum 变量的声明

function_declaration:函数解析

function_parameter:函数参数的解析

function_body:函数体的解析

enum_declaration:enum 变量的解析

expression(level):函数体内表达式的分析,包含对变量、函数、枚举类型的分析以及对操作符的分析

eval():虚拟机的入口,对相关代码做解释

该编译器一共支持3种类型:enum { CHAR, INT, PTR };

不支持 struct;

变量必须先声明再使用;

没有 do-while、for、switch,只有 if 和 while;

有 text、stack、data 三个段;

包含 PC、SP、BP、AX 四个有关寄存器。

有如下指令集:

enum { LEA ,IMM ,JMP ,CALL,JZ  ,JNZ ,ENT ,ADJ ,LEV ,LI  ,LC  ,SI  ,SC  ,PUSH,
       OR  ,XOR ,AND ,EQ  ,NE  ,LT  ,GT  ,LE  ,GE  ,SHL ,SHR ,ADD ,SUB ,MUL ,DIV ,MOD ,
       OPEN,READ,CLOS,PRTF,MALC,MSET,MCMP,EXIT };

相关指令含义如下:

LEA 2 :将栈往上的2个位置变量加载到寄存器 加载本地变量
IMM :将代码区位置指定的代码加载到寄存器 加载到全局变量
JMP 2 :将代码区跳转到2这个位置 跳转
JSR 2 :将下一个代码区位置保存到栈顶,并将代码区跳转到2这个位置 跳转到子程序
BZ 2 :如果全局变量为a,则跳转到下一个代码区域,否则跳转到2这个位置 零跳转
BNZ 2 :如果全局变量不为a,则跳转到下一个代码区域,否则跳转到2这个位置 非零跳转
ENT 2 :保存前一个程序的栈顶到当前栈上,并设置新栈顶,和局部变量区 开辟一个新的子程序
ADJ 2 :栈调整,往前2个位置 栈跳转
LEV :栈回滚到当前程序的栈顶,然后恢复上一个程序的栈顶,再恢复上一个程序的代码区,最后恢复上一个程序的栈 子程序退出
LI :将寄存器的整数变量加载出来 加载整数
LC :将寄存器的字符变量加载出来 加载字符
SI :将栈顶指定位置的地址保存整数 保存整数
SC :将栈顶指定位置的地址保存字符 保存字符
PSH :将寄存器变量保存到栈上 保存栈 EXIT 将栈顶变量打印出来,作为返回值,并退出来 退出整个程序 JZ/JNZ: 零跳转/非零跳转
CALL:调用子程序

CALL:调用子程序

他们的含义都在 eval() 函数中进行了解释:

 if (op == IMM)       {ax = *pc++;}                                     // load immediate value to ax
        else if (op == LC)   {ax = *(char *)ax;}                               // load character to ax, address in ax
        else if (op == LI)   {ax = *(int *)ax;}                                // load integer to ax, address in ax
        else if (op == SC)   {ax = *(char *)*sp++ = ax;}                       // save character to address, value in ax, address on stack
        else if (op == SI)   {*(int *)*sp++ = ax;}                             // save integer to address, value in ax, address on stack
        else if (op == PUSH) {*--sp = ax;}                                     // push the value of ax onto the stack
        else if (op == JMP)  {pc = (int *)*pc;}                                // jump to the address
        else if (op == JZ)   {pc = ax ? pc + 1 : (int *)*pc;}                   // jump if ax is zero
        else if (op == JNZ)  {pc = ax ? (int *)*pc : pc + 1;}                   // jump if ax is not zero
        else if (op == CALL) {*--sp = (int)(pc+1); pc = (int *)*pc;}           // call subroutine
        //else if (op == RET)  {pc = (int *)*sp++;}                              // return from subroutine;
        else if (op == ENT)  {*--sp = (int)bp; bp = sp; sp = sp - *pc++;}      // make new stack frame
        else if (op == ADJ)  {sp = sp + *pc++;}                                // add esp, <size>
        else if (op == LEV)  {sp = bp; bp = (int *)*sp++; pc = (int *)*sp++;}  // restore call frame and PC
        else if (op == LEA)  {ax = (int)(bp + *pc++);}                         // load address for arguments.

        else if (op == OR)  ax = *sp++ | ax;
        else if (op == XOR) ax = *sp++ ^ ax;
        else if (op == AND) ax = *sp++ & ax;
        else if (op == EQ)  ax = *sp++ == ax;
        else if (op == NE)  ax = *sp++ != ax;
        else if (op == LT)  ax = *sp++ < ax;
        else if (op == LE)  ax = *sp++ <= ax;
        else if (op == GT)  ax = *sp++ >  ax;
        else if (op == GE)  ax = *sp++ >= ax;
        else if (op == SHL) ax = *sp++ << ax;
        else if (op == SHR) ax = *sp++ >> ax;
        else if (op == ADD) ax = *sp++ + ax;
        else if (op == SUB) ax = *sp++ - ax;
        else if (op == MUL) ax = *sp++ * ax;
        else if (op == DIV) ax = *sp++ / ax;
        else if (op == MOD) ax = *sp++ % ax;

        else if (op == EXIT) { printf("exit(%d)", *sp); return *sp;}
        else if (op == OPEN) { ax = open((char *)sp[1], sp[0]); }
        else if (op == CLOS) { ax = close(*sp);}
        else if (op == READ) { ax = read(sp[2], (char *)sp[1], *sp); }
        else if (op == PRTF) { tmp = sp + pc[1]; ax = printf((char *)tmp[-1], tmp[-2], tmp[-3], tmp[-4], tmp[-5], tmp[-6]); }
        else if (op == MALC) { ax = (int)malloc(*sp);}
        else if (op == MSET) { ax = (int)memset((char *)sp[2], sp[1], *sp);}
        else if (op == MCMP) { ax = memcmp((char *)sp[2], (char *)sp[1], *sp);}

词法分析

因为该语法不支持 struct,所以用一个整型数组来保存相关的标识符(id)信息。每个标识符由 9 个部分组成,分别是:Token(标识符对应的标记), Hash(标识符的hash值), Name(标识符的名称), Type(标识符的类型,如int, char), CLass(标识符的类别,如数字,全局变量), Value(标识符存放的变量值,若标识符是函数,则存放函数地址), Bclass, BValue, IdSize(BXXXX,当全局标识符和局部标识符同时存在时,用作存放局部标识符)。

这里的词法分析并没有一次吧所有符号解析出来,而是每次只解析一个符号。

用于词法分析的程序是 next().

对标识符的处理

整体思路是:每次向前读取一个字符,直到读取一个完整的标识符为止。然后判断该标识符是否在符号表中,若在,结束;若不在,加入符号表,结束。其中 last_pos是该标识符起点处的上一个位置。

最外层的while循环用于遍历字符串,遇到换行,行数+1,遇到‘#’,跳过,因为这个语法不支持宏定义,如遇到的第一个字符满足a-z或A-Z或_,则说明有可能是标识符,然后while遍历得到该标识符。symbols 是符号表的地

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值