本文系 Creating JVM language 翻譯的第 9 篇。
原文中的代碼和原文有不一致的地方均在新的代碼倉庫中更正過,建議參考新的代碼倉庫。
源碼
1. 語法規(guī)則改動
我們新建一個規(guī)則 “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 關(guān)鍵字是必須的替废,后面不需要表達式
- RETURNWITHVALUE - 用在有返回值的方法中。return 關(guān)鍵字不是必須的泊柬,但是需要一個表達式
因此椎镣,方法可以顯示或者隱士的返回一個值:
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
}
}
上述代碼經(jīng)過解析后,AST 圖形展示如下:
我們可以看到兽赁,AST 中并沒有處理 fun2 中的隱士返回值状答。這是因為方法是空的語句塊冷守,匹配空的語句塊作為返回值并不是一個好的想法。因此惊科,確實的返回語句會在字節(jié)碼生成階段手動添加拍摇。
2. 匹配 Antlr 上下文對象
經(jīng)過解析后,返回語句從 antlr 的上下文對象轉(zhuǎn)換成 POJO 類 ReturnStatement
馆截。這一步的目的是僅匹配字節(jié)碼生成需要的數(shù)據(jù)充活,而不是直接從 antlr 生成的對象中取數(shù)據(jù),這樣會讓代碼看起來很丑陋蜡娶。
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. 檢測隱士空返回
假設(shè)方法中包含有隱士返回混卵,在解析階段是不會生成返回語句的,這就是為什么我們需要檢測這種情景窖张,并且在字節(jié)碼生成階段手動添加返回語句幕随。
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. 生成字節(jié)碼
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
會經(jīng)過如下階段:
- 從返回語句中獲得表達式(這里是5,類型是值)
- 生成 5 對應(yīng)的字節(jié)碼澄阳。(expression.accept(expressionGenerator) 調(diào)用 ExpressionGenerator.generate(Value value))
- 字節(jié)碼生成階段,會生成一個新的值 5 并壓入操作數(shù)棧
- IRETURN 指令將操作數(shù)棧棧頂數(shù)據(jù)出棧踏拜,并返回
字節(jié)碼表示:
bipush 5
ireturn
5. 示例
假設(shè)我們又如下 Enkel 代碼:
SumCalculator {
void main(string[] args) {
print sum(5,2)
}
int sum (int x ,int y) {
x+y
}
}
生成的字節(jié)碼如下:
$ 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)
}