๐ ํด๋น ํฌ์คํ
์ ๋ฐ๋ฐ๋ฅ๋ถํฐ ๋ง๋๋ ์ธํฐํ๋ฆฌํฐ in go ์ฑ
์ ์ฝ๊ณ ๊ฐ์ธ์ ์ธ ์ ๋ฆฌ ๋ชฉ์ ํ์ ์์ฑ๋ ๊ธ์
๋๋ค. ๋ณธ ํฌ์คํ
์ ์ฌ์ฉ๋ ์๋ฃ๋ ๋ชจ๋ ๋ณธ์ธ์ด ์ง์ ์ฌ๊ตฌ์ฑํ์ฌ ์์ฑํ์์์ ์๋ฆฝ๋๋ค.
์ต๊ทผ์ C์ธ์ด๋ฅผ ์กฐ๊ธ์ฉ ์ ํ๊ธฐ ์์ํ๋ฉด์ ๋ก์ฐ ๋ ๋ฒจ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด ๊ณต๋ถ์ ๋ํ ๊ฐ์ฆ์ด ๋ง์์ก๋ค. ํ์ง๋ง C์ธ์ด์ ๋ํ ๊ธฐ์ด๋ฅผ ๋ฐฐ์ฐ๊ณ ๋ ๋ค, ๊ฐ์ฅ ๋นํน์ค๋ฌ์ ๋ ๋ถ๋ถ์ ํ์ฌ ์ค๋ฌด์์ C์ธ์ด๋ฅผ ์์ฃผ ์ ํ ์ผ์ด ์์ด์ ํ๋ก์ ํธ ํ ๋งํ ๊ฒ ์๋ค๋ ๊ฒ์ด์๋ค. ๋ฌผ๋ก Python์ ๊ตฌํ์ฒด ์ค ํ๋์ธ CPython ์์ค์ฝ๋๋ฅผ ์ดํด๋ณผ ์ ์๊ฒ ์ง๋ง, ์ด๊ฒ๋ ๋จ์ง '๋ณด๊ธฐ๋ง ํ ๋ฟ'์ด์ง, ๋ญ๊ฐ ๊ฒฐ๊ณผ๋ฌผ์ด ์๊ฑฐ๋ ๋ด ์ง์์ผ๋ก ์ฒด๋๋๋ ๋๋์ด ์๋์๋ค. ๊ทธ๋ฌ๋ ์ค, ์์ฆ ์ค๋ฌด์์ ์ฟ ๋ฒ๋คํฐ์ค ๊ธฐ์ ๊ณผ Go๋ก ์์ฑ๋ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ Milvus(๊ฒ์ ์์ง์ C๊ณ์ด๋ก ์์ฑ๋์์ง๋ง) ๋ฑ์ ๋ค๋ฃจ์ด๋ณด๋ฉด์ Go์ธ์ด์ ๋ํ ๊ณต๋ถ์ ํ์์ฑ์ ๋๊ผ๋ค. ๊ทธ๋ฆฌ๊ณ ์๋์ ์ฑ
์ ์ถ์ฒ๋ฐ์ CS ๊ณต๋ถ๋ฅผ ๋ ํ๊ณ ์ถ์๋๋ฐ, ์๋ ์ฑ
์ด Go์ธ์ด๋ก ์์ฑ๋ ๊ฒ์ ๋ฐ๊ฒฌํ๋ค! ๊ทธ๋์ ํด๋น ์ฑ
์ ๊น๊ฒ ๊ณต๋ถํ๊ธฐ๋ก ๋ง์ ๋จน์๋ค. CS ์ง์์ ์์ค์ฝ๋ ๋ ๋ฒจ์์ ์ดํดํ๊ณ , ๊ทธ ์์ค์ฝ๋๋ ๋ด๊ฐ ๋ฐฐ์ฐ๊ณ ์ ํ๋ Go์ธ์ด๋ฅผ ๋ฐฐ์ฐ๋ฉด์ ๋ง์ด๋ค. ๊ทธ๋ฌ๋ฉด ์ด๋ฒ์๋ ์ฒ์ฒํ ํ ๊ฑธ์์ฉ ๋ด๋์ด๋ณด์!
์ผ๋ฐ์ ์ผ๋ก ์ฌ๋๋ค์ด ์ฃผ๋ก ์ฌ์ฉํ๋ Python ์ฆ, CPython ๊ตฌํ์ฒด๋ก ๊ตฌํ๋ Python ์ธ์ด๋ ์ธํฐํ๋ฆฌํฐ ์ธ์ด์ ์ํ๋ค. ๋ฌผ๋ก ๋ด๋ถ์ ์ผ๋ก ์ผ๋ถ ๋ฐ์ดํธ ์ฝ๋๋ก ์ปดํ์ผํ๋ ๊ณผ์ ์ด ์์ง๋ง, C์ธ์ด๋ Java, Go ์ธ์ด๋ค๊ณผ ๊ฐ์ ์์ ์ปดํ์ผ ์ธ์ด๋ ์๋๋ค. Python ์ด๋ผ๋ ์ธ์ด๋ฅผ ์ฃผ๋ก ์ฌ์ฉํด์จ ๊ฐ๋ฐ์๋ก์, ๊ถ๊ธํ๋ค. ๋์ฒด ์ธํฐํ๋ฆฌํฐ๋ ์ด๋ค ์๋ฆฌ๋ก ๋ง๋ค์ด์ง๋ ๊ฒ์ผ๊น?
์ธํฐํ๋ฆฌํฐ๋ ์ ๋ง ๊ทธ ์์ฒด๋ก๋ ๋จ์ํ๋ค. 'interpret' ๋ผ๋ ์์ด๋จ์ด์ ์๋ฏธ์ฒ๋ผ "์ด๋ค ๊ฒ์ ํด์ ์ฆ, ๋ฒ์ญํ๋ค". ๋ค์ ๋งํด, ์ด๋ค ๊ฒ์ ์
๋ ฅ์ผ๋ก ๋ฐ๋ ์๊ฐ, ๋ฐ๋ก ๋ฌด์์ธ๊ฐ ์ถ๋ ฅ์ผ๋ก ํ์ด๋์จ๋ค. ์ฐ๋ฆฌ๊ฐ ํํ ๋ํํ ์ธํฐํ๋ฆฌํฐ๋ผ๊ณ ํ๋ ๊ฒ์ฒ๋ผ, ํฐ๋ฏธ๋์ python ์ด๋ผ๋ ๋ช
๋ น์ด๋ฅผ ์ณ ์ ์ํ ํ, ์ฝ๋๋ฅผ ์
๋ ฅํ ๋๋ง๋ค ๊ฒฐ๊ณผ๋ฌผ์ด ์ฝ์์ ์ถ๋ ฅ๋์ด ๋์จ๋ค. ๊ทธ๋ฌ๋ฉด ๋จ์ํ ๋์์ ์ํํ๋ ์ธํฐํ๋ฆฌํฐ๋ ๋์ฒด ์ด๋ค ์๋ฆฌ๋ ๋์ํ๋ ๊ฑธ๊น? ์ฐ๋ฆฌ๋ ์ด์ ์์ผ๋ก ์ด ์๋ฆฌ์ ๋ํด ์์๋ณด๋ ค๊ณ ํ๋ค.
๋ฌผ๋ก ๋ชจ๋ ์ธํฐํ๋ฆฌํฐ๊ฐ ๋ฐฉ๊ธ ์ธ๊ธํ ๊ฒ์ฒ๋ผ ๋จ์ํ ๋ฒ์ญํ๋ ๋์๋ง ์ํํ์ง ์๋๋ค. CPython๋ ๋ด๋ถ์ ์ผ๋ก๋ ๋ฐ์ดํธ ์ฝ๋๋ก ๋ณํํ๋ ๋์์ด ์๋ฐ๋๋ค.(์ด ๊ณผ์ ์ ๊ฑฐ์ณ ๋์ค๋ ํ์ผ์ด ํ์ฅ์ .pyc ๋ผ๋ ํ์ผ์ด๋ค) ์ฆ, ์
๋ ฅ์ ๋จ์ํ ํ๊ฐํ๋ ๊ฒ ์๋๋ผ, ๋ฐ์ดํธ ์ฝ๋๋ผ๋ ๋ด๋ถ ํํ๋ฌผ๋ก ์ปดํ์ผํ ๋ค์ ํ๊ฐํ๋ค. ์ข ๋ ์ง๋ณด๋ ์ธํฐํ๋ฆฌํฐ ์ข
๋ฅ๋ก๋ JIT(Just in Time) ์ธํฐํ๋ฆฌํฐ๋ก, ์
๋ ฅ์ ๊ทธ ์๋ฆฌ์์ ๋ค์ดํฐ๋ธ ๊ธฐ๊ณ์ด๋ก ์ปดํ์ผํ ํ ์คํ์ ํ๊ธฐ๋ ํ๋ค.
์ด๋ ๊ฒ ์์ฉํ๋๊ณ ์ง๋ณด๋ ์ธํฐํ๋ฆฌํฐ์ ์๋ฆฌ์ ๋ํด์๋ ๋ฐฐ์ฐ์ง๋ ์๋๋ค. ์ฐ๋ฆฌ๋ ์ด๋ฌํ ์ธํฐํ๋ฆฌํฐ๋ค ๋ณด๋ค๋ ๋น๋ก ์ฑ๋ฅ์ด ์ข์ง ์๊ฒ ์ง๋ง ๋ ๊ฐ๋จํ ์๋ฆฌ๋ก ๋ง๋ค์ด์ง๋ ์ธํฐํ๋ฆฌํฐ๋ฅผ ์ง์ ๋ง๋ค์ด๋ณด๋ฉด์ ์๋ฆฌ๋ฅผ ์ตํ๋ณผ ๊ฒ์ด๋ค. ์ด ์๋ฆฌ๋ผ ํจ์ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์์ค์ฝ๋๋ฅผ ํ์ฑํ๊ณ , ์ด๋ฅผ ์ถ์๊ตฌ๋ฌธํธ๋ฆฌ(AST, Abstract Syntax Tree)๋ก ๋ง๋ค๊ณ , ์ด๊ฒ์ ํ๊ฐํ๋ ์ด๋ฅธ๋ฐ 'ํธ๋ฆฌ ํ์(tree-walking) ์ธํฐํ๋ฆฌํฐ'๋ฅผ ์๋ฏธํ๋ค.
ํธ๋ฆฌ ํ์ ์ธํฐํ๋ฆฌํฐ๋ฅผ ๊ตฌํํ๊ธฐ ์ํด์๋ ๋ ์(Lexer), ํ์(Parser), ํธ๋ฆฌ ํํ๋ฒ(Tree representation), ํ๊ฐ๊ธฐ(Evaluator)๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค. ์ด๋ฒ ํฌ์คํ
์์๋ ๊ทธ ์ฒซ๋ฒ์งธ ๊ตฌ์ฑ์์๋ก, ๋ ์๋ฅผ ๋ง๋ค์ด๋ณผ ๊ฒ์ด๋ค.
์ฐธ๊ณ ๋ก ํด๋น ์ฑ
์์ ์์ ์๋ ํด๋น ์ฑ
์ ์ดํดํ๊ธฐ ์ํด Go ์ธ์ด์ ๋ํด์ ์์ ๋ชฐ๋ผ๋ ์ดํดํ ์ ์๋ค๊ณ ํ๋ค. ํ์ง๋ง ๋ฒ์ญ๊ฐ๋ถ๋ ์ฑ
์์ ์ธ๊ธํ๋ฏ์ด ํ์์ ์๊ฐ์๋ Go ์ธ์ด์ ๋ํ ๊ธฐ์ด๋ ๊ฐ๋จํ ์์งํ ํ ์ฑ
์ ์ฝ๊ธฐ๋ฅผ ๊ถ์ฅํ๋ค. ํ์๋ Go ์ธ์ด์ ๋ํ ๊ธฐ์ด๋ฅผ ๊ฐ๋จํ๊ณ ๋น ๋ฅด๊ฒ ์ต๋ํ ํ ์ฑ
์ ์ฝ์ผ๋ ์์ํ๋ค. ๊ฐ์ธ์ ์ผ๋ก๋ Go ๊ณต์ ๋ฌธ์์ Tutorials ์ A Tour of Go ์ ๋๋ ๊ฐ์ด ๋ณํํ๋ฉด์ ์ฑ
์ ์ฝ๊ธฐ๋ฅผ ๊ถ์ฅํ๋ค.
1. ์์ค์ฝ๋๋ผ๋ ์ดํ๋ฅผ ๋ถ์ํ๋ ๊ณผ์
์์ค์ฝ๋๋ฅผ ๊ฐ์ง๊ณ ์ด๋ค ์์
์ ์ํํ๋ ค๋ฉด ์ด ์์ค์ฝ๋๋ฅผ ์ข๋ ์ ๊ทผํ๊ธฐ ์ฌ์ด ํํ๋ก ๋ณํํ ํ์๊ฐ ์๋ค. ์ด ๋ณํ ์์
์ ์ํํ๊ธฐ ์ํด์๋ ์๋์ ๋จ๊ณ๋ค์ด ํ์ํ๋ค. ์๋ ๊ทธ๋ฆผ์ ๋ณด์.
๋จผ์ ์์ค์ฝ๋ ํํ๋ ์ฌ๋ฌ ๊ฐ์ ํ ํฐ๋ค๋ก ๋๋์ด์ ธ์ผ ํ๋ค. ์ต๊ทผ ๋ LLM ์๋์ ์ ์ด๋ค๋ฉด์ 'ํ ํฐ'์ด๋ผ๋ ๋จ์ด๋ ๋๋ถ๋ถ ์ต์ํ ๊ฒ์ด๋ค. ์์ฐ์ด ์ฒ๋ฆฌํ ๋์ ํ ํฐ ์๋ฏธ์ ์ ์ฌํ๋ค. ์ด์จ๊ฑด ์์ค์ฝ๋๋ ๋ฌธ์์ด ํํ์ด๋ค. ์ด ๋ฌธ์์ด๋ค์ ์ด๋ค ๋จ์๋ก ๋๊ฐ๋๊ฐ ์๋ผ๋ด์ผ ํ๋๋ฐ, ์ด ๊ณผ์ ์ ์ธํฐํ๋ฆฌํฐ์์๋ ์ดํ ๋ถ์ ์ฆ, ๋ ์ฑ(Lexing)์ด๋ผ๊ณ ์ฉ์ด๋ฅผ ์ ์ํ๋ค. ์ด ๋, ๋ ์ฑ์ ์ํํ๋ ์ฃผ์ฒด๋ ๋ ์๊ฐ ์ํํ๋ค.
์ด์ ์๊ฒ ์ชผ๊ฐ์ง ํ ํฐ๋ค์ ํธ๋ฆฌ ํํ์ ์๋ฃ๊ตฌ์กฐ์ธ ์ถ์๊ตฌ๋ฌธํธ๋ฆฌ๋ก ๋ณํ์ด ๋๋๋ฐ, ์ด๋ฅผ ํ์ฑ(Parsing)์ด๋ผ๊ณ ํ๋ฉฐ ํ์ฑ์ ์ํํ๋ ์ฃผ์ฒด๋ ํ์๊ฐ ๋๋ค.
์ฐ๋ฆฌ๋ ์ด๋ฒ ๋ชฉ์ฐจ์์๋ ์ ๊ณผ์ ์ค Lexing ์ ์ํํ๋ Lexer๋ฅผ ๋ง๋ค์ด๋ณผ ๊ฒ์ด๋ค.
2. ๊ฐ์ฅ ๋จผ์ ํ ์ผ: ํ ํฐ ์ ์ํ๊ธฐ
๋ ์ฑ์ ๋ณธ๊ฒฉ์ ์ผ๋ก ํ๊ธฐ์ ์์ ๊ฐ์ฅ ๋จผ์ ํ ์ผ์ ํ ํฐ์ ๋ฏธ๋ฆฌ ์ ์ํ๋ ๊ฒ์ด๋ค. ์ง์ ๋ชฉ์ฐจ์์ ๋ดค๋ ๊ทธ๋ฆผ์ ๋ณด๋ฉด ์ด์ํ๋ค ์ถ์ ์ ์๋ค. ๋ ์ฑ์ ๊ฑฐ์ณ์ ๋์จ ๊ฒ ํ ํฐ์ด๋ผ๋ฉด์ ์ ํ ํฐ์ ๋จผ์ ์ ์ํ๋ ๊ฒ์ด์ง? ํ ์ ์๋ค. ํ ํฐ์ ๋จผ์ ์ ์ํด์ผ ํ๋ ์ด์ ๋ ์ปดํจํฐ์๊ฒ "์ด๋ฐ ๋ฌธ์์ด์ ์ด๋ฐ ๋ป์ด์ผ" ๋ผ๊ณ ๋งคํํ ์ ์๋๋ก ๋ง์น ๊ฐ์ด๋๋ฅผ ์ฃผ๋ ์
์ด๋ค. ๋จธ์ ๋ฌ๋ ๋ถ์ผ์์ ์ฝ๊ฒ ์ด์ผ๊ธฐํด๋ณด์๋ฉด, ์์ฐ์ด ์ฒ๋ฆฌ๋ฅผ ํ๊ฑฐ๋ ์นดํ
๊ณ ๋ฆฌ์ปฌํ ์ฑ๊ฒฉ์ด ์๋ ํผ์ณ๋ฅผ ์ธ์ฝ๋ฉ ํ ๋, ์ด๋ค ๋จ์ด๋ ์ด๋ค ์ซ์์ ๋งคํ๋๋๋ก ํ๋ค. ์ด๋ ๊ฒ ๋จ์ด์ ์ซ์๊ฐ ๋งคํ๋ ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ผ๋ช
์ฌ์ (Vocabulary)์ด๋ผ๊ณ ๋ ํ๋ค.
๋ค์ ๋งํด, ์ปดํจํฐ์๊ฒ "์์ค์ฝ๋ ์ค '=' ๋ผ๋ ๋ฌธ์๊ฐ ์์ผ๋ฉด ์ด๊ฑด ์ด๋ค ๊ฐ์ ํ ๋นํ๋ค๋ ์๋ฏธ์ผ. ๋๋ '+' ๋ผ๋ ๋ฌธ์๊ฐ ๋์ค๋ฉด ์ด๊ฒ์ ๋ง์
์ฐ์ฐ์ ์๋ฏธํ๋ ๊ฑฐ์ผ." ์ฒ๋ผ ์ด์ผ๊ธฐ ํด์ฃผ๊ธฐ ์ํด, ์์ค์ฝ๋์ ์ด๋ค ๋ฌธ์์ด์ด ๋ฑ์ฅํ๋ฉด ์ด๊ฒ์ ์ด๋ค ๊ฒ์ ์๋ฏธํ๋์ง ๋ํ๋ด๋ ํ ํฐ์ ์ฌ์ ์ ๋ฏธ๋ฆฌ ์ ์ํด๋๋ ๊ฒ์ด๋ค. ์ธํฐํ๋ฆฌํฐ๋ฅผ ๊ณ์ ํ์ฅํด์ ๊ฐ๋ฐํ ๋, ์ด์ ์ ์ปดํจํฐ๊ฐ ์์๋ฃ์ง ๋ชปํ๋ ๋ฌธ์์ด์ ์ดํด์ํค๊ธฐ ์ํด์ ๋จ์ํ ์ด ํ ํฐ์ด๋ผ๋ ๊ฒ์ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋๋ ๊ฒ์ด๋ค.
์ด์ ํ ํฐ์ ์ ์ํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ง์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด ์์ค์ฝ๋๋ฅผ ๋จผ์ ์ดํด๋ณด์. ์ด ์์ค์ฝ๋๋ (์ฑ
์์๋ ์๊ฐํ๋) Monkey ๋ผ๋ ์ธ์์ ์์ฉํ๋์ด ์์ง ์์ง๋ง ์ฐ๋ฆฌ๋ง์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด ์์ค์ฝ๋์ด๋ค. ์ฐ๋ฆฌ๋ ์ด Monkey ๋ผ๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ก ์์ฑ๋์ด ์๋ ์์ค์ฝ๋๋ฅผ ๋ ์ฑํ๋ ๋ ์๋ฅผ ๋ง๋ค ๊ฒ์ด๋ค.
let five = 5;
let ten = 10;
let add = fn(x, y) {
x + y;
};
let result = add(5, 10);
์ ์์ค์ฝ๋๋ฅผ ๋ณด์์ ๋, ์ฌ์ ์ ์ ์ํด์ผ ํ ํ ํฐ์ ๋ฌด์์ด ์์๊น?
- 5, 10๊ณผ ๊ฐ์ ๊ฐ์ ๋ณด๊ณ ์ซ์ ํ ํฐ ํ์ ์ด ํ์. ์ฐธ๊ณ ๋ก ์ ์ํ์ ๋ํ ํ ํฐ์ด๋ฉด ๋จ. ๋ ์์ ํ์๋ ์ซ์์ธ์ง๋ง ์๋ณํ๋ฉด ๋จ. ์ซ์๊ฐ 5์ธ์ง 10์ธ์ง๋ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋จ
- x, y, five, ten, add, result ๋ฅผ ๋ณด๊ณ ๋ณ์ ์ด๋ฆ์ด๋ผ๊ณ ์ถ์ธกํ ์ ์๋ค. ์ฆ, ์๋ณ์ ์ค '์ฌ์ฉ์ ์ ์ ์๋ณ์'๋ฅผ ์ํ ํ ํฐ์ด ํ์
- fn, let ๊ณผ ๊ฐ์ด ์๋ณ์์ด๊ธด ํ์ง๋ง, ๋ณ์๊ฐ ์๋ ์ผ๋ช ์์ฝ์ด์ ๋ํ ํ ํฐ์ด ํ์
- { } , ( ) ; = ๊ณผ ๊ฐ์ด ํน์๋ฌธ์์ ๋ํ ํ ํฐ์ด ํ์
ํ ํฐ์ ์ด์ ์ ์ํด๋ณด์. ๊ทธ๋ฆฌ๊ณ ํ ํฐ์ ๋ด๊ณ ๋ ์์์ ์ฌ์ฉํ๊ธฐ ์ํด์ ๊ตฌ์กฐ์ฒด๋ฅผ ํ์ฉํด ์ ์ํ์.
// token/token.go
package token
type TokenType string
type Token struct {
Type TokenType
Literal string // ์ฌ๊ธฐ์ ํ ํฐํํ ๋ฌธ์์ด์ ๋ด์๋์ ๊ณณ
}
const (
ILLEGAL = "ILLEGAL"
EOF = "EOF"
// Identifiers + literals
IDENT = "IDENT" // add, result, x, y, ...
INT = "INT" // 1343456
// Operators
ASSIGN = "="
PLUS = "+"
// Delimiters
COMMA = ","
SEMICOLON = ";"
LPAREN = "("
RPAREN = ")"
LBRACE = "{"
RBRACE = "}"
// ์์ฝ์ด
FUNCTION = "FUNCTION"
LET = "LET"
)
์์์ ์ ์ํ ํ ํฐ ์ค ํน์ดํ ๊ฒ์ด 2๊ฐ ์๋ค. ๋ฐ๋ก ILLEGAL ๊ณผ EOF์ด๋ค. ILLEGAL์ ๋ ์๊ฐ ์ ์ ์๋ ํ ํฐ์ด๋ ๋ฌธ์๋ฅผ ๋ง์ฃผํ์ ๋ ๋ช
์ํ ํ์
์ด๋ค. ๊ทธ๋ฆฌ๊ณ EOF๋ End Of File์ ์ค์๋ง๋ก, ๋ง ๊ทธ๋๋ก ์
๋ ฅ๋ ์์ค์ฝ๋์ ๋์ด๋ค. ์ด EOF๋ ์ถํ์ ๋ง๋ค ํ์์๊ฒ ์ด์ ๊ทธ๋ง ํ์ฑ์ ๋ฉ์ถ๋ผ๊ณ ์๋ ค์ฃผ๋ ์๊ทธ๋์ด๊ธฐ๋ ํ๋ค.
2. ์์ค์ฝ๋๋ฅผ ํ ํฐํ์ํค๊ธฐ: Lexing
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก Lexing์ ์ํํ๋ ๋ ์๋ฅผ ๋ง๋ค์ด๋ณผ ์ฐจ๋ก๋ค. ๊ฐ์ฅ ์ฒ์์๋ ๊ฐ๋จํ ์์ค์ฝ๋๋ง ๋ ์ฑํ ์ ์๋ ๊ธฐ๋ฅ์ ๋ง๋ค๊ณ , ์ด๋ฅผ ํ๋์ฉ ์ ์ ๊ณ ๋ํ์ํค๋ ๋จ๊ณ๋ก ์งํํ๋ค. ๊ทธ์ ์์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ ์๊ฐ ์ ๋์ํ๋์ง ํ
์คํธํ๋ ํ
์คํธ ์ฝ๋๋ถํฐ ์๊ฐํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ ๋ค, ์ด ํ
์คํธ ์ฝ๋์์ ์ฌ์ฉ๋๊ณ ์๋ ํจ์ ํ๋ํ๋์ ๊ธฐ๋ฅ์ ์ฝ๋ ์กฐ๊ฐ์ผ๋ก ์ดํด๋ณด๋๋ก ํ์.
(์ฐธ๊ณ ๋ก Go๋ฅผ ํ์ฉํ ํ
์คํธ ์ฝ๋ ์์ฑ ๋ฐฉ๋ฒ์ ์ฌ๊ธฐ์ ๋ค๋ฃจ์ง ์๋๋ค. ๊ณต์ ๋ฌธ์์์ ์ ๊ฐ์ด๋ ๋์ด์์ผ๋, ํน์ ๋ชจ๋ฅธ๋ค๋ฉด ๊ผญ ์ฝ์ด๋ณด์)
// lexer/lexer_test.go
package lexer
import (
"monkey/token"
"testing"
)
func TestNextToken(t *testing.T) {
input := `=+(){},;`
tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.ASSIGN, "="},
{token.PLUS, "+"},
{token.LPAREN, "("},
{token.RPAREN, ")"},
{token.LBRACE, "{"},
{token.RBRACE, "}"},
{token.COMMA, ","},
{token.SEMICOLON, ";"},
{token.EOF, ""},
}
src := New(input)
for i, tt := range tests {
tok := src.NextToken()
if tok.Type != tt.expectedType {
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
i, tt.expectedType, tok.Type)
}
if tok.Literal != tt.expectedLiteral {
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
i, tt.expectedLiteral, tok.Literal)
}
}
}
๊ฐ์ฅ ๋จผ์ input ๋ณ์๋ช
์ ๋ณด๋ฉด ๋ฐฑํฑ(`)์ ํ์ฉํด์ ์ด๋ค ๋ฌธ์์ด์ ๋ช
์ํ๋ค. ์ฐธ๊ณ ๋ก Go์ธ์ด์์ ๋ฐฑ์ฌ๋์ฌ๋ ๊ฐํ์ ๋ฌธ์๋ก ๊ฐ์ฃผํ๊ธฐ ์ํด์๋ ๋ฐฑํฑ์ ์ฌ์ฉํ๋ฉด ๋๋ค. ์ด์จ๊ฑด ๋ฐฑํฑ์ ํ์ฉํด ๋ฌธ์์ด์ ๋ช
์ํ ๊ฒ์ด ๋ฐ๋ก ๋ ์๊ฐ ๋ ์ฑํ ๋์์ธ ์์ค์ฝ๋์ด๋ค. ์ฆ, ์ฐ๋ฆฌ๋ ์๋์ ์์ค์ฝ๋๋ฅผ ๋ ์ฑํ ๊ฒ์ด๋ค.
=+(){},;
๊ทธ๋ฆฌ๊ณ tests ๋ผ๋ ๋ณ์์ ์ฌ๋ผ์ด์ค๋ก ์ด๋ฃจ์ด์ง ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ํ๋ค. ํด๋น ๊ตฌ์กฐ์ฒด์๋ 2๊ฐ์ ๋ฉค๋ฒ๋ฅผ ๊ฐ๋๋ฐ, ํ๋๋ ์ง์ ๋ชฉ์ฐจ์์ ์ ์ํ token.TokenType ์ด๋ผ๋ ํ์
์ด๋ค. ๊ทธ๋ฆฌ๊ณ ๋๋จธ์ง ํ๋๋ ๋ฌธ์์ด ํ์
์ด๋ค. ๊ทธ๋ฆฌ๊ณ ๊ตฌ์กฐ์ฒด๋ค์ ์ ์ํ๋ค. ์ด ๊ตฌ์กฐ์ฒด๋ค์ ์ฌ์ค ๋ฐฉ๊ธ '๋ ์๊ฐ ๋ ์ฑํ ๋์์ธ ์์ค์ฝ๋'๋ฅผ ํ ํฐ์ผ๋ก ์ฌ์ ์ ๋ถํ ํด ๋์ ๊ฒ์ด๋ค. ์ด๋ป๊ฒ ๋ณด๋ฉด ์ ๋ต์ ๋ฏธ๋ฆฌ ์ ์ํ ์
์ด๋ค. ์ฐ๋ฆฌ๋ ์ด '์ ๋ต'์ ๊ฐ์ง๊ณ for loop ๊ตฌ๋ฌธ์ ์ด์ฉํด ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ ์๊ฐ ์ ๋์ํ๋์ง ๊ฒ์ฌํ๋ ๊ฒ์ด๋ค.
src ๋ณ์๋ฅผ ๋ณด๋ฉด New ๋ผ๋ ํจ์์ ์์ค์ฝ๋ ๋ฌธ์์ด์ ๋ฃ์ด์ฃผ์๋ค. ์ด ํจ์๋ ๋ ์๊ฐ ๋ ์ฑํ ์ ์๋๋ก ๋ง๋๋ ๊ตฌ์กฐ์ฒด๋ก ๋ง๋๋ ์ญํ ์ ํ๋ค. ์ถํ์ ์๊ฐํ ๊ฒ์ด๋ค. ์ด์ for loop ๊ตฌ๋ฌธ์ ๋ณด์. tests ๋ผ๋ ๋ณ์์ ์ ์ํ ์ฆ, ๋ฏธ๋ฆฌ ์ ์ํ ํ ํฐ ์ ๋ต์ ํ๋์ฉ loop๋ฅผ ๋๋ฉด์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ ์๊ฐ ์์ค์ฝ๋ ๋ฌธ์์ด์ ์ ๋ ์ฑํ๋์ง ํ
์คํธํ๋ ๊ฒ์ด๋ค.
์ด์ ๊ทธ๋ฌ๋ฉด ํ
์คํธ ์ฝ๋์ ์์ฑ๋์ด ์๋ ์์ง ์๊ฐํ์ง ์์ ์ฌ๋ฌ๊ฐ์ง ํจ์๋ค์ ํ๋์ฉ ์ดํด๋ณด์.
2-1. ๋ ์ฑํ ์ ์๋ ๋์์ผ๋ก ๋ง๋ค๊ธฐ
๊ฐ์ฅ ๋จผ์ ์
๋ ฅ์ผ๋ก ๋ฐ์ ์์ค์ฝ๋ ๋ฌธ์์ด(src ๋ณ์)์ ์ฐ๋ฆฌ๋ง์ ๋ ์๊ฐ ๋ ์ฑํ ์ ์๋ ๋์์ผ๋ก ๋ง๋ค์ด์ผ ํ๋ค. ์์ค์ฝ๋ ๋ถํฐ ์ดํด๋ณด์.
// lexer/lexer.go
package lexer
type Lexer struct {
input string
position int
readPosition int
ch byte
}
func New(input string) *Lexer {
l := &Lexer{input: input}
return l
}
๋จผ์ Lexer ๋ผ๋ ์ด๋ฆ์ ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ํ๋ค. ํด๋น ๊ตฌ์กฐ์ฒด๊ฐ ๊ฐ๋ ๋ฉค๋ฒ๋ ์ด 4๊ฐ์ง์ธ๋ฐ ์ค๋ช
์ ๋ค์๊ณผ ๊ฐ๋ค.
- input : ์ ๋ ฅ๋ ์์ค์ฝ๋ ๋ฌธ์์ด
- position : ์ ๋ ฅ๋ ์์ค์ฝ๋ ๋ฌธ์์ด์์ ํ์ฌ ์์น
- readPosition : ์ ๋ ฅ๋ ์์ค์ฝ๋ ๋ฌธ์์ด์์ ํ์ฌ ์์น์ ๋ค์ ์์น
- ch : ํ์ฌ ๋ ์ฑํ๊ณ ์๋ ๋์ ๋ฌธ์(๋ฌธ์์ด ์๋! ์ ์ฃผ์. ๊ทธ๋์ ์์์ ํ์ ์ byte๋ก ์ ์ํจ. Go์ธ์ด์์ byte๋ ๋ถํธ๊ฐ ์๋ 8๋นํธ ์ฆ, uint8 ํ์ ์ด๋ฉฐ ๋ํ๋ผ ์ ์๋ ์ซ์ ๊ฒฝ์ฐ์ ์๊ฐ 0~255์ด๋ค. ์ฐ๋ฆฌ๊ฐ ๋ง๋ค ๋ ์๋ ASCII ๋ฌธ์ ๋ฒ์ ๋ด์ ํ๊ธฐ๋๋ ์์ค์ฝ๋๋ฅผ ๋ ์ฑํ๋ ๊ฒ์ด ๋ชฉํ์ด๊ธฐ ๋๋ฌธ์ rune(32๋นํธ) ํ์ ์ ์ฌ์ฉํ์ง ์์)
๋ค์์ผ๋ก New ๋ผ๋ ํจ์๋ฅผ ๋ณด์. ํด๋น ํจ์๋ ์
๋ ฅ๋ ์์ค์ฝ๋ ๋ฌธ์์ด์ ๊ทธ๋๋ก ๋ฐ๋ Lexer ๋ผ๋ ๊ตฌ์กฐ์ฒด๋ก ๋ง๋๋ ์ญํ ์ ํ๋ค. ๋จ, ๋ฆฌํดํ๋ ๊ฐ์ ๊ตฌ์กฐ์ฒด์ ํฌ์ธํฐ ๋ณ์์ด๋ค.
๊ทธ๋ฐ๋ฐ ์ด์ ์ฐ๋ฆฌ๋ ์ด New ํจ์์ ํ ๊ฐ์ง ํจ์๋ฅผ ๋ ์ถ๊ฐํ ๊ฒ์ด๋ค. ์์ค์ฝ๋๋ถํฐ ๋ณด์.
// lexer/lexer.go
func New(input string) *Lexer {
l := &Lexer{input: input}
l.readChar()
return l
}
func (l *Lexer) readChar() {
if l.readPosition >= len(l.input) {
l.ch = 0
} else {
l.ch = l.input[l.readPosition]
}
l.position = l.readPosition
l.readPosition += 1
}
Lexer ๊ตฌ์กฐ์ฒด์ ํฌ์ธํฐ ๋ณ์๋ฅผ Receiver๋ก ํ๋ readChar ํจ์๋ฅผ ํธ์ถํ๋ค. ์ด readChar ํจ์๋ ์ด๋ค ๊ธฐ๋ฅ์ ํ๋๊ฑด์ง ๋ณด์. ์ด๋ฆ ๊ทธ๋๋ก ๋ฌธ์(Char)๋ฅผ ์ฝ๋ ๊ธฐ๋ฅ์ด๋ค. if ~ else ๋ถ๊ธฐ๋ฌธ์ ๋ณด์. Lexer ๊ตฌ์กฐ์ฒด์ readPosition ๊ฐ์ด ์์ค์ฝ๋ ๋ฌธ์์ด๋ณด๋ค ํฌ๊ฑฐ๋ ๊ฐ๋ค๋ฉด ์ฆ, readPosition ๊ฐ์ด ์์ค์ฝ๋ ๋ฌธ์์ด ๊ธธ์ด์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ฌ๋ค๋ฉด Lexer ๊ตฌ์กฐ์ฒด์ ๋ ์ฑ ๋์ ๋ฌธ์(ch ๋ผ๋ ๊ตฌ์กฐ์ฒด ๋ฉค๋ฒ)์๋ 0 ์ฆ, NULL์ ์ง์ด๋ฃ๋๋ค.
๋ง์ฝ ๊ทธ๋ ์ง ์์ผ๋ฉด ์์ค์ฝ๋ ๋ฌธ์์ด์์ ๋ ์ฑํ ๋์์ ๋ฌธ์๋ฅผ ch ๊ตฌ์กฐ์ฒด ๋ฉค๋ฒ์ ํ ๋นํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ ์ฑํ ๋์์ ๋ฌธ์ ์์น๋ฅผ ํ์ฌ ์ฒ๋ฆฌํ ๋ ์ฑ ๋์ ๋ฌธ์์ ๋ค์ ์์น์ ์๋ ๋ฌธ์๋ก ์ฎ๊ธฐ๋๋ก ํ๋ค.
2-2. ์์ค์ฝ๋ ๋ฌธ์์ด์์ ํ ํฐํ ์ํค๊ธฐ
์ง์ ๋ชฉ์ฐจ๊น์ง๋ ์์ค์ฝ๋ ๋ฌธ์์ด์์ ๋ ์ฑํ ๋์์ ์์น๋ฅผ ์ฐพ๋ ์์
์ ํ๋ค. ๋ ์ฑํ ๋์์ ์์น๋ฅผ ์ฐพ์์ผ๋ ์ด์ ๋ ๋ ์ฑ ๋์์ ๋ฌธ์๊ฐ ์ด๋ค ํ ํฐ์ผ๋ก ๋งคํ์ํฌ์ง ๊ฒฐ์ ํด์ผ ํ๋ค. ์ด๋ฒ์๋ ์์ค์ฝ๋๋ฅผ ๋จผ์ ์ดํด๋ณด์.
// lexer/lexer.go
func (l *Lexer) NextToken() token.Token {
var tok token.Token
switch l.ch {
case '=':
tok = newToken(token.ASSIGN, l.ch)
case ';':
tok = newToken(token.SEMICOLON, l.ch)
case '(':
tok = newToken(token.LPAREN, l.ch)
case ')':
tok = newToken(token.RPAREN, l.ch)
case ',':
tok = newToken(token.COMMA, l.ch)
case '+':
tok = newToken(token.PLUS, l.ch)
case '{':
tok = newToken(token.LBRACE, l.ch)
case '}':
tok = newToken(token.RBRACE, l.ch)
case 0:
tok.Type = token.EOF
tok.Literal = ""
}
l.readChar()
return tok
}
func newToken(tokenType token.TokenType, ch byte) token.Token {
return token.Token{Type: tokenType, Literal: string(ch)}
}
๋จผ์ newToken ์ด๋ผ๋ ํจ์๋ฅผ ๋ณด์. ํด๋น ํจ์๋ ํ ํฐ ํ์
๊ณผ ๋ ์ฑ ๋์ ๋ฌธ์๋ฅผ ์ธ์๋ก ๋ฐ์ ๋ค, token.Token ์ด๋ผ๋ ๊ตฌ์กฐ์ฒด๋ก ๋ง๋ ํ ๋ฆฌํดํ๋ค.
์ด์ NextToken ํจ์๋ฅผ ๋ณด์. ํด๋น ํจ์์์๋ ์ค์ ํ์ฌ ๋ ์ฑ ๋์์ ๋ฌธ์(ch)๊ฐ ์ด๋ค ๋ฌธ์๋ ์ผ์นํ๋์ง ๋ณด๊ณ , ๊ทธ ๋ฌธ์๋ ๋งคํ๋๋ ํ ํฐ์ผ๋ก ๋ณํ ํ ํ ํฐ์ ๋ฐํํ๋ค. ์ฌ๊ธฐ์ ์ฃผ๋ชฉํ ์ ์ switch-case ๋ฌธ์ด ๋๋ ๋ค, ์์์ ์์๋ณธ readChar ํจ์๋ฅผ ํ ๋ฒ ํธ์ถํจ์ผ๋ก์จ ๋ ์ฑ ๋์์ ๋ฌธ์๋ฅผ ๋ค์ ์์น๋ก ์ฎ๊ธฐ๊ฒ ๋๋ค.
์ด์ ์ ํจ์๊น์ง ๋ชจ๋ ์์ฑํ์ผ๋ฉด ์์์ ์ดํด๋ณด์๋ lexer_test.go ๋ผ๋ ํ
์คํธ ์ฝ๋ ํ์ผ์ ์คํ์์ผ๋ณด์. ํ
์คํธ ์ฝ๋ ์คํจ๊ฐ ๋์ง ์๋๋ค๋ฉด ์ ์ ํต๊ณผํ ๊ฒ์ด๋ค!
3. ์กฐ๊ธ ๋ ๊ทธ๋ด๋ฏํ ์์ค์ฝ๋๋ฅผ ๋ ์ฑํด๋ณด์
์ง์ ๋ชฉ์ฐจ์์๋ ๋จ์ํ ์์ค์ฝ๋๋ฅผ ๋ ์ฑํด๋ณด์๋ค. ์ด๋ฒ์๋ ์ ๋ง ์ฐ๋ฆฌ๋ง์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ธ Monkey ์ธ์ด์ ๋ฌธ๋ฒ์ ๋ ์ฑํ ์ ์๋๋ก ๋ ์๋ฅผ ๊ฐ์ ์์ผ๋ณด์. ์ด๋ฒ์๋ ํ
์คํธ ์ฝ๋๋ฅผ ๋จผ์ ์ดํด๋ณด์. ์ง์ ์ ๋ณด์๋ ํ
์คํธ ์ฝ๋์์ inputs ์ tests ๋ณ์๋ง ๋ฌ๋ผ์ก๋ค.
// lexer/lexer_test.go
func TestNextToken(t *testing.T) {
input := `let five = 5;
let ten = 10;
let add = fn(x, y) {
x + y;
};
let result = add(five, ten);`
tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.LET, "let"},
{token.IDENT, "five"},
{token.ASSIGN, "="},
{token.INT, "5"},
{token.SEMICOLON, ";"},
{token.LET, "let"},
{token.IDENT, "ten"},
{token.ASSIGN, "="},
{token.INT, "10"},
{token.SEMICOLON, ";"},
{token.LET, "let"},
{token.IDENT, "add"},
{token.ASSIGN, "="},
{token.FUNCTION, "fn"},
{token.LPAREN, "("},
{token.IDENT, "x"},
{token.COMMA, ","},
{token.IDENT, "y"},
{token.RPAREN, ")"},
{token.LBRACE, "{"},
{token.IDENT, "x"},
{token.PLUS, "+"},
{token.IDENT, "y"},
{token.SEMICOLON, ";"},
{token.RBRACE, "}"},
{token.SEMICOLON, ";"},
{token.LET, "let"},
{token.IDENT, "result"},
{token.ASSIGN, "="},
{token.IDENT, "add"},
{token.LPAREN, "("},
{token.IDENT, "five"},
{token.COMMA, ","},
{token.IDENT, "ten"},
{token.RPAREN, ")"},
{token.SEMICOLON, ";"},
{token.EOF, ""},
}
src := New(input)
for i, tt := range tests {
tok := src.NextToken()
if tok.Type != tt.expectedType {
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
i, tt.expectedType, tok.Type)
}
if tok.Literal != tt.expectedLiteral {
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
i, tt.expectedLiteral, tok.Literal)
}
}
}
inputs์ ์๋ ์์ค์ฝ๋ ๋ฌธ์์ด์ ๋ณด๋, ์ ๋ง ๊ฝค๋ ์ค์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ต๋ค! ์ด inputs ๋ฌธ์์ด์ ๋ง๊ฒ tests ๋ณ์์๋ ๋ฏธ๋ฆฌ ์ ํด์ง ์ ๋ต ํ ํฐ์ ์ถ๊ฐํด๋๋๋ก ํ์.
์ด๋ฒ์๋ ์ง์ ์ ๋ง๋ ๋ ์ ๊ธฐ๋ฅ์ผ๋ก ๋ ์ฑํ๊ธฐ์๋ ํ๊ณ์ ์ด ์๋ค. ๋ฐ๋ก ์๋ณ์, ์์ฝ์ด, ์ซ์์ด๋ค. ์๋ณ์๋ ํฌ๊ฒ ์ฌ์ฉ์ ์ ์ ์๋ณ์(์ผ๋ช
๋ณ์ ๊ฐ์..)์ ์์ฝ์ด๋ก ๊ตฌ์ฑ๋๋ค. ์ฌ์ฉ์ ์ ์ ์๋ณ์๋ผ ํจ์ ์ ์์ค์ฝ๋์์ five, ten, add ์ ๊ฐ์ ๊ฒ๋ค์ด๋ค. ์์ฝ์ด๋ ์ด๊ฑด ๋ณ์๋ผ๋ ๊ฒ์ ์๋ฏธํ๋ let ๋๋ ์ด๊ฑด ํจ์๋ผ๋ ๊ฒ์ ์๋ฏธํ๋ fn ๊ฐ์ ๊ฒ์ด ๋๋ค. ๋ง์ง๋ง์ผ๋ก ์ซ์๋ 5์ 10์ด๋ผ๋ ๊ฒ๋ค์ธ๋ฐ, ๋ ์ฑ์์๋ ์ฌ์ค ์ด๊ฒ์ด ์ซ์๋ผ๋ ๊ฒ๋ง ์ธ์งํ๋ฉด ๋๋ค. ์ด ๊ฐ์ด 5์ธ์ง 1์ธ์ง 2์ธ์ง๋ ๋ ์ฑ์ด ์ ๊ฒฝ์ธ ์ญํ ์ ์๋๊ธฐ ๋๋ฌธ์ด๋ค.
์ด์ ๊ฐ์ ๋ ๋ ์์ ๊ธฐ๋ฅ์ ํ๋์ฉ ์ถ๊ฐํด๋ณด์.
3-1. ์ฌ์ฉ์ ์ ์ ์๋ณ์, ์์ฝ์ด, ์ซ์๋ฅผ ๊ตฌ๋ถํ๊ธฐ
๋ ์๋ ์ฌ์ฉ์ ์ ์ ์๋ณ์, ์์ฝ์ด, ์ซ์๋ฅผ ์ ๊ตฌ๋ถํด์ ์
๋ ฅ๋ ์์ค์ฝ๋ ๋ฌธ์์ด์ ์ ๋ ์ฑํด์ผ ํ๋ค. ์ด๋ฅผ ์ํด NextToken ํจ์๋ฅผ ๊ฐ์ ํด์ผ ํ๋ค. ๋จผ์ ๊ฐ์ ๋ NextToken ํจ์๋ฅผ ์ดํด๋ณด์.
// lexer/lexer.go
func (l *Lexer) NextToken() token.Token {
var tok token.Token
switch l.ch {
case '=':
tok = newToken(token.ASSIGN, l.ch)
case ';':
tok = newToken(token.SEMICOLON, l.ch)
case '(':
tok = newToken(token.LPAREN, l.ch)
case ')':
tok = newToken(token.RPAREN, l.ch)
case ',':
tok = newToken(token.COMMA, l.ch)
case '+':
tok = newToken(token.PLUS, l.ch)
case '{':
tok = newToken(token.LBRACE, l.ch)
case '}':
tok = newToken(token.RBRACE, l.ch)
case 0:
tok.Type = token.EOF
tok.Literal = ""
default:
if isLetter(l.ch) {
tok.Literal = l.readIdentifier()
tok.Type = token.LookupIdent(tok.Literal)
return tok
} else {
tok = newToken(token.ILLEGAL, l.ch)
}
}
l.readChar()
return tok
์์ ๋ ๋ถ๋ถ์ default ๋ถ๋ถ์ด๋ค. default ๊ตฌ๋ฌธ์ ์ case ๊ตฌ๋ฌธ์ ์ด๋ค ๊ฒ๋ ๋ถ๊ธฐ๋์ง ์์์ ๊ฒฝ์ฐ ํ๊ฒ ๋๋ ๊ตฌ๋ฌธ์ด๋ค. ๋จผ์ isLetter ํจ์๊ฐ ๋ฑ์ฅํ๋ค. ์ด isLetter ํจ์๋ ์ด๋ค ๊ธฐ๋ฅ์ ํ๋์ง ์ดํด๋ณด์.
// lexer/lexer.go
func isLetter(ch byte) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}
๊ฐ๋จํ๋ค. ๋ฐ์ดํธ ํ์
์ ๋ฌธ์๋ฅผ ๋ฐ์์ ํด๋น ๋ฌธ์๊ฐ ์ํ๋ฒณ a ~ z ๋๋ A ~ Z ๋๋ ์ธ๋์ค์ฝ์ด(_)์ธ ๊ฒฝ์ฐ์๋ง true๋ฅผ ๋ฐํํ๋ค. ๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ ํจ์ ๊ธฐ๋ฅ๋ณด๋ค๋ ์ด isLetter ํจ์๊ฐ ์ ์กด์ฌํด์ผ ํ๋์ง๋ฅผ ์ดํดํ๋ ๊ฒ์ด ๋ ์ค์ํ๋ค. ์ฐ์ '๋ฌธ์' ๋ผ๋ ๊ฒ์๋ '๊ธ์'๊ฐ ํฌํจ๋์ด ์๋ค. ์ฝ๊ฒ ๋งํด ๋ฒค๋ค์ด์ด ๊ทธ๋จ์ผ๋ก ํ์ํ๋ฉด ๋ฌธ์์ ๊ธ์๊ฐ์ ๊ด๊ณ๋ ๋ค์๊ณผ ๊ฐ๋ค.
๊ทธ๋ฌ๋ฉด ์ด ๋ฌธ์ ์ค์ '๊ธ์' ์ธ ๊ฒ๊ณผ '๊ธ์๊ฐ ์๋ ๋ฌธ์'๋ฅผ ๋์ฒด ์ ๊ตฌ๋ถํด์ผ ํ ๊น? ๋ฐ๋ก ์๋ณ์๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ํจ์ด๋ค. ์๋ฅผ ๋ค์ด, ์ฐ๋ฆฌ๊ฐ ์ฒ์ ์์์์ ์
๋ ฅ ์์ค์ฝ๋๋ก ๋ณด์๋ =+(){},; ์ด๋ฐ ๊ฒ๋ค๋ '๋ฌธ์'์ด๋ค. ๊ทธ๋ฆฌ๊ณ add, five, ten ๊ณผ ๊ฐ์ ๊ฒ๋ค๋ '๋ฌธ์'๊ฐ ๋ชจ์ฌ ๋ฌธ์์ด์ ํ์ฑํ ๊ฒ์ด๋ค. ํ์ง๋ง, =+(){},; ์ด๋ฐ ๊ฒ๋ค์ '๊ธ์๊ฐ ์๋ ๋ฌธ์'์ด๋ค. ๋ฐ๋ฉด์ add, five, ten ๊ฐ์ ๊ฒ๋ค์ '๊ธ์'์ด๋ค. ์ฆ, ์ด ๋๊ฐ๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ํน์ ๋ฌธ์๊ฐ '๊ธ์'์ธ์ง, ํน์ '๊ธ์๊ฐ ์๋ ๋ฌธ์'์ธ์ง ํ๋ณํด์ผ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ ํ๋ณ์ ์ํํ๋ ๊ฒ์ด ๋ฐ๋ก isLetter ํจ์์ด๋ค.
์ด์ ๋ค์์ผ๋ก ๋์ด๊ฐ๋ณด์. ๋ค์์ผ๋ก ๋ณผ ํจ์๋ Lexer ๊ตฌ์กฐ์ฒด ํฌ์ธํฐ ๋ณ์๋ฅผ Receiver ๋กํ๋ ๋ฉ์๋์ธ readIdentifier ํจ์์ด๋ค. ํด๋น ํจ์์ ์์ค์ฝ๋ ๋ถํฐ ์ดํด๋ณด์.
// lexer/lexer.go
func (l *Lexer) readIdentifier() string {
position := l.position
for isLetter(l.ch) {
l.readChar()
}
return l.input[position:l.position]
}
ํจ์ ์ด๋ฆ์์ ์ ์ ์๋ฏ์ด ์ด ํจ์๋ ๋ ์ฑ ๋์ ๋ฌธ์์ด(์
๋ ฅ๋ ์์ค์ฝ๋ ๋ฌธ์์ด)์์ '์๋ณ์'๋ฅผ ์ถ์ถํด๋ด๋ ํจ์์ด๋ค. ๋ฐ๋ผ์ ๋ ์ฑ ๋์ ๋ฌธ์์ด์ ํ์ฌ ์์น๋ฅผ ์์์ ์ผ๋ก ํด์ ํด๋น ์์น์ ์๋ ๋ฌธ์๊ฐ '๊ธ์'๋ผ๋ฉด ๋ฌดํ ๋ฃจํ๋ฅผ ํ๊ฒํ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ฌดํ๋ฃจํ ์์์๋ ๋ ์ฑ ๋์ ๋ฌธ์์ด์ ํ์ฌ ์์น์์ ๋ค์ ์์น๋ก ์ด๋์ํค๋ readChar ํจ์๋ฅผ ํธ์ถํ๋๋ก ํ๋ค. ์ด๋ ๊ฒ ๋ฃจํ๋ฅผ ๋๋ค๊ฐ '๊ธ์๊ฐ ์๋ ๋ฌธ์'๊ฐ ๋์ค๊ฒ ๋๋ฉด ๋ฃจํ๋ฅผ ๋น ์ ธ๋์ '๊ธ์๊ฐ ์๋ ๋ฌธ์'์ ์ง์ ์์น๊น์ง์ ๋ฌธ์์ด์ ์ฌ๋ผ์ด์ฑํด์ ๋ฆฌํดํ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก token.LookupIdent ํจ์๋ฅผ ์ดํด๋ณด์. ํด๋น ํจ์๋ ์๋ณ์ ์ค์์๋ ์ด๊ฒ์ด ์ฌ์ฉ์ ์ ์ ์๋ณ์(IDENT)์ธ์ง, ์์ฝ์ด์ธ์ง ๋ถ๊ธฐํ๋ ๊ธฐ๋ฅ์ ํ๋ ํจ์๋ค. ํด๋น ํจ์์ ์์ค์ฝ๋๋ฅผ ๋ณด์.
// token/token.go
var keywords = map[string]TokenType{
"fn": FUNCTION,
"let": LET,
}
func LookupIdent(ident string) TokenType {
if tok, ok := keywords[ident]; ok {
return tok
}
return IDENT
}
์ฐ์ ๊ฐ์ฅ ๋จผ์ ํ ์ผ์ keywords ๋ผ๋ ๋งต ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ ์ํ๋ค. ํด๋น ๋งต์ ์์ค์ฝ๋ ๋ฌธ์์ด์์ ์ถ์ถํ ์๋ณ์๊ฐ ์์ฝ์ด์ ํด๋นํ๋์ง ๋งคํํด๋์ ํ
์ด๋ธ์ด๋ค. ์ฆ, ๋ฃฉ์
ํ
์ด๋ธ์ธ ์
์ด๋ค. ์ถํ์ ํ์ฅํ๊ฒ ์ง๋ง ์์ผ๋ก ์ฐ๋ฆฌ๊ฐ ๋ ์ฑ ๋์์ ์ด๋ค ์์ฝ์ด๋ฅผ ํฌํจ์ํค๊ณ ์ถ๋ค๊ณ ํ๋ฉด ํด๋น ๋งต ์๋ฃ๊ตฌ์กฐ์ ๊ณ์ ์ถ๊ฐํด๋๊ฐ๋ฉด ๋๋ค.
์ด์ LookupIdent ํจ์๋ฅผ ๋ณด์. ๋ก์ง์ ๊ฐ๋จํ๋ค. ๋ฌธ์์ด์ ์
๋ ฅ ๋ฐ์ ๋ค ํด๋น ๋ฌธ์์ด์ด keywords ๋งต ์๋ฃ๊ตฌ์กฐ์ ํค ๊ฐ์ ์กด์ฌํ๋์ง ์ฆ, ์ฐ๋ฆฌ๊ฐ ์ฌ์ ์ ๋ช
์ํด๋์ ์์ฝ์ด ๋ชฉ๋ก์ ํฌํจ๋๋์ง ์ฌ๋ถ๋ฅผ ํ๋จํ๋ ๊ฒ์ด๋ค. ํด๋น ๋ถ๊ธฐ๋ฅผ ํ์ง ์์ผ๋ฉด ๊ทธ๊ฒ์ ์ฌ์ฉ์ ์ ์ ์๋ณ์๋ก ๊ฐ์ฃผํด ๋ฆฌํดํ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก default ๊ตฌ๋ฌธ ๋ด์ else ๊ตฌ๋ฌธ์ด ์กด์ฌํ๋ ๊ฒ์ case ๊ตฌ๋ฌธ์์ '๊ธ์๊ฐ ์๋ ๋ฌธ์'์ ๋ํด์๋ ๋ถ๊ธฐ๋ฅผ ๋ค ํ์ผ๋ฏ๋ก, ๋ง์ฝ ํด๋น else ๊ตฌ๋ฌธ์ผ๋ก ํ๋ ๋ฌธ์๋ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ ์๊ฐ ์ฒ๋ฆฌํ ์ ์๋ ๋ฌธ์์์ ์ฆ, ์์ธ์ฒ๋ฆฌ๋ฅผ ํ๊ธฐ ์ํด ILLEGAL ์ด๋ผ๋ ํ ํฐ์ ์ฌ์ฉํ๋ค. ํด๋น ํ ํฐ์ด ๋ฐํ๋๋ฉด ์ฐ๋ฆฌ์ ๋ ์์ ๋ํ ํ
์คํธ๊ฐ ์คํจํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค.
3-2. ๊ณต๋ฐฑ๊ณผ ์ซ์์์ ๊ตฌ๋ถํ๊ธฐ
๋ค์์ผ๋ก ๊ฐ์ ํ ๋ถ๋ถ์ ๋ ์ฑ ๋์ ๋ฌธ์์ด์ ๊ณต๋ฐฑ์ด ์๊ฑฐ๋ ์ซ์๋ก ๋ ๋ฌธ์์ด(ex. 5, 10)์ด ์์ ๊ฒฝ์ฐ ์ซ์๋ผ๋ ํ ํฐ์ผ๋ก ์ธ์งํ๋๋ก ๊ฐ์ ํด์ผ ํ๋ค. ๋จผ์ ๊ณต๋ฐฑ์ด ์์ ๊ฒฝ์ฐ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์๋์ ์์ค์ฝ๋๋ฅผ ์ถ๊ฐํด๋ณด์.
// lexer/lexer.go
func (l *Lexer) skipWhitespace() {
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
l.readChar()
}
}
์ด ํจ์ ๋ก์ง๋ ๊ฐ๋จํ๋ค. ํ์ฌ ๋ ์ฑ ๋์ ๋ฌธ์๊ฐ ๊ณต๋ฐฑ์ด๊ฑฐ๋ ํญ ๋ฌธ์, ๊ณต๋ฐฑ ๋ฌธ์, ๊ทธ๋ฆฌ๊ณ ์ปค์๋ฅผ ์์ผ๋ก ์ฎ๊ธฐ๋ ๋ฌธ์(\r)๋ผ๋ฉด ๋ค์ ์์น๋ก ๋ ์ฑ ๋์์ ์ฎ๊ธฐ๋ ๊ฒ์ด๋ค.
๊ทธ๋ฆฌ๊ณ ์ซ์์ธ์ง๋ฅผ ๊ตฌ๋ถํ๋ ํจ์์ ์์ค์ฝ๋๋ ์ดํด๋ณด์.
// lexer/lexer.go
func (l *Lexer) readNumber() string {
position := l.position
for isDigit(l.ch) {
l.readChar()
}
return l.input[position:l.position]
}
func isDigit(ch byte) bool {
return '0' <= ch && ch <= '9'
}
isDigit ํจ์๋ ๊ฐ๋จํ๋ ๋ณ๋ค๋ฅธ ์ค๋ช
์ ํ์ง ์๊ฒ ๋ค. readNumber ํจ์๋ ์ฌ์ค ์์์ ์ดํด๋ณธ ์๋ณ์๋ฅผ ์ถ์ถํ๋ ํจ์์ธ readIdentifier ํจ์์ ๋ก์ง์ ๋์ผํ๋ค. ๋จ์ํ ์กฐ๊ฑด๋ฌธ ๋ถ๊ธฐ์ isDigit ํจ์๋ฅผ ์ฌ์ฉํ ๊ฒ์ผ๋ก ๋ฐ๋์์ ๋ฟ์ด๋ค.
์ด์ ์ 2๊ฐ์ง ํจ์๋ฅผ NextToken ํจ์์ ์ถ๊ฐํด์ฃผ๋ฉด ๋ค์๊ณผ ๊ฐ์์ง๋ค.
// lexer/lexer.go
func (l *Lexer) NextToken() token.Token {
var tok token.Token
switch l.ch {
case '=':
tok = newToken(token.ASSIGN, l.ch)
case ';':
tok = newToken(token.SEMICOLON, l.ch)
case '(':
tok = newToken(token.LPAREN, l.ch)
case ')':
tok = newToken(token.RPAREN, l.ch)
case ',':
tok = newToken(token.COMMA, l.ch)
case '+':
tok = newToken(token.PLUS, l.ch)
case '{':
tok = newToken(token.LBRACE, l.ch)
case '}':
tok = newToken(token.RBRACE, l.ch)
case 0:
tok.Type = token.EOF
tok.Literal = ""
default:
if isLetter(l.ch) {
tok.Literal = l.readIdentifier()
tok.Type = token.LookupIdent(tok.Literal)
return tok
} else if isDigit(l.ch) {
tok.Literal = l.readNumber()
tok.Type = token.INT
return tok
} else {
tok = newToken(token.ILLEGAL, l.ch)
}
}
l.readChar()
return tok
}
์ด์ ์์ฒ๋ผ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ํ
์คํธ ์ฝ๋๋ฅผ ์ํํ๋ฉด ๊ทธ๋ด๋ฏํ Monkey ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ก ์์ฑ๋ ์์ค์ฝ๋๊ฐ ์ ๋ ์ฑ๋ ๊ฒ์ด๋ค!
3-3. ๋๊ฐ ์ง๋ฆฌ ๋ฌธ์์ ๋ค๋ฅธ ์ข ๋ฅ์ ์์ฝ์ด๋ ๋ ์ฑํ๊ธฐ
์ด๋ฒ์ [๋ชฉ์ฐจ 3-2]์์ ํ ์คํธ ์ฝ๋์์ ์ํํ๋ ๊ฒ๋ณด๋ค ์ข ๋ ๋ค์ํ ์์ฝ์ด๊ฐ ์ถ๊ฐ๋ Monkey ์ธ์ด ์์ค์ฝ๋๋ฅผ ๋ ์ฑํด๋ณด์. ์ด๋ฒ์๋ ํ ์คํธ ์ฝ๋๋ถํฐ ์ดํด๋ณด๋๋ก ํ์.
// lexer/lexer_test.go
func TestNextToken(t *testing.T) {
input := `let five = 5;
let ten = 10;
let add = fn(x, y) {
x + y;
};
let result = add(five, ten);
!-/*5;
5 < 10 > 5;
if (5 < 10) {
return true;
} else {
return false;
}
10 == 10;
10 != 9;
`
tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.LET, "let"},
{token.IDENT, "five"},
{token.ASSIGN, "="},
{token.INT, "5"},
{token.SEMICOLON, ";"},
{token.LET, "let"},
{token.IDENT, "ten"},
{token.ASSIGN, "="},
{token.INT, "10"},
{token.SEMICOLON, ";"},
{token.LET, "let"},
{token.IDENT, "add"},
{token.ASSIGN, "="},
{token.FUNCTION, "fn"},
{token.LPAREN, "("},
{token.IDENT, "x"},
{token.COMMA, ","},
{token.IDENT, "y"},
{token.RPAREN, ")"},
{token.LBRACE, "{"},
{token.IDENT, "x"},
{token.PLUS, "+"},
{token.IDENT, "y"},
{token.SEMICOLON, ";"},
{token.RBRACE, "}"},
{token.SEMICOLON, ";"},
{token.LET, "let"},
{token.IDENT, "result"},
{token.ASSIGN, "="},
{token.IDENT, "add"},
{token.LPAREN, "("},
{token.IDENT, "five"},
{token.COMMA, ","},
{token.IDENT, "ten"},
{token.RPAREN, ")"},
{token.SEMICOLON, ";"},
{token.BANG, "!"},
{token.MINUS, "-"},
{token.SLASH, "/"},
{token.ASTERISK, "*"},
{token.INT, "5"},
{token.SEMICOLON, ";"},
{token.INT, "5"},
{token.LT, "<"},
{token.INT, "10"},
{token.GT, ">"},
{token.INT, "5"},
{token.SEMICOLON, ";"},
{token.IF, "if"},
{token.LPAREN, "("},
{token.INT, "5"},
{token.LT, "<"},
{token.INT, "10"},
{token.RPAREN, ")"},
{token.LBRACE, "{"},
{token.RETURN, "return"},
{token.TRUE, "true"},
{token.SEMICOLON, ";"},
{token.RBRACE, "}"},
{token.ELSE, "else"},
{token.LBRACE, "{"},
{token.RETURN, "return"},
{token.FALSE, "false"},
{token.SEMICOLON, ";"},
{token.RBRACE, "}"},
{token.INT, "10"},
{token.EQ, "=="},
{token.INT, "10"},
{token.SEMICOLON, ";"},
{token.INT, "10"},
{token.NOT_EQ, "!="},
{token.INT, "9"},
{token.SEMICOLON, ";"},
{token.EOF, ""},
}
src := New(input)
for i, tt := range tests {
tok := src.NextToken()
if tok.Type != tt.expectedType {
t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q",
i, tt.expectedType, tok.Type)
}
if tok.Literal != tt.expectedLiteral {
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
i, tt.expectedLiteral, tok.Literal)
}
}
}
์ ๋ ฅ์ผ๋ก ์ฃผ์ด์ง๋ ์์ค์ฝ๋ ๋ฌธ์์ด์ ๋ณด๋, ํฌ๊ฒ ์ถ๊ฐ๋ ๊ฒ๋ค์ !-/*<> ๊ทธ๋ฆฌ๊ณ !=, == ๋ผ๋ 2๊ฐ์ง๋ฆฌ ๋ฌธ์์ if, else, return ๊ฐ์ ๋ค๋ฅธ ์ข ๋ฅ์ ์์ฝ์ด์ด๋ค. ์ฌ๊ธฐ์ ์์ค์ฝ๋ ๋ฌธ์์ด ์ค ์๋์ ๊ฐ์ ๋ถ๋ถ์ด ๋ณด์ธ๋ค.
!-/*5;
5 < 10 > 5;
๋ฌผ๋ก ์๋ฌด๋ฆฌ ์ฐ๋ฆฌ๋ง์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ธ Monkey ๋ผ๊ณ ํด๋ ์ฌ์ค ์ด๊ฒ ๋ฌธ๋ฒ์ ์ผ๋ก๋ ๋ง์ง ์๋๋ค. ํ์ง๋ง ์ด ํ ์คํธ์์ ์ด๋ฐ ์์ค์ฝ๋ ๋ฌธ์์ด์ ์ ๋ ฅํ ๊ฒ์ ๋ ์์ ์ญํ ์ด "๊ทธ์ ์ ๋ ฅ๋ ์์ค์ฝ๋ ๋ฌธ์์ด์ ํ ํฐํ ํ๋ ๊ฒ์ผ ๋ฟ"์ ๊ฐ์กฐํ๊ธฐ ์ํด์๋ค. ๋ค์ ๋งํด, ๋ ์๋ ์์ค์ฝ๋ ๋ฌธ์์ด์ ํ ํฐํํ๋ ๊ฒ๋ง ์ ๊ฒฝ์ฐ๋ฉด ๋๋ค. ๊ทธ ์์ค์ฝ๋ ๋ฌธ์์ด์ด ๋ฌธ๋ฒ์ ์ผ๋ก ๋ง๋์ง, ๋ง์ง ์๋์ง๋ ๊ทธ ์ดํ์ ํ๋ณํ๋ฉด ๋๋ค.
๋จผ์ !-/*<> ์ ๊ฐ์ 1๊ฐ์ง๋ฆฌ ๋ฌธ์๋ก ๋ ๊ฒ์ ๋ ์ฑํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ๊ฐ์ฅ ๋จผ์ ํด๋น ๋ฌธ์๋ฅผ ํ ํฐ์ ์ถ๊ฐํด์ค๋ค. ๊ทธ๋ฆฌ๊ณ if, else, return ๊ณผ ๊ฐ์ด ์๋กญ๊ฒ ์ถ๊ฐ๋ ์์ฝ์ด์ ๋ํด์๋ ํ ํฐ ๋ฟ๋ง ์๋๋ผ ์ด๋ค ๊ธ์๊ฐ ์ด๋ค ์์ฝ์ด๋ก ๋งคํ๋๋์ง ๋ฃฉ์ ํ ์ด๋ธ์ธ keywords ๋ณ์์๋ค๊ฐ๋ ์ถ๊ฐํด์ฃผ์.
// token/token.go
const (
ILLEGAL = "ILLEGAL"
EOF = "EOF"
// Identifiers + literals
IDENT = "IDENT" // add, result, x, y, ...
INT = "INT" // 1343456
// Operators
ASSIGN = "="
PLUS = "+"
MINUS = "-"
BANG = "!"
ASTERISK = "*"
SLASH = "/"
LT = "<"
GT = ">"
// Delimiters
COMMA = ","
SEMICOLON = ";"
LPAREN = "("
RPAREN = ")"
LBRACE = "{"
RBRACE = "}"
// ์์ฝ์ด
FUNCTION = "FUNCTION"
LET = "LET"
TRUE = "TRUE"
FALSE = "FALSE"
IF = "IF"
ELSE = "ELSE"
RETURN = "RETURN"
)
var keywords = map[string]TokenType{
"fn": FUNCTION,
"let": LET,
"true": TRUE,
"false": FALSE,
"if": IF,
"else": ELSE,
"return": RETURN,
}
๊ทธ๋ฆฌ๊ณ ์ด์ ๋ ์ฑํ ๋์ ๋ฌธ์๊ฐ ์ด๋ค ํ ํฐ์ธ์ง ํ์ธํ๊ณ ๊ทธ์ ๋ง๋ ํ ํฐ์ ์์ฑํ๋ NextToken ํจ์์ switch-case ๊ตฌ๋ฌธ์ ๋ฐฉ๊ธ ์ถ๊ฐํ ๋ฌธ์๋ค์ ๋ฐ์ํด๋ณด์. ์ฐธ๊ณ ๋ก ์๋กญ๊ฒ ์ถ๊ฐ๋ ์์ฝ์ด์ ๊ฒฝ์ฐ๋ ๊ธฐ์กด NextToken ํจ์์ default ๊ตฌ๋ฌธ์ ๊ทธ๋๋ก ์ด์ฉํ๋ฉด ๋๋ค.
// lexer/lexer.go
func (l *Lexer) NextToken() token.Token {
var tok token.Token
switch l.ch {
case '=':
tok = newToken(token.ASSIGN, l.ch)
case ';':
tok = newToken(token.SEMICOLON, l.ch)
case '(':
tok = newToken(token.LPAREN, l.ch)
case ')':
tok = newToken(token.RPAREN, l.ch)
case ',':
tok = newToken(token.COMMA, l.ch)
case '+':
tok = newToken(token.PLUS, l.ch)
case '-':
tok = newToken(token.MINUS, l.ch)
case '!':
tok = newToken(token.BANG, l.ch)
case '*':
tok = newToken(token.ASTERISK, l.ch)
case '/':
tok = newToken(token.SLASH, l.ch)
case '<':
tok = newToken(token.LT, l.ch)
case '>':
tok = newToken(token.GT, l.ch)
case '{':
tok = newToken(token.LBRACE, l.ch)
case '}':
tok = newToken(token.RBRACE, l.ch)
case 0:
tok.Type = token.EOF
tok.Literal = ""
default:
if isLetter(l.ch) {
tok.Literal = l.readIdentifier()
tok.Type = token.LookupIdent(tok.Literal)
return tok
} else if isDigit(l.ch) {
tok.Literal = l.readNumber()
tok.Type = token.INT
return tok
} else {
tok = newToken(token.ILLEGAL, l.ch)
}
}
l.readChar()
return tok
ํ์ง๋ง ์ ์ํ์์์ ๋ ์๋ ์์ง ๊ทน๋ณตํ์ง ๋ชปํ๋ ์ ์ด ์๋ค. ๋ฐ๋ก ๋ ๊ฐ์ ๊ธ์๊ฐ ์๋ ๋ฌธ์๋ก ๊ตฌ์ฑ๋, ์๋ฅผ ๋ค์ด != ๋ == ์ ๊ฐ์ ์ฐ์ฐ์์ ๋ํ ํ ํฐํ์ด๋ค.(๋ ๋ค๋ฅธ ์์๋ก <= >= ++, -- ์ด๋ฐ ๊ฒ๋ค์ด ์๋ค. ํ์ง๋ง ํด๋น ์ฑ ์์๋ ์ด๋ฐ ์ฐ์ฐ์๋ค์ ๋ํ ๋ ์ฑ์ ๋ค๋ฃจ์ง ์๋๋ค)
์ฐ๋ฆฌ๋ ์ด 2๊ฐ ๋ฌธ์๋ก ์ด๋ฃจ์ด์ง ์ฐ์ฐ์๋ฅผ ์ด๋ป๊ฒ ํ ํฐํ์ํฌ ์ ์์๊น? ์ฐ์ ์ฐ๋ฆฌ๊ฐ ํ์ธํ๋ ค๊ณ ๋ '2๊ฐ ๋ฌธ์'๊ฐ ๋ง๋์ง ํ์ธํ๋ ํจ์๊ฐ ํ์ํ๋ค. ์ฆ, ๋ง์ฝ ๋ ์ฑ ๋์์ ๋ฌธ์๊ฐ = ๋ก ๋ฑ์ฅํ๋ค๊ณ ํด๋ณด์. ์ด = ๋ฌธ์ ๋ค์์ ๋ ๋ฌธ์ = ๊ฐ ๋์ค๋์ง๋ฅผ ์ดํด๋ณด์์ผ ํ๋ค. ์ด๋ฅผ ํ์ธํ๊ธฐ ์ํ peekChar ํจ์๋ฅผ ๋จผ์ ์ดํด๋ณด์.
// lexer/lexer.go
func (l *Lexer) peekChar() byte {
if l.readPosition >= len(l.input) {
return 0
} else {
return l.input[l.readPosition]
}
}
ํด๋น ํจ์์ ๋ก์ง ์์ฒด๋ ๊ฐ๋จํ๋ค. ๋จ์ํ ํ์ฌ ๋ ์ฑ ๋์ ๋ฌธ์์ ๋ค์ ์์น์ ์๋ ๋ฌธ์๋ฅผ ๋ฐํํ๋ ๊ฒ์ด๋ค. ์ด์ ์ ํจ์๋ฅผ NextToken ํจ์์ ๋ฐ์ํด๋ณด๋๋ก ํ์. ์ฐ๋ฆฌ๊ฐ ๊ณ ๋ คํ๋ 2๊ฐ ๋ฌธ์๋ == ๋๋ != ์ด๊ธฐ ๋๋ฌธ์ switch-case ๊ตฌ๋ฌธ์์ = ๋๋ ! ๋ฌธ์๊ฐ ๋์์ ๋์ ๋ถ๋ถ์ ๋ฐ์ํด์ฃผ๋ฉด ๋๋ค.
// lexer/lexer.go
func (l *Lexer) NextToken() token.Token {
var tok token.Token
switch l.ch {
case '=':
if l.peekChar() == '=' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{Type: token.EQ, Literal: literal}
} else {
tok = newToken(token.ASSIGN, l.ch)
}
...(์๋ต)...
case '!':
if l.peekChar() == '=' {
ch := l.ch
l.readChar()
literal := string(ch) + string(l.ch)
tok = token.Token{Type: token.NOT_EQ, Literal: literal}
} else {
tok = newToken(token.BANG, l.ch)
}
...(์๋ต)...
l.readChar()
return tok
}
์ด์ ๋ง์ง๋ง์ผ๋ก, == ์ != ๋ผ๋ ํ ํฐ์ด ์กด์ฌํ๋ค๋ ๊ฒ์ ์๋์ ๊ฐ์ด ์์๊ฐ์ ์ถ๊ฐํด์ฃผ์.
// token/token.go
const (
...(์๋ต)...
ASTERISK = "*"
SLASH = "/"
EQ = "=="
NOT_EQ = "!="
...(์๋ต)...
ELSE = "ELSE"
RETURN = "RETURN"
)
4. ๋ ์์ ๋ํ REPL ๋ง๋ค๊ธฐ
3๋ฒ ๋ชฉ์ฐจ๊น์ง ํด์ ์ฐ๋ฆฌ๋ง์ ๋ ์๋ฅผ ๋ง๋ค์๋ค! ์ด์ ๋ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ ์๋ฅผ ํ ์คํธํ๊ธฐ ์ํด ์ข ๋ ์ฌ์ฉ์ ์นํ์ ์ธ REPL(Read, Eval, Print, Loop)์ ๋ง๋ค์ด๋ณด์. REPL์ ์ฝ์ ๋๋ ๋ํํ ๋ชจ๋๋ผ๊ณ ์ด์ผ๊ธฐํ๋ค. ํ์ด์ฌ ์ธ์ด๊ฐ ์ต์ํ ์ฌ๋๋ค์ ํฐ๋ฏธ๋์ python ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ๋ฉด ๋ค์ด๊ฐ๊ฒ ๋๋ ๋ํํ ๋ชจ๋๋ ๋๊ฐ๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
REPL์ ๋ง๋๋ ์ฝ๋๋ ์ฐ๋ฆฌ๊ฐ ๊ตฌ์ฒด์ ์ธ ๋ก์ง์ ์์ฑํ๋ ๊ฒ์ ๊ฑฐ์ ์๊ณ , ํจํค์ง๋ฅผ ๊ฐ์ ธ๋ค ์ฐ๊ฑฐ๋ ์์ธ์ฒ๋ฆฌํ๋ ๋ก์ง์ด ์ ๋ถ๋ค. ๋ฐ๋ผ์ ์ฝ๋๋ง ์ฒจ๋ถํ๊ณ ๊ธ์ ๋ง๋ฌด๋ฆฌํ๋ ค๊ณ ํ๋ค.
๋จผ์ repl ํจํค์ง๋ฅผ ์์ฑํด์ ์๋์ ์์ค์ฝ๋๋ฅผ ์ ๋ ฅํ์.
// repl/repl.go
package repl
import (
"bufio"
"fmt"
"io"
"monkey/lexer"
"monkey/token"
)
const PROMPT = ">> "
func Start(in io.Reader, out io.Writer) {
scanner := bufio.NewScanner(in)
for {
fmt.Fprintf(out, PROMPT)
scanned := scanner.Scan()
if !scanned {
return
}
line := scanner.Text()
l := lexer.New(line)
for tok := l.NextToken(); tok.Type != token.EOF; tok = l.NextToken() {
fmt.Fprintf(out, "%+v\n", tok)
}
}
}
๊ทธ๋ฆฌ๊ณ ์ด์ ์ฌ์ฉ์์ ์ํธ๋ฆฌํฌ์ธํธ์ธ main ํจํค์ง๋ฅผ ๋ง๋ค๊ณ , main ํจ์๋ฅผ ์์ฑํด๋ณด์.
// main.go
package main
import (
"fmt"
"monkey/repl"
"os"
"os/user"
)
func main() {
user, err := user.Current()
if err != nil {
panic(err)
}
fmt.Printf("Hello, %s!\n", user.Username)
fmt.Printf("Feel free to type in commands\n")
repl.Start(os.Stdin, os.Stdout)
}
๊ทธ๋ฆฌ๊ณ ์ ์คํฌ๋ฆฝํธ๋ฅผ ํ์ฌ ๊ฒฝ๋ก๋ก ๋๊ณ , go run ๋ช ๋ น์ด๋ฅผ ์ํํด๋ณด์. ์ฐ๋ฆฌ๋ง์ ๋ ์์ ๋ํด์ ๋ํํ ๋ชจ๋๋ก ํ ์คํธ๋ฅผ ์ํํ ์ ์๊ฒ ๋์๋ค!
์ด์ ๋ค์ ํฌ์คํ ์์๋ ์ด๋ฒ ํฌ์คํ ์์ ๋ง๋ ๋ ์๊ฐ ํ ํฐํ์ํจ ํ ํฐ๋ค์ ์ถ์๊ตฌ๋ฌธํธ๋ฆฌ๋ก ๋ง๋๋ ํ์ฑ(Parsing)์ ํด๋ณผ ์ฐจ๋ก๋ค.