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

KevinOfNeu發表於2018-09-06

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

原始碼

Github

位元組碼 - JVM 語言的通用描述

所有基於 JVM 的程式語言最終被編譯到位元組碼,然後被虛擬機器載入解釋執行,這意味著虛擬機器並不知道是什麼語言生成了位元組碼。只要類在 classpath 上。

這為 Enkel 呼叫 Java 庫以及其他框架提供了具體可能性。

查詢類方法和構造器

為了能夠引用到其他類,有如下兩種選擇:

  • 執行時 相信程式設計師並且不檢驗生成的位元組碼,這可能會導致如果 classpath 不存在,JVM 會丟擲異常
  • 編譯時 在生成位元組碼之前驗證。如果驗證失敗,會終止編譯過程。

在 Enkel 中,我決定使用第二種方式,主要是出於安全考量。我們可以使用反射 API 來實現:

public class ClassPathScope {

 public Optional<FunctionSignature> getMethodSignature(Type owner, String methodName, List<Type> arguments) {
     try {
         Class<?> methodOwnerClass = owner.getTypeClass();
         Class<?>[] params = arguments.stream()
                 .map(Type::getTypeClass).toArray(Class<?>[]::new);
         Method method = methodOwnerClass.getMethod(methodName,params);
         return Optional.of(ReflectionObjectToSignatureMapper.fromMethod(method));
     } catch (Exception e) {
         return Optional.empty();
     }
 }

 public Optional<FunctionSignature> getConstructorSignature(String className, List<Type> arguments) {
     try {
         Class<?> methodOwnerClass = Class.forName(className);
         Class<?>[] params = arguments.stream()
                 .map(Type::getTypeClass).toArray(Class<?>[]::new);
         Constructor<?> constructor = methodOwnerClass.getConstructor(params);
         return Optional.of(ReflectionObjectToSignatureMapper.fromConstructor(constructor));
     } catch (Exception e) {
         return Optional.empty();
     }
 }
}
複製程式碼

如果方法或者構造器不存在,會拋異常並且終止編譯:

    //Scope.java
    return new ClassPathScope().getMethodSignature(owner.get(), methodName, argumentsTypes)
                    .orElseThrow(() -> new MethodSignatureNotFoundException(this,methodName,arguments));
複製程式碼

這種方式看起來更加安全,但是同時也會慢一點。所有的依賴需要在編譯的時候通過反射的方式來解決依賴。

示例

呼叫其他 Enkel 類

下面我們從 Client 類中呼叫 Library 的方法:

 Client {
 
     start {
         print "Client: Calling my own 'Library' class:"
         var myLibrary = new Library()
         var addition = myLibrary.add(5,2)
         print "Client: Result returned from 'Library.add' = " + addition
     }
 
 }
複製程式碼
Library {

    int add(int x,int y) {
        print "Library: add() method called"
        return x+y
    }

}
複製程式碼

這裡我們需要先編譯 Library(我們目前不支援多檔案同時編譯),否則 Client 沒辦法編譯通過。

kuba@kuba-laptop:~/repos/Enkel-JVM-language$ java -classpath compiler/target/compiler-1.0-SNAPSHOT-jar-with-dependencies.jar:. com.kubadziworski.compiler.Compiler EnkelExamples/ClassPathCalls/Library.enk 
kuba@kuba-laptop:~/repos/Enkel-JVM-language$ java -classpath compiler/target/compiler-1.0-SNAPSHOT-jar-with-dependencies.jar:. com.kubadziworski.compiler.Compiler EnkelExamples/ClassPathCalls/Client.enk 
kuba@kuba-laptop:~/repos/Enkel-JVM-language$ java Client 
Client: Calling my own 'Library' class:
Library: add() method called
Client: Result returned from 'Library.add' = 7
複製程式碼

呼叫 Java 的 API


    start {
        var someString = "someString"
        print someString + " to upper case : " +  someString.toUpperCase()
    }

}
複製程式碼
$ java Client 
cos to upper case = COS
複製程式碼

相關文章