编译[5]要有个打印方法

45 阅读2分钟

概述

上一篇实现了使用变量,这一篇只简单加一个功能,打印功能。之前的实现都是解释完一个表达式就打印结果,这样当然不利于后续扩展,这一篇只简单加个打印语句。在完善语言过程中,遇到问题时思考如何拆解问题并解决它,同时又希望每一步不要跨太大,想到什么就加什么,所以篇幅时长时短。

动手

完整代码在这里

词法分析(nl.l)加一个关键字表示要调用打印方法,完整代码在这里

.....
%%
"print" return PRINT;
.....
%%
.....

语法分析(nl.y)加上新token(PRINT)定义

%token SEMICOLON ADD SUB MUL DIV LP RP MOD ASSIGN PRINT

语法分析识别到一个表达式语句(expression SEMICOLON)后去掉打印逻辑,语句(statement)的产生式加上打印语句(PRINT LP expression RP SEMICOLON),形式上类似于调用print方法,打印语句括号间放表达式,逻辑上需要先计算表达式值,再打印出结果。

statement
    : expression SEMICOLON
    {
        nl_eval_expression($1);
    }
    | IDENTIFIER ASSIGN expression SEMICOLON
    {
        King *king = nl_get_current_king();
        nl_execute_assign_statement(king, $1, $3);
    }
    | PRINT LP expression RP SEMICOLON
    {
        NL_Value v = nl_eval_expression($3);
        nl_print_value(&v);
    }
    ;

这样逻辑就改完了,然后我们可以在代码中加上打印方法的调用

结果

比如我们可以这样写代码

abc = 34;
4 + 10 * abc;

print(abc);

_abc = 1+ 1.1;
10 * _abc + abc;

print(10 * abc);

abc = abc * 2;
abc * 2;

print(abc);

录屏看看运行结果,可以看到现在不会打印所有语句,只会打印我们指定的表达式。

打印语句.gif

结束

当前编译器解释执行逻辑有个大问题,它一边做语法分析,一边执行其中的语句和表达式,这样没办法往后扩展。打个比方,后续如果要支持定义方法和调用方法,按照目前的解析逻辑,在解析到方法定义中的打印语句时,马上执行打印。而实际上这只是方法定义而已,方法还没被调用。整个程序其实也是一个大方法,正确的逻辑顺序应该是区分开解析和执行这两个阶段。

下一篇,我们会改造代码逻辑,做到先解释后执行,这种做法未来还能扩展成先编译成另一个中语言单独存放,需要时可以调用执行。