LCC编译器的源程序分析(8)语法分析的开始

准备好词法分析之后,接着的工作就是检查源程序是否合法,以及源程序表达的意思是什么。这两个问题就是语法和语义的分析,也就是把源程序里所包含的属性分析出来,并保存到符号表里。下面就来仔细地分析LCC编译器是怎么样处理这两个问题的。
#001 t = gettok();
#002
#003 //调用后端代码生成初始化工作。
#004 (*IR->progbeg)(argc, argv);
#005
#006 for (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
#048 if (glevel && IR->stabinit)
#049 {
#050 (*IR->stabinit)(firstfile, argc, argv);
#051 }
#052
#053 //开始整个程序处理。
#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 于深圳
//
#001 //程序开始分析。
#002 void program(void)
#003 {
#004 int n;
#005
#006 //作用域为全局。
#007 level = GLOBAL;
#008
#009 //分析源程序到文件尾。
#010 for (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
#036 if (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 /*
#002 xx(symbol, value, prec, op, optree, kind, string)
#003 */
#004 yy(0, 0, 0, 0, 0, 0, 0)
#005 xx(FLOAT, 1, 0, 0, 0, CHAR, "float")
#006 xx(DOUBLE, 2, 0, 0, 0, CHAR, "double")
#007 xx(CHAR, 3, 0, 0, 0, CHAR, "char")
#008 xx(SHORT, 4, 0, 0, 0, CHAR, "short")
#009 xx(INT, 5, 0, 0, 0, CHAR, "int")
#010 xx(UNSIGNED, 6, 0, 0, 0, CHAR, "unsigned")
#011 xx(POINTER, 7, 0, 0, 0, 0, "pointer")
#012 xx(VOID, 8, 0, 0, 0, CHAR, "void")
#013 xx(STRUCT, 9, 0, 0, 0, CHAR, "struct")
#014 xx(UNION, 10, 0, 0, 0, CHAR, "union")
#015 xx(FUNCTION, 11, 0, 0, 0, 0, "function")
#016 xx(ARRAY, 12, 0, 0, 0, 0, "array")
#017 xx(ENUM, 13, 0, 0, 0, CHAR, "enum")
#018 xx(LONG, 14, 0, 0, 0, CHAR, "long")
#019 xx(CONST, 15, 0, 0, 0, CHAR, "const")
#020 xx(VOLATILE, 16, 0, 0, 0, CHAR, "volatile")
#021 yy(0, 17, 0, 0, 0, 0, 0)
#022 yy(0, 18, 0, 0, 0, 0, 0)
#023 yy(0, 19, 0, 0, 0, 0, 0)
#024 yy(0, 20, 0, 0, 0, 0, 0)
#025 yy(0, 21, 0, 0, 0, 0, 0)
#026 yy(0, 22, 0, 0, 0, 0, 0)
#027 yy(0, 23, 0, 0, 0, 0, 0)
#028 yy(0, 24, 0, 0, 0, 0, 0)
#029 yy(0, 25, 0, 0, 0, 0, 0)
#030 yy(0, 26, 0, 0, 0, 0, 0)
#031 yy(0, 27, 0, 0, 0, 0, 0)
#032 yy(0, 28, 0, 0, 0, 0, "long long")
#033 yy(0, 29, 0, 0, 0, 0, 0)
#034 yy(0, 30, 0, 0, 0, 0, 0)
#035 yy(0, 31, 0, 0, 0, 0, "const volatile")
#036 xx(ID, 32, 0, 0, 0, ID, "identifier")
#037 yy(0, 33, 0, 0, 0, ID, "!")
#038 xx(FCON, 34, 0, 0, 0, ID, "floating constant")
#039 xx(ICON, 35, 0, 0, 0, ID, "integer constant")
#040 xx(SCON, 36, 0, 0, 0, ID, "string constant")
#041 yy(0, 37, 13, MOD, bittree,'%', "%")
#042 yy(0, 38, 8, BAND, bittree,ID, "&")
#043 xx(INCR, 39, 0, ADD, addtree,ID, "++")
#044 yy(0, 40, 0, 0, 0, ID, "(")
#045 yy(0, 41, 0, 0, 0, ')', ")")
#046 yy(0, 42, 13, MUL, multree,ID, "*")
#047 yy(0, 43, 12, ADD, addtree,ID, "+")
#048 yy(0, 44, 1, 0, 0, ',', ",")
#049 yy(0, 45, 12, SUB, subtree,ID, "-")
#050 yy(0, 46, 0, 0, 0, '.', ".")
#051 yy(0, 47, 13, DIV, multree,'/', "/")
#052 xx(DECR, 48, 0, SUB, subtree,ID, "—")
#053 xx(DEREF, 49, 0, 0, 0, DEREF, "->")
#054 xx(ANDAND, 50, 5, AND, andtree,ANDAND, "&&")
#055 xx(OROR, 51, 4, OR, andtree,OROR, "||")
#056 xx(LEQ, 52, 10, LE, cmptree,LEQ, "<=")
#057 xx(EQL, 53, 9, EQ, eqtree, EQL, "==")
#058 xx(NEQ, 54, 9, NE, eqtree, NEQ, "!=")
#059 xx(GEQ, 55, 10, GE, cmptree,GEQ, ">=")
#060 xx(RSHIFT, 56, 11, RSH, shtree, RSHIFT, "»")
#061 xx(LSHIFT, 57, 11, LSH, shtree, LSHIFT, "«")
#062 yy(0, 58, 0, 0, 0, ':', ":")
#063 yy(0, 59, 0, 0, 0, IF, ";")
#064 yy(0, 60, 10, LT, cmptree,'<', "<")
#065 yy(0, 61, 2, ASGN, asgntree,'=', "=")
#066 yy(0, 62, 10, GT, cmptree,'>', ">")
#067 yy(0, 63, 0, 0, 0, '?', "?")
#068 xx(ELLIPSIS, 64, 0, 0, 0, ELLIPSIS,"…")
#069 xx(SIZEOF, 65, 0, 0, 0, ID, "sizeof")
#070 yy(0, 66, 0, 0, 0, 0, 0)
#071 xx(AUTO, 67, 0, 0, 0, STATIC, "auto")
#072 xx(BREAK, 68, 0, 0, 0, IF, "break")
#073 xx(CASE, 69, 0, 0, 0, IF, "case")
#074 xx(CONTINUE, 70, 0, 0, 0, IF, "continue")
#075 xx(DEFAULT, 71, 0, 0, 0, IF, "default")
#076 xx(DO, 72, 0, 0, 0, IF, "do")
#077 xx(ELSE, 73, 0, 0, 0, IF, "else")
#078 xx(EXTERN, 74, 0, 0, 0, STATIC, "extern")
#079 xx(FOR, 75, 0, 0, 0, IF, "for")
#080 xx(GOTO, 76, 0, 0, 0, IF, "goto")
#081 xx(IF, 77, 0, 0, 0, IF, "if")
#082 xx(REGISTER, 78, 0, 0, 0, STATIC, "register")
#083 xx(RETURN, 79, 0, 0, 0, IF, "return")
#084 xx(SIGNED, 80, 0, 0, 0, CHAR, "signed")
#085 xx(STATIC, 81, 0, 0, 0, STATIC, "static")
#086 xx(SWITCH, 82, 0, 0, 0, IF, "switch")
#087 xx(TYPEDEF, 83, 0, 0, 0, STATIC, "typedef")
#088 xx(WHILE, 84, 0, 0, 0, IF, "while")
#089 xx(TYPECODE, 85, 0, 0, 0, ID, "typecode")
#090 xx(FIRSTARG, 86, 0, 0, 0, ID, "
firstarg")
#091 yy(0, 87, 0, 0, 0, 0, 0)
#092 yy(0, 88, 0, 0, 0, 0, 0)
#093 yy(0, 89, 0, 0, 0, 0, 0)
#094 yy(0, 90, 0, 0, 0, 0, 0)
#095 yy(0, 91, 0, 0, 0, '[', "[")
#096 yy(0, 92, 0, 0, 0, 0, 0)
#097 yy(0, 93, 0, 0, 0, ']', "]")
#098 yy(0, 94, 7, BXOR, bittree,'^', "^")
#099 yy(0, 95, 0, 0, 0, 0, 0)
#100 yy(0, 96, 0, 0, 0, 0, 0)
#101 yy(0, 97, 0, 0, 0, 0, 0)
#102 yy(0, 98, 0, 0, 0, 0, 0)
#103 yy(0, 99, 0, 0, 0, 0, 0)
#104 yy(0, 100, 0, 0, 0, 0, 0)
#105 yy(0, 101, 0, 0, 0, 0, 0)
#106 yy(0, 102, 0, 0, 0, 0, 0)
#107 yy(0, 103, 0, 0, 0, 0, 0)
#108 yy(0, 104, 0, 0, 0, 0, 0)
#109 yy(0, 105, 0, 0, 0, 0, 0)
#110 yy(0, 106, 0, 0, 0, 0, 0)
#111 yy(0, 107, 0, 0, 0, 0, 0)
#112 yy(0, 108, 0, 0, 0, 0, 0)
#113 yy(0, 109, 0, 0, 0, 0, 0)
#114 yy(0, 110, 0, 0, 0, 0, 0)
#115 yy(0, 111, 0, 0, 0, 0, 0)
#116 yy(0, 112, 0, 0, 0, 0, 0)
#117 yy(0, 113, 0, 0, 0, 0, 0)
#118 yy(0, 114, 0, 0, 0, 0, 0)
#119 yy(0, 115, 0, 0, 0, 0, 0)
#120 yy(0, 116, 0, 0, 0, 0, 0)
#121 yy(0, 117, 0, 0, 0, 0, 0)
#122 yy(0, 118, 0, 0, 0, 0, 0)
#123 yy(0, 119, 0, 0, 0, 0, 0)
#124 yy(0, 120, 0, 0, 0, 0, 0)
#125 yy(0, 121, 0, 0, 0, 0, 0)
#126 yy(0, 122, 0, 0, 0, 0, 0)
#127 yy(0, 123, 0, 0, 0, IF, "{")
#128 yy(0, 124, 6, BOR, bittree,'|', "|")
#129 yy(0, 125, 0, 0, 0, '}', "}")
#130 yy(0, 126, 0, BCOM, 0, ID, "~")
#131 xx(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程序是采用所有变量和函数都要先声明再使用的规则。

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License