手拉手教你實現一門程式語言 Enkel, 系列 16

KevinOfNeu發表於2018-09-06

本文系 Creating JVM language 翻譯的第 16 篇。 原文中的程式碼和原文有不一致的地方均在新的程式碼倉庫中更正過,建議參考新的程式碼倉庫。

原始碼

Github

OOP 和 statics

面嚮物件語言中最大的優點是啥?我個人認為是多型, 如何實現多型,使用繼承唄,可以在靜態語義下使用繼承麼?肯定不行呀。

在我認為,靜態語義違反了物件導向的思想,不應該出現在物件導向中,為了避免使用多型,你可以用單例呀。

因此,為何有 static 情況下,Java 還聲稱自己是物件導向的呢?我認為,Java 是為了迎合 C++ 更加方便的接納 Java 才引入的歷史因素。

去掉 static

直到上一篇部落格,Enkel 中一直存在 static。包括 main 方法和其他的靜態方法,這麼是為了方便實現語言的其他特性,比如變數,條件表示式,迴圈,方法呼叫,然後才轉成 OO。

那麼我們來實現沒有靜態的 OO 吧。

main 方法

static main 方法需要 Java 程式設計師手動編寫。Enkel 是這樣處理的:

  • 編譯器自動生成
  • 在 main 方法中,用預設的構造器建立一個物件
  • 然後呼叫 start 方法
  • 程式設計師需要提供 start 方法定義
private Function getGeneratedMainMethod() {
     FunctionParameter args = new FunctionParameter("args", BultInType.STRING_ARR, Optional.empty());
     FunctionSignature functionSignature = new FunctionSignature("main", Collections.singletonList(args), BultInType.VOID);
     ConstructorCall constructorCall = new ConstructorCall(scope.getClassName());
     FunctionSignature startFunSignature = new FunctionSignature("start", Collections.emptyList(), BultInType.VOID);
     FunctionCall startFunctionCall = new FunctionCall(startFunSignature, Collections.emptyList(), scope.getClassType());
     Block block = new Block(new Scope(scope), Arrays.asList(constructorCall,startFunctionCall));
     return new Function(functionSignature, block);
 }
複製程式碼

start 方法是非靜態的,但其實是 main 方法的一種變體。

INVOSTATIC vs INVOKEVIRTUAL

在第七部分,我用了 INVOKESTATIC 來做方法呼叫,是時候換成 INVOKEVIRTUAL 了。

INVOKEVIRTUAL 有一點和 INVOKESTATIC 有很大的差異,INVOKEVIRTUAL 需要一個所有者,INVOKESTATIC 從棧中出棧引數,INVOKEVIRTUAL 首先是把所有者出棧,然後才是出棧引數。

如果沒有顯示的提供所有者資訊,預設用 this。


//Mapping antlr generated FunctionCallContext to FunctionCall 
@Override
public Expression visitFunctionCall(@NotNull EnkelParser.FunctionCallContext ctx) {
    //other stuff
    boolean ownerIsExplicit = ctx.owner != null;
    if(ownerIsExplicit) {
        Expression owner = ctx.owner.accept(this);
        return new FunctionCall(signature, arguments, owner);
    }
    ClassType thisType = new ClassType(scope.getClassName());
    return new FunctionCall(signature, arguments, new VarReference("this",thisType)); //pass "this" as a owner 
}
複製程式碼
//Generating bytecode using mapped FunctionCall object
public void generate(FunctionCall functionCall) {
    functionCall.getOwner().accept(this); //generate owner (pushses it onto stack)
    generateArguments(functionCall);  //generate arguments
    String functionName = functionCall.getIdentifier();
    String methodDescriptor = DescriptorFactory.getMethodDescriptor(functionCall.getSignature());
    String ownerDescriptor = functionCall.getOwnerType().getInternalName();
    //Consumes owner and arguments off the stack
    methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, ownerDescriptor, functionName, methodDescriptor, false); 
}
複製程式碼

示例

如下 Enkel 程式碼:

HelloStart {

    start {
        print "Hey I am non-static 'start' method"
    }
}
複製程式碼

生成位元組碼:

public class HelloStart {
  public void start();
    Code:
       0: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #14                 // String Hey I am non-static  'start' method
       5: invokevirtual #19                 // Method "Ljava/io/PrintStream;".println:(Ljava/lang/String;)V
       8: return

  //Constructor
  public HelloStart();
    Code:
       0: aload_0   //get "this"
       1: invokespecial #22                 // Method java/lang/Object."<init>":()V - call super
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class HelloStart - create new object
       3: dup       //duplicate new object so that invokespecial does not consumes it
       4: invokespecial #25                 // Method "<init>":()V - call constructor
       7: invokevirtual #27                 // Method start:()V
      10: return
}
複製程式碼

與之對應 Java 類如下:

public class HelloStart {
    public HelloStart() {
    }

    public static void main(String[] var0) {
        (new HelloStart()).start();
    }
    
    public void start() {
        System.out.println("Hey I am non-static \'start\' method");
    }

}
複製程式碼

相關文章