quinta-feira, 16 de outubro de 2008

Portando GDBM para Lua

Lua Espero que este artigo seja interessante. =)

Além de ser um linguagem de programação brasileira, desenvolvida na PUC Rio, Lua possui uma poderosa API com C, na minha opinião, sua maior vantagem.

A outra parte importante deste artigo é GDBM, um conjunto de sub-rotinas de banco de dados baseado em hash, uma alternativa livre ao Unix DBM.

A ideia aqui é demonstrar um pouco dos recursos da biblioteca GDBM e como é simples estender a linguagem Lua.

O objeto


Pessoalmente, quando crio extensões para Lua, gosto de criar primeiro uma classe que represente meu objeto principal, ou uma classe para cada tipo de objeto que será usado nos scripts; depois crio um módulo que porta cada recurso para Lua.

Neste caso vamos criar uma classe GdbmObject que faça a interface com a base de dados, GDBM_FILE.

É claro que poderia ter usado direto GDBM_FILE, aliás comecei fazendo assim, mas acabei voltando ao procedimento que me é natural.

Quanto a objetos datum, não vi necessidade de criar uma interface, já que datum não passa de uma representação mais flexível de string.

O cabeçalho, gdbmobj.h, começa então com as instruções de preprocessamento triviais e a inclusão do cabeçalho gdbm.h, essencial para lidar com GDBM:
#ifndef _GDBMOBJ_H_
#define _GDBMOBJ_H_

#include <gdbm.h>


Podemos então criar a classe e começar os métodos públicos pelo construtor e o destruidor.

O Construtor precisa criar o GDBM_FILE. Para tanto, temos a função gdbm_open, que recebe cinco parâmetros:
  1. o nome do arquivo
  2. o tamanho de blocos para leitura e gravação em bytes (mínimo 512)
  3. o modo de leitura e gravação
  4. as permissões do arquivo, para caso ele seja criado
  5. uma função de callback a ser chamada quando ocorrer um erro fatal


Assim, pelo menos alguns desses parâmetros precisam ser argumentos do construtor. Para simplificar, vamos reduzir: caso o callback seja nulo, é usada uma função padrão. Vamos ficar com ela:
class GdbmObject {
public:
GdbmObject(
const char *name,
int block=512,
int mode=GDBM_READER,
int perm=0644
);
~GdbmObject();


Nosso construtor então recebe os elementos essencias para gdbm_open.

Os outros métodos públicos importantes precisam representar as funções de gdbm.h que queremos acessar, em ordem alfabética: gdbm_close, gdbm_delete, gdbm_exists, gdbm_fetch, gdbm_firstkey, gdbm_nextkey, gdbm_reorganize, gdbm_setopt, gdbm_store e gdbm_sync:
        bool gclose(void);
bool gdelete(const char *);
bool gexists(const char *);
double gfetch_number(const char *);
const char *gfetch_string(const char *);
const char *gnext(const char *key=NULL);
bool greorganize(void);
bool gsetopt(int, int);
bool gstore(const char *, const char *);
bool gstore(const char *, double);
bool gsync(void);


Agora um método para verificar se a base está fechada um método para retornar a última mensagem de erro:
        bool isclosed(void) const;
const char *last_error(void) const;


Entrando em território privado, precisamos de dois atributos: um flag que indique se a base está fechada e o GDBM_FILE em si:
    private:
bool closed;
GDBM_FILE fd;


Então um método privado para converter datum em string e outro para fazer o gdbm_fetch comum a gfetch_number e gfetch_string:
        static const char *get_string(datum);
datum gfetch(const char *);
};

#endif /* _GDBMOBJ_H_ */


Código dos métodos


A lógica em si vai no arquivo gdbmobj.cc, que deve iniciar incluindo nosso cabeçalho e o cabeçalho cstring, para que possamos usar strlen nas conversões entre strings e datum.
#include <cstring>
#include "gdbmobj.h"


