2013년 1월 9일 수요일

Lua C API 정리

1. States
루아 스테이트를 열고 다는 것. 루아 스테이트르 열어야만 루아 API 와 스크립트가 동작한다.

lua_State* lua_open( void );

로 열고,

void lua_close( lua_State* L );

로 닫는다.

꼭 루아 스테이트를 닫진 않아도 된다고 하지만, 프로그램 종료시엔 꼭 닫아주는 것이 좋을듯. 하지만 웹 서버나 대몬이나 기타 등등의 길게 돌아가는 프로그램에서는 필요 없어지면 닫아주는 것이 예의랜다. -_- 스택이 계속 커지기 전에,

2. The Stack and Indices
루아는 가상 스택(virtual stack)이란 놈을 사용해서 C 와 인터페이스 하는데, 스택에는 루아에서 사용하는 값들이 들어가 있다. (nil, number, string, etc.)

스 택에서 인덱스를 조회해서 값을 얻어올 수 있는데, (스택의 맨 밑 바닥이 1이다. 1부터 시작한다) 양수는 스택의 절대 인덱스, 음수는 스택의 top index 에서부터 시작하는 상대 인덱스로 표현한다. 그러므로 인덱스의 범위는

1 <= abs(index) <= top

가 된다.

int lua_gettop( lua_State* L); // 스택의 맨 꼭대기 인덱스 0 이면 스택이 비어있음.

루아 C API 인 스택을 사용하면 스택의 오버플로우를 직접 관리해줘야 한덴다.

int lua_checkstack( lua_State* L, int extra ); // 스택 사이즈를 top+extra 로 늘린다. 늘어난 스택을 줄일 수는 없음.

기본 스택 사이즈는 lua.h 에 LUA_MINSTACK 매크로로 20으로 정의되어 있으나 스택을 건드리지 않는 한 사이즈에 대해서 신경 쓸 필요 없음.

