本文通过学习王博俊、张宇的《DIY Compiler and Linker》 ,实现语法分析器,一方面作为自己的学习笔记,一方面也作与大家分享与交流
一、语法分析的任务
语法分析任务是在词法分析识别出单词符号的基础上,分析源程序的语法结构,即分析由这些单词如何组成各种语法成分,比如“声明”、“函数”、“语句”、“表达式”等,并分析判断程序的语法结构是否复合语法规则。
语法分析分为自上而下和自下而上两个大类,自上而下核心思路是推导,自下而上核心思路是规约,本语法分析采用自上而下的方法。
在《编译原理》课上学到,自上而下方法需要避免左递归、消除回溯。自上而下分析方法分为两种:递归子程序法和预测分析方法,这里采用递归子程序法。
二、语法定义
在实现语法分析之前,先对语言的语法进行定义。
先列出巴科斯范式(EBNF)语法符号表,以防看不懂语法定义:
EBNF元符号 | 含义 |
---|---|
::= | 意思是“推导为” |
| | 或 |
{} | 含0次在内任意多次重复 |
[] | 含0次和1次重复 |
() | 括号内看作一项 |
. | 一条生成规则的结束 |
<> | 非终结符 |
“” | 终结符 |
2.1 外部定义
<翻译单元>::={
<外部声明>}<文件结束符>
<外部声明>::=<函数定义>|<声明>
2.1.1 函数定义
<函数定义>::=<类型区分符><声明符><函数体>
<函数体>::=<复合语句>
2.1.2 声明
<声明>::=<类型区分符>[<初值声明符表>]<分号>
<初值声明符表>::=<初值声明符>{
<逗号><初值声明符>}
<初值声明符>::=<声明符>|<声明符><赋值运算符><初值符>
2.1.3 类型区分符
<类型区分符>::=<void关键字>|<char关键字>|<short关键字>|<int关键字>|<结构区分符>
<结构区分符>::=<struct关键字><标识符><左大括号><结构声明表><右大括号>|<struct关键字><标识符>
<结构声明表>::=<结构声明>{
<结构声明>}
<结构声明>::=<类型区分符>{
<结构声明符表>}<分号>
<结构声明符表>::=<声明符>{
<逗号><声明符>}
2.1.4 声明符
<声明符>::={
<指针>}[<调用约定>][<结构体成员对齐>]<直接声明符>
<调用约定>::=<__cdecl关键字>|<__stdcall关键字>
<结构体成员对齐>::=<__align关键字><左小括号><整数常量><右小括号>
<指针>::=<星号>
<直接声明符>::=<标识符><直接声明符后缀>
<直接声明符后缀>::={
<左中括号><右中括号>
|<左中括号><整数常量><右中括号>
|<左小括号><右小括号>
|<左小括号><形参表><右小括号>}
<参数声明>::=<类型区分符><声明符>
2.1.5 初值符
<初值符>::=<赋值表达式>
2.2 语句
<语句>::={
<复合语句>|
<if语句>|
<for语句>|
<break语句>|
<continue语句>|
<return语句>|
<表达式语句>}
2.2.1 复合语句
<复合语句>::=<左大括号>{
<声明>}{
<语句>}<右大括号>
2.2.2 表达式语句与空语句
<表达式语句>::=[<expression>]<分号>
2.2.3 选择语句
<if语句>::=<if关键字><左小括号><表达式><右小括号><语句>[<else关键字><语句>]
2.2.4 循环语句
<for语句>::=<for关键字><左小括号><表达式语句><表达式语句><表达式><右小括号><语句>
2.2.5 跳转语句
<continue语句>::=<continue关键字><分号>
<break语句>::=<break关键字><分号>
<return语句>::=<return关键字><expression><分号>
2.3 表达式
<表达式>::=<赋值表达式>{
<逗号><赋值表达式>}
2.3.1 赋值表达式
<赋值表达式>::=<相等类表达式>|<一元表达式><赋值等号><赋值表达式>
2.3.2 相等类表达式
<相等类表达式>::=<关系表达式>{
<等于号><关系表达式>|<不等于号><关系表达式>}
2.3.3 关系表达式
<关系表达式>::=<加减类表达式>{
<小于号><加减类表达式>
|<大于号><加减类表达式>
|<小于等于号><加减类表达式>
|<大于等于号><加减类表达式>}
2.3.4 加减类表达式
<加减类表达式>::=<乘除类表达式>{
<加号><乘除类表达式>
<减号><乘除类表达式>}
2.3.5 乘除类表达式
<乘除类表达式>::=<一元表达式>{
<星号><一元表达式>
|<除号><一元表达式>
|<取余运算符><一元表达式>}
2.3.6 一元表达式
<一元表达式>::=<后缀表达式>
|<与号><一元表达式>
|<星号><一元表达式>
|<加号><一元表达式>
|<减号><一元表达式>
|<sizeof表达式>
<sizeof表达式>::=<sizeof关键字>(<类型区分符>)
2.3.7 后缀表达式
<后缀表达式>::=<初等表达式>{
<左中括号><expression><右中括号>
|<左小括号><右小括号>
|<左小括号><实参表达式表><右小括号>
|<点号>IDENTIFIER
|<箭头>IDENTIFIER}
<实参表达式表>::=<赋值表达式>{
<逗号><赋值表达式>}
2.3.8 初等表达式
<初等表达式>::=<标识符>|<整数常量>|<字符串常量>|<字符常量>|(<表达式>)
三、语法分析的实现
为了将语法分析表示出来,我们给源代码进行语法缩进,来表示顺利实现语法分析
语法缩进由两个全局变量控制:
int syntax_state; //语法状态
int syntax_level; //缩进级别
语法状态取值为:
enum e_SynTaxState
{
SNTX_NUL, // 空状态,没有语法缩进动作
SNTX_SP, // 空格 int a; int __stdcall MessageBoxA(); return 1;
SNTX_LF_HT, // 换行并缩进,每一个声明、函数定义、语句结束都要置为此状态
SNTX_DELAY // 延迟取出下一单词后确定输出格式,取出下一个单词后,根据单词类型单独调用syntax_indent确定格式进行输出
};
通过Tab键控制缩进级别:
void print_tab(int n)
{
int i = 0;
for (; i < n; i++)
printf("\t");
}
语法缩进程序为:
void syntax_indent()
{
switch (syntax_state)
{
case SNTX_NUL:
color_token(LEX_NORMAL);
break;
case SNTX_SP:
printf(" ");
color_token(LEX_NORMAL);
break;
case SNTX_LF_HT:
{
if (token == TK_END) // 遇到'}',缩进减少一级
syntax_level--;
printf("\n");
print_tab(syntax_level);
}
color_token(LEX_NORMAL);
break;
case SNTX_DELAY:
break;
}
syntax_state = SNTX_NUL;
}
不仅延续之间词法分析的词法着色,并且加上了语法缩进
每次在取单词时执行syntax_indent()函数
void get_token()
{
...
syntax_indent();
}
3.1 外部定义语法分析
语法定义:
<翻译单元>::={
<外部声明>}<文件结束符>
<外部声明>::=<函数定义>|<声明>
&