O código do construtor é simples: basta pegar os argumentos e passá-lo como parâmetro para gdbm_close – não esqueça de ajudar closed como false!
GdbmObject::GdbmObject(
const char *name, int block, int mode, int perm
): closed(false)
{
this->fd = gdbm_open(
const_cast<char *>(name),
block, mode, perm, NULL
);
}


O destruidor deve apenas fechar a base, caso esqueçam de fazê-lo:
GdbmObject::~GdbmObject() {
this>gclose();
}


Precisamos fazer o gclose. Ele deve:
  1. verificar se a base já não está fechada
  2. fechar a base
  3. verificar se não houve erro
  4. marcar o objeto como fechado

bool GdbmObject::gclose(void) {
if (this->closed)
return false;

gdbm_close(this->fd);

if (gdbm_errno == GDBM_NO_ERROR) {
this->closed = true;
return true;
} else
return false;
}


Agora o método gdelete: primeiro verificar se já não está fechado – todos os métodos precisam verificar isso –, em seguida converter a chave string em datum – para isso o cabeçalho cstring – e então fazer o que tem de ser feito:
bool GdbmObject::gdelete(const char *key) {
if (this->close)
return false;

datum dkey;
dkey.dptr = const_cast<char *>(key);
dkey.dsize = strlen(key);

gdbm_delete(this->fd, dkey);

return gdbm_errno == GDBM_NO_ERROR;
}


O próximo método verifica se uma chave existe e funciona similar ao gdelete, só o retorno é que tem um significado diferente:
bool GdbmObject::gexists(const char *key) {
if (this->close)
return false;

datum dkey;
dkey.dptr = const_cast<char *>(key);
dkey.dsize = strlen(key);

return static_cast<bool>(gdbm_exists(this->fd, dkey));
}


Os métodos gfetch_number e gfetch_string devem retornar um valor para uma chave, um retornando double e outro string (const char *), ambos usando o método privado gfetch para obter a chave em formato datum antes:
double GdbmObject::gfetch_number(const char *key) {
datum data = this->gfetch(key);
double resp = -1.;

if (data.dsize == sizeof(double)) {
double *aux = reinterpret_cast<double *>(data.ptr);
resp = *aux;
}

delete[] data.dptr;
return resp;
}

const char *GdbmObject::gfetch_string(const char *key) {
if (this->closed)
return NULL;

datum data = this->gfetch(key);
const char *aux = get_string(data);

delete[] data.dptr;
return aux;
}


O método gnext é mais complexo, pois faz interface com gdbm_firstkey (quando a chave for nula) e com gdbm_nextkey.
const char *GdbmObject::gnext(const char *key) {
if (this->closed)
return NULL;

datum next;
next.dptr = NULL;
next.dsize = 0;

if (key == NULL)
next = gdbm_firstkey(this->fd);

else {
datum current;
current.dptr = const_cast<char *>(key);
current.dsize = strlen(key);
next = gdbm_nextkey(this->fd, current);
}

if (next.dptr != NULL) {
const char *aux = get_string(next);

delete[] next.dptr;
return aux;
} else
return NULL;
}


O método greorganize é similar a gclose:
bool GdbmObject::greorganize(void) {
if (this->closed)
return false;

gdbm_reorganize(this->fd);
return gdbm_errno == GDBM_NO_ERROR;
}


O método gsetopt precisa tornar transparente algumas peculiaridades de gdbm_setopt:
bool GdbmObject::gsetopt(int option, int value) {
if (this->closed)
return false;

gdbm_setopt(this->fd, option, &value, sizeof(int));
return gdbm_errno == GDBM_NO_ERROR;
}


Temos dois métodos gstore: um para armazenar números, outro para strings, mas a lógica é a mesma: converter tudo pra datum e usar gdbm_store para armazenar. Vamos usar apenas o flag GDBM_REPLACE:
bool GdbmObject::gstore(const char *key, double value) {
if (this->closed)
return false;

datum dkey, data;

dkey.dptr = const_cast<char *>(key);
dkey.dsize = strlen(key);
data.dptr = reinterpret_cast<char *>(&value);
data.dsize = sizeof(double);

gdbm_store(this->fd, dkey, data, GDBM_REPLACE);
return gdbm_errno == GDBM_NO_ERROR;
}

