* 본 내용은 충남대학교 조은선 교수님의 컴파일러 개론을 수강하고 작성한 글입니다.
저번 글에서는 ANTLR가 파스트리를 DFS로 접근하며 접근될 때는 enter, 그 노드를 벗어날 때는 exit 함수를 실행한다고 공부했다. 따라서 enter에 구현을 하던, exit에 구현을 하던 결국 우리는 최종적으로 특정 메소드에 해당 노드 방문시 처리 함수를 정의해주어야하는 것 이었다.
그럼 이제 g4 rule을 보고 해당 토큰의 함수에 가서 그에 맞게 구현을 해주면 된다.

우선 IfflowOrder는 If_stmt를 다룰 때 더 자세히 설명하겠지만, Jasmin 상에서 If, elif label 명을 지어주기 위해 만들어둔 인스턴스이다. 본 프로그램에서 If label은 IFLABEL_(IfflowOrder2)(IfflowOrder), IFEND(IfflowOrder2)등 과 같이 구성된다.

WhileFlowOrder의 경우도 똑같이 Loop label을 작성하기 위해 사용되는 order instance이다. (예시 사진의 Lloop0 참조)
temFuncFormat의 경우는 Def_stmt에서 함수의 이름을 임시로 저장해두어 최종적으로 함수 저장 테이블에 기록해두기 위한 저장 공간이다.
result의 경우 컴파일링이 된 최종 jasmin code를 저장하고 있는 string 공간이며 이를 Test.j 파일에 write하는 것으로 프로그램을 끝내게 된다.
functionTable은 선언된 함수를 저장해두는 Hashtable이다.
table은 할당된 변수를 저장해두는 ArrayList 이다.
visitedNodes는 Enter method에 정의를 하는 본 프로그램의 특성상, enter에서 처리가 끝난후 다시 노드를 재방문하게 되어 중복 처리가 되는 현상이 일어난다. 이를 막기 위해서 set에 추가시켜 모든 방문 함수가 실행될 때 이미 처리가 된 노드인지 검사를 하게 한다.

makeFile() 의 경우는 그냥 최종 result 문자열을 담은 Test.j 파일을 디렉토리 내에 만들기 위해 exitProgram() 함수에 사용되는 private 함수이다.
1. enter & exitProgram
@Override public void enterProgram(tinyPythonParser.ProgramContext ctx) {
this.result+=(".class public Test\n");
this.result+=(".super java/lang/Object\n");
this.result+=("; standard initializer\n");
this.result+=(".method public <init>()V\n");
this.result+=("aload_0\n");
this.result+=("invokenonvirtual java/lang/Object/<init>()V\n");
this.result+=("return\n");
this.result+=(".end method\n");
/* 기본적으로 class 정의는 존재한다고 가정 */
}
@Override public void exitProgram(tinyPythonParser.ProgramContext ctx) {
this.result +=("return\n");
this.result += (".end method\n");
try {
this.makeFile();
}catch(Exception e){
this.result+=("Error occured");
}
}//return main
enterProgram의 경우 '본 텀프로젝트의 제약 조건에 Class 정의는 기본적으로 존재.(이름은 Test로 가정).' 이라고 주어져 있으므로 따로 동작을 정의해주는 것이 아니라 기본적인 public class 생성 및 기본 생성자를 하드코딩 해줬다.
여기서 java/lang/Object는 java에서 모든 class를 선언할 때 자동적으로 상속하는 super class이다.
생성자는 기본 생성자로 선언해주었다.

