ANTLR를 이용한 JASMIN 컴파일러

ANTLR를 이용한 tinyPython to Jasmin 컴파일러 만들기. - 6

Beige00 2024. 1. 8. 12:00

* 본 내용은 충남대학교 조은선 교수님의 컴파일러 개론을 수강하고 작성한 글입니다.

 

1. enterDef_stmt

@Override public void enterDef_stmt(tinyPythonParser.Def_stmtContext ctx) {
    if(visitedNodes.contains(ctx)){
        return;
    }
    visitedNodes.add(ctx);
    tempFuncFormat+=(ctx.getChild(1).getText()+"(");
    this.result+=(".method public static "+ctx.getChild(1).getText()+"(");
    enterArgs((tinyPythonParser.ArgsContext)ctx.getChild(3));//파라미터들을 로딩해서 table에 mapping 해야함
    tempFuncFormat+=")I";
    this.functionTable.put(ctx.getChild(1).getText(),tempFuncFormat);
    this.result+=(")I\n");//return type은 int 고정
    this.result+=(".limit stack 32\n");
    this.result+=(".limit locals 32\n");//함수 헤더 종료
    enterSuite((tinyPythonParser.SuiteContext)ctx.getChild(6));
    this.result+=(".end method\n");
    table.clear(); //함수 선언에 사용했던 임시 변수들 전부 날린다. def는 defs 안에서만 선언됨.
    tempFuncFormat="";
}
//함수에 해당하는 문장을 스캔해서 번역해야함. def add(int x) {} => public static int add(int x){}

defs 토큰에 묶여서 함수를 선언하는데 사용될 함수이다.

(임의로 해당 함수가 사용할 stack과 local memory는 32로 제한한다.)

가정을 확인해보면 항상 public static이라고 하였으므로 .method public static을 하드 코딩 해준다.

(만일 private, public, protect를 나누는 언어를 컴파일링할 시 파싱된 ctx 내부의 문장을 읽어 public, private, protect를  기록해주면 된다.)

 

우선 주석에 써있는데로 python 상에서 함수는 def name( type parameter ) { body } 와 같이 구성될 것이므로, 

def 이후 2번째 child( getChild(1) )이 함수의 이름일 것이다. 따라서 tempFuncFormat에 name+"("를 기록해준다.

 

이 후, .method public static + "name" + "(" 를 result에 기록한다. result는 최종 파일에 기록될 Jasmin 코드고, tempFuncFormat은 functionTable에 value 값으로 저장할 것이다.


다음, parameter를 enterArgs()로 처리한다.

enterArgs는 파라미터를 읽으며 하나의 파라미터가 인식될 때마다 'I' 를 기록해준다.

(가정에서 항상 int만 사용한다고 함. 만약 아닐 시 Args 함수 내부에서 인식된 parameter 문장을 스캔해서 int인지, char인지 등등을 결정해줘야할 것이다.)

그 후, tempFuncFormat+= ")I"를 통해 tempFuncFormat이 완성된다.

더보기

만약 def add(int x)를 파싱한다면, tempFuncFormat에는 최종적으로

"add(I)I\n"이 저장될 것이고 이 문장이 functionTable에 (add, tempFuncFormat) 으로 저장될 것이다.

완성된 tempFuncFormat을 functionTable에 저장하고 result 역시 주어진 형식에 맞춰 문장들을 추가해준다.

이 후, 함수의 body를 처리해주기 위해 enterSuite를 해주고, 함수를 끝낸다는 의미인 .end method를 작성한다.

table.clear()는 함수 선언에 사용했던 임시 변수들을 날리는 거다. 이는 enterArgs에서 상세히 설명하겠다.

그 후, tempFuncFormat 역시 초기화 하고 함수를 끝낸다.

 

2. enterSuite

@Override public void enterSuite(tinyPythonParser.SuiteContext ctx) {
    if(ctx.getChild(0).getClass().equals(tinyPythonParser.Simple_stmtContext.class)){
        enterSimple_stmt((tinyPythonParser.Simple_stmtContext) (ctx.getChild((0))));
    }//simple_stmt
    else{
        for(int i=1; i<ctx.getChildCount();i++){
            enterStmt((tinyPythonParser.StmtContext) ctx.getChild((i)));
        }//NEWLINE filtering
    }
}//simple_stmt | NEWLINE stmt+

 

주석에 써있는 것이 suite의 문법이다. non terminal 들만 존재하므로 단순히 어떠한 non terminal인지 판단해서 enter만 실행시켜준다.

 

3. enterArgs

@Override public void enterArgs(tinyPythonParser.ArgsContext ctx) {
    if(!visitedNodes.contains(ctx)) {
        for (int i = 0; i < ctx.getChildCount(); i += 2) {
            if(table.contains(ctx.getChild(i).getText())) {
                System.exit(0);
            }
            table.add(ctx.getChild(i).getText());//변수 테이블에 등록
            tempFuncFormat+="I";
            this.result+=("I"); //Name,Name의 반복이므로 0,2,4...씩 늘려가며 변수의 개수를 센다. 인수는 Int 고정
        }
    }
    visitedNodes.add(ctx);
}//NAME(',' NAME)*

 