bool GdbmObject::gstore(const char *key, const char *value) {
if (this->closed)
return false;

datum dkey, data;

dkey.dptr = const_cast<char *>(key);
dkey.dsize = strlen(key);
data.dptr = const_cast<char *>(&value);
data.dsize = sizeof(double);

gdbm_store(this->fd, dkey, data, GDBM_REPLACE);
return gdbm_errno == GDBM_NO_ERROR;
}


O método gsync é quase idêntico a greorganize:
bool GdbmObject::gsync(void) {
if (this->closed)
return false;

gdbm_sync(this->fd);
return gdbm_errno == GDBM_NO_ERROR;
}


Agora os métodos constantes, isclosed (está fechado) e last_error (último erro):
bool GdbmObject::isclosed(void) const {
return this->closed;
}

const char *GdbmObject::last_error(void) const {
if (this->closed)
return "database closed";

else
return const_cast<const char *>(gdbm_strerror(gdbm_errno));
}


O método gfetch faz a verdadeira interface com gdbm_fetch para gfetch_number e gfetch_string:
datum GdbmObject::gfetch(const char *key) {
datum data;
data.dptr = NULL;
data.dsize = 0;

if (!this->closed) {
datum dkey;
dkey.dptr = const_cast<char *>(key);
dkey.dsize = strlen(key);

data = gdbm_fetch(this->fd, dkey);
}

return data;
}


O método estático get_string precisa converter datum em string:
const char *GdbmObject::get_string(datum data) {
if (data.dptr == NULL)
return NULL;

char *aux = new char[data.dsize + 1];

for (int i=0; i<data.dsize; ++i)
aux[i] = data.dptr[i];
aux[data.dsize] = '\0';

return const_cast<const char *>(aux);
}


Aqui cabe um comentário para justificar o uso de for em lugar de strcpy:

O ponteiro de datum não é uma string, é um ponteiro para um grupo de bytes – mais ou menos similiar a uma string de Pascal, que tem a vantagem de suportar carácter nulo como parte. Portanto pode não ter um carácter nulo ou ter caracteres nulos dentro do grupo de bytes.

Então a função strcpy não é aplicável.

Que entre a Lua!


Vamos introduzir então a API com Lua, no arquivo luagdbm.cc.

O código precisa começar incluindo o cabeçalho de nossa classe e o cabeçalho de Lua para C++:
#include <lua.hpp>
#include "gdbmobj.h"


Algumas configurações importantes são dados de versão e nome do módulo:
#define VERSION "1.0"
#define MODULE "gdbm"
const char *modulename = MODULE;


Quando Lua carrega uma extensão, chama a função luaopen_nome_da_função, no caso luaopen_gdbm, que deve receber um ponteiro para um estado Lua e deve responder com um inteiro representando a quantidade de elementos empilhados para o estado Lua ao final.

Outra função interessante é set_info, que serve para separar de luaopen_* os dados de diretos, strings e números.

Também vamos criar uma função para empilhar um objeto GdbmObject para o estado Lua:
extern "C" int luaopen_gdbm(lua_State *);
static void set_info(lua_State *);
static void pushdb(lua_State *, GdbmObject *);


Agora vamos declarar uma função para cada método público de GdbmObject – é nossa interface com Lua:
extern "C" {
static int luagdbm_close(lua_State *);
static int luagdbm_delete(lua_State *);
static int luagdbm_exists(lua_State *);
static int luagdbm_fetch_number(lua_State *);
static int luagdbm_fetch_string(lua_State *);
static int luagdbm_isclosed(lua_State *);
static int luagdbm_next(lua_State *);
static int luagdbm_open(lua_State *);
static int luagdbm_reorganize(lua_State *);
static int luagdbm_setopt(lua_State *);
static int luagdbm_store(lua_State *);
static int luagdbm_sync(lua_State *);
}


Podemos implementar cada uma dessas funções.

Função de carregamento


