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

KevinOfNeu發表於2018-09-06

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

原始碼

Github

Java 以及預設引數

Java 中沒有預設引數是我感覺最不爽的一點。有些人建議我使用 Builder 模式,但是我覺得這樣會產生大量的模板程式碼。我一直搞不懂為何 Java 這麼久以來忽略了這個特性。雖然實現起來並不困難。

實參(Argument) VS 形參 (Parameter)

這兩個概念非常容易被混淆。簡單說來就是:

  • 形參 - 方法的簽名
  • 實參 - 方法呼叫的傳參

方法呼叫的時候傳遞的表示式是實際引數,而形參是用來表示一個方法的簽名。

概念

思路:在方法呼叫的時候,查詢對應的方法簽名,並且獲得引數的預設值。這樣位元組碼生成部分無需修改。方法呼叫的時候,把沒明確給出值的引數用預設值代替,模擬正常的呼叫。

語法規則更改

語法規則變動僅發生在 functionParameterRule:

functionParameter : type ID functionParamdefaultValue? ;
functionParamdefaultValue : '=' expression ;
複製程式碼

方法引數包含了型別以及名字,以及後面可選的 = 以及緊隨其後的預設值。

匹配 Antlr 上下文物件

這部分的改動也是很小的。在 FunctionParameter 類中新增新的欄位 defaultValue ,欄位中儲存了 Optional<Expression> 。如果解析器發現 defaultValue 有明確傳值,那麼 Optional 中有值,否則是 empty。

public class FunctionSignatureVisitor extends EnkelBaseVisitor<FunctionSignature> {

    @Override
    public FunctionSignature visitFunctionDeclaration(@NotNull EnkelParser.FunctionDeclarationContext ctx) {
       //other stuff
        for(int i=0;i<argsCtx.size();i++) { //for each parsed argument
        //other stuff
            Optional<Expression> defaultValue = getParameterDefaultValue(argCtx);
            FunctionParameter functionParameters = new FunctionParameter(name, type, defaultValue);
            parameters.add(functionParameters);
        }
        //other stuff
    }

    private Optional<Expression> getParameterDefaultValue(FunctionParameterContext argCtx) {
        if(argCtx.functionParamdefaultValue() != null) {
            EnkelParser.ExpressionContext defaultValueCtx = argCtx.functionParamdefaultValue().expression();
            return Optional.of(defaultValueCtx.accept(expressionVisitor));
        }
        return Optional.empty();
    }
}
複製程式碼

生成位元組碼

方法呼叫的位元組碼生成需要新增如下處理:

  • 檢查實際傳參的數量是否多餘方法簽名引數數量
  • 對於沒有傳遞的引數,需要對預設的表示式求值

缺失引數的索引值是這樣安排的:引數的索引在方法簽名的最後一個引數之前(閉區間)和方法呼叫的實際傳參索引之間(開區間)。

舉例: signature: fun(int x,int x2=5,int x3=4)

call: fun(2)

缺失的引數是 x2 (索引是 1)和 x3(索引值是2),因為方法呼叫的引數索引是0, 方法簽名的索引值最大是 2,所以缺失引數的索引分別是 1 和 2。

public class ExpressionGenrator {
    public void generate(FunctionCall functionCall) {
        //other stuff
        if(arguments.size() > parameters.size()) {  
            throw new BadArgumentsToFunctionCallException(functionCall);
        }
        arguments.forEach(argument -> argument.accept(this));
        for(int i=arguments.size();i<parameters.size();i++) {
            Expression defaultParameter = parameters.get(i).getDefaultValue()
                    .orElseThrow(() -> new BadArgumentsToFunctionCallException(functionCall));
            defaultParameter.accept(this);
        }
        //other stuff   
    }
}
複製程式碼

示例

如下 Enkel 程式碼:

DefaultParamTest {

    main(string[] args) {
         greet("andrew")
         print ""
         greet("kuba","enkel")
    }

    greet (string name,string favouriteLanguage="java") {
        print "Hello my name is "
        print name
        print "and my favourite langugage is "
        print favouriteLanguage
    }
}
複製程式碼

得到的位元組碼輸出:

public class DefaultParamTest {
  public static void main(java.lang.String[]);
    Code:
       0: ldc           #8                  //push String "andrew" onto the stack
       2: ldc           #10   // push String "java" onto the stack  <-- implicit argument value
       4: invokestatic  #14                 // invoke static method greet:(Ljava/lang/String;Ljava/lang/String;)V
       7: getstatic     #20                 // get static field java/lang/System.out:Ljava/io/PrintStream;
      10: ldc           #22                 // push  empty String (empty line)
      12: invokevirtual #27                 // call Method "Ljava/io/PrintStream;".println:(Ljava/lang/String;)V to print empty line
      15: ldc           #29                 // push String "kuba"
      17: ldc           #31   // push String "enkel" <-- explicit argument value
      19: invokestatic  #14                 //invoke static method greet:(Ljava/lang/String;Ljava/lang/String;)V
      22: return

  public static void greet(java.lang.String, java.lang.String);
    Code:
       0: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #33                 // String Hello my name is
       5: invokevirtual #27                 // Method "Ljava/io/PrintStream;".println:(Ljava/lang/String;)V
       8: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_0                           // load (push onto stack) variable at index 0 (first parameter of a method)
      12: invokevirtual #27                 // Method "Ljava/io/PrintStream;".println:(Ljava/lang/String;)V
      15: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
      18: ldc           #35                 // String and my favourite langugage is
      20: invokevirtual #27                 // Method "Ljava/io/PrintStream;".println:(Ljava/lang/String;)V
      23: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
      26: aload_1                           // load (push onto stack) variable at index 1 (second parameter of a method)
      27: invokevirtual #27                 // Method "Ljava/io/PrintStream;".println:(Ljava/lang/String;)V
      30: return
}
複製程式碼

執行輸出是:

Hello my name is 
andrew
and my favourite langugage is 
java

Hello my name is 
kuba
and my favourite langugage is 
enkel
複製程式碼

相關文章