int $0x80

1. Kaleidoscope: Tutorial Introduction and the Lexer 한글 번역 본문

컴퓨터공부/엘엘비엠

1. Kaleidoscope: Tutorial Introduction and the Lexer 한글 번역

cd80 cd80 2017.07.20 16:01

1.1 Tutorial Introduction


이 튜토리얼은 LLVM을 이용해 자신만의 프로그래밍 언어를 만들어 볼 수 있도록 아주 쉽고 간단하게 단계별로 설명합니다

만들 언어의 이름은 Kaleidoscope고, 이 언어를 파싱해 LLVM IR로 바꿔주는 프론트엔드, 만들어진 IR을 이용해 프로그램을 최적화시키는 Optimizer, 그리고 최종 결과 IR을 원하는 아키텍쳐의 머신코드, 혹은 우리가 원하는 커스텀 아키텍쳐의 머신코드로 바꿔주는 백엔드단까지, 모든 레이어를 순서대로 구현해볼것입니다


튜토리얼 진행 순서는 아래와 같습니다

챕터 1. 튜토리얼 소개 및 Lexer 구현 : (이 글입니다) Kaleidoscope언어에 대한 소개와, 소스코드 토큰을 분석하는 Lexer를 구현합니다.


챕터 2. 파서, AST 구현 : 이전 장에서 구현한 Lexer(어휘분석기)를 바탕으로, Parser(구문분석기)를 구현하고, 소스코드의 AST를 구축합니다.이 튜토리얼에서는 재귀하향파싱과 연산자우선순위 분석에 대해 설명합니다. 여기까지는 LLVM의 기능을 이용하지 않는, 완전히 프론트엔드 구현만 합니다


챕터 3. LLVM IR 생성 : 이전 장에서 구현한 AST를 이용해 소스코드를 LLVM IR로 변환해봅니다


챕터 4. JIT, 최적화 기능 추가 : LLVM은 clang과 더불어 단독 컴파일러로도 많이 사용되지만, LLVM의 JIT기능 사용에 관심있는 사람들도 굉장히 많습니다. 심지어 JIT기능을 추가하는데는 3줄만 넣어주면 되기 때문에 먼저 설명합니다.


챕터 5. 실행 흐름 기능 추가 : 이전까지 구현한 컴파일러는 선형 소스코드만 지원합니다. 이 장에서는 if, for 등과 같은 분기문을 언어에 추가해줍니다.


챕터 6. 사용자지정 연산자 추가 : C++에서 처럼 연산자들을 커스터마이징 할 수 있는 기능을 우리 언어에 추가합니다.


챕터 7. 변수 추가 : 대입연산자를 이용해 변수를 만드는 기능을 추가합니다. 


챕터 8. 컴파일 : LLVM IR을 오브젝트 파일로 컴파일시켜주는 코드를 우리 컴파일러에 추가합니다.


챕터 9. 디버깅정보 추가 : 오브젝트 파일에 소스코드 디버깅정보를 추가시켜봅니다


챕터 10. 여러 기능 추가 : 여기서는 자세하게는 다루진 않지만 가비지컬렉션, 예외처리, 디버깅지원 등 유용한 기능들을 구현하려면 어떻게 공부해야 하는지 설명합니다.





1.2 The Basic Language

우리가 만들 언어의 이름은 Kaleidoscope입니다.

Kaleidoscope는 절차지향 언어고, 함수를 정의할 수 있고, 조건분기를 할 수 있으며, 수학계산이 가능한 간단한 언어입니다

처음에는 단순한 선형계산밖에 하지 못하지만, 조건분기, 연산자 오버로딩등의 기능들을 점점 추가할겁니다.


언어 구현을 간결하게 하기 위해 모든 변수 타입은 double형입니다(64비트 실수). 그리고 파이썬처럼 변수 선언시에 타입선언을 필요로 하지 않습니다


코드 예시:


그리고, Kaleidoscope는 외부 라이브러리 함수를 호출할 수 있는 기능도 있습니다




