本文系 Creating JVM language 翻译的第 11 篇。 原文中的代码和原文有不一致的地方均在新的代码仓库 中更正过,建议参考新的代码仓库。
源码 Github
Java 以及默认参数 Java 中没有默认参数是我感觉最不爽的一点。有些人建议我使用 Builder 模式,但是我觉得这样会产生大量的模板代码。我一直搞不懂为何 Java 这么久以来忽略了这个特性。虽然实现起来并不困难。
实参(Argument) VS 形参 (Parameter) 这两个概念非常容易被混淆。简单说来就是:
方法调用的时候传递的表达式是实际参数,而形参是用来表示一个方法的签名。
概念 思路:在方法调用的时候,查找对应的方法签名,并且获得参数的默认值。这样字节码生成部分无需修改。方法调用的时候,把没明确给出值的参数用默认值代替,模拟正常的调用。
语法规则更改 语法规则变动仅发生在 functionParameterRule:1 2 functionParameter : type ID functionParamdefaultValue? ; functionParamdefaultValue : '=' expression ;
方法参数包含了类型以及名字,以及后面可选的 =
以及紧随其后的默认值。
匹配 Antlr 上下文对象 这部分的改动也是很小的。在 FunctionParameter 类中添加新的字段 defaultValue ,字段中保存了 Optional<Expression>
。如果解析器发现 defaultValue 有明确传值,那么 Optional 中有值,否则是 empty。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 } }
得到的字节码输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 }
运行输出是:
1 2 3 4 5 6 7 8 9 Hello my name is andrew and my favourite langugage is java Hello my name is kuba and my favourite langugage is enkel
全文完。