美文网首页
手拉手教你实现一门编程语言 Enkel, 系列 8

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

作者: KevinOfNeu | 来源:发表于2018-09-08 01:31 被阅读0次

    本文系 Creating JVM language 翻译的第 8 篇。
    原文中的代码和原文有不一致的地方均在新的代码仓库中更正过,建议参考新的代码仓库。

    源码

    Github

    1. 语法改动

    基本的算数操作包括:

    • /

    本节需要改动的语法规则仅是 "expression"。
    表达式通俗来讲就是求值(方法调用,值,变量引用等)。
    而语句会做一些操作,但不一定会产生值,例如 if 语句。
    既然算数操作总是返回值,那么他就是表达式:

    expression : varReference #VARREFERENCE
               | value        #VALUE
               | functionCall #FUNCALL
               |  '('expression '*' expression')' #MULTIPLY
               | expression '*' expression  #MULTIPLY
               | '(' expression '/' expression ')' #DIVIDE
               | expression '/' expression #DIVIDE
               | '(' expression '+' expression ')' #ADD
               | expression '+' expression #ADD
               | '(' expression '-' expression ')' #SUBSTRACT
               | expression '-' expression #SUBSTRACT
               ;
    

    说明:

    • # 标号表示为当前规则创建可选的回调。Antlr 会在 ENkelVisotor 中生成诸如 visitDIVIDE(), visitADD() 的接口。
    • 规则的定义先后顺序至关重要。假设我们有如下表达式: 1 +*3。这样会产生歧义,因为有很多解释:1+2=3 3*3=9 或者 2*3=6 6+1=7。Antlr 通过选择第一个符合的规则来解决歧义。因此,规则定义的顺序会影响到算数表达式的执行顺序。
    • () 里的表达式优先级高于普通优先级。因此诸如 (1+2)*3 的表达式能被正确解析和执行。

    2. 匹配 Antlr 上下文对象

    Antlr 为每一条规则生成新的类和回调。为每个操作新建一个类是个不错的选择,这样会让字节码的生成看起来更加干净:

    public class ExpressionVisitor extends EnkelBaseVisitor<Expression> {
    
        //some other methods (visitFunctionCall, visitVaraibleReference etc)
        
        @Override
        public Expression visitADD(@NotNull EnkelParser.ADDContext ctx) {
            EnkelParser.ExpressionContext leftExpression = ctx.expression(0);
            EnkelParser.ExpressionContext rightExpression = ctx.expression(1);
    
            Expression leftExpress = leftExpression.accept(this);
            Expression rightExpress = rightExpression.accept(this);
    
            return new Addition(leftExpress, rightExpress);
        }
    
        @Override
        public Expression visitMULTIPLY(@NotNull EnkelParser.MULTIPLYContext ctx) {
            EnkelParser.ExpressionContext leftExpression = ctx.expression(0);
            EnkelParser.ExpressionContext rightExpression = ctx.expression(1);
    
            Expression leftExpress = leftExpression.accept(this);
            Expression rightExpress = rightExpression.accept(this);
    
            return new Multiplication(leftExpress, rightExpress);
        }
        
        //Division
        
        //Substration
    }
    

    Multiplcation,Addition,Division 和 Substraction 都是不可变的 POJO,存储了操作符的左侧和右侧的表达式(1+2,其中1 是左侧,2 是右侧)。

    3. 生成字节码

    当 Enkel 代码被解析和匹配到表达式对象后,我们可以进行下一步,字节码生成了。这里我们还需要创建另一个类,类方法中的参数是表达式的类型,方法体内生成对应的字节码。

    public class ExpressionGenrator {
    
        //other methods (generateFunctionCall, generateVariableReference etc.)
    
        public void generate(Addition expression) {
            evaluateArthimeticComponents(expression);
            methodVisitor.visitInsn(Opcodes.IADD);
        }
    
        public void generate(Substraction expression) {
            evaluateArthimeticComponents(expression);
            methodVisitor.visitInsn(Opcodes.ISUB);
        }
    
        public void generate(Multiplication expression) {
            evaluateArthimeticComponents(expression);
            methodVisitor.visitInsn(Opcodes.IMUL);
        }
    
        public void generate(Division expression) {
            evaluateArthimeticComponents(expression);
            methodVisitor.visitInsn(Opcodes.IDIV);
        }
        
        private void evaluateArthimeticComponents(ArthimeticExpression expression) {
                Expression leftExpression = expression.getLeftExpression();
                Expression rightExpression = expression.getRightExpression();
                leftExpression.accept(this);
                rightExpression.accept(this);
        }
    }
    

    算数表达式中用到的字节码非常通俗易懂。字节码指令将两个操作数从出栈,执行计算,结果入栈。

    • iadd - 整数相加。
    • isub - 整数相减
    • imul - 整数相乘
    • idiv - 整数相除

    其他数据类型的指令以此类推。

    4. 结果

    假设我们有如下 Enkel 代码:

    First {
            void main (string[] args) {
                var result = 2+3*4
            }
    }
    

    编译后的字节码如下所示:

    $ javap -c First
    public class First {
      public static void main(java.lang.String[]);
        Code:
           0: bipush        2 //push 2 onto the stack
           2: bipush        3 //push 3 onto the stack
           4: bipush        4 //push 4 onto the stack
           6: imul          //take two top values from the stack (3 and 4) and multiply them. Put result on stack
           7: iadd          //take two top values from stack (2 and 12-result of imul) and add em. Put result back on stack
           8: istore_1     //store top value from the stack into local variable at index 1 in local variable array of the curennt frame
           9: return
    }
    

    相关文章

      网友评论

          本文标题:手拉手教你实现一门编程语言 Enkel, 系列 8

          本文链接:https://www.haomeiwen.com/subject/wcfkgftx.html