quarta-feira, 10 de dezembro de 2008

Perl6

Perl Larry Wall, criador do Perl e cocriador do Parrot, declarou que a versão 6 de Perl não será compatível com as versões anteriores.

Realmente dei uma olhada no Perl6 e está muito diferente… só que para melhor. =)

A única coisa de que não gostei foram das mudanças em expressões regulares, no mais as mudanças só acrescentam.

Tipagem


Uma das maiores reclamações que os programadores fazem de Perl é sua tipagem insanamente fraca, apenas três tipos: escalar ($scalar), vetor (@array) e vetor associativo (%hash).

Perl6 traz o suporte a tipos! Pelo que vi na documentação, há seis tipos novos: inteiro (int), ponto flutuante (num), string (str), booleano (bool), bit (bit) e referência (ref), de forma estática:
my int $foo = 5;
my str $bar = "foo";
my num $baz = 5.3;
my bool $foobar = 1;


Conversão:
print str 5;
print scalar @list;


Passagem de parâmetros


Em Perl a passagem de parâmetros sempre foi por cópia, agora passa a ser por referência, exceto para tipos primitivos.

No caso de atribuição de referência de tipos primitivos, em vez do tradicional:
 my $b = \$a;


Passa a ser:
 my $b := $a;


Mudanças em vetores


A criação de vetor passa a suportar um formato mais simples.

Temos o tradicional:
my @fruits = ("apple", "pear", "banana");


Que passa a ser:
my @fruits = "apple", "pear", "banana";


E a lista:
my @fruits = qw/apple pear banana/;


Passa a ser:
my @fruits = <apple pear banana>;


Em Perl5, para se obter um item de um vetor, o primeiro carácter mudava de @ para $: $fruits[0]. Agora, em Perl6, não há mais alteração: @fruits[0].

A quantidade de elementos de um vetor era dada por:
my $count = $#fruits + 1;


Agora, em Perl6 passa a ser:
my int $count = @fruits.elems;


Repara que o famigerado -> virou um simples ponto!

