概述

上一篇其实只做了一个小小改动,增加了打印办法,本质上没改动什么。当前完成的解析器,它一边读入字符,一边剖析,碰到一个句子就履行一个,这种方式往后难以扩展。比方引入函数,界说函数,函数的内容也是句子,解析函数界说时必定不能履行其间的句子,函数内的句子只能在函数调用时才履行,并且还跟函数调用时的上下文有关。这一篇,我们要做的便是改造解析履行流程,解析的进程只解析,把解析的内容保存下来,终究再履行一切内容。

动手

完好代码在这儿

修正类型界说

修正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,本来该办法直接拿到两个操作数leftright核算出值,回来整数/浮点数表达式。

新逻辑先判别两个操作数的类型,假如两者都是现成的整数或浮点数,能够直接核算成果回来整数/浮点数表达式,比方操作数都是数字常量,则解析阶段就能够直接核算成果。

若两个操作数有一个或以上是别的类型表达式,意味着当前还不知道值,就需求直接把左右表达式记载下来,用表达式记载值的联合体中的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_EXPRESSIONMINUS_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_inteval_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办法中,编译和履行也分开成两步。有了这个根底之后,下一篇,将引入办法的运用,包含界说/声明办法以及调用办法。