博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
手拉手教你实现一门编程语言 Enkel, 系列 9
阅读量:6279 次
发布时间:2019-06-22

本文共 4168 字,大约阅读时间需要 13 分钟。

本文系 翻译的第 9 篇。 原文中的代码和原文有不一致的地方均在新的中更正过,建议参考新的代码仓库。

源码

1. 语法规则改动

我们新建一个规则 “returnStatement”。 那为什么不叫 “returnExpression” 呢?毕竟表达式总是返回值的,语句没有返回值么? 这听起来有点绕口,但是返回值并不总是返回一个值。在 Java 中,代码 int x = return 5; 没有意义, 在 Enkel 中也是如此。换句话说,表达式总可以给一个变量赋值。这就是为什么返回是语句,而不是表达式。

statement : variableDeclaration           //other statements rules           | returnStatement ;variableDeclaration : VARIABLE name EQUALS expression;printStatement : PRINT expression ;returnStatement : 'return' #RETURNVOID                | ('return')? expression #RETURNWITHVALUE;复制代码

返回语句有两种形式:

  • RETURNVOID - 用在没有返回值的方法中。return 关键字是必须的,后面不需要表达式
  • RETURNWITHVALUE - 用在有返回值的方法中。return 关键字不是必须的,但是需要一个表达式

因此,方法可以显示或者隐士的返回一个值:

SomeClass {    fun1 {       return  //explicitly return from void method    }        fun2 {        //implicitly return from void method    }        int fun2 {        return 1  //explicitly return "1" from int method    }        int fun3 {        1  //implicitly return "1" from int method    }}复制代码

上述代码经过解析后,AST 图形展示如下:

我们可以看到,AST 中并没有处理 fun2 中的隐士返回值。这是因为方法是空的语句块,匹配空的语句块作为返回值并不是一个好的想法。因此,确实的返回语句会在字节码生成阶段手动添加。

2. 匹配 Antlr 上下文对象

经过解析后,返回语句从 antlr 的上下文对象转换成 POJO 类 ReturnStatement 。这一步的目的是仅匹配字节码生成需要的数据,而不是直接从 antlr 生成的对象中取数据,这样会让代码看起来很丑陋。

public class StatementVisitor extends EnkelBaseVisitor
{ //other stuff @Override public Statement visitRETURNVOID(@NotNull EnkelParser.RETURNVOIDContext ctx) { return new ReturnStatement(new EmptyExpression(BultInType.VOID)); } @Override public Statement visitRETURNWITHVALUE(@NotNull EnkelParser.RETURNWITHVALUEContext ctx) { Expression expression = ctx.expression().accept(expressionVisitor); return new ReturnStatement(expression); } }复制代码

3. 检测隐士空返回

假设方法中包含有隐士返回,在解析阶段是不会生成返回语句的,这就是为什么我们需要检测这种情景,并且在字节码生成阶段手动添加返回语句。

public class MethodGenerator {    //other stuff    private void appendReturnIfNotExists(Function function, Block block,StatementGenerator statementScopeGenrator) {        Statement lastStatement = block.getStatements().get(block.getStatements().size() - 1);        boolean isLastStatementReturn = lastStatement instanceof ReturnStatement;        if(!isLastStatementReturn) {            EmptyExpression emptyExpression = new EmptyExpression(function.getReturnType());            ReturnStatement returnStatement = new ReturnStatement(emptyExpression);            returnStatement.accept(statementScopeGenrator);        }    }}复制代码

上述方法检测方法最后的语句是不是返回语句,如果不是就添加返回指令。

4. 生成字节码

public class StatementGenerator {    //oher stuff    public void generate(ReturnStatement returnStatement) {        Expression expression = returnStatement.getExpression();        Type type = expression.getType();        expression.accept(expressionGenrator); //generate bytecode for expression itself (puts the value of expression onto the stack)        if(type == BultInType.VOID) {            methodVisitor.visitInsn(Opcodes.RETURN);        } else if (type == BultInType.INT) {            methodVisitor.visitInsn(Opcodes.IRETURN);        }    }}复制代码

因此,return 5 会经过如下阶段:

  • 从返回语句中获得表达式(这里是5,类型是值)
  • 生成 5 对应的字节码。(expression.accept(expressionGenerator) 调用 ExpressionGenerator.generate(Value value))
  • 字节码生成阶段,会生成一个新的值 5 并压入操作数栈
  • IRETURN 指令将操作数栈栈顶数据出栈,并返回

字节码表示:

bipush        5 ireturn复制代码

5. 示例

假设我们又如下 Enkel 代码:

SumCalculator {    void main(string[] args) {        print sum(5,2)    }    int sum (int x ,int y) {        x+y    }}复制代码

生成的字节码如下:

$ javap -c  SumCalculatorpublic class SumCalculator {  public static void main(java.lang.String[]);    Code:       0: getstatic     #12                 //get static field java/lang/System.out:Ljava/io/PrintStream;       3: bipush        5       5: bipush        2       7: invokestatic  #16                 // call method sum (with the values on operand stack 5,2)      10: invokevirtual #21                 // call method println (with the value on stack - the result of method sum)      13: return                           //return  public static int sum(int, int);    Code:       0: iload_0       1: iload_1       2: iadd       3: ireturn //return the value from operand stack (result of iadd)}复制代码

转载于:https://juejin.im/post/5b914bd4f265da0afa3dc1c6

你可能感兴趣的文章
PHP高级编程之守护进程,实现优雅重启
查看>>
PHP字符编码转换类3
查看>>
rsync同步服务配置手记
查看>>
http缓存知识
查看>>
Go 时间交并集小工具
查看>>
iOS 多线程总结
查看>>
webpack是如何实现前端模块化的
查看>>
TCP的三次握手四次挥手
查看>>
关于redis的几件小事(六)redis的持久化
查看>>
package.json
查看>>
webpack4+babel7+eslint+editorconfig+react-hot-loader 搭建react开发环境
查看>>
Maven 插件
查看>>
初探Angular6.x---进入用户编辑模块
查看>>
计算机基础知识复习
查看>>
【前端词典】实现 Canvas 下雪背景引发的性能思考
查看>>
大佬是怎么思考设计MySQL优化方案的?
查看>>
<三体> 给岁月以文明, 给时光以生命
查看>>
Android开发 - 掌握ConstraintLayout(九)分组(Group)
查看>>
springboot+logback日志异步数据库
查看>>
Typescript教程之函数
查看>>