手拉手教你实现一门编程语言 Enkel, 系列 11

本文系 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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容

  • 西边阳、一抹黄、 晚霞映照泛麟琅, 小桥拦、临荷塘、 傍晚微风,徐徐送凉, 爽、爽、爽。 突惆怅、骤悲沧、 一丝情...
    妙颜智慧阅读 271评论 0 0
  • 总有那么些弱不禁风的往昔 让人怀念,想念 上下铺的姐妹 在一起背书做题讨论梦想 也牵手漫步操场的各个死角 单人床被...
    simbfry阅读 82评论 0 0