5일만에 뚝딱 스크립트 언어 만들기 PGLight (2/5)

Posted by 적분 ∫2tdt=t²+c
2013.06.15 20:19 프로그래밍/PG어



저번에 PGLight언어 문법를 BNF스타일로 정리했습니다. 이제 파싱하는 코드를 짤 차례인데요, 실은 저번에 제대로 정리하지 않고 넘어간게 있었습니다.

원래 BNF로 문법을 정리할때 말단 노드들이 어떻게 정의되는지도 명확히 해놔야 파싱 코드 작성할 때 용이합니다만, 그런거 없다! 하고 넘어왔었죠.


저번 BNF 표기법에서 어물쩍 넘어간

<identifier>와 <simple_literal>이 어떤 놈인지를 명확하게 하고 갑시다.

<identifier>는 말그대로 식별자가 될만한 놈들인데, 대게의 언어에서는 특수기호가 아닌 문자열로 구성됩니다. 정규표현식으로 쓰자면 [A-Za-z][A-Za-z0-9]* 요런식으로 되겠습니다. 근데 PGLight는 소스에서부터 유니코드를 지원하기로 했기때문에, 식별자에도 특수문자를 제외한 모든 유니코드문자를 허용하기로 했는데... 음 정규표현식으로 쓰기엔 귀찮으니 생략하도록 할게요ㅋ

<simple_literal>은 문장 내에서 쓰이는 단순한 상수값들입니다. 정수인경우에는 0 | [1-9][0-9]* 가 될것이고, 문자열일 경우는 "" 따옴표로 둘러쌓인 덩어리, boolean 값일 경우는 true | false가 될겁니다. 이중 파싱하기 까다로운건 문자열인데요, 따옴표 내에 \로 이스케이프 된 경우까지 따져야하기 때문이죠. 그래서 정규표현식으로 쓰기에는 조금 까다로운 면이 있으니 생략할게요.





자, 첫번째 단계는 줄줄이 이어져있는 소스 코드를 최소 의미 단위인 토큰(token)으로 쪼개는 것입니다. 이를 Tokenize라고 합니다. 일단 언어에 어떤 토큰들이 있나를 정의할 필요가 있습니다.


딱 보면 뭐하는 토큰인지 알기 쉽게 나름대로 이름을 지었습니다. 토큰 타입을 알려주는 enum 뿐만 아니라 각각의 토큰을 담는 토큰 구조체가 필요하겠죠. 보통 토큰을 담을때는 토큰의 종류문자열, 이 두 가지만 담으면 충분하다고 생각할 수 있는데, 요러면 나중에 에러 코드 뿜어내는 부분을 작성할때 힘든 부분이 많습니다. 그래서 토큰이 위치한 줄번호까지도 함께 토큰 구조체에 담아두고, 나중에 구문 오류가 발생했을때 줄번호를 표시할수 있게 해줍니다.



123 4*2

123, , 4, *, 2

abc      de

abc, , de

로 분리되어야합니다.

띄어쓰기는 싹다 무시하고, * 와 같은 연산자 앞에서는 무조건 토큰을 끊어줘야하지요. 자 먼저 무조건 토큰을 끊어줘야하는 문자들을 구분해내볼까요



이스케이프에서 사용되는 \xAA 같은 표현을 해석할때 위 두 함수는 매우 유용합니다.

다음은 문자열에서 \로 시작하는 이스케이프 문자열을 해석해주는 함수입니다.




이제 진짜로 tokenizer를 짜봅시다. 좀 길고 코드가 더러운데 그냥 봐주세요 ㅠㅠ (너무 지저분해서 다시 건드리기조차 무섭네요)



므앙

쫌 길죠. 근데 어려운건 하나도 없는 코드입니다. (파싱 작업이라는게 실제로 어려운 부분은 하나도 없습니다. 다만 귀찮은 부분이 너무 많을뿐)


토크나이징 끝냈으면, 이제 진짜 파싱을 시작할 수 있어요. 파싱을 시작하기 전에 AST(Abstract Syntax Tree, 추상구문트리)를 설계할 필요가 있습니다. 우리가 파싱을 통해서 얻고자 하는게 바로 이 구문트리니까요.

PGLight의 문장 구조는 크게 표현식(expression)과 구문(statement)로 나눌 수 있어요.

표현식은 이런 놈들

1 2, 3*4*foo("sent")

이구요,

구문

if(test())

{

foo(bar);

}


var a = 123*4;

요런 놈들입니다. 그래도 모르겠다면, 표현식은 연산자로 연결되어서 값을 평가할 수 있는 코드 일부분, 구문은 표현식을 포함하는 문장으로, 실제로 코드를 구성하는 단위라고 생각하시면 됩니다.

먼저 표현식을 나타내는 트리 구조체를 짜봅시다.

그 다음은 구문들을 나타내는 트리 구조체!

PGLID, PGLFunctionData 같은 놈들이 등장해서 코드가 번잡해졌지만 저런거 싹다 무시하고, 전체적인 구조만 보면돼요. 이제 우리가 할 일은 토큰목록을 파싱하여 이 구조에 맞춰서 내용물들을 채워주면 되는거죠. 으앙 코드가 너무 길어져서 터질거 같네요. 본격 파싱은 다음으로 넘겨야겠습니다.

신고
Tags
이 댓글을 비밀 댓글로
  1. 글 유용하게 잘 읽고 있습니다!
    게시물에 작성된 코드의 언어를 좀 알 수 있을까요?
    • 소스코드는 c++11입니다.
    • 사실 이 코드들을 자바로 짜보고 있습니다. 정의만 해놓고 완성하지 않은 파싱 함수만 절반 가량 되는데 1000줄을 바라보고 있네요...
    • 파싱부분이 어려운건 없는데 할일이 많아서 작성하기가 귀찮죠. 사실 요즘에는 사람이 저렇게 일일히 파싱 부분을 작성하는게 아니라 파싱 규칙만 던져주면 자동으로 파싱 코드를 만들어주는 yacc 같은 도구들을 많이 씁니다. 저는 파싱 쪽으로 공부도 해볼겸 직접 작성해본거구요. 아무튼 화이팅입니다~