๐ ํด๋น ํฌ์คํ
์ ๋ฐ๋ฐ๋ฅ๋ถํฐ ๋ง๋๋ ์ธํฐํ๋ฆฌํฐ in go ์ฑ
์ ์ฝ๊ณ ๊ฐ์ธ์ ์ธ ์ ๋ฆฌ ๋ชฉ์ ํ์ ์์ฑ๋ ๊ธ์
๋๋ค. ๋ณธ ํฌ์คํ
์ ์ฌ์ฉ๋ ์๋ฃ๋ ๋ชจ๋ ๋ณธ์ธ์ด ์ง์ ์ฌ๊ตฌ์ฑํ์ฌ ์์ฑํ์์์ ์๋ฆฝ๋๋ค.
์ ๋ฒ ํฌ์คํ ๊น์ง ํด์ ์ฐ๋ฆฌ๋ Monkey ๋ผ๋ ์ฐ๋ฆฌ๋ง์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ๋ ์ฑํ๋ ๋ ์์ ํ์ฑํ๋ ํ์ 2๊ฐ์ง๋ฅผ ๋ชจ๋ Golang ๊ธฐ๋ฐ์ผ๋ก ๋ง๋ค์ด๋ณด์๋ค. ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ๋ ํ์์ ๊ฒฐ๊ณผ๋ฌผ๋ก AST ์๋ฃ๊ตฌ์กฐ๊น์ง ๋ง๋๋ ๊ฒ๊น์ง ๋ชจ๋ ํด๋ณด์๋ค. ์ด์ ๋ ๋ ์์ ํ์์ ์ ๋ ฅ์ผ๋ก ๋ค์ด๊ฐ Monkey ์ธ์ด ์์ค์ฝ๋ ๋ฌธ์์ด์ด ์ค์ ์๋ฏธ๋ฅผ ๊ฐ๋๋ก ํด์ฃผ๋ ํ๊ฐ(Evaluation)๋ฅผ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ๋ฐฐ์๋ณผ ๊ฒ์ด๋ค.
1. ์ธํฐํ๋ฆฌํฐ์ ๋ง์ง๋ง ๋จ๊ณ: ํ๊ฐ(Evaluation) ํ๋ก์ธ์ค
์ฐ๋ฆฌ๊ฐ ์ฒ์์ ๋ ์๋ฅผ ๋ง๋ค ๋ ๋ณด์๋ ๊ทธ๋ฆผ์ ๋ค์ ๊ฐ๊ณ ์ ๋ณด์.
๊ฐ์ฅ ๋จผ์ ์์ค์ฝ๋ ๋ฌธ์์ด์ด ์ ๋ ฅ์ผ๋ก ๋ค์ด๊ฐ๋ฉด ๋ ์๊ฐ ํ ํฐํ์ํจ ๋ค ํ์๊ฐ ํ์ฑ์ ์ํํด ์ถ์๊ตฌ๋ฌธํธ๋ฆฌ์ธ AST ์๋ฃ๊ตฌ์กฐ๋ก ๋ง๋ ๋ค๊ณ ํ๋ค. ์ด์ ์ด๋ ๊ฒ ๋ง๋ค์ด์ง AST ์๋ฃ๊ตฌ์กฐ๋ ํ๊ฐ ๋์์ด ๋๋ค. ์ฆ, AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ํ๊ฐํจ์ผ๋ก์จ ์ ๋ ฅ์ผ๋ก ๋ค์ด๊ฐ ์์ค์ฝ๋๊ฐ ์ง์ง ์๋ฏธ๋ฅผ ๊ฐ๊ฒ ๋๋ค.
์ธํฐํ๋ฆฌํฐ์ ํ๊ฐ ํ๋ก์ธ์ค๋ ๋ฒ์ญ๋ ์ธ์ด์ ๋์ ๋ฐฉ์์ ์ ์ํ๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ Monkey ์ธ์ด ์์ค์ฝ๋๊ฐ ์๋ค๊ณ ํด๋ณด์.
# Monkey ์ธ์ด์
let num = 10;
if (num) {
return a;
} else {
return b;
}
์ฌ๋์ ๋์ผ๋ก ์ฝ์ผ๋ฉด ๋น์ฐํ a๊ฐ ๋ฐํ๋ ๊ฒ์์ ์ ์ ์๋ค. ํ์ง๋ง ์ด๊ฑด ์ฐ๋ฆฌ ์ฌ๋์ ํ๋จ์ด๋ค. ์ปดํจํฐ๊ฐ ์ฐ๋ฆฌ์ ๊ฐ์ ํ๋จ์ ํ๊ธฐ ์ํด์๋ ์ ์์ค์ฝ๋์ ๋ํ ํ๊ฐ๊ฐ ๋ฐ๋์ ํ์ํ๋ค. ์ฆ, ํ๊ฐ ํ๋ก์ธ์ค๊ฐ num ๋ณ์์ ํ ๋น๋์ด ์๋ ๊ฐ 10์ ์ฐธ(true)์ผ๋ก ํ๊ฐํ๋๋ก ํด์ผ ํ๋ค. ๋ ๋ค๋ฅธ ์์๋ก ์๋์ ๊ฐ์ ์์ค์ฝ๋๊ฐ ์๋ค๊ณ ํด๋ณด์.
# Monkey ์ธ์ด์
let one = fn() {
printLine("one");
return 1;
}
let two = fn() {
printLine("two");
return 2;
}
add(one(), two());
์ ์์ค์ฝ๋๋ฅผ ์คํํ์ ๋, ์ฝ์์ one ์ด๋ผ๋ ๋ฌธ์์ด์ด ๋จผ์ ๋ฑ์ฅํ ๊น? ์๋๋ฉด two ๋ผ๋ ๋ฌธ์์ด์ด ๋จผ์ ๋ฑ์ฅํ ๊น? ์ด ์์๋ฅผ ๊ฒฐ์ ํ๋ ๊ฒ์ ์ด Monkey ์ธ์ด์ ์ธํฐํ๋ฆฌํฐ๊ฐ ํธ์ถ ํํ์์ ์ธ์๋ฅผ ํ๊ฐํ๋ ์์๊ฐ ์ด๋ป๊ฒ ๋๋์ง์ ๋ฐ๋ผ ๋ฌ๋ ค ์๋ค. ๋ง์ฝ ์ค๋ฅธ์ชฝ์ ์๋ ์ธ์๋ฅผ ๋จผ์ ํ๊ฐํ๋ค๋ฉด two ๋ผ๋ ๋ฌธ์์ด์ด ๋จผ์ ์ฝ์์ ๋ฑ์ฅํ ๊ฒ์ด๋ค.(์ํ์ผ์, Python์์๋ ํด๋ณด์๋๋ฐ, CPyhton ๊ตฌํ์ ๊ธฐ์ค์ Python ์ธํฐํ๋ฆฌํฐ๋ ์ผ์ชฝ์ ์๋ ์ธ์๋ฅผ ๋จผ์ ํ๊ฐํ๋ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค)
2. ๋ค์ํ ํ๊ฐ(Evaluation) ์ ๋ต
ํ๊ฐ ํ๋ก์ธ์ค์ ์ฌ์ฉ๋๋ ํ๊ฐ ์ ๋ต์ ์ธํฐํ๋ฆฌํฐ ๊ตฌํ์ฒด์ ๋ฐ๋ผ ๋ค์ํ๊ฒ ๋ฌ๋ผ์ง๋ค. ์์ผ๋ก ํ๊ฐ๋ฅผ ๊ตฌํํ๊ธฐ์ ์์ ์ฐ๋ฆฌ๋ ๋ ์์ ํ์๊ฐ ๋ง๋ AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ํ๊ฐํ๋ ์ ๋ต์๋ ์ด๋ค ๊ฒ๋ค์ด ์๋์ง ์ดํด๋ณผ ํ์๊ฐ ์๋ค.
2-1. AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ๋ฒ์ญํ์: ํธ๋ฆฌ ์ํ(Tree-walking) ์ธํฐํ๋ฆฌํฐ
ํธ๋ฆฌ ์ํ ์ธํฐํ๋ฆฌํฐ๋ผ๋ ํ๊ฐ ์ ๋ต์ ๋จ์๋ฌด์(?)ํ ๋ฐฉ์์ด๋ค. ๋ง ๊ทธ๋๋ก AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ํ๋์ฉ ์ํํ๊ณ ๊ฐ ๋ ธ๋๋ฅผ ๋ฐฉ๋ฌธํ๋ฉด์ ๋ ธ๋๊ฐ ๊ฐ๋ ์๋ฏธ๋๋ก ์ฆ์(on the fly) ์ฒ๋ฆฌํ๋ค. ๊ฐ์ฅ ๋จ์ํ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ์ธํฐํ๋ฆฌํฐ์ ์ํ์ด ๋๊ธฐ๋ ํ๋ค. ๋ค๋ง, ํธ๋ฆฌ ์ํ ์ธํฐํ๋ฆฌํฐ๋ผ๋ ์ ๋ต์ ์ฌ์ฉํด์ ํ๊ฐ๋ฅผ ์ํํ๊ธฐ ์ ์ ๋ช ๊ฐ์ง ์ต์ ํ๋ฅผ ์ํํ๊ธฐ๋ ํ๋ค. ์ฒซ๋ฒ์งธ๋ ์ ์ํ์ง๋ง ์ ์ฐ๋ ๋ณ์๋ฅผ ์ ๊ฑฐํ๋ ๋ฐฉ์์ผ๋ก AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ฌ๊ตฌ์ฑํ๋ค. ๋ ๋ฒ์งธ๋ก๋ AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ฌ๊ท ํน์ ๋ฐ๋ณต ํ๊ฐ์ ๋์ฑ ์ ํฉํ ์ค๊ฐ ํํ๋ฌผ(Intermediate Representation, IR)๋ก ๋ณ๊ฒฝํ๊ธฐ๋ ํ๋ค.
๊ทธ๋ฐ๋ฐ ๋ ๋ฒ์งธ ์ต์ ํ ๋ฐฉ๋ฒ์์ ์๊ฐํ๋ ์ค๊ฐ ํํ๋ฌผ ๊ด๋ จํด์ ์ข ๋ ์์๋ณผ ๋ด์ฉ์ด ์๋ค. ์ด๋ค ์ธํฐํ๋ฆฌํฐ๋ค์ ๋๊ฐ์ด AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ํง์ง๋ง, AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ๋ฒ์ญํ๋ ๋์ ๋ฐ์ดํธ์ฝ๋(Bytecode) ๋ผ๋ ๊ฒ์ผ๋ก ๋ณํํ๋ค. ์ด ๋ฐ์ดํธ์ฝ๋๊ฐ ๋ฐ๋ก ์ค๊ฐ ํํ๋ฌผ์ ์ ํ ์ค ํ๋์ด๋ค. ๋ฐ์ดํธ์ฝ๋์๋ AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ๋ํ๋ด๋ ์ ๋ณด๊ฐ ๋ค์ด๊ฐ ์๋ค. ์ด ๋ฐ์ดํธ์ฝ๋๋ฅผ ๊ตฌ์ฑํ๋ ํ์๊ณผ ๋ช ๋ น์ฝ๋์ ์ ํ๋ค์ ๊ฒ์คํธ ์ธ์ด์ ํธ์คํธ ์ธ์ด์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ค. ๊ฒ์คํธ ์ธ์ด๋, ์ธํฐํ๋ฆฌํฐ๊ฐ ๋ฒ์ญํ ๋์์ธ ์ธ์ด ์ฆ, ์ฐ๋ฆฌ์ ํ๊ฒฝ์์๋ Monkey ์ธ์ด๊ฐ ๋๋ค. ๋ฐ๋ฉด์ ํธ์คํธ ์ธ์ด๋ ์ธํฐํ๋ฆฌํฐ ์์ฒด๋ฅผ ๊ตฌํํ๋ ๋ฐ ์ฌ์ฉ๋ ์ธ์ด ์ฆ, ์ฐ๋ฆฌ์ ํ๊ฒฝ์์๋ Go ์ธ์ด๊ฐ ๋๋ค.
๋ฐ์ดํธ์ฝ๋์ ๋ํด์ ์ข ๋ ์ธ๋ถ์ ์ผ๋ก ์์๋ณด์. ๋๋ถ๋ถ์ ๋ฐ์ดํธ์ฝ๋์๋ ์คํ ์ฐ์ฐ์ธ Push, Pop์ ํ๊ธฐ ์ํด ์ด์ ๋์ํ๋ ๋ช ๋ น ์ฝ๋๋ฅผ ์ ์๋์ด ์๋ค. ํ์ง๋ง ๊ทธ๋ ๋ค๊ณ ํด์ ๋ฐ์ดํธ์ฝ๋๊ฐ ๋ฌผ๋ฆฌ์ ์ธ CPU ๊ฐ์ ์ฐ์ฐ ์ฅ์น๊ฐ ์ดํดํ ์ ์๋ ๊ธฐ๊ณ์ด๋ ์๋๋ค. ๋ค์ ๋งํด, ๋ฐ์ดํธ์ฝ๋๋ ์ฌ๋์ด ์ง๊ด์ ์ผ๋ก ์ดํดํ ์ ์๊ฒ ํํ๋์ด ์๋ ์ธ์ด ์ค ๊ฐ์ฅ ๊ธฐ๊ณ์ด ์์ค์ ๊ฐ๊น์ด ์ด์ ๋ธ๋ฆฌ์ด๋ ์๋๊ณ , ๊ธฐ๊ณ์ด๋ ์๋๋ค. ๊ทธ ์ด๋ ์ค๊ฐ์ ์๋ค.
๊ทธ๋ฌ๋ฉด ๋ฐ์ดํธ์ฝ๋๋ผ๋ ์ค๊ฐ ๊ฒฐ๊ณผ๋ฌผ์ ํด์์ ๋๊ฐ, ์ด๋ป๊ฒ ํ๋ ๊ฑธ๊น? ํด์์ ํ๋ ์ฃผ์ฒด๋ ๋ฐ๋ก ์ธํฐํ๋ฆฌํฐ์ ์ผ๋ถ์ธ ๊ฐ์ ๋จธ์ ์ด๋ค. VMWare๋ VirtualBox์ ๊ฐ์ ์ํํธ์จ์ด๋ค์ ์จ๋ณธ ๋ถ๋ค์ด ์์ ๊ฒ์ด๋ค. ์ด ์ํํธ์จ์ด๋ค์ ์ค์ ๋จธ์ ๊ณผ CPU๋ฅผ ๊ฐ์ํ(์ํํธ์จ์ดํ)ํ ์ ํ์ด๋ค. ์ฆ, ์ค๋ฉ ๋จธ์ ๊ณผ CPU๋ฅผ ๋ชจ๋ฐฉํ ๊ฒ์ด๋ค. ์ด์ฒ๋ผ ์ธํฐํ๋ฆฌํฐ์ ๊ฐ์ ๋จธ์ ๋ ๋ง์ฐฌ๊ฐ์ง๋ค. ์ธํฐํ๋ฆฌํฐ์ ๊ฐ์๋จธ์ ๋ ๋ฐ์ดํธ์ฝ๋ ํ์์ ์ดํดํ๋ ์์คํ ์ ๋ชจ๋ฐฉํ๋ค. ์ผ๋ก๋ก, CPython ๊ตฌํ์ฒด์ธ Python ์์๋ PVM(Python Virtual Machine)์ด ๋ฐ๋ก ์ธํฐํ๋ฆฌํฐ์ ๊ฐ์๋จธ์ ์ด๋ค.
์ด๋ ๊ฒ ๋ฐ์ดํธ์ฝ๋๋ผ๋ ์ค๊ฐ ํํ๋ฌผ์ ์ฌ์ฉํ๋ ๋ฐฉ์์ ์ฑ๋ฅ ์์ ์ด์ ์ ํฌ๊ฒ ๋ง๋ ๋ค. ๋์ฒด ์ด๋ค ์ฅ์ ์ด ์๊ธธ๋ ์ฑ๋ฅ ์์ ์ด์ ์ ๋ง๋ค์๋์ง ์ดํด๋ณด์.
๊ฐ์ฅ ๋จผ์ ์ค๊ฐ ํํ๋ฌผ๋ก ๋ง๋ค์ด์ง ๋ฐ์ดํธ์ฝ๋ ๋๋ถ์ ์ธํฐํ๋ฆฌํฐ๊ฐ ์ ๋ ฅ์ผ๋ก ๋ค์ด์จ ์์ค์ฝ๋ ๋ฌธ์์ด์ ๋งค๋ฒ ๋ ์ฑ, ํ์ฑํ์ง ์์๋ ๋๋ค. ์ด๋ ๊ฒ ๋ ์ฑํ๊ณ ํ์ฑํ๋ ๊ณผ์ ์ ์ค๋ฒํค๋๊ฐ ํฐ ์์ ์ด๋ค. ํ์ง๋ง ๋ฐ์ดํธ์ฝ๋๊ฐ ์ค๊ฐ ํํ๋ฌผ๋ก ๋ง๋ค์ด์ง๋ฉด ์ต์ด์๋ง ๋ ์ฑ, ํ์ฑ์ ์ํํ๊ณ ๊ทธ ์ดํ๋ถํฐ๋ ๋ฐ์ดํธ์ฝ๋์ ๋์ด๋ ๊ฐ์๋จธ์ ์ด ์ดํดํ ์ ์๋ ๋ช ๋ น์ด๋ค๋ง ์คํํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ํจ์ฌ ๋นจ๋ผ์ง๋ค. ๋ง์น CPU๊ฐ ์ด์ ๋ธ๋ฆฌ ๋ช ๋ น์ด๋ฅผ ๋น ๋ฅด๊ฒ ์์ฐจ ์คํํ๋ ๊ฒ์ฒ๋ผ ๋ง์ด๋ค.
๋ ๋ฒ์งธ๋ก๋ ๋ฐ์ดํธ์ฝ๋๋ฅผ ์คํํ๋ ์ฃผ์ฒด์ธ ๊ฐ์ ๋จธ์ ์ ๋จ์ํ๊ณ ํจ์จ์ ์ธ ์คํ ๋ชจ๋ธ์ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ด๋ค. ๊ฐ์ ๋จธ์ ์ ๊ณ ์ ๋ ์คํ ํ๋ฆ์ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ ์ค๋ฒํค๋๊ฐ ๋ฎ๊ณ , ๊ฐ์ ๋จธ์ ์ ๊ตฌํ์ฒด์ ๋ฐ๋ผ ์ฐ์ฐ ์ฒ๋ฆฌ๋ฅผ ์ต์ ํํ ์ ์๋ค.
์ธ ๋ฒ์ฌ๋ก๋ ์บ์๋ฅผ ํ์ฉํด์ ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค. ์ค๊ฐ ํํ๋ฌผ์ธ ๋ฐ์ดํธ์ฝ๋๋ ์บ์ฑํด๋ ์ ์๋ค. ์ผ๋ก๋ก, Python์ผ๋ก ๋ง๋ค์ด์ง ํ๋ก๊ทธ๋จ์ ์คํํ๋ค ๋ณด๋ฉด .pyc ๋ผ๋ ํ์ฅ์์ ํ์ผ์ด ์ด๋ ์๊ฐ ์์ฑ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ด .pyc ํ์ผ ๋ด๋ถ์๋ ๋ฐ์ดํธ์ฝ๋๊ฐ ๋ค์ด์์ผ๋ฉฐ ๋ค์ ์คํ ์์๋ .pyc ํ์ผ์ ์ฌ์ฉํด์ ํ์ฑ๊ณผ ์ปดํ์ผ ๊ณผ์ ์ ๊ฑด๋ ๋ฐ๊ฒ ๋๋ค.
2-2. AST ์๋ฃ๊ตฌ์กฐ๋ ๊ณ ๋ คํ์ง ๋ง์: JIT(Just In Time) ์ธํฐํ๋ฆฌํฐ/์ปดํ์ผ๋ฌ
๊ทธ๋ฐ๋ฐ ํ์๋ ํด๋น ๋ด์ฉ์ ๊ณต๋ถํ๋ฉด์ ๊ถ๊ธํ ์ ์ด ์๊ฒผ๋ค. AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ๊ฐ์๋จธ์ ์ด ๋ฐ์ดํธ์ฝ๋๋ผ๋ ์ค๊ฐ ํํ๋ฌผ๋ก ๋ณํํ๋ ๊ฒ๊น์ง๋ ์ดํด๊ฐ ๋์๋๋ฐ, ๊ทธ๋ฌ๋ฉด ์ค์ง์ ์ธ ๋ฌผ๋ฆฌ์ ์ฐ์ฐ์ ์ํํ๋ CPU ๊ฐ์ ์ฅ์น๊ฐ ์ดํดํ ์ ์๋ ๊ธฐ๊ณ์ด๋ก ๋ณํ๋๊ณ ์คํ๋๋ ๊ณผ์ ๊น์ง๋ ๋์ฒด ์ด๋ป๊ฒ ์งํ๋๋ ๊ฑด์ง ๊ถ๊ธํ๋ค. ๊ฒฐ๋ก ๋ถํฐ ๋งํ๋ฉด ์ธํฐํ๋ฆฌํ ๋ฐฉ์์ด๋ JIT ์ปดํ์ผ ๋ฐฉ์์ด๋์ ๋ฐ๋ผ ๊ณผ์ ์ด ์ฝ๊ฐ ๋ฌ๋ผ์ง๋ค. ์ผ๋จ ์๋ ์์๋๋ถํฐ ๋ณด์.
๋ ๋ฐฉ์ ๋ชจ๋ ๋ฐ์ดํธ ์ฝ๋๋ก ๋ณํํ๋ ๊ณผ์ ๊น์ง๋ ๋์ผํ๋ค. ๋จผ์ ์ธํฐํ๋ฆฌํ ๋ฐฉ์ ์ฆ, ํธ๋ฆฌ ์ํ ์ธํฐํ๋ฆฌํฐ ์ ๋ต์ ๊ฒฝ์ฐ๋ฅผ ์ดํด๋ณด์. CPython ๊ตฌํ์ฒด์ธ Python์ ์์๋ก ๋ค์ด๋ณด๊ฒ ๋ค. PVM์ด ๋ฐ์ดํธ ์ฝ๋์ ๋์๋๋ C ํจ์๋ฅผ ์์๋ธ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด C ํจ์๋ฅผ ํธ์ถํจ์ผ๋ก์จ ์ค์ ๋ฌผ๋ฆฌ์ ์ธ CPU ์ฅ์น๊ฐ ์ดํดํ ์ ์๋ ๊ธฐ๊ณ์ด๋ก ๋ณํ์ด ๋๊ณ , ์ด ๋ CPU๊ฐ ํด๋น ๊ธฐ๊ณ์ด๋ก ์ค์ง์ ์ธ ์ฐ์ฐ์ ์คํํ๊ฒ ๋๋ค.
๋ฐ๋ฉด์, JIT ์ปดํ์ผ ๋ฐฉ์์ ๋ฐ์ดํธ ์ฝ๋์ ์๋ ๋ช ๋ น์ด๋ฅผ VM์์ ์ง์ ์คํํ์ง ์๋๋ค. VM์ด ๋ฐ์ดํธ ์ฝ๋๋ฅผ ๋ถ์ํ์ฌ ์์ฃผ ์คํ๋๋ ์ฝ๋ ์๋ฅผ ๋ค์ด, loop ๊ตฌ๋ฌธ์ด๋ ํจ์์ ๊ฐ์ ์ฝ๋๋ค์ ์ฆ์ ๊ธฐ๊ณ์ด๋ก ๋ฐ๋ก ๋ณํ ์ฆ, ์ปดํ์ผ์ ํด๋ฒ๋ฆฐ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ณํ๋ ๊ธฐ๊ณ์ด๋ฅผ RAM์ ์ ์ฅํ ๋ค, ๋ฌผ๋ฆฌ์ ์ธ CPU ์ฅ์น๋ RAM์ ์ ์ฅ๋ ๊ธฐ๊ณ์ด๋ฅผ ์ฝ์ด๋ค์ฌ ์ฆ์ ์ฐ์ฐ์ ์ํํ๋ค.
์ 2๊ฐ ๋ฐฉ์์ ๋น๊ตํด๋ณด์์ ๋ ์ด๋ค ๋ฐฉ์์ด ๋ ์ฑ๋ฅ์ด ์ข์ ๋ณด์ผ๊น? ๋ฑ ๋ณด์๋ ๋ฌผ๋ฆฌ CPU ์ฐ์ฐ ์ฅ์น๊ฐ ์คํ๋๊ธฐ ์ ๊น์ง ๊ฑฐ์น๋ ๋จ๊ณ๊ฐ ๊ฐ์ํ๋๊ฒ ๋ณด์ธ๋ค. ๊ทธ๋์ JIT ์ปดํ์ผ๋ฌ ๋ฐฉ์์ด ํจ์ฌ ๋ ๋น ๋ฅธ ์ฑ๋ฅ์ ๋ณด์ธ๋ค. JIT ์ด๋ผ๋ ์ฉ์ด๋ Just In Time ์ ์ค์๋ง๋ก, VM์ด ์คํ ์ง์ ์ ๋ฐ์ดํธ์ฝ๋๋ฅผ ๊ธฐ๊ณ์ด๋ก ์ฆ์์์ ์ปดํ์ผํ๋ค๊ณ ํ์ฌ Just In Time์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์ฌ์ง์ด ์ด๋ค ํ๊ฐ ์ ๋ต์ AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ๊ณ ๋ คํ์ง๋ ์๊ณ ํ์๊ฐ ๋ฐ๋ก ๋ฐ์ดํธ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒฝ์ฐ๋ ์๊ณ , ์ด๋ค ๊ตฌํ์ฒด๋ ๋ฐ์ดํธ์ฝ๋๋ก ์ปดํ์ผ ํ๋ ๊ณผ์ ์ ์ง๋์น๊ธฐ๋ ํ๋ค. ๋ชจ๋ ํ๊ฐ ์ ๋ต์ ์ฌ๊ธฐ์ ๋ค ์์๋ณผ ์๋ ์์ง๋ง ์ด์จ๊ฑด ์ ๋ง ๋ค์ํ ํ๊ฐ ์ ๋ต์ด ์๋ค๋ ์ฌ์ค์ ๋ฐ๋์ ์์๋๋๋ก ํ์. ๋ณดํต์ ๋ฒ์ญ ๋์์ด ๋๋ ์ธ์ด๊ฐ ์๊ตฌํ๋ ์ฑ๋ฅ, ์ด์์ฑ, ๊ตฌํ์๊ฐ ๊ตฌํํ๊ณ ์ ํ๋ ๋ชฉํ์น์ ๋ฐ๋ผ ํ๊ฐ ์ ๋ต์ ์ ํํ๋ค. ์ฌ์ค ๊ฐ์ ๋จธ์ ์ ์ด์ฉํด์ ๋ฐ์ดํธ ์ฝ๋๋ฅผ ์ปดํ์ผํ๊ณ ํ๊ฐํ๋๋ก ๋์ํ๋ ์ธํฐํ๋ฆฌํฐ๋ ์์ผ๋ก ๊ณ์ ์๋๊ฐ ๋นจ๋ผ์ง ๊ฒ์ด๋ค. ๋ค๋ง, ์ธํฐํ๋ฆฌํฐ์ ํ๊ฐ ์ ๋ต์ด ๋ณต์กํด์ง์ ๋ฐ๋ผ ๋ง๋ค๊ธฐ๋ ์ด๋ ค์์ง ๋ฟ๋๋ฌ ์ธํฐํ๋ฆฌํฐ๋ฅผ ๊ฐ์ ธ๋ค๊ฐ ์ฌ์ฉํ๋ ๋๋ถ๋ถ์ ๊ฐ๋ฐ์๋ค ์ ์ฅ์์๋ ๋ด๋ถ ๋์์ ์๋ฒฝํ๊ฒ ์ดํดํ๊ธฐ๊ฐ ์ ์ ์ด๋ ค์์ง๊ฒ ๋๋ค. Java์ JVM์ด๋ Python์ PVM๋ง ํ๋๋ผ๋ ๋ฌผ๋ฆฌ CPU ์ฐ์ฐ ์ฅ์น์ ์๋ฆฌ๋ฅผ ์ดํดํด์ผํ ๋ฟ๋ง ์๋๋ผ JVM, PVM ๊ณผ ๊ฐ์ ๊ฐ์ ๋จธ์ ์ด ๋ฐ์ดํธ ์ฝ๋๋ฅผ ์ด๋ป๊ฒ ์ดํดํ๊ณ ๋์ํ๋์ง์ ๋ํ ์ดํด๋ ๊ฐ์ด ๋๋ฐ๋์ด์ผ ํ๋ค. ๋ฌผ๋ก ๋๋ถ๋ถ์ ๊ฐ๋ฐ์๊ฐ ์ธํฐํ๋ฆฌํฐ๋ฅผ ์ง์ ๊ฐ๋ฐํ์ง๋ ์๋๋ค. ๊ฐ์ธ์ ์ผ๋ก ์ด๋ฐ ๊ฒ์ด ๋์ ํ์์ด๋ผ๊ณ ์ ๋ ์๊ฐํ์ง ์๋๋ค. ์คํ๋ ค ๋ฐ์ด๋ ๊ฐ๋ฐ์๋ค์ด ๋ด๊ฐ ์ฌ์ฉํ๋ ์ธ์ด์ ์๋๋ฅผ ๊ฐ์ํ์์ผ์ค๋ค๋ ์ ์์๋ ํฐ ๊ฐ์ฌ์ธ์ฌ๋ฅผ ์ ๋ฌํ๊ณ ์ถ์ ๋ง์ด๋ค. ๋ค๋ง, ์ฐ๋ฆฌ๊ฐ ๊ธฐ์ตํด์ผ ํ ๊ฒ์ ์ธํฐํ๋ฆฌํฐ๊ฐ ๋ณดํต ์ด๋ฐ์์ผ๋ก ๋์ํ๋ค๋ ๊ฐ๋ ์ ์์งํ๋ ๊ฒ์ด๋ค. ์ด ๊ฐ๋ ์ ๋ํ ๊ฒ๋ง์ ๊ผญ ๋จธ๋ฆฟ์์ ๋ฃ๊ณ ๋์ด๊ฐ๋๋ก ํ์.
3. ํ๊ฐ(Evaluation)๊ธฐ ๋ง๋ค๊ธฐ
ํ๊ฐ์ ๋ํ ๊ฐ๋ ๋ฟ๋ง ์๋๋ผ ํ๊ฐ ์ ๋ต์ ๋ํด์ ์ด๋ก ์ ์ธ ๋ถ๋ถ๊น์ง ๋ฐฐ์๋ณด์์ผ๋ ์ด์ ์ง์ ํ๊ฐ๊ธฐ๋ฅผ ๊ตฌํํด๋ณผ ์ฐจ๋ก๋ค. ํ๊ฐ๊ธฐ์ ์ ๋ ฅ์ผ๋ก๋ (ํ์๊ฐ ๋ง๋ค์ด๋ธ) AST ์๋ฃ๊ตฌ์กฐ์ ๋ ธ๋๊ฐ ๋ค์ด๊ฐ๋ค. ๊ทธ๋ฆฌ๊ณ ํ๊ฐ๊ธฐ์ ์ถ๋ ฅ์ ํ๊ฐ๋ ๊ตฌ๋ฌธ์ด ๋์จ๋ค. ์์ค์ฝ๋๋ก ํํํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
leftEvaluated = eval(astNode.Left)
rightEvaluated = eval(astNode.Right)
eval ์ด๋ผ๋ ๋ฉ์๋๊ฐ ๋ฐ๋ก ํ๊ฐ๊ธฐ์ ์ญํ ์ ํ๋ ๊ฒ์ด๋ค. ๊ทธ๋ฌ๋ฉด eval ๋ฉ์๋๊ฐ ๋ฐํํ๋ ๊ฒ์ ๋ฌด์์ผ๊น? ์ด ๋ง์ ๋ฐ๊ฟ ๋งํ๋ฉด ์ฐ๋ฆฌ๊ฐ ๊ตฌํํ๋ ์ธํฐํ๋ฆฌํฐ๊ฐ ์ด๋ค ๋ด๋ถ ๊ฐ์ฒด ์์คํ ์ ์ ์ํ๋์ง์ ๊ฐ๋ค. ํ์ฌ ์ฐ๋ฆฌ๋ ํธ์คํธ ์ธ์ด๋ฅผ Go ์ธ์ด๋ก ํ์ฌ ๊ฒ์คํธ ์ธ์ด์ธ Monkey ์ธ์ด๋ฅผ ํด์ํ๋ ์ธํฐํ๋ฆฌํฐ๋ฅผ ๊ฐ๋ฐํ๊ณ ์๋ค. ๊ฒฐ๊ตญ, Monkey ์ธ์ด๋ก ํํ๋๋ ํ์ (ex. ์ ์, Bool, Null, ์ค์, ๋ฌธ์์ด ๋ฑ..)๋ค์ ๋ํ ๊ตฌํ์ Go ์ธ์ด๋ก ํํํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค. ๊ทธ๋์ ๋ณดํต ๊ฒ์คํธ ์ธ์ด์ ์ธํฐํ๋ฆฌํฐ๋ฅผ ๊ตฌํํ๋ ๋ฐ ํธ์คํธ ์ธ์ด์ ๋งค์ฐ ์์กด์ ์ด๊ฒ ๋๋ค.
์ผ๋ก๋ก, Python์ ์ธํฐํ๋ฆฌํฐ ์ธ์ด์ด๋ค. CPython ๊ตฌํ์ฒด ๊ธฐ์ค์ผ๋ก, Python์ด๋ผ๋ ๊ฒ์คํธ ์ธ์ด๋ฅผ ๊ฐ๋ฐํ ํธ์คํธ ์ธ์ด๋ C์ธ์ด์ด๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ Python์ ํ์ ์ C์ธ์ด์ ํ์ ์ ๋งค์ฐ ์์กด์ ์ผ ์ ๋ฐ์ ์๋ค๋ ๊ฒ์ด๋ค.
๋ค์ ์ฐ๋ฆฌ์ ์ํฉ์ผ๋ก ๋์์์ ์ฐ๋ฆฌ๋ Monkey ์ธ์ด๋ฅผ ํ๊ฐํ๊ธฐ ์ํด์ Monkey ์ธ์ด์ ๊ฐ์ฒด ์์คํ ์ Go ์ธ์ด๋ก ๊ตฌํํด์ผ ํ๋ค. ์ด ๊ตฌํ์ ๋ํด์ ํ ๋ฐ์ฉ ๋ด๋์ด๋ณด์.
3-1. ๊ฐ์ฒด ํํํ๊ธฐ: ์ ์(integer)
AST ๋ ธ๋๋ฅผ ๊ตฌํํ ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ์ฒด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด์ Go ์ธ์ด์ ์ธํฐํ์ด์ค๋ฅผ ํ์ฉํ๋ค. ์์ค์ฝ๋๋ฅผ ๋ณด์.
// object/object.go
package object
type ObjectType string
type Object interface {
Type() ObjectType
Inspect() string
}
Object ๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ์ ์ํ๊ณ , ์ด ์ธํฐํ์ด๋ Type(), Inspect() ๋ผ๋ 2๊ฐ์ง ๋ฉ์๋ ๊ตฌํ์ ์ถฉ์กฑ์์ผ์ผ ํ๋ค. Type() ๋ฉ์๋๋ ๋ ๋ค๋ฅธ ์๋ก์ด ํ์ ์ธ ObjectType์ ๋ฐํํด์ผ ํ๋๋ฐ, ์ด๋ ์์ค์ฝ๋์์ ์ ์๋ ๊ฒ์ฒ๋ผ ๋จ์ํ ๋ฌธ์์ด ํ์ ์ ์๋ฏธํ๋ค. ๊ทธ๋ฆฌ๊ณ Inspect() ๋ฉ์๋๋ ๋ฐํ ํ์ ์ด ๋ฌธ์์ด์ด๋ค.
์ฐ๋ฆฌ๋ ์ ์ธํฐํ์ด์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํด์ Monkey ์ธ์ด์์ ์ ์ ๊ฐ์ฒด ํ์ ์ ๊ตฌํํด๋ณผ ๊ฒ์ด๋ค.
// object/object.go
const (
INTEGER_OBJ = "INTEGER"
)
type Integer struct {
Value int64
}
func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }
func (i *Integer) Type() ObjectType { return INTEGER_OBJ }
Integer ๋ผ๋ ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ํ๊ณ , ํด๋น ๊ตฌ์กฐ์ฒด๋ Value ๋ผ๋ ๋ฉค๋ฒ๋ฅผ ๊ฐ๋๋ค. ์ด Value ๋ฉค๋ฒ์ ํ์ ์ด ์ค์ง์ ์ผ๋ก integer๋ฅผ ๊ฐ๋๋ก ํ๋ค. Integer ๋ผ๋ ๊ตฌ์กฐ์ฒด๊ฐ Object ์ธํฐํ์ด์ค๋ฅผ ๋ง์กฑ์ํค๊ธฐ ์ํด 2๊ฐ์ง ๋ฉ์๋๋ฅผ ์ ์ํ๋ค. Inspect ๋ฉ์๋๋ ๋ฌธ์์ด์ ๋ฐํํด์ผ ํ๋ฏ๋ก, Integer ๊ตฌ์กฐ์ฒด์ Value ๋ฉค๋ฒ ๋ณ์๋ฅผ ๋ฐํํ๋ Sprintf ํจ์๋ฅผ ์ด์ฉํด์ ๋ฌธ์์ด๋ก ๋ณํ์ํค๋๋ก ํ๋ค. ๋ค์์ Type ๋ฉ์๋๋ ObjectType์ ๋ฐํํ๋ค๊ณ ํ๋ค. ๊ทธ๋ฐ๋ฐ ObjectType ์ด๋ผ๋ ํ์ ์ ๋ฌธ์์ด์ด์๋ค. ObjectType์ด ์์ผ๋ก ์ฌ๋ฌ๊ฐ์ง์ผ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์์๋ก ์ ์ํด๋๋๋ก ํ์.
์ด๊ฒ ๋์ด๋ค. ์ฐ๋ฆฌ๋ Monkey ์ธ์ด์ ์ ์ ๊ฐ์ฒด๋ฅผ ๊ตฌํํ๋ค.
3-2. ๊ฐ์ฒด ํํํ๊ธฐ: Booleans
๋ค์์ผ๋ก ํํํด๋ณผ ๊ฐ์ฒด๋ true ๋๋ false๋ฅผ ๊ฐ๋ Bool ํ์ ์ด๋ค. ๋ฐฉ๊ธ ์ ์๋ฅผ ๊ตฌํํ ๊ฒ๊ณผ ๊ฑฐ์ ๋์ผํ๋ค. ์์ค์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
// object/object.go
const (
INTEGER_OBJ = "INTEGER"
BOOLEAN_OBJ = "BOOLEAN"
)
type Boolean struct {
Value bool
}
func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }
func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ }
3-3. ๊ฐ์ฒด ํํํ๊ธฐ: Null
๋ค์์ผ๋ก ํํํด๋ณผ ๊ฐ์ฒด๋ ๋ฐ๋ก ์๋ฌด๊ฒ๋ ์์์ ์๋ฏธํ๋ null ์ด๋ค. ํฌ์ธํฐ๋ฅผ ์ฌ์ฉํ๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ์กฐ๊ธ์ด๋ผ๋ ์ฌ์ฉํด๋ณธ ๊ฐ๋ฐ์๋ผ๋ฉด null ์ฐธ์กฐ๋ผ๋ ์ํฉ์ ํ ๋ฒ ์ฏค์ด๋ผ๋ ๊ฒฝํํด๋ณด์์ ๊ฒ์ด๋ค. null ์ฐธ์กฐ๋, ๋ง ๊ทธ๋๋ก ์๋ฌด๊ฒ๋ ์๋ ๋์์ ์ฐธ์กฐํ๋ ค๊ณ ํ๋ ๊ฒ์ด๋ค. ์ฐ๋ฆฌ์ Monkey ์ธ์ด๋ null ์ฐธ์กฐ๋ ํํํ ์ ์๋๋ก ํ๊ธฐ ์ํด null ๊ฐ์ฒด๋ ๊ตฌํํ๊ธฐ๋ก ํ๋ค.
// object/object.go
const (
INTEGER_OBJ = "INTEGER"
BOOLEAN_OBJ = "BOOLEAN"
NULL_OBJ = "NULL"
)
type Null struct{}
func (n *Null) Inspect() string { return "null" }
func (n *Null) Type() ObjectType { return NULL_OBJ }
null๋ ์ด์ ์ ๊ตฌํํ๋ ์ ์, Boolean ๊ณผ ๋ค๋ฅด๊ฒ ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ํ ๋ ๋ง ๊ทธ๋๋ก ์ ๋ง ์๋ฌด๊ฒ๋ ์๋ ๋น ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ํ๋ค. ์ด๊ฒ๋ง ์ฐจ์ด์ ์ด๊ณ ๋๋จธ์ง๋ ๋ชจ๋ ๋์ผํ๋ค.
3-4. ํํ์ ํ๊ฐํ๊ธฐ
๋ฐฉ๊ธ๊น์ง ์ ์, Booleans, Null ์ด๋ผ๋ 3๊ฐ์ง ์ข ๋ฅ์ ๊ฐ์ฒด๋ฅผ ๊ตฌํํ๋ค. ์ด์ ์ฐ๋ฆฌ๋ ์ด 3๊ฐ์ง ์ข ๋ฅ์ ๊ฐ์ฒด ์์คํ ์ ์ด์ฉํด์ ํ๊ฐํ๋ ํจ์ ์ฆ, Eval ํจ์๋ฅผ ๊ตฌํํด๋ณผ ์ฐจ๋ก๋ค. ๋จผ์ Eval ํจ์์ ์๊ทธ๋์ฒ๋ ๋ค์๊ณผ ๊ฐ์ด ์๊ฒผ๋ค.
func Eval(node ast.node) object.Object
์ธ์๋ node๋ก ์ฐ๋ฆฌ๊ฐ ์ด์ ์ ํ์๋ฅผ ๊ตฌํํ ๋ ๋ง๋ ast.Node ํ์ ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ๋ฐํํ๋ ํ์ ์ ์ฐ๋ฆฌ๊ฐ ์ ์ํ Object ํ์ ์ด๋ค. ์ฃผ๋ชฉํ ์ ์ ์ธ์๋ฅผ ast.Node ๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ์ถฉ์กฑ์ํค๋ ํ์ ์ด์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ด๋ก ์ธํด Eval ๋ฉ์๋๋ฅผ ์ฌ๊ท์ ์ผ๋ก ํธ์ถ ํ ์ ์๊ฒ ๋๋ค. ์ด๋ฌํ ์ ์ ์ฌ๋ฌ๊ฐ์ง ๋ช ๋ น๋ฌธ๋ค์ ์ฌ๋ฌ ๋ฒ์ ์ฌ๊ท์ ์ธ ํธ์ถ๋ก ๋ชจ๋ ํ๊ฐํ ์ ์์์ ์๋ฏธํ๋ค.
3-4-1. ์์ฒดํ๊ฐ ํํ์(1): ์ ์ ๋ฆฌํฐ๋ด
๊ฐ์ฅ ๋จผ์ ํด๋ณผ ํํ์ ํ๊ฐ๋ ์์ฒดํ๊ฐ ํํ์์ด๋ค. ์์ฒดํ๊ฐ ํํ์์ด๋, ํ๊ฐ๋ฅผ ํ๋ ๊ด์ ์์ ๋ฆฌํฐ๋ด์ ๋ถ๋ฅด๋ ๊ฒ์ด๋ค. ์ ์ ๋ฆฌํฐ๋ด, Bool ๋ฆฌํฐ๋ด ๋ฑ ๋ง์ด๋ค. ์๋ฅผ ๋ค๋ฉด, 5๋ฅผ ์ ๋ ฅํ๋ฉด 5๊ฐ ๋์ค๊ณ , true๋ฅผ ์ ๋ ฅํ๋ฉด true๊ฐ ๋์ค๋ ๊ฒ ๋ง์ด๋ค.
๋จผ์ ์ ์ ๋ฆฌํฐ๋ด์ด๋ผ๋ ์์ฒดํ๊ฐ ํํ์์ ํ๊ฐํ ์ ์๋๋ก ๊ตฌํํด๋ณด์. ๊ทธ์ ์์ ์ด๋ฅผ ์ํ ํ ์คํธ ์ฝ๋๋ถํฐ ์ดํด๋ณด์.
// evaluator/evaluator_test.go
package evaluator
import (
"monkey/lexer"
"monkey/object"
"monkey/parser"
"testing"
)
func TestEvalIntegerExpression(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"5", 5},
{"10", 10},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}
func testEval(input string) object.Object {
l := lexer.New(input)
p := parser.New(l)
program := p.ParseProgram()
return Eval(program)
}
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
result, ok := obj.(*object.Integer)
if !ok {
t.Errorf("object is not Integer. got=%T (%+v)", obj, obj)
return false
}
if result.Value != expected {
t.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected)
return false
}
return true
}
ํ ์คํธ ์ฝ๋ ์์ฒด๋ ๊ฐ๋จํ๋ค. ๋ฌธ์ 5๋ฅผ ์ ๋ ฅํ์ ๋, 5๋ก ํ๊ฐ๋๋๋ก ํ๊ณ , ๋ฌธ์ 10์ ์ ๋ ฅํ์ ๋, 10์ผ๋ก ํ๊ฐ๋๋๋ก ํ๋ ๊ฒ์ด๋ค. ๊ทธ๋ฆฌ๊ณ testEval ์ด๋ผ๋ ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ฐ, ์ด ๋ฉ์๋ ์์์๋ ๊ทธ๋์ ์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ ๋ ๋ฌธ์์ด ์์ค์ฝ๋๋ฅผ ์ ๋ ฅ ๋ฐ์ผ๋ฉด ๋ ์๋ฅผ ์์ฑํ๊ณ , ์ด ๋ ์๋ฅผ ์ธ์๋ก ์ฃผ์ด ํ์ฑ์ ์ํํ๋ค. ํ์ฑ์ด ์ํ๋ ๊ฒฐ๊ณผ์ธ AST ๋ ธ๋๋ค์ด program ๋ณ์์ ๋ด๊ฒจ์๊ณ , ์ด์ ๋ํ ์ค์ง์ ์ธ ํ๊ฐ๋ฅผ ์ํํ๋ Eval ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. ๋ฌผ๋ก ์์ง ์ฐ๋ฆฌ๋ Eval ๋ฉ์๋๋ฅผ ์ ์ํ์ง ์์๋ค. ์ด์ ์ ์ํด๋ณผ ์ฐจ๋ก๋ค.
// evaluator/evaluator.go
package evaluator
import (
"monkey/ast"
"monkey/object"
)
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
// ๋ช
๋ น๋ฌธ
case *ast.Program:
return evalStatements(node.Statements)
case *ast.ExpressionStatement:
return Eval(node.Expression)
// ํํ์
case *ast.IntegerLiteral:
return &object.Integer{Value: node.Value}
}
return nil
}
func evalStatements(stmts []ast.Statement) object.Object {
var result object.Object
for _, statement := range stmts {
result = Eval(statement)
}
return result
}
Eval ๋ฉ์๋์ ์ธ์๋ ํ์์ ์ํ ๊ฒฐ๊ณผ๋ฌผ์ธ AST ๋ ธ๋๋ผ๊ณ ํ๋ค. ๊ทธ๋ฌ๋ฏ๋ก ast.Node ์ธํฐํ์ด์ค ํ์ ์ด๋ค. switch ๊ตฌ๋ฌธ์๋ ํ์ ์ค์์น๋ฅผ ํ์ฉํด์ ast.Node์ ์ค์ ํ์ ์ ์ถ์ถํด ํ์ธํ๋ค. ์ค์ ํ์ ์ด ๋ง์ฝ ์ฌ๋ฌ๊ฐ์ง ๋ช ๋ น๋ฌธ๋ค๋ก ๊ตฌ์ฑ๋ ast.Program ํ์ ์ด๋ผ๋ฉด evalStatements ๋ผ๋ ์๋ก์ด ๋ฉ์๋๋ฅผ ๋ ํธ์ถํ๋ค. evalStatements ๋ฉ์๋์ ์๊น์๋ฅผ ๋ณด๋ฉด Eval ๋ฉ์๋๋ฅผ ์ฌ๊ท์ ์ผ๋ก ํธ์ถํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ๋ณต์กํ ๊ฒ์ ์๋๊ณ , ์ฌ๋ฌ ๋ช ๋ น๋ฌธ์ผ๋ก ๊ตฌ์ฑ๋ ๊ฒ์ loop๋ฅผ ๋๋ฉด์ ๋ช ๋ น๋ฌธ ํ๋์ฉ ํ๊ฐ๋ฅผ ์ํํ๋ ๊ฒ์ด๋ค. ์ฌ๊ท์ ์ผ๋ก ๊ณ์ ํธ์ถ๋๋ฉด์ ๊ฒฐ๊ตญ์๋ object.Object ์ธํฐํ์ด์ค ํ์ ์ ๋ฐ์ดํฐ๊ฐ ๋ฐํ๋๋ค.
์ฌ๋ฌ ๊ฐ์ ๋ช ๋ น๋ฌธ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ง ์๊ณ , ๋จ์ผ ๋ช ๋ น๋ฌธ์ผ ์๋ ์์ ๊ฒ์ด๋ค. ๊ทธ๋ด ๊ฒฝ์ฐ๋ ์ฆ์ Eval ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก ์ง๊ธ ์ฐ๋ฆฌ๊ฐ ์ง์คํ๊ณ ์๋ ์์ฒดํ๊ฐ ํํ์์ผ ๊ฒฝ์ฐ, ์ฆ์ ์ ์๋ฉด object.Integer ๋ผ๋ object.Object ์ธํฐํ์ด์ค๋ฅผ ์ถฉ์กฑํ๋ ํ์ ์ ๋ฐํํ๋ค.
์ด๋ ๊ฒ ํ ๋ค, ์๋ ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด ๋ฐฉ๊ธ ์์ฑํ ํ ์คํฌ ์ฝ๋๋ ํต๊ณผํ๋ค.
go test ./evaluator
์ฐ๋ฆฌ๋ ์ ์ ๋ฆฌํฐ๋ด์ด๋ผ๋ ์์ฒดํ๊ฐ ํํ์ ์ข ๋ฅ ์ค ํ๋๋ฅผ ํ๊ฐํ ์ ์๋๋ก ๊ตฌ์ฑํ๋ค. ๋ค๋ฅธ ์ข ๋ฅ์ ์์ฒดํ๊ฐ ํํ์๋ ํ๊ฐํ ์ ์๋๋ก ํด๋ณด๊ธฐ์ ์์, ์ฐ๋ฆฌ๊ฐ ๋ง๋ค์ด์จ REPL์ E(Evaluation) ๊ธฐ๋ฅ์ ๋ง์ ์ถ๊ฐํด๋ณด์. ์ด์ ๋ํ ์ฝ๋ ์ค๋ช ์ ์๋ตํ๊ฒ ๋ค.
// repl/repl.go
package repl
import (
"bufio"
"fmt"
"io"
"monkey/evaluator"
"monkey/lexer"
"monkey/parser"
)
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)
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(out, p.Errors())
continue
}
evaluated := evaluator.Eval(program)
if evaluated != nil {
io.WriteString(out, evaluated.Inspect())
io.WriteString(out, "\n")
}
}
}
const MONKEY_FACE = ` __,__
.--. .-" "-. .--.
/ .. \/ .-. .-. \/ .. \
| | '| / Y \ |' | |
| \ \ \ 0 | 0 / / / |
\ '- ,\.-"""""""-./, -' /
''-' /_ ^ ^ _\ '-''
| \._ _./ |
\ \ '~' / /
'._ '-=-' _.'
'-----'
`
func printParserErrors(out io.Writer, errors []string) {
io.WriteString(out, MONKEY_FACE)
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
io.WriteString(out, " parser errors:\n")
for _, msg := range errors {
io.WriteString(out, "\t"+msg+"\n")
}
}
3-4-2. ์์ฒดํ๊ฐ ํํ์(2): Bool ๋ฆฌํฐ๋ด
๋ค์์ผ๋ก ์ถ๊ฐํด๋ณผ ์์ฒดํ๊ฐ ํํ์์ true/false๋ฅผ ๋ํ๋ด๋ Bool ๋ฆฌํฐ๋ด์ด๋ค. ๋จผ์ ํ ์คํธ ์ฝ๋๋ถํฐ ์ดํด๋ณด์. ์ ์ ๋ฆฌํฐ๋ด ๋์ ํฌ๊ฒ ๋ฌ๋ผ์ง๋ ์ ์ ์๊ณ , Bool ํ์ ์ ํ ์คํธ ํ๊ธฐ ์ํ ์ฝ๋๋ก๋ง ์ด์ง ๋ณ๊ฒฝ๋ ๊ฒ ๋ฟ์ด๋ค.
// evaluator/evaluator_test.go
func TestEvalBooleanExpression(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"true", true},
{"false", false},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testBooleanObject(t, evaluated, tt.expected)
}
}
func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
result, ok := obj.(*object.Boolean)
if !ok {
t.Errorf("object is not Boolean. got=%T (%+v)", obj, obj)
return false
}
if result.Value != expected {
t.Errorf("object has wrong value. got=%t, want=%t", result.Value, expected)
return false
}
return true
}
Bool ๋ฆฌํฐ๋ด ์ด๋ผ๋ ์์ฒดํ๊ฐ ํํ์์ ํ๊ฐํ ์ ์๋๋ก ์ฐ๋ฆฌ๋ Eval ํจ์์ ๋ค์๊ณผ ๊ฐ์ด ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
// evaluator/evaluator.go
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
// ๋ช
๋ น๋ฌธ
case *ast.Program:
return evalStatements(node.Statements)
case *ast.ExpressionStatement:
return Eval(node.Expression)
// ํํ์
case *ast.IntegerLiteral:
return &object.Integer{Value: node.Value}
case *ast.Boolean: // Bool์ ๋ํ case ๋ฌธ ์ถ๊ฐ
return &object.Boolean{Value: node.Value}
}
return nil
๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ ํ ๊ฐ์ง ๊ฐ์ ํ ํฌ์ธํธ๊ฐ ์๋ค. ๋ฐ๋ก Bool ๊ฐ์ธ true/false๋ ์ธ์ ๋ ๋์ผํ๊ฒ true/false ๋ผ๋ ์ ์ด๋ค. ์ด์ ์ ์ถ๊ฐํ๋ ์ ์์ ๊ฒฝ์ฐ ์ ์๊ฐ 5์ผ๋๋, 10์ผ๋๋, 15์ผ ๋๋, 999์ผ ๋๋ ์กด์ฌํ๋ค. ์ฆ, ๋งค๋ฒ ์ ์์ ๊ฐ์ด ๋ฌ๋ผ์ง๋ค๋ ์ ์ด๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๋ ๋งค๋ฒ object.Integer ๋ผ๋ ๊ตฌ์กฐ์ฒด๋ฅผ ์๋ก ์์ฑํ๋ค. ํ์ง๋ง true/false์ ๊ฒฝ์ฐ๋ ์ธ์ ๋ ๋์ผํ true/false์ด๋ค. ๋ฐ๋ผ์ ์ ์์ค์ฝ๋์ฒ๋ผ ๋งค๋ฒ object.Boolean ์ด๋ผ๋ ๊ตฌ์กฐ์ฒด๋ฅผ ์๋ก ์์ฑํ ํ์๊ฐ ์๋ค๋ ๊ฒ์ด๋ค. ๋ค์ ๋งํด์, ๋งค๋ฒ ์๋ก์ด ๊ตฌ์กฐ์ฒด๋ฅผ ์์ฑํด ์ธ๋ฐ์๋ ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น๋ฅผ ํ ํ์๊ฐ ์๋ค๋ ๊ฒ์ด๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก, true/false์ ๋ํ ๊ตฌ์กฐ์ฒด๋ ๊ณ ์ ๋ ๊ฐ์ด๊ธฐ์ ์ ์ญ ๋ณ์๋ก ์ ์ํด๋๊ณ , ์ด๋ฅผ ๊ฐ์ ธ๋ค ์ฌ์ฉํด๋ ๋ ๊ฒ์ด๋ค. ์ด๋ก ์ธํด ๊ฐ์ ๋ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
// evaluator/evaluator.go
var (
TRUE = &object.Boolean{Value: true}
FALSE = &object.Boolean{Value: false}
)
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
// ๋ช
๋ น๋ฌธ
case *ast.Program:
return evalStatements(node.Statements)
case *ast.ExpressionStatement:
return Eval(node.Expression)
// ํํ์
case *ast.IntegerLiteral:
return &object.Integer{Value: node.Value}
case *ast.Boolean:
return nativeBoolToBooleanObject(node.Value) // ๊ฐ์ ๋ ๋ฐฉ์
}
return nil
func nativeBoolToBooleanObject(input bool) *object.Boolean {
if input {
return TRUE
}
return FALSE
}
3-4-3. ์์ฒดํ๊ฐ ํํ์(3): Null
๋ค์์ผ๋ก ์ถ๊ฐํด๋ณผ ์์ฒดํ๊ฐ ํํ์ ์ข ๋ฅ๋ก๋ Null ์ด๋ค. Null๋ ๋ง์ฐฌ๊ฐ์ง๋ก Bool ๊ฐ์ธ true/false ์ฒ๋ผ ์ฐธ์กฐ ๊ฐ๋ฅํ ๊ฐ์ฒด๋ฅผ ํ๋๋ง ๋ง๋ค์ด๋ ๋ฌด๋ฐฉํ๋ค. ๋ฐ๋ผ์ ์๋์ฒ๋ผ ์ ์ญ๋ณ์์ Null ์ฉ ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ญ์ ์ผ๋ก ์ ์ํ๊ณ case ๊ตฌ๋ฌธ์ ์ถ๊ฐํด์ค๋ค.
// evaluator/evaluator.go
var (
NULL = &object.Null{}
TRUE = &object.Boolean{Value: true}
FALSE = &object.Boolean{Value: false}
)
์, ์ด์ ์ง๊ธ๊น์ง ๋ง๋ ํ์ ๊ฐ์ฒด๋ค์ REPL ๊ธฐ๋ฅ์ ํ์ฉํด์ ๋ง๊ป ์ ๋ ฅํด๋ณด์. ์ฐ๋ฆฌ์ ์๋๋๋ก ํ๊ฐ๊น์ง ์ ๋์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ฐธ๊ณ ๋ก null ์ ์ ๋ ฅํ๋ฉด ์๋ฌด๊ฒ๋ ์ถ๋ ฅ๋์ง ์๋ ๊ฒ์ด ๋ง๋ค. ์ค์ ํ์ด์ฌ ์ธํฐํ๋ฆฌํฐ์์๋ null์ ์๋ฏธํ๋ None์ ์ ๋ ฅํ๋ฉด ์๋ฌด๊ฒ๋ ์ถ๋ ฅ๋์ง ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
go run main.go
3-4-4. ์ ์ ํํ์
๋ค์์ ์ ์ ํํ์์ ๋ํด์ ํ๊ฐํด๋ณด๋๋ก ํ์. Monkey ์ธ์ด์์ ์ง์์ด ๊ฐ์ผํ ์ ์ ํํ์์ ! ์ - ์ด๋ค. ์ฐ์ ์ ์ ํํ์์ด๋ ๋ฌด์์ธ์ง ๋ค์ ๋ณต์ตํด์กฐ๋ฉด ํผ์ฐ์ฐ์๊ฐ 1๊ฐ์ธ ์ฐ์ฐ์๋ฅผ ์๋ฏธํ๋ค. ์ฐ๋ฆฌ๋ ์ ์ ํํ์์ ์ ์ ํ ํ๊ฐํ๊ธฐ ์ํด์๋ ํผ์ฐ์ฐ์์ ๋ํด์ ๋จผ์ ํ๊ฐ๋ฅผ ์ํํ๊ณ ๋ ๋ค, ์ ์ ์ฐ์ฐ์๋ฅผ ํ๊ฐํ๊ธฐ ์ํด ์ง์ ์ ํผ์ฐ์ฐ์๋ฅผ ํ๊ฐํ ๊ฐ์ ์ฌ์ฉํ๊ฒ ๋๋ค.
๋จผ์ ์ ์ ํํ์์ ํ๊ฐํ๋ ํ ์คํธ ์ฝ๋๋ถํฐ ์์ฑํด๋ณด์.
// evaluator/evaluator_test.go
func TestBangOperator(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"!true", false},
{"!false", true},
{"!5", false},
{"!!true", true},
{"!!false", false},
{"!!5", true},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testBooleanObject(t, evaluated, tt.expected)
}
}
๊ทธ๋ฌ๋ฉด ์ด์ ์ ์ ํํ์์ ์ค์ง์ ์ผ๋ก ํ๊ฐํ๋๋ก ํ๋ ๋ก์ง์ ์์ฑํ ์ฐจ๋ก๋ค. ํ๊ฐ ๋ก์ง์ ์ํธ๋ฆฌ ํฌ์ธํธ ๋ฉ์๋ ๊ฒฉ์ธ Eval ๋ฉ์๋์ ์ ์ ํํ์์ ํ๊ฐํ๋๋ก switch-case ๊ตฌ๋ฌธ์ ์ถ๊ฐํด๋ณด๋๋ก ํ์.
// evaluator/evaluator.go
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
(.. ์๋ต ...)
// ํํ์
case *ast.IntegerLiteral:
return &object.Integer{Value: node.Value}
case *ast.Boolean:
return nativeBoolToBooleanObject(node.Value)
case *ast.PrefixExpression:
right := Eval(node.Right)
return evalPrefixExpression(node.Operator, right)
}
return nil
์ด์ ์ ํ์๋ฅผ ๊ตฌํํ ๋ ์ ์ํ AST ๋ ธ๋ ์ค ์ ์ ํํ์์ ํด๋นํ๋ ํ์ ์ด๋ผ๋ฉด ํด๋น ๋ถ๊ธฐ๋ฅผ ํ๋๋ก ํ๋ค. ๋ฐฉ๊ธ ์ ์ ํํ์์ ํ๊ฐํ๊ธฐ ์ํด์ ํผ์ฐ์ฐ์๋ถํฐ ํ๊ฐํ๋ค๊ณ ํ๋ค. ๊ทธ๋์ right := Eval(node.Right) ๋ผ๋ ์ฝ๋๋ฅผ ์ฌ์ฉํด ์ ์ ํํ์์ ์ค๋ฅธ์ชฝ์ ์์นํ ํผ์ฐ์ฐ์๋ฅผ ๋จผ์ ํ๊ฐํ๋๋ก ํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ ๋ค, evalPrefixExpression ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ์ ์ ์ฐ์ฐ์์ ๋ํ ํ๊ฐ๋ฅผ ์ํํ๋ค. ์ด์ evalPrefixExpression ๋ฉ์๋์ ๋ํด์ ์ดํด๋ณผ ์ฐจ๋ก๋ค.
// evaluator/evaluator.go
func evalPrefixExpression(operator string, right object.Object) object.Object {
switch operator {
case "!":
return evalBangOperatorExpression(right)
default:
return NULL
}
}
evalPrefixExpression ๋ฉ์๋์ ์ธ์ ์ค operator ์๋ ์ ์ ์ฐ์ฐ์๊ฐ ๋ด๊ฒจ ๋์ด์จ๋ค. ! ๋ - ๊ฐ ๋๊ฒ ๋ค. ์ฐ์ ! ์ฐ์ฐ์๋ถํฐ ํ๊ฐํด๋ณด์. operator๊ฐ ! ์ผ ๊ฒฝ์ฐ, ๋ค์ evalBangOperatorExpression ์ด๋ผ๋ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. ๋น์ฐํ evalBangOperatorExpression ๋ฉ์๋์ ์๊น์๋ฅผ ๋ด๋ณด์.
// evaluator/evaluator.go
func evalBangOperatorExpression(right object.Object) object.Object {
switch right {
case TRUE:
return FALSE
case FALSE:
return TRUE
case NULL:
return TRUE
default:
return FALSE
}
}
์ง๊ธ๊น์ง์ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์ฐ๋ฆฌ๋ TestBangOperator ๋ผ๋ ์ด๋ฆ์ ํ ์คํธ ์ฝ๋๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ํ๋๋ค. ๋์ผํ ๋ฐฉ์์ผ๋ก ์ด๋ฒ์ - ์ ์ ์ฐ์ฐ์์ ๋ํด์ ํ๊ฐ๋ฅผ ์ํํด๋ณด์. ์ด๋ฒ ํ ์คํธ ์ฝ๋๋ ๊ธฐ์กด์ ์์ฑํ๋ TestEvalIntegerExpression ์ด๋ผ๋ ํ ์คํธ ์ฝ๋์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ถ๊ฐํด๋ณด์.
// evaluator/evaluator_test.go
func TestEvalIntegerExpression(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"5", 5},
{"10", 10},
{"-5", -5}, // ์ ์ ์ฐ์ฐ์ ํ
์คํธ ์ผ์ด์ค(1) ์ถ๊ฐ
{"-10", -10}, // ์ ์ ์ฐ์ฐ์ ํ
์คํธ ์ผ์ด์ค(2) ์ถ๊ฐ
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}
์ถ๊ฐ๋ ํ ์คํธ ์ผ์ด์ค๋ฅผ ํต๊ณผ์ํค๋ ค๋ฉด ๋ฐฉ๊ธ ์์ฑํ๋ ๋ฉ์๋ ์ค evalPrefixExpression ๋ฉ์๋๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ํ์ฅํด์ผ ํ๋ค.
// evaluator/evaluator.go
func evalPrefixExpression(operator string, right object.Object) object.Object {
switch operator {
case "!":
return evalBangOperatorExpression(right)
case "-":
return evalMinusPrefixOperatorExpression(right)
default:
return NULL
}
}
๊ทธ๋ฆฌ๊ณ evalMinusPrefixOperatorExpression ๋ฉ์๋๋ผ๋ ๊ฒ์ด ์๋กญ๊ฒ ๋ฑ์ฅํ๋ค. ํด๋น ๋ฉ์๋์ ์๊ทธ๋์ฒ๋ก ๊ฐ๋ณด์.
// evaluator/evaluator.go
func evalMinusPrefixOperatorExpression(right object.Object) object.Object {
if right.Type() != object.INTEGER_OBJ {
return NULL
}
value := right.(*object.Integer).Value
return &object.Integer{Value: -value}
}
๋ก์ง ์์ฒด๋ ๊ฐ๋จํ๋ค. ๋จผ์ Type ์ด๋ผ๋ ๋ฉ์๋๋ก ์ ์ ๊ฐ์ฒด์ธ์ง ํ์ธํ๋ค. ์ ์ ๊ฐ์ฒด๊ฐ ์๋ ๊ฒฝ์ฐ ์ฆ์ NULL์ ๋ฐํํ๋๋ก ํ๊ณ , ์ ์ ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ์๋ type assertion์ ์ด์ฉํด์ object.Integer ๊ตฌ์กฐ์ฒด์ Value ๋ฉค๋ฒ๋ฅผ ์ถ์ถํ ํ -๋ฅผ ๋ถ์ฌ ๋ค์ object.Integer ๊ตฌ์กฐ์ฒด๋ฅผ ์๋กญ๊ฒ ์์ฑํด ๋ฆฌํดํด์ค๋ค.
3-4-5. ์ค์ ํํ์
๋ค์์ ์ค์ ํํ์์ ํ๊ฐํด๋ณด๋๋ก ํ์. ์ผ๋จ ์ง๊ธ ๋น์ฅ Monkey ์ธ์ด์์ ์ค์ ํํ์์ ํ๊ฐํ ์ ์๋ ์ค์ ํํ์ ์์ ๋ ํฌ๊ฒ ๋ค์๊ณผ ๊ฐ๋ค.
5 + 5;
5 - 5;
5 * 5;
5 / 5;
1 < 2;
1 > 2;
1 == 1;
1 != 1;
์ 4๊ฐ์ง๋ ๊ฒฐ๊ณผ๊ฐ์ด Boolean ๊ฐ์ด ์๋ ๊ฒ, ์๋ 4๊ฐ์ง๋ ๊ฒฐ๊ณผ๊ฐ์ด Boolean ๊ฐ์ผ๋ก ๋์ค๊ฒ ๋๋ค. ์ฐ์ ์๋ ๊ฒฐ๊ณผ๊ฐ์ด Boolean ๊ฐ์ด ์๋ ์ค์ ํํ์ ์ฆ, ์์ชฝ 4๊ฐ์ง์ ์ค์ ํํ์์ ํ๊ฐํ ์ ์๋๋ก ํด๋ณผ ๊ฒ์ด๋ค. ํ ์คํธ ์ฝ๋๋ ๋ฐฉ๊ธ ์ฌ์ฉํ๋ TestEvalIntegerExpression ์ด๋ผ๋ ํ ์คํธ ์ฝ๋ ๋ฉ์๋๋ฅผ ๋ ์ฌ์ฉํ ๊ฒ์ด๋ค.
// evaluator/evaluator.go
func TestEvalIntegerExpression(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"5", 5},
{"10", 10},
{"-5", -5},
{"-10", -10},
{"5 + 5 + 5 + 5 - 10", 10}, // ์๋กญ๊ฒ ์ถ๊ฐ๋ ์ผ์ด์ค๋ค
{"2 * 2 * 2 * 2 * 2", 32},
{"-50 + 100 + -50", 0},
{"5 * 2 + 10", 20},
{"5 + 2 * 10", 25},
{"20 + 2 * -10", 0},
{"50 / 2 * 2 + 10", 60},
{"2 * (5 + 10)", 30},
{"3 * 3 * 3 + 10", 37},
{"3 * (3 * 3) + 10", 37},
{"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
์์ ์ถ๊ฐ๋ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ปค๋ฒํ๊ธฐ ์ํด์ ๊ฐ์ฅ ๋จผ์ ํด์ผํ ๊ฒ์ ๋น์ฐํ๊ฒ๋ ํ๊ฐ ํ๋ก์ธ์ค์ ์ํธ๋ฆฌ ํฌ์ธํธ ๋ฉ์๋์ธ Eval ๋ฉ์๋๋ฅผ ํ์ฅํด์ผ ํ๋ค.
// evaluator/evaluator.go
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
// ํํ์
(... ์๋ต ...)
case *ast.PrefixExpression:
right := Eval(node.Right)
return evalPrefixExpression(node.Operator, right)
case *ast.InfixExpression:
left := Eval(node.Left)
right := Eval(node.Right)
return evalInfixExpression(node.Operator, left, right)
}
return nil
}
์ค์ ํํ์๋ ์ ์ ํํ์ ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ผ๋จ ๋จผ์ ํผ์ฐ์ฐ์๋ค์ ํ๊ฐํ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ Eval ๋ฉ์๋๋ฅผ ๊ฐ๊ฐ ํธ์ถํด ์ฐ์ฐ์์ ์ผ์ชฝ, ์ค๋ฅธ์ชฝ์ ์๋ ๊ฐ ํผ์ฐ์ฐ์๋ค์ ๋จผ์ ํ๊ฐํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ ๋ค์ evalInfixExpression ์ด๋ผ๋ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. evalInfixExpression ๋ฉ์๋๋ก ์ด๋ํด๋ณด์.
// evaluator/evaluator.go
func evalInfixExpression(operator string, left, right object.Object) object.Object {
switch {
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
return evalIntegerInfixExpression(operator, left, right)
default:
return NULL
}
}
๋จผ์ ์ผ์ชฝ, ์ค๋ฅธ์ชฝ ํผ์ฐ์ฐ์๋ค์ ํ์ ์ ๊ฐ๊ฐ ํ์ธํ๋ค. ํ์ฌ ์ฐ๋ฆฌ๋ ์ ์ ํ์ ๋ค ๊ฐ์ ์ค์ ํํ์์ ๋ํด์๋ง ํ๊ฐํ๊ณ ์์ผ๋ฏ๋ก ๋ ํผ์ฐ์ฐ์์ ํ์ ์ด ๋ชจ๋ ์ ์์ผ ๋๋ง evalIntegerInfixExpression ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. evalIntegerInfixExpression ๋ฉ์๋๋ก ๋ ์ด๋ํด๋ณด์.
// evaluator/evaluator.go
func evalIntegerInfixExpression(operator string, left, right object.Object) object.Object {
leftVal := left.(*object.Integer).Value
rightVal := right.(*object.Integer).Value
switch operator {
case "+":
return &object.Integer{Value: leftVal + rightVal}
case "-":
return &object.Integer{Value: leftVal - rightVal}
case "*":
return &object.Integer{Value: leftVal * rightVal}
case "/":
return &object.Integer{Value: leftVal / rightVal}
default:
return NULL
}
}
ํด๋น ๋ฉ์๋์์ ์ค์ง์ ์ธ ์ค์ ํํ์ ์ฐ์ฐ์ด ์ด๋ฃจ์ด์ง๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ๋จผ์ ํผ์ฐ์ฐ์ 2๊ฐ์ ์๋ ์ค์ง์ ์ธ ๊ฐ์ ์ถ์ถํ ๋ค, ์ฐ์ฐ์ ์ข ๋ฅ์ ๋ง๊ฒ ๊ณ์ฐ ํ ๋ค์ object.Integer ๊ตฌ์กฐ์ฒด๋ก ๋ง๋ค์ด ๋ฐํํด์ค๋ค.
์ด์ ๋๋จธ์ง 4๊ฐ์ ์ค์ ํํ์ ์ฆ, ๊ฒฐ๊ณผ๊ฐ์ด Boolean์ผ๋ก ๋์ค๋ ์ค์ ํํ์๋ค์ ๋ํด์๋ ํ๊ฐ๊ฐ ๊ฐ๋ฅํ๋๋ก ํด๋ณด์. ์ง๊ธ๊น์ง ๊ตฌํํ ๋ฉ์๋์ ํ์ฅ ๋ฐ์ ์๋ค. ์ผ๋จ ๊ธฐ์กด ํ ์คํธ ์ฝ๋ ๋ฉ์๋์ธ TestEvalBooleanExpression์ ์๋์ ๊ฐ์ด ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ถ๊ฐํด๋ณด์.
// evaluator/evaluator_test.go
func TestEvalBooleanExpression(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"true", true},
{"false", false},
{"1 < 2", true}, // ์๋ก ์ถ๊ฐ๋ ์ผ์ด์ค๋ค
{"1 > 2", false},
{"1 < 1", false},
{"1 > 1", false},
{"1 == 1", true},
{"1 != 1", false},
{"1 == 2", false},
{"1 != 2", true},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testBooleanObject(t, evaluated, tt.expected)
}
}
์ถ๊ฐ๋ ์ผ์ด์ค๋ฅผ ์ปค๋ฒํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ๋ฐฉ๊ธ ๊ตฌํํ๋ evalIntegerInfixExpression์ ๋ค์๊ณผ ๊ฐ์ด ํ์ฅํ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
// evaluator/evaluator.go
func evalIntegerInfixExpression(operator string, left, right object.Object) object.Object {
leftVal := left.(*object.Integer).Value
rightVal := right.(*object.Integer).Value
switch operator {
case "+":
return &object.Integer{Value: leftVal + rightVal}
case "-":
return &object.Integer{Value: leftVal - rightVal}
case "*":
return &object.Integer{Value: leftVal * rightVal}
case "/":
return &object.Integer{Value: leftVal / rightVal}
case "<":
return nativeBoolToBooleanObject(leftVal < rightVal)
case ">":
return nativeBoolToBooleanObject(leftVal > rightVal)
case "==":
return nativeBoolToBooleanObject(leftVal == rightVal)
case "!=":
return nativeBoolToBooleanObject(leftVal != rightVal)
default:
return NULL
}
}
๊ธฐ์กด์ Boolean ๋ฆฌํฐ๋ด์ ํ๊ฐํ ๋ ๋ง๋ค์ด๋์๋ nativeBoolToBooleanObject ๋ฉ์๋๋ฅผ ์ฌ์ฌ์ฉํ๋ฉด ๊ฐํธํ๊ฒ ๊ตฌํํ ์๊ฐ ์์ด์ง๋ค.
์ง๊ธ๊น์ง๋ ๋ชจ๋ ํผ์ฐ์ฐ์๊ฐ ์ ์์ผ ๋์ ์ค์ ํํ์์ด์๋ค. ์ฐ๋ฆฌ๊ฐ ์ถ๊ฐํด์ฃผ์ด์ผ ํ ๊ฒ์ ๋ฐ๋ก Boolean์ด ํผ์ฌ์ฐ์์ผ ๋์ ์ค์ ํํ์์ด๋ค. ๋ฐ๋ก ๋ค์๊ณผ ๊ฐ์ด ๋ง์ด๋ค.
true == true
false == false
true != false
true != true
Monkey ์ธ์ด์์๋ Boolean์ด ํผ์ฐ์ฐ์์ผ ๋์ ์ค์ ํํ์์ผ ๊ฒฝ์ฐ, == ์ != ์ฐ์ฐ์๋ง ์ง์์ด ๋๋ค. <, > ์ ๊ฐ์ ๋์๋น๊ต ์ฐ์ฐ์๋ ์ง์๋์ง ์๋๋ค. ๋ฐ๋ผ์ ์ง์์ด ๋๋ ํ ์คํธ ์ผ์ด์ค๋ฅผ ๋จผ์ ์ถ๊ฐํด๋ณด์.
// evaluator/evaluator_test.go
func TestEvalBooleanExpression(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"true", true},
{"false", false},
{"1 < 2", true},
{"1 > 2", false},
{"1 < 1", false},
{"1 > 1", false},
{"1 == 1", true},
{"1 != 1", false},
{"1 == 2", false},
{"1 != 2", true},
{"true == true", true},
{"false == false", true},
{"true == false", false},
{"true != false", true},
{"false != true", true},
{"(1 < 2) == true", true},
{"(1 < 2) == false", false},
{"(1 > 2) == true", false},
{"(1 > 2) == false", true},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testBooleanObject(t, evaluated, tt.expected)
}
}
์ด์ ์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ปค๋ฒํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ๋จ ๋ช ์ค๋ง evalInfixExpression ๋ฉ์๋์ ์ถ๊ฐ๋ง ํด์ฃผ๋ฉด ๋๋ค.
// evaluator/evaluator.go
func evalInfixExpression(operator string, left, right object.Object) object.Object {
switch {
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
return evalIntegerInfixExpression(operator, left, right)
case operator == "==":
return nativeBoolToBooleanObject(left == right)
case operator == "!":
return nativeBoolToBooleanObject(left != right)
default:
return NULL
}
}
์ฌ๊ธฐ์ ์ฐ๋ฆฌ๊ฐ ์ฃผ๋ชฉํ ์ ์ true, false์ ๊ฒฝ์ฐ ๋๋ฑ์ฑ์ ํฌ์ธํฐ ๋น๊ต๋ก ๊ฐ๋ฅํ๋ค๋ ์ ์ด๋ค. ์๋ํ๋ฉด true, false๋ ์ฐ๋ฆฌ๊ฐ ์ ์ญ๋ณ์๋ก ์ ์ํ ๊ฒ์ฒ๋ผ ์ธ์ ๋ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ๊ฐ๊ธฐ ๋๋ฌธ์ด๋ค. NULL๋ ๋ง์ฐฌ๊ฐ์ง๋ค. ํ์ง๋ง ๋น์ฐํ ์ ์๋ ๋ค๋ฅธ ๋ฐ์ดํฐ ํ์ ์ ๊ฒฝ์ฐ์๋ ์ด๋ฐ ๊ฒ์ด ์ ์ฉ๋์ง ์๋๋ค. ์๋ํ๋ฉด ์ ์๋ ๋งค๋ฒ ์์ฑ๋ ๋๋ง๋ค ์๋ก์ด ๋ฉ๋ชจ๋ฆฌ ์ฃผ์(ํฌ์ธํฐ ๋ณ์)๋ฅผ ๊ฐ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ ์ ์์ ๊ฐ์ด ํฌ์ธํฐ ๋ณ์๋ก ๋๋ฑ์ฑ ๋น๊ต๊ฐ ์๋๋ ๊ฒฝ์ฐ์ ๋๋นํ๊ธฐ ์ํด์ switch-case ๊ตฌ๋ฌธ ์ค์ ๊ฐ์ฅ ์ฒซ๋ฒ์งธ์ case ๊ตฌ๋ฌธ์ ์ผ์ชฝ, ์ค๋ฅธ์ชฝ ํผ์ฐ์ฐ์์ ํ์ ์ด ๋ชจ๋ ์ ์์ธ ๊ฒ์ ํ์ธํ ๋ค์ ํผ์ฐ์ฐ์๋ค์ด ๋ชจ๋ ์ ์์ผ ๋์ ์ค์ ํํ์์ ํ๊ฐํ๋ evalIntegerInfixExpression ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ก ํ ๊ฒ์ด๋ค.
3-4-6. ์กฐ๊ฑด์
์ด๋ฒ์ ํ๊ฐํด๋ณผ ํํ์์ ์กฐ๊ฑด์์ด๋ค.์กฐ๊ฑด์์ ๋ณดํต if ~ else ๊ตฌ๋ฌธ์ผ๋ก ๊ตฌ์ฑ๋์ด ์๋ค. ํต์ฌ์ ์ฌ๋ฐ๋ฅธ ๋ถ๊ธฐ๋ฅผ ์ด๋ป๊ฒ ํ๊ฐํ ๊ฒ์ด๋์ด๋ค. ์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ ์์ค์ฝ๋๊ฐ ์๋ค๊ณ ํด๋ณด์.
if (x > 10) {
print 'A'
} else {
print 'B'
}
์กฐ๊ฑด์์ x > 10์ด๋ค. ๊ทธ๋ฌ๋ฉด if ๊ตฌ๋ฌธ์ผ๋ก ๋ถ๊ธฐ์ํฌ ๊ฒฝ์ฐ๋ ์กฐ๊ฑด์์ด ์ฐธ์ผ ๊ฒฝ์ฐ์๋ง ํด์ผํ ๊น? ์๋๋ฉด ์กฐ๊ฑด์์ด ์ฐธ์ธ ๊ฒ '๊ฐ์' ๋์๋ ํด์ผํ ๊น? ์ฆ, ์ ๋ง ๋ง ๊ทธ๋๋ก ์กฐ๊ฑด์ x > 10์ ๊ฒฐ๊ณผ๊ฐ true์ผ ๋๋ง ๋ถ๊ธฐํด์ผ ํ๋์ง, ์๋๋ฉด x > 10์ ๊ฒฐ๊ณผ๊ฐ true๊ฐ ์๋์ง๋ง false๋ ์๋๊ณ null๋ ์๋ ๊ฒฝ์ฐ(์ด๋ฅผ truthy ํ๋ค๋ผ๊ณ ํจ)์๋ ๋ถ๊ธฐํด์ผ ํ ๊น? ๊ฒฐ๋ก ๋ถํฐ ๋งํ๋ฉด ์ฐ๋ฆฌ๊ฐ ๊ฐ๋ฐํ๊ณ ์๋ Monkey ์ธ์ด์์๋ ์กฐ๊ฑด์์ ๊ฒฐ๊ณผ๊ฐ truthy ํ ๊ฒฝ์ฐ๋ผ๋ฉด ๋ชจ๋ if ๋ถ๊ธฐ๋ฌธ์ ํ๋๋ก ํ ๊ฒ์ด๋ค.
์ด์ ์กฐ๊ฑด์์ ํ๊ฐํด๋ณด๋๋ก ํ์. ๋ค์ ์ฝ๋๋ ํ ์คํธ ์ฝ๋์ด๋ค.
// evaluator/evaluator_test.go
func TestIfElseExpression(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{"if (true) { 10 }", 10},
{"if (false) { 10 }", nil},
{"if (1) { 10 }", 10},
{"if (1 < 2) { 10 }", 10},
{"if (1 > 2) { 10 }", nil},
{"if (1 > 2) { 10 } else { 20 }", 20},
{"if (1 < 2) { 10 } else { 20 }", 10},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
}
}
func testNullObject(t *testing.T, obj object.Object) bool {
if obj != NULL {
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
return false
}
return true
}
๊ทธ๋ฐ๋ฐ ์ ํ ์คํธ ์ฝ๋์ ๋ช ํ ์คํธ ์ผ์ด์ค๋ฅผ ๋ณด๋ฉด ์ฝ๊ฐ ๋ค๋ฅธ ์กฐ๊ฑด์์ด ์๋ค. ๋ฐ๋ก else ๊ตฌ๋ฌธ์ด ์๋ ์ผ์ด์ค๋ค. ์กฐ๊ฑด์์ด ํญ์ if ์ else๋ก๋ง ์ด๋ฃจ์ด์ ธ ์์ผ๋ ๋ฒ์ ์๋ค. Monkey ์ธ์ด์์๋ else๊ฐ ์์ด if ๊ตฌ๋ฌธ๋ง ์์ด๋ ์กฐ๊ฑด์์ด ํ๊ฐ๋ ์ ์๋๋ก ์ง์ํ ๊ฒ์ด๋ค.
์ด์ ์กฐ๊ฑด์์ ์ค์ง์ ์ผ๋ก ํ๊ฐํ๋ ํจ์๋ฅผ ์์ฑํด๋ณด์. ๊ฐ์ฅ ๋จผ์ ํ๊ฐ์ ์ํธ๋ฆฌ ํฌ์ธํธ ๋ฉ์๋๊ฒฉ์ธ Eval ๋ฉ์๋์ case ๊ตฌ๋ฌธ์ ์ถ๊ฐํด์ฃผ์.
// evaluator/evaluator.go
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
// ๋ช
๋ น๋ฌธ
... (์๋ต) ...
// ํํ์
... (์๋ต) ...
case *ast.BlockStatement:
return evalStatements(node.Statements)
case *ast.IfExpression:
return evalIfExpression(node)
}
return nil
}
๊ทธ๋ฐ๋ฐ ๊ฐ์๊ธฐ BlockStatement๊ฐ ๋ฑ์ฅํ ๊ฒ์ด ์์ํ ์ ์๊ฒ ๋ค. ์ด๋ฅผ ์ดํดํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ์์ ์ ์กฐ๊ฑด์์ AST ๋ ธ๋๋ก ํ์ฑํ๊ธฐ ์ํด ์ ์ํ๋ IfExpression ๊ตฌ์กฐ์ฒด์ ํํ๋ฅผ ๋ค์ ํ๋ฒ ๋ด์ผ ํ๋ค.
// ast/ast.go
type IfExpression struct {
Token token.Token
Condition Expression
Consequence *BlockStatement
Alternative *BlockStatement
}
type BlockStatement struct {
Token token.Token
Statements []Statement
}
๊ทธ ๋ ๋น์ ์กฐ๊ฑด์์ ์กฐ๊ฑด์๊ณผ if ๋ถ๊ธฐ๋ฌธ์ ํด๋นํ๋ Consequence ๊ตฌ๋ฌธ, if ๋ถ๊ธฐ๋ฌธ๋ฅผ ํ์ง ์๋ ๋๋จธ์ง ๊ตฌ๋ฌธ์ Alternative ๊ตฌ๋ฌธ์ด๋ผ๊ณ ๋ถ๋ ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด Consequence, Alternative ๊ตฌ๋ฌธ์ ํ๋ ์ด์์ ๋ช ๋ น๋ฌธ์ผ๋ก ๊ตฌ์ฑ๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๋ BlockStatement ๋ผ๋ ๊ตฌ์กฐ์ฒด๋ก ์ฌ๋ฌ ๊ฐ์ ๋ช ๋ น๋ฌธ์ ๋ค๊ณ ์๋ ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ํ์๋ค. ๋ฐ๋ผ์ ํ๊ฐํ๋ Eval ๋ฉ์๋์ case ๊ตฌ๋ฌธ์ BlockStatement์ด ์ถ๊ฐ๋ ๊ฒ์ด๋ค.
์ด์ ๊ทธ๋ฌ๋ฉด evalIfExpression ๋ฉ์๋๋ฅผ ์ดํด๋ณผ ์ฐจ๋ก๋ค.
// evaluator/evaluator.go
func evalIfExpression(ie *ast.IfExpression) object.Object {
condition := Eval(ie.Condition)
if isTruthy(condition) {
return Eval(ie.Consequence)
} else if ie.Alternative != nil {
return Eval(ie.Alternative)
} else {
return NULL
}
}
๊ฐ์ฅ ๋จผ์ ์กฐ๊ฑด์์ ํ๊ฐํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ์กฐ๊ฑด์ ํ๊ฐ ๊ฒฐ๊ณผ๊ฐ Truthy ํ์ง ์ฒดํฌํ๊ณ Truthy ํ๋ค๊ณ ํ๋จ๋๋ฉด if ๋ถ๊ธฐ๋ฌธ์ธ Consequence ๊ตฌ๋ฌธ์ ํ๊ฐํ๋ค. ์ด ๋, else ๊ตฌ๋ฌธ์ด ์กด์ฌํ๋ ์ฆ, Alternative๊ฐ nil ์ด ์๋๋ผ๋ฉด else ๊ตฌ๋ฌธ์ ํ๊ฐํ๋๋ก ํ๋ค. Alternative๊ฐ nil ์ด๋ผ๋ ๊ฒ์ ๊ณง else ๊ตฌ๋ฌธ์ด ์ ์๋์ง ์์ if ๊ตฌ๋ฌธ๋ง ์๋ ์กฐ๊ฑด์์ด๋ผ๋ ๋ป์ด๊ธฐ ๋๋ฌธ์ ๊ทธ๋ฌํ ๊ฒฝ์ฐ ์ฆ์ NULL์ ๋ฐํํ๋ค.
๋ง์ง๋ง์ผ๋ก ์กฐ๊ฑด์ ํ๊ฐ ๊ฒฐ๊ณผ๊ฐ Truthy์ธ์ง ์ฒดํฌํ๋ isTruthy ๋ฉ์๋๋ง ์ดํด๋ณด๊ณ ์กฐ๊ฑด์ ํ๊ฐ๋ฅผ ๋ง๋ฌด๋ฆฌํ์.
// evaluator/evaluator.go
func isTruthy(obj object.Object) bool {
switch obj {
case NULL:
return false
case TRUE:
return true
case FALSE:
return false
default:
return true
}
}
์๊น ์์์ Truthy ํ๋ค๋ผ๋ ๊ฒ์ด false๋ null๋ ์๋ ๊ฐ์ด๋ผ๊ณ ํ๋ค. ๋ฐ๋ผ์ true์ผ ๋๋ฅผ ํฌํจํด์ false, null์ด ์๋ ๊ฒฝ์ฐ๋ฅผ ์๋ฏธํ๋ค.
3-4-7. Return ๋ฌธ
๋ค์์ผ๋ก ํ๊ฐํด๋ณผ ํํ์์ Return ๋ฌธ์ด๋ค. ์ฐ๋ฆฌ๊ฐ ํํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ์ฌ์ฉํด์ ํจ์๋ฅผ ์์ฑํ๊ฒ ๋๋ฉด ๋ฐ๋์ return ๋ฌธ์ ์ฌ์ฉํด์ ํด๋น ํจ์๊ฐ ํน์ ๊ฐ์ ๋ฐํํ๋๋ก ํ๊ณค ํ๋ค. ๋ฐ๋ก ๊ทธ return ๋ฌธ์ ์ฐ๋ฆฌ๋ ํ๊ฐํด๋ณผ ๊ฒ์ด๋ค. ์ฐ์ return ๋ฌธ์ ์ผ๋ จ์ ๋ช ๋ น๋ฌธ๋ค์ ํ๊ฐํ๋ ๊ฒ์ ๋ฉ์ถ๊ณ , ๋ช ๋ น๋ฌธ ์์ ํฌํจ๋ ํํ์์ ๋จ๊ฒจ๋๊ณ ๋์๋ฒ๋ฆฐ๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ ์์ค์ฝ๋๊ฐ ์๋ค๊ณ ํด๋ณด์.
5 * 5;
return 10;
10 + 1;
์ ์์ค์ฝ๋๋ฅผ ํ๊ฐํ ๊ฒฝ์ฐ, 10 + 1 ์ด๋ผ๋ ์ฝ๋๋ ์ ๋ ํ๊ฐ๋์ง ์๋๋ค. ์๋ํ๋ฉด ๊ทธ ์ ์ return ๋ฌธ์ ๋ง๋์ ํ๊ฐ๊ฐ ์ค๋จ๋๊ธฐ ๋๋ฌธ์ด๋ค. return ๋ฌธ์ ๊ตฌํํ๋ ๋ฐ์๋ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์กด์ฌํ๋ค. Golang์์์ goto ๊ตฌ๋ฌธ์ด๋ ์ผ๋ฐ์ ์ธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ ์ฌ์ฉํ๋ ์์ธ(Exception)๋ฅผ ์ฌ์ฉํ๋ค. ํ์ฌ ์ฐ๋ฆฌ๋ ๊ฒ์คํธ ์ธ์ด์ธ Monkey ์ธ์ด๋ฅผ ํธ์คํธ ์ธ์ด์ธ Golang์ผ๋ก ๊ตฌํํ๋ ค๊ณ ํ๋ ์ํฉ์ด๋ค. Golang ์์๋ catch ์ ๊ฐ์ ํค์๋๋ฅผ ์ง์ํ์ง๋ ์๊ณ , goto ๋ฅผ ์ฌ์ฉํด์ ๊น๋ํ๊ฒ ๊ฐ๋ฐํ ์ ์๋ ๋ฐฉ์๋ ๊ฐ๊ณ ์์ง ์๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ๋ return ๋ฌธ์ ๊ตฌํํ๊ธฐ ์ํด ๋จ์ํ ๋ฐํ๊ฐ์ ํ๊ฐ๊ธฐ์ ๋๊ธธ ๊ฒ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ํ๊ฐ ๋์ค return ๋ฌธ์ ๋ง๋ฌ์ ๋ ์ค์ ๋ฐํํด์ผ ํ ๊ฐ์ return ๋ฌธ ์ ์ฉ ๊ฐ์ฒด ํ๋๋ก ๊ฐ์ธ์ ์ฒ๋ฆฌํ ๊ฒ์ด๋ค. ์ด๋ ๊ฒ ํ๋ ์ด์ ๋ ๊ทธ๋์ผ ํ๊ฐ๊ธฐ๊ฐ ์ด return ์ ์ฉ ๊ฐ์ฒด๋ฅผ ๊ณ์ ์ถ์ ํ ์ ์๊ฒ ๋๊ณ , ์ถ์ฒ์ด ๊ฐ๋ฅํ๋ฉด ํ๊ฐ ๋์ค์ ํ๊ฐ๋ฅผ ๊ณ์ํ ์ง ๋ง์ง ๊ฒฐ์ ํ ์ ์๋ ๊ธฐ์ค์ด ๋๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ ๊ฐ์ฅ ๋จผ์ ํ ์ผ์ ์ค์ ๋ฐํํด์ผ ํ ๊ฐ์ return ๋ฌธ ์ ์ฉ ๊ฐ์ฒด๋ฅผ ์ ์ํ๋ ๊ฒ์ด๋ค. ์ฝ๋๋ฅผ ์ดํด๋ณด์.
// object/object.go
package object
const (
... (์๋ต) ...
RETURN_VALUE_OBJ = "RETURN_VALUE"
)
type ReturnValue struct {
Value Object
}
func (r *ReturnValue) Inspect() string { return r.Value.Inspect() }
func (r *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ }
ReturnValue ๋ผ๋ ์๋ก์ด ๊ตฌ์กฐ์ฒด๋ Value ๋ผ๋ ๋ฉค๋ฒ๋ง์ ๊ฐ๋๋ฐ, ์ด Value ํ์ ์ด Object์ธ ๊ฒ์ ๋ณผ ์ ์๋ค. ๊ฒฐ๊ตญ, ReturnValue๋ ํน๋ณํ๊ฒ ์๋กญ๊ฒ ๋ง๋ค์ด์ง ๊ฐ์ฒด๋ผ๊ธฐ ๋ณด๋ค๋ ๊ธฐ์กด์ ์ฐ๋ฆฌ๊ฐ ์ ์ํ๋ Integer, Boolean, Null ๋ฑ๊ณผ ๊ฐ์ Object๋ฅผ ํ๋ฒ ๊ฐ์ผ ๊ฐ์ฒด์ ๋ถ๊ณผํ๋ค๋ ๊ฒ์ด๋ค.
์ด์ ๋ค์์ผ๋ก๋ ํ ์คํธ ์ฝ๋๋ฅผ ์ดํด๋ณด์.
// evaluator/evaluator_test.go
func TestReturnStatements(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"return 10;", 10},
{"return 10; 9;", 10},
{"return 2 * 5; 9;", 10},
{"9; return 2 * 5; 9;", 10},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}
์ฃผ๋ก return ๋ฌธ์ ๋ง๋ฌ์ ๋ return ๋ค์ ์๋ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐํํ๋์ง ๊ฒ์ฆํ๋ ๊ฐ๋จํ ํ ์คํธ ์ฝ๋๋ค. ์ด์ ์ด ํ ์คํธ ์ฝ๋๋ฅผ ํต๊ณผ์ํฌ ์ ์๋๋ก ํ๊ธฐ ์ํด์ ์์ค์ฝ๋๋ฅผ ์์ฑํด๋ณด์. ๋จผ์ Eval ๋ฉ์๋์ return ๋ฌธ์ ๋ํ case ๋ฌธ์ ์ถ๊ฐํ์.
// evaluator/evaluator.go
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
// ๋ช
๋ น๋ฌธ
... (์๋ต) ...
// ํํ์
... (์๋ต) ...
case *ast.ReturnStatement:
val := Eval(node.ReturnValue)
return &object.ReturnValue{Value: val}
}
return nil
๋ก์ง ์์ฒด๋ ๊ฐ๋จํ๋ค. return ๋ฌธ์ด ๋ฑ์ฅํ ๊ฒฝ์ฐ, ReturnStatement ๊ตฌ์กฐ์ฒด์ ReturnValue ๋ฉค๋ฒ๋ฅผ ํ๊ฐํ๋๋ก ํ๋ค. ๊ทธ๋ฐ ๋ค์ ๋ฐฉ๊ธ ์ ์ํ๋ object.ReturnValue ๋ผ๋ ์๋ก์ด ๊ตฌ์กฐ์ฒด์ ๋ค์ ํ๊ฐํ ๊ฒฐ๊ณผ ๊ฐ์ ๋ค์ ๋ด๋๋ค. ๋ค๋ง ์ฌ๊ธฐ์ ๋์ด ์๋๋ค. ์ฐ๋ฆฌ๋ evalStatements ๋ผ๋ ๋ฉ์๋์๋ ๋ณ๊ฒฝ์ฌํญ์ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ค. ์๋ํ๋ฉด ํ์ฌ ํ๊ฐ๊ธฐ๋ AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ํํ๋ฉด์ Eval ๋ฉ์๋๋ฅผ ์ฌ๊ท์ ์ผ๋ก ํธ์ถํ๊ณ ์๋ ๊ตฌ์กฐ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฐ๋ผ์ evalStatements ๋ฉ์๋์ ๋ค์๊ณผ ๊ฐ์ ๋ก์ง์ ์ถ๊ฐํด์ฃผ์.
// evaluator/evaluator.go
func evalStatements(stmts []ast.Statement) object.Object {
var result object.Object
for _, statement := range stmts {
result = Eval(statement)
if resultValue, ok := result.(*object.ReturnValue); ok {
return resultValue.Value
}
}
return result
}
๋ฐ๋ก, object.ReturnValue ๋ก type assertion์ ์ํํด์ ํด๋น ๋ฉ์๋์์ ์ค์ง์ ์ผ๋ก ๋ฐํํ ๋ฐ์ดํฐ ์ฆ, object.ReturnValue ๊ตฌ์กฐ์ฒด์ Value ๋ฉค๋ฒ์ ์๋ ๋ฐ์ดํฐ ๊ฐ์ ๋ฐํํ๋ ๊ฒ์ด๋ค. ์ด๋ฐ ๋ณ๊ฒฝ์ฌํญ์ด ๋ฐ์๋จ์ ๋ฐ๋ผ ์ฐ๋ฆฌ๋ ์ธํฐํ๋ฆฌํฐ์์ ์์ค์ฝ๋๋ฅผ ์ ๋ ฅํ๊ณ ํ๊ฐ๋์ด ๋ฐํ๋๋ ๊ฒฐ๊ณผ๊ฐ Monkey ์ธ์ด์ ๊ตฌํ์ฒด๊ฐ ์๋ ์ค์ ๋ฐ์ดํฐ๋ง ๋ฐํ๋๋ ๊ฒ์ด๋ค. ์ผ๋ก๋ก, ์ฐ๋ฆฌ๊ฐ python ์ธํฐํ๋ฆฌํฐ์๋ค๊ฐ 3 + 3์ ์ ๋ ฅํ์ ๋ 6๋ง ๋ฐํ๋๋ ๊ฒ์ฒ๋ผ ๋ง์ด๋ค.
๊ทธ๋ฐ๋ฐ ์์ ๊ฐ์ evalStatements ๋ฉ์๋ ๋ก์ง์ ํ๊ณ์ ์ด ์กด์ฌํ๋ค. ๋ฐ๋ก ์ค์ฒฉ ๊ตฌ๋ฌธ์ด ์กด์ฌํ ๋์ด๋ค. ์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ ํ ์คํธ ์ผ์ด์ค๊ฐ ์ถ๊ฐ๋๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
// evaluator/evaluator_test.go
func TestReturnStatements(t *testing.T) {
tests := []struct {
input string
expected int64
}{
... (์๋ต) ...
{`if (10 > 1) {
if (10 > 1) {
return 10;
}
return 1;
}`, 10},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
}
}
ํ ์คํธ ์ฝ๋ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๋ฅผ ๋ด๋ฑ๋๋ค.
--- FAIL: TestReturnStatements (0.00s)
evaluator_test.go:51: object has wrong value. got=1, want=10
FAIL
FAIL monkey/evaluator 0.444s
FAIL
๋ฉ์ธ์ง๋ฅผ ๋ณด๋ฉด 10์ return ํด์ผ ํ๋๋ฐ, 1์ return ํ๋ค. ์ฆ, ์ค์ฒฉ๋ if ๋ถ๊ธฐ๋ฌธ์ ํ์ 10์ ๋ฐํํ์ด์ผ ํ์์๋ return 1; ์ด ํ๊ฐ๋ ๊ฒ์ด๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ evalStatements ๋ฉ์๋๋ฅผ 2๊ฐ๋ก ๋ถ๋ฆฌํ๋๋ก ํ์. ๋จผ์ evalProgram ์ด๋ผ๋ ๋ฉ์๋๋ฅผ ์๋กญ๊ฒ ์ ์ํ๋๋ฐ, ์ด ๋ฉ์๋๋ evalStatements ๋ฉ์๋์ ๋ก์ง์ ๋์ผํ๋ค.
// evaluator/evaluator.go
func evalProgram(stmts []ast.Statement) object.Object {
var result object.Object
for _, statement := range stmts {
result = Eval(statement)
if resultValue, ok := result.(*object.ReturnValue); ok {
return resultValue.Value
}
}
return result
}
func evalBlockStatement(stmts []ast.Statement) object.Object {
var result object.Object
for _, statement := range stmts {
result = Eval(statement)
if result != nil && result.Type() == object.RETURN_VALUE_OBJ {
return result
}
}
return result
}
์ฐ๋ฆฌ๊ฐ ์ฃผ๋ชฉํ ๋ฉ์๋๋ evalBlockStatment ๋ฉ์๋๋ค. ๋ฐ๋ก ์ด ๋ฉ์๋๊ฐ ์ค์ฒฉ๋ ๊ตฌ๋ฌธ์ด ๋ฑ์ฅํด๋ return ๊ตฌ๋ฌธ์ ์ ์ฒ๋ฆฌํ ์ ์๋๋ก ํด์ค๋ค. ๋ณ๊ฒฝ๋ ๋ถ๋ถ์ for-loop ๊ตฌ๋ฌธ ๋ด์ if ๋ถ๊ธฐ๋ฌธ์ด๋ค. ๋ช ๋ น๋ฌธ์ ํ์ฑํ ๊ฒฐ๊ณผ์ธ result ๋ณ์๊ฐ nil ์ด ์๋๋ฉด์ Type() ๋ฉ์๋๋ฅผ ํธ์ถํ์ ๋ ํ์ ๊ฐ์ด RETURN_VALUE_OBJ ๋ผ๋ฉด object.ReturnValue ๊ตฌ์กฐ์ฒด๋ฅผ ํ์ด๋ด์ง ์๊ณ ํด๋น ๊ตฌ์กฐ์ฒด ํํ ๊ทธ๋๋ก ๋ฆฌํดํ๋๋ก ํ๋ค. ์ด๋ ๊ฒ ํ๊ฒ ๋๋ฉด ๋ฐ๊นฅ์ชฝ ๋ธ๋ก๋ฌธ ์ด๋๊ฐ์์ ์คํ์ ๋ฉ์ถ๊ฒ ๋๊ณ , evalProgram ๋ฉ์๋๊น์ง ์คํ๋๋ ๋จ๊ณ์ ๊ฐ์์ผ object.ReturnValue ๊ตฌ์กฐ์ฒด์ Value ๊ฐ์ ํ์ด๋ด์ด ๋ฐํํ๊ฒ ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ถ๋ฆฌ์ํจ ๋ฉ์๋ evalProgram, evalBlockStatement ๋ฉ์๋๋ฅผ Eval ๋ฉ์๋ ๋ด์ case ๋ฌธ์ ์ ์ ํ ๊ต์ฒดํด์ค๋ค.
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
// ๋ช
๋ น๋ฌธ
case *ast.Program:
return evalProgram(node.Statements)
... (์๋ต) ...
// ํํ์
... (์๋ต) ...
case *ast.BlockStatement:
return evalBlockStatement(node.Statements)
... (์๋ต) ...
}
return nil
}
3-5. ์๋ฌ ์ฒ๋ฆฌ
๋ค์์ผ๋ก ํด๋ณผ ๊ฒ์ ์๋ฌ ์ฒ๋ฆฌ์ด๋ค. ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ์ ํ๋ง๋ค ๋ด๋ถ์ ์ผ๋ก ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ตฌํ์ฒด๊ฐ ์กด์ฌํ๋ค. python์์๋ Exception ๊ฐ์ฒด๊ฐ ๋ ๊ฒ ๊ฐ๋ค. python์์์ Exception ๊ฐ์ฒด์ฒ๋ผ ์ฐ๋ฆฌ๋ Monkey ์ธ์ด์์์ Exception ๊ฐ์ฒด์ ๊ฐ์ ๊ฒ์ ๊ตฌํํด ๋ณผ ๊ฒ์ด๋ค. ๋ฐ๋ก ์ด๋ฅผ ๋ด๋ถ ์๋ฌ ์ฒ๋ฆฌ(internal error handling) ์ด๋ผ๊ณ ๋ ํ๋ค. ์ด๋ฐ ๋ด๋ถ ์๋ฌ๋, ์๋ชป๋ ์ฐ์ฐ์๋ฅผ ์ฐ๊ฑฐ๋ ์ง์๋์ง ์๋ ์ฐ์ฐ์ ํ๋ค๊ฑฐ๋ ๋๋ ์ฌ์ฉ์๊ฐ ํ๋ก๊ทธ๋จ ์ค๊ฐ์ Ctrl + c(ํค๋ณด๋ ์ธํฐ๋ฝํธ)๋ฅผ ๋๋ฌ ํ๋ก๊ทธ๋จ์ ์ธ์์ ์ผ๋ก ์ค๋จ์ํค๊ฑฐ๋ ํ๋ ๋ฑ ๊ทธ ๋ฐ์ ์คํ ์ค์ ์ผ์ด๋ ์ ์๋ ์ฌ์ฉ์ ๋๋ ๋ด๋ถ ์๋ฌ๋ฅผ ์๋ฏธํ๋ค.
์๋ฌ ์ฒ๋ฆฌ๋ ์ง์ ์ ์ดํด๋ณธ return ๋ฌธ ์ฒ๋ฆฌ ๋ฐฉ์๊ณผ ๊ฑฐ์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๊ตฌํํ ์ ์๋ค. ์๋ํ๋ฉด ์๋ฌ ์ฒ๋ฆฌ๋ return ๋ฌธ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์ผ๋ จ์ ๋ช ๋ น๋ฌธ์ ํ๊ฐํ๋ค๊ฐ ๋ง์ฃผํ๋ฉด ๋์ค์ ํ๊ฐ๋ฅผ ๋ฉ์ถ๊ฒ ํด์ผ ํ๋ค๋ ์ ์ด ๊ณตํต์ ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
์ผ๋จ ์ฐ๋ฆฌ๋ง์ ์๋ฌ ๊ตฌํ์ฒด๋ฅผ ๋ง๋ค๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด ์๋ฌ ๊ฐ์ฒด๋ฅผ ์ ์ํด๋ณด์.
// object/object.go
const (
... (์๋ต) ...
ERROR_OBJ = "ERROR"
)
type Error struct {
Message string
}
func (e *Error) Inspect() string { return "ERROR: " + e.Message }
func (e *Error) Type() ObjectType { return ERROR_OBJ }
์๋ฌ ๊ฐ์ฒด๋ ๊ทธ๋์ ์ฐ๋ฆฌ๊ฐ ์ ์ํด์๋ ๋ค์ํ ํ์ ์ ๊ฐ์ฒด์ ๋น์ทํ๊ฒ ์ ์ํ๋ค. ๋์ผํ๊ฒ Inspect, Type ๋ฉ์๋๋ฅผ ๊ตฌํํ์ฌ Object ๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ์ถฉ์กฑ์ํค๋๋ก ํ๋ค.
๋ฌผ๋ก ์์ฉ ์ธํฐํ๋ฆฌํฐ๋ฅผ ๋ชฉํ๋ก ํ๋ ๊ฒฝ์ฐ, ์์ ๊ฐ์ ์๋ฌ ์ฒ๋ฆฌ์์ ์คํ ํธ๋ ์ด์ค๋ฅผ ๋ฌ์๋ณผ ์๋ ์๋ค. ์ฆ, ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ง์ ์ ํ๊ณผ ์ด ๋ฒํธ๋ฅผ ๊ฐ์ด ๋ฃ์ด์ ๋จ์ํ ์๋ฌ ๋ฉ์ธ์ง๋ณด๋ค ๋ ๋ง์ ์ ๋ณด๋ฅผ ์ค ์ ์๋๋ก ํ ์ ์๋ค. ํ์ง๋ง ์ด๋ฅผ ํ๊ธฐ ์ํด์๋ ํ๊ฐ ์ด์ ์ ์ ๋ ฅ ์์ค์ฝ๋ ๋ฌธ์์ด์ ํ ํฐํ์ํค๋ ๋ ์๊ฐ ํ ํฐ์ ํ๊ณผ ์ด ๋ฒํธ๋ฅผ ๋ฌ์๋์์ด์ผ ๊ตฌํ์ด ์ฌ์์ง๋ค. ์ฌ์ค ์ฐ๋ฆฌ๊ฐ ๊ตฌํํด์จ ๋ ์๋ ํ ํฐ์ ํ๊ณผ ์ด ๋ฒํธ๋ฅผ ๋ฌ์๋๋๋ก ํ์ง๋ ์์๋ค. ๊ทธ๋ฌ๋ฏ๋ก ์ฐ๋ฆฌ ์์ ์์๋ ๊ตฌํํ์ง๋ ์์ง๋ง, ์ผ๋ฐ ์์ฉ ์ธํฐํ๋ฆฌํฐ์ ๊ฒฝ์ฐ ์ด๋ฐ ๊ณผ์ ์ด ์๋ฐ๋์์์ ์๊ณ ์๋ ๊ฒ๋ ์ธ์ ๊ฐ๋ ๋์์ด ๋ ๋ฏ ํ๋ค.
์๋ฌ ์ฒ๋ฆฌ๋ ๋๊ฐ์ด ํ ์คํธ ์ฝ๋๋ถํฐ ์์ฑํด๋ณด์.
// evaluator/evaluator_test.go
func TestErrorHandling(t *testing.T) {
tests := []struct {
input string
expectedMessage string
}{
{
"5 + true;",
"type mismatch: INTEGER + BOOLEAN",
},
{
"5 + true; 5;",
"type mismatch: INTEGER + BOOLEAN",
},
{
"-true",
"unknown operator: -BOOLEAN",
},
{
"true + false;",
"unknown operator: BOOLEAN + BOOLEAN",
},
{
"5; true + false; 5",
"unknown operator: BOOLEAN + BOOLEAN",
},
{
"if (10 > 1) { true + false; }",
"unknown operator: BOOLEAN + BOOLEAN",
},
{
`
if (10 > 1) {
if (10 > 1) {
return true + false;
}
return 1;
}
`,
"unknown operator: BOOLEAN + BOOLEAN",
},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Errorf("no error object returned. got=%T (%+v)", evaluated, evaluated)
continue
}
if errObj.Message != tt.expectedMessage {
t.Errorf("error object has wrong message. got=%q, want=%q", errObj.Message, tt.expectedMessage)
}
}
}
์ ์์ค์ฝ๋ ์ ์ฌ๋ฌ๊ฐ์ง ํ ์คํธ ์ผ์ด์ค๋ ํฌ๊ฒ 2๊ฐ์ง ๋์์ ์๋ํ๋์ง๋ฅผ ๊ฒ์ฆํ๋ค. ์ฒซ๋ฒ์งธ๋ก ์๋ฌ๋ฅผ ๋ง๋ค์ด๋ด๋ ๋์, ๋๋ฒ์งธ๋ ํ๊ฐ๋ฅผ ์ค๋จํ๋ ๋์์ด๋ค.
์ด์ ์ค์ ํ๊ฐ ์ฝ๋ ๋ด๋ถ์์ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ํด๋ณผ ๊ฒ์ด๋ค. ๊ทธ ์ ์ ์์ ์ฐ๋ฆฌ๋ง์ ์๋ฌ๋ฅผ ๋ง๋ค์ด๋ณด์. ๊ตฌํ์ ๊ฐ๋จํ๋ค. ์์์ ์ ์ํ ์ฐ๋ฆฌ๋ง์ ์๋ฌ ๊ฐ์ฒด๋ฅผ ํ์ฉํ๋ฉด ๋๋ค.
// evaluator/evaluator.go
func newError(format string, a ...interface{}) *object.Error {
return &object.Error{Message: fmt.Sprintf(format, a...)}
}
์ฐธ๊ณ ๋ก ์ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ ์๊ฒ ๋ Golang ๋ฌธ๋ฒ์ ๋ํด ์๊ฐํ๋ค. ์ฐ์ ... ์ด๋ผ๋ ๊ฒ์ ํฌ๊ฒ 2๊ฐ์ง ๊ธฐ๋ฅ์ ํ๋๋ฐ, ํจ์ ์๊ทธ๋์ฒ์์๋ ๊ฐ๋ณ ์ธ์๋ฅผ ๋ปํ๋ค. python์์ *args, *kwargs ๋ผ๋ ํค์๋๋ก ์ฃผ๋ก ์ฐ์ด๋ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํ๋ฉด ๋๋ค. ๊ทธ๋ฆฌ๊ณ return ๊ตฌ๋ฌธ์์ ๋ ... ์ด ๋ฑ์ฅํ๋๋ฐ, ์ด๋ ๊ฐ๋ณ์ธ์๋ก ๋ฐ์ ๊ฒ์ ๊ทธ๋๋ก ๋ถํด ์ฆ, unpacking์ ์ํํ๋ ๊ฒ์ด๋ค. ์ ์์์์๋ a ์ธ์๊ฐ ๊ฐ๋ณ์ธ์๋ก ๋ค์ด์ค๊ธด ํ์ง๋ง ๋ง์ฝ a ์ธ์๊ฐ ์ฌ๋ผ์ด์ค๋ก ๋ฐ๋๋ค๊ณ ํ์ ๋, a... ๋ผ๊ณ ๋ช ์ํ๋ฉด ์ฌ๋ผ์ด์ค a๋ ์์๋ค์ unpacking ํ๊ฒ ๋๋ค.
๊ธฐ์กด๊น์ง ์ฐ๋ฆฌ๋ ๋ฌธ์ ๊ฐ ๋๋ ์ง์ ์ ๊ทธ๋ฅ ๋จ์ํ NULL์ ๋ฆฌํดํ๋๋ก ๋์ด ์๋ค. ์ด์ ์ด๋ ๊ฒ ํ์ง ๋ง๊ณ , ๋ฐฉ๊ธ ์ ์ํ newError ๋ฉ์๋๋ก ์๋ฌ๋ฅผ ํธ๋ค๋ง ํด๋ณด๋๋ก ํ์.
// evaluator/evaluator.go
func evalPrefixExpression(operator string, right object.Object) object.Object {
switch operator {
case "!":
return evalBangOperatorExpression(right)
case "-":
return evalMinusPrefixOperatorExpression(right)
default:
return newError("unknown operator: %s%s", operator, right.Type())
}
}
func evalInfixExpression(operator string, left, right object.Object) object.Object {
switch {
case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
return evalIntegerInfixExpression(operator, left, right)
... (์๋ต) ...
case left.Type() != right.Type():
return newError("type mismatch: %s %s %s", left.Type(), operator, right.Type())
default:
return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
}
}
func evalMinusPrefixOperatorExpression(right object.Object) object.Object {
if right.Type() != object.INTEGER_OBJ {
return newError("unknown operator: -%s", right.Type())
}
value := right.(*object.Integer).Value
return &object.Integer{Value: -value}
}
func evalIntegerInfixExpression(operator string, left, right object.Object) object.Object {
leftVal := left.(*object.Integer).Value
rightVal := right.(*object.Integer).Value
switch operator {
case "+":
return &object.Integer{Value: leftVal + rightVal}
...(์๋ต)...
default:
return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type())
}
}
๊ทธ๋ฐ๋ฐ ์ฌ์ ํ ํ ์คํธ ์ผ์ด์ค 2๊ฐ๊ฐ ํต๊ณผํ์ง ๋ชปํ๊ณ ์๋ค. ์ฌ์ ํ ํ๊ฐ๋ฅผ ๋ฉ์ถ๋ ๋์์ ์์ง ๋์ํ์ง ์๋๋ค๋ ์ ์ด๋ค.
์ด๋ฅผ ๋ฐ์ํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ evalProgram ๊ณผ evalBlockStatement ๋ฉ์๋์ ๋ค์๊ณผ ๊ฐ์ ๋ก์ง์ ์ถ๊ฐํด์ค๋ค.
// evaluator/evaluator.go
func evalProgram(stmts []ast.Statement) object.Object {
var result object.Object
for _, statement := range stmts {
result = Eval(statement)
switch result := result.(type) {
case *object.ReturnValue:
return result.Value
case *object.Error:
return result
}
}
return result
}
func evalBlockStatement(stmts []ast.Statement) object.Object {
var result object.Object
for _, statement := range stmts {
result = Eval(statement)
if result != nil {
rt := result.Type()
if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ {
return result
}
}
}
return result
}
๋ ๋ฉ์๋ ๋ณด๋ ์ค๊ฐ์ object.Error ํ์ ์ธ์ง๋ฅผ ์ฒดํฌํ๊ณ ํด๋น ํ์ ์ด๋ผ๋ฉด ์ฆ์ ํ๊ฐ๋ฅผ ์ค๋จํ๋๋ก ์ถ๊ฐํ๋ค. ๋ง์ง๋ง์ผ๋ก ํ ์ผ์ Eval ๋ฉ์๋ ์์์ ์ฌ๊ท์ ์ผ๋ก Eval ๋ฉ์๋๋ฅผ ํธ์ถํ ๋๋ง๋ค ์๋ฌ๊ฐ ๋ฐ์ํ๋์ง ์ฒดํฌํด์ผ ํ๋ค. ๊ทธ๋์ผ๋ง ์๋ฌ๊ฐ ๋ฐ์ํ ์ง์ ์ ๋ช ํํ ํ์ ํ ์ ์๊ณ , ๋ค๋ฅธ ๊ณณ์ผ๋ก ์ฎ๊ฒจ์ง์ง ์๋๋ก ํด์ ์๋ฌ๊ฐ ๋ฐ์ํ ์ง์ ์ ์๋ชป๋ ์ง์ ์ผ๋ก ๊ฐ๋ฆฌํค๋ ๋ฌธ์ ๋ฅผ ๋ง์ ์ ์๋ค.
// evaluator/evaluator.go
func isError(obj object.Object) bool {
if obj != nil {
return obj.Type() == object.ERROR_OBJ
}
return false
}
์ ํจ์๋ฅผ Eval ๋ฉ์๋๋ฅผ ์ฌ๊ท์ ์ผ๋ก ํธ์ถํ๋ ๋ถ๋ถ์๋ค๊ฐ ์ถ๊ฐํ์ฌ ๊ฒ์ฆํ๋ ๊ฒ์ ๋ฐ์ํ๋๋ก ํ์.
3-6. ๋ฐ์ธ๋ฉ๊ณผ ํ๊ฒฝ(Environment)
๋ค์์ผ๋ก ์ถ๊ฐํด๋ณผ ๊ธฐ๋ฅ์ ๋ฐ์ธ๋ฉ(Bindings)์ด๋ค. ์ฐ๋ฆฌ๊ฐ ์ง๊ธ๊น์ง๋ let ๋ฌธ, return ๋ฌธ, ์ค์ ํํ์, ์ ์ ํํ์ ๋ฑ์ ํ๊ฐํ๋ ๊ฒ๊น์ง๋ง ํด๋ณด์๋ค. ๊ทธ๋ฐ๋ฐ ํน์ ์ ํ์ ํํ์์ ํ๊ฐํ ๋ค์ ์ด๋ค ๋ณ์์ ์ ์ฅ์ ํ ์๊ฐ ์๋ค. ๋ํ์ ์ผ๋ก let ๋ฌธ์ด ๊ทธ๋ ๋ค. ์๋์ฒ๋ผ ๋ง์ด๋ค.
let x = 5 + 10;
์ผ๋ฐ์ ์ธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ ์์ ๊ฐ์ด ์์ค์ฝ๋๋ฅผ ์์ฑํ ๋ค, x๋ฅผ ์ถ๋ ฅํ๋ฉด ๋ชจ๋ 15๋ผ๋ ๊ฐ์ ์ถ๋ ฅํ ๊ฒ์ด๋ค. ์ฆ, ์์ ๊ฐ์ let ๋ฌธ ํํ์์ด ํ๊ฐ๋ ๋ค, x ๋ผ๋ ๊ฐ์ ํํ์ ํ๊ฐ์ ๊ฒฐ๊ณผ๋ก ์ป์ด์ง๋ ๊ฐ(15)์ด ํ ๋น๋๋ ๊ฒ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ๋ฐ๋ก '๋ฐ์ธ๋ฉ'์ด๋ผ๊ณ ํ๋ค.
์ด๋ฌํ ๋ฐ์ธ๋ฉ ๊ธฐ๋ฅ์ ์์ง ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ธํฐํ๋ฆฌํฐ์ ์ถ๊ฐ๋์ง ์์ ์ํ๋ค. ์์ผ๋ก ์ถ๊ฐํด๋ณด๋๋ก ํ์. ์ฐ๋ฆฌ๋ let ๋ฌธ ํํ์์ ๋ํ ๋ฐ์ธ๋ฉ์ ์ถ๊ฐํด๋ณผ ๊ฒ์ด๋ค. ์ด๋ฅผ ์ํ ํ ์คํธ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
// evaluater/evaluator_test.go
func TestLetStatements(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"let a = 5; a;", 5},
{"let a = 5 * 5; a;", 25},
{"let a = 5; let b = a; b;", 5},
{"let a = 5; let b = a; let c = a + b + 5; c;", 15},,
}
for _, tt := range tests {
testIntegerObject(t, testEval(tt.input), tt.expected)
}
}
func TestErrorHandling(t *testing.T) {
tests := []struct {
input string
expectedMessage string
}{
{
"foobar",
"identifier not found: foobar",
},
}
... ์๋ต ...
}
ํ ์คํธ ์ผ์ด์ค๋ ๊ฐ๋จํ๋ค. ํฌ๊ฒ 2๊ฐ์ง ๊ธฐ๋ฅ์ ํ ์คํธํ๋ค. ์ฒซ ๋ฒ์งธ๋ let ๋ฌธ ์์ ๊ฐ์ ๋ง๋ค์ด๋ด๋ ํํ์์ ํ๊ฐํ๋ ๊ฒ์ด๊ณ , ๋ ๋ฒ์งธ๋ ์ด๋ฆ์ ๋ฐ์ธ๋ฉ๋ ์๋ณ์๋ฅผ ํ๊ฐํ๋ ๊ฒ์ด๋ค. ์ฌ๊ธฐ์ ํ ๊ฐ์ง ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ณธ๋ค๋ฉด ๋ง์ฝ ๋ฐ์ธ๋ฉ๋์ด ์์ง ์์ ์๋ณ์๋ฅผ ํ๊ฐํ๋ ค ํ ๋ ์๋ฌ๋ฅผ ๋ด๋ฑ๋๋ก ํ๋ ๊ฒ๋ ์ถ๊ฐํด ๋ณผ ๊ฒ์ด๋ค. ์ด ๊ธฐ๋ฅ์ ๊ธฐ์กด์ ์์ฑํ ํ ์คํธ ์ฝ๋์ธ TestErrorHandling ๋ฉ์๋์ ์์ ๊ฐ์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ถ๊ฐํด๋ณด์.
์ด์ ์ ํ ์คํธ ์ผ์ด์ค๋ค์ ํต๊ณผํ๋๋ก ๋ง๋ค๊ธฐ ์ํด ํ๊ฐ๊ธฐ์ ์ํธ๋ฆฌํฌ์ธํธ ๋ฉ์๋์ธ Eval ๋ฉ์๋์ ์์ค์ฝ๋๋ฅผ ์ถ๊ฐํด๋ณด์.
// evaluator/evalutor.go
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
... ์๋ต ...
case *ast.LetStatement:
val := Eval(node.Value)
if isError(val) {
return val
}
}
return nil
์ ์์ค์ฝ๋๋ let ๋ฌธ ํํ์์ ํ๊ฐํ๋ ๊ฒ๋ง์์ ๋๋๋ค. ์ฆ, ๋ฐ์ธ๋ฉ ๊ธฐ๋ฅ์ ์ถ๊ฐ๋์ง ์์ ๊ฒ์ด๋ค. ์ฐ๋ฆฌ๋ ์ด์ ๋ฐ์ธ๋ฉ์ ์ค์ง์ ์ผ๋ก ์ํํ๋ ๋ก์ง์ ์์ฑํด์ผ ํ๋ค.
๊ทธ์ ์์ ์ฌ์ ์ ์์๋ ๊ฐ๋ ์ด ํ๋ ์๋ค. ๋ฐ๋ก 'ํ๊ฒฝ(Environment)' ์ด๋ผ๋ ๊ฒ์ด๋ค. ํ๊ฒฝ์ด๋ผ๋ ๊ฐ๋ ์ ์ธํฐํ๋ฆฌํฐ ๋ถ์ผ์์ ๋งค์ฐ ํํ๊ฒ ์ฐ์ด๋ ์ฉ์ด์ธ๋ฐ, ํ๊ฒฝ์ด๋ ์ธํฐํ๋ฆฌํฐ๊ฐ ๊ฐ์ ์ถ์ ํ ๋ ์ฌ์ฉํ๋ ๊ฐ์ฒด๋ก, ๊ฐ(value)์ ์ด๋ฆ(name)๊ณผ ์ฐ๊ด์ํค๋ ๊ฒ์ด๋ค. ์ด๋ ๊ฒ ๋งํ๋ฉด ํ๊ฒฝ์ด๋ผ๋ ๊ฐ๋ ์ด ๋๊ฒ ๋ฏ์ค๊ณ ์ถ์์ ์ผ๋ก ๋ค๋ฆด ์ ์์ง๋ง, ๊ฒฐ๊ตญ "๋ฌธ์์ด๊ณผ ๊ฐ์ฒด๋ฅผ ์ฐ๊ด์ํค๋ ํด์ ๋งต ์๋ฃ๊ตฌ์กฐ"์ ๋ถ๊ณผํ๋ค. ์ด๋ฅผ ์ดํดํ๊ธฐ ์ํด ์์ค์ฝ๋๋ก ์ดํดํด๋ณด์. ์ฐ๋ฆฌ๋ ์๋์ ๊ฐ์ด Environemnt ๋ผ๋ ์ด๋ฆ์ ์๋ก์ด ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ํด๋ณด์.
// object/environment.go
package object
type Environment struct {
store map[string]Object
}
func NewEnvironment() *Environment {
s := make(map[string]Object)
return &Environment{s}
}
func (e *Environment) Get(name string) (Object, bool) {
obj, ok := e.store[name]
return obj, ok
}
func (e *Environment) Set(name string, val Object) Object {
e.store[name] = val
return val
}
Environment ๊ตฌ์กฐ์ฒด์ store ๋ฉค๋ฒ ํ์ ์ ๋ณด๋ฉด ๋จ์ํ map ์๋ฃ๊ตฌ์กฐ๋ฅผ ๋ํํ ๊ฒ์ ๋ถ๊ณผํ ๊ฒ์์ ์ ์ ์๋ค. ์ด๋ ๊ฒ ๋ํ์ ํ ์ด์ ๋ ์ถํ์ ์ฐ๋ฆฌ๊ฐ ์ฝ๋๋ฅผ ํ์ฅํ๊ธฐ ์ํ ์ค๋น๋ผ๊ณ ์๊ฐํด๋๊ณ ๋์ด๊ฐ์. NewEnvironment ๋ฉ์๋๋ ๋ฐฉ๊ธ ์๊ฐํ Environment ๊ตฌ์กฐ์ฒด๋ฅผ ์๋ก ์์ฑํ๋ ์ญํ ์ ํ๋ค. ์ด๊ฒ ํ์ํ ์ด์ ๋ ์ถํ์ ์๊ฐํ๊ฒ ์ง๋ง ํ ์คํธํ ๋๋ง๋ค ์๋ก์ด ํ๊ฒฝ์ ๋ง๋ค๊ธฐ ์ํ ๊ฒ์ด๋ค. ๊ทธ๋ฆฌ๊ณ Get ์ด๋ผ๋ Pointer Receiver ๋ฉ์๋๋ map ์๋ฃ๊ตฌ์กฐ ์ฆ, ํ๊ฒฝ์ ์ ์ฅ๋์ด ์๋ ํน์ ๊ฐ์ ๊ฐ์ ธ์ค๋ ์ญํ ์, Set ๋ฉ์๋๋ map ์๋ฃ๊ตฌ์กฐ์ ํน์ ๊ฐ์ ์ ์ฅํ๋ ์ญํ ์ ํ๋ค.
์ด๋ ๊ฒ ๊ตฌํ๋ Environment ๊ตฌ์กฐ์ฒด๋ฅผ ์ฐ๋ฆฌ๋ ์ด๋์์ ํ์ฉํ ๊น? ๋ฐ๋ก Eval ์ด๋ผ๋ ์ํธ๋ฆฌํฌ์ธํธ ๋ฉ์๋์ ์ถ๊ฐ ์ธ์๋ก ๋ฃ์ด์ฃผ์ด์ผ ํ๋ค. ๊ทธ๋์ Eval ์ด๋ผ๋ ๋ฉ์๋์ ์๊ทธ๋์ฒ๋ ๋ค์๊ณผ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ค.
// evaluator/evaluator.go ์ Eval ๋ฉ์๋ ์๊ทธ๋์ฒ ๋ณ๊ฒฝ ์ /ํ
// as-is
func Eval(node ast.Node) object.Object {
...
}
// to-be
func Eval(node ast.Node, env *object.Environment) object.Object {
...
}
์ด์ ๊ทธ๋์ ์์ฑ๋ ์์ค์ฝ๋์์ Eval ๋ฉ์๋๊ฐ ํธ์ถ๋๋ ๋ฉ์๋์ ์๊ทธ๋์ฒ๋ฅผ ๋ชจ๋ to-be ํํ๋ก ๋ฐ๊พธ์ด์ฃผ๋๋ก ํ์. ๊ทธ๋ฆฌ๊ณ testEval ์ด๋ผ๋ ํ ์คํธ ์ฝ๋ ํจ์์์๋ ๋งค๋ฒ ํ ์คํธ ์ผ์ด์ค๊ฐ ์ํ๋ ๋๋ง๋ค ์๋ก์ด ํ๊ฒฝ(Environment) ์ฆ, ์๋ก์ด map ์๋ฃ๊ตฌ์กฐ๊ฐ ์์ฑ๋๋๋ก ์๋์ฒ๋ผ ๋ณ๊ฒฝํด์ฃผ์.
// evaluator/evaluator_test.go
func testEval(input string) object.Object {
l := lexer.New(input)
p := parser.New(l)
program := p.ParseProgram()
env := object.NewEnvironment()
return Eval(program, env)
}
์ด์ ๊ทธ๋ฌ๋ฉด Eval ๋ฉ์๋ ๋ด๋ถ์์ ๋ฐ์ธ๋ฉํ๋ ๋ก์ง์ ์์ฑํด๋ณด์. ์ฐ๋ฆฌ๊ฐ ์์์ ์ ์ํ๋ Environment์ Pointer Receiver ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
// evaluator/evalutor.go
func Eval(node ast.Node) object.Object {
// type switch
switch node := node.(type) {
... ์๋ต ...
case *ast.LetStatement:
val := Eval(node.Value)
if isError(val) {
return val
}
env.Set(node.Name.Value, val)
}
return nil
์ด์ env.Set ๋ฉ์๋๋ก ํ๊ฒฝ์ let ๋ฌธ ํํ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ด์๋์๋ค. ๋ง์ง๋ง์ผ๋ก ํ ์ผ์ ์๋ณ์๋ฅผ ํ๊ฐํ์ ๋, ํด๋น map ์๋ฃ๊ตฌ์กฐ์์ ์๋ณ์๋ฅผ key๋ก ํ๋ ๊ฐ์ ์ฐพ์์์ ํ๊ฐํ ์ ์๋๋ก ํด์ผ ํ๋ค. ์๋ฅผ ๋ค์ด, let x = 5 + 5; ํ๊ฐํ ๋ค, x; ๋ผ๋ ์๋ณ์ ํํ์๋ ํ๊ฐ๋ฅผ ํด์ 10 ์ด๋ผ๋ ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋๋๋ก ํด์ผ ํ๋ค๋ ์๋ฏธ์ด๋ค. ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด Eval ๋ฉ์๋์ ์๋ณ์ ํํ์์ ํ๊ฐํ๋ ๋ก์ง์ ์ถ๊ฐํด๋ณด์.
// evaluator/evaluator.go
func Eval(node ast.Node, env *object.Environment) object.Object {
// type switch
switch node := node.(type) {
... ์๋ต ...
case *ast.Identifier:
return evalIdentifier(node, env)
}
return nil
}
func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
val, ok := env.Get(node.Value)
if !ok {
return newError("identifier not found: %s", node.Value)
}
return val
}
๊ทธ๋ฆฌ๊ณ ํ ์คํธ ์ฝ๋๋ฅผ ์ํํ๋ฉด ์ ์ฑ๊ณตํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. REPL๋ ์ํํด์ ๋ฐ์ธ๋ฉ์ด ์ ํ๊ฐ๋๋์ง๋ ์ํํด๋ณด๋๋ก ํ์.
3-7. ํจ์์ ํจ์ ํธ์ถ
์ด์ ๊ฑฐ์ ๋ง๋ฐ์ง๋ค. ๋ค์์ผ๋ก ํด๋ณผ ๊ฒ์ ํจ์์ ํจ์ ํธ์ถ์ ๋ํ ํ๊ฐ์ด๋ค. ์ด๋ฒ ๋ชฉ์ฐจ๋ฅผ ์ ์ํํ๋ฉด ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ ์ฝ๊ฒ ์ดํดํ๊ธฐ ์ด๋ ค์ด ํด๋ก์ ๋ผ๋ ๊ฐ๋ ์ด ์ด๋ค ๊ฒ์ธ์ง๋ ์ดํดํด๋ณผ ์ ์๋ค. ์ฐ์ ์ฐ๋ฆฌ๊ฐ ๋ชฉํ๋ก ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ณ ๋๋ฉด REPL์์ ๋ค์๊ณผ ๊ฐ์ ์ฝ๋ ์ถ๋ ฅ์ด ๊ฐ๋ฅํด์ง๋ค.
>>> let add = fn(a, b, c, d) { return a + b + c + d };
>>> add(1, 2, 3, 4);
10
>>> let max = fn(x, y) { if (x > y) { x } else { y } };
>>> max(5, 10)
10
>>> let callTwoTimes = fn(x, func) { func(func(x)) };
>>> callTwoTimes(3, fn(x) { x + 1 });
5
>>> let newAdder = fn(x) { fn(n) { x + n } };
>>> let addTwo = newAdder(2);
>>> addTwo(2);
4
์ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด์๋ ํฌ๊ฒ 2๊ฐ์ง ์์ ์ด ํ์ํ๋ค. ์ฒซ ๋ฒ์งธ๋ ์ฐ๋ฆฌ๊ฐ ๊ทธ๋์ ๊ตฌํํด์จ ๊ฐ์ฒด ์์คํ (object.go)์ ํจ์๋ฅผ ๋ํ๋ด๋ ๋ด๋ถ ํํ์ ์ ์ํด์ผ ํ๋ค. ๋ง์น ์ ์๋ฅผ ์ฐ๋ฆฌ๊ฐ object.Integer ๊ตฌ์กฐ์ฒด๋ก ์ ์ํ ๊ฒ์ฒ๋ผ ๋ง์ด๋ค. ๋ ๋ฒ์งธ๋ ํ๊ฐ๋ฅผ ์ํํ๋ ์ํธ๋ฆฌํฌ์ธํธ ํจ์๊ฒฉ์ธ Eval ๋ฉ์๋๊ฐ 'ํจ์ ํธ์ถ'์ ์ ๋๋ก ํ๊ฐํ ์ ์์ด์ผ ํ๋ค.
3-7-1. ํจ์ ํํ ์ ์ํ๊ธฐ
๋จผ์ ํด๋ณผ ๊ฒ์ ํจ์ ํํ์ ๊ฐ์ฒด ์์คํ ์ ์ ์ํด๋ณด๋ ๊ฒ์ด๋ค. ๊ฐ์ฒด ์์คํ ์ ์ ์ํด์ผ๋ง ํ๋ ์ด์ ๋ ๊ทธ๋์ผ ํจ์๋ฅผ ๋ณ์ ์ด๋ฆ์ ๋ฐ์ธ๋ฉํ๊ณ , ํํ์์ ์ฌ์ฉํ๊ธฐ๋ ํ๋ฉฐ, ๋ค๋ฅธ ํจ์์ ํน์ ํจ์๋ฅผ ์ธ์๋ก ๋๊ธธ ์๋ ์๊ณ ํจ์๋ฅผ ๋ฐํ๊ฐ์ผ๋ก ์ฌ์ฉํ ์๋ ์๋ค.
ํจ์ ํํ์ ์ ์ํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ์ด์ Parser ๊ด๋ จ ํฌ์คํ ์์ ํจ์ ๋ฆฌํฐ๋ด์ ๋ํด์ AST ์๋ฃ๊ตฌ์กฐ๋ฅผ ๋ง๋ค ๋ ๋ฐฐ์ ๋ FunctionLiteral ๊ตฌ์กฐ์ฒด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ผ์๋ณผ ์ ์๋ค. FunctionLiteral ๊ตฌ์กฐ์ฒด์ ๋ํ ์์ค์ฝ๋๋ฅผ ๋ค์ ํ๋ฒ ์ดํด๋ณด์.
// ast/ast.go
type FunctionLiteral struct {
Token token.Token
Parameters []*Identifier
Body *BlockStatement
}
์ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ณด๊ณ ์ฐ๋ฆฌ๋ object.go ํ์ผ ์ฆ, ์ฐ๋ฆฌ๋ง์ ๊ฐ์ฒด ์์คํ ์ Function ์ด๋ผ๋ ๊ตฌ์กฐ์ฒด๋ฅผ ์ ์ํด๋ณด๋๋ก ํ์.
// object/object.go
const (
... ์๋ต ...
FUNCTION_OBJ = "FUNCTION"
)
type Function struct {
Parameters []*ast.Identifier
Body *ast.BlockStatement
Env *Environment
}
func (f *Function) Type() ObjectType { return FUNCTION_OBJ }
func (f *Function) Inspect() string {
var out bytes.Buffer
params := []string{}
for _, p := range f.Parameters {
params = append(params, p.String())
}
out.WriteString("fn")
out.WriteString("(")
out.WriteString(strings.Join(params, ", "))
out.WriteString(") {\n")
out.WriteString(f.Body.String())
out.WriteString("\n}")
return out.String()
}
์ฐ์ ๊ฐ์ฒด ์์คํ ์์์ ํจ์๋ ์ญ์ ํ ํฐ์ ํ์ํ์ง ์๋ค. ๋ค๋ง, Prameters ์ Body ๋ผ๋ ๋ฉค๋ฒ๋ค์ด ํ์ํ๋ค. Parameters๋ 0๊ฐ ์ด์์ ์ธ์๊ฐ ๋ค์ด๊ฐ ์ ์๊ธฐ ๋๋ฌธ์ ์ฌ๋ผ์ด์ฑ ํํ์ ์๋ณ์๊ฐ ํ์ ์ผ๋ก ์ง์ ๋์๋ค. ๊ทธ๋ฆฌ๊ณ Body๋ ํจ์์ ๋ฐ๋ ์ค์ฝํ๋ฅผ ์๋ฏธํ๋ค. ์ด ๋ฐ๋ ์ค์ฝํ๋ ์ด๋ฏธ ์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ด BlockStatement ํ์ ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก Env ๋ผ๋ ํ๋๊ฐ ์กด์ฌํ๋๋ฐ, ์ด ํ๋์ ๋ํด์๋ ์ถํ์ ์์ธํ ๋ฐฐ์ธ ์์ ์ด๋ค. ์ฐ์ ์ฌ๊ธฐ์๋ ํจ์๋ผ๋ ๊ฒ์ด ์๊ธฐ ํ๊ฒฝ๊ณผ ๊ฐ์ด ์์ง์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฌํ ํน์ฑ์ ํ์ฉํด ํด๋ก์ (Closure)๊ฐ ๋์ํ ์ ์๋ ๊ทผ๊ฑฐ๋ฅผ ๋ง๋ จํ๋๋ก ํด์ค๋ค๊ณ ์์๋๋๋ก ํ์.
์ฐ์ ๊ฐ์ฅ ๊ฐ๋จํ ํ ์คํธ ์ฝ๋๋ถํฐ ์์ฑํด๋ณด์. ํ ์คํธํ๋ ค๋ ์ผ์ด์ค๋ ๊ธฐ๋ณธ์ ์ธ ํจ์ ๋ฆฌํฐ๋ด ์ฝ๋์ด๋ค. ํ ์คํธ ์ฝ๋ ๋ก์ง์ ๊ทธ๋์ ํ ๊ฒ๋ค๊ณผ ๋์ผํ๋ ๋ณ๋ค๋ฅธ ์ค๋ช ์ ์๋ตํ๊ฒ ๋ค.
// evaluator/evaluator_test.go
func TestFunctionObject(t *testing.T) {
input := "fn(x) { x + 2; };"
evaluated := testEval(input)
fn, ok := evaluated.(*object.Function)
if !ok {
t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated)
}
if len(fn.Parameters) != 1 {
t.Fatalf("function has wrong number of parameters. got=%d, want=1", len(fn.Parameters))
}
if fn.Parameters[0].String() != "x" {
t.Fatalf("parameter is not 'x'. got=%q, want='x'", fn.Parameters[0])
}
expectedBody := "(x + 2)"
if fn.Body.String() != expectedBody {
t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String())
}
}
์ด์ Eval ๋ฉ์๋์ ํ์๊ฐ ๋ง๋ค์ด๋ธ AST ์๋ฃ๊ตฌ์กฐ ์ค ํจ์ ๋ฆฌํฐ๋ด ์ฆ, FunctionLiteral์ด ๋ฑ์ฅํ ๊ฒฝ์ฐ, Function ๊ฐ์ฒด๋ก ๋ง๋ค์ด์ฃผ๋ ๋ก์ง์ ์ถ๊ฐํด๋ณด์.
// evaluator/evaluator.go
func Eval(node ast.Node, env *object.Environment) object.Object {
// type switch
switch node := node.(type) {
... ์๋ต ...
case *ast.FunctionLiteral:
params := node.Parameters
body := node.Body
return &object.Function{Parameters: params, Body: body, Env: env}
}
return nil
}
์์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๊ณ ๋ฐฉ๊ธ ์์ฑํ ํ ์คํธ ์ฝ๋๋ฅผ ์ํํ๋ฉด ํต๊ณผํ๋ค. ํ์ฌ ๋ชฉ์ฐจ์์ ์ฐ๋ฆฌ๊ฐ ํ๋ คํ๋ ๊ฒ์ ํจ์ ํํ์ ์ฐ๋ฆฌ์ ๊ฐ์ฒด ์์คํ ์ ์ ์ํ๋ ๊ฒ์ด์๋ค. ์ด ๋ถ๋ถ์ ๋ฌ์ฑํ๋ค. ํ์ง๋ง ์ฐ๋ฆฌ์ ๋ชฉํ๋ ์ด์ ๋ ์์ค์ด ์๋๋ค. ๋ค์๋ถํฐ ๋ค๋ฃฐ ์ฃผ์ ๋ 'ํจ์ ์ ์ฉ'์ด๋ค.
3-7-2. ํจ์ ์ ์ฉ(Function Application)
ํจ์ ์ ์ฉ์ด๋ ๋ง์ ์ธํฐํ๋ฆฌํฐ๋ฅผ ํ์ฅํด์ ํจ์๋ฅผ ํธ์ถํ ์ ์๋๋ก ์ฝ๋๋ฅผ ์์ ํ๊ฒ ๋ค๋ ์๋ฏธ์ด๋ค. [๋ชฉ์ฐจ 3-7-1]์์๋ ๋จ์ํ ํ๊ฐ๊ธฐ๊ฐ ํจ์ ๋ฆฌํฐ๋ด์ ํ๊ฐํ๋ ๊ฒ์ ๊ทธ์ณค๋ค. ํ์ง๋ง ๋ง์ฝ ํจ์๋ฅผ ํธ์ถํ ๊ฒฝ์ฐ์๋ ์ด๋ป๊ฒ ํ ๊ฒ์ธ๊ฐ? ๋ฐ๋ก ์ด ๋ถ๋ถ์ ๊ฐ๋ฅํ๊ฒ ํ๊ณ ์ ํ๋ค. ํ ์คํธ ์ฝ๋ ๋ด์ ํ ์คํธ ์ผ์ด์ค๋ก ํํํ๋ฉด ์๋์ ๊ฐ์ ์ผ์ด์ค๋ค์ด ํด๋น๋๋ค.
// evaluator/evaluator_test.go
func TestFunctionApplication(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"let identity = fn(x) { x; }; identity(5);", 5},
{"let identity = fn(x) { return x; }; identity(5);", 5},
{"let double = fn(x) { x * 2; }; double(5);", 10},
{"let add = fn(x, y) { x + y; }; add(5, 5);", 10},
{"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
{"fn(x) { x; }(5)", 5},
}
for _, tt := range tests {
testIntegerObject(t, testEval(tt.input), tt.expected)
}
}
ํจ์ ์ ์ฉ์ ํด๋ณด๊ธฐ์ ์์ ์ฐ๋ฆฌ๋ ๊ณผ๊ฑฐ์ ๋ฐฐ์ ๋ AST ์๋ฃ๊ตฌ์กฐ ์ค ํจ์ ํธ์ถ ํํ์์ ํ์ฑํ๋ ๋ด์ฉ์ ํ๊ณ ํ ํ์๊ฐ ์๋ค. ํจ์ ํธ์ถ ํํ์์ ast.CallExpression ์ด๋ผ๋ ๊ตฌ์กฐ์ฒด๋ก ์ ์ํ๊ณ , ์ด ํจ์ ํธ์ถ ํํ์์ ํฌ๊ฒ 2๊ฐ์ง ํํ๋ก ์ผ์ด์ค๊ฐ ๋๋๋ค๊ณ ํ๋ค. ์ฒซ ๋ฒ์งธ ์ผ์ด์ค๋ ์๋ณ์์ผ ๋์ด๋ค. ์ด ์๋ณ์์ผ ๋์ ์์ ์์ค์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
>>> add = fn(x, y) { return x + y };
>>> add(1, 2)
3
๋ ๋ฒ์งธ ์ผ์ด์ค๋ ํจ์ ๋ฆฌํฐ๋ด ์์ฒด์ผ ๊ฒฝ์ฐ์ด๋ค.
>>> fn(x, y) { return x + y }(1, 2);
3
๋ฌผ๋ก Eval ๋ฉ์๋์์ ์ด 2๊ฐ์ง ์ผ์ด์ค์ ๋ฐ๋ผ ๋ก์ง์ด ๋ถ๊ธฐ๋๊ฑฐ๋ ํ์ง๋ ์๋๋ค. ์๋ฒฝํ๊ฒ ์์ฑ๋ Eval ๋ฉ์๋๋ ์๋์ง๋ง ์ง๊ธ ๋น์ฅ ์์ฑํ ์ ์๋ ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
// evaluator/evaluator.go
func Eval(node ast.Node, env *object.Environment) object.Object {
// type switch
switch node := node.(type) {
... ์๋ต ...
case *ast.FunctionLiteral:
params := node.Parameters
body := node.Body
return &object.Function{Parameters: params, Body: body, Env: env}
case *ast.CallExpression:
function := Eval(node.Function, env)
if isError(function) {
return function
}
// TODO: ์์ฑ ์์
}
return nil
}
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ํจ์ ํธ์ถ ํํ์์ ํ๊ฐํด๋ณผ ์ฐจ๋ก๋ค. ๊ฐ์ฅ ๋จผ์ ํ ์ผ์ 'ํธ์ถ ํํ์์ ์ธ์๋ฅผ ํ๊ฐ'ํ๋ ๋์์ด๋ค. ๋จผ์ ์์ ์์ค์ฝ๋๋ฅผ ๋ณด์.
>>> let add = fn(x, y) { x + y };
>>> add(2 + 2, 5 + 5);
์ฐ๋ฆฌ๊ฐ ํ๊ฐํ ๋ถ๋ถ์ add ๋ค์ ์๊ดํธ๊ฐ ๋์ค๋ฉด์ ์ธ์๋ก ๋ฑ์ฅํ๋ 2 + 2 ์ 5 + 5 ์ด๋ค. ์ฐ๋ฆฌ๋ 2 + 2 ์ 5 + 5๋ฅผ ์์ฒด ํํ์์ด ์๋ ๊ฐ ํํ์์ ๊ฒฐ๊ณผ๋ฌผ๋ค ์ฆ, 4์ 10์ผ๋ก ํ๊ฐํ ๋ค ๋๊ธฐ๊ณ ์ถ์ ๊ฒ์ด๋ค. ์ด๊ฒ์ ๋ฐ๋ก ํธ์ถ ํํ์์ ์ธ์๋ฅผ ํ๊ฐํ๋ค๊ณ ํ๋ค. ์ธ์๋ฅผ ํ๊ฐํ๋ ๋์์ ์ฌ์ค ์ฌ๋ฌ๊ฐ์ ํํ์์ผ๋ก ๊ตฌ์ฑ๋ ์ฆ, ํํ์ ๋ฆฌ์คํธ๋ฅผ ํ๊ฐํ๋ ๋์๊ณผ ๋์ผํ๋ค. ์๋ํ๋ฉด ๋ฐฉ๊ธ ์ด์ผ๊ธฐ ํ๋ฏ์ด 2 + 2 ์ 5 + 5๋ ๊ฐ๊ฐ ํํ์์ ์๋ฏธํ๊ณ , 2๊ฐ์ ํํ์์ผ๋ก ๊ตฌ์ฑ๋ ์ ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ์๋ ์์ค์ฝ๋๋ฅผ ๋ณด์.
// evaluator/evaluator.go
func Eval(node ast.Node, env *object.Environment) object.Object {
// type switch
switch node := node.(type) {
... ์๋ต ...
case *ast.CallExpression:
function := Eval(node.Function, env)
if isError(function) {
return function
}
args := evalExpressions(node.Arguments, env)
if len(args) == 1 && isError(args[0]) {
return args[0]
}
}
return nil
}
func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object {
var result []object.Object
for _, e := range exps {
evaluated := Eval(e, env)
if isError(evaluated) {
return []object.Object{evaluated}
}
result = append(result, evaluated)
}
return result
}
Eval ๋ฉ์๋๋ฅผ ๋ณด๋ฉด evalExpressions ๋ผ๋ ์๋ก์ด ๋ฉ์๋๊ฐ ๋ฑ์ฅํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ๋ฉ์๋์ ์ธ์๋ก node.Arguments๊ฐ ๋ฑ์ฅํ๋ค. ์ด๋ ์ฐ๋ฆฌ๊ฐ ์ด์ ์ ๋ฐฐ์ ๋ค๊ณ ํ AST ์๋ฃ๊ตฌ์กฐ ์ค ํจ์ ํธ์ถ ํํ์์ ์๋ฏธํ๋ CallExpression ๊ตฌ์กฐ์ฒด์ ๋ฉค๋ฒ๋ฅผ ์๋ฏธํ๋ค. ์ ์ ์๊ธฐ์ํค๊ธฐ ์ํด CallExpression ๊ตฌ์กฐ์ฒด ์์ค์ฝ๋ ๋ถ๋ถ๋ง ๋ฐ์ทํด์๋ค.
// ast/ast.go
type CallExpression struct {
Token token.Token
Function Expression
Arguments []Expression
}
Arguments๋ฅผ ๋ณด๋ฉด ์ฌ๋ผ์ด์ฑ ํํ์ ํํ์ ์ฆ, ํํ์ ๋ฆฌ์คํธ์์ ๋ณผ ์ ์๋ค. ๋ค์์ผ๋ก evalExpressions ๋ฉ์๋๋ฅผ ์ดํด๋ณด์. ํน๋ณํ ๊ฒ์ด ์๋ค. ํํ์ ๋ฆฌ์คํธ๊ฐ ๋ค์ด์๊ธฐ ๋๋ฌธ์ ์ด ๋ฆฌ์คํธ์ ๋ํด์ ๋ฃจํ๋ฅผ ๋๋ฉด์ ํํ์ 1๊ฐ์ฉ Eval ๋ฉ์๋๋ฅผ ๋ ํ๋ฒ ํธ์ถํด์ฃผ๋ฉด์ 1๊ฐ์ ํํ์์ ๋ํด์ ํ๊ฐ๋ฅผ ์ํํ๊ณ result ๋ณ์์ append ํ๋ ๋ก์ง์ด๋ค. ์ฌ๊ธฐ์ ์ฐ๋ฆฌ๊ฐ ์ฃผ๋ชฉํ ๋ถ๋ถ์ ๋ฐ๋ก ์ด ํํ์ ๋ฆฌ์คํธ์ ๋ํด ๋ฃจํ๋ฅผ ๋๋ฉด์ ํ๊ฐ๋ฅผ ํ๋ ์์๊ฐ ๋ฐ๋ก ํจ์ ํธ์ถ์ ์ธ์๊ฐ ์ฌ๋ฌ ๊ฐ๊ฐ ์์ ๊ฒฝ์ฐ, ์ด๋ค ์ชฝ์ ์ธ์๋ถํฐ ํ๊ฐ๋๋์ง ์์๋ฅผ ๋์น์ฑ ์ ์๋ ํํธ๊ฐ ๋๋ค. ํ ๋ฒ ์์ ์๊ฒ ๊ฐ์ฅ ์ต์ํ ์ธํฐํ๋ฆฌํฐ ์ธ์ด์ ํ๊ฐ๊ธฐ ๊ตฌํ์ฒด๋ฅผ ํ๋ฒ ์ดํด๋ณด๋ ๊ฒ๋ ์ฌ๋ฐ์ ๊ฒ ๊ฐ๋ค.
ํจ์ ํธ์ถ ํํ์์ ์ธ์๋ฅผ ํ๊ฐํ๋ ๊ฒ์ ์ด์ ๋ง์ณค๋ค. ๋ค์์ผ๋ก๋ ์ค์ง์ ์ผ๋ก ํจ์์ ๋ก์ง์ ๋ด๋นํ๋ค๊ณ ํ ์ ์๋ ํจ์ Body๋ฅผ ํ๊ฐํด์ผ ํ๋ค. ํจ์ Body๋ ์์ ๋ฐฐ์์๋ BlockStatement์ ํด๋นํ๊ณ ์ด BlockStatement๋ฅผ ํ๊ฐํ๋ ๋ก์ง์ evalBlockStatement๋ก ์ด๋ฏธ ๊ตฌํ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋จ์ํ๊ฒ ์๊ฐํ๋ฉด Eval ๋ฉ์๋์ ์ธ์์ ํจ์ Body๋ง ์ธ์๋ก ์ง์ด๋ฃ์ผ๋ฉด ๋์ง ์์๊น ์๊ฐํ ์ ์๋ค.
ํ์ง๋ง ์ด๋ ๊ฒ ๊ตฌํํ๊ฒ ๋๋ฉด ํจ์ ํธ์ถ ํํ์์ ์ธ์ ๋ถ๋ถ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ์ฆ, ํจ์ Body์์ ์ธ์๋ค์ ์ฐธ์กฐํด์ผ ํ๋๋ฐ, ์ด๋ ๊ฒ ๊ตฌํํ๊ฒ ๋๋ฉด ํจ์ Body๋ฅผ ํ๊ฐํ ๋ ์ธ์๋ค์ ์ ์ ์๋ ์ด๋ฆ์์ผ๋ก ๊ฐ์ฃผํ๊ฒ ๋๋ค. ๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ ํจ์๊ฐ ํ๊ฐ๋ ํ๊ฒฝ์ ๋ฐ๊พธ๋ ๋์์ ์ํํจ์ผ๋ก์จ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ผ ํ๋ค. ๊ทธ๋์ผ ํจ์ Body์์์ ์ฐธ์กฐํ๋ ์ธ์๋ค์ด ์ ๋๋ก ๋ ๊ฐ์ผ๋ก ๋ณํ๋๋ค.
๊ทธ๋ ๋ค๊ณ ํด์ ํ์ฌ ํ๊ฒฝ์ด ์ ๋ฌํ ์ธ์๋ฅผ ํจ์ Body์ ๊ทธ๋๋ก ์ถ๊ฐํ๋ฉด ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ๋ฐ๋ก ์ด์ ์ ์๋ ๋ฐ์ธ๋ฉ์ ๋ฎ์ด์ฐ๊ฒ ๋๋ค. ์ด๊ฒ ๋์ฒด ๋ฌด์จ ๋ง์ธ๊ฐ? ์๋ ์์ ์์ค์ฝ๋๊ฐ ์๋ค๊ณ ํด๋ณด์.
let i = 5;
let printNum = fn(i) {
puts(i);
};
printNum(10);
puts(i);
puts ํจ์๋ C์ธ์ด์์์ ๋์ผํ๊ฒ ํ์ ์ถ๋ ฅํด์ฃผ๋ ํจ์๋ผ๊ณ ์๊ฐํ์. ์ฐ๋ฆฌ๊ฐ ํํ ์ฌ์ฉํ๋ ์ธํฐํ๋ฆฌํฐ ์ธ์ด์ ๊ฒฝ์ฐ ์ ์์ค์ฝ๋ ๋ก์ง์ด๋ผ๋ฉด ๊ฒฐ๊ณผ๋ฌผ์ ๊ฐ๊ฐ 10๊ณผ 5๊ฐ ๋์ฌ ๊ฒ์ด๋ผ๊ณ ์์ํ ์ ์๋ค. ํ์ง๋ง ๋ฐฉ๊ธ ์ด์ผ๊ธฐํ '๋ฎ์ด์ฐ๊ธฐ ๋ฌธ์ '๊ฐ ๋ฐ์ํ๊ฒ ๋๋ฉด ๊ฒฐ๊ณผ๋ 10๊ณผ 10์ ์ถ๋ ฅํ๊ฒ ๋๋ค. ๋ค์ ๋งํด, printNum(10); ์ด๋ผ๋ ํจ์ ํธ์ถ ํํ์์ด ํ๊ฐ๋๋ฉด์ 10์ด ์ธ์๋ก ์ ๋ฌ๋๋ค. ์ฆ, ํ์ฌ ํ๊ฒฝ์์ 10์ ์ ๋ฌํ๋ ๊ฒ์ด๋ค. ์ด ๋ printNum ์ด๋ผ๋ ํจ์ ๋ฆฌํฐ๋ด์ ํ๊ฐํ๋ฉด์ ํ์ฌ ํ๊ฒฝ์์ ์ ๋ฌ๋ 10์ด ์ ์ญ์ ์ผ๋ก ์ ์ธ๋ i = 5 ๋ผ๊ณ ๋ฐ์ธ๋ฉ๋ ๊ฐ์ ๋ฎ์ด์ฐ๊ธฐ ํ๋ค๋ ์๋ฏธ์ด๋ค.
๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํผํ๊ธฐ ์ํด์ ํ์ฌ ํ๊ฒฝ์ ๋ณด์กดํ๋ฉด์ ๋์์ ์๋ก์ด ํ๊ฒฝ์ ๋ง๋ค์ด์ผ ํ๊ณ , ์ด๋ฌํ ๊ฒ์ 'ํ๊ฒฝ ํ์ฅ'์ด๋ผ๊ณ ๋ถ๋ฅด๋๋ก ํ์. ํ๊ฒฝ ํ์ฅ์ ์ฐ๋ฆฌ๊ฐ ๊ทธ๋์ ๋ง๋ ์์ค์ฝ๋ ๋ ๋ฒจ๋ก ์ดํดํ๋ค๋ฉด [๋ชฉ์ฐจ 3-6]์์ ๋ฐฐ์ด object.Environment ์ธ์คํด์ค๋ฅผ ๋ง๋๋๋ฐ, ํด๋น ์ธ์คํด์ค์ ํ์ฅํ ํ๊ฒฝ์ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ ๋ณ์๋ฅผ ๊ฐ๋๋ก ํ๋ ๊ฒ์ด๋ค. ์ด๋ ๊ฒ ํจ์ผ๋ก์จ ์ฐ๋ฆฌ๋ ํ์ฌ ํ๊ฒฝ์ ์ ์งํ๋ฉด์ ์๋ก์ด ํ๊ฒฝ์ ์๋กญ๊ฒ ์์ฑํ๊ฒ ๋๋ค.
์ด๋ ๊ฒ ํ๋ ์ด์ ๋ ์๋ก์ด ํ๊ฒฝ์์ Environment ๊ตฌ์กฐ์ฒด์ Get ๋ฉ์๋๋ฅผ ํธ์ถํ์ ๋ ํด๋น ํ๊ฒฝ์ ์กด์ฌํ์ง ์๋ ์ด๋ฆ๊ณผ ๊ฐ์ด๋ผ๋ฉด ํด๋น ํ๊ฒฝ์ ๊ฐ์ธํ๊ณ ์๋ ํ๊ฒฝ์์ ์ฐพ๊ณ , ๋ ์กด์ฌํ์ง ์์ผ๋ฉด ๊ฐ์ธ๊ณ ์๋ ํ๊ฒฝ์์ ์ฐพ๊ณ ๋ฅผ ๋ฐ๋ณตํ๋ค. ๊ทธ๋ฌ๋ค๊ฐ ๊ฒฐ๊ตญ์ ์ฐพ์ ์ ์์ ๋ ์ฐ๋ฆฌ๋ Undefined ๋ผ๋ ํค์๋๋ฅผ ๊ฐ์ง๋ฉด์ ์ ์ธ๋ ๋ณ์๋ฅผ ์ฐพ์ ์ ์๋ค๋ ์๋ฌ๊ฐ ๋ฑ์ฅํ๊ฒ ๋๋ค. ํ๊ฒฝ์ ๊ฐ์ธ๋ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
// environment/environment.go
package object
type Environment struct {
store map[string]Object
outer *Environment
}
func NewEnclosedEnvironment(outer *Environment) *Environment {
env := NewEnvironment()
env.outer = outer
return env
}
func NewEnvironment() *Environment {
s := make(map[string]Object)
return &Environment{store: s, outer: nil}
}
func (e *Environment) Get(name string) (Object, bool) {
obj, ok := e.store[name]
if !ok && e.outer != nil {
obj, ok = e.outer.Get(name)
}
return obj, ok
}
func (e *Environment) Set(name string, val Object) Object {
e.store[name] = val
return val
}
์ ์์ค์ฝ๋๋ก ๊ตฌํํ ๋งค์ปค๋์ฆ์ ํํ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ์ธ์ด์์ ๋ณ์ ์ค์ฝํ์ ๊ฐ๋ ์ ๋ฐ์ํ๊ณ ์๋ค. ์๋ฅผ ๋ค์ด, ์ค์ฒฉ๋ ์ค์ฝํ๊ฐ ์๋ค๊ณ ํ์ ๋, ์์ชฝ๊ณผ ๋ฐ๊นฅ์ชฝ ์ค์ฝํ๊ฐ ์กด์ฌํ๋ค. ์ต์ด์๋ ์์ชฝ ์ค์ฝํ์์ ๊ฐ์ ์ฐพ์ผ๋ ค๊ณ ์๋ํ๊ณ ์ฐพ์ง ๋ชปํ๋ค๋ฉด ๋ฐ๊นฅ์ชฝ ์ค์ฝํ์์ ์ฐพ๋ ์ฌ๋ก์ด๋ค.
์ด์ ์์์ ๊ตฌํํ ํ๊ฒฝ ํ์ฅ์ด๋ผ๋ ๊ฒ์ Eval ๋ฉ์๋์ ์ ์ฉํด๋ณผ ์ฐจ๋ก๋ค. ์์ ๋ Eval ๋ฉ์๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
// evaluator/evaluator.go
func Eval(node ast.Node, env *object.Environment) object.Object {
// type switch
switch node := node.(type) {
... ์๋ต ...
case *ast.CallExpression:
function := Eval(node.Function, env)
if isError(function) {
return function
}
args := evalExpressions(node.Arguments, env)
if len(args) == 1 && isError(args[0]) {
return args[0]
}
return applyFunction(function, args)
}
return nil
}
applyFunction ๋ฉ์๋๊ฐ ์๋กญ๊ฒ ๋ฑ์ฅํ๋ค. ์ด ๋ฉ์๋์ ์๊น์๋ฅผ ๋ณด์.
// evaluator/evaluator.go
func applyFunction(fn object.Object, args []object.Object) object.Object {
function, ok := fn.(*object.Function)
if !ok {
return newError("not a function: %s", fn.Type())
}
extendedEnv := extendedFunctionEnv(function, args)
evaluated := Eval(function.Body, extendedEnv)
return unwrapReturnValue(evaluated)
}
๋จผ์ type assertion์ ํตํด์ Object ํ์ ์ ์ธํฐํ์ด์ค์์ ๊ตฌ์ฒด์ ์ธ ํ์ ์ธ Function ํ์ ์ผ๋ก ๋ฐ๊พธ์ด์ค๋ค. ๊ทธ๋ฐ๋ค์ ๋ ์๋ก์ด ๋ฉ์๋์ธ extendedFunctionEnv๊ฐ ๋ฑ์ฅํ๋ค. ์ด๋ฆ์์ ์ ์ถํ ์ ์๋ค์ํผ ์ด ๋ฉ์๋๊ฐ ๋ฐ๋ก ํ์ฅ ํ๊ฒฝ์ ๋ง๋ค์ด์ฃผ๋ ๊ฒ์ด๋ค. extendedFunctionEnv ๋ฉ์๋๋ก ๊ฐ๋ณด์.
// evaluator/evaluator.go
func extendedFunctionEnv(fn *object.Function, args []object.Object) *object.Environment {
env := object.NewEnclosedEnvironment(fn.Env)
for paramIdx, param := range fn.Parameters {
env.Set(param.Value, args[paramIdx])
}
return env
}
์ฃผ๋ชฉํ ๋ถ๋ถ์ ํ์ฅ ํ๊ฒฝ์ ์๋กญ๊ฒ ์์ฑํ ๋ค, ํด๋น ํ๊ฒฝ์ ์ธ์๋ก ๋ฐ์ Function์ ํ๋ผ๋ฏธํฐ๋ค๊ณผ ํด๋น ํ๋ผ๋ฏธํฐ๋ค์ ํ๊ฐํ ๊ฒฐ๊ณผ๊ฐ์ ๋ฃ์ด์ค๋ค๋ ๊ฒ์ด๋ค. ํ๋ผ๋ฏธํฐ๋ฅผ ํ๊ฐํ ๊ฒฐ๊ณผ๊ฐ๋ค์ args ๋ผ๋ ๋ณ์์ ๋ด๊ฒจ์๊ณ , ์ด args ๋ผ๋ ๋ณ์๋ Eval ๋ฉ์๋ ๋ด์ evalExpressions ๋ฉ์๋์ ์ํด ํ๊ฐ๋์๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก unwrapReturnValue ๋ผ๋ ๋ฉ์๋๊ฐ ์๋ค. ํด๋น ๋ฉ์๋๋ ์ฐ๋ฆฌ๊ฐ ์ด์ ์ evalProgram ๋ฉ์๋๋ฅผ ๊ตฌํํ์ ๋์ ์ ์ฌํ๋ค. ์ ๊ธฐ์ต์ด ๋์ง ์๋๋ค๋ฉด evalProgram ๋ฉ์๋๋ก ์ ์ ๋์๊ฐ๋ณด์.
// evaluator/evaluator.go
func unwrapReturnValue(obj object.Object) object.Object {
if returnValue, ok := obj.(*object.ReturnValue); ok {
return returnValue.Value
}
return obj
}
์ด๋ ๊ฒ ํ์ฅ ํ๊ฒฝ์ ์ฌ์ฉํ๋ ์ด์ ๋ ๋ฐ๋ก ํด๋ก์ ๋งค์ปค๋์ฆ์ ์ฌ์ฉํ ๋์ ์ผ์ด์ค ๋๋ฌธ์ด๋ค. ์ด๋ฅผ ์ํ ํ ์คํธ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
// evaluator/evaluator_test.go
func TestClosures(t *testing.T) {
input := `
let newAdder = fn(x) {
fn(y) { x + y };
};}
let addTwo = newAdder(2);
addTwo(2);`
testIntegerObject(t, testEval(input), 4)
}
ํ ์คํธ ์ฝ๋๋ฅด ๋๋ ค๋ณด๋ฉด ์์ ๊ฐ์ ํด๋ก์ ๋งค์ปค๋์ฆ๋ ์ ๋์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ๊ทธ๋ฐ๋ฐ ์์ง ํด๋ก์ ๋ผ๋ ๊ฒ ๋ฌด์์ด๊ณ , ์ ์ด๊ฒ ๋๋ฌธ์ ํ์ฅ ํ๊ฒฝ์ ์ฌ์ฉํ๋์ง๊ฐ ์ ์๋ฟ์ง ์๋๋ค. ๋ฐฉ๊ธ ์ฌ์ฉํ๋ ํ ์คํธ ์ผ์ด์ค๋ฅผ ๋ณด์.
let newAdder = fn(x) { fn(y) { x + y } };
let addTwo = newAdder(2);
ํด๋ก์ ๋ ๊ทธ ํจ์๊ฐ ์ ์๋ ํ๊ฒฝ์ '๋ด์ ๋ด๋' ํจ์์ด๋ค. ๊ทธ๋์ ํด๋ก์ ๋ ์์ ์ ํ๊ฒฝ์ ๋ด๊ณ ์์ง์ด๋ค๊ฐ ํธ์ถ๋์์ ๋ ๊ทธ ํ๊ฒฝ์ ์ ๊ทผํ๋ค. ์ ์์ ์ฝ๋์์ newAdder ์ด๋ผ๋ ๋ณ์๋ ๊ณ ์ฐจ(High-order) ํจ์์ด๋ค. ๊ณ ์ฐจ ํจ์๋, ํจ์๋ฅผ ๋ฐํํ๊ฑฐ๋ ๋ค๋ฅธ ํจ์๋ฅผ ์ธ์๋ก ๋ฐ๋ ํจ์๋ฅผ ๋งํ๋ค. ์ ์์ ์ฝ๋์ ๊ฒฝ์ฐ, ํจ์๋ฅผ ๋ฐํํ๋ ์ ํ์ ๊ณ ์ฐจ ํจ์์ด๋ค.
ํ์ง๋ง ํด๋ก์ ๋ ๋จ์ํ ํจ์๊ฐ ์๋๋ค. addTwo์ ์ธ์ 2๋ฅผ ๋๊ฒจ์ ํธ์ถํ์ ๋ ์ฆ, "addTwo(2)" ๋ผ๊ณ ํ์ ๋ addTwo์๋ ๋ฐํ๋ ํด๋ก์ ๊ฐ ๋ฐ์ธ๋ฉ๋์ด ์๋ค. ์๋ํ๋ฉด "addTwo(2)" ๋ผ๊ณ ํด์ผ addTwo ํจ์์ ํธ์ถ ํํ์์ด ํ๊ฐ๋๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋ฌ๋ฉด addTwo๋ฅผ ํด๋ก์ ๋ก ๋ง๋๋ ๊ฒ์ ๋ฌด์์ผ๊น? ๋ฐ๋ก addTwo๊ฐ ํธ์ถ๋์์ ๋ ์ฆ, "addTwo(2)" ๋ผ๊ณ ํ์ ๋ ๊ฐ๊ณ ์๋ ๋ฐ์ธ๋ฉ์ด addTwo๋ฅผ ํด๋ก์ ๋ก ๋ง๋ค๊ฒ ๋๋ค. addTwo๊ฐ ํธ์ถ๋์์ ๋ addTwo ํจ์๋ ํธ์ถ ์ธ์์ธ ํ๋ผ๋ฏธํฐ y์ ์ ๊ทผํ ์๋ ์๊ณ , ์ฌ์ง์ด newAdder(2)์ ํธ์ถ๋ ์์ ์ ๋ฐ์ธ๋ฉ๋ x์๋ ์ ๊ทผํ ์ ์๋ค. ๊ฒฐ๊ตญ, addTwo ํจ์๋ Body ๋ด๋ถ์์ x, y๋ฅผ ์ถ๋ ฅํ๋ฉด ๋ชจ๋ ๊ฐ์ด ์ถ๋ ฅ๋๋ค๋ ๊ฒ์ด๋ค.
๋ค์ ๋งํด์, ํด๋ก์ ํจ์์ธ addTwo๋ ์ฌ์ ํ ์ฝ๋๊ฐ ์ ์๋ ๋น์์ ํ๊ฒฝ ์ฆ, ์ ์์ ์์ค์ฝ๋๋ก ์น๋ฉด "let addTwo = newAdder(2)" ๋ผ๊ณ ์ ์๋๋ ํ๊ฒฝ์๋ ์ ๊ทผํ ์ ์๋ค๋ ๋ง์ด๋ค.
์ด๋ฌํ ํด๋ก์ ์ ํน์ฑ ๋๋ฌธ์ ์ด๋ฅผ ๋ฐ์ํ๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ํ์ฅ ํ๊ฒฝ์ด๋ผ๋ ๊ฐ๋ ์ ๋์ ํ๊ณ ์ ์ฉํ ๊ฒ์ด๋ค. ์ฐธ๊ณ ๋ก ๊ณ ์ฐจ ํจ์์ ๋ค๋ฅธ ์ข ๋ฅ๋ก ๋ค๋ฅธ ํจ์๋ฅผ ์ธ์๋ก ๋ฐ๋ ํจ์๋ ์ฐ๋ฆฌ์ ์ธํฐํ๋ฆฌํฐ์์๋ ๋์ํ๋ค.
>> let add = fn(a,b) {a+b};
>> let sub = fn(a,b) {a-b};
>> let applyFunc = fn(a, b, func) { func(a, b) };
>> applyFunc(2, 2, add)
4
>> applyFunc(10, 2, sub);
8