概述
上一篇其实只做了一个小小改动,增加了打印办法,本质上没改动什么。当前完成的解析器,它一边读入字符,一边剖析,碰到一个句子就履行一个,这种方式往后难以扩展。比方引入函数,界说函数,函数的内容也是句子,解析函数界说时必定不能履行其间的句子,函数内的句子只能在函数调用时才履行,并且还跟函数调用时的上下文有关。这一篇,我们要做的便是改造解析履行流程,解析的进程只解析,把解析的内容保存下来,终究再履行一切内容。
动手
完好代码在这儿
修正类型界说
修正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办法中,编译和履行也分开成两步。有了这个根底之后,下一篇,将引入办法的运用,包含界说/声明办法以及调用办法。