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

KevinOfNeu發表於2018-09-06

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

原始碼

Github

範圍 for 迴圈

本節中我們來實現範圍迴圈,在範圍內迭代值,在 Java 中大概張這個樣子: for (int i=0;i<=5;i++)

Enkel 的等價形式: for i from 0 to 5

我實現另外一個特性,迴圈會自動檢測是遞增還是遞減:

for i from 0 to 5 //increment i from 0 to 5  - for (int i=0;i<=5;i++)

for i from 5 to 0 //decremenet i from 5 to 0 - for (int i=5;i>=0;i--)
複製程式碼

遞增或者遞減必須在執行時推斷,因為範圍的值可能是方法呼叫的返回值。

for while 迴圈或者容器迭代器都很相似,本節不做描述。

語法規則更改

statement : block
           //other statement alternatives
           | forStatement ;

forStatement : 'for' ('(')? forConditions (')')? statement ;
forConditions : iterator=varReference  'from' startExpr=expression range='to' endExpr=expression ;
複製程式碼
  • forConditions 是迭代的條件表示式
  • = 提高可讀性
  • 迭代器必須是變數的名字
  • startExpression 用來初始化迭代器
  • endExpressions 是迭代器的終止值

for (i from 0 to 5) print i 圖形化的解析樹如下所示:

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

匹配 Antlr 上下文物件

Antlr 根據語法規則會生成 ForStatementContext 物件,我們用它生成對編譯器更加友好的類。可以解決迭代器變數未生明的問題。

public class ForStatementVisitor extends EnkelBaseVisitor<RangedForStatement> {

    //other stuff
    
    @Override
    public RangedForStatement visitForStatement(@NotNull ForStatementContext ctx) {
        EnkelParser.ForConditionsContext forExpressionContext = ctx.forConditions();
        Expression startExpression = forExpressionContext.startExpr.accept(expressionVisitor);
        Expression endExpression = forExpressionContext.endExpr.accept(expressionVisitor);
        VarReferenceContext iterator = forExpressionContext.iterator;
        String varName = iterator.getText();
        //If variable referenced by iterator already exists in the scope
        if(scope.localVariableExists(varName)) { 
            //register new variable value
            Statement iteratorVariable = new AssignmentStatement(varName, startExpression); 
            //get the statement (usually block))
            Statement statement = ctx.statement().accept(statementVisitor); 
            return new RangedForStatement(iteratorVariable, startExpression, endExpression,statement, varName, scope); 
        //Variable has not been declared in the scope
        } else { 
            //create new local variable and add to the scope
            scope.addLocalVariable(new LocalVariable(varName,startExpression.getType())); 
            //register variable declaration statement
            Statement iteratorVariable = new VariableDeclarationStatement(varName,startExpression); 
            Statement statement = ctx.statement().accept(statementVisitor);
            return new RangedForStatement(iteratorVariable, startExpression, endExpression,statement, varName,scope);
        }
    }
}
複製程式碼

迭代器變數可能在作用中存在或者未生明,這兩種情況都需要被妥善處理:

var iterator = 0
for (iterator from 0 to 5) print iterator
複製程式碼

迭代器已經宣告過,賦值給 startExpression。 new AssignmentStatement(varName,startExpression);

    for (iterator from 0 to 5) print iterator
複製程式碼

迭代器沒有宣告,首先宣告,然後賦值給 startExpression。 new VariableDeclarationStatement(varName,startExpression);

位元組碼生成

RangedForStatement 生成後,下面我們開始生成位元組碼。

JVM 中沒有為 for 迴圈設計特殊的指令。一種實現方式就是使用控制流指令。

