我们完成了对词法分析的获取函数next(),现在我们尝试根据token分析,和我们的语法规则,进行简单的语法分析。

解析变量

我们的解释器的语法结构,可以用下面的EBNF的表示法直观的体现:

program ::= {global_declaration}+

global_declaration ::= enum_decl | variable_decl | function_decl

enum_decl ::= 'enum' [id] '{' id ['=' 'num'] {',' id ['=' 'num'} '}'

variable_decl ::= type {'*'} id { ',' {'*'} id } ';'

function_decl ::= type {'*'} id '(' parameter_decl ')' '{' body_decl '}'

parameter_decl ::= type {'*'} id {',' type {'*'} id}

body_decl ::= {variable_decl}, {statement}

statement ::= non_empty_statement | empty_statement

non_empty_statement ::= if_statement | while_statement | '{' statement '}'
| 'return' expression | expression ';'

if_statement ::= 'if' '(' expression ')' statement ['else' non_empty_statement]

while_statement ::= 'while' '(' expression ')' non_empty_statement

对于我们的程序而言,一切都是从对global_delartion开始:

  • 变量定义
  • 类型定义(目前只支持enum)
  • 函数定义

我们的program()作为语法解析函数,框架如下:

void program() {
    next();
    while(token > 0){
        global_declaration();
    }
}

global_declaration

即全局定义的语句,我们通过递归下降的方法,来判断当前的定义类型,具体实现如下:

void global_declaration(){
    int type;
    int i;
    basetype = INT;
    if(token == Enum){
        match(Enum);
        if(token != '{') match(Id);
        else{
            match('{');
            enum_declaration();
            match('}');
        } 
        match(';');
        return;
    }else if(token==Int){
        match(Int);
    }else if(token==Char){
        match(Char);
        basetype = CHAR;
    }

    while(token != ';' && token != '{' && token != '}'){
        type = basetype;
        // 指针处理
        while(token==Mul){
            match(Mul);
            type = type + PTR;
        }
        if (token!=Id){
            printf("%d: bad global declaration\n", line);
            exit(-1);
        }
        if (current_id->class != 0){
            printf("%d: duplicate global declaration\n", line);
            exit(-1);
        }
        match(Id);
        current_id->type = type;

        if (token=='('){
            current_id->class = Fun;
            current_id->value = (int)(text);
            function_declaration();
        }else{
            current_id->class = Glo;
            current_id->value = (int)(data);
            data += sizeof(int);
        }
        if(token==','){
            match(',');
        }
    }
    next();
}

这里我们使用的依旧是lookahead的思想,我们无法根据一个token信息就实现对这一部分的语法功能解析,所以我们需要结合之后的信息,来对定义进行判断。

我们的解释器同时也支持指针类型,我们这里使用type = type+PTR的方式,来表示其包含指针的信息。

enum_declaration

主要用来解析用,分隔的变量,这里我们需要注意编译器对枚举变量的实现:

void enum_declaration(){
    int i=0;
    while(token!='}'){
        if(token!=Id){
            printf("%d: 错误的枚举变量声明\n", line);
            exit(-1);
        }
        next();
        if(token=='='){
            match('=');
            if(token!=Num){
                printf("%d: 枚举变量赋值错误\n", line);
                exit(-1);
            }
            i = token_val;
            next();
        }
        current_id->class = Num;
        current_id->type = INT;
        current_id->value = i++;
        if(token==',') next();
    }
}

辅助函数match

这里我们使用match来匹配并获取下一个token:

void match(int tk){
    if(token == tk){
        next();
    }else{
        printf("%d: 语法错误, 缺少 %c\n", line, tk);
        exit(-1);
    }
}

函数定义的解析

我们在先前的函数Id判断中:

...
if (token == '(') {
current_id[Class] = Fun;
current_id[Value] = (int)(text); 
function_declaration();
} else {
...

在确定是函数类型之后,我们开始对函数结构的解析,我们对函数体的解析结构如下:

void function_declaration(){
    match('(');
    function_parameters();
    match(')');
    match('{');
    function_body();
    // match('}');
    while(current_id->token){
        if(current_id->class==Loc){
            current_id->class=current_id->Bclass;
            current_id->type=current_id->Btype;
            current_id->value=current_id->Bvalue;
        }
        current_id++;
    }
}

这里需要注意两个点:

  • 一是在对函数体的分析结束之后,不需要match("}"),因为函数体解析的函数会匹配他
  • 二是在函数解析完毕之后,我们需要将用到的token信息还原。这是因为在函数体解析的过程中,同名的局部变量会把原来定义的全局变量信息覆盖。

进一步的实现,则分别是对参数和函数体的内容的解析,首先是对参数的解析:

void function_parameters(){
    int type;
    int params = 0;
    type = basetype = INT;
    while(token!=')'){
       if(token==Int) match(Int);
       else if(token==Char){
            match(Char);
            type = CHAR;
       }
       while(token==Mul){
            match(Mul);
            type = type + PTR;
       }
       if(token!=Id){
            printf("%d: 错误的函数参数声明\n", line);
            exit(-1);
        }
        if (current_id->class != 0){
            printf("%d: 重复定义了局部变量-^-\n", line);
            exit(-1);
        }
        match(Id);

        current_id->Bclass = current_id->class;
        current_id->Btype = current_id->type;
        current_id->Bvalue = current_id->value;
        current_id->class = Loc;
        current_id->type = type;
        current_id->value = ++params;
        
        if(token==',') match(',');
    }
}

这里我们需要注意两点,首先是index_of_bp,这个变量用于确定参数在栈上的偏移值,因为从bp到函数传参之间隔着一个返回地址,所以在计算时需要将index_of_bp加一。

其次是,当我们遇到一个Id类型的token,我们将其所有的信息都备份一遍,并且将值赋值为参数的索引

然后是对函数体的解析,我们可以进一步分成局部变量声明和语句部分:

void function_body(){
    int pos_local = 0;
    int type;

    while(token==Int||token==Char){
        basetype = (token==Int ? INT : CHAR);
        match(token);
        while(token!= ';'){
            type = basetype;
            while(token==Mul){
                match(Mul);
                type = type + PTR;
            }
            if (token!=Id){
                printf("%d: 错误的局部变量声明\n", line);
                exit(-1);
            }
            if (current_id->class != 0){
                printf("%d: 重复定义了局部变量-^-\n", line);
                exit(-1);
            }
            match(Id);
            current_id->Bclass = current_id->class;
            current_id->Btype = current_id->type;
            current_id->Bvalue = current_id->value;
            current_id->class = Loc;
            current_id->type = type;
            current_id->value = ++pos_local;
            if(token==',') match(',');
        }
        match(';');
    }
    *++text = ENT;  // 函数入口指令
    *++text = pos_local; // 局部变量大小
    // 语句处理
    while(token != '}'){
        statement();
    }
    *++text = LEV;  // 函数返回指令
}

这一部分需要注意的就是*++text的含义,相当于我们在栈上为局部变量预留了空间,后面的LEV则是用来返回函数调用。

至此我们对全局变量和函数定义的解析完成了,之后我们将着重实现对语句的实现,并为他们生成可用的字节码指令。