准备好词法分析之后,接着的工作就是检查源程序是否合法,以及源程序表达的意思是什么。这两个问题就是语法和语义的分析,也就是把源程序里所包含的属性分析出来,并保存到符号表里。下面就来仔细地分析LCC编译器是怎么样处理这两个问题的。
#001t = gettok();
#002
#003//调用后端代码生成初始化工作。
#004(*IR->progbeg)(argc, argv);
#005
#006for (i = 1; i < argc; i++)
#007{
#008 if (strcmp(argv[i], "-n") == 0)
#009 {
#010 if (!YYnull)
#011 {
#012 YYnull = install(string("_YYnull"), &globals, GLOBAL, PERM);
#013 YYnull->type = func(voidptype, NULL, 1);
#014 YYnull->sclass = EXTERN;
#015 (*IR->defsymbol)(YYnull);
#016 }
#017
#018 }
#019 else if (strncmp(argv[i], "-n", 2) == 0)
#020 { /* -nvalid[,check] */
#021 char *p = strchr(argv[i], ',');
#022 if (p)
#023 {
#024 YYcheck = install(string(p+1), &globals, GLOBAL, PERM);
#025 YYcheck->type = func(voidptype, NULL, 1);
#026 YYcheck->sclass = EXTERN;
#027 (*IR->defsymbol)(YYcheck);
#028 p = stringn(argv[i]+2, p - (argv[i]+2));
#029 }
#030 else
#031 {
#032 p = string(argv[i]+2);
#033 }
#034
#035 YYnull = install(p, &globals, GLOBAL, PERM);
#036 YYnull->type = func(voidptype, NULL, 1);
#037 YYnull->sclass = EXTERN;
#038 (*IR->defsymbol)(YYnull);
#039
#040 }
#041 else
#042 {
#043 profInit(argv[i]);
#044 traceInit(argv[i]);
#045 }
#046}
#047
#048if (glevel && IR->stabinit)
#049{
#050 (*IR->stabinit)(firstfile, argc, argv);
#051}
#052
#053//开始整个程序处理。
#054program();
在获取一个记号之后,在第4行就调用后端代码生成器准备生成代码的初始化。接着第6行到第46行是处理-n参数,由于我们的例子里没有这些参数,就不会运行这些代码。把这些不是很重要的细节代码放下,我们继续地分析后面的代码。
第48行到第51行是调用后端stabinit进行初始化。
最重要的语法和语义分析开始了,它就是在第54行里调用函数program。由于这个C编译器是使用递归下降的分析方法来进行语法和语义分析的,所以在这个函数里面会有很多递归调用的函数。简单地来说递归调用就如下面的形式:
int Test1(void)
{
return Test3();
}
int Test2(void)
{
returnTest1();
}
int Test3(void)
{
return Test2();
}
上面只是形式说明一下,并不能进行运行的程序,现在就去分析C编译器的语法语义分析的总入口函数program:
//
//蔡军生2007/5/18 于深圳
//
#001//程序开始分析。
#002void program(void)
#003{
#004int n;
#005
#006//作用域为全局。
#007level = GLOBAL;
#008
#009//分析源程序到文件尾。
#010for (n = 0; t != EOI; n++)
#011{
#012 if (kind[t] == CHAR || kind[t] == STATIC
#013 || t == ID || t == '*' || t == '(')
#014 {
#015 //声明开始.
#016 decl(dclglobal);
#017
#018 deallocate(STMT);
#019 if (!(glevel >= 3 || xref))
#020 {
#021 deallocate(FUNC);
#022 }
#023 }
#024 else if (t == ';')
#025 {
#026 warning("empty declaration/n");
#027 t = gettok();
#028 }
#029 else
#030 {
#031 error("unrecognized declaration/n");
#032 t = gettok();
#033 }
#034}
#035
#036if (n == 0)
#037{
#038 warning("empty input file/n");
#039}
#040}
函数开始的第7行设置作用域为全局作用域GLOBAL。比如分析例子里的代码,在main函数以外的代码,都是全局作用域的。
第10行使用一个for循环,它的终止条件是分析源程序文件到结束,第11行到第34行里都是分析源程序的代码。
第12行和第13行就判断开始的记号是否合法的。先看一下,就发现有一个数组kind[t],并且用它来判断记号的合法性。这个数组的定义如下:
char kind[] = {
#define xx(a,b,c,d,e,f,g) f,
#define yy(a,b,c,d,e,f,g) f,
#include "token.h"
};
上面定义了两个宏,然后包含了头文件token.h,因为所有宏定义解释中在头文件里,如下:
#001/*
#002xx(symbol, value, prec, op, optree, kind, string)
#003*/
#004yy(0, 0, 0,0, 0, 0, 0)
#005xx(FLOAT, 1, 0,0, 0, CHAR, "float")
#006xx(DOUBLE, 2, 0,0, 0, CHAR, "double")
#007xx(CHAR, 3, 0,0, 0, CHAR, "char")
#008xx(SHORT, 4, 0,0, 0, CHAR, "short")
#009xx(INT, 5, 0,0, 0, CHAR, "int")
#010xx(UNSIGNED,6, 0,0, 0, CHAR, "unsigned")
#011xx(POINTER, 7, 0,0, 0, 0, "pointer")
#012xx(VOID, 8, 0,0, 0, CHAR, "void")
#013xx(STRUCT, 9, 0,0, 0, CHAR, "struct")
#014xx(UNION, 10, 0,0, 0, CHAR, "union")
#015xx(FUNCTION, 11, 0,0, 0, 0, "function")
#016xx(ARRAY, 12, 0,0, 0, 0, "array")
#017xx(ENUM, 13, 0,0, 0, CHAR, "enum")
#018xx(LONG, 14, 0,0, 0, CHAR, "long")
#019xx(CONST, 15, 0,0, 0, CHAR, "const")
#020xx(VOLATILE, 16, 0,0, 0, CHAR, "volatile")
#021yy(0, 17, 0, 0, 0, 0, 0)
#022yy(0, 18, 0, 0, 0, 0, 0)
#023yy(0, 19, 0,0, 0, 0, 0)
#024yy(0, 20, 0, 0, 0, 0, 0)
#025yy(0, 21, 0, 0, 0, 0, 0)
#026yy(0, 22, 0, 0, 0, 0, 0)
#027yy(0, 23, 0, 0, 0, 0, 0)
#028yy(0, 24, 0, 0, 0, 0, 0)
#029yy(0, 25, 0, 0, 0, 0, 0)
#030yy(0, 26, 0, 0, 0, 0, 0)
#031yy(0, 27, 0, 0, 0, 0, 0)
#032yy(0, 28, 0, 0, 0, 0, "long long")
#033yy(0, 29, 0, 0, 0, 0, 0)
#034yy(0, 30, 0, 0, 0, 0, 0)
#035yy(0, 31, 0, 0, 0, 0, "const volatile")
#036xx(ID, 32, 0, 0, 0, ID, "identifier")
#037yy(0, 33, 0, 0, 0, ID, "!")
#038xx(FCON, 34, 0, 0, 0, ID, "floating constant")
#039xx(ICON, 35, 0, 0,0, ID, "integer constant")
#040xx(SCON, 36, 0, 0, 0, ID, "string constant")
#041yy(0, 37, 13, MOD,bittree,'%', "%")
#042yy(0, 38, 8, BAND, bittree,ID, "&")
#043xx(INCR, 39, 0, ADD,addtree,ID, "++")
#044yy(0, 40, 0, 0, 0, ID, "(")
#045yy(0, 41, 0, 0, 0, ')', ")")
#046yy(0, 42, 13, MUL,multree,ID, "*")
#047yy(0, 43, 12, ADD,addtree,ID, "+")
#048yy(0, 44, 1,0, 0, ',', ",")
#049yy(0, 45, 12, SUB,subtree,ID, "-")
#050yy(0, 46, 0,0, 0, '.', ".")
#051yy(0, 47, 13, DIV,multree,'/', "/")
#052xx(DECR, 48, 0,SUB,subtree,ID, "--")
#053xx(DEREF, 49, 0,0, 0, DEREF,"->")
#054xx(ANDAND, 50, 5,AND,andtree,ANDAND, "&&")
#055xx(OROR, 51, 4,OR, andtree,OROR, "||")
#056xx(LEQ, 52, 10, LE, cmptree,LEQ, "<=")
#057xx(EQL, 53, 9, EQ, eqtree, EQL, "==")
#058xx(NEQ, 54, 9, NE, eqtree, NEQ, "!=")
#059xx(GEQ, 55, 10, GE, cmptree,GEQ, ">=")
#060xx(RSHIFT, 56, 11, RSH, shtree, RSHIFT, ">>")
#061xx(LSHIFT, 57, 11, LSH, shtree, LSHIFT, "<<")
#062yy(0, 58, 0, 0, 0, ':', ":")
#063yy(0, 59, 0, 0, 0, IF, ";")
#064yy(0, 60, 10, LT, cmptree,'<', "<")
#065yy(0, 61, 2, ASGN, asgntree,'=', "=")
#066yy(0, 62, 10, GT, cmptree,'>', ">")
#067yy(0, 63, 0, 0, 0, '?', "?")
#068xx(ELLIPSIS, 64, 0, 0, 0, ELLIPSIS,"...")
#069xx(SIZEOF, 65, 0, 0, 0, ID, "sizeof")
#070yy(0, 66, 0, 0, 0, 0, 0)
#071xx(AUTO, 67, 0, 0, 0, STATIC, "auto")
#072 xx(BREAK, 68, 0, 0, 0, IF, "break")
#073xx(CASE, 69, 0, 0, 0, IF, "case")
#074xx(CONTINUE, 70, 0, 0, 0, IF, "continue")
#075xx(DEFAULT, 71, 0, 0, 0, IF, "default")
#076xx(DO, 72, 0, 0, 0, IF, "do")
#077xx(ELSE, 73, 0, 0, 0, IF, "else")
#078xx(EXTERN, 74, 0, 0, 0, STATIC, "extern")
#079xx(FOR, 75, 0, 0, 0, IF, "for")
#080xx(GOTO, 76, 0, 0, 0, IF, "goto")
#081xx(IF, 77, 0, 0, 0, IF, "if")
#082xx(REGISTER, 78, 0, 0, 0, STATIC, "register")
#083xx(RETURN, 79, 0, 0, 0, IF, "return")
#084xx(SIGNED, 80, 0, 0, 0, CHAR, "signed")
#085xx(STATIC, 81, 0, 0, 0, STATIC, "static")
#086xx(SWITCH, 82, 0,0, 0, IF, "switch")
#087xx(TYPEDEF, 83, 0, 0, 0, STATIC, "typedef")
#088xx(WHILE, 84, 0, 0, 0, IF, "while")
#089xx(TYPECODE, 85, 0, 0, 0, ID, "__typecode")
#090xx(FIRSTARG, 86, 0, 0, 0, ID, "__firstarg")
#091yy(0, 87, 0, 0, 0, 0, 0)
#092yy(0, 88, 0, 0, 0, 0, 0)
#093yy(0, 89, 0, 0, 0, 0, 0)
#094yy(0, 90, 0, 0, 0, 0, 0)
#095yy(0, 91, 0, 0, 0, '[', "[")
#096yy(0, 92, 0, 0, 0, 0, 0)
#097yy(0, 93, 0, 0,0, ']', "]")
#098yy(0, 94, 7, BXOR, bittree,'^', "^")
#099yy(0, 95, 0, 0, 0, 0, 0)
#100yy(0, 96, 0, 0, 0, 0, 0)
#101yy(0, 97, 0,0, 0, 0, 0)
#102yy(0, 98, 0, 0, 0, 0, 0)
#103yy(0, 99, 0, 0, 0, 0, 0)
#104yy(0, 100, 0, 0, 0, 0, 0)
#105yy(0, 101, 0, 0, 0, 0, 0)
#106yy(0, 102, 0, 0, 0, 0, 0)
#107yy(0, 103, 0, 0, 0, 0, 0)
#108yy(0, 104, 0, 0, 0, 0, 0)
#109yy(0, 105, 0, 0, 0, 0, 0)
#110yy(0, 106, 0, 0, 0, 0, 0)
#111yy(0, 107, 0, 0, 0, 0, 0)
#112yy(0, 108, 0, 0, 0, 0, 0)
#113yy(0, 109, 0, 0, 0, 0, 0)
#114yy(0, 110, 0, 0, 0, 0, 0)
#115yy(0, 111, 0, 0, 0, 0, 0)
#116yy(0, 112, 0, 0, 0, 0, 0)
#117yy(0, 113, 0, 0, 0, 0, 0)
#118yy(0, 114, 0, 0, 0, 0, 0)
#119yy(0, 115, 0, 0, 0, 0, 0)
#120yy(0, 116, 0, 0, 0, 0, 0)
#121yy(0, 117, 0, 0, 0, 0, 0)
#122yy(0, 118, 0, 0, 0, 0, 0)
#123yy(0, 119, 0, 0, 0, 0, 0)
#124yy(0, 120, 0, 0, 0, 0, 0)
#125yy(0, 121, 0, 0, 0, 0, 0)
#126yy(0, 122, 0, 0, 0, 0, 0)
#127yy(0, 123, 0, 0, 0, IF, "{")
#128yy(0, 124, 6, BOR, bittree,'|', "|")
#129yy(0, 125, 0, 0, 0, '}', "}")
#130yy(0, 126, 0, BCOM, 0, ID, "~")
#131xx(EOI, 127, 0, 0, 0, EOI, "end of input")
#132#undef 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行到第33行是处理错误的声明,因为不能处理这种记号开始的声明。
第36行到第39行是当整个文件没有写一行代码的情况给出警告。
这样就可以循环地分析所有源程序,把所有声明和语义都分析出来,并且在遇到函数的定义时,就会调用后端生成汇编代码。LCC编译器是一遍编译器,当语法和语义没有错误的情况下,一次分析就会生成所有的代码。
这样就开始了语法分析,下一步会调用函数decl来分析声明的开始,由于C程序是采用所有变量和函数都要先声明再使用的规则。
本文介绍LCC编译器如何通过递归下降的方法进行语法和语义分析,包括如何处理源程序中的声明和语义,以及如何生成后端代码。
2613

被折叠的 条评论
为什么被折叠?



