서론

Lua 언어는 가벼운, 멀티 패러다임에, 특히 내장용 스크립트 언어로서 디자인되었습니다. 오랜 시간동안 적지 않은 사람들에게 사랑받아 왔지만, 떨어지는 접근성과 그 난이도 또 적은 정보로 널리 알려지지는 않았습니다. 특히 ‘내장용 스크립트’의 필요성을 크게 느끼지 못하는 사람들이 많다고 필자는 생각합니다. 본 문서에서는 그러한 Lua의 내장에 관한 기초적인 정보를 전달하고자 합니다.

내장용 스크립트

제일 먼저 말하고 싶은 것은, 대부분의 프로그래밍 언어들은 ‘근본적으로’ 스크립팅을 위해 설계되지 않았다는 것입니다. 작은 규모라면 몰라도 매우 대규모의 스크립트를, 사용하는 프로그래밍 언어를 통해, 어플리케이션에 내장해 버리는 것은 생산성과 구조 등의 관점에서 항상 좋은 접근법이라고 하기는 어렵습니다. 관련된 경험이 있으신 분들은 공감하시리라 생각합니다.

다만 가장 큰 문제는, 그러한 스크립트가 필요한 어플리케이션을 개발하는 개발자 중 적지 않은 수가 ‘스크립트’라는 개념을 크게 자각하지 못한다는 것입니다. 필자는 개인적으로 이 부분을 매우 안타깝다고 생각합니다. 성능에 매몰되어 스크립트라는 개념을 이해하길 거부하거나, 책 등에서 개념적으로만 본 스크립트 라는 개념에 멈추어 서서 이러한 스크립트를 사용할 기회를 놓치는 개발 상황을 종종 보아 왔습니다.

비유하자면 스크립트와 어플리케이션은 게임과 게임 엔진의 관계에 무척 가깝습니다. 게임의 구현보다 게임 엔진의 구현의 성능이 보통 높습니다. 그렇다고 게임 엔진에 게임을 넣어버리는 행위는 그다지 효율적이지 못합니다. 생산성과 접근성 등, 여러 의미에서 말입니다. 이상적으로는 게임 하나마다 게임 엔진이 하나씩 있어야 하겠지만, 그러지 않으니까요.

본론

Lua를 내장하기 위해, 보통은 본인이 사용하는 언어의 라이브러리를 찾아볼 것입니다. 그러한 라이브러리들의 구조는 대개 다양하여 제가 명확하게 말할 것은 없습니다. 다만 Lua의 기본적인 어플리케이션-VM간 Communication은 스택을 통해 이루어집니다.

스택

Lua가 어플리케이션에 데이터를 전달할 때 스택에 값을 쌓고, 어플리케이션은 해당 스택에서 값을 빼(pop) 받습니다. 반대로 어플리케이션이 데이터를 전달할 때도 스택에 값을 쌓고, Lua가 스택에서 값을 빼 받습니다. AST 등의 경험이 있다면 이해하기 쉬울 거라고 생각합니다.

이러한 동작, push-pop 동작들이 Lua 내장의 근본이 됩니다.

Table의 탐색

스택에 table이 맨 위에 쌓여 있을 때(함수의 결과로서 리턴되었던가 하여) 해당 table을 가져오기 위해서는 하나하나 탐색하는 수밖에 없습니다. 이를 위해 Lua는 next(index)를 제공합니다. index는 테이블의 인덱스이며 보통은 -2(위에서 두 번째)입니다. next 호출시 next는 스택의 맨 위 값을 key로서 pop하고, 다음 key, value 두 값을 push합니다. 첫 탐색시 스택 맨 위에는 nil가 사용되는데, 이는 초기 접근 인덱스가 됩니다. next는 만약 다음 key-value 쌍이 없다면 0을 리턴합니다. 즉 수도코드로 구현하자면 다음과 같습니다.

lua.getGlobal("Table")
lua.pushNil()
while lua.next(-2) {
	print("key: ", lua.toSomething(-2))
	print("value: ", lua.toSomething(-1))
	lua.pop(1)
}

혹은, {1, 2, 3, 4}와 같은 형태의 table의 경우 다음과 같은 방식도 가능합니다.

lua.getGlobal("Table")
lua.getLength(-1)
len = lua.toInteger(-1)
for i=0; i<len; i++ {
	lua.rawGetInteger(-2, i+1)
	print(lua.toInteger(-1))
}