domingo, 9 de setembro de 2007

Criando um módulo binário em Lua

Lua Uma das coisas que tornam a linguagem de programação Lua interessante é justamente sua poderosa API com a linguagem C.

Com ela é possível criar módulos em C, explorando todo poder e flexibilidade dessa incrível linguagem.

De fato a linguagem Lua não passa de uma linguagem de extensão para C e o interpretador Lua é uma programa escrito em C para ser uma máquina virtual.

A criação de um módulo de baixo nível – como são conhecidos os módulos em C para Lua – não tem nenhum truque ou sortilégio. =)

Basicamente você precisa de uma função int luaopen_módulo(lua_State *) que chame a função lua_register() para registrar o módulo. Essa função recebe três parâmetros: o estado Lua, o nome do módulo e um vetor do tipo luaL_reg com a lista de funções a serem exportadas. A função deve retornar 1.

As funções a serem exportadas devem ser estáticas, recebendo o estado Lua e retornando inteiro.

Os parâmetros vindos de Lua são recolhidos com as funções lua_totipo() e o retorno é dado pelas funções lua_pushtipo(). A função C real deve retornar um número representando a quantidade de valores a serem retornados para Lua.

Confuso até aqui, né? Mas tudo se esclarecerá com um exemplo.

Exemplo


Vamos criar um módulo que permita mudar e informar o diretório atual. Para tanto, precisaremos do cabeçalho unistd.h.

Precisaremos também do cabeçalho stdlib.h – para o malloc() – e dos cabeçalhos de Lua:
#include <stdlib.h>
#include <unistd.h>

#include <lua.h>
#include <luaxlib.h>
#include <lualib.h>


Agora podemos declarar os cabeçalhos das funções para mudar e informar o diretório atual, e uma função para ajustar informações sobre o módulo:
static int dir_chdir(lua_State *);
static int dir_cwd(lua_State *);
static void set_info(lua_State *);


Repare que todas as funções recebem como único argumento o estado Lua.

Vamos agora ao vetor que define as funções do módulo. Deve ser um vetor do tipo luaL_reg. Esse tipo é uma estrutura onde o primeiro elemento é uma string representando o nome da funções e o segundo elemento a função em si. O último elemento do vetor deve ser equivalente a nulo.

O último elemento do vetor deve ser nulo:
static const luaL_reg Funcs[] = {
{ "chdir", dir_chdir },
{ "cwd", dir_cwd },
{ NULL, NULL }
}


Agora já podemos criar a função de abertura do módulo, como já vimos, luaopen_dir():
int luaopen_dir(lua_State *L) {
luaL_register(L, "dir", Funcs);
set_info(L);
return 1;
}


Em set_info(), o que vemos é lua_pushliteral(), que empilha dados literais no estado, depois «agrupamos» os dados literais com lua_settable() – não vou entrar em detalhes sobre isso.
static void set_info(lua_State *L) {
lua_pushliteral(L, "_COPYRIGHT");
lua_pushliteral(L, "Copyright (C) 2007 Rodrigo Cacilhas");
lua_settable(L, -3);
lua_pushliteral(L, "_DESCRIPTION");
lua_pushliteral(L, "Directory control");
lua_settable(L, -3);
lua_pushliteral(L, "_NAME");
lua_pushliteral(L, "dir");
lua_settable(L, -3);
lua_pushliteral(L, "_VERSION");
lua_pushliteral(L, "1.0");
lua_settable(L, -3);
}


As funções exportadas


Vamos agora às funções exportadas para Lua.

O cabeçalho da primeira função é:
static int dir_chdir(lua_State *L) {
int r;


Usaremos r para fornecer o retorno.

Em Lua, a função chdir() deverá receber um parâmetro string. Para pegar esse parâmetro na função em C usamos:
    char *path = (char *) lua_tostring(L, 1);


A chamada da função lua_tostring() recebe como primeiro parâmetro o estado Lua que executou a função e, como segundo parâmetro, o índice do parâmetro passado para a função Lua – 1 indica o primeiro parâmetro.

Então a linha acima recolherá o primeiro parâmetro passado em Lua como uma string.

Agora é código C simples:
    if (chdir(path) == 0)
r = 1;
else
r = 0;


Ou seja, se o status for zero, o retorno será 1 (verdadeiro), senão será 0 (falso).

Agora precisamos empilhar a resposta no estado Lua como booleano:
    lua_pushboolean(L, r);


Por último avisamos o estado de que um valor foi retornado:
    return 1;
}


Vamos agora à segunda função: ela retornará uma string representando o diretório atual ou nulo caso ocorra um erro – veja que é possível retornar quantidades e tipos diferentes em chamadas diferentes:
static int dir_cwd(lua_State *L) {
char *path = (char *) malloc(sizeof(char) * 1024);
getcwd(path, 1023);

if (path == NULL) {
lua_pushnil(L);
lua_pushstring(L, "getcwd error");
return 2; // dois retornos
} else {
lua_pushstring(L, path);
return 1; // um retorno
}
}


Compilando


Agora é preciso compilar o módulo – chamei o arquivo de dir.c:
bash$ gcc -fPIC -Wall -c dir.c
bash$ ld -lm -shared dir.o -o dir.so


Agora, como superusuário, mova a biblioteca compartilhada dir.so para o diretório $LUA_CPATH – geralmente /usr/local/lib/lua/5.1/ ou /usr/lib/lua/5.1/.

Testando


Vamos então testar! Levante a máquina virtual Lua e experimente:
lua> require "dir"
lua> print(dir.chdir "/tmp")
true
lua> print(dir.cwd())
/tmp


Se tudo correu bem, você terá algo similar a isso. =)

Dúvidas nos comentários, por favor!

[]'s
Cacilhas