public void generate(RangedForStatement rangedForStatement) {
    Scope newScope = rangedForStatement.getScope();
    StatementGenerator scopeGeneratorWithNewScope = new StatementGenerator(methodVisitor, newScope);
    ExpressionGenrator exprGeneratorWithNewScope = new ExpressionGenrator(methodVisitor, newScope);
    Statement iterator = rangedForStatement.getIteratorVariableStatement();
    Label incrementationSection = new Label();
    Label decrementationSection = new Label();
    Label endLoopSection = new Label();
    String iteratorVarName = rangedForStatement.getIteratorVarName();
    Expression endExpression = rangedForStatement.getEndExpression();
    Expression iteratorVariable = new VarReference(iteratorVarName, rangedForStatement.getType());
    ConditionalExpression iteratorGreaterThanEndConditional = new ConditionalExpression(iteratorVariable, endExpression, CompareSign.GREATER);
    ConditionalExpression iteratorLessThanEndConditional = new ConditionalExpression(iteratorVariable, endExpression, CompareSign.LESS);

    //generates varaible declaration or variable reference (istore)
    iterator.accept(scopeGeneratorWithNewScope);

    //Section below checks whether the loop should be iterating or decrementing
    //If the range start is smaller than range end (i from 0 to 5)  then iterate (++)
    //If the range start is greater than range end (i from 5 to 0) then decrement (--)

    //Pushes 0 or 1 onto the stack 
    iteratorLessThanEndConditional.accept(exprGeneratorWithNewScope);
    //IFNE - is value on the stack (result of conditional) different than 0 (success)?
    methodVisitor.visitJumpInsn(Opcodes.IFNE,incrementationSection);

    iteratorGreaterThanEndConditional.accept(exprGeneratorWithNewScope);
    methodVisitor.visitJumpInsn(Opcodes.IFNE,decrementationSection);

    //Incrementation section
    methodVisitor.visitLabel(incrementationSection);
    rangedForStatement.getStatement().accept(scopeGeneratorWithNewScope); //execute the body
    methodVisitor.visitIincInsn(newScope.getLocalVariableIndex(iteratorVarName),1); //increment iterator
    iteratorGreaterThanEndConditional.accept(exprGeneratorWithNewScope); //is iterator greater than range end?
    methodVisitor.visitJumpInsn(Opcodes.IFEQ,incrementationSection); //if it is not go back loop again 
    //the iterator is greater than end range. Break out of the loop, skipping decrementation section
    methodVisitor.visitJumpInsn(Opcodes.GOTO,endLoopSection); 

    //Decrementation section
    methodVisitor.visitLabel(decrementationSection);
    rangedForStatement.getStatement().accept(scopeGeneratorWithNewScope);
    methodVisitor.visitIincInsn(newScope.getLocalVariableIndex(iteratorVarName),-1); //decrement iterator
    iteratorLessThanEndConditional.accept(exprGeneratorWithNewScope);
    methodVisitor.visitJumpInsn(Opcodes.IFEQ,decrementationSection);

    methodVisitor.visitLabel(endLoopSection);
}
複製程式碼

這看起來有點複雜,因為遞增遞減的推測邏輯是在執行時決定的。

for (i from 0 to 5) 為例,我們來看一下整個流程:

  1. 宣告迭代器變數 i 並且賦予初始值 0
  2. 檢測迭代器的值 0 是否大於結束值 5
  3. 因為 0 < 5, 因此遞增,跳到遞增部分
  4. 執行 for 迴圈體內的語句
  5. 遞增 1
  6. 檢查迭代器的值是否大於 5
  7. 如果條件不成立,跳到 4
  8. 迴圈體執行 5 次後,跳到結束部分

示例

如下 Enkel 程式碼:

Loops {
    main(string[] args) {
        for i from 1 to 5 {
            print i
        }
    }
}
複製程式碼

生成後的位元組碼反編譯後的 Java 程式碼:

public class Loops {
    public static void main(String[] var0) {
        int var1 = 1;
        if(var1 >= 5 ) { //should it be decremented?
            do {
                System.out.println(var1);
                --var1;
            } while(var1 >= 5);
        } else { //should it be incremented?
            do {
                System.out.println(var1);
                ++var1;
            } while(var1 <= 5);
        }

    }
}
複製程式碼

執行結果:

$ java Loops 
1
2
3
4
5
複製程式碼

相關文章