本文系 翻译的第 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)}复制代码