Também para obter o último elemento do vetor, que era:
my $last = $fruit[$#fruit];


Passa a ser:
my $last = @fruit[@fruit.end];


Ou ainda:
my $last = @fruit[*-1];


Para se obter um vetor em um contexto string, Perl6 oferece o atalho ~. Para numérico + e para booleano ?:
my str $str_fruits = ~@fruits;
my num $num_fruits = +@fruits;
my bool $bool_fruits = ?@fruits;


Tratamento de exceções


Em Perl5 era usado eval para avaliar um bloco com possível exceção e $@ recebia os dados de exceção:
eval {

};
if ($@) {
warn "exception: $@";
}


Perl6 passa a usar try-CATCH:
try {

CATCH {
warn "exception: $!";
}
}


Repare que a mensagem de exceção passa a ser capturada em $! e o bloco CATCH fica dentro do bloco try.

Repetições


Não há uma forma menos dolorosa de dizer isso: foreach vira for, que também passa a suportar algumas construções de while, for vira loop, que também passa a suportar algumas construções de while.

Então:
foreach (@fruits) {


Vira:
for @fruits {


E:
foreach my $fruit (@fruits) {


Vira:
for @fruits -> $fruit {


Agora, olha o while aí:
while (my($age, $sex, $location) = splice @whatever, 0, 3) {


Vira:
for @whatever -> $age, $sex, $location {


Contando até dez em Perl5:
for (my $i = 1; $i <=10; $i++) {
print "$i\n";
}


Contando até dez em Perl6:
loop (my $i = 1; $i <=10; $i++) {
say $i;
}


E loop { … } funciona como while (1) { … }.

Vetor associativo


Sofre alterações parecidas com as de vetor.

Por exemplo, em Perl5:
my $c = $days{February};


Em Perl6 vira:
my int $c = %days{'February'};


Ou ainda:
my int $c = %days<February>;


Alguns métodos


Para se obter o tamanho de uma string em Perl5:
my $len = length $string;


Em Perl6 fica:
my int $len = $string.chars;


Para exibir um vetor de modo ordenado em Perl5:
print sort(@fruits);


Em Perl6:
say @array.sort;


Ou ainda:
@array.sort.print;


Variáveis de sistema


Para se obter o valor da variável HOME em Perl5:
print $ENV{HOME};


Em Perl6 passa a ser:
print %*ENV<HOME>;


Ou ainda:
print $+HOME;


Orientação a objetos


Criar uma classe em Perl5 era uma aventura!

Você criava um pacote (package) e «abençoava» um escalar com o pacote… pronto: aí tinha uma instância cuja classe era o pacote.

Um tremendo bacalhau.

Em Perl6 temos as palavras reservadas class, has (para atributos) e method (para métodos), para criar classes reais.

Métodos privados precisam começar com !, por exemplo, $!age, e métodos públicos precisam começar com ., como por exemplo $.name.

A passagem de parâmetros também mudou. Além de ser por referência, como citado, passa a suportar parâmetros nominais e parâmetros excedentes – como *args de Python, mas com a sintaxe *@args.

Se o argumento for mutável precisa ser seguido de is rw.

O que era:
package Square;
@ISA = qw/ Rectangle /;


Passa a ser:
class Square is Rectangle {


E o que era:
my $rect = new Rectangle(4, 5);


Passa a ser:
my Rectangle $rect .= new(4, 5);


Conclusão


Há muito mais! Consulte a página Perl6::Perl5::Differences, mas o que já sabemos é: quem trabalha com Perl vai precisar aprender de novo!

[]'s
Cacilhas, La Batalema

sexta-feira, 5 de dezembro de 2008

Persistência de dados entre execuções em OpenStep

GNUstep OpenStep facilita a persistência de dados entre uma execução e outra do programa através da classe NSUserDefaults.

Para persistir dados para a próxima execução, basta criar um dicionário (NSDictionary) representando um hash dos dados a serem persistidos.

Digamos que o dicionário seja defaults e APP_NAME é uma string (NSString) representando o nome da aplicação:
[[NSUserDefaults standardUserDefaults]
setPersistentDomain: defaults
forName: APP_NAME
];


E para recuperar os dados persistidos da última execução:
NSDictionary *defaults = [
[NSUserDefaults standardUserDefaults]
persistentDomainForName: APP_NAME
];


Então defaults será um dicionário contendo os dados persistidos!

Simples assim. =)

Referência: GNUstep Base.

[]'s
Cacilhas, La Batalema

quinta-feira, 20 de novembro de 2008

Base64

De vez enquando, não tem como não, nós programadores sempre esbarramos na Base64.

Base64 é um protocolo de codificação que usa apenas seis bits, o que significa um conjunto de sessenta e quatro (64) elementos – daí Base64.

A conversão de oito (byte) para seis bits é feita da seguinte forma:
xxxxxx.xx xxxx.xxxx xx.xxxxxx


Ou ainda:
aaaaaabb bbbbcccc ccdddddd


Os elementos usados são caracteres simples, começando com as letras maiúsculas, A (0) a Z (25), então as letras minúsculas, a (26) a z (51), os números, 0 (52) a 9 (61), e os caracteres + (62) e / (63).

Na conversão de bytes (8b) para Base64 (6b), cada três bytes é convertido em quatro dígitos, então um código Base64 é sempre pensado em grupos de quatro. Se o tamanho de um código Base64 não for múltiplo de quatro, caracteres = são acrescentados ao final até que o tamanho seja múltiplo de quatro.

Python


Em Python, Base64 é tão simples que nem tem graça:
>>> print "Kodumaro".encode("base64")
S29kdW1hcm8=
>>> print "S29kdW1hcm8=".decode("base64")
Kodumaro


Lua


Em Lua, é preciso baixar o módulo Mime do LuaSocket:
> require "mime"
> print(mime.b64 "Kodumaro")
S29kdW1hcm8=
> print(mime.unb64 "S29kdW1hcm8=")
Kodumaro


Smalltalk


Sendo muito sincero sobre o assunto, não sei como fazer conversão de Base64 em Smalltalk. =(

Mas sei que os módulos do Seaside providenciam isso!

Se alguém souber, por favor informe!

[update 2009-11-27]
Sugestão do Hugo:

No Squeak tem isso aqui:
(Base64MimeConverter mimeEncode: 'base64' readStream) contents
(Base64MimeConverter mimeDecode: 'S29kdW1hcm8' as: ByteString) contents


E no Pharo tem métodos para strings:
'base64' base64Encoded
'S29kdW1hcm8' base64Decoded


A implementação usa as coisas do Squeak:
String>>base64Decoded
↑ (Base64MimeConverter mimeDecode: self as: self class)

String>>base64Encoded
↑ (Base64MimeConverter mimeEncode: self readStream) contents


Para outros Smalltalks eu não sei…

Legal né? =)

Muito legal sim, Hugo, valeu!
[/update]


C


Aha! Aqui começa de verdade a brincadeira!

É claro que você pode usar as funcionalidades de Base64 da gLibC, mas descobri que nem todas as versões dela apresentam tais funcionalidades:
#include <glib/gbase64.h>


Vamos precisar usar os cabeçalhos stdlib.h, string.h e sys/types.h. Como também vamos criar um cabeçalho para «compartilhar» algumas funções, ele também será incluído no início de nosso arquivo base64.c:
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "base64.h"


Agora vamos criar um array com todos os possíveis caracteres Base64 em sua ordem natural:
static const char b64all[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
"ghijklmnopqrstuvwxyz0123456789+/";


Também vamos precisar de três funções locais: uma para obter o índice de um elemento (_getindex), outra para codificar para Base64 um grupo de três bytes (_encode) e mais uma para decodificar um grupo de quatro elementos Base64 (_decode):
int _decode(u_int8_t *, const u_int8_t *);
void _encode(u_int8_t *, const u_int8_t *, int);


Não é preciso uma função para _getindex:
#define _getindex(c) (int) (index(b64all, c) - b64all)


Repara que, em vez de char, estamos usando u_int8_t, que é mais conveniente quando queremos lidar com bytes enquanto bytes, não caracteres.

A partir daqui, se preferir, organize as funções em ordem alfabética – ou na ordem que quiser.

A primeira função que implementaremos será para codificar uma string C (const char *) para Base64. Como a string pode não ser bem formada, a função deverá receber também seu tamanho:
const char *b64encode(const char *original, int length) {
// Se o tamanho não for informado, consideramos uma string bem
// formada
if (length == 0)
length = strlen(original);

// Inteiro com o tamanho do código a ser gerado
int b64length = ((length + 2) / 3) * 4 + 1;

// Contadores para percorrer as strings
int i=0, j=0;

// Alocando memória para o código
char *b64 = (char *) malloc(sizeof(char) * b64length);
memset(b64, 0, b64length);

while (i < length) {
// Codifica um grupo de três bytes...
_encode(
(u_int8_t *) b64 + j,
(const u_int8_t *) original + i,
(length - i)
);

// E segue para o próximo grupo
i += 3;
j += 4;
}

// Retorna o código
return (const char *) b64;
}


A próxima função deve fazer o contrário, converter um código Base64 para uma string. Como a string resultante pode não ser bem formada – pode não ser terminada em carácter nulo ou possuir caracteres nulos no meio –, é preciso uma forma de informar seu tamanho, então ela receberá um ponteiro para um inteiro:
const char *b64decode(const char *b64, int *length) {
// Inteiro com o tamanho do código
int b64length = strlen(b64);

// Se não for múltiplo de quatro, há algo errado
if (b64length % 4 != 0)
return NULL;

// Tamanho máximo da string decifrada
int prob = (b64length / 4) * 3 + 1;

// Contadores para percorrer as strings
int i=0, j=0;

// Alocando memória para o resultado
char *s = (char *) malloc(sizeof(char) * prob);

while (j < b64length) {
// Decifra um grupo de quatro elementos
// e conta o resultado
i += _decode(
(u_int8_t *) s + i,
(const u_int8_t *) b64 + j
);

// Segue para o próximo grupo
j += 4;
}

// Se foi fornecido um inteiro para contagem, informa o tamanho
if (length != NULL)
*length = i;

// Retorna a string decifrada
return (const char *) s;
}


Agora precisamos das funções específica para as conversões.

Primeiro para codificar:
void _encode(u_int8_t *dest, const u_int8_t *src, int len) {
// Menor que 1, nada a fazer
if (len < 1)
return;

// Dados a serem retornados
int aux[] = { 0, 0, 0, 0 };

// Primeiro elemento: os 6 bits mais significativos do primeiro
// byte
aux[0] = src[0] >> 2;

// Segundo elemento: os 2 bits menos significativos do primeiro e
// os quatro bits mais significativos do segundo byte
aux[1] = (src[0] & 0x03) << 4;

if (len > 1) {
// SE houver um segundo...
aux [1] |= (src[1] & 0xf0) >> 4;

// Terceiro elemento: os quatro bits menos significativos do
// segundo e os dois mais significativos do terceiro byte
aux [2] = (src[1] & 0x0f) << 2;

if (len > 2) {
// Se houver um terceiro...
aux[2] |= src[2] >> 6;

// Quarto elemento: os seis bits menos significatos do
// terceiro byte
aux[3] = src[2] & 0x3f;
}
}

// Codifica agora os valores numéricos para string
dest[0] = b64all[aux[0]];
dest[1] = b64all[aux[1]];
dest[2] = '=';
dest[3] = '=';
if (len > 1) {
dest[2] = b64all[aux[2]];
if (len > 2)
dest[3] = b64all[aux[3]];
}
}

int _decode(u_int8_t *dest, const u_int8_t *src) {
// Representação numérica do código
int aux[] = { 0, 0, 0, 0 };

// Contador
int i, c = 1;

// Converte código para valores numéricos
for (i = 0; i < 4; ++i)
aux[i] = _getindex(src[i]);

// Primeiro byte: primeiro elemento seguido dos quatro bits mais
// significativos do segundo
dest[0] = (u_int8_t) (aux[0] << 2) | ((aux[1] & 0x30) >> 4);

// Zera os bytes seguintes
dest[1] = '\0';
dest[2] = '\0';

if (aux[2] != -1) {
// Se houver um terceiro elemento...
++c;

// Segundo byte: quatro bits menos significativos do segundo
// elemento seguidos pelos quatro bits mais significativos do
// terceiro
dest[1] = (u_int8_t) ((aux[1] & 0x0f) << 4) | (aux[2] >> 2);

if (aux[3] != -1) {
// Se houver um quarto elemento...
++c;

// Terceiro byte: dois bits menos significativos do
// terceiro elemento seguidos pelo quarto elemento
dest[2] = (u_int8_t)
((aux[2] & 0x03) << 6) |
aux[3];
}
}

// Retorna o tamanho da string
return c;
}


Cabeçalho


Por último criamos o cabeçalho base64.h, tornando públicas as duas funções de codificação e decodificação:
#ifndef _BASE64_H
#define _BASE64_H

const char *b64decode(const char *, int *);
const char *b64encode(const char *, int);

#endif


Conclusão


Mesmo que ninguém vá implementar uma biblioteca de conversão Base64, espero que este artigo sirva para ajudar a entender melhor do que se trata Base64.

[update]

Se quiser acrescentar algum açucar sintático, coloque em base64.c:
#ifdef C_PLUS_PLUS
const char *b64decode(const char *b64, int &length) {
return b64decode(b64, &length);
}
#endif


E mude o conteúdo de base64.h para:
#ifndef _BASE64_H
#define _BASE64_H

#ifdef C_PLUS_PLUS
extern "C" {
#endif

const char *b64decode(const char *, int *);
const char *b64encode(const char *, int);

#ifdef C_PLUS_PLUS
}
const char *b64decode(const char *, int &);
#else
#define _b64decode(s, len) b64decode(s, &len)
#endif

#endif

[/update]


[]'s
Cacilhas, La Batalema

quinta-feira, 13 de novembro de 2008

Objective C

GNUstep Escrevi recentemente um artigo com um exemplo bem simples de desenvolvimento de uma aplicação para ambiente GNUstep, mas cometi a gafe de não dedicar algum tempo falando da linguagem de programação em questão, Objective C.

Objective C, ou Objective-C, ou ObjC, é uma linguagem de programação reflectiva orientada a objetos criada nos idos da década de 1980 – mais ou menos na mesma época que o C++ – pelos fundadores da Stepstone, mais tarde adquirida pela NeXT. É na verdade uma camada bem fina sobre a linguagem C padrão.

Isso significa que, se você sabe programar em C, conseguirá programar em ObjC sem muito esforço.

Diferenças para C


Sobre a linguagem C, ObjC adiciona os seguintes recursos:
  • Orientação a objetos, adaptada de Smalltalk.
  • Tipo boleano – BOOL. Verdadeiro é YES e falso é NO.
  • Comentário de linha, iniciado por // (na época C não suportava).
  • A diretiva de preprocessador #import, que funciona como #include, mas só inclui o cabeçalho se ele não foi incluído ainda.
  • O tipo id, similar a void * de C/C++.
  • A constante nil, que é um objeto id sem valor: (id)0.


A maior parte dos comandos referentes a orientação a objetos começam com @, como @interface, @implementation, @end, @private, @protected, @public, @protocol, etc.… Entre colchetes é avaliado o ambiente de mensagem, com uma sintaxe muito parecida com a de Smalltalk.

Criando uma classe


Como exemplo, vamos criar a classe Point, com os atributos x e y.

Para tanto, primeiro criamos a interface no arquivo Point.h:
#ifndef _POINT_H
#define _POINT_H

#import <Foundation/Foundation.h>

@interface Point : NSObject
{
int x;
int y;
}

+ (id) newWithX: (int)valuex Y: (int)valuey;
- (id) initWithX: (int)valuex Y: (int)valuey;
- (int) diagonal;
- (int) getX;
- (int) getY;
- (void) setX: (int)value;
- (void) setY: (int)value;

@end

#endif


As diretivas #ifndef, #define e #endif já são velhas conhecidas dos programadores C/C++. O #import já foi explicado, funciona parecido com #include.

O cabeçalho Foundation/Foundation.h inclui a classe NSObject, definida pela API OpenStep, usada aqui como classe pai de nossa classe Point.

O comando @interface inicia a descrição da interface e o comando @end encerra. O pequeno escopo entre chaves define os atributos de instância, protegidos por padrão – para defini-los como públicos a sintaxe seria:
{
@public
int x;
int y;
}


As linhas começadas por um sinal de menos (-) indicam métodos de instância – métodos de classe são iniciados por +. Por exemplo:
- (id) initWithX: (int)valuex Y: (int)valuey;

Significa um método de instância que recebe dois atributos inteiros – valuex e valuey, e retorna um id.

Agora precisamos implementar a interface. Para isso vamos criar o arquivo Point.m:
#include <math.h>
#import "Point.h"

@implementation Point

+ (id) newWithX: (int)valuex Y: (int)valuey {
return [[Point alloc]
initWithX: valuex Y: valuey];
}

- (id) initWithX: (int)valuex Y: (int)valuey {
if ((self = [super init])) {
x = valuex;
y = valuey;
}
return self;
}

- (int) diagonal {
return (int) sqrt((double) ((x * x) + (y * y)));
}

- (int) getX {
return x;
}

- (int) getY {
return y;
}

- (void) setX: (int)value {
x = value;
}

- (void) setY: (int)value {
y = value;
}

@end


O código é muito parecido com C, e até mesmo com C++, cabendo apenas alguns comentários:
  • self é uma palavra reservada que representa a instância, como this de Java e C++.
  • super referencia a classe pai, como em Java.
  • Repare no comando [super init]: aqui é passada uma mensagem para super e o valor retornado é atribuído a self.


Para instanciar um objeto, a sintaxe é:
id point = [Point newWithX: 4 Y: 5];


Ou, com tipagem estática:
Point *point = [Point newWithX: 4 Y: 5];


Para obter o valor de x:
printf("x = %d\n", [point getX]);


E para mudar seu valor:
[point setX: 2];


OpenStep


OpenStep é a API aberta para desenvolvimento de aplicações para NEXTSTEP, resultado de um trabalho colaborativo da NeXT com a SUN Microsystems.

Diversos sistemas e ambientes oferecem suporte a OpenStep, como OS X e GNUstep.

Diversos recursos interessantes são oferecidos nessa API, desde vantagens semelhantes à STL de C++, até conjunto de widgets para construção de janelas, toolkit.

Já vimos uma classe dessa API, NSObject. Outra interessante é NSString, cuja finalidade é a mesma de std::string de C++ e String de Java.

A criação de uma NSString é:
id url = [NSString stringWithCString: "http://kodumaro.blogspot.com/"];


Há ainda um apelido sintático:
id url = @"http://kodumaro.blogspot.com/";


Um forma legal de conhecer mais sobre OpenStep é ler os manuais de referência do GNUstep (toolkit aqui) e do OS X.

[]'s
Cacilhas, La Batalema

sexta-feira, 7 de novembro de 2008

Desenvolvendo aplicações GNUstep

GNUstep Sempre fui fascinado pelo NeXTSTEP e gostaria muito tivesse dado certo. Por mais que a Apple negue, seu Mac OS X não é muito mais do que uma versão nova de um NeXTSTEP portado para equipamentos MacIntosh.

Mas não só o OS X herdou os recursos do NeXTSTEP. No começo da década de 1990, em colaboração com a SUN Microsystems a NeXT lançou o OpenStep, uma especificação aberta que define a API para o NeXTSTEP.

Assim ambientes baseados em NeXTSTEP podem rodar em quaisquer sistemas operacionais e aplicações portáteis podem ser criadas.

A Free Software Foundation lançou um projeto de implementação da API OpenStep para sistemas GNU, o GNUstep.

Mais do que uma API de programação, GNUstep é todo um ambiente, desde a API até aplicações e ambiente operacional.

Inicialmente o ambiente gráfico / geranciador de janelas era o AfterStep. Certo dia, na lista de discussão para definir os rumos da versão 2 do AfterStep, um brasileiro chamado Alfredo Kojima sugeriu a reescrita do código do zero.

Enquanto o grupo continuava debadendo, Kojima decidiu iniciar o desenvolvimento da versão 2 do AfterStep, codinome WindowMaker, por conta própria e depois apresentou-o à comunidade. O Window Maker então substituiu o AfterStep como gerenciador de janelas do projeto GNUstep e o AfterStep continuou seu desenvolvimento a parte.

Diversas versões do Window Maker foram lançadas, apesar de nunca ter chegado a uma versão 1.0, um eterno beta – atualmente se encontra na versão 0.92. Porém seu desenvolvimento esfriou gradativamente.

Ano passado o projeto voltou a esquentar e promessas de novas versões estão no ar, o que me inspirou a escrever este artigo.

Consulte o blog do Bardo.


GNUstep


Mas este artigo não é sobre Window Maker, e sim sobre GNUstep.

Para desenvolver aplicações GNUstep, você precisa baixar seu core antes. Acesse a página de recursos, vá ao tópico GNUstep Core e baixe as versões estáveis de cada pacote. Descompacte, compile e instale na ordem em que os pacotes aparecem na tabela (startup, make, base, gui e backend). O core do GNUstep deverá ser instalado em /usr/GNUstep/.

Você precisa acrescentar /usr/GNUstep/System/Tools a seu path de executáveis, /usr/GNUstep/System/Library/Libraries a sua lista de diretórios de bibliotecas – em ambiente GNU/Linux, acrescente esse path ao arquivo /etc/ld.so.conf e execute ldconfig – e crie uma variável de ambiente GNUSTEP_MAKEFILES com o valor /usr/GNUstep/System/Library/Makefiles e outra GNUSTEP_INSTALLATION_DOMAIN com o valor SYSTEM.

Tendo o GNUstep instalado, podemos partir para as ferramentas de desenvolvimento, Gorm e Project Center. Você também encontrará apontadores para as versões estáveis na mesma máquina de recursos, no tópico GNUstep Development Tools.

Criando um projeto


Vamos criar nosso primeiro projeto GNUstep!

Primeiro abrimos o Project Center com o comando:
bash$ openapp ProjectCenter


Vá em ProjectNew... (ou pressione M-n), vai abrir uma janela de diálogo pedindo o nome do projeto. Selecione o tipo Application e forneça o nome Temperature e clique Ok.

Por enquanto nossa brincadeira com Project Center acaba por aqui. Pode salvar – ProjectSave ou M-s – e sair – Quit ou M-q.

Criando uma aplicação


Agora vamos trabalhar no Gorm, execute:
bash$ openapp Gorm


Um segredinho: nas versões anteriores você mandava abrir o Temperature.gorm a partir do Project Center e ele já abria o Gorm para a aplicação atual. Na versão que experimentei – Gorm 1.2.6 e Project Center 0.5.0 – há um bug. O truque é abrir o Gorm a parte e criar uma nova aplicação.
Se quiser testar se está funcionando – em algumas lugares que testei funcionou – para abrir o Gorm, na janela principal do Project Center clique em Interfaces e duplo clique em Temperature.gorm.

No Gorm, crie uma janela e marque como Visible at launch time.


No Gorm então acesse DocumentNew Application, depois vá em DocumentSave As.... Na janela de diálogo encontre seu projeto Temperature, dentro dele Resources e, dentro desse, salve como Temperature.gorm – confirme o Replace.

Vão abrir três janelas: Main Menu, My Window e Temperature.gorm. Se as janelas Palettes e Inspector não estiverem abertas, encontre-as no menu Tools.

Selecione a janela My Window – ela representa a janela principal da aplicação – e veja em Inspector o campo Title contendo My Window – se não estiver vendo, mude o seletor para Attributes. Mude o título para Temperature Converter – repare que o título da janela My Window também mudou.

Repare que na janela Palettes tem alguns widgets. Para acrescentar um widget à janela é só segurar e arrastar (drag'n'drop). Vamos acrescentar três tipos de widget: Text, System Bold e Button. Adicione componentes até ficar mais ou menos com a cara deste corte:
screenshot 1

Para mudar o texto de um widget, use o duplo clique. Você também pode mudar a borda no Inspector. Altere os widgets até ficarem mais ou menos assim:
screenshot 2

Agora é hora de criar a classe para gerenciar essa janela! Hora de codar? Ainda não…

Na janela Temperature.gorm, na barra principal clique em Classes e selecione NSObject – futuca um pouco que você acha. No menu Operations selecione SubClass. Na lista da direita vai aparecer NewClass e o Inspector vai selecioná-lo – veja a caixa Class com NewClass. Mude o nome da classe para ConverterManager. Apenas confirme na janela Modifying Class.

Podemos agora criar os atributos (outlets) e métodos da classe (actions).

Vamos criar um atributo para cada caixa de texto que criamos – incluside a de Kelvin.

Na janela Inspector há a aba Outlets (0) com o botão Add. Clique nesse botão e teremos NewOutlet com um duplo clique você será capaz de mudar seu nome para degreeC.

Crie mais dois atributos: degreeF e kelvin.

Para criar o primeiro método acesse a aba Actions (0) e siga o mesmo procedimento anterior. Os métodos serão chamados convertC2F: e convertF2C:.

E a classe já está pronta!

Instanciando a classe


Mantendo a classe ConverterManager, no menu principal do Gorm selecione ClassesInstantiate. Na janela Temperature.gorm aparecerá um objeto ConverterManager: é agora que vem parte da mágica…

Precisamos inicialmente ligar o atributo degreeC dessa instância à primeira caixa de texto da janela (ao lado do rótulo °C).

Segure a tecla Ctrl, clique sobre a instância, segure e arraste, solte sobre a caixa de diálogo – vai aparecer um T na caixa –, então, na janela Inspector selecione degreeC e clique no botão Connect. Vai aparecer um S sobre a instância e, na janela Inspector, em Connections, vai aparecer: degreeC (TextField(0)).

Faça o mesmo para as outras caixas ligando cada caixa a um atributo.

Agora precisamos fazer com que os botões acionem métodos da instância, para isso o procedimento é o inverso: segurando a tecla Ctrl clique sobre o botão Celsius to Fahrenheit, segure, arraste e solte sobre a instância de ConverterManager na janela Temperature.gorm.

Na janela Inspector clique em targetconvertC2F: e então clique no botão Connect.

Faça o mesmo para conectar o outro botão ao outro método. Agora todas as ações estão conectadas!

Salvando tudo


Na janela Temperature.gorm, selecione Classes e encontre a classe ConverterManager. Selecione-a, então, no menu principal do Gorm clique em ClassesCreate Class Files. Apenas confirme as duas janelas de diálogo. Isso criará os dois arquivos ConverterManager.m e ConverterManager.h dentro de Resources.

Agora clique em DocumentSave All e Quit.

De volta ao Project Center


Volte ao Project Center e clique em ProjectOpen... e abra o arquivo PC.project do projeto Temperature.

Na janela principal do projeto dê um duplo clique em Classes e depois em ConverterManager.m, isso adicionará o código ao projeto do Project Center.

Finalmente o código!


Precisamos finalmente editar o conteúdo dos métodos. Duplo clique sobre ConverterManager.m na janela principal e vai abrir a janela do editor:
/* All Rights reserved */

#include <AppKit/AppKit.h>
#include "ConverterManager.h"

@implementation ConverterManager


- (void) convertC2F: (id)sender
{
/* insert your code here */
}


- (void) convertF2C: (id)sender
{
/* insert your code here */
}

@end


O código é Objective C, um variante orientado a objetos de C que lembra um pouco Smalltalk.

O método covertC2F deve obter o valor do atributo degreeC – que está ligado à caixa de diálogo com o valor em graus Celsius –, calcular e ajustar os valores em Fahrenreit e Kelvin:
- (void) convertC2F: (id)sender
{
float celsius = [degreeC floatValue];
float fahrenreit = (9.0f * celsius / 5.0f) + 32.0f;

[degreeF setStringValue: [NSString stringWithFormat: @"%1.2f",
fahrenreit]];

[kelvin setStringValue: [NSString stringWithFormat: @"%1.2fK",
(celsius + 273.15f)]];
}


O método convertF2C deve fazer o contrário:
- (void) convertF2C: (id)sender
{
float fahrenreit = [degreeF floatValue];
float celsius = 5.0f * (fahrenreit - 32.0f) / 9.0f;

[degreeC setStringValue: [NSString stringWithFormat: @"%1.2f",
celsius]];

[kelvin setStringValue: [NSString stringWithFormat: @"%1.2fK",
(celsius + 273.15f)]];
}


Salve agora (M-s) e feche o editor. Clique no ícone com a chave de fenda para compilar:
Build

Na janela de build há outro ícone igual, clique nele para compilar. Se tudo correr bem você verá uma bela mensagem:
=== Build succeeded! ===


Se falhar, volte e veja o que está errado.

Clique então no ícone do foguete para rodar:
Launch

Na janela Launch clique no mesmo ícone e a janela irá abrir. Faça alguns testes para ver se funciona bem.

Para sair clique com o botão direito e escolha Quit.

Se tudo funcionar bem, seu programa está pronto! Salve tudo em ProjectSave.

Instalando a aplicação


Para instalar, abra um terminal:
bash$ cd Temperature/
bash$ make
bash$ sudo make install


Assim sua aplicação estará instalada em /usr/GNUstep/System/Applications/Temperature.app – ou /usr/GNUstep/Local/Applications/Temperature.app, se você se esqueceu de ajustar o conteúdo da variável de ambiente GNUSTEP_INSTALLATION_DOMAIN – e você executá-la com o comando:
bash$ openapp Temperature


Conclusão


Espero que com este artigo algumas pessoas se interessem pelo desenvolvimento de aplicações para ambiente GNUstep, que aliás roda sobre Mac OS X também – em plataforma OS X, o WINGs (toolkit de widgets para Window Maker) e o GNUstep são abstrações do Cocoa, toolkit de widgets do OS X.

A facilidade de desenvolvimento de aplicações gráficas para GNUstep com Project Center e Gorm é incrível, apesar de seu jeitão esquisito e do uso da tremendamente estranha linguagem de programação Objective C.

[]'s
Cacilhas

PS: Eu já estava morrendo de sono quando escrevi este artigo, portanto alguns erros de português podem ocorrer. Por favor me avisem!

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]

quarta-feira, 15 de outubro de 2008

Tcl/Tk

tcl.jpg Uma linguagem de script muito subestimada é Tcl/Tk.

Tcl significa Tool Command Language e foi criada por John Ousterhout para ser uma extensão de C, uma linguagem de agregação de componentes, mais ou menos como o Shell Script.

Tk é uma extensão gráfica para Tcl escrita em C. É um acrónimo para toolkit, nome comumente usado para frameworks gráficos. Em cada plataforma operacional, Tk oferece uma interface homogénia com o sistema de janelas. Outras linguagens também pode acessar a biblioteca Tk – como por exemplo Tkinter.

Tcl e Tk são de código aberto.

Vamos a alguns exemplos:
bash$ tclsh
% puts stdout {Olá Mundo!}
Olá Mundo!


Uma característica fortíssima de Tcl/Tk é que tudo são strings.

Cada token é uma string e strings não precisam de delimitadores, mas aspas (") e chaves ({ e }) podem ser usados como.

No exemplo acima, puts, stdout e {Olá Mundo!} são strings. A primeira string da linha referencia o comando a ser executado, no caso puts «coloca» uma string em um fluxo (stream).

A segunda string (stdout) indica qual o fluxo de saída, no caso a saída padrão.

A terceira string é aquela que será direcionada para a saída selecionada. No caso exibe Olá Mundo! na saída padrão.

Vamos a um exemplo um pouco mais complicado:
puts -nonewline stdout {Informe um número: }
gets stdin num
set dob [expr [set num] * 2]
puts stdout "O dobro é [set dob]"


Como já vimos, puts exibe uma string em uma saída, no caso novamente na saída padrão (stdout). A string -nonewline é um parâmetro que indica a puts para não inserir mudança de linha ao final.

O comando gets recebe uma string de uma entrada – no caso stdin, entrada padrão: o teclado – e a atribui a uma variável referenciada pela última string, num.

O comando set retorna o conteúdo de uma variável, se receber uma terceira string, atribui esta à variável e retorna.

Os colchetes executam o comando interno e tudo é substituído pela string retornada.

O comando expr trata todas as strings como uma expressão matemática, retornando o resultado.

Assim, [set num] retorna o valor da variável num, o expr pega esse valor de num, tratado como um número, e múltiplica por dois (2), retornando.

Daí os colchetes substituem novamente toda a expressão pelo valor retornado, ou seja, uma string representando o dobro do valor de num, então o set mais externo atribui essa valor à variável dob.

No final o último comando exibe na saída padrão a string, substituindo o que estiver entre colchetes. Como set dob retorna o valor de dob, acho que é óbvio o que é exibido.

Só que Tcl/Tk suporta um «apelido» para set, que é o cifrão ($), como em Shell e Perl (não como em PHP):
puts -nonewline stdout {Informe um número: }
gets stdin num
set dob [expr $num * 2]
puts stdout "O dobro é $dob"


Isso deixa o código bem mais limpo. =)

Outras extensões


Há ainda outras linguagens baseadas em Tcl/Tk que valem a pena serem citadas.

A primeira é [incr Tcl], uma variação orientada a objetos.

Outra é TclX (Tcl estendido), que, além de suportar todas os recursos de Tcl/Tk ainda suporta outros similares ao bash, como echo, loop e & (para execução paralela).

Finalizando


Este artigo é apenas um esboço. Outro mais completo pode ser encontrado nas Reflexões de Monte Gasppa e Giulia C..

[]'s
Cacilhas, La Batalema

quarta-feira, 24 de setembro de 2008

HTML Unescape em Python

Toda linguagem tem seus pontos altos e baixos, e suas próprias idiossincracias. Ontem eu esbarrei em algo interessante com relação a biblioteca padrão do Python.

Eu precisava de uma função que fosse mais ou menos equivalente ao html_entity_decode do PHP.

Eu estava usando a biblioteca webhelpers, que tem uma função escape, mas cadê a unescape? Olhando o código, descubro que a webhelpers usa uma função definida em webhelpers.utils, chamada cgi_escape, que seria uma implementação melhor de cgi.escape.

Minhas esperanças então se concentratam no módulo cgi. Mas uma olhada mais atenta me frustrou de novo: temos escape, mas não unescape. Até que um comentário num post do codare que, vejam só a ironia, mostrava como implementar um escape, me trouxe a solução: xml.sax.saxutils.unescape (e sua par, xml.sax.saxutils.escape).

Sei que teria sido muito fácil implementar uma função assim eu mesmo – até mesmo trivial. O ponto é que algo que eu considerava intuitivo e esperava encontrar naquele lugar, se mostrou muito mais difícil de achar do que minha intuição supôs. E eu não gosto de ter minha intuição enganada.

sábado, 12 de julho de 2008

Regressão linear

Em meu trabalho ora me envolvo em projetos de frontend – aplicações web –, ora me envolvo em projetos de backend. Ora trabalho em projetos de uma frente a outra.

No entanto sou fascinado por problemas conceituais e soluções matemáticas, portanto gosto muito mais de backend do que de frontend.

Em problemas conceituais, uma coisa magnífica é poder prever estados de um sistema por meio de regressão.

Há alguns tipos de regressão: regressão linear, regressão por potência, regressão logarítmica e regressão exponencial, mas todas podem ser reduzidas à regressão linear.

A função da regressão linear é simples:
&ycirc;=α + βx

Ou seja, a ordenada estimada é igual à intercepção (α) mais a inclinação (β) vezes a abscissa. O problema real é calcular α e β.

Há muitas e muitas formas de calcular esses, umas mais simples, outras mais interessantes. =)

Uma forma legal é usando o produto de uma matriz quadrada pelo vetor de coeficientes.
--

Assim sendo, o primeiro procedimento deve ser montar as matrizes. Hoje vamos trabalhar com Python.
from __future__ import division
__metaclass__ = type

class LinearRegression:
def __init__(self, pointset):
# pointset deve ser uma lista de pares x, y
self.__set = pointset

self.__createMatrices()
self.__calculateAlpha()
self.__calculateBeta()

def __createMatrices(self):
s = self.__set

m11 = len(s)
m21 = sum([e[0] for e in s])
m12 = m21
m22 = sum([e[0] ** 2 for e in s])

self.__m = [m11, m21, m12, m22]

self.__rvector = [
sum([e[1] for e in s]),
sum ([e[0] * e[1] for e in s])
]


Agora é preciso resolver o produto de matriz por vetor. Podemos traduzir isso por meio de determinantes:
--

Daí:
    def __calculateAlpha(self):
r = self.__rvector
m = self.__m

num = r[0] * m[3] - r[1] * m[2]
den = m[0] * m[3] - m[1] * m[2]

self.__alpha = num / den


Também é possível calcular β através de determinantes:
--

Daí:
    def __calculateBeta(self):
r = self.__rvector
m = self.__m

num = m[0] * r[1] - m[1] * r[0]
den = m[0] * m[3] - m[1] * m[2]

self.__beta = num / den


Falta somente agora o método para prever um estado estrapolado:
    def predict(self, x):
return self.__alpha + (self.__beta * x)


Esta mesma lógica pode ser implementada em outras linguagens. Há ainda outros algoritmos mais simples para calcular regressão linear, mas o apresentado é o mais didático.

[]'s
Cacilhas, La Batalema