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:- o nome do arquivo
- o tamanho de blocos para leitura e gravação em bytes (mínimo 512)
- o modo de leitura e gravação
- as permissões do arquivo, para caso ele seja criado
- 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:- verificar se a base já não está fechada
- fechar a base
- verificar se não houve erro
- 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:
- carregar o ponteiro para objeto
GdbmObject
do estado - verificar se ele ele não está nulo
- fazer o que tem de ser feito
- 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]