Como já foi dito, a função de carrgamento é luaopen_gdbm.

Ela deve criar um vetor de luaL_reg relacionando cada função com as chaves no módulo em lua, terminando com nulo:
int luaopen_gdbm(lua_State *L) {
static const luaL_reg gdbm_funcs[] = {
{"close", luagdbm_close },
{"delete", luagdbm_delete },
{"exists", luagdbm_exists },
{"fetch_number", luagdbm_fetch_number },
{"fetch_string", luagdbm_fetch_string },
{"isclosed", luagdbm_isclosed },
{"next", luagdbm_next },
{"open", luagdbm_open },
{"reorganize", luagdbm_reorganize },
{"setopt", luagdbm_setopt },
{"store", luagdbm_store },
{"sync", luagdbm_sync },
{ NULL, NULL }
};


Então o módulo deve ser registrado com as funções. Para poder usar o módulo como metatabela para os objetos GdbmObject, vamos também criar também a chave __index apontando para o próprio módulo:
    luaLregister(L, modulename, gdbm_funcs);
lua_pushliteral(L, "__index");
lua_getglobal(L, modulename);
lua_settable(L, -3);


Agora só falta chamar set_info e informar que uma estrutura – o módulo – foi empilhada:
    set_info(L);
return 1;
}


Função para empilhar um objeto de banco de dados


É a função pushdb, que recebe a pilha e um ponteiro para o objeto a ser empilhado.

A função também deve associar ao objeto o próprio módulo como metatabela:
void pushdb(lua_State *L, GdbmObject *db) {
lua_pushlightuserdata(L, db);
lua_getglobal(L, modulename);
lua_setmetatable(L, -2);
}


Vamos deixar set_info para o final…

Funções de interface com os métodos público


Praticamente todas funções devem:
  1. carregar o ponteiro para objeto GdbmObject do estado
  2. verificar se ele ele não está nulo
  3. fazer o que tem de ser feito
  4. retornar o que foi solicitado ou uma mensagem de erro


Repare que todas as funções retornam a quantidade de elementos empilhados (lua_push*).

Assim como começamos pelo método gclose, vamos começar pela função luagdbm_close:
int luagdbm_close(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

if (db->gclose()) {
delete db;
lua_pushboolean(L, 1);
return 1;

} else {
lua_pushnil(L);
lua_pushstring(L, db->last_error());
return 2;
}
}


A função luagdbm_delete ainda precisa recuperar a chave a ser deletada:
int luagdbm_delete(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

const char *key = lua_tostring(L, 2);

if (db->gdelete(key)) {
lua_pushboolean(L, 1);
return 1;

} else {
lua_pushnil(L);
lua_pushstring(L, db->last_error());
return 2;
}
}


A função luagdbm_exists é similar à anterior, só que mais simples:
int luagdbm_exists(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

const char *key = lua_tostring(L, 2);

lua_pushboolean(L, static_cast<int>(db->gexists(key)));
return 1;
}


As funções luagdbm_fetch_number e luagdbm_fetch_string são similares, só que, assim como os métodos gfetch_number e gfetch_string, retornam número e string:
int luagdbm_fetch_number(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

const char *key = lua_tostring(L, 2);
double value = db->getch_number(key);

lua_pushnumber(L, value);

if (value == -1.) {
lua_pushstring(L, db->last_error());
return 2;

} else
return 1;
}

int luagdbm_fetch_string(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

const char *key = lua_tostring(L, 2);
const char *value = db->getch_string(key);

if (value == NULL) {
lua_pushnil(L);
lua_pushstring(L, db->last_error());
return 2;

} else {
lua_pushstring(L, value);
return 1;
}
}


A função luagdbm_isclose é extremamente simples:
int luagdbm_isclosed(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

lua_pushboolean(L, static_cast<int>(db->isclosed()));
return 1;
}


