基于Lex 和 Yacc 的 C 语言编译器

       最近由于项目需要,看了点关于编译原理和编译器等方面的资料,特别是词法分析和语法分析部分,现做一下小结。

      一、编译器及其工作流程

       编 译器,是将便于人编写,阅读,维护的高级计算机语言翻译为计算机能识别,运行的低级机器语言的程序。编译器将源程序(Source program)作为输入,翻译产生使用目标语言(Target language)的等价程序。源程序一般为高级语言(High-level language),如Pascal,C++等,而目标语言则是汇编语言或目标机器的目标代码(Object code),有时也称作机器代码(Machine code)。

       一个现代编译器的主要工作流程如下:
       源程序(source code)→预处理器(preprocessor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→连接器(链接器,Linker)→可执行程序(executables)

       而编译器各阶段的主要工作包括:  

      1. 词法分析

       词法分析器根据词法规则识别出源程序中的各个记号(token),每个记号代表一类单词(lexeme)。源程序中常见的记号可以归为几大类:关键字、标识符、字面量和特殊符号。词法分析器的输入是源程序,输出是识别的记号流。词法分析器的任务是把源文件的字符流转换成记号流。本质上它查看连续的字符然后把它们识别为“单词”。

       2. 语法分析

       语法分析器根据语法规则识别出记号流中的结构(短语、句子),并构造一棵能够正确反映该结构的语法树。

       3. 语义分析

       语义分析器根据语义规则对语法树中的语法单元进行静态语义检查,如果类型检查和转换等,其目的在于保证语法正确的结构在语义上也是合法的。

       4. 中间代码生成

      中间代码生成器根据语义分析器的输出生成中间代码。中间代码可以有若干种形式,它们的共同特征是与具体机器无关。最常用的一种中间代码是三地址码,它的一种实现方式是四元式。三地址码的优点是便于阅读、便于优化。

        5. 中间代码优化

      优化是编译器的一个重要组成部分,由于编译器将源程序翻译成中间代码的工作是机械的、按固定模式进行的,因此,生成的中间代码往往在时间和空间上有很大浪费。当需要生成高效目标代码时,就必须进行优化。

       6. 目标代码生成

      目标代码生成是编译器的最后一个阶段。在生成目标代码时要考虑以下几个问题:计算机的系统结构、指令系统、寄存器的分配以及内存的组织等。编译器生成的目标程序代码可以有多种形式:汇编语言、可重定位二进制代码、内存形式。

       7 符号表管理

      符号表的作用是记录源程序中符号的必要信息,并加以合理组织,从而在编译器的各个阶段能对它们进行快速、准确的查找和操作。符号表中的某些内容甚至要保留到程序的运行阶段。

      8 出错处理

      用 户编写的源程序中往往会有一些错误,可分为静态错误和动态错误两类。所谓动态错误,是指源程序中的逻辑错误,它们发生在程序运行的时候,也被称作动态语义 错误,如变量取值为零时作为除数,数组元素引用时下标出界等。静态错误又可分为语法错误和静态语义错误。语法错误是指有关语言结构上的错误,如单词拼写 错、表达式中缺少操作数、begin和end不匹配等。静态语义错误是指分析源程序时可以发现的语言意义上的错误,如加法的两个操作数中一个是整型变量名,而另一个是数组名等。

       二、 lex 和 yacc 简单入门

Lex(Lexical Analyzar 词法分析生成器),Yacc(Yet Another Compiler Compiler
编译器代码生成器)是Unix下十分重要的词法分析,语法分析的工具。经常用于语言分
析,公式编译等广泛领域。遗憾的是网上中文资料介绍不是过于简单,就是跳跃太大,
入门参考意义并不大。本文通过循序渐进的例子,从0开始了解掌握Lex和Yacc的用法。

<本系列文章的地址:http://blog.csdn.net/liwei_cmg/category/207528.aspx>

1.Lex(Lexical Analyzar) 初步示例

先看简单的例子(注:本文所有实例皆在RetHat Linux下完成):

一个简单的Lex文件 exfirst.l 内容:

%{
#include "stdio.h"
%}
%%
[\n]                  ;
[0-9]+                printf("Int     : %s\n",yytext);
[0-9]*\.[0-9]+        printf("Float   : %s\n",yytext);
[a-zA-Z][a-zA-Z0-9]* printf("Var     : %s\n",yytext);
[\+\-\*\/\%]          printf("Op      : %s\n",yytext);
.                     printf("Unknown : %c\n",yytext[0]);
%%

在命令行下执行命令flex解析,会自动生成lex.yy.c文件:
[root@localhost liweitest]flex exfirst.l

进行编译生成parser可执行程序:
[root@localhost liweitest]cc -o parser lex.yy.c -ll

[注意:如果不加-ll链结选项,cc编译时会出现以下错误,后面会进一步说明。]

/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/http://www.cnblogs.com/../crt1.o(.text+0x18): In function `_start':
../sysdeps/i386/elf/start.S:77: undefined reference to `main'
/tmp/cciACkbX.o(.text+0x37b): In function `yylex':
: undefined reference to `yywrap'
/tmp/cciACkbX.o(.text+0xabd): In function `input':
: undefined reference to `yywrap'
collect2: ld returned 1 exit status


创建待解析的文件 file.txt:

title
i=1+3.9;
a3=909/6
bcd=4%9-333

通过已生成的可执行程序,进行文件解析。

[root@localhost liweitest]# ./parser < file.txt
Var     : title
Var     : i
Unknown : =
Int     : 1
Op      : +
Float   : 3.9
Unknown : ;
Var     : a3
Unknown : =
Int     : 909
Op      : /
Int     : 6
Var     : bcd
Unknown : =
Int     : 4
Op      : %
Int     : 9
Op      : -
Int     : 333

到此Lex用法会有个直观的了解:

1.定义Lex描述文件
2.通过lex,flex工具解析成lex.yy.c文件
3.使用cc编译lex.yy.c生成可执行程序


再来看一个比较完整的Lex描述文件 exsec.l :


%{

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LexYacc是Unix系统中的常见工具,它们可以用于生成编译器的词法分析器和语法分析器。我们可以使用这两个工具来实现一个简单的C编译器。 下面是一个实现C编译器的基本步骤: 1. 设计语法规则:首先需要确定C语言的语法规则。可以参考C语言标准来确定语法规则,并将其表示为BNF范式。 2. 编写Lex文件:根据语法规则,编写Lex文件。Lex文件包含正则表达式和对应的动作,用于生成词法分析器。词法分析器会根据输入的源代码逐个读取字符并生成词法单元。 3. 编写Yacc文件:根据语法规则,编写Yacc文件。Yacc文件包含语法规则和对应的动作,用于生成语法分析器。语法分析器会根据词法分析器生成的词法单元,逐步构建语法树,并执行对应的动作。 4. 编写代码生成器:根据语法树,生成对应的目标代码。 下面是一个简单的例子,用于演示如何使用LexYacc生成C编译器: 首先,我们需要定义C语言的语法规则。例如,我们可以定义一个简单的语法规则,用于表示一个整数常量: ``` <constant> ::= <digit> | <digit> <constant> <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 ``` 接下来,我们可以编写Lex文件,用于生成词法分析器。例如,我们可以编写如下的Lex文件: ```lex %{ #include <stdio.h> #include <stdlib.h> %} DIGIT [0-9] %% {DIGIT}+ { printf("CONSTANT: %s\n", yytext); return CONSTANT; } . { return yytext[0]; } %% int yywrap() { return 1; } ``` 在上面的代码中,我们使用了Lex的正则表达式来匹配整数常量。当遇到一个整数常量时,我们会打印出它的值,并返回对应的词法单元CONSTANT。 接下来,我们编写Yacc文件,用于生成语法分析器。例如,我们可以编写如下的Yacc文件: ```yacc %{ #include <stdio.h> #include <stdlib.h> #include <string.h> int yylex(); void yyerror(char *); %} %token CONSTANT %% program: statement_list ; statement_list: statement | statement_list statement ; statement: expression_statement ; expression_statement: CONSTANT ; %% void yyerror(char *s) { fprintf(stderr, "%s\n", s); } int main() { yyparse(); return 0; } ``` 在上面的代码中,我们定义了一个简单的语法规则,用于表示一个整数常量。当遇到一个整数常量时,我们会执行对应的动作。 最后,我们还需要编写代码生成器,用于生成对应的目标代码。由于这超出了本题的范围,这里不再给出具体实现。 综上所述,上述步骤就是使用LexYacc实现C编译器的基本步骤。当然,实际上还有许多细节需要考虑,例如错误处理、符号表管理等等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值