THE LITTLE C INTERPRETER
这一章节,将会开发C解释器的核心部分。在阅读解释器的源码之前,如果我们能理解解释器怎样运作将大有裨益。在许多方面,解释器的代码都比表达式剖析器的更容易理解,因为在概念上,解释执行C程序可以用以下算法概括起来:
while(tokens_present){
get_next_token;
take_appropriate_action;
}
这段代码和表达式剖析器比起来,看起来简单得不可思议,但这确实就是解释器所作的一切!记住一件事情就是“take_appropriate_action”这一步也许需要从输入流中读取标记。
1)THE INTERPRETER PRESCAN(解释器预扫描)
在解释器能够准确地执行程序之前,有少量的工作必须做。在心中设计一个解释器不是编译器的特点就是它从源程序开头开始执行,然后到源程序结尾为止。这就是传统BASIC的工作方式。无论如何,C(或任何其他的结构化语言)无法适用于这种方式。首先,所有的C的程序从main()函数开始执行。我们不要求main()在程序中必须是第 一个函数;因此,在开始执行时必须弄清main()函数在源码中的位置。(记住全局变量也许走在main()之前,所以虽然它是第一个函数,也没有必要写在第一行。)必须找到一些办法让解释器从正确的地方开始解释。
最后为了执行速度,在程序中定义每个函数的位置以便能够尽快地调用函数。(但在技术上不是必需的。)如果这一步没有做,每次调用函数时,都将需要一个对源码的冗长的顺序查找以确定函数的入口。
解决这些问题的方法叫做interpreter prescan(解释器预扫描)。扫描器(有时也被称作预处理程序)被所有的商业解释器所使用,无论何种语言。在执行之前,一个预扫描器先会读取源代码,然后有些任务将优先执行。在我们的C解释器中,它将做两件重要的事:首先,寻找并标记所有永和定义函数的位置,包括main()函数;其二,寻找并分配空间给所有的全局变量。以下是预扫描部分prescan()的代码:
void prescan()
{
char *p;
char temp[32];
int brace=0; /*when 0,this var tells us that
current source position is
outside of any function
*/
p=prog;
func_index=0;
do{
while (brace){
get_token();
if (*token=='{') brace++;
if (*token=='}') brace--;
}
get_token();
if (tok==CHAR||tok==INT){
putback();
decl_global();
}
else if (token_type==IDENTIFIER){
strcpy(temp,token);
get_token();
if (*token=='('){
func_table[func_index].loc=prog;
strcpy(func_table[func_index].func_name,temp);
func_index++;
while (*prog!=')') prog++;
prog++;
}
else putback();
}
else if (*token=='{') brace++;
}while(tok!=FINISHED);
prog=p;
}
prescan()函数时这样工作的。每次遇到开口大括号“{”时,brace就增加。当读到闭口大括号“}”时,brace就减少。因此,只要brace大于零,当前的标记就会从函数中被读入。然而如果当找到一个变量时,brace等于零,则预扫描器就会知道它一定是全局变量。同理,如果找到一个函数名且brace等于0时,那么它一定是函数的定义。
全局变量被decl_global()储存在一个叫做global_vars的全局变量表里,代码如下:
/*
An array of these structures will hold the info
associated with global variables
*/
struct var_type {
char var_name[ID_LEN];
int var_type;
int value;
} global_vars[NUM_GLOBAL_VARS];
int gvar_index; /*index into global variable table*/
/*Declare a global variable*/
void decl_global()
{
get_token();
global_vars[gvar_index].var_type=tok;
global_vars[gvar_index].value=0;
do{
get_token();
strcpy(global_vars[gvar_index].var_name,token);
get_token();
gvar_index++;
}while (*token==',');
if (*token!=';') sntx_err(SEMI_EXPECTED);
}
整数型的变量gvar_index将在数组中存入下一个要素的空余位置。每次用户自定义函数的位置将被放入func_table数组中,代码如下:
struct func_type {
char func_name[ID_LEN];
char *loc; /*location of entry point in file*/
} func_table[NUM_FUNC];
int func_index; /*index into function table*/
2)The main() Function
main()函数是如此工作的:载入源代码,全局变量初始化,调用prescan(),让解释器为调用main()“做好准备”,然后执行call(),代码如下:
int main(int argc,char *argv[])
{
if(argc!=2){
printf("usage:littlec \n");
exit (1);
}
/*allocate memory for the program*/
if ((p_buf=(char *) malloc(PROG_SIZE))==NULL){
printf ("allocation failure");
exit (1);
}
/*load the program to execute*/
if (!load_program(p_buf,argv[1])) exit(1);
if (setjmp(e_buf)) exit(1);
/*set program pointer to start of program buffer*/
prog=p_buf;
prescan ();
gvar_index=0;
lvartos=0;
functos=0;
/*setup call to main()*/
prog=find_func("main");
prog--;
strcpy(token,"main");
call();
return 0;
}
3)The interp_block() Function
interp_block()函数时解释器的核心。这个函数将基于输入流中的下一个标记来决定采取什么动作。这个函数被设计成解释一个程序块然后再返回。如果“程序块(block)”由单个的语句组成,这个语句将被执行然后函数返回。代码如下:
/*
Interpret a single statement or block of code.
When interp_block() returns from its initial call,
the final brace (or a return in main() has been encountered.
*/
void interp_block()
{
int value;
char block=0;
do {
token_type=get_token();
/*see what kind of token is up*/
if (token_type==IDENTIFIER) {
putback ();
eval_exp(&value);
if (*token!=';') sntx_err(SEMI_EXPECTED);
}
else if(token_type==BLOCK){
if (*token=='{')
block=1;
else return;
}
else
switch (tok){
case CHAR:
case INT:
putback();
decl_local();
break;
case RETURN:
func_ret();
return;
case IF:
exec_if();
break;
case ELSE:
find_eob();
break;
case WHILE:
exec_while();
break;
case DO:
exec_do();
break;
case FOR:
exec_for();
break;
case END:
exit(0);
}
} while (tok!=FINISHED&&block);
}
除了调用像exit()的函数,当main()里遇见最后一个大括号时,C程序也将结束。这就是interp_block()函数只执行一句或一块代码,而不是整个程序的原因。