A função luagdbm_next também não é complicada, apenas é preciso verificar se o parâmetro chave foi fornecido:
int luagdbm_next(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

const char *key = NULL;
if (lua_type(L, 2) == LUA_TSTRING)
key = lua_tostring(L, 2);

const char *value = db->gnext(key);

if (value == NULL) {
lua_pushnil(L);
lua_pushstring(L, db->last_error());
return 2;

} else {
lua_pushstring(L, value);
return 1;
}
}


Vamos deixar a função luagdbm_open, devido a sua complexidade, então passamos direto para luagdbm_reorganize:
int luagdbm_reorganize(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

if (db->greorganize()) {
lua_pushboolean(L, 1);
return 1;

} else {
lua_pushnil(L);
lua_pushstring(L, db->last_error());
return 2;
}
}


A função luagdbm_setopt precisa obter dois inteiros, representando a opção e o valor a ser ajustado:
int luagdbm_setopt(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

int option = static_cast<int>(lua_tonumber(L, 2));
int value = static_cast<int>(lua_tonumber(L, 3));

if (db->gsetopt(option, value)) {
lua_pushboolean(L, 1);
return 1;
} else {
lua_pushnil(L);
lua_pushstring(L, db->last_error());
return 2;
}
}


Outra função um pouco mais complicada é luagdbm_store, que deve armazenar um par chave-valor.

A complexidade não está no armazenamento em si, mas na tomada de decisão do tipo da chave, número ou string.

Começamos como as outras funções:
int luagdbm_store(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

const char *key = lua_tostring(L, 2);
bool resp = false;


Então precisamos decidir se o próximo parâmetro é número ou string e usar uma variável do tipo conveniente:
    switch (lua_type(L, 3)) {
case LUA_TSTRING: {
const char *aux = lua_tostring(L, 3);
resp = db->gstore(key, aux);
break;
}

case LUA_TNUMBER: {
double aux = lua_tonumber(L, 3);
resp = db->gstore(key, aux);
break;
}

default:
lua_pushnil(L);
lua_pushstring(L, "not compatible data");
return 2;
}


Podemos então retornar convenientemente:
    if (resp) {
lua_pushboolean(L, 1);
return 1;

} else {
lua_pushnil(L);
lua_pushstring(L, db->last_error());
return 2;
}
}


A última função é luagdbm_sync, que não tem segredos:
int luagdbm_sync(lua_State *L) {
GdbmObject *db =
reinterpret_cast<GdbmObject *>(lua_touserdata(L, 1));

if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L, "no database");
return 2;
}

if (db->gsync()) {
lua_pushboolean(L, 1);
return 1;

} else {
lua_pushnil(L);
lua_pushstring(L, db->last_error());
return 2;
}
}


Abertura de uma base de dados


Finalmente chegamos à função luagdbm_open!

Ela é bem complexa, pois pode receber parâmetros diferentes.

Se o parâmetro for string, será considerada como o nome do arquivo de base de dados e para todas as demais configurações são usados os valores por defeito.

Se for uma tabela:
  • o valor para a chave name representará o nome do arquivo;
  • o valor para a chave block representará o tamanho do bloco;
  • o valor para a chave mode representará o modo de leitura e/ou gravação;
  • o valor para a chave permission representará as permissões para a criação do arquivo.


No entanto, mais de um parâmetros podem ser passados para o modo, como por exemplo GDBM_WRCREAT e GDBM_SYNC. Para esses casos, o valor para a chave mode deve ser uma tabela indexada contendo os modos desejados – e a função precisa estar preparada para processar isso!