이상, 루아 스택에서 받아들일 수 있는 유요한 스택 인덱스의 범위는 다음과 같다.
(index < 0 && abs(index) <= top || (index > 0 && index <= statckspace)

3. Stack Manipulation
루아에서 스택 건드리는 함수.

void lua_settop(Lua_State* L, int index )
void lua_pushvalue(Lua_State* L, int index )
void lua_remove(Lua_State* L, int index )
void lua_insert(Lua_State* L, int index )
void lua_replace(Lua_State* L, int index )

lua_settop 함수는 들어간 index 까지 스택을 늘리고 nil 로 채우는 효과가 있는데 index가 0 이면 스택의 모든 값들이 제거된다.
n 개의 데이터를 스택에서 pop 하는 요런 매크로가 lua.h 에 정의되어있음.
#define lua_pop(L, n) lua_settop(L, -(n)-1 )

위의 스택 API를 사용한 간단한 예제. (10 20 30 40 50* 으로 시작한다. * 는 스택의 최상위를 의미)
lua_pushvalue( L, 3 ) // 10 20 30 40 50 30*
lua_pushvalue( L, -1) // 10 20 30 40 50 30 30*
lua_remove(L, -3) // 10 20 30 40 30 30*
lua_remove(L, 6) // 10 20 30 40 30*
lua_insert(L, 1) // 30 10 20 30 40*
lua_insert(L, -1) // 30 10 20 30 40* (효과 없음)
lua_replace(L, 2) // 30 40 20 30*
lua_settop(L, -3) // 30, 40*
lua_settop(L, 6) // 30 40 nil nil nil nill*

기본적으로 요놈들은 statck 의 최상위 값을 갖고 노는듯.

4. Querying the Stack
스택의 타입을 조회하는 함수
int lua_type( lua_State* L, int index );
int lua_isnill( lua_State* L, int index );
int lua_isboolean( lua_State* L, int index );
int lua_isnumber( lua_State* L, int index );
int lua_isstring( lua_State* L, int index );
int lua_istable( lua_State* L, int index );
int lua_isfunction( lua_State* L, int index );
int lua_iscfunction( lua_State* L, int index );
int lua_isuserdata( lua_State* L, int index );
int lua_islightuserdata( lua_State* L, int index );

is* 이 함수들은 맞으면 1, 아니면 0 을 리턴하는데 isboolean 은 예외라고 하는데 뭐라고 하는 건지 잘 모르겄다. (lua_isboolean is an exception to this rule: It succeeds only for boolean values (otherwise it would be useless, as any value has a boolean value))

유효한 스택 인덱스가 아닌 경우 LUA_TNONE 을 리턴.

lua_type() 함수는 요놈들을 리턴
LUA_TNILL
LUA_TNUMBER
LUA_TBOOLEAN
LUA_TSTRING
LUA_TTABLE,
LUA_TFUNCTION
LUA_TUSERDATA,
LUA_TTHREAD
LUA_TLIGHTUSERDATA

요놈들이 타입을 정의한 매크로인데
const char* lua_typename( lua_State* L, int type );
함수로 매칭되는 타입 이름 문자열을 얻을 수 있다.

스택의 값을 비교하는 API 도 있다.
int lua_equal (lua_State* L, int index1, int index2 )
int lua_rawequal(lua_State* L, int index1, int index2 )
int lua_lessthan(lua_State* L, int index1, int index2 )

lua_rawequal은 메타메서드 호출없이 primitive equality를 수행한다.(는데 primitive equality 이게 뭔 말일까? 메타메서드 호출 없이 순수하게 비교만 수행한다는 뜻인지..)

5. Getting Values from the Stackint lua_toboolean (lua_State *L, int index);
lua_Number lua_tonumber (lua_State *L, int index);
const char *lua_tostring (lua_State *L, int index);
size_t lua_strlen (lua_State *L, int index);
lua_CFunction lua_tocfunction (lua_State *L, int index);
void *lua_touserdata (lua_State *L, int index);
lua_State *lua_tothread (lua_State *L, int index);
void *lua_topointer (lua_State *L, int index);

to* 함수는 C 의 타입으로 스택의 해당 인덱스 값을 얻어오는 함수이다. 타입이 맞지 않거나 잘못된 인덱스로 호출 했을 경우엔 0 이 리턴된다.

루아에서는 number 가 string 이 될 수도 있고 string 이 number 가 될 수도 있는데, 레퍼런스 문서의 2.2.1 항목을 참조할 것,

그 리고, lua_tostring 으로 number 에서 string 으로 변환될 경우 실제 스택 내부에서도 string 타입으로 바뀌어버리므로 주의! 그리고 얻은 문자열은 언제 garbage collection 될지 모르므로 나중에 쓸거면 registry 에 복사해두도록 한다. (registry 는 레퍼런스 문서 3.18 참고)

lua_tocfunction 함수는 스택의 값을 C 함수로 바꿔서 리턴해주는데 함수 타입이 아닐경우엔 NULL 리턴, lua_CFunction 타입에 관해서는 레퍼런스 문서 3.16에 참조.

lua_tothread 는 스택의 값을 Lua Thread(lua_State*) 로 리턴해준다. 타입이 맞지 않으면 NULL 리턴.

lua_topointer 는 스택의 값을 C 포인터로 리턴해 주는데, (값의 포인터가 아니라 값 자체가 포인터) userdata, table, thread, function 등등이 될 수 있으나 곧바로 원래 값의 포인터로 쓸 수는 없고 대부분 디버깅 목적으로 사용된다고 한다.

lua_touserdata 함수는 레퍼런스 문서 3.8 참조

6. Pushing Values onto the Stackvoid lua_pushboolean (lua_State *L, int b);
void lua_pushnumber (lua_State *L, lua_Number n);
void lua_pushlstring (lua_State *L, const char *s, size_t len);
void lua_pushstring (lua_State *L, const char *s);
void lua_pushnil (lua_State *L);
void lua_pushcfunction (lua_State *L, lua_CFunction f);
void lua_pushlightuserdata (lua_State *L, void *p);

lua_pushstring 과 lua_pushlstring 은 딥 카피로 스트링을 복사해서 스택에 담는다. 또한,

const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp);

함 수도 있는데 sprintf 나 vsprintf 와 같은 효과로 스택에 문자열을 집어넣는다. 리턴 값은 스택에 들어가는 문자열의 포인터. 이 문자열의 포인터는 루아에서 관리되는 문자열을 가리키고 있기 때문에 함부로 건드리지 않는게 좋다.

void lua_concat (lua_State *L, int n);

함 수는 스택의 탑에서부터 n 개의 값을 concatenate 하고 모두 pop 한 후에, 결과 값을 탑에 놓는다. n 이 1이면 아무 것도 하지 않고 0 이면 빈 문자열이 쌓인다. Concatenation 에 관해선 2.5.4 를 참조

7. Controlling Garbage Collection
가비지 콜렉션에 대해선 2.9 를 참조한다.
int lua_getgccount(lua_State* L);
int lua_getgcthreshold(lua_State* L);
함수로 가비지 콜렉션에 사용되는 두 개의 값을 조회할 수 있으며,
void lua_setgcthreshold( lua_State* L, int newthreshold );
함수로 threshold 값을 직접 건드릴 수 있다. threshold를 0으로 해주면 그 즉시 강제로 가비지 콜렉션을 실행하며 그 다음부턴 루아가 하던대로 알아서 한다.

- 개인적인 견해로는.. 루아의 가비지 콜렉션을 별로 건드릴 일이 없을 듯.

8. Userdata
Userdata 란 루아에서 표현되는 C 데이터 값인데, 루아에서 지원하는 Userdata 는 full userdata(전체 메모리) 와 light userdata(포인터) 가 있다. light userdata 인 경우에는 metatable을 가질 수가 없으며 C 에서 사용하는 주소값일 뿐이다.(숫자)

