segunda-feira, 11 de agosto de 2008

Configurando o tamanho do stack trace

Chicken dispõe de uma opção para configurar o tamanho do relatório de chamadas de procedimentos (stack trace) a ser exibido em caso de erro. Se a configuração for omitida, o valor 8 é usado, o que significa que serão exibidas as últimas 8 chamadas de procedimento.

Para algumas aplicações, esse número pode ser muito pequeno, mas pode ser aumentado com o parâmetro -:aNUMBER, onde NUMBER é o número de chamadas a ser mostrado (http://chicken.wiki.br/Using%20the%20compiler#runtime-options).

Abaixo está um exemplo de uma situação onde o aumento do stack trace pode ser útil:

(define (l) (error 'oops))
(define (k) (l))
(define (j) (k))
(define (i) (j))
(define (h) (i))
(define (g) (h))
(define (f) (g))
(define (e) (f))
(define (d) (e))
(define (c) (d))
(define (b) (c))
(define (a) (b))

(a)

Executando este código com o interpretador, temos:


$ csi -s oops.scm
Error: oops

Call history:

[e] (f)
[f] (g)
[g] (h)
[h] (i)
[i] (j)
[j] (k)
[k] (l)
[l] (error (quote oops)) <--

Com esse relatório, não fica claro que quem originou a chamada de l foi a. Mas, se aumentarmos o tamanho do relatório:


$ csi -:a14 -s oops.scm
Error: oops

Call history:

(a)
(a)
[a] (b)
[b] (c)
[c] (d)
[d] (e)
[e] (f)
[f] (g)
[g] (h)
[h] (i)
[i] (j)
[j] (k)
[k] (l)
[l] (error (quote oops)) <--

O parâmetro -:aNUMBER também é válido para programas compilados com o compilador de Chicken (csc):


$ csc oops.scm
$ ./oops
Error: oops

Call history:

oops.scm: 8 f
oops.scm: 7 g
oops.scm: 6 h
oops.scm: 5 i
oops.scm: 4 j
oops.scm: 3 k
oops.scm: 2 l
oops.scm: 1 error <--

$ ./oops -:a14
Error: oops

Call history:

oops.scm: 14 a
oops.scm: 12 b
oops.scm: 11 c
oops.scm: 10 d
oops.scm: 9 e
oops.scm: 8 f
oops.scm: 7 g
oops.scm: 6 h
oops.scm: 5 i
oops.scm: 4 j
oops.scm: 3 k
oops.scm: 2 l
oops.scm: 1 error <--

quarta-feira, 6 de agosto de 2008

Chicken Web REPL

Há algum tempo eu e o Vilson estávamos discutindo sobre REPLs na Web (não lembro o que desencadeou esse tópico -- também é possível que o tópico não tenha sido esse -- tá feio o caso da minha memória). Bem, o fato é que desta conversa surgiu a idéia de fazer um REPL via Web para Chicken. Lembrei do egg sandbox, do Chicken Playground (um ambiente chroot com uma instalação de Debian, Chicken e um monte de eggs) e fiz um Web REPL simples para Chicken.

Em seguida, o Vilson descobriu o EditArea (um editor de código em Javascript) e modificou para adicionar um suporte básico a Common Lisp. Enviou o código para mim e eu, com base nele, adicionei suporte básico a Scheme.

O resultado está em http://repl.ucpel.tche.br:8080.



Uma das funcionalidades interessantes do Web REPL é o uso de sessões HTTP para manter coisas como histórico de trechos de código submetidos ao avaliador e definições feitas na sessão. A implementação de sessões é feita com o egg http-session.

O Web REPL também usa os eggs web-scheme, ajax, spiffy-utils e spiffy (servidor web).

REPL de Chicken para acesso a bases de dados do Postgres

A seguir está uma forma de usar o REPL de Chicken (csi) como um REPL para bases de dados do Postgres.

Com isso, tem-se um REPL que possibilita a execução de consultas SQL e código Scheme. A implementação usa o próprio REPL do sistema Chicken e alguns eggs como: postgresql (para acesso so Postgres), readline (para edição de linhas de comando, histórico) e stty (para configuração do terminal na leitura de senhas).

O programa db-repl.scm usa como argumentos não interativos (opcionais) o usuário do banco de dados, o nome do host e a base de dados, os quais devem ser fornecidos no seguinte formato:

<usuario>@<host>/<base de dados>

Quando executado, o programa pede para o usuário digitar a senha.

A associação do REPL de Chicken com a base de dados é feita através da definição de comandos do REPL (toplevel-command). Na implementação mostrada abaixo, são definidos três comandos:

  • tables: mostras as tabelas da base de dados.

  • table: mostra estrutura da tabela dada como argumento (nome e tipo das colunas e se podem ou não ser nulas).

  • -: executa a consulta SQL dada como argumento.


Exemplos:

csi -s db-repl.scm mario@localhost/sgpc
Senha: *****

#;1> ,tables
perms
news
users_sites
ticket_comments
ticket_attachments
obj_type
acervos
autores
videos
audios
images
objects
texts
scanners
users
wiki
sites
tickets

#;1> ,table news
news_id integer NO
user_id integer NO
site_id integer NO
timestamp timestamp without time zone YES
title character varying(100) NO
news text YES

#;1> ,- select * from news
(#(2
1
1
#(2008 7 30 19 37 54 271625)
"Teste."
"teste

=== titulo")
#(3
1
1
#(2008 7 30 19 40 27 180765)
"Outra notícia!"
"Aqui vai o texto da notícia.

[[image:http://subversion.tigris.org/branding/images/logo.gif|Logo]]"))

Obviamente, os comandos para acesso ao banco de dados disponibilizados através do REPL podem ser estendidos. O texto PostgreSQL INFORMATION_SCHEMA fornece várias dicas de como extrair informações de bases de dados do Postgres.

Nesta implementação, o parâmetro pg-repl:conn armazena o objeto que representa a conexão com o banco de dados, de forma que ele pode ser usado pelos procedimentos do egg postgresql para a execução de consultas:


#;1> (define query "select * from news")
#;2> (vector-ref (car (pg:query-tuples query (pg-repl:conn))) 3)
#(2008 7 30 19 37 54 271625)

O código do programa (db-repl.scm) está a seguir:

(use utils postgresql readline stty regex (srfi 13))

(define pg-repl:conn (make-parameter #f))

(define (pg-repl:query . query)
(pg:query-tuples
(string-intersperse (map ->string query) "")
(pg-repl:conn)))

(toplevel-command
'-
(lambda ()
(pp (pg-repl:query
(with-output-to-string
(cut print (read-line)))))))

(toplevel-command
'table
(lambda ()
(let* ((table (string-trim-both (read-line)))
(cols
(pg-repl:query
"select column_name,data_type,"
"character_maximum_length,is_nullable "
"from information_schema.columns "
"where table_name = '" table "'")))
(if (null? cols)
(print "Tabela \"" table "\" nao existe.")
(for-each
(lambda (f)
(let ((colname (vector-ref f 0))
(type (vector-ref f 1))
(size (let ((size (vector-ref f 2)))
(if (pg:sql-null-object? size)
""
(conc "(" size ")"))))
(nullable (vector-ref f 3)))
(print
colname
(make-string
(- 30 (string-length colname)))
type size
(make-string
(- 30 (string-length (conc type size))))
nullable)))
cols)))))

(toplevel-command
'tables
(lambda ()
(for-each
(lambda (item)
(print (vector-ref item 0)))
(pg-repl:query
"select table_name from information_schema.tables "
"where table_type = 'BASE TABLE' "
"and table_schema not in "
"('pg_catalog', 'information_schema')"))))

(define (pg-repl:usage #!optional exit-code)
(print (program-name) " [<user>@<server>/<database>]")
(when exit-code (exit exit-code)))

(let ((args (command-line-arguments))
(user "postgres")
(host "localhost")
(db "template")
(passwd #f))

;; Restaura o terminal em caso de termino via C-c
(set-signal-handler! signal/int
(lambda (_) (stty '(echo))))

(unless (or (null? args) (equal? "\"\"" (car args)))
(let* ((cred (string-trim-both
(car args)
(cut memq <> '(#\space #\newline #\")))) ;"
(@tokens (string-split cred "@"))
(/tokens (if (null? @tokens)
'()
(string-split (cadr @tokens) "/"))))
(if (and (null? @tokens) (null? /tokens))
(pg-repl:usage 1)
(begin
(unless (null? @tokens)
(set! user (car @tokens)))
(unless (null? /tokens)
(set! host (car /tokens))
(set! db (cadr /tokens)))))))
(display "Senha: ")
(pg-repl:conn
(pg:connect
`((user . ,user)
(dbname . ,db)
(host . ,host)
(password . ,(with-stty '(not echo) read-line)))))
(current-input-port (make-gnu-readline-port))
(gnu-history-install-file-manager
(string-append (or (getenv "HOME") ".")
"/.csi.history"))
(newline)
(repl))