概述
上一篇其实只做了一个小小改动,增加了打印方法,本质上没改变什么。当前实现的解析器,它一边读入字符,一边分析,碰到一个语句就执行一个,这种方式往后难以扩展。比如引入函数,定义函数,函数的内容也是语句,解析函数定义时肯定不能执行其中的语句,函数内的语句只能在函数调用时才执行,并且还跟函数调用时的上下文有关。这一篇,我们要做的就是改造解析执行流程,解析的过程只解析,把解析的内容保存下来,最后再执行所有内容。
动手
修改类型定义
修改nl.h,修改后的完整代码在这里。
枚举类型ExpressionType记录了所有表达式类型,现在要加上变量表达式(VARIABLE_EXPRESSION),和负数表达式(MINUS_EXPRESSION)。
因为原来是边解析边计算,遇到取负数时,被取负数的那个数肯定已经算出来了,直接给它取负数即可,但现在不能一边解析一边计算了,被取负数的对象可能是一个负载的表达式,当前你不知道它的值,你只能先记录这个表达式,后续执行时,计算出这个表达式的值再取负数。
同理,变量表达式记录了一个变量,解析时还不知道这个变量的值,特别是在函数中时,函数未调用前,都不确定里面的变量会变成什么值。
加上后,定义变成这样:
typedef enum {
INT_EXPRESSION = 1,
DOUBLE_EXPRESSION,
ADD_EXPRESSION,
SUB_EXPRESSION,
MUL_EXPRESSION,
DIV_EXPRESSION,
MOD_EXPRESSION,
VARIABLE_EXPRESSION,
MINUS_EXPRESSION,
EXPRESSION_TYPE_PLUS
} ExpressionType;
加一种二元表达式取值类型,加减乘除这些表达式都是二元表达式,对于二元表达式,操作符左右的操作数也依然是表达式,需要记录起来。
typedef struct {
Expression *left;
Expression *right;
} BinaryExpression;
表达式定义结构体Expression_tag,之前它的取值只有整数或者浮点数,定义成下面这样,u是一个联合体,根据type不同,u取不同值,之前都是直接计算出每个表达式的结果,所以只需要记录整数或者浮点数即可。
struct Expression_tag {
ExpressionType type;
union {
int int_value;
double double_value;
} u;
};
改成解析阶段不计算结果,解析时不同表达式记录关键信息,变量表达式需要记录变量名,负数表达式需要记录被取负值的表达式,执行时能计算该表达式的值,然后取负得到最终结果。而所有二元表达式需构造上面定义的BinaryExpression结构体来记录,修改后如下:
struct Expression_tag {
ExpressionType type;
union {
int int_value;
double double_value;
char *identifier;
Expression *minus_expression;
BinaryExpression binary_expression;
} u;
};
各种语句也需在解析过程一一记录。之前并没有使用语句类型结构体,因为之前解析到表达式直接得出计算结果,还没用到语句类型的结构体,而赋值语句也是解析到之后就创建变量或直接赋值,现在解析到一个语句后不做执行计算,当然要先记录下来,在执行阶段取执行。
给语句类型枚举StatementType加上赋值语句类型(ASSIGN_STATEMENT)和打印语句类型(PRINT_STATEMENT):
typedef enum {
EXPRESSION_STATEMENT = 1,
ASSIGN_STATEMENT,
PRINT_STATEMENT,
STATEMENT_TYPE_PLUS
} StatementType;
定义赋值语句结构体,赋值语句关键信息是被赋值的变量名,以及值来源的表达式。
typedef struct {
char *identifier;
Expression *expression;
} AssignStatement;
添加语句结构体,type记录是什么类型语句,u是一个联合体记录关键信息。表达式语句和打印语句只需要记录目标语句即可,当前的打印语句还比较简单,赋值语句就用刚新加的赋值语句结构体记录。
typedef struct {
StatementType type;
union {
Expression *expression;
AssignStatement assign;
} u;
} Statement;
有了单个语句,还需要有语句列表,表示一连串语句,这里用链表来表示,简单粗暴。
typedef struct StatementList_tag {
Statement *statement;
struct StatementList_tag *next;
} StatementList;
修改一下全局控制变量king的结构体定义,多加了语句列表的记录,最终解析完所有语句都记录在这上面。
struct King_tag {
Variable *variable;
StatementList *statement_list;
};
还有一些新方法的定义,都添加到这个文件,下面会讲到。
增加/修改创建方法
修改create.c,改变个别已有方法,添加新的方法。完整文件内容在这里
之前已有的nl_create_variable_expression方法用于创建一个变量表达式,它的逻辑是直接找king拿到那个变量的值,拿到的是整数或浮点数值,然后再转成表达式返回,所以实际拿到的就是一个整数或浮点数表达式。要解析和执行分开,这里要创建一个真正的变量表达式,记录关键的变量名即可。
Expression *
nl_create_variable_expression(char *identifier) {
Expression *exp;
exp = nl_alloc_expression(VARIABLE_EXPRESSION);
exp->u.identifier = identifier;
return exp;
}
修改创建二元表达式的方法nl_create_binary_expression,原来该方法直接拿到两个操作数left和right计算出值,返回整数/浮点数表达式。
新逻辑先判断两个操作数的类型,如果两者都是现成的整数或浮点数,可以直接计算结果返回整数/浮点数表达式,比如操作数都是数字常量,则解析阶段就可以直接计算结果。
若两个操作数有一个或以上是别的类型表达式,意味着当前还不知道值,就需要直接把左右表达式记录下来,用表达式记录值的联合体中的binary_expression来记录。
Expression *
nl_create_binary_expression(ExpressionType type, Expression *left, Expression *right) {
if ((left->type == INT_EXPRESSION || left->type == DOUBLE_EXPRESSION)
&& (right->type == INT_EXPRESSION || right->type == DOUBLE_EXPRESSION)) {
NL_Value v;
v = nl_eval_binary_expression(type, left, right);
left = convert_value_to_expression(&v);
return left;
} else {
Expression *exp;
exp = nl_alloc_expression(type);
exp->u.binary_expression.left = left;
exp->u.binary_expression.right = right;
return exp;
}
}
修改负数表达式,若目标表达式类型是整数或浮点数,可用原来的逻辑,直接计算取负数的结果返回,否则就要分配空间创建负数表达式类型的表达式,记录目标表达式。
Expression *
nl_create_minus_expression(Expression *exp) {
Expression *result;
if (exp->type == INT_EXPRESSION) {
exp->u.int_value = -exp->u.int_value;
result = exp;
} else if (exp->type == DOUBLE_EXPRESSION) {
exp->u.double_value = -exp->u.double_value;
result = exp;
} else {
result = nl_alloc_expression(MINUS_EXPRESSION);
result->u.minus_expression = exp;
}
return result;
}
接着还要添加一系列语句相关的创建方法。
分配空间创建语句结构体并指定语句类型的方法:
Statement *
malloc_statement(StatementType type) {
Statement *statement = malloc(sizeof(Statement));
statement->type = type;
return statement;
}
创建表达式语句的方法:
Statement *
nl_create_expression_statement(Expression *expression) {
Statement *statement = malloc_statement(EXPRESSION_STATEMENT);
statement->u.expression = expression;
return statement;
}
创建赋值语句和打印语句的方法,用到新加的赋值语句类型ASSIGN_STATEMENT和打印语句类型PRINT_STATEMENT。而赋值语句用到了Statement结构体中u联合体新加的assign成员。
Statement *
nl_create_assign_statement(char *identifier, Expression *expression) {
Statement *statement = malloc_statement(ASSIGN_STATEMENT);
statement->u.assign.identifier = identifier;
statement->u.assign.expression = expression;
return statement;
}
Statement *
nl_create_print_statement(Expression *expression) {
Statement *statement = malloc_statement(PRINT_STATEMENT);
statement->u.expression = expression;
return statement;
}
创建语句列表的方法:
StatementList *
nl_create_statement_list(Statement *statement) {
StatementList *statement_list = malloc(sizeof(StatementList));
statement_list->statement = statement;
statement_list->next = NULL;
return statement_list;
}
把语句添加到语句列表的方法,逻辑是先判断列表是否为空,若为空,则创建语句列表,第一个语句就是要添加的语句。若已有语句列表,就遍历它,知道最后一个,然后把新的语句插到最后:
StatementList *
nl_add_to_statement_list(StatementList *list, Statement *statement) {
StatementList *now;
if (list == NULL) {
return nl_create_statement_list(statement);
}
for (now = list; now->next; now = now->next)
;
now->next = nl_create_statement_list(statement);
return list;
}
计算表达式值
修改eval.c文件,修改后完整文件内容在这里
添加变量表达式求值方法,之前已有的变量求值方法nl_eval_variable要删掉,变量求值改成了变量表达式求值,因为解析阶段都只记录各种表达式,执行阶段需要对各种表达式求值。从变量表达式拿到变量名,让king搜索该变量,能找到该变量则返回值,找不到则报错。
这里声明表达式求值方法eval_expression,因为负数表达式求值要用到,而eval_expression方法的定义在负数表达式求值方法之后。至于为什么不把eval_expression的定义放在负数表达式之前,是因为eval_expression里面也要调用负数表达式求值方法,它们互相调用了对方,所以不管定义顺序如何,都需要在其中一个方法前声明另一个方法。
添加负数表达式求值方法,先对目标表达式求值,结果直接取负数即可。
static NL_Value
eval_variable_expression(Expression *exp) {
King *king = nl_get_current_king();
Variable *var = nl_search_global_variable(king, exp->u.identifier);
if (var != NULL) {
return var->value;
} else {
printf("[runtime error] This variable[%s] has not been declared.\n", exp->u.identifier);
exit(1);
}
}
static NL_Value eval_expression(Expression *exp);
static NL_Value
eval_minus_expression(Expression *exp) {
NL_Value result = eval_expression(exp->u.minus_expression);
if (result.type == INT_VALUE) {
result.u.int_value = -result.u.int_value;
} else if (result.type == DOUBLE_VAULE) {
result.u.double_value = -result.u.double_value;
} else {
printf("[runtime error] eval minus expression with unexpected value type: %d.\n", result.type);
exit(1);
}
return result;
}
表达式求值方法eval_expression也需要修改,switch中添加VARIABLE_EXPRESSION和MINUS_EXPRESSION两个case,分别调用变量表达式求值方法和负数表达式求值方法。其它二元表达式的case也添加了二元表达式求值的方法调用。
修改后的代码如下:
static NL_Value
eval_expression(Expression *exp) {
NL_Value v;
switch (exp->type) {
case INT_EXPRESSION: {
v = eval_int_expression(exp->u.int_value);
break;
}
case DOUBLE_EXPRESSION: {
v = eval_double_expression(exp->u.double_value);
break;
}
case VARIABLE_EXPRESSION: {
v = eval_variable_expression(exp);
break;
}
case MINUS_EXPRESSION: {
v = eval_minus_expression(exp);
break;
}
case ADD_EXPRESSION:
case SUB_EXPRESSION:
case MUL_EXPRESSION:
case DIV_EXPRESSION:
case MOD_EXPRESSION: {
v = nl_eval_binary_expression(exp->type, exp->u.binary_expression.left, exp->u.binary_expression.right);
break;
}
case EXPRESSION_TYPE_PLUS:
default: {
printf("[runtime error] eval expression with unexpected type:%d\n", exp->type);
exit(1);
}
}
return v;
}
另外,原有的eval_binary_int和eval_binary_double两个方法,switch中要补齐新的表达式类型case,补到default前的类型中。
增加更多执行方法
修改execute.c文件,增加更多语句执行方法,修改后的完整文件在这里。
先给文件添加一个依赖
#include <stdlib.h>
添加表达式语句执行方法,其实就是直接调用表达式求值方法
void
nl_execute_expression_statement(Statement *statement) {
nl_eval_expression(statement->u.expression);
}
修改赋值语句执行方法nl_execute_assign_statement,逻辑本质上没有变化,但语句结构体变化,相关逻辑也要变化,本质上先从语句拿到要赋值的变量名,还有值来源的表达式,先做表达式求值,从king中搜索记录的变量。如果搜索不到,则给king添加全局新的变量,如果能搜索到,则直接改变变量的值。
void
nl_execute_assign_statement(Statement *statement) {
NL_Value val;
Variable *var;
King *king = nl_get_current_king();
Expression *exp = statement->u.assign.expression;
char *identifier = statement->u.assign.identifier;
val = nl_eval_expression(exp);
var = nl_search_global_variable(king, identifier);
if(var != NULL) {
var->value = val;
} else {
nl_add_global_variable(king, identifier, &val);
}
}
添加打印语句执行方法,从语句结构体中拿到要打印的表达式,求值,打印值。
void
nl_execute_print_statement(Statement *statement) {
NL_Value v = nl_eval_expression(statement->u.expression);
nl_print_value(&v);
}
添加语句执行方法,switch判断语句类型,根据不同类型调用上面定义好的对应语句的执行方法。
void
nl_execute_statement(Statement *statement) {
switch(statement->type) {
case EXPRESSION_STATEMENT: {
nl_execute_expression_statement(statement);
break;
}
case ASSIGN_STATEMENT: {
nl_execute_assign_statement(statement);
break;
}
case PRINT_STATEMENT: {
nl_execute_print_statement(statement);
break;
}
case STATEMENT_TYPE_PLUS:
default: {
printf("[runtime error] in execute statement with unexpected type [%d].\n", statement->type);
exit(1);
}
}
}
添加语句列表执行方法,就是遍历列表中每个语句,逐个调用语句执行方法去执行。
void
nl_execute_statement_list(StatementList *list) {
StatementList *now;
if (list == NULL) {
return;
}
for (now = list; now; now = now->next) {
nl_execute_statement(now->statement);
}
}
修改语法逻辑
修改nl.y,修改后的完整文件内容在这里。
增加两种值类型,语句(statement)和语句列表(statement_list),增加语句类型的非终结变量和语句列表类型的非终结变量。
%type中定义的都是非终结符,非终结符即需要产生式进一步表达具体内容的变量。
%union {
Expression *expression;
char *identifier;
Statement *statement;
StatementList *statement_list;
}
%type <statement> statement expression_statement assign_statement print_statement
%type <statement_list> statement_list
之前语句列表的产生式,识别到语句和语句列表后并没有做任何逻辑,现在要加上相关逻辑。识别到语句需要创建语句添加到语句列表中,记录在king中。
statement_list
: statement
{
King *king = nl_get_current_king();
king->statement_list = nl_add_to_statement_list(king->statement_list, $1);
$$ = king->statement_list;
}
| statement_list statement
{
King *king = nl_get_current_king();
king->statement_list = nl_add_to_statement_list($1, $2);
$$ = king->statement_list;
}
;
语句的产生式分成三种细分语句产生式,表达式语句,赋值语句,打印语句。当然也需要给出各个语句的产生式。之前表达式语句是直接计算值,现在改成创建表达式语句。
statement
: expression_statement
| assign_statement
| print_statement
;
expression_statement
: expression SEMICOLON
{
$$ = nl_create_expression_statement($1);
}
;
assign_statement
: IDENTIFIER ASSIGN expression SEMICOLON
{
$$ = nl_create_assign_statement($1, $3);
}
;
print_statement
: PRINT LP expression RP SEMICOLON
{
$$ = nl_create_print_statement($3);
}
;
对外接口增改
修改interface.c文件,修改后的完整文件内容在这里。
其中NL_create_king方法中,创建king之后要多加一句语句列表初始化,语句列表是这次修改给king新加的属性。
king->statement_list = NULL;
添加执行开始的方法,本质上就是调用语句列表执行方法执行解析完成后的king中记录的语句列表。
void
NL_run(King *king) {
nl_execute_statement_list(king->statement_list);
}
这个NL_run方法也要添加到对外类型定义的文件_NL.h中。
修改程序主入口方法
修改main.c文件,修改后的完整文件内容在这里。
之前直接调用NL_compile(king, fp);就完事了,现在要在它之后多调用执行开始方法。
NL_compile(king, fp);
NL_run(king);
总结
本次修改,先修改原来的表达式创建方法,逻辑上是真正的创建,不包含执行。添加之前没有的表达式类型创建方法,添加各种语句创建方法,还完善了各种表达式计算方法和各种语句执行方法。在主入口main方法中,编译和执行也分开成两步。有了这个基础之后,下一篇将引入方法的使用,包括定义/声明方法以及调用方法。