* 본 내용은 충남대학교 조은선 교수님의 컴파일러 개론을 수강하고 작성한 글입니다.
1. enterExpr
@Override public void enterExpr(tinyPythonParser.ExprContext ctx) {
if(ctx==null || visitedNodes.contains(ctx)){
return;
}
if(ctx.NUMBER()!=null){
visitedNodes.add(ctx);
this.result+=("bipush "+Integer.parseInt(ctx.getChild(0).getText()))+"\n";
}//상수 load
else if(ctx.NAME()!=null){
visitedNodes.add(ctx);
if(table.contains(ctx.getChild(0).getText())) {
this.result+=("iload_"+table.indexOf(ctx.getText())+"\n");
}//변수 호출
else if(this.functionTable.containsKey(ctx.getChild(0).getText())){
enterOpt_paren((tinyPythonParser.Opt_parenContext) ctx.getChild(1));
this.result+=("invokestatic Test/"+functionTable.get(ctx.getChild(0).getText())+"\n");
}//함수 호출
}//Name을 기반으로 table에 매핑되어있는 순서 호출
else if(ctx.OPEN_PAREN()!=null){
visitedNodes.add(ctx);
enterExpr((tinyPythonParser.ExprContext)ctx.getChild(1));
}//(expr)
else{
visitedNodes.add(ctx);
//Expr 긁어가며 load => 연산자 읽어서 연산
enterExpr((tinyPythonParser.ExprContext)ctx.getChild(0));
for(int i=2; i<ctx.getChildCount(); i+=2){
enterExpr((tinyPythonParser.ExprContext)ctx.getChild(i));
if(ctx.getChild(i-1).getText().equals("+")){
this.result+=("iadd\n");
}
else{
this.result+=("isub\n");
}
}
}//사칙 연산, 계산해서 tempVal 출력. 매 expr의 끝은 결국 NAME 이거나 NUMBER이며 이 연산의 끝은 tempVal0이 setting되는 것이다.
}// Number일 시, 해당되는 값을 tempVal0에 set, Name일 시 해당 변수의 위치를 Set OPEN_PAREN일시 () 무시하고 expr 진입, op일 시,
대부분의 terminal 노드 이전에 파싱이 되는 노드 expr의 enter 함수이다.
그만큼 표현하는 범위도 방대하기 때문에 문법을 첨부했다.
expr:
NAME opt_paren
| NUMBER
| '(' expr ')'
| expr (( '+' | '-' ) expr)+
;
문법을 보지않아도 이해가 잘된 사람들은 expr이 어떤 역할을 하는지 알 것이다.
expr의 마지막은 NUMBER나 NAME opt_paren으로 귀결되고, 이외에는 '(' expr ')', expr '+'|'-' expr.. 과 같이 expr의 표현하는 방식을 다시 expr로 포장한 것이다.
ex) 3+3+5의 파싱과정
enterExpr(3+3+5) - expr '+' expr '+' expr =>여기서 개별 expr로 enterExpr
enterExpr(3) -> NUMBER {bipush 3}
'+' -> + {iadd}
enterExpr(3) -> NUMBER {bipush 3}
'+' -> + {iadd}
enterExpr(5) -> NUMBER {bipush 5}
그렇다면 이제 구현을 해보자.
만약 파라미터 ctx가 NUMBER였다면, 단순히 스택에 해당 expr NUMBER 값을 load해주면 된다. (bipush)
받아온 ctx가 NAME일 시 함수를 가리키거나, 변수를 호출하는 expression이다.
따라서 해당 표현을 table에서 검사해 존재할 시, iload_(table index)를 통해 스택에 해당 변수 메모리의 값을 로드한다.
만약 functionTable에 존재할 시, enterOpt_paren으로 함수 선언에 사용된 파라미터들을 처리한다. { a '()' <- ()부분 }
enterOpt_paren이 끝났을 시, 함수 파라미터들의 load가 끝났다고 가정했으므로, 바로 invokestatic Test/(함수이름)을 통해
함수를 실행시킨다. (런타임 스택 개념도 같이 생각해주면 좋다.)
받아온 ctx가 OPEN_PAREN일 시, (3+5)와 같은 형태의 괄호 표현일 것이다. 과제를 제출했을 때는 단순히 괄호는 처리해주지 않을 것이라는 것만 생각하여 OPEN_PAREN을 걷어내고 안에 있는 expr만 접근했다. 그러나 지금와서 생각하니 조금 수정이 필요할 것 같다.
괄호 수식내에 괄호가 또 존재할 수 있으며 만약 존재시, 해당 부분을 먼저 enterExpr을 해줘야할 것 같다.
따라서, 입력받은 expr이 OPEN_PAREN으로 시작하며, ChildCount가 2보다 클 시 (즉, 수식일 시.) 받아온 ctx를 getText로 문자열로 바꿔주고, 변환된 수식 문자열의 처음부터 수식을 읽어나가며 괄호 표현이 사용된 expr들을 찾고, 해당 expr부터 enter를 해줘 load를 해주는 것이 필요할 것 같다.
만약 expr로 시작할 시, 무조건 +, -로 연결된 수식의 구조이므로, 앞부터 읽어나가며 {'+'|'-' enterExpr} 세트를 처리해나간다. 여기서 +가 파싱될 시 iadd, -가 파싱될 시 isub를 해주면 된다.
사실 이 수식처리 부분을 학기가 끝난 지금 돌아보니 너무 대충 구현해준 것 같다.
사칙연산에 괄호가 들어갈 수 있으므로, 먼저 파싱된 expr {('+'|'-') expr} + 표현의 expr들을 전부 들어가서 OPEN_PAREN이 존재하는 지 확인하고, OPEN_PAREN 구조의 expr일 시 해당 expr load Jasmin code를 먼저 작성해줬어야하는데, 지나치게 단순하게 처리해 오류가 발생할 것 같다.
보다 완벽하게 구현하기 위해서는 Postfic Notation을 사용하고, 연산자 우선순위 또한 정해주면 될 것이다.
(해당 방법을 사용 시, +, - 가 아닌 연산자 우선순위가 정의된 모든 연산자를 사용 가능하다.)
* 해당 사칙연산 파싱 과정에는 Postfix Notation을 활용하면 좋을 것이다.
여기서 해당 내용까지 다루기에는 너무 지저분해지기에 잘 요약해둔 다른 블로그의 링크를 첨부한다.
2. enterOpt_paren
@Override public void enterOpt_paren(tinyPythonParser.Opt_parenContext ctx) {
if(visitedNodes.contains(ctx)){
return;
}
visitedNodes.add(ctx);
if(ctx.getChildCount()>2){
for(int i=1; i<ctx.getChildCount(); i+=2){
enterExpr((tinyPythonParser.ExprContext) ctx.getChild((i)));
}
}
}
괄호가 파싱되었을 때, 특히 a() 와 같이 함수에서 파라미터 입력 혹은 선언에 사용되는 괄호가 파싱될 시 사용되는 함수이다.
따라서 괄호 안에 있는 모든 expr을 enter 해줘 load를 하든, bipush를 하든 처리를 해준다.
3. 결과
.class public Test
.super java/lang/Object
; standard initializer
.method public <init>()V
aload_0
invokenonvirtual java/lang/Object/<init>()V
return
.end method
.method public static sum(II)I
.limit stack 32
.limit locals 32
iload_0
iload_1
iadd
ireturn
.end method
.method public static main([Ljava/lang/String;)V
.limit stack 32
.limit locals 32
bipush 3
istore_0
bipush 4
istore_1
iload_0
iload_1
invokestatic Test/sum(II)I
istore_2
getstatic java/lang/System/out Ljava/io/PrintStream;
iload_2
invokevirtual java/io/PrintStream/println(I)V
iload_0
bipush 3
if_icmplt IFLABEL_00
getstatic java/lang/System/out Ljava/io/PrintStream;
iload_0
invokevirtual java/io/PrintStream/println(I)V
goto IFEND0
IFLABEL_00:
iload_1
bipush 2
if_icmpge IFLABEL_01
getstatic java/lang/System/out Ljava/io/PrintStream;
iload_1
invokevirtual java/io/PrintStream/println(I)V
goto IFEND0
IFLABEL_01:
iload_1
bipush 3
if_icmple IFELSE0
getstatic java/lang/System/out Ljava/io/PrintStream;
bipush 3
invokevirtual java/io/PrintStream/println(I)V
goto IFEND0
IFELSE0:
getstatic java/lang/System/out Ljava/io/PrintStream;
iload_2
invokevirtual java/io/PrintStream/println(I)V
IFEND0:
Lloop0:
iload_2
bipush 6
bipush 10
iadd
if_icmpge Lend0
iload_2
bipush 7
if_icmpne IFLABEL_10
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "c=7"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
iload_2
bipush 1
iadd
istore_2
goto IFEND1
IFLABEL_10:
iload_2
bipush 9
if_icmpne IFELSE1
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "break point"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
goto Lend0
goto IFEND1
IFELSE1:
getstatic java/lang/System/out Ljava/io/PrintStream;
iload_2
invokevirtual java/io/PrintStream/println(I)V
iload_2
bipush 1
iadd
istore_2
IFEND1:
goto Lloop0
Lend0:
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello world! This is final Term project."
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return
.end method
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
public class Test {
public Test() {
}
public static int sum(int var0, int var1) {
return var0 + var1;
}
public static void main(String[] var0) {
byte var32 = 3;
byte var1 = 4;
int var2 = sum(var32, var1);
System.out.println(var2);
if (var32 >= 3) {
System.out.println(var32);
} else if (var1 < 2) {
System.out.println(var1);
} else if (var1 > 3) {
System.out.println(3);
} else {
System.out.println(var2);
}
while(var2 < 6 + 10) {
if (var2 == 7) {
System.out.println("c=7");
++var2;
} else {
if (var2 == 9) {
System.out.println("break point");
break;
}
System.out.println(var2);
++var2;
}
}
System.out.println("Hello world! This is final Term project.");
}
}
제작한 컴파일러가 생성한 Test.j jasmin 코드를 jasmin decompiler로 다시 java로 구성한 코드이다.
결과적으로 tinyPython -> (자체 제작 컴파일러) -> Jasmin -> (Jasmin decompiler) -> Java code 로 컴파일링을 완료하였다.
이번 포스팅을 끝으로 마치려고 했으나, 일정이 있어 다음 포스팅을 마지막으로 작성할 것 같다.
다음 포스팅에서는 Test.j Jasmin code를 Test.tpy 코드와 비교해가며 어떤 과정으로 나의 컴파일러가 동작했을지 간단히 따라가볼것이고, 터미널에 어떻게 명령어를 주어 컴파일링, 디컴파일링을 했는지 기록할 것이다.
'ANTLR를 이용한 JASMIN 컴파일러' 카테고리의 다른 글
ANTLR를 이용한 tinyPython to Jasmin 컴파일러 만들기. - 完 (0) | 2024.01.10 |
---|---|
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 컴파일러 만들기. - 3 (0) | 2023.12.29 |