exitProgram의 경우 최종적으로 public class Test를 끝마치고 makeFile()을 실행시켜 결과를 Test.j에 저장하는 과정을 정의하였다.
2. enter & exitDefs
@Override public void enterDefs(tinyPythonParser.DefsContext ctx) {
if(visitedNodes.contains(ctx)){
return;
}
visitedNodes.add(ctx);
if(ctx.getChildCount()>0){
for(int i=0; i<ctx.getChildCount(); i++){
if(ctx.getChild(i).getClass().equals(tinyPythonParser.Def_stmtContext.class)){
enterDef_stmt((tinyPythonParser.Def_stmtContext) ctx.getChild(i));
}//def_stmt, 만약 이외의 경우 NEWLINE이므로 연산 X
}
}
}
/* program이 실행되면 시작하는 자동 method로, 사전에 함수들을 전부 정의하는 것을 의미한다. */
@Override public void exitDefs(tinyPythonParser.DefsContext ctx) {
this.result+=(".method public static main([Ljava/lang/String;)V\n");
this.result+=(".limit stack 32\n");
this.result+=(".limit locals 32\n");
}//main 함수의 시작
enterDefs는 앞서 계속 설명했던 것처럼 class의 시작과 동시에 사용할 모든 함수를 선언하는 선언부를 정의하는 토큰이다. 따라서 def_stmt, NEWLINE(\n)의 반복이 될 것이고 sub parsing tree를 직접 순회하며 def_stmt를 마주칠 때마다 방문처리를 해주게 된다. NEWLINE의 경우 따로 처리를 해주지 않을 것이기 때문에 생략한다.
exitDefs는 main 함수의 시작을 의미하므로, main 함수의 시작을 직접 하드코딩 해줬다.
3. enterStmt & enterSimple_stmt
@Override public void enterStmt(tinyPythonParser.StmtContext ctx) {
if(visitedNodes.contains(ctx)){
return;
}
visitedNodes.add(ctx);
if(ctx.getChild(0).getClass().equals(tinyPythonParser.Simple_stmtContext.class)){
enterSimple_stmt((tinyPythonParser.Simple_stmtContext) ctx.getChild(0));
}
else{
enterCompound_stmt((tinyPythonParser.Compound_stmtContext) ctx.getChild(0));
}
}
g4 rule에 따르면 stmt가 파싱될 시, simple_stmt | compount_stmt 라고 선언이 되어있다. 따라서 스캔된 stmt sub parse tree의 첫번째 child가 Simple일시 Simple_stmt로 간주하고 방문처리를, Compound일 시 Compount_stmt로 간주하고 방문처리를 한다.

@Override public void enterSimple_stmt(tinyPythonParser.Simple_stmtContext ctx) {
if(visitedNodes.contains(ctx)){
return;
}
visitedNodes.add(ctx);
enterSmall_stmt((tinyPythonParser.Small_stmtContext)ctx.getChild(0));
}
Simple_stmt의 경우도 똑같이 Small_stmt로 이어지는 경우밖에 g4 rule에 정의되어있지 않으므로, Small_stmt를 방문처리 해준다.
4. enterSmall_stmt
@Override public void enterSmall_stmt(tinyPythonParser.Small_stmtContext ctx) {
if(visitedNodes.contains(ctx)){
return;
}
visitedNodes.add(ctx);
if(ctx.getChild(0).getClass().equals(tinyPythonParser.Assignment_stmtContext.class)){
enterAssignment_stmt((tinyPythonParser.Assignment_stmtContext) ctx.getChild(0));
}
else if(ctx.getChild(0).getClass().equals(tinyPythonParser.Flow_stmtContext.class)){
enterFlow_stmt((tinyPythonParser.Flow_stmtContext) ctx.getChild(0));
}
else if(ctx.getChild(0).getClass().equals(tinyPythonParser.Print_stmtContext.class)){
enterPrint_stmt((tinyPythonParser.Print_stmtContext) ctx.getChild(0));
}
else{
enterReturn_stmt((tinyPythonParser.Return_stmtContext)ctx.getChild(0));
}
}
Small_stmt는 각각 assignment, flow(continue,break), print, return_stmt로 이어진다.
이번에는 구체적인 구현 시작과 구현의 접근법에 대해 알아보았다. 이렇듯, non-terminal간의 파싱은 단순히 방문처리를 반복하는 것이므로 아주 쉬운 구현이 가능하다. 하지만 parsing rule에 terminal이 등장하는 시점에서(leaf node에 도달)는 최종적으로 Jasmin 코드를 써주어야하는 action을 정의해야한다. 그 부분에서는 여러가지 아이디어가 필요하게 되는데, 다음부터는 그 부분을 중점으로 글을 작성해보겠다.
'ANTLR를 이용한 JASMIN 컴파일러' 카테고리의 다른 글
ANTLR를 이용한 tinyPython to Jasmin 컴파일러 만들기. - 6 (0) | 2024.01.08 |
---|---|
ANTLR를 이용한 tinyPython to Jasmin 컴파일러 만들기. - 5 (0) | 2024.01.04 |
ANTLR를 이용한 tinyPython to Jasmin 컴파일러 만들기. - 4 (0) | 2024.01.03 |
ANTLR를 이용한 tinyPython to Jasmin 컴파일러 만들기. - 2 (0) | 2023.12.28 |
ANTLR를 이용한 tinyPython to Jasmin 컴파일러 만들기. - 1 (0) | 2023.12.27 |