本文系 Creating JVM language 翻译的第 8 篇。
原文中的代码和原文有不一致的地方均在新的代码仓库中更正过,建议参考新的代码仓库。
源码
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
}
网友评论