ucc编译器分析与总结(1)语法分析

更新时间:2023-07-24 10:35:30 阅读: 评论:0

ucc编译器分析与总结(1)语法分析
编译器、操作系统、数据库是计算机领域中的三⼤基础软件,基本上所有应⽤软件都是建⽴在这三个基础软件之上,这些领域经过前辈们不断打磨现在已经变得⾮常成熟,很多涉及到的代码技术都是精华中的精华,所以把上⾯3个软件搞清楚对代码⽔平的提⾼会有很多帮助,⽽且能够对现代计算机的软件架构有更清晰的了解。
我现在要研究⼀个逻辑推理系统,这个推理系统以C语⾔为基础,这⾥必然涉及到C语⾔的解析问题,所以C语⾔编译器是必须要先学习的,C语⾔编译器是把C语⾔转换成汇编语⾔,我打算在编译器的基础上⽣成⼀些关于逻辑的断⾔,然后以这些逻辑断⾔为基础再做⼀个推理系统。
⽬前C语⾔编译器⽐较成熟的是gcc、clang加llvm,但是这些都太庞⼤了,这⾥⾯有各种技术优化上的考量,个⼈是没有精⼒也没有时间掌握的。我需要学习的是编译器的⼀些基本思想,所以只要看⼀些⼩型的开源C语⾔编译器就可以了,其实还是挺多的,有lcc、ucc、tcc、8cc等等。
1.基本架构
其实ucc编译器并不是⼀个完整的编译器,完整的编译器包括预处理器、C编译器、汇编器和连接器,ucc只有把C语⾔编译成汇编语⾔的这⼀部分,如下图所⽰:
打开ucc的源码,可以看到包含ucc和ucl两部分⼯程,因为ucc编译器中只做了.i⽂件⽣成.s⽂件的⼯作,在ucl的代码中完成,所以为了从.c ⽂件⽣成可执⾏⽂件还要在ucc中调⽤gcc的预处理器、汇编器和连接器的命令来完成整个编译的过程。
现在假如要编译⼀个test.c⽂件,可以先运⾏命令ucc -E -v test.c来⽣成预处理后的⽂件test.i,然后再运⾏ucl -ext:.s test.i来调试分析整个编译的过程。
2.词法分析
现在从ucl.c⽂件中的main函数来分析整个编译的流程,代码如下
i =ParCommandLine(argc, argv);
SetupRegisters();
SetupLexer();
SetupTypeSystem();
for(; i < argc;++i)初中补习
{
Compile(argv[i]);
}outrage
⾸先是寄存器和词法分析器的相关初始化,然后通过for循环由Compile编译每⼀个.c⽂件。
之后由ParTranslationUnit()来完成词法分析和语法分析⼯作,⾸先读取整个⽂件,放在Input的全局变量⾥,这是⼀个struct input结构体类型的变量,定义如下:
struct input
{
//⽂件名,预处理后会把所有.h⽂件展开到.i⽂件
//例如 # 667 "/usr/include/stdio.h" 3 4
//会记录头⽂件名和对应⾏数
char*filename;
unsigned char*ba;//整个⽂件
//当前解析到的单词的下⼀个地址
//例如 int aa[4];
//如果现在解析了aa,那么cursor指向[4];
unsigned char*cursor;
unsigned char*lineHead;//每⼀条语句以;为单位
int line;// 当前在.i⽂件中的⾏数
void* file;//⽂件描述符
void* fileMapping;
unsigned long size;//⽂件⼤⼩
};
另外还⼀个TokenCoord全局变量是Input的补充,增加了头⽂件中的⾏数。在C语⾔中是以单词为单位的,上⾯代码的注释中⽤int aa[4];举了⼀个例⼦,这条语句的单词分别为int、aa、[、]、4、; 。ucc会⽤NEXT_TOKEN取得⼀个单词放在CurrentToken⾥,然后再进⾏语法分析:
#define NEXT_TOKEN  CurrentToken = GetNextToken();
CurrentToken是⼀个枚举型变量,代表⼀个单词的属性,并不是每个单词字符串本⾝,每个关键字都会有⼀个对应的枚举变量,下⾯是所有token的定义:
enum token
{
TK_BEGIN,
role是什么意思
#define TOKEN(k, s) k,
#include"token.h"
#undef  TOKEN
};
//keywords
TOKEN(TK_AUTO,"auto")
TOKEN(TK_EXTERN,"extern")
TOKEN(TK_REGISTER,"register")
TOKEN(TK_STATIC,"static")
TOKEN(TK_TYPEDEF,"typedef")
......
C语⾔中定义的标识符Token统⼀⽤TK_ID来表⽰,在取得⼀个单词的Token后会添加到语法树的结点⾥。
3.语法分析
新东方英语暑假班词法分析从C源⽂件中取得单词的token,⽽语法分析则把这些单词添加到语法树⾥⾯。AstNode是语法树结点最基本的结构体:
美好的回忆英文#define AST_NODE_COMMON  \
int kind;            \
struct astNode *next; \
struct coord coord;
typedef struct astNode
{
AST_NODE_COMMON
}*AstNode;
其中kind代表结点的类型。next⽤来把同类的表达式关联在⼀起,例如⽤;和,隔开的语句。coord则标记所在⽂件和与其对应的⾏数。其他类型的结点由于kind不同所需要的信息不同,结构体会有所差异,但都是在AstNode的基础上扩充⽽来的。
在语法分析的过程中,⾸先会先建⽴⼀个NK_TranslationUnit类型的根结点,该结点定义如下
struct astTranslationUnit
{
AST_NODE_COMMON
AstNode extDecls;
};
这个根结点把⼀个.c⽂件中每⼀条全局声明语句和函数串联在⼀起,如下图所⽰可以直观感受到语法树的特点
代码如下:
AstTranslationUnit ParTranslationUnit(char*filename)
{
AstTranslationUnit transUnit;
AstNode *tail;
//...
while(CurrentToken != TK_END)
{
*tail =ParExternalDeclaration();
tail =&(*tail)->next;
SkipTo(FIRST_ExternalDeclaration,
"the beginning of external declaration");
}
}
每个结点可以分为声明语句和表达式两种类型,表达式⼜由赋值和运算语句组成。以下⾯的代码举例说明
bailoutextern int c;
int test(int a)
{
int b =1;
a = b+sizeof(int);
if(a>0){
颁发的意思c++;
}
return0;
}
其中声明语句有
extern int c;
int test(int a){}
int b = 1;
natal
表达式语句有
a = b+sizeof(int);
if(a>0){
c++;
}
return 0;
根据这2种类型的结点会再建⽴⼦语法树,层层递归直到语法树结点中的元素为单词为此。
3.1 声明语句
声明语句的解析会在ParCommonHeader()函数中完成,ParDeclarationSpecifiers()函数会解析声明关键字如int、static、typedef等,声明中的标识符由ParInitDeclarator()来解析,碰到逗号通过next指针连接在⼀起。
while(CurrentToken == TK_COMMA)
{
NEXT_TOKEN;
*tail =(AstNode)ParInitDeclarator();
tail =&(*tail)->next;
}
声明的语句中可能会初始化如int a = 3;在解析完后再把=后⾯的语句加⼊到语法树中
static AstInitDeclarator ParInitDeclarator(void)
{
AstInitDeclarator initDec;
CREATE_AST_NODE(initDec, InitDeclarator);
initDec->dec =ParDeclarator(DEC_CONCRETE);
if(CurrentToken == TK_ASSIGN)//遇到=说明要初始化
{
NEXT_TOKEN;
initDec->init =ParInitializer();
}
return initDec;
}
ParDeclarator解析具体的声明标识符,后⾯的参数DEC_CONCRETE说明标识符不可省略,有些地
⽅的声明语句不能有标识符,如sizeof(int),这时候⽤DEC_ABSTRACT,还有些地⽅可有可⽆,如声明函数的时候int f(int,int); ,这时候调⽤
ParDeclarator(DEC_CONCRETE|DEC_ABSTRACT);
声明符中可能会碰到指针的情况,*会作为⼀个单独的语法树结点解析
static AstDeclarator ParDeclarator(int kind)
magnumopus
{
if(CurrentToken == TK_MUL)//指针声明符*
{
AstPointerDeclarator ptrDec;
CREATE_AST_NODE(ptrDec, PointerDeclarator);
//...
//递归调⽤继续解析
ptrDec->dec =ParDeclarator(kind);
return(AstDeclarator) ptrDec;
}
//继续解析⾮指针情形下的声明标识符
return ParPostfixDeclarator(kind);
圣诞节快乐英文翻译}
在ParPostfixDeclarator()函数中会调⽤ParDirectDeclarator()解析最后的标识符单词,标识符后⾯跟着[]是数组,跟着()是函数形参,还需要继续解析。
当声明语句中碰到typedef时,会通过CheckTypedefName()函数将声明的标识符加⼊到TDName列表中记录下来。然后遇到TK_ID的标识符时,会通过IsTypedefName()确认之前有没有通过typedef声明过。在C语⾔中每个{}代表⼀个作⽤域,ucc中⽤Level标记当前的作⽤域,作⽤域每加深⼀层Level 加1。有时碰到内部作⽤域定义的变量和外部typedef声明的标识符重名了,需要加到OverloadNames列表中重载。
最后通过⼀张图来直观感受⼀下声明语句的解析过程,图⽚取⾃《C编译器剖析》
3.2 表达式语句

本文发布于:2023-07-24 10:35:30,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/90/187236.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:声明   语句   编译器   解析   标识符   单词   结点
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图