LCC编译器源程序分析(8)语法分析开始x
来源:初三 发布时间:2020-08-31 点击:
LCC 编译器的源程序分析(8) 语法分析的开始 准备好词法分析之后,接着的工作就是检查源程序是否合法,以及源程序表达的意思是什么。这两个问题就是语法和语义的分析,也就是把源程序里所包含的属性分析出来,并保存到符号表里。下面就来仔细地分析 LCC 编译器是怎么样处理这两个问题的。
1 t = gettok(); 2
3
//调用后端代码生成初始化工作。
4
(*IR->progbeg)(argc, argv); 5
6
for (i = 1; i < argc; i++) 7
{ 8
if (strcmp(argv[i], "-n") == 0)
09
{ 0
if (!YYnull)
1
{ 2
= install(string("_YYnull"), &globals, GLOBAL, PERM); 3
type = func(voidptype, NULL, 1); 4
YYnull->sclass = EXTERN; 5
(*IR->defsymbol)(YYnull); 6
} 7
8
}
19
else if (strncmp(argv[i], "-n", 2) == 0)
0
{
/* -nvalid[,check] */ 1
char *p = strchr(argv[i], ",");
2
if (p)
3
{ 4
= install(string(p+1), &globals, GLOBAL, PERM); 5
type = func(voidptype, NULL, 1); 6
YYcheck->sclass = EXTERN; 7
(*IR->defsymbol)(YYcheck); 8
p = stringn(argv[i]+2, p - (argv[i]+2)); 29
}
0
else 1
{ 2
p = string(argv[i]+2); #033
}
4
5
= install(p, &globals, GLOBAL, PERM); 6
type = func(voidptype, NULL, 1); 7
YYnull->sclass = EXTERN; 8
(*IR->defsymbol)(YYnull); 39
0
}
1
else
2
{ 3
profInit(argv[i]); 4
traceInit(argv[i]); 5
} 6
} 7
8
if (glevel && IR->stabinit) 49
{ 0
(*IR->stabinit)(firstfile, argc, argv); 1
}
2
3
//开始整个程序处理。
#054
program(); 在获取一个记号之后,在第 4 行就调用后端代码生成器准备生成代码的初始化。接着第 6 行到第 46 行是处理-n 参数,由于我们的例子里没有这些参数,就不会运行这些代码。把这些不是很重要的细节代码放下,我们继续地分析后面的代码。
第 48 行到第 51 行是调用后端 stabinit 进行初始化。
最重要的语法和语义分析开始了,它就是在第 54 行里调用函数 program。由于这个 C 编译器是使用递归下降的分析方法来进行语法和语义分析的,所以在这个函数里面会有很多递归调用的函数。简单地来说递归调用就如下面的形式:
int Test1(void) {
return Test3(); }
int Test2(void) {
return Test1();
}
int Test3(void) {
return Test2(); } 上面只是形式说明一下,并不能进行运行的程序,现在就去分析 C 编译器的语法语义分析的总入口函数 program:
蔡军生 2007/5/18 于深圳 // 1 //程序开始分析。
2 void program(void)
3 { 4
int n; 5
6
//作用域为全局。
7
level = GLOBAL; 8
09
//分析源程序到文件尾。
0
for (n = 0; t != EOI; n++) 1
{ 2
if (kind[t] == CHAR || kind[t] == STATIC 3
|| t == ID || t == "*" || t == "(")
4
{ 5
//声明开始. 6
decl(dclglobal); 7
8
deallocate(STMT); 19
if (!(glevel >= 3 || xref)) 0
{ 1
deallocate(FUNC); 2
}
3
}
4
else if (t == ";")
5
{ 6
warning("empty declaration/n"); 7
t = gettok(); 8
}
29
else
0
{ 1
error("unrecognized declaration/n"); 2
t = gettok(); #033
}
4
} 5
6
if (n == 0) 7
{ 8
warning("empty input file/n"); 39
}
#040 } 函数开始的第 7 行设置作用域为全局作用域 GLOBAL。比如分析例子里的代码,在main 函数以外的代码,都是全局作用域的。
10 行使用一个 for 循环,它的终止条件是分析源程序文件到结束,第 11 行到34 行里都是分析源程序的代码。
第 12 行和第 13 行就判断开始的记号是否合法的。先看一下,就发现有一个数组kind[t],并且用它来判断记号的合法性。这个数组的定义如下:
char kind[] = { xx(a,b,c,d,e,f,g) f, #define yy(a,b,c,d,e,f,g) f, #include "token.h" }; 上面定义了两个宏,然后包含了头文件 token.h,因为所有宏定义解释中在头文件里,如下:
1 /* 2 xx(symbol,
value,
prec,
op, optree, kind,
string) 3 */ 4 yy(0,
0, 0, 0,
0,
0,
0) 5 FLOAT,
1, 0, 0,
0,
CHAR,
"float") 6 DOUBLE,
2, 0, 0,
0,
CHAR,
"double") 7 CHAR,
3, 0, 0,
0,
CHAR,
"char") 8 SHORT,
4, 0,
0,
CHAR,
"short") 09 INT,
5, 0, 0,
0,
CHAR,
"int") 0 UNSIGNED, 6, 0, 0,
0,
CHAR,
"unsigned") 1 PINTER,
7, 0, 0,
0,
0,
"pointer") 2 VOID,
8, 0, 0,
0,
CHAR,
"void") 3 STRUCT,
9,
,
,
"struct4 UNION,
10, 0, 0,
0,
CHAR,
"union") 5 FUNCTION, 11, 0,
0,
0,
"function") 6 ARRAY,
12, 0, 0,
0,
0,
"array") 7 ENUM,
13, 0, 0,
0,
CHAR,
"enum") 8 LG,
14, 0, 0,
0,
CHAR,
"long") 19 CONST,
15, 0, 0,
0,
CHAR,
"const") #020 xx(VOLATILE, 16, 0, 0,
0,
CHAR,
"volatile")
1 7 2 80,
0,
0,
0) 3 190,
0,
0,
0) 4 0 5 1 6 2 7 3 8 4 29 5 0 6 1 70) 2 8"long long") 3 29 4 00) 5 yy(0,
31,
0,
0,
0,
0,
"const volatile") 6 xx(ID,
32,
0,
0,
0,
ID,
"identifier") 7 yy(0,
33,
0,
0,
0,
ID,
"!") 8 FCON,
34,
0,
0,
0,
ID,
"floating constant") 39 ICON,
35,
0,
0,
0,
ID,
"integer constant") 0 xx(SCON,
36,
0,
0,
0,
ID,
"string constant") 1 713,
MOD, bittree,"%",
"%") 2 yy(0,
38,
8,
BAND, bittree,ID,
"&") 3 xx(INCR,
39,
0,
ADD, addtree,ID,
"++") 4 0ID,
"(") 5 1
0,
0
0,
")",
")") 6 23MUL, multree,ID,
"* 7 32, ADD, addtree,ID,
"+") 8 4, 0,
0,
",",
",") 49 512, SUB, subtree,ID,
"-") 0 60, 0,
0,
".",
".") 1 yy(0,
47, 13, DIV, multree,"/",
"/") 2 CR,
48, 0, SUB, subtree,ID,
"--") 3 DEREF,
49, 0, 0,
0,
DEREF, "->") 4 ANDAND,
50, 5, AND, andtree,ANDAND, "&&") 5 OROR,
51, 4OR,
andtree,OROR,
"||6 LEQ52, 10, LE,
cmptree,LEQ,
"<=") 7 EQL,
53,
9,
EQ,
eqtree, EQL,
"==") 8 NEQ,
54,
9,
NE,
eqtree, NEQ,
"!=") 59 GEQ,
55,
10,
GE,
cmptree,GEQ,
">=") #060 xx(RSHIFT,
56,
11,
RSH,
shtree, RSHIFT, ">>")
1 xx(LSHIFT,
57,
11,
LSH,
shtree, LSHIFT, "<<") 2 8":",
":") 3 590,
0,
0,
IF,
";") 4 010,
LT,
cmptree,"<",
"<") 5 12,
ASGN,
asgntree,"=",
"=") 6 210,
GT,
cmptree,">",
">") 7 yy(0,
63,
0,
0,
0,
"?",
"?") 8 ELLIPSIS,
64,
0,
0,
0,
ELLIPSIS,"...") 69 xx(SIZEOF,
65,
0,
0,
0,
ID,
"sizeof") 0 yy(0,
66,
0,
0,
0,
0,
0) 1 xx(AUTO,
67,
0,
0,
0,
STATIC, "auto") 2
xx(BREAK,
68,
0,
0,
0,
IF,
"break") 3 ASE,
69,
0,
0,
0,
IF,
"case") 4 CONTINUE,
70,
0,
0,
0,
IF,
"continue") 5 EFAULT,
71,
0,
0,
0,
IF,
"default") 6 DO,
72,
0,
0,
0,
IF,
"do") 7 LSE,
73,
0,
0,
0,
IF,
"else") 8 EXTERN,
74,
0,
0,
0,
STATIC, "extern") 79 FOR,
75,
0,
0,
0,
IF,
"for") 0 GOTO,
76,
0,
0,
0,
IF,
"goto") 1 IF,
77,
0,
0,
0,
IF,
"if") 2 GISTER,
78,
0,
0,
0,
STATIC, "register") 3 RETURN,
79,
0,
0,
0,
IF,
"return") 4 IGNED,
80,
0,
0,
0,
CHAR,
"signed") 5 TATIC,
81,
0,
0,
0,
STATIC, "static") 6 SWITCH,
82,
0,
0,
0,
IF,
"switch") 7 TYPEDEF,
83,
0,
0,
0,
STATIC, "typedef") 8 WHILE,
84,
0,
0,
0,
IF,
"while") 89 TYPECODE,
85,
0,
0,
0,
ID,
"__typecode") 0 xx(FIRSTARG,
86,
0,
0,
0,
ID,
"__firstarg") 1 7 2 8 3 89 4 00,
0) 5 1"[",
"[") 6 20,
0,
0) 7 300,
0,
"]",
"]") 8 47BXOR,
bittree,"^",
"^") #099 5 #100 yy(0,
96,
0,
0,
0,
0,
0)
1 70,
0,
0,
0) 2 8 3 99,
0,
0,
0,
0,
0) 4 0 5 1 6 2 7 3 8 4 09 5 0 6 1 7 2 8 3 09 4 0 5 1 6 2 7 3 8 4 19 5 0 6 1 7 2 8 3 19 4 0 5 1 6 20,
0) 7 300,
0,
IF,
"{") 8 46BOR,
bittree,"|",
"|") 29 50,
0
"}",
"}") 0 yy(0,
126,
0,
BCOM,
0,
ID,
"~") 1 xx(EOI,
127,
0,
0,
0,
EOI,
"end of input") 2 xx #133 #undef yy 上面的构造了一个表格,这样可以灵活地构造任何的对应关系的表格。
因此生成的 kind[]数组如下:
kind[] = {0,CHAR,CHAR,…, EOI};
再回过头来分析 program,因此第 12 行和第 13 行用 kind[t]就是判断是否关键字开始,并判断记号 t 是否 ID,或者’*’,或者’(‘开始。如果是合法的记号开始,接着
在第 16 行就会调用函数 decl 来分析声明。第 18 行到第 22 行是删除分配内存空间。
第 24 行到第 28 行是取得记号 t 为分号,那这种情况是出错的,发出警告给软件开发人员,说明写了一行空行代码,接着获取下一个记号。
29 3 处理错误的声明,因为不能处理这种记号开始的声明。
第 36 行到第 39 行是当整个文件没有写一行代码的情况给出警告。
这样就可以循环地分析所有源程序,把所有声明和语义都分析出来,并且在遇到函数的定义时,就会调用后端生成汇编代码。LCC 编译器是一遍编译器,当语法和语义没有错误的情况下,一次分析就会生成所有的代码。
这样就开始了语法分析,下一步会调用函数 decl 来分析声明的开始,由于 C 程序是采用所有变量和函数都要先声明再使用的规则。
推荐访问:分析 源程序 编译器