A primeira parte da função é carregar os valores por defeito:
int luagdbm_open(lua_State *L) {
const char *name = NULL;
int block = 512;
int mode = GDBM_READER;
int perm = 0644;


Então precisamos verificar se o parâmetro é string (nome do arquivo, tudo mais ajustado por defeito) ou tabela:
    switch(lua_type(L, 1)) {
case LUA_TSTRING:
name = lua_tostring(L, 1);
break;

case LUA_TTABLE: {
lua_getfield(L, 1, "name");

if (lua_type(L, -1) == LUA_TSTRING) {
name = lua_tostring(L, -1);


Agora precisamos pegar o valor de block:
                lua_getfield(L, 1, "block");
if (lua_type(L, -1) == LUA_TNUMBER)
block = static_cast<int>(lua_tonumber(L, -1));


Ao trazermos o valor de mode para cima, precisamos ainda verificar seu tipo:
                lua_getfield(L, 1, "mode");
switch (lua_type(L, -1)) {
case LUA_TNUMBER:
mode = static_cast<int>(lua_tonumber(L, -1));
break;


Agora, caso seja uma tabela, é preciso percorrer seus valores enquantos estes forem números:
                    case LUA_TTABLE: {
int i = 1;
lua_rawgeti(L, -1, i);
mode = 0;
while (lua_type(L, -1) == LUA_TNUMBER) {
mode |= static_cast<int>(
lua_tonumber(L, -1)
);
lua_pop(L, 1);
lua_rawgeti(L, -1, ++i);
}

mode = (mode == 0) ? GDBM_READER : mode;
break;
}


Repare que lua_rawgeti(L, -1, i) empilha o i-ésimo elemento da tabela no fim da pilha (-1) e lua_pop(L, 1) desempilha para voltarmos à tabela.

Agora, caso não seja nem número, nem tabela, ignora:
                    default:
break;
}


Obter as permissões é tão simples quanto obter o tamanho do bloco. Aproveitamos e já encerramos o switch o parâmetro ignorando outros tipos:
                lua_getfield(L, 1, "permission");
if (lua_type(L, -1) == LUA_TNUMBER)
perm = static_cast<int>(lua_tonumber(L, -1));
}

break;
}

default:
break;
}


Repare bem em uma coisa: lua_to*(L, 1) retorna o primeiro parâmetro passado à função, lua_to*(L, 2) retorna o segundo parâmetro. lua_to*(L, -1) retorna o último elemento empilhado por lua_push*, lua_getglobal, lua_getfield, lua_rawget, lua_rawgeti ou qualquer outra função da API que empilhe elementos.

Agora é preciso verificar se, após todo esse trabalho, o nome do arquivo foi realmente ajustado:
    if (name == NULL) {
lua_pushnil(L);
lua_pushstring(L, "file not supplied");
return 2;


Finalmente podemos abrir o arquivo de banco de dados e, se tudo correr bem, retorná-lo para o estado Lua – caso contrário, retornar um erro:
    } else {
GdbmObject *db = new GdbmObject(name, block, mode, perm);

if (gdbm_errno == GDBM_NO_ERROR) {
if (db == NULL) {
lua_pushnil(L);
lua_pushstring(L,
"no error, but no database returned"
);
return 2;

} else {
pushdb(L, db);
return 1;
}
} else {
delete db;
lua_pushnil(L);
lua_pushstring(L, gdbm_strerror(gdbm_errno));
return 2;
}
}
}


Informações


A função set_info é na verdade a mais chata: primeiro ajusta dados padrão sobre o módulo, depois carrega as constantes usadas pelo GDBM com nomes similares.