1.2 The Lexer

소스코드를 컴파일하기 위해선, 텍스트파일과 다름없는 소스코드에 들어있는 언어의 요소들을 분석해야합니다. 예를들어 int i = 3; 이라면, int가 무엇이고 i가 무엇이고 = 가 무엇이고 3이 무엇인지, 그리고 각 요소들이 어떻게 서로 상호작용하는지 분석해야 합니다


소스코드를 분석하는 맨 첫 단계를 Lexical Analysis(어휘분석)이라고 하고, 어휘분석을 하는 툴을 Lexer라고 합니다.

Lexer에서는 예로 든 것 처럼, int i = 3; 이 있을 때, int가 type specifier고, i가 variable name이고, =이 operator고, 3이 constant int라는것을 분석하는 작업을 하고 각각을 토큰이라고 합니다


렉서를 구현할때는 우선 토큰의 종류부터 정리합니다


우리가 구현할 Lexer는 현재 문자가 EOF, def, extern, identifier, number라면 각각 tok_~~을 리턴하고, tok_identifier일 경우 IdentifierStr 변수에 identifier의 이름을 저장해주고, tok_number일 경우에는 NumVal에 그 숫자를 저장해둡니다.

'+' 같이, 현재 Lexer에서 처리되지 않는 토큰들은 그 문자 자체를 리턴해 별도로 처리합니다(주석의 경우 '#' 이 리턴되면 주석으로 처리)


이제 코드에서 토큰을 가져오는 코드를 작성해봅니다. 원문에서는 standard input에서만 입력받게 돼있지만, 본 튜토리얼에서는 임의의 file stream에서 읽어오게 바꿨습니다

위의 코드 바로 밑에부터 작성을 하시면됩니다.

우선 공백은 모두 건너뜁니다.



현재 문자가 알파벳이라면 identifier로써 취급해 처리합니다

그 중, 예약어인 def와 extern에 대해서는 따로 처리를 하고, 나머지는 모두 tok_identifier로 리턴합니다

따로 언급되진 않았지만, 숫자가 아닌 모든 문자는 숫자로 시작하면 안됩니다.(3cakes 같은 변수명 불가)

그리고 IdentifierStr에 현재 식별자가 저장됩니다.



만약 첫 시작이 숫자라면, 쭉 수를 받고, NumVal에 설정해줍니다.

int나 float 상관없이 모두 double로 처리합니다(strtod)


이 코드는 123, 123.123 등은 정상적으로 처리하지만, 12.34.56또한 숫자로 처리합니다. 원문 튜토리얼에서는 이 문제를 직접 고쳐보라고 돼있습니다


아래는 제가 고친 예시입니다



다음으로 주석을 처리합니다



#이 나오면 주석으로 취급해 그 줄의 끝이나 파일끝이 나올때까지 스킵합니다


마지막으로 다른 모든 문자들을 단일문자로 리턴합니다.



바로 lastChar를 리턴하지 않는 이유는 gettok함수 맨 처음에서 lastChar변수를 static으로 선언했기 때문입니다. 위 다섯줄가량을 읽어보시면 이해하실 수 있습니다.



여기까지 Lexer를 구현해봤습니다. 다음 글에서는 이 Lexer를 도구로 이용해 Parser를 구현하고 Abstract Syntax Tree를 구현해보겠습니다



여기까지 하시면서 실수가 없었는지 확인해보시려면 clang -c Lexer.cpp를 해보시면 됩니다. 문제가 있으면 에러가 뜰것이고 없다면 Lexer.o가 만들어질것입니다

아래는 Lexer의 full code입니다.


Lexer.cpp


Lexer.h



Lexer구현한것이 잘 작동하는지 보기 위해 간단한 드라이버를 작성해보겠습니다


main.cpp



컴파일은 

clang++ -o main main.cpp Lexer.cpp

로 하시면 됩니다.

Lexer에서는 LLVM기능을 사용하지 않았기 때문에 따로 할건 없습니다




신고
2 Comments
댓글쓰기 폼