루아코드에서는 이놈이 full userdata 인지 light userdata 인지 알아낼 수 없으며 C 코드에서는 lua_type 함수로 구분 할 수 있다.

void* lua_newuserdata( lua_State* L, size_t size );
함수로 full userdata를 생성할 수 있으며 lua_pushlightuserdata 함수로 light userdata 를 생성할 수 있다. (3.6 참조)

lua_touserdata 로 userdata 의 포인터를을 얻을 수 있다. (3.5 참조)

루아의 garbage collector 가 full userdata 를 회수 할 때 userdata의 gc 메타 메서드가 호출된다. 그리고 메모리에서 해제.

9. Metatables
다음 함수로 Metatable(2.6 참조) 조작 가능
int lua_getmetatable( lua_State* L, int index);
lnt lua_setmetatable( lua_State* L, int index);
lua_getmetatable 함수는 주어진 오브젝트의 메타테이블을 스택에 올려놓는다. (주어진 오브젝트란 스택의 index에 들어있는 값이 가리키는 것을 얘기하는 듯. 테이블이나 userdata 가 되겠지.) 해당 오브젝트가 메타테이블을 가질 수 없으면 0을 리턴하고 스택엔 아무것도 쌓지 않는다.

setmetatable 함수는 스택에서 테이블에서 테이블을 pop 하고 그 테이블에 인덱스로 주어진 새로운 메타테이블을 set 한다. 메타테이블이 셋팅될 수 없다면 0을 리턴한다. (that is, when the object is neither a userdata nor a table)

10. Loading Lua Chunks
typedef const char* (*lua_Chunkreader)
(lua_State* L, void* data, size_t *size);
int lua_load( lua_State* L, lua_Chunkreader reader, void* data, const char* chunkname);
함수로 루아 chunk를 로드 할 수 있다.
0 이면 정상, LUA_ERRSYNTAX 면 문법 에러, LUA_ERRMEM 이면 메모리 할당 실패 이다.

lua_load 가 성공하면 chunk 를 루아 함수로 간주하여 스택에 푸시.
lua 파일이 텍스트던 바이너리던 상관 없음, 알아서 로드.
뒷 내용은 아직 잘 모르겠으므로 생략.

11. Manipulatiing Tables
void lua_newtable( lua_State* L );
함수로 루아 테이블 생성, 빈 테이블이 생성되고 스택에 푸시된다.

void lua_gettable( lua_State* L, int index );
index 는 테이블을 가리키고 lua_gettable이 호출되면, 스택에 있는(맨 꼭대기) key 가 pop 되고 key 에 해당하는 내용이 스택에 리턴된다. 이 때 테이블은 그대로 남아있음. 이 함수를 사용하면 index 이벤트가 일어나서 metatable에 있는 __index metamethod가 호출되는데 이걸 피하고 싶으면 raw 버전인
void lua_rawget( lua_State* L, int index);
함수를 사용하면 된다.

테이블에 어떤 요소를 저장하고 싶다면
void lua_settable( lua_State* L, int index );
함 수를 사용하는데, index는 스택에서 테이블의 인덱스를 가리키고, key와 들어갈 내용, 즉 2개의 값을 스택에서 pop 하고 테이블에 집어넣게 된다. 이때, settable 이나 newindex 메타 메서드가 호출될 수가 있는데 그게 싫으면 raw 버전인
void lua_rawset( lua_State* L, int index );
함수를 사용하면 된다.

int lua_next( lua_State* L, int index );
함 수를 사용하여 테이블을 조회할 수 있는데, index는 조회할 테이블을 가리키고, 스택에서 key 값이 pop 된다. 그리고 주어진 다음 key에 해당하는 key-value pair(key가 먼저 value가 나중에 푸시된다. 즉, 스택의 top엔 value가 푸시되어있음) 가 푸시된다. 더 이상 조회할 것이 없으면 0을 리턴. 전형적인 조회 코드는 다음과 같다.

// table is in the stack at index 't'
lua_pushnil( L ); // first key
while( lua_next(L, t) != 0
{
// 'key' is at index -2 and 'value' at index -1
printf( "%s - %s\n", lua_typename( L, lua_type(L, -2) ), lua_typename(L, lua_type(L, -1)) );
lua_pop(L, 1); // removes 'value'; keeps 'key' for next iteration
}

주 의할 점은 key 값에 직접 lua_tostring 을 호출하지 말라는 것. key 값이 number 타입이라면 lua_tostring 은 스택 내부의 key 값을 string 타입으로 바꿔버리기 때문에 다음에 lua_next 함수를 호출할 경우 무슨 일이 일어날지 모른다.

출처 : 
http://www.hankiya.com/tc/153

댓글 없음: