美文网首页
手拉手教你实现一门编程语言 Enkel, 系列 9

手拉手教你实现一门编程语言 Enkel, 系列 9

作者: KevinOfNeu | 来源:发表于2018-09-08 01:31 被阅读0次

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

    源码

    Github

    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 图形展示如下:


    image

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

    2. 匹配 Antlr 上下文对象

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

    public class StatementVisitor extends EnkelBaseVisitor<Statement> {
    
        //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  SumCalculator
    public 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)
    }
    

    相关文章

      网友评论

          本文标题:手拉手教你实现一门编程语言 Enkel, 系列 9

          本文链接:https://www.haomeiwen.com/subject/acfkgftx.html