É bastante extenso e se não quiser, não precisa ler agora. Está aqui apenas para quem quiser programá-lo.
void set_info(lua_State *L) {
lua_pushliteral(L, "_COPYRIGHT");
lua_pushliteral(L, "Copyright (C) 2008 Rodrigo Cacilhas");
lua_settable(L, -3);
lua_pushliteral(L, "_DESCRIPTION");
lua_pushliteral(L, "GDBM interface");
lua_settable(L, -3);
lua_pushliteral(L, "_NAME");
lua_pushliteral(L, MODULE);
lua_settable(L, -3);
lua_pushliteral(L, "_VERSION");
lua_pushliteral(L, VERSION);
lua_settable(L, -3);

lua_pushliteral(L, "READER");
lua_pushnumber(L, GDBM_READER);
lua_settable(L, -3);
lua_pushliteral(L, "WRITER");
lua_pushnumber(L, GDBM_WRITER);
lua_settable(L, -3);
lua_pushliteral(L, "WRCREAT");
lua_pushnumber(L, GDBM_WRCREAT);
lua_settable(L, -3);
lua_pushliteral(L, "NEWDB");
lua_pushnumber(L, GDBM_NEWDB);
lua_settable(L, -3);
lua_pushliteral(L, "FAST");
lua_pushnumber(L, GDBM_FAST);
lua_settable(L, -3);
lua_pushliteral(L, "SYNC");
lua_pushnumber(L, GDBM_SYNC);
lua_settable(L, -3);
lua_pushliteral(L, "NOLOCK");
lua_pushnumber(L, GDBM_NOLOCK);
lua_settable(L, -3);

lua_pushliteral(L, "INSERT");
lua_pushnumber(L, GDBM_INSERT);
lua_settable(L, -3);
lua_pushliteral(L, "REPLACE");
lua_pushnumber(L, GDBM_REPLACE);
lua_settable(L, -3);


lua_pushliteral(L, "CACHESIZE");
lua_pushnumber(L, GDBM_CACHESIZE);
lua_settable(L, -3);
lua_pushliteral(L, "FASTMODE");
lua_pushnumber(L, GDBM_FASTMODE);
lua_settable(L, -3);
lua_pushliteral(L, "SYNCMODE");
lua_pushnumber(L, GDBM_SYNCMODE);
lua_settable(L, -3);
lua_pushliteral(L, "CENTFREE");
lua_pushnumber(L, GDBM_CENTFREE);
lua_settable(L, -3);
lua_pushliteral(L, "COALESCEBLKS");
lua_pushnumber(L, GDBM_COALESCEBLKS);
lua_settable(L, -3);


lua_pushliteral(L, "NO_ERROR");
lua_pushnumber(L, GDBM_NO_ERROR);
lua_settable(L, -3);
lua_pushliteral(L, "MALLOC_ERROR");
lua_pushnumber(L, GDBM_MALLOC_ERROR);
lua_settable(L, -3);
lua_pushliteral(L, "BLOCK_SIZE_ERROR");
lua_pushnumber(L, GDBM_BLOCK_SIZE_ERROR);
lua_settable(L, -3);
lua_pushliteral(L, "FILE_OPEN_ERROR");
lua_pushnumber(L, GDBM_FILE_OPEN_ERROR);
lua_settable(L, -3);
lua_pushliteral(L, "FILE_WRITE_ERROR");
lua_pushnumber(L, GDBM_FILE_WRITE_ERROR);
lua_settable(L, -3);
lua_pushliteral(L, "FILE_SEEK_ERROR");
lua_pushnumber(L, GDBM_FILE_SEEK_ERROR);
lua_settable(L, -3);
lua_pushliteral(L, "FILE_READ_ERROR");
lua_pushnumber(L, GDBM_FILE_READ_ERROR);
lua_settable(L, -3);
lua_pushliteral(L, "BAD_MAGIC_NUMBER");
lua_pushnumber(L, GDBM_BAD_MAGIC_NUMBER);
lua_settable(L, -3);
lua_pushliteral(L, "EMPTY_DATABASE");
lua_pushnumber(L, GDBM_EMPTY_DATABASE);
lua_settable(L, -3);
lua_pushliteral(L, "CANT_BE_READER");
lua_pushnumber(L, GDBM_CANT_BE_READER);
lua_settable(L, -3);
lua_pushliteral(L, "CANT_BE_WRITER");
lua_pushnumber(L, GDBM_CANT_BE_WRITER);
lua_settable(L, -3);
lua_pushliteral(L, "READER_CANT_DELETE");
lua_pushnumber(L, GDBM_READER_CANT_DELETE);
lua_settable(L, -3);
lua_pushliteral(L, "READER_CANT_STORE");
lua_pushnumber(L, GDBM_READER_CANT_STORE);
lua_settable(L, -3);
lua_pushliteral(L, "READER_CANT_REORGANIZE");
lua_pushnumber(L, GDBM_READER_CANT_REORGANIZE);
lua_settable(L, -3);
lua_pushliteral(L, "UNKNOWN_UPDATE");
lua_pushnumber(L, GDBM_UNKNOWN_UPDATE);
lua_settable(L, -3);
lua_pushliteral(L, "ITEM_NOT_FOUND");
lua_pushnumber(L, GDBM_ITEM_NOT_FOUND);
lua_settable(L, -3);
lua_pushliteral(L, "REORGANIZE_FAILED");
lua_pushnumber(L, GDBM_REORGANIZE_FAILED);
lua_settable(L, -3);
lua_pushliteral(L, "CANNOT_REPLACE");
lua_pushnumber(L, GDBM_CANNOT_REPLACE);
lua_settable(L, -3);
lua_pushliteral(L, "ILLEGAL_DATA");
lua_pushnumber(L, GDBM_ILLEGAL_DATA);
lua_settable(L, -3);
lua_pushliteral(L, "OPT_ALREADY_SET");
lua_pushnumber(L, GDBM_OPT_ALREADY_SET);
lua_settable(L, -3);
lua_pushliteral(L, "OPT_ILLEGAL");
lua_pushnumber(L, GDBM_OPT_ILLEGAL);
lua_settable(L, -3);
}