가지고 온 Args parse tree "ctx"를 끝날 때까지 전부 스캔하며 table에 add 한다. 추 후, table에 이미 존재하는 parameter가 재파싱되면 add(int a, int a) 일 것이므로, 오류이다.(가정에는 오류가 없다고 하였으므로 가정 외 구현이다.)

그 후 파싱된 parameter에 대해 tempFuncFormat, result에 I를 기록해준다.

(형식은 int만 존재)

 

4. enterReturn

@Override public void enterReturn_stmt(tinyPythonParser.Return_stmtContext ctx) {
    if(visitedNodes.contains(ctx)){
        return;
    }
    visitedNodes.add(ctx);
    if(ctx.getChildCount()>1){
        enterExpr((tinyPythonParser.ExprContext) ctx.getChild(1));
    }//return에 값이 존재할 시 전부 load하고 계산한 뒤, 출력해야함.
    this.result+=("ireturn\n");
}//return은 무조건 int형만 존재한다.

 

return은 가정에 의해 무조건 int 형만 존재하므로, 만약 return a, return 2+3 과 같이 return을 해야하는 expression이 있다면 enterExpr을 통해 연산을 해주고 스택에 load한다.

그 이후 load 해둔 값을 ireturn을 통해 return 한다.

 

5. enterTest

@Override public void enterTest(tinyPythonParser.TestContext ctx) {
    if(visitedNodes.contains(ctx)){
        return;
    }
    this.visitedNodes.add(ctx);
    if(ctx.getChildCount()<2){
        enterExpr((tinyPythonParser.ExprContext) ctx.getChild(0)); // t1 load
        return;
    }
    enterExpr((tinyPythonParser.ExprContext) ctx.getChild(0)); // t1 load
    enterExpr((tinyPythonParser.ExprContext) ctx.getChild((2))); //t2 load
    enterComp_op((tinyPythonParser.Comp_opContext) ctx.getChild(1));
}//필요한 expr을 전부 load하고 if조건까지 걸어준다. ex) iload_0 iload_1 OP ifne : 이후 suite는 상단에서 처리

 

조건식은 a "comp_op" b 와 같은 구조이다. 따라서 비교에 필요한 2가지 연산을 enterExpr로 하고, Comp_op를 load한다.

만약 getChildCount() 값이 2보다 작을 시, 조건이 while(1) 과 같은 구조일 것이다. 따라서 enterExpr을 해주고 끝낸다.

 

6. enterPrint_stmt

@Override public void enterPrint_stmt(tinyPythonParser.Print_stmtContext ctx) {
    if(visitedNodes.contains(ctx)) {
        return;
    }
    visitedNodes.add(ctx);
    this.result += ("getstatic java/lang/System/out Ljava/io/PrintStream;\n");
    enterPrint_arg((tinyPythonParser.Print_argContext)ctx.getChild(1));
}

 

System.out.println을 사용할 것이므로 PrintStream을 static field에서 스택으로 가져온다.

그 후, print할 argument를 load하기 위해 enterPrint_arg를 실행한다.

 

7. enterPrint_arg

@Override public void enterPrint_arg(tinyPythonParser.Print_argContext ctx) {
    if(visitedNodes.contains(ctx)){
        return;
    }
    visitedNodes.add(ctx);
    if(ctx.getChild(0).getClass().equals(tinyPythonParser.ExprContext.class)){
        enterExpr((tinyPythonParser.ExprContext)ctx.getChild(0));
        this.result+=("invokevirtual java/io/PrintStream/println(I)V\n");
    }
    else{
        this.result+=("ldc "+ctx.getChild(0).getText()+"\n");
        this.result+=("invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V\n");
    }//print는 int or string 출력
}

 

Print_stmt에서 파싱된 Print argument가 expr일 시, enterExpr 처리, 아닐 시 String 문이므로 "ldc 'string' \n"을 result에 추가한다.

(가정 : print 문에 들어갈 수 있는 parameter는 string 혹은 int 이다.)

그 후, int던 string이던 invokevirtual로 println문을 실행시킨다.

 

8. enterComp_op

@Override public void enterComp_op(tinyPythonParser.Comp_opContext ctx) {
    if(visitedNodes.contains(ctx)){
        return;
    }
    visitedNodes.add(ctx);
    switch (ctx.getChild(0).getText()){
        case ">":
            this.result+=("if_icmple");
            break;
        case "<":
            this.result+=("if_icmpge");
            break;
        case "==":
            this.result+=("if_icmpne");
            break;
        case ">=":
            this.result+=("if_icmplt");
            break;
        case "<=":
            this.result+=("if_icmpgt");
            break;
        case "!=":
            this.result+=("if_icmpeq");
            break;
    }
}
// >,<,==,>=,<=,!= 에 따른 tempOp set. 뒤집어서 넣는다.

 

가지고 온 ctx의 등호에 따라 conditional jump문을 result에 작성해준다. 

cjump condition과 같이 작성되어야하는데, condition은 Comp_op를 호출한 caller function에서 작성해줄 것이다.

이 때, 파싱된 등호와 반대로 conditional jump 문을 작성해주면 된다.

 


다음시간에는 마지막 enter문인 enterExpr, enterOpt_paren을 알아보고, Test.tpy가 번역되는 과정 일부를 설명해볼 것이다.

또한 전체적인 요약과 jasmin을 이용해 class로 변환하고, java로 실행하는 과정을 설명할 것이다.