简单的C语言解释运行器实现(一)—— 词法分析

引入

编译器的构造想必度过编译原理的同学已经了解了。大概分为
预处理器 -> 编译器 -> 汇编器 -> 链接器,4个阶段。
编译器又可以分为词法分析 -> 语法分析 -> 语义分析 -> 中间代码生成 -> 代码优化 ->生成目标代码等阶段。

阶段功能
预处理器处理宏定义,如#include表示引入其他源文件的代码,#define表示定义宏,对代码片进行一个替换,#if系列命令可以控制预处理器的功能做到面向不同环境的代码等等。
编译器生成中间代码,生成中间代码的意义是方便该阶段以后的所有阶段共用一个后端,比如LLVM,这样不同的语言能共享同一套优化系统
词法分析将高级语言代码文本切割成词汇,输出单词流,删除注释、空格、空行等
语法分析根据单词流生成语法树
语义分析构建带类型和符号表的语法树、检查类型是否匹配、检查等号左侧是否为左值等
中间代码生成生成中间代码
代码优化优化中间代码
汇编器根据中间代码生成汇编命令
链接器将多个目标代码库链接成可执行文件

目前本系列文章,我们只做词法分析到中间代码生成4个阶段,以及解释执行中间代码的一个解释器。

一个小例子

比如我们有一个语句:x = a + b * c;
经过词法分析后得到x|=|a|+|2|*|c|;,其中用|表示分隔符。更加抽象地:id|=|id|+|constant|*|id|;,其中id表示标识符。
我们定义文法:

S ::= ID = E
E ::= T | E + T
T ::= F | T * F
F ::= ID | CONSTANT

经过语法分析后,我们可以得到一棵语法树:

   S
  /|\
 F = E
 |  /|\
 i F + T
   |  /|\
   i F * F
     |   |
     2   i

其中字母表示文法的名字,i表示id。
然后我们根据语法树生成字节码:

iconst 2
iload c
imul
iload a
iadd
istore x

然后解释器解释执行就可以得到答案了。
我将在接下来的博文中解释执行过程。

词汇分析

由于经过我们精简的C语言语法并不复杂,因此词汇分析并没有采用自动机匹配切割文本。如果对自动机更感兴趣的同学可以参考 http://www.cnblogs.com/Ninputer/archive/2011/06/07/2074632.html

首先切割文本,显然比需要有意义地切割才行,也就是连续的有意义的一个单词不能被切开,比如int就不能被切成int这种。因此对于下列程序:

int max(int a, int b) {
    // find the maximum of a and b.
    return a > b ? a : b;
}
printf("%d %d %c %d", &a, &b, 'c', 1 << 2);

我们可以这么切割:

int|max|(|int|a|,|int|b|)|{
    |return|a|>|b|?|a|:|b|;|
}
printf|(|"%d %d %c %d"|,|&|a|,|&|b|,|'c'|,|1|<<|2|)|;

词汇分析阶段我们需要将注释全部清除。同时要注意字符和字符串都是完整的单词不能被切开,这将方便语法分析阶段进行处理。而且两个字符的符号如<<++这类也不能被切开。

那么编写这样简化的词法分析就相当简单了,我们只需要遇到引号就按字符串处理,注意转义;遇到数字就按数字处理,注意科学计数法和16进制数以及类型标识(1LL, 1L, 1.0F等);遇到///*就按注释处理即可。

最后我们应该得到这样4种单词:

  1. 关键字/保留字:如ifforwhile
  2. 标识符:字母开头的字母数字下划线串
  3. 常量:小数(1.2)、科学计数法(1.2e+97.7e-56.666e2)整数(19)、八进制数(012)、十六进制数(0x7FFFFFFF)、字符常量('a')、字符串常量("String"
  4. 符号:+、-、*、/、%、&、|、^、&&、||、!、~、>>、<<、(、)、<<、>>、+=、-=、*=、/=、&=、|=、^=、%=、,、;、>、<、>=、<=、!=、==、::
    我们可以在输出单词流的时候标注是哪种标记,方便我们进行语法分析。

具体代码请参考
https://github.com/huanghongxun/Compiler/blob/master/compiler/lexical_analyzer.cpp

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理是计算机科学中的一个重要分支,主要研究如何将高级语言转换为机语言的过程。其中,词法分析是编译中的一个重要组成部分,它负责将源代码中的字符序列转换为有意义的单词序列,也就是词法单元。 下面是一个简单词法分析的C语言代码示例: ```c #include <stdio.h> #include <ctype.h> #include <string.h> #define MAX_TOKEN_LEN 100 typedef enum { INT, FLOAT, IDENTIFIER, KEYWORD } TokenType; typedef struct { TokenType type; char value[MAX_TOKEN_LEN]; } Token; char *keywords[] = {"if", "else", "while", "for", "int", "float"}; int is_keyword(char *word) { int i; for (i = 0; i < 6; i++) { if (strcmp(word, keywords[i]) == 0) { return 1; } } return 0; } Token get_token() { Token token; char c = getchar(); while (isspace(c)) { c = getchar(); } if (isdigit(c)) { token.type = INT; int i = 0; while (isdigit(c)) { token.value[i++] = c; c = getchar(); } if (c == '.') { token.type = FLOAT; token.value[i++] = c; c = getchar(); while (isdigit(c)) { token.value[i++] = c; c = getchar(); } } token.value[i] = '\0'; ungetc(c, stdin); } else if (isalpha(c) || c == '_') { token.type = IDENTIFIER; int i = 0; while (isalnum(c) || c == '_') { token.value[i++] = c; c = getchar(); } token.value[i] = '\0'; ungetc(c, stdin); if (is_keyword(token.value)) { token.type = KEYWORD; } } else { token.type = c; } return token; } int main() { Token token; do { token = get_token(); switch (token.type) { case INT: printf("INT: %s\n", token.value); break; case FLOAT: printf("FLOAT: %s\n", token.value); break; case IDENTIFIER: printf("IDENTIFIER: %s\n", token.value); break; case KEYWORD: printf("KEYWORD: %s\n", token.value); break; default: printf("%c\n", token.type); break; } } while (token.type != EOF); return 0; } ``` 这个词法分析可以识别整数、浮点数、标识符和关键字。它通过一个`get_token()`函数来获取下一个词法单元,并根据单元的类型进行相应的处理。在`get_token()`函数中,它会读取输入流中的字符,根据字符的类型来判断当前单元的类型,并将单元的值存储在一个`Token`结构体中返回。在`main()`函数中,它会不断调用`get_token()`函数来获取下一个单元,并根据单元的类型进行相应的输出,直到读取到输入流的结尾。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值