Compilando


Uma forma legal de compilar é através de um arquivo Makefile.

Esse arquivo começa com um comentário informando data e seu criador, e logo em seguida os recursos usados: compilador (CC), link-editor (LD), flags de compilação (CFLAGS) e bibliotecas envolvidas (LIBS), no caso:
# $Id: luagdbm,v 1.0 2008/10/16 23:25:31 cacilhas Exp $

CC ?= c++
LD ?= c++
RM ?= rm -f

CFLAGS ?=
LIBS ?= -lm -llua -lgdbm -lstdc++

# **********************************************************************


Então os procedimentos de compilação.

O procedimento para compilar os arquivos fontes (*.cc) em objetos (*.o) é:
.cc.o:
$(CC) -c $< $(CFLAGS) -o $@


Finalmente fazemos a link-edição de tudo na biblioteca compartilhada gdbm.so:
gdbm.so: gdbmobj.o luagdbm.o
$(LD) -shared $? ($LIBS) -o $@


Agora precisamos informar ao make o que fazer por defeito:
all: gdbm.so


E como limpar a bagunça:
clean:
$(RM) luagdbm.o
$(RM) gdbmobj.o


Feito isso já podemos compilar:
bash$ make


Se tudo correu bem, a biblioteca gdbm.so terá sido gerada, se não, verifique os erros e volte corrigindo-os. Caso encontre algum erro neste artigo, por favor informe para que eu possa corrigi-lo!

Fazendo funcionar


Agora mova a biblioteca para um diretório onde Lua possa encontrá-la. Aqui para mim é /usr/lib/lua/5.1/, mas aí para você pode ser qualquer coisa. O conteúdo da variável de ambiente LUA_CPATH pode dar uma dica.

Na ausência dessa variável, entrando no interpretador Lua, a chave package.cpath informa o local correto.

Feito isso, podemos iniciar um interpretador Lua e fazer testes:
bash$ lua
Lua 5.1.3 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> require "gdbm"
> db = assert(gdbm.open { name = "dbtest.db", mode = gdbm.WRCREAT })
> db:store("first", "a")
> db:store("second", "b")
> db:store("thirt", "c")
> db:close()


Saindo do interpretador, você poderá ver o arquivo dbtest.db criado. Vamos reler seu conteúdo:
> db = assert(gdbm.open "dbtest.db")
> k = db:next()
> while k do print(k, db:fetch_string(k)) k = db:next(k) end
first a
second b
thirt c
> db:close()


A ordem de gdbm_next não é necessariamente a ordem de registro.

Conclusão


Acho que este artigo está cheio de informações e é bastante longo, mas se você conseguiu lê-lo até aqui, significa que consegui fazê-lo útil.

Para saber mais, leia a manpage de gdbm e o PiL.

[]'s
Cacilhas, La Batalema

[update 2008-10-23]Valeu pela divulgação no BR-Linux.org, Augusto![/update]
blog comments powered by Disqus