本文系 Creating JVM language 翻譯的第 7 篇浑测。
原文中的代碼和原文有不一致的地方均在新的代碼倉庫中更正過呀闻,建議參考新的代碼倉庫躺翻。
源碼
1. 方法
到目前為止鸠项,我們可以在 Enkel 中聲明類和變量饼暑,但是他們都處于同一個全局作用域中婚被。下一步狡忙,我們需要支持方法。
我們的目標(biāo)是可以處理如下代碼:
First {
void main (string[] args) {
var x = 25
metoda(x)
}
void metoda (int param) {
print param
}
}
2. 作用域
為了可以訪問其他的函數(shù)或者變量址芯,他們需要在同一個作用域下:
public class Scope {
private List<Identifier> identifiers; //think of it as a variables for now
private List<FunctionSignature> functionSignatures;
private final MetaData metaData; //currently stores only class name
public Scope(MetaData metaData) {
identifiers = new ArrayList<>();
functionSignatures = new ArrayList<>();
this.metaData = metaData;
}
public Scope(Scope scope) {
metaData = scope.metaData;
identifiers = Lists.newArrayList(scope.identifiers);
functionSignatures = Lists.newArrayList(scope.functionSignatures);
}
//some other methods that expose data to the outside
}
對象 scope
是在類創(chuàng)建的時候被創(chuàng)建的灾茁,然后傳遞給下一層級(方法)。下一層級拷貝并且添加其他的選項谷炸。
3. 簽名
函數(shù)調(diào)用的時候北专,需要提供函數(shù)的一些額外信息。假設(shè)有如下的偽代碼:
f1() {
f2()
}
f2(){
}
解析后如下圖所示:
節(jié)點的訪問順序如下:
- Root
- 函數(shù) f1
- 對函數(shù) f2 的調(diào)用//錯誤旬陡,此時 f2 還沒有定義
- 函數(shù) f2
因此拓颓,當(dāng)函數(shù)調(diào)用發(fā)生時,函數(shù)的定義可能沒有訪問到描孟,f1 解析的時候并沒有 f2 的信息驶睦。
為了解決這個問題,我們必須訪問所有函數(shù)的定義并且把函數(shù)的簽名存儲到作用域中匿醒。
public class ClassVisitor extends EnkelBaseVisitor<ClassDeclaration> {
private Scope scope;
@Override
public ClassDeclaration visitClassDeclaration(@NotNull EnkelParser.ClassDeclarationContext ctx) {
String name = ctx.className().getText();
FunctionSignatureVisitor functionSignatureVisitor = new FunctionSignatureVisitor();
List<EnkelParser.FunctionContext> methodsCtx = ctx.classBody().function();
MetaData metaData = new MetaData(ctx.className().getText());
scope = new Scope(metaData);
//First find all signatures
List<FunctionSignature> signatures = methodsCtx.stream()
.map(method -> method.functionDeclaration().accept(functionSignatureVisitor))
.peek(scope::addSignature)
.collect(Collectors.toList());
//Once the signatures are found start parsing methods
List<Function> methods = methodsCtx.stream()
.map(method -> method.accept(new FunctionVisitor(scope)))
.collect(Collectors.toList());
return new ClassDeclaration(name, methods);
}
}
4. Invokestatic
當(dāng)所有相關(guān)的信息都被正確解析后场航,接下來需要生成字節(jié)碼了。當(dāng)前 Enkele 還沒有實現(xiàn)對象的創(chuàng)建廉羔,因此方法的調(diào)用先使用 static 的方式來調(diào)用旗闽。
int access = Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC;
靜態(tài)方法的調(diào)用對應(yīng)的字節(jié)碼指令是 invokestatic
, 需要兩個參數(shù):
- Filed Descriptor - 方法持有類的描述 (Ljava/io/PrintStream;)
- Method Descriptor - 例如:println:(I)V
操作數(shù)棧中的會執(zhí)行出棧操作蜜另,并傳遞給方法調(diào)用(類型和個數(shù)必須和方法描述一致)适室。
public class MethodGenerator {
private final ClassWriter classWriter;
public MethodGenerator(ClassWriter classWriter) {
this.classWriter = classWriter;
}
public void generate(Function function) {
Scope scope = function.getScope();
String name = function.getName();
String description = DescriptorFactory.getMethodDescriptor(function);
Collection<Statement> instructions = function.getStatements();
int access = Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC;
MethodVisitor mv = classWriter.visitMethod(access, name, description, null, null);
mv.visitCode();
StatementGenerator statementScopeGenrator = new StatementGenerator(mv);
instructions.forEach(instr -> statementScopeGenrator.generate(instr,scope));
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(-1,-1); //asm autmatically calculate those but the call is required
mv.visitEnd();
}
}
5. 效果
如下 Enkel 代碼:
First {
void main (string[] args) {
var x = 25
metoda(x)
}
void metoda (int param) {
print param
}
}
會被編譯成如下所示的字節(jié)碼:
$ javap -c First
public class First {
public static void main(java.lang.String[]);
Code:
0: bipush 25 //push value 25 onto the stack
2: istore_0 //store value from stack into variable at index 0
3: iload_0 //load variable at index onto the stack
5: invokestatic #10 //call metod Method metoda:(I)V
8: return
public static void metoda(int);
Code:
0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iload_0
4: invokevirtual #20 // Method "Ljava/io/PrintStream;".println:(I)V
7: return
}