저번에 의해 간단한 언어 Hum을 컴파일 하는 과정을 다시 한번 해보겠다.
그 전에 컴파일이라고해서 바로 기계어로 변환되는것은 아니다. 컴파일은 기계어에 가까워지게 바뀐다면 그 자체로 컴파일이라 하고 그 반대라면 디컴파일이라고 한다.
translate함수를 구현하는 중
-구조 설계-
머릿속으로 구조적 설계 했을 때 공백 간격으로 문자열들을 받아 Hul* pattern으로 이루어진 string은 tokens arrayList에 저장 그리고 정수가 들어온다면 변수 선언과 반복횟수는 숫자 순서 출력이 반대이기에 dequeue를 쓸 생각을 했다.
그리고 tokenList에 있는 값들마다 token을 부여하고 부여된 token에 저장되어 있는 출력값을 읽어준다.
---------------------------------------------
-코딩-
이제 실전 코딩 시작!!!
먼저 Hul?, Hul>, Hul! 와 같은 경우에는 들여쓰기도 필요 없고 Token에 각 해당되는 enum타입이 들어오면 그대로 출력해준다.
-입력된 값들 control 하고 해당하는 토큰으로 변환해주는 parser class
public class Parser {
public enum Token{
READ("Hul?"), WRITE("Hul!"), INC("Hul>"), DEC("Hul<"),
BLOCK_BEGIN("Hul{"), BLOCK_END("}"), FAIL("fail");
String str;
Token(String s) {
this.str =s;
}
}
private ArrayList<Token> tokens = new ArrayList<>();
//가장 바깥자료가 마지막에 위치하고 바깥자료 먼저 출력해야함으로 스텍 쓰기
private Deque<Integer> maxList = new ArrayDeque<>();
public Deque<Integer> getMaxList() {
return maxList;
}
public ArrayList<Token> getTokens() {
return tokens;
}
public Parser(String msg) {
parsing(msg);
}
public void parsing(String msg){
//split함수 이용해 msg들 분리
String[] msgList = msg.split("[\n\t \r\n]+");
//리스트에 정수가 있다면 따로 처리하기 위해 분리함
for(String s: msgList){
if(s.matches("[+-]?\\d*(\\.\\d+)?")){
//문자열이 정수인지 확인 후 정수면 deque에 저장
maxList.push(Integer.parseInt(s));
}
else{
tokens.add(findToken(s));
}
}
}
private Token findToken(String msg){
if(msg.equals(Token.BLOCK_BEGIN.str)) return Token.BLOCK_BEGIN;
if(msg.equals(Token.BLOCK_END.str)) return Token.BLOCK_END;
if(msg.equals(Token.READ.str)) return Token.READ;
if(msg.equals(Token.WRITE.str)) return Token.WRITE;
if(msg.equals(Token.DEC.str)) return Token.DEC;
if(msg.equals(Token.INC.str)) return Token.INC;
return Token.FAIL;
}
}
그리고 Hul파일에서 C파일로 바꿔는 HulToC클래스
public class HulToC {
/**
*
Hul? standard input으로 정수를 읽어서 변수에 저장한다.
Hul? standard output으로 변수를 출력한다.
Hul> 변수의 값을 1증가시킨다.
Hul< 변수의 값을 1감소시킨다.
Hul{ <명령들> } <반복 횟수>
<명령들>을 <반복 횟수>번 수행한다
*/
Parser parser;
FileWriter fw;
String hulText;
// 들여쓰기 { -> ++, } -> --
static int lines=1;
//정수가 입력되었을 때 순서를 부여하기 위한 값
static int countNum=0;
public Parser getParser() {
return parser;
}
public HulToC(String msg, FileWriter fw) throws IOException {
this.fw = fw;
//기본 셋팅 항상 일관되게
this.hulText=msg;
this.fw.append("#include <stdio.h>\nint main() {\n\tint _hul;\n");
//Parser객체를 생성과 동시에 void parsing함수로 인해 tokens혹은 numlist에 저장됌
parser = new Parser(msg);
//입력된 정수들 이터레이터로 꺼내주고 변수로 만들어주기
Iterator<Integer> itr = this.getParser().getMaxList().iterator();
//정수가 입력된 갯수만큼만 만들어줘야함
int count= this.getParser().getMaxList().size();
for(int i=0; i<count;i++) {
this.fw.append("\tint max" + i + " = " + itr.next() + ";\n");
}
}
//tokens를 순회할 함수
public Parser.Token next() {
if(parser.getTokens().isEmpty()){
return null;
}
//return parser.getTokens().get(0);
return parser.getTokens().remove(0);
}
//이상태는 이미 tokens와 maxList가 다 완성 되어있는 상태 각 값들마다 출력형식을 정한다,.
public void translate(Parser.Token token) throws IOException {
switch (token){
case READ:{
//standard input으로 정수를 읽어서 변수에 저장한다.
fw.append(("\t").repeat(lines)+"printf(\"input: \");\n"+("\t").repeat(lines)+"scanf(\"%d\", &_hul);\n");
break;
}
case WRITE:{
//standard output으로 변수를 출력한다.
fw.append(("\t").repeat(lines)+"printf(\"%d\", _hul);\n");
break;
}
case INC:{
//변수의 값을 1증가시킨다.
fw.append(("\t").repeat(lines)+"_hul++;\n");
break;
}
case DEC:{
//변수의 값을 1감소시킨다.
fw.append(("\t").repeat(lines)+"_hul--;\n");
break;
}
//Hul{ <명령들> } <반복 횟수>
//<명령들>을 <반복 횟수>번 수행한다.
case BLOCK_BEGIN:{
fw.append(("\t").repeat(lines)+"for (int i"+countNum+"=0; i"+countNum+" < max+"+countNum+"; i"+countNum+"++){\n ");
countNum++;
lines++;
break;
}
case BLOCK_END:{
fw.append(("\t").repeat(lines)+"}"+this.getParser().getMaxList().removeLast()+"\n");
lines--;
break;
}
default: {
fw.append("문제발생 !");
}
}
}
//cmd 윈도우 명령어 처리기를 이용하기
}
끝! 이제 main class에서 실제로 작동되는지 확인해보자 .
임의로 String값에 파일이름을 해놓고 ->(나중에 Scanner를 이용해 파일 이름 받는걸로 바꿀거임 )
public class Test {
public static void main(String[] args) {
String msg = "Hul<";
HulToC hulToC;
//입력값 확인
Pattern pattern = Pattern.compile("Hul*");
Matcher matcher = pattern.matcher(msg);
System.out.println(matcher.find());
//읽어오기
String filePath = "test5.hul";
try(FileInputStream stream = new FileInputStream(filePath)){
byte[] rb = new byte[stream.available()];
///file이 끝에 도달하면 -1반환
while(stream.read(rb)!=-1){}
msg = new String(rb);
}
catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//쓰기
try(FileWriter fw = new FileWriter("test.c")) {
hulToC = new HulToC(msg, fw);//이 순간 모든 command들이 enum타입에 따라 tokens ArrayList에 담김
while (!hulToC.getParser().getTokens().isEmpty()){
hulToC.translate(hulToC.next()); //Parser에 있는 tokens
}
fw.append("\treturn 0;\n}\n");
fw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
inoput과 output확인
벌써부터 머리 아픈 전공생들 사이에서 복전생의 악깡버로 버티기..
'3-2 > Compiler Introduction' 카테고리의 다른 글
[컴파일러 개론]LL 파싱과 FIRST, FOLLOW (0) | 2022.09.19 |
---|---|
[컴파일러 개론] 구문 분석기 (0) | 2022.09.15 |
[컴파일러 개론] 컴파일러 만들기(1) (0) | 2022.09.05 |
[컴파일러 개론] 어휘 분석 (0) | 2022.09.05 |
컴파일러 개론 1주차 2022.09.01 (0) | 2022.09.01 |