Elmord's Magic Valley

Software, lingüística, mitologia nórdica e rock'n'roll

FISL 16

2015-07-11 23:27 -0300. Tags: comp, life, freedom

Nos últimos quatro dias ocorreu o 16º Fórum Internacional do Software Livre. Minha proposta de palestra sobre o lash não foi aceita (o que por um lado foi bom, porque o projeto anda meio dormente devido a obrigações mestrariosas e má administração temporal), mas eu assisti uma porção de palestras, a maioria das quais foram bem boas. Também encontrei o Marcus Aurelius por lá, que trabalhou como voluntário na tradução do site do FISL para o esperanto, e troquei umas palavras com seu Lucas Zawacki, que estava no espaço do Dumont Hackerspace (e que ganhou a Hackathon EBC/FISL 16 com o aplicativo Pautaí, por sinal; congrats!).

Eis um resumo das palestras que eu assisti por lá. O site do FISL contém a programação completa, com link para os vídeos da maioria das palestras.

Dia 1

Email criptografado: usando GPG e icedove para criptografar sua correspondência, por Felipe Cabral

A idéia era que fosse uma oficina, mas acabou sendo mais uma palestra sobre conceitos básicos de PGP. Também foi visto um programinha chamado Seahorse (pacote seahorse no Debian) para gerar e administrar chaves de PGP e afins. Eu fui na palestra achando que ia aprender alguma coisa que me ajudasse a fazer o PGP funcionar no Claws Mail. Turns out que no Thunderbird/Icedove, tudo o que é necessário para fazer o PGP funcionar é instalar o add-on Enigmail (pacote enigmail no Debian), seguir os passos de configuração que serão apresentados depois que o Enigmail é instalado, e ser feliz. Me deu até vontade de experimentar o Thunderbird de novo, mas tem que ver isso aí. Meanwhile, continuo unenlightened quanto ao Claws.

Half my life with Perl, por Randal Schwartz

O autor do Programming Perl (a.k.a. Camel Book), entre outros, e atual host do podcast FLOSS Weekly, conta sua vida e seu envolvimento com o Perl. Foi uma palestra bem interessante, mesmo eu não conhecendo grandes coisas do Perl e sua comunidade.

Negócios em Software Livre, isso existe?, por Vagner Fonseca

Uma palestra sobre como ganhar dinheiro vendendo serviços baseados em software livre para empresas, tais como suporte, customização de programas livres de acordo com as necessidades da empresa, soluções de monitoramento de rede e de uso de recursos, entre outros. Também foram discutidos tipos de contrato (por projeto, por hora, contrato de suporte) e quando cada tipo vale a pena (basicamente, cobrar por projeto só vale a pena para coisas bem simples e punctuais, caso contrário corre-se o risco de algo demorar muito mais do que o previsto e o cidadão acabar efetivamente pagando para trabalhar; em contrato de suporte se cobra menos do que em um contrato por hora, mas tem-se a estabilidade e se consome um tempo mais limitado por semana em uma empresa, então é possível atender mais de uma ao mesmo tempo), entre outras coisas.

"Enemy spotted - Applying infovis at security field", por Felipe Afonso Espósito

Uma palestra sobre visualização de dados e como isso pode ser usado com dados de segurança. Não tenho muito mais o que comentar.

Let's Encrypt: Uma Autoridade Certificadora Gratuita e Automatizada, por Seth Schoen

Seth Schoen, membro da EFF, falou (em português!) sobre o andamento do Let's Encrypt, uma iniciativa da EFF, Mozilla, University of Michigan e outros para criar uma autoridade certificadora e um mecanismo para obtenção e validação automática e gratuita de certificados digitais, e instalação automática em servidores web, eliminando uma barreira que existe atualmente para se usar HTTPS. A data prevista para o serviço ser disponibilizado para o público é 14 de setembro deste ano.

Eu pretendia assistir a APIs em Tempo Real Usando Websockets em PHP, mas a sala lotou e eu não cheguei a tempo. Ao invés disso, eu e o Marcus ficamos tentando entender as entranhas do cua-mode do Emacs, inter alia.

Dia 2

Encontro de Hackers GNU

Alexandre Oliva (FSF Latin America), Felipe Sanches, e Deborah Anne Nicholson (Open Invention Network, MediaGoblin) falaram sobre uma porção de projetos relacionados com liberdade digital. Entre eles, o Twister, uma plataforma peer-to-peer de microblogging, e MediaGoblin, uma plataforma descentralizada de compartilhamento de mídia. Também foram mencionadas algumas idéias de projetos que seriam interessantes de criar, como uma plataforma peer-to-peer para distribuição de código (eu mencionei que já existe um projeto nesse sentido).

Outra idéia interessante que o Alexandre Oliva mencionou é que os ambientes computacionais modernos são pouco programáveis (sounds weirdly familiar), e que seria interessante criar uma biblioteca que facilitasse para o usuário descobrir que funções o programa chama quando se clica em algum botão ou menu e tornasse esses programas programáveis. O conceito é parecido com o que eu vejo como ideal de ambiente computacional, mas eu nunca tinha pensando em pôr essa funcionalidade em uma biblioteca, ao invés de algo mais fundamental. É uma abordagem interessante de se pensar.

O Felipe Sanches também comentou a questão de firmware aberto, as dificuldades envolvidas em reverse-engineering de firmware e hardware, e a necessidade de incentivar o compartilhamento de informação sobre técnicas de engenharia reversa.

Programação Orientada a Objetos em C puro: o caso do htop, por Hisham Muhammad

O autor do htop falou sobre as técnicas de programação que usou no desenvolvimento desse programa, em particular o uso de orientação a objetos em C. Eu já sabia um pouco sobre o assunto, mas a palestra valeu a pena mesmo assim. Mais para o final, o Hisham falou sobre o uso de collections para gerenciar "ownership" de ponteiros (alocação e liberação de memória) em C, e como isso torna a gerência manual de memória do C menos horrível de se usar. Por fim, ele falou sobre o dit, um editor de texto que ele escreveu reusando componentes do htop.

Assinatura digital e o padrão ICP-Brasil, por Paulo Cesar Barbosa Fernandes

Uma palestra sobre o padrão brasileiro de assinaturas digitais, seus aspectos legais, e algumas informações gerais sobre o uso de assinaturas digitais. Talvez a coisa mais importante que eu aprendi na palestra é que uma assinatura digital obtida adequadamente seguindo o padrão ICP-Brasil tem o valor legal de uma assinatura de papel (embora ainda haja legislação a ser atualizada para levar isso em conta).

Desvendando o IPv6: Tecnologia indispensável para o Futuro da Internet, por Lucenildo Lins de Aquino Júnior

Nada muito aprofundado tecnicamente, mas foi uma boa palestra. O mais surprising aqui foi saber que uma boa parte do tráfego da Internet já é em IPv6, inclusive no Brasil (pelo menos em São Paulo), mas não lembro mais os números. Até me deu alguma esperança de que teremos o bendito IPv6 em alguns anos.

What's new in systemd in 2015, and what's coming in 2016, por Lennart Poettering

Exatamente o que o título diz. Foram mencionadas funcionalidades de configuração de rede, resolução de DNS, containers, e todas essas coisas que nós nos perguntamos se deviam mesmo fazer parte do systemd, mas isso fica para outra discussão.

Finding a Great Project to Work On, or Great People to Work on Your Project, por Deborah Anne Nicholson

Uma palestra muito boa sobre, entre outras coisas, como fazer com que um projeto seja "welcoming" a novos colaboradores, especialmente não-desenvolvedores (pessoas envolvidas com tradução, documentação, divulgação, arte, etc.). Alguns dos tópicos mencionados são coisas que deveriam ser senso comum, mas infelizmente não são, tais como ter uma descrição de o que é o projeto na página inicial, links para mailing lists, FAQs, informação de contato, etc. Outro ponto mencionado é que se você como desenvolvedor não tem grandes skills comunicativos, encontre uma pessoa para fazer esse papel no seu projeto. Also, incentive e recompense os esforços dos colaboradores. Provavelmente é melhor assistir o vídeo do que eu tentar explicar (talvez eu tenha skills comunicativos sub-ótimos).

Acho que esse foi o melhor dia do FISL para mim.

Dia 3

Ocorreu uma miniDebConf (conferência sobre o Debian) durante esse dia na sala 41D.

Debian: o que é, e como funciona, por Antonio Terceiro

Uma palestra introdutória sobre o Debian, que no entanto me ensinou sobre uma porção de recursos online do Debian que eu não conhecia, tais como o blog Bits from Debian, o Ultimate Debian Database (que é mais útil para desenvolvedores do Debian), um pastebin, a página com informações para se tornar um novo membro do Debian, páginas onde se pode navegar pelos fontes do Debian e fazer buscas textuais no código, e um security bug tracker.

Não sou programador, como posso ajudar o Projeto Debian?, por Luiz Guaraldo

Não lembro mais o que foi visto nessa.

Containers and systemd, por Lennart Poettering

Uma palestra sobre o suporte a containers do systemd. A palestra começou com "everyone knows what containers are", except I didn't, então embora as funcionalidades apresentadas tenham parecido interessantes, eu meio que fiquei boiando sobre como as coisas funcionam.

Empacotamento de software no Debian, por João Eriberto Mota Filho

Uma palestra muito boa sobre o processo de criar um pacote Debian, cobrindo as ferramentas utilizadas, o ambiente de empacotamento, os arquivos que se deve editar, etc. Se você pretende criar um .deb na vida, assista.

Dependências de pacotes de código fonte, por Thadeu Lima de Souza Cascardo

Essa palestra pode ser resumida a "pacotes de fonte possuem dependências". Foram vistos alguns conceitos como dependências de compilação vs. dependências de execução, mas nada do que eu esperava ver foi visto, tal como como baixar pacotes fonte e suas dependências e compilá-los usando as ferramentas do Debian. A palestra terminou com o palestrante debugando um script em Perl que ele escreveu para calcular as dependências recursivas de um pacote fonte, e eu fui embora fazer outras coisas pelo mundo.

Dia 4

Decidi almoçar em casa e só consegui chegar às 13h e pouco no FISL.

Javascript e as novidades nas funções em ES2015+, por Felipe Nascimento de Moura

Cheguei uns 10 minutos atrasado na palestra. Ficou bem pouco claro para mim o que eram coisas novas do JavaScript vs. coisas que já existem. Foram vistas features como generators (que o Firefox já suporta há mais de oito mil anos, mas agora eles vão entrar no EcmaScript oficial, com uma sintaxe levemente diferente), execução assíncrona, setInterval para executar ações periodicamente, "arrow functions" (uma sintaxe nova para funções anônimas, e que captura o valor de this), e outras coisas que ya no recuerdo más.

HTTP: passado, presente e futuro, por Luiz Fernando Rodrigues

Aprendi o suficiente para querer me informar melhor sobre o HTTP/2, but that's it.

Programming Efficiently, por Jon "Maddog" Hall

A palestra foi mais sobre a importância da eficiência em programação, e não sobre técnicas de programação eficiente. O Maddog também falou sobre o fato de que muita gente sai de uma escola/faculdade sem saber como funciona o hardware, que o Raspberry Pi foi criado para ser uma máquina "hackable" e boa para o aprendizado, e que ele usou alguns Banana Pi para montar um cluster pequeno, barato e com bom poder computacional que pode ser usado para ensinar high-performance computing e afins. A palestra foi bem boa, embora não fosse sobre o que eu pensei.

How much of your computer is non-free, and how worried should you be?, por Matthew Garrett

Palestra sobre o fato de que nossos dispositivos contêm bem mais processadores do que normalmente a gente pensa, normalmente rodando software proprietário que não temos nem como ver nem como modificar. Exemplos particularmente alarmantes são HDs (com uma menção de uma galera que conseguiu fazer um HD bootar um kernel Linux) e SD cards. Worse still, muitos desses dispositivos só aceitam firmwares assinados pelo fabricante, usualmente encriptados, o que nos impede de analisar e controlar o comportamento desses dispositivos. Recomendo assistir o vídeo.

Archlinux: Você no comando, por Israel Lopes dos Santos

Uma palestra introdutória sobre o Arch Linux, explicando a filosofia da distribuição, o gerenciador de pacotes, o fato de que criar um pacote para o Arch é relativamente fácil e que qualquer um pode submeter um pacote para o Arch User Repository (AUR). Na seção de perguntas, um cidadão resolveu criticar o uso de "Linux" ao invés de "GNU/Linux" de uma maneira indireta/irônica que o palestrante não entendeu. Foi bem desnecessário; ser indireto e irônico não ganha ninguém à causa, na minha humilde opinião.

That was it. 9/10 would go again.

3 comentários

Como usar o modelo LaTeX do INF/UFRGS para TCCs e afins

2015-06-26 01:16 -0300. Tags: comp, latex, academia

Salve! Como muita gente cai neste blog procurando pelos modelos LaTeX do INF/UFRGS, e eu parei de disponibilizar o meu "pack" em favor do repositório oficial, resolvi escrever um tutorial de como usar o modelo.

Instalando o LaTeX

GNU/Linux

Nas distribuições Debian, Ubuntu e afins, você pode instalar os pacotes do LaTeX com o comando:

sudo apt-get install texlive-latex-base texlive-lang-portuguese

Se você já possui o comando pdflatex, você já tem o LaTeX instalado e pode dispensar esse passo. Porém, é possível que você não tenha o pacote texlive-lang-portuguese, o que pode causar problemas como hifenação incorreta e strings na língua errada na capa do modelo e outras partes com texto pré-definido. Se isso acontecer, verifique se o pacote está instalado. Mesmo que o seu trabalho seja em inglês, o modelo pode não funcionar corretamente na ausência desse pacote.

Outros sistemas

É possível usar o LaTeX em outros sistemas operacionais, como o Windows, mas nunca fiz isso e não sei como é a experiência. Se alguém tiver alguma dica, deixe nos comentários.

ShareLaTeX

Uma outra opção é usar o ShareLaTeX.com, uma espécie de Google Docs para documentos LaTeX. A vantagem é que você não precisa instalar nada, e pode acessar de qualquer lugar. A desvantagem é que se o servidor do ShareLaTeX.com sair do ar você está ralado, então eu recomendo baixar regularmente o .zip com os arquivos para não correr esse risco.

Instalando o iiufrgs

O pacote iiufrgs, que contém os modelos do INF/UFRGS, fica disponível em um repositório do GitHub. Para baixá-lo, você pode clicar em Download ZIP na barra lateral da página, ou usar o comando git para clonar o repositório:

git clone https://github.com/schnorr/iiufrgs.git

A vantagem de usar o git é que você pode usar o comando git pull dentro do diretório do repositório para atualizá-lo.

Dentro do repositório, há um diretório inputs, que contém o pacote propriamente dito, e um diretório examples, que contém alguns documentos de exemplo.

Para usar o modelo, é necessário que os arquivos em inputs estejam no path do LaTeX. Há diversas maneiras de fazer isso:

That's it.

Preparando o documento

O diretório exemplos contém uma porção de (adivinhe só?) arquivos de exemplo para os diversos tipos de documentos:

Copie o arquivo apropriado para o diretório onde você vai colocar seu projeto LaTeX (que pode ser um diretório vazio qualquer que você criou, ou uma cópia do inputs caso tenha optado por não instalar o iiufrgs no local padrão, ou faça upload do arquivo de exemplo para o seu projeto no ShareLaTeX). Renomeie o arquivo se for do seu interesse.

Agora é necessário editar algumas partes do arquivo.

A linha \documentclass indica o tipo de documento a ser gerado. Ela tem a seguinte cara:

\documentclass[cic,tc]{iiufrgs}

Como você já copiou um documento de exemplo do tipo apropriado, a princípio você não tem que mexer nessa linha. Se seu documento for em inglês, adicione a opção english na linha:

\documentclass[cic,tc,english]{iiufrgs}

Há outras opções, que estão documentadas nos comentários do arquivo.

Como mencionado, não há um exemplo de Plano de Estudos e Pesquisa; para isso, use o arquivo ppgc-diss.tex e altere a opção diss para pep na linha \documentclass. Você provavelmente também vai querer apagar coisas como dedicatória e agradecimentos do documento.

As próximas coisas a alterar são a linha \title, que contém o título do trabalho, e \author, que contém o nome do autor. Essa linha tem o formato:

\author{último nome}{primeiro nome e nomes do meio}

A linha \advisor, que contém o nome do orientador, segue o mesmo formato, mas inclui também o título (Prof. Dr., por exemplo). Se houver um co-orientador, descomente a linha \coadvisor e preencha da mesma maneira.

Descomente a linha \location e preencha com a cidade e UF adequados (i.e.,

\location{Porto Alegre}{RS}

que por alguma razão não é o default nos arquivos de exemplo; go figure).

O documento possui uma série de linhas comentadas definindo comandos \nominataWhatever. Na segunda página do PDF gerado a partir do modelo, fica um quadrinho com os nomes dos responsáveis por diversos setores da universidade (reitor, bibliotecário-chefe, etc.). Confira no PDF se os nomes inclusos estão atualizados. Se algum não estiver correto, descomente a linha correspondente e substitua o nome. Note ainda que alguns títulos têm concordância de gênero (e.g., "Bibliotecário-chefe" vs. "Bibliotecária-chefe"), então lembre-se de ajustar a linha com o título do cargo também. Você pode descobrir quem é o bibliotecário/a-chefe atual no site da biblioteca do INF.

Mais adiante, há uma série de comandos \keyword; estas aparecem como palavras-chave do documento na página do resumo/abstract. Escolher as palavras-chave é uma arte esotérica e misteriosa; consulte seu orientador em caso de dúvida.

A próxima coisa a mudar é a "dedicatória", que costuma ser uma citação de sua preferência. Ela é opcional, mas é uma das partes mais divertidas de escrever a monografia anyway. Logo depois, vem a seção de agradecimentos, que também é opcional (or so they say).

Em seguida, vem o o bloco \begin{abstract}, onde vai o resumo na língua do documento. Não pode haver linhas em branco entre o fim do texto e o \end{abstract}; caso contrário, a compilação do documento dá um erro de "There's no line here to end". Go figure.

A seguir, vem o bloco \begin{englishabstract}{keyword1, keyword2, …}. Apesar do nome, o que vai nesse bloco é o resumo na outra língua: se a monografia for em português, aí vai o abstract em inglês; se a monografia for em inglês, aí vai o abstract em português. O segundo argumento do bloco é a lista de palavras-chave/keywords (as mesmas que você usou antes com os comandos \keyword, mas na outra língua). Também não pode haver linhas em branco antes do \end{englishabstract}.

A seguir, vem a lista de abreviaturas; confira as instruções nos comentários. Note, entretanto, que essa lista deveria estar em ordem alfabética, que eu saiba. Em seguida, há a lista de símbolos, que vem comentada por padrão, mas você pode descomentá-la e preenchê-la de maneira análoga se você usa algum símbolo matemático que requeira explicação no seu documento.

A seguir vêm os comandos \listoffigures, \listoftables e \tableofcontents, que geram automaticamente as respectivas listas. Você não precisa mexer nesses comandos.

Finalmente, vem o texto do documento propriamente dito. No documento de exemplo, há algumas instruções sobre citações, figuras e outros detalhes, que você pode (deve) ler, mas no final você deve apagar tudo desde o \chapter{Introdução} até logo antes do \bibliographystyle e substituir pelo conteúdo da sua monografia. Se você preferir criar arquivos separados para cada capítulo para se organizar, você pode usar o comando:

\input{arquivo.tex}

que é uma espécie de #include, que insere o conteúdo do arquivo especificado nesse ponto do documento. Você pode incluir arquivos em outros diretórios. Por exemplo, você pode criar um diretório capitulos e usar \input{capitulos/introducao.tex}.

A linha \bibliography{nome} diz o nome (sem a extensão) do arquivo onde estão suas referências bibliográficas (no formato BibTeX). Por exemplo, se a linha diz:

\bibliography{biblio}

então o LaTeX vai procurar as referências no arquivo biblio.bib.

Compilando o documento

Se você está usando o ShareLaTeX, tudo o que você tem que fazer é clicar no botão "faz". Há outras IDEs de edição de LaTeX com funcionalidades análogas. Se não é o seu caso, continue lendo.

O comando principal para compilação de documentos LaTeX é o pdflatex. A sintaxe básica é pdflatex arquivo.tex. Se a compilação for bem-sucedida, será gerado um arquivo.pdf com o resultado, bem como um arquivo.log com mensagens do LaTeX e outros arquivo.* usados internamente pelo LaTeX.

Se ocorrer um erro durante a compilação, por padrão o pdflatex cai em um prompt de depuração muito doido. Para sair do prompt, digite x e dê ENTER. Para evitar cair no prompt, use a opção -halt-on-error antes do nome do arquivo.

Outra opção útil é -file-line-error, que precede os erros com nome-do-arquivo:linha:. Alguns editores de texto, como Emacs e Vim, possuem funcionalidades para rodar um comando externo de compilação, coletar as mensagens de erro e saltar diretamente para a linha onde ocorreu o erro, desde que as mensagens de erro contenham a localização do erro em um formato reconhecido (que é o que essa opção faz).

O comando pdflatex realiza apenas uma passada pelo arquivo. Porém, coisas como gerar os números das páginas no índice e números de seção em referências requerem duas passadas pelo arquivo. Além disso, a geração de bibliografia requer a chamada do comando bibtex, e depois disso é necessário rodar o pdflatex novamente (mais duas vezes). O mais prático é escrever um Makefile para rodar todos esses comandos de uma vez. Crie um arquivo chamado Makefile com o seguinte conteúdo:

# As linhas 'export' só são necessárias se você optou por não instalar o
# iiufrgs em um local padrão e não colocar seu documento e os arquivos de
# 'inputs/' no mesmo diretório.
export TEXINPUTS = .:caminho-completo-do-diretório-inputs:
export BSTINPUTS = $(TEXINPUTS)
export BIBINPUTS = $(TEXINPUTS)

PDFLATEX = pdflatex -halt-on-error -file-line-error
FILENAME = nome-do-seu-documento-sem-a-extensão

all:
	$(PDFLATEX) $(FILENAME).tex
	$(PDFLATEX) $(FILENAME).tex
	bibtex $(FILENAME)
	$(PDFLATEX) $(FILENAME).tex
	$(PDFLATEX) $(FILENAME).tex

Note que as linhas indentadas devem ser precedidas de um caractere TAB, não de espaços. Note também que o bibtex recebe o nome do arquivo LaTeX (não o nome do arquivo de bibliografia), sem a extensão.

Feito isso, agora é só rodar make para compilar seu documento. No Vim, você pode usar o comando :make para compilar, e :copen para listar os erros encontrados. No Emacs, você pode usar M-x compile. (No latex-mode do Emacs também há um comando tex-compile, acessível via C-c C-c, que serve para compilar o documento, mas ele não vai usar o Makefile. Eu sou novo nestas terras do Emacs, então não sei bem como isso funciona. Também existe um modo mais avançado de edição de LaTeX chamado AUCTeX, mas não tenho experiência com ele.)

lastpage.sty

O iiufrgs usa um arquivo lastpage.sty para determinar o número da última página, que ele inclui no quadrinho CIP na segunda página do documento. Se você se deparou com o erro:

! LaTeX Error: File `lastpage.sty' not found.

você tem duas opções:

Bibliografia

A maneira normal de trabalhar com referências bibliográficas no LaTeX é através do BibTeX. O iiufrgs vem com um arquivo BibTeX de exemplo, biblio.bib, que aparentemente saiu da coleção pessoal de alguém e foi gerado pelo JabRef (pacote jabref no Debian/Ubuntu/etc), um gerenciador gráfico de bibliografias em BibTeX. Eu nunca usei o JabRef, mas se você gosta de ferramentas gráficas, pode querer experimentá-lo. Caso contrário, continue lendo.

O arquivo .bib é basicamente uma seqüência de entradas bibliográficas. Uma entrada tem mais ou menos essa cara:

@tipo-de-publicação{label-da-sua-escolha,
  title = {Título da Publicação},
  author = {Turing, Alan and Church, Alonzo and Gödel, Kurt},
  year = {2101},
  booktitle = {Proceedings of the 42nd Intergalactic Conference on Computability Theory},
  pages = {123--134},
  publisher = {ACM}
}

tipo-da-publicação é algo como article (artigo em journal), inproceedings (artigo de conferência), book (livro inteiro), incollection (capítulo de livro, quando o autor do capítulo é diferente do autor/editor do livro), electronic (publicação na Internet), ou uma infinidade de outras possibilidades. O tipo de publicação define os campos que podem aparecer dentro da entrada e o formato como ela aparece na bibliografia do PDF gerado.

label-da-sua-escolha é a label que você vai usar com os comandos \cite e afins.

BibTeX é um mundo à parte e eu não vou tentar explicar tudo o que há para explicar aqui; para mais informações, procure por "BibTeX" na Internet. Deixo, entretanto, algumas dicas:

Fim do conto

Acho que era isso. Em um post futuro, pode ser que eu escreva uma introduçãozinha aos comandos de formatação do LaTeX, mas com o que aparece no arquivo de exemplo já dá para se virar um pouco. Você pode dar uma olhada nos outros posts com a tag latex.

Dúvidas, sugestões e correções podem ser deixadas nos comentários.

2 comentários

Trocando de janela pelo nome

2015-06-23 02:50 -0300. Tags: comp, unix

Por uma série de acidentes enquanto experimentava window managers hoje,

  1. Eu me dei conta de que seria bem conveniente poder trocar de janela digitando uma parte do título ao invés de procurar na barra de tarefas ou dar vinte Alt+Tabs até encontrar a janela. (Eu me dei conta disso quando, depois de não conseguir achar a janela que eu queria no i3, a minha primeira reação foi querer dar C-x b (o comando switch-buffer do Emacs) e digitar o nome da janela.)
  2. Eu descobri ferramentas apropriadas para tornar isso possível.

[Screenshot do programa 'dmenu']

Você vai precisar de:

Com isso, podemos escrever um pequeno script para apresentar um menu e selecionar a janela escolhida.

O script

#!/bin/bash

option="$(
    wmctrl -xl |
        #         \1       \2       \3       \4       \5
        #         win-id   desktop  class    hostname title
        sed -r 's|([^ ]* +)([^ ]* +)([^ ]* +)([^ ]* +)(.*)|\1 \3 \5|' |
        sort -k 3 |
        dmenu -i -l 10
    )" || exit 1

wmctrl -ia "${option%% *}"

wmctrl -l lista as janelas existentes. A opção -x inclui a classe da janela na listagem. O sed não é estritamente necessário, mas deixa a lista menos poluída removendo campos desnecessários; você pode alterar essa linha para escolher os campos. O ID da janela é meio irrelevante para o usuário, mas precisamos dele para poder passá-lo ao wmctrl para ativar a janela.

sort -k 3 ordena o menu pelo título da janela. Você pode comentar essa linha fora se não quiser ordenar a lista, ou mudar os parâmetros para obter uma ordem diferente (e.g., sort -k 2 para ordenar pela classe).

Quanto ao dmenu, a opção -i faz com que ele ignore maiúsculas vs. minúsculas ao filtrar as opções pelo texto digitado pelo usuário. -l 10 indica que queremos uma opção por linha, e que no máximo 10 linhas devem ser mostradas de cada vez. Por padrão, o dmenu usa apenas uma linha e mostra as opções lado a lado. (Uma coisa meio ruim do dmenu é que ele não dá nenhuma indicação de que é possível scrollar o menu; ele só mostra as primeiras N opções e as demais ficam escondidas.)

wmctrl -a JANELA ativa a primeira janela cujo título contenha a string specificada. Como queremos que a seleção seja inambígua, utilizamos a opção -i, que permite especificar o ID da janela ao invés do título. Para extrair o ID da seleção, removemos tudo depois do primeiro espaço na string ("${option%% *}").

Instalação

Salve o script no local de sua preferência, dê permissão de execução a ele (chmod +x nome-do-script), e associe-o a alguma tecla de atalho no seu ambiente gráfico favorito. Por exemplo, no IceWM isso pode ser feito adicionando no ~/.icewm/keys uma linha como:

key "Super+Tab" /caminho/do/script

substituindo Super+Tab pelo atalho de sua preferência (Super é a tecla "janelinha").

Para mais informações e possibilidades, consulte a manpage dos programas.

Comentários

Emacs, again

2015-06-05 02:30 -0300. Tags: comp, editor, emacs

No último sábado eu resolvi experimentar usar o Emacs de novo. O plano era ficar usando durante um mês e depois escrever um post relatando a experiência. Porém, como ao longo dos três anos de blog eu observei que em geral quando eu deixo um post para escrever depois eu acabo não escrevendo, em parte porque eu acabo esquecendo o que eu ia escrever, resolvi escrever um post inicial agora. (Além disso, quando eu comecei a escrever esse post eu não estava conseguindo dormir e precisava passar o tempo.)

So far, estou curtindo horrores.

Da primeira vez que eu tentei usar o Emacs, eu larguei de mão em dois ou três dias. Acho que o principal fator de diferença foi que dessa vez eu resolvi usar a versão gráfica do Emacs. Assim, quando eu não consigo fazer alguma coisa pelos comandos de teclado eu posso recorrer ao mouse e get stuff done. Dito isso, como eu não sou exatamente um fã do mouse, na prática eu acabo me motivando a aprender os comandos anyway.

(Um vício bem mais difícil de eu me livrar é usar as setas, Home/End e afins ao invés dos comandos "tradicionais" do Emacs. Right now eu larguei um pano de prato por cima do lado não-alfabético do teclado para ver se me habituo a não usar essas teclas.)

Mas por quê?

Tudo começou porque eu estava meio de saco cheio do Claws Mail, experimentei o Thunderbird e não gostei muito (não lembro mais por quê), e aí lembrei do Gnus e resolvi experimentar. No fim das contas eu consegui resolver o problema que eu estava tendo com o Claws comentando fora um teste no código-fonte e não curti muito o Gnus, mas a essas alturas eu já tinha mexido um bocado no Emacs e ressurgiu o interesse, reforçado pelos seguintes acontecimentos:

E aqui estamos.

Coming from Vim...

Em alguns aspectos, o Emacs pode ser mais "difícil" de usar do que o Vim. Seguem algumas observações nesse sentido.

Enquanto usar o Vim sem nenhuma customização é relativamente ok, usar o Emacs sem nenhuma customização não é uma experiência tão agradável. Desde que eu comecei a usar o Emacs, quase todo dia eu adiciono alguma coisa no meu ~/.emacs, que provavelmente vai continuar crescendo na mesma taxa por um bom tempo. (De certa forma o ponto do editor é ser customizado/reprogramado to death, então eu desconfio que enquanto eu usar o Emacs eu não vou parar de mexer no ~/.emacs; porém, eu espero mexer nele com menos freqüência depois de algum tempo de uso.)

Além disso, embora o Emacs ganhe bonito em flexibilidade, certas configurações simples são mais fáceis de fazer no Vim do que no Emacs. Por exemplo, no Vim define-se um keybinding novo em termos das teclas que teriam que ser pressionadas para realizar o comando desejado. No Emacs, cada tecla é associada a uma função que é executada quando a tecla é pressionada, o que por um lado é bem mais limpo do que a maneira como o Vim faz as coisas (definir uma ação complexa em termos de keypresses pode ser um inferno), mas por outro lado exige que uma função em Emacs Lisp seja definida sempre que se quer associar uma seqüência de comandos a uma tecla.

Dito isso, Emacs Lisp é uma linguagem infinitamente mais agradável de usar do que Vimscript. Além disso, é fácil descobrir qual é o nome da função associada a cada tecla, usando os comandos Ctrl-h k (describe-key) e Ctrl-h c (describe-key-briefly), o que facilita na hora de definir funções novas em termos das funções das teclas existentes.

Além disso, embora eu tenha mudado as cores do editor para texto branco no fundo preto, o restante do esquema de cores se adaptou sozinho, e eu achei o esquema de cores tão bom que nem desativei o syntax highlighting (que normalmente é a primeira coisa que eu faço quando uso o Vim em outro computador). A única exceção foi o AUCTeX, que escolheu umas cores diferentes do resto do editor por alguma razão.

Comandos

Enquanto o Vim possui um modo específico para dar comandos (o Normal mode), o que permite que os comandos sejam teclas simples1, o Emacs possui apenas um "modo" (no sentido Vim da palavra2), e os comandos de edição normalmente são introduzidos por um keystroke envolvendo Control ou Alt (Meta no linguajar emacsístico). Muitos comandos exigem uma seqüência de keystrokes: o comando para abrir um arquivo é Ctrl-x Ctrl-f, por exemplo, e o comando para abrir um arquivo em uma nova janela é Ctrl-x 4 Ctrl-f. Da primeira vez que eu experimentei o Emacs eu achei esses comandos totalmente bizarros e meio que larguei ele de mão por causa disso. Porém, agora que eu resolvi usar o editor de novo com a mente em um modo (heh) mais propenso a aceitar coisas novas (e com a interface gráfica para me salvar nos momentos difíceis), eu tenho me adaptado aos comandos novos relativamente fácil.

Embora seja tentador logo no começo alterar o keymap para usar comandos mais intuitivos, eu resolvi fazer um esforço para aprender os comandos convencionais do editor, e só definir keybindings novas para comandos que eu mesmo criei ou que não têm uma keybinding padrão, for most part. Até agora tem sido relativamente tranqüilo. Se depois de mais tempo de uso eu vir que algum keybinding realmente não me agrada, eu mudo. O mais difícil tem sido lembrar de usar Ctrl-s no Emacs e Ctrl-f no Firefox para procurar texto, e usar Ctrl-f (forward) para avançar um caractere e Ctrl-b (back) para voltar um caractere, e analogamente Meta-f e Meta-b para avançar e voltar uma palavra. Esses aí eu sou capaz de acabar rebindando para Ctrl-. e Ctrl-, ou algo do tipo (ou me conformar em usar as setas mesmo), se não me habituar nas próximas semanas.

O Emacs usa uma notação especial para descrever keystrokes. Basicamente, C-x significa Ctrl-x, M-x significa Meta-x (Alt-x), C-M-x significa Ctrl-Meta-x, RET é o Enter, SPC é o espaço, DEL é o backspace, e seqüências de teclas são escritas separadas por espaço (C-x C-f significa "tecle Ctrl-x, e depois tecle Ctrl-f"). Eu vou usar essa notação no restante do post.

Uma coisa legal do Emacs é que todo comando possui um nome (o nome da função Lisp que o implementa). Mesmo quando não se sabe o atalho de teclado que ativa o comando, é possível dar M-x e digitar o nome do comando (que freqüentemente é relativamente fácil de adivinhar, pois a nomenclatura é mais ou menos consistente; além disso é possível usar TAB para completar os nomes). Se o comando possui um atalho associado, após executar o comando o Emacs mostra uma mensagem como You can run the command `mark-paragraph' with M-h, e assim você aprende a tecla do comando.

Outra coisa que ajuda é que o help do Emacs é muito bom de usar. Todos os comandos de ajuda começam com C-h; C-h ? mostra todas as opções de ajuda. C-h f mostra a documentação de uma função ou comando, C-h k mostra a documentação do comando associado a uma tecla, C-h c mostra o nome do comando associado à tecla, C-h m mostra a documentação do modo atual, C-h r abre o manual do Emacs, entre outros. Como já mencionado, a documentação de uma função vem com um link para o código-fonte, quando disponível (no Debian, é necessário instalar o pacote emacs24-el). O link não é parte do texto da documentação, mas sim uma funcionalidade do próprio Emacs: se você pedir o help de uma função que você mesmo definiu no seu ~/.emacs, o Emacs vai mostrar um link para o ponto da definição no arquivo. (Até as poucas funções definidas em C têm links para a definição no fonte, mas nesse caso o Emacs abre uma janela perguntando o diretório onde se encontra o fonte C do Emacs, que eu não tenho aqui.)

Uma coisa um pouco ruim é que quase todos os keystrokes já têm um comando associado, seja por padrão, seja pelo modo em uso, o que limita as escolhas para novos keybindings que não conflitem com os existentes. Por convenção, o Emacs reserva C-c seguido por uma letra para o usuário, mas C-c seguido por outras coisas são reservados para os modos. Porém, existe um refúgio: a tecla C-z por padrão vem associada ao comando suspend-frame, que suspende o Emacs em modo texto e minimiza a janela no modo gráfico. Eu basicamente não uso esse comando no modo gráfico anyway (e ele também fica acessível via C-x C-z), então o que eu fiz foi rebindar ele como uma "prefix key" e colocar os comandos novos que eu defino sob o C-z (por exemplo, C-z C-z para salvar e fechar o arquivo e C-z d para inserir a data atual). Como C-z é um comando padrão do Emacs, isso tem a vantagem de que basicamente nenhum modo usa essa tecla.

Emacs as a daemon

Enquanto o modo de uso normal do Vim (e editores de texto em geral) é abrir o editor, editar arquivos e fechar, o modo de uso normal do Emacs (e sistemas operacionais em geral) é deixar o editor aberto o tempo inteiro enquanto se está trabalhando, em parte porque o editor tende a acumular estado (arquivos e aplicativos abertos, modos em uso), em parte porque ele demora um pouco mais para carregar do que o Vim (especialmente a versão gráfica, e especialmente se o ~/.emacs faz mil coisas na inicialização). Porém, é possível rodar o Emacs como um daemon, e aí pode-se usar o comando emacsclient para abrir um novo "frame" (janela) do Emacs conectado à sessão já em execução, o que é mais rápido do que iniciar o editor do zero. emacsclient -a "" inicia um daemon automaticamente se já não houver um rodando, e conecta-se a um existente se houver. Além disso, é possível passar as opções -t para rodar no terminal e -c para criar uma nova janela gráfica (por padrão ele abre o arquivo passado na linha de comando em uma janela existente, se houver). É um comando bem útil para setar como o valor da variável de ambiente EDITOR, que programas como git, crontab, etc., usam para determinar o editor a ser usado.

Shell mode

O Emacs possui um "modo shell" (M-x shell), que executa o bash ou afim dentro de um buffer do Emacs, onde os comandos normais de edição do Emacs ficam disponíveis. O modo tem algumas bizarrices (o texto é um buffer normal, o que significa que é possível editar a saída dos comandos que já foram executados, apagar o prompt [update: isso é controlável pela variável comint-prompt-read-only; mais informações no help da variável (C-h v comint-prompt-read-only)], etc.), mas é interessante se você quer realizar o máximo de tarefas possível sem sair do Emacs.

O shell mode toma conta do completion de nomes de arquivos e comandos, mas ele usa a função de completion do Emacs, que por padrão exclui certos tipos de arquivos que normalmente não se tem interesse em abrir num editor de textos (e.g., arquivos .o), o que não faz muito sentido em um shell. A solução que eu encontrei foi adicionar um "hook" (função que roda quando um modo é iniciado) no meu ~/.emacs que anula a lista de extensões a ignorar no buffer do shell.

Outro "gotcha" é que como o buffer usa os comandos normais do Emacs, coisas como Ctrl-C, seta para cima, etc., não têm o comportamento normal do terminal. Os comandos Ctrl-* devem ser precedidos de C-c (e.g., C-c C-c para interromper o processo, C-c C-z para suspender, etc.). Manipulação de histórico é feita através de M-p e M-n (de "previous" e "next", análogo ao C-p e C-n para trocar de linha no buffer).

Como trata-se de um buffer simples, coisas que exigem poderes "gráficos" do terminal, como aplicativos de tela cheia, não funcionam no shell mode. Porém, é possível adicionar suporte a cores no buffer (para coisas como ls --color) rodando a função ansi-color-for-comint-mode-on (seja via M-x, seja no ~/.emacsrc (lembrando de pôr parênteses em volta do comando nesse caso)). Vale ainda notar que o shell mode seta a variável de ambiente TERM para dumb, o que faz com que ls, grep e companhia não usem cores por default. É possível contornar isso alterando o ~/.bashrc para mudar o valor de TERM (para vt100, por exemplo) se a variável INSIDE_EMACS estiver setada, mas aí os programas que usam TERM para decidir se vão operar em modo tela cheia ou não vão incorretamente assumir que podem rodar em tela cheia dentro do shell mode. Não sei se há algum valor de TERM que indique suporte a cores mas não às demais funções gráficas do terminal.

Também existe um "modo terminal" (M-x term), que é um emulador de terminal de verdade (não apenas um buffer com um shell) onde se pode rodar qualquer programa de terminal, mas aí perdem-se os comandos normais do Emacs. Além disso, pelo que eu testei o emulador é pra lá de lento (o alsamixer leva uns três ou quatro segundos para abrir com o processador a 800MHz3).

M-x finish-post RET

Por hoje ficamos por aqui. Eu provavelmente hei de postar mais coisas à medida em que for descobrindo funções novas no editor, e atualizar meu ~/.emacs no GitHub ocasionalmente.

_____

1 Na verdade, faz uns cinco anos que eu uso o Vim com keybindings especiais para não ter que trocar de modo para os comandos de edição mais freqüentes, então eu já usava um editor não-modal for most part, o que significa que de certa forma eu não aproveitava muito das vantagens de usar o Vim.

2 O que o Emacs chama de modos são coisas como o html-mode, latex-mode, c-mode, etc., chamados major modes, que alteram o comportamento do editor (como comandos e syntax highlighting) para facilitar a edição de um certo tipo de arquivo, ou aplicações como o Gnus. Também há os minor modes, que são usados em conjunto com os major modes e se comportam mais ou menos como algumas opções ativadas com :set no Vim.

3 Normalmente eu uso a máquina em modo de economia de energia e só seto o governor do cpufreq para performance quando vou fazer alguma coisa mais pesada, primariamente porque com a freqüência baixa o fan faz menos barulho.

9 comentários

lash status update

2015-05-14 23:42 -0300. Tags: comp, prog, pldesign, lash, life

Faz quase dois meses desde o primeiro commit do lash. O status do projeto é o seguinte:

No decorrer dessa última semana, o parser original, baseado na biblioteca Comparse de parser combinators, foi substituído por um parser descendente recursivo escrito à mão. Os motivos principais para a reescrita foram que a Comparse não mantém informação de linha e coluna dos elementos parseados, aparentemente não tem suporte nenhum a error reporting (o parser simplesmente "backtracka" quando se depara com um erro, até que o parser inicial backtracka e devolve #f), e o parser estava com uns comportamentos estranhos diante de algumas entradas (o que não é culpa da Comparse, mas não tinha por que eu perder tempo debugando se eu já teria que reescrever o parser uma hora ou outra pelos outros motivos citados). O handling de espaços e newlines no parser antigo também estava o caos, enquanto no atual aparentemente tudo funciona como deveria nesse quesito.

O parser novo reconhece quase toda a linguagem prevista para a "release inicial", lança exceções nos pontos certos do código ao encontrar erros de sintaxe (embora as exceções ainda não sejam muito descritiva, mas já é um começo), e armazena a linha e a coluna de cada construção nos nós da árvore sintática (com pequenos erros, mas nada difícil de resolver). O código ainda está meio crude, e tem muita coisa que ainda dá para refatorar (e.g., repetições que estão codificadas como loops explícitos ao invés de uma construção que abstraia a repetição), mas isso vai ir se resolvendo ao longo do tempo. De repente as partes mais abstratas do parser podem até virar uma biblioteca de parser combinators no futuro (com a diferença de que eu estou usando uma struct mutável e exceções ao invés de uma mônada para manter o estado do parser e indicar erros, o que seria meio unusual para uma biblioteca de parser combinators, mas whatever; ninguém disse que seriam parser combinators funcionais).

O parser novo reconhece mais construções do que o resto do shell é capaz de executar (por exemplo, pipelines, redirects, &, &&, ||, $()... em outras palavras, praticamente todas as funções do shell), o que de certa forma é bom, porque me compele a implementar as coisas que faltam. Nos últimos dias o desenvolvimento anda numa taxa mais ou menos ok (para mim), e acho que é mais ou menos realista prever uma release 0.1* mais ou menos funcional para julho. Pelo menos é (mais ou menos) isso que eu espero. Isso é bom, porque em um momento de otimismo em março eu submeti uma proposta de palestra para o FISL sobre o shell e, na vaga possibilidade de ela ser aceita, até lá seria bom o shell estar num estado usável. (Eu submeti a proposta sob a premissa de que se tudo desse errado com o shell e eu fosse aceito era só pedir para tirarem a palestra, mas a essa altura acho que isso não será mais necessário. Tudo isso assumindo que eu seja aceito, o que seria muito doido, na real.)

Comecei a usar a wiki do projeto no GitHub para fazer anotações. O plano que ela venha a conter:

Para editar a wiki é necessário criar uma conta no GitHub, aparentemente, mas acho que podemos conviver com isso. Contribuições são sempre bem-vindas.

Quanto à linguagem do shell, algumas coisas mudaram:

Durante o desenvolvimento do novo parser eu descobri um "bug" no Chicken que faz com que variáveis criadas com define dentro de um cond sejam declaradas como globais. A galera na mailing list parece ser da opinião de que isso não é um bug e sim uma feature, entretanto. Meanwhile, eu resolvi o problema no lash redefinindo o cond para wrappear as cláusulas em um (let () ...) implícito (o que cria um "scope boundary" que torna as definições locais), e de brinde ainda lançar uma exceção se nenhuma cláusula for verdadeira. Scheme, yay.

Enquanto o lash anda às mil maravilhas, o mestrado vai por água abaixo, mas isso é assunto possivelmente para outro post.

_____

* No momento eu não estou numerando as versões, mas pelo esquema de numeração previsto (<major>.<minor>.<número de commits desde o último update do minor>), estaríamos na versão 0.0.31. Parece bastante, mas é porque eu tenho o hábito de commitar loucamente enquanto estou mexendo no código.

1 comentário

You're doing it completely wrong

2015-04-28 22:03 -0300. Tags: comp, prog, wrong, ramble

Encontrei um e-mail de três anos atrás que explica uma das coisas que eu acho que estão "completely wrong" com os ambientes computacionais modernos. O texto era para ter servido de base para um post que nunca foi escrito. Reproduzo-o semi-intacto abaixo. (Fico feliz que a minha visão ainda seja a mesma, mas por outro lado o "plano para os próximos dez mil anos" parece longe de se realizar...)

* * *

From: Vítor De Araújo
To: Carolina Nogueira
Subject: You're doing it completely wrong
Date: Mon, 12 Mar 2012 00:37:18 -0300

[Core dump follows.]

Acho que eu não vou escrever o post tão cedo, então explico agora. O que há de completely wrong é que os programas em execução são "caixas pretas"; em geral um programa é um brinquedo estático, que tu não pode "olhar dentro" e modificar facilmente. Em um mundo ideal tu deveria poder alterar partes de qualquer programa sem ter que recompilar tudo, e de preferência sem sequer ter que fechar e abrir o programa para as mudanças entrarem em efeito.

Exemplo simples: o Pidgin a cada 60 segundos manda um "XMPP ping" pro servidor de XMPP (Jabber), para ver se a conexão ainda está de pé. Se eu quiser mudar de quanto em quanto tempo ele manda o request, ou quanto tempo ele espera por uma resposta, eu tenho que alterar o fonte. Idealmente:

  1. Isso poderia ser (e talvez já seja) uma variavelzinha feliz do programa, que eu deveria poder alterar durante a execução;
  2. Alterar o fonte não deveria ser nenhum grande mistério; o fonte deveria ser facilmente acessível e modificável a partir do programa, e eu deveria poder recompilar uma função ou módulo individual sem ter que recompilar o universo. (Mais de uma vez eu quis mudar coisinhas simples do Pidgin, e.g., eliminar o atalho Ctrl-Up, que bizonhamente exigem alterar o fonte, e falhei miseravelmente em conseguir fazer o troço compilar.)

Além disso, deveria ser fácil desfazer qualquer modificação. Versioning é uma tecnologia muito supimpa dos anos 60 que, como muitas tecnologias supimpas do passado, se perdeu com a popularização de maquininhas pequenas com recursos insuficientes pra suportar a coisa. Hoje em dia temos recursos sobrando, mas a tecnologia caiu no esquecimento.

Exemplo menos simples: o Claws Mail por padrão ordena as mensagens em ordem ascendente de data (da mais antiga pra mais recente), e ele lembra o critério de ordenação por pasta, o que quer dizer que o usuário tem que alterar pra ordem decrescente individualmente pra cada uma das mais de oito mil pastas que há (Inbox, Sent, Queue, Trash, Drafts pra cada mailbox, mais os feeds de RSS). Provavelmente 99.5% das pessoas esperam ordem decrescente por padrão, e provavelmente teria que alterar pouca coisa do código pra mudar o padrão, mas a barreira pra alterar o software é muito grande (baixar fontes, encontrar o trecho de código relevante, se entender com ./configures e bibliotecas da vida, recompilar o mundo, fechar e abrir o programa). Idealmente, eu deveria poder apontar para um menu relacionado com o que eu quero, teclar alguma combinação mágica, e ir parar na função que aquele menu ativa. Daí em diante eu deveria conseguir navegar pelo código até achar o trecho relevante; os nomes de funções e variáveis deveriam funcionar como links (coisa que acho que é possível com a maior parte das IDEs atuais). Tendo achado o dito cujo, eu deveria poder alterar o código, mandar recompilar aquele trecho, e as mudanças se tornarem imediatamente efetivas.

E aparentemente essa utopia toda já existiu, sob o nome de Lisp Machine. Este cara diz:

Within DW, you can move your mouse over any object or subobject that has been displayed, whether or not it was part of any GUI. You get this virtually for free and have to work hard to suppress it if you don't want it. But it doesn't require any special programming.

One of the most common ways to get a foothold in Genera for debugging something is to (1) find a place where the thing you want to use appears visually, (2) click Super-Left to get the object into your hands for read-eval-print, inspection, etc, (3) use (ed (type-of *)) to find its source reliably and with no foreknowledge of what the type is, who wrote the program, what their file conventions were, who loaded it, or any of myriad other things that other systems make me do.

Quer dizer, tu pode pegar um objeto qualquer presente na tela (e praticamente tudo é um objeto, pelo visto), descobrir o tipo/classe dele e mandar editar o código correspondente [(ed (type-of *))]. Eu queria rodar o simulador de Genera aqui pra ver isso funcionando na prática, mas não consegui fazer ele funcionar total... :-(

Enfim, muita tecnologia gloriosa do passado se perdeu. Um dos meus objetivos para os próximos dez mil anos é avançar o estado da arte a o que ele era nos anos 80... :P

Aí o camarada se pergunta: será que faz sentido ter essa tecnologia em um desktop? Será que o "usuário final" tem algum interesse nisso tudo? A resposta é a mesma que se pode dar para justificar a presença de uma linha de comando em um sistema visando o "usuário final" (e.g., Ubuntu, Mac OS):

  1. Mesmo que o usuário final não tenha interesse, se é de ajuda ao desenvolvedor e não prejudica o usuário de maneira alguma, não há por que não ter a feature.
  2. Alguns usuários têm interesse em explorar o funcionamento do sistema e usar as features mais avançadas. Em particular, se a barreira para editar os programas for pequena e for fácil desfazer quaisquer modificações, é bem possível que muito mais gente tenha interesse em bagunçar com os internals das coisas.
  3. Uma coisa muito legal que se vê com Ubuntus da vida é que mesmo um usuário que não saiba usar a linha de comando pode pedir ajuda num fórum e alguém que sabe pode ajudar o cara a resolver um problema que envolva brincar de linha de comando. Da mesma forma, um usuário poderia resolver problemas que envolvessem alterar um programa com a ajuda de pessoinhas em fóruns e afins.

Além de dar todo o poder para os cidadãos infra-vermelhos, o que lembra um pouco as idéias do camarada Alan Kay [1][2], a tendência é que a facilidade de alterar e testar os programas leve a programas mais bem testados, menos bugados e que tendam à perfeição mais rápido. Todos comemora.

Existem mil dificuldades envolvidas na criação de um sistema assim, especialmente se quisermos abandonar o modelo da Lisp Machine de uma máquina mono-usuário em que todos os processos podem tudo, mas isso fica pra outra discussão.

Escrevi bem mais do que eu pretendia. Parece que eu até tenho material para um post... :P

Comentários

Mind dump

2015-04-10 01:34 -0300. Tags: comp, prog, pldesign, lash, life, mind, ramble, music

Coloquei o lash no GitHub, for what it's worth. Eu me pergunto se foi uma coisa sensata publicar ele agora, mas já faz um tempo que eu vinha anunciando que ia publicar "em breve", então coloquei lá de uma vez. (Além disso, esses dias eu quis mexer nele fora de casa e não tinha o código.) O código está em um estágio bem inicial – vergonhosamente inicial, dado que já faz umas três semanas que comecei a trabalhar nele, e o que eu fiz até agora eu provavelmente poderia ter feito em uns três dias se tivesse tido a disciplina de trabalhar nele semi-diariamente. Por outro lado, o projeto está andando para frente, mesmo que devagar, o que já é melhor do que todos os anos anteriores em que eu disse "puxa, eu queria fazer um shell" e não escrevi uma linha de código. So, that's progress. Além disso, na atual conjuntura eu provavelmente deveria tentar relaxar um pouco a cuca e me preocupar menos com isso; afinal, isso é um projeto pessoal e eu não devo nada para ninguém. No final das contas, megalomanias de dominação mundial à parte, o principal afetado pelo bom sucesso do projeto sou eu mesmo.

Eu cheguei à brilhante e inaudita conclusão de que eu vou ter que reduzir bastante minha atividade twittereira e internética em geral se eu quiser começar a fazer alguma coisa produtiva com a minha vida (onde escrever um shell e estudar línguas obscuras contam como coisas produtivas). Por outro lado, a Internet atualmente é responsável por uns 95% das minhas interações sociais, especialmente agora que eu não tenho mais aulas, coisa que faz uma certa falta, a despeito da minha fama de anti-social. A solução provavelmente é (shudder) sair de casa e falar com pessoas.

Eu também cheguei à conclusão (igualmente brilhante) de que muita coisa nessa vida é questão de criar hábitos. Por exemplo, até algumas semanas atrás eu costumava usar toda a louça da casa até não ter mais louça limpa, momento em que eu aplicava o garbage collector e lavava tudo (ou, dependendo da preguiça, só o que eu precisasse na hora). Eu me dei conta de que isso não estava sendo muito conveniente e resolvi começar a lavar as coisas logo depois que uso, ou antes de ir dormir. No começo era meio ruim ter que me "obrigar" a fazer isso, mas agora eu já me habituei e isso não me incomoda mais tanto (além do que, como a louça não acumula, normalmente o esforço de lavar é pequeno). Talvez seja uma questão de criar o hábito de sentar uma hora do dia para programar/estudar/whatever. O flip side disso é que a gente também se habitua ao longo da vida a uma porção de coisas que a gente deveria questionar e/ou atirar pela janela, não só hábitos acionais como também (e principalmente) hábitos mentais. Estas são minhas (brilhantes e inauditas) palavras de sabedoria do dia. (Tecnicamente todo hábito é mental, mas deu pra entender. Acho.)

Escrever o lash em Chicken Scheme tem sido uma experiência bastante agradável. Eu estou aprendendo (a parte não-R5RS d)a linguagem "as I go", mas até agora a linguagem não me deixou na mão, a implementação é estável e gera executáveis pequenos e razoavelmente rápidos, e o sistema de pacotes funciona. (Rodar chicken-install pacote e consistentemente ver o pacote ser baixado e compilado sem erros era quase chocante no começo. O fato de que as bibliotecas são shared objects (a.k.a. DLLs) de verdade e carregam instantaneamente também muito alegrou o espírito, especialmente dada minha experiência anterior com bibliotecas em Common Lisp.) A única coisa que deixa um pouco a desejar é o error reporting, mas nada "deal-breaking".

Eu me dei conta de que uma das coisas que eu mais gosto em linguagens "dinâmicas" é a habilidade de rodar um programa incompleto. Eu já meio que escrevi sobre isso antes, mas eu já não lembrava mais quão deeply satisfying é poder rodar um programa pela metade e ver a parte que foi escrita até o momento funcionando. Por outro lado, é bastante incômodo errar um nome de função ou os argumentos e só descobrir o erro em tempo de execução. Faz muito tempo que eu acho que o ideal seria uma linguagem com análise estática de tipos, mas em que erros de tipo gerassem warnings ao invés de impedir a compilação, e que permitisse a declaração opcional dos tipos de variáveis e funções. Uma dificuldade que eu via nisso até agora é que enquanto em uma linguagem dinâmica os dados costumam ter uma representação uniforme em memória que carrega consigo alguma tag indicando o tipo do dado, e portanto é possível chamar uma função com argumentos do tipo errado e detectar isso em tempo de execução, em uma linguagem estaticamente tipada convencional os dados costumam ter uma representação untagged/unboxed e de tamanho variável, o que tornaria impossível compilar um programa com um erro de tipo sem violar a segurança da linguagem (e.g., se uma função f recebe um vetor e eu a chamo com um int, ou eu rejeito o programa em tempo de compilação, o que atrapalha minha habilidade de rodar programas incompletos/incorretos, ou eu gero um programa que interpreta o meu int como um vetor sem que isso seja detectado em tempo de execução, o que provavelmente vai causar um segfault ou algo pior). Porém, esses dias eu me dei conta de que ao invés de compilar uma chamada (insegura) a f(some_int), poder-se-ia simplesmente (além de gerar o warning) compilar uma chamada a error(f, some_int), onde error é uma função que avalia os argumentos e lança uma exceção descrevendo o erro de tipo. O resultado prático é que o executável gerado roda até o ponto em que é seguro rodar (inclusive avaliando a função e os argumentos) e interrompe a execução no ponto em que seria necessário chamar a função com um argumento de tipo/representação incompatível. Melhor dos dois mundos, não? Vai para o meu caderninho de idéias para a Linguagem Perfeita™.

Eu ia escrever mais umas notas sobre a vida, mas ultimamente eu ando mui receoso quanto a publicar coisas da minha vida pessoal – o que provavelmente é uma boa idéia. É fácil esquecer que qualquer um, no presente ou no futuro, pode ler o que a gente escreve nessa tal de Internet. Eu também já falei sobre isso mil vezes antes, which makes it all the more surprising que eu ainda tenha que me relembrar disso ocasionalmente. Anyway.

Unrelated com qualquer coisa, conheci recentemente uma bandinha chamada Clannad, na qual eu me encontro totalmente viciado no momento. Também conheci uma coisa totalmente excelente chamada Galandum Galundaina, uma banda mirandesa com certeza.

12 comentários

Blueprints for a shell, parte 4: Ramblings on syntax

2015-03-17 01:10 -0300. Tags: comp, prog, pldesign, shell, lash

Este post é parte de uma série sobre o lash, um shell que eu estou ideando.

Hoje discutiremos algumas questões sintáticas do shell. Depois disso eu provavelmente vou dar uma pausa na série e tentar implementar um protótipo do lash, mesmo com algumas questões ainda em aberto. Em particular, falta falar sobre estruturas de controle (mas o básico (if, while, each) não tem muito o que discutir) e módulos (que vão ficar para o futuro).

O meu objetivo ao escolher a sintaxe do shell é achar um ponto de equilíbrio entre minimalismo sintático total (e.g., S-expressions1) e ter sintaxe especial para tudo (e.g., bash). No geral, o guiding principle é expor a maior parte das funcionalidades do shell por meio de funções, e usar sintaxe especial apenas quando seria inconveniente escrever uma chamada de função, especialmente para features freqüentemente usadas em modo interativo (e.g., redirects e pipelines). Este post é uma survey dos elementos sintáticos do (ba)sh e como eles serão representados em lash.

Comandos simples

A sintaxe básica de um comando em (ba)sh é, em BNF fuleiro:

command ::= {var=value}* {word | redirect}*

A semântica é: se há words no comando, a primeira word é o nome do comando a ser executado, e as demais são os argumentos. O comando é executado em um ambiente acrescido das variáveis de ambiente especificadas, e com os redirects em efeito. Se não há words, as variáveis especificadas (do shell, não de ambiente) recebem os valores atribuídos, e os redirects... bom, aparentemente não fazem nada, mas isso depende da variante de sh, porque o comportamento aparentemente é indefinido no padrão POSIX. A ordem de avaliação das coisas também é um pouco peculiar:

bash# a=$(date >&2) uname $(pwd >&2) 2>/dev/null
/tmp
Mon Mar 16 21:27:09 BRT 2015
Linux

dash# a=$(date >&2) uname $(pwd >&2) 2>/dev/null
/tmp
Linux

Vale notar que os redirects e as words podem aparecer intercalados na linha de comando (inclusive minha BNF está errada, porque redirects podem aparecer intercalados com as atribuições também); a ordem em que eles aparecem relativos aos outros elementos sintáticos parece ser irrelevante.

Em lash, depois de muita hesitação, eu decidi atirar pela janela as atribuições prefixadas; o comando env do Unix já serve para rodar comandos em um ambiente modificado (env FOO=bar comando). Eu pensei em obrigar os redirects a aparecerem no final, mas me dei conta de que pode ser útil escrever um redirect intercalado em comandos que recebem blocos. e.g.:

each_line </etc/passwd {|line|
    echo "bla bla $line"
}

Ainda não sei até que ponto isso pode ser útil, mas por enquanto fica aí. Fica a questão da ordem de avaliação. A remoção das variáveis prefixadas são uma coisa a menos na equação. Quanto ao momento em que os redirects tomam efeito, há algumas possibilidades:

  1. Antes de tudo, afetando inclusive chamadas a comandos com $(...), $[...] e companhia. Tem o detalhe de que o redirect em si também pode envolver avaliação (ls >$[generate-a-file-name]). Nesse caso o redirect evidentemente só pode ter efeito depois do comando.
  2. Depois da avaliação de tudo e imediatamente antes de executar o comando propriamente dito. Aparentemente é isso que o bash faz.
  3. O redirect afeta a avaliação de tudo o que aparece depois dele na linha de comando, i.e., 2>/dev/null foo $(bar) afeta a execução de bar, mas foo $(bar) 2>/dev/null não.

Por ora o plano é fazer como o bash, primariamente porque sim.

Fica ainda a questão da atribuição, já mencionada anteriormente: usar um comando para atribuição (set x = 42), ou tratar o = especialmente no parser? Eu não gosto muito de casos especiais, mas talvez a atribuição mereça tratamento especial. Eu nem sei se atribuição (por oposição a definição de uma nova variável) é particularmente freqüente em um script para justificar um caso especial.

Quoting

O bash possui uma porção de coisas quote-like:

O plano para o lash é:

Outra utilidade de strings com delimitador (semi-)arbitrário é que elas supririam a funcionalidade dos "here-documents" do bash, os quais veremos adiante.

Here-documents

Here-documents permitem embutir um trecho de texto, delimitado por uma string à escolha, a ser enviado para a entrada padrão (ou outro file descriptor) do comando a ser executado:

cat <<FIM >foo.txt
The quick brown fox
jumps over the lazy dog.
FIM

Por padrão, o shell realiza substituições no conteúdo do here-document. Se o delimitador for citado/escapado, o conteúdo é interpretado literalmente. Além disso, se o delimitador é precedido de -, espaços e tabs no começo de cada linha são descartados.

Em alguma versão o bash introduziu também "here-strings", que permitem usar uma string simples ao invés de um documento multi-linha como entrada:

sed 's/foo/bar/' <<<"$content"

Se o lash adotasse um mecanismo para strings com delimitadores (semi-)arbitrários, como a contra-aspa descrita anteriormente, seria possível unificar esses dois casos. Strings com delimitador arbitrário podem ser usadas também para inicializar variáveis, por exemplo, coisa que não é possível com here-documents em bash.

Parameter substitution

O bash possui uma dúzia de coisas da forma ${varsomething}, que permitem fazer alguma transformação sobre o valor de uma variável. Além de a sintaxe ser abstrusa, a string a ser manipulada tem que estar armazanada em uma variável (não pode ser o resultado de outra substituição, por exemplo; para aplicar múltiplas substituições é necessário armazenar os resultados parciais em uma variável). O plano em lash é substituir todas as substituições (heh) por funções.

Existe um pequeno problema envolvido: o bash distingue entre ${var//$match/$replacement} e ${var//"$match"/$replacement}. No primeiro caso, *, ? e similares dentro de $match têm seus significados de globbing, enquanto no segundo eles são interpretados literalmente. Esse problema afeta outras coisas que trabalham com patterns. No comentário linkado (que trata da função glob, que retorna uma lista dos arquivos que casam com um padrão), a solução que eu encontrei foi usar uma format string para separar as partes que devem ser interpretadas como pattern das partes que devem ser interpretadas literalmente (assim como printf em C separa a string de controle de strings incluídas com %s e que são usadas literalmente), mas no caso de substituições não sei se seria muito conveniente – talvez agrupando o pattern e seus argumentos em um array:

# Equivalente a ${string//"$match"*/"$replacement"} em bash.

subst $string ("%s*" $match) $replacement

Kinda weird, mas eu consigo sobreviver. Na verdade, acho que o melhor seria tratar o pattern como literal por padrão, senão certo que alguém vai escrever $[subst $var $match $replacement] sem nem pensar se $match contém asteriscos ou não, e aí vai ser outra daquelas situações em que um script funciona 99% do tempo, até que um dia alguém resolve usar uma string com * e o script tem um comportamento inesperado. A sintaxe de subst poderia ser:

Qual a sua opinião?

Outra situação que usa patterns e sofre do mesmo problema é o case, que a princípio há de ser um comando comum sem sintaxe especial (case STRING (PATTERN-1 BLOCO-1 ... PATTERN-N BLOCO-N)2). Idealmente a sintaxe adotada para as substituições deverá ser utilizada para o case também.

And, or, not

Em (ba)sh, comando1 && comando2 executa comando1 e, se este retornar 0 (i.e., verdadeiro), executa comando2. O exit status do comando como um todo é o exit status do último comando que for executado. Analogamente, comando1 || comando2 executa comando1 e, se este retornar não-zero (i.e., falso), executa comando2. Em ambos os casos, comando é um "comando completo", que pode envolver pipelines. Há dois casos de uso principais desses operadores:

Portanto, eles permanecem.

! nega o exit status do comando (troca de não-zero para 0 e de 0 para 1). Ele também se aplica a um "comando completo", negando uma pipeline inteira (o exit status de uma pipeline é o exit status do último comando), e essa seria a única razão que eu vejo para tratá-lo como sintaxe especial e não apenas um comando chamado !. Não sei se justifica; além de ser uma situação bem rara, nada impede de simplesmente escrever o ! antes do último comando da pipeline. Além disso, talvez fosse o caso de escrever ! {comando1 | comando2} anyway, por clareza. While we are at it, podíamos renomear o comando para not, para deixar mais claro que se trata de um comando comum e não sintaxe especial, mas aí já não sei.

Process substitution

Em bash, <(comando) cria um pipe (um par de file descriptors em que tudo que entra numa ponta sai na outra), executa comando com a saída padrão redirecionada para o lado entrante do pipe, e a expressão é substituída por um nome de arquivo que corresponde ao lado de saída do pipe. Por exemplo, é possível escrever:

diff <(sort file1) <(sort file2)

que executa sort file1 e sort file2 e chama algo como diff /dev/fd/63 /dev/fd/62. Analogamente, >(comando) executa comando com a entrada padrão vinda da ponta de saída do pipe, e a expressão é substituída por um nome de arquivo correspondente à ponta de entrada.

Embora essa sintaxe seja bastante conveniente para usar na linha de comando (e na verdade acho que o exemplo com o diff é o único que eu já usei na linha de comando na vida), não sei se eu quero mantê-la em lash. Não só pelo princípio de evitar sintaxe extra gratuita, mas também porque ela parece um redirecionamento, mas é uma word. Se eu quisesse redirecionar um file descriptor para o resultado do process substitution (o que é útil primariamente para fazer um pipeline com um file descriptor que não seja a stdout, e.g., redirecionar a stderr para um comando), eu teria que escrever algo como (o espaço é necessário):

ls 2> >(comando)

o que não é exatamente óbvio. Talvez uma função desse conta do recado, algo como:

diff $[popen -r {sort file1}] $[popen -r {sort file2}]

Ok, a cara disso é terrível3. Talvez se a popen ganhar outro nome, e o comando aceitar um nome de comando e argumentos diretamente ao invés de obrigatoriamente um bloco:

diff $[readfrom {sort file1}] $[readfrom {sort file2}]
diff $[pipefrom {sort file1}] $[pipefrom {sort file2}]
diff $[pipefrom sort file1] $[pipefrom sort file2]

Não sei.

Outro problema com a sintaxe do bash é que o comando parece um array, e talvez um array fizesse sentido como alvo do redirect (redirecionaria para todos os nomes de arquivo no array). Por outro lado, o caso do array poderia ser representado pelo array "spliced", qualquer que seja a sintaxe escolhida para ele (e.g., >$@(file1 file2)), ou simplesmente permitindo múltiplos redirects do mesmo file descriptor (>file1 >file2; o zsh permite isso, acho). Não sei.

Humanitas precisa dormir

Por hoje ficamos por aqui. Como sempre, tudo o que eu digo que "é" de tal jeito é só o plano atual, tudo está sujeito a discussão, comentários e sugestões são sempre bem-vindos, live free or die, do what you want 'cause a pirate is free, etc. Como esse é, a princípio, o último post da série for a while, sinta-se a vontade para comentar aqui sobre tópicos não abordados até agora na série.

_____

1 Em tempos de outrora eu pensei em usar S-expressions para toda a sintaxe (inclusive redirecionamentos e pipelines), mas permitir omitir os parênteses em torno de comandos que aparecem sozinhos em uma linha. O resultado não me foi exatamente satisfatório. Além disso, turns out que um shell totalmente baseado em S-expressions já foi feito (o qual por sinal provavelmente é uma boa fonte de inspiração).

2 Os patterns e blocos vão em um array primariamente para permitir que eles ocupem múltiplas linhas sem ter que pôr um \ no final de cada linha:

case $file (
    "*.mp3" { ... }
    "*.ogg" { ... }
    "*" { ... }
)

3 Revisando o post, eu olhei para isso e pareceu a sintaxe mais natural do mundo, mas a essa altura minha percepção já está meio alterada pelo sono.

8 comentários

Blueprints for a shell, parte 3: Tipos de dados

2015-03-13 22:47 -0300. Tags: comp, prog, pldesign, shell, lash

Este post é parte de uma série sobre o lash, um shell que eu estou ideando.

A world made of strings

Em (ba)sh só existe um tipo de dado: a string. Em bash, uma variável pode ser declarada como um array (e em versões mais recentes, como um dicionário), mas embora a variável seja um array, o array em si não é um valor de primeira classe: não é possível passar um array como argumento para uma função, ou armazenar um array dentro de outro, por exemplo. Isso limita um bocado o que se pode fazer em bash sem apelar para gambiarras do inferno. (Claro que "dá" para viver sem essas coisas. Também "dá" para programar com máquinas de Turing...)

lash quebra com a tradição, se revolta contra o sistema e introduz arrays, dicionários e blocos de primeira classe (bem como possivelmente outros objetos, como canais de comunicação, mas isso ainda está em aberto). Assim, é possível fazer coisas futurísticas como manter uma coleção de dados estruturados e escrever funções para manipular arrays e produzir outros arrays. Fantástico, não? Welcome to 2015.

Independentemente do shell, variáveis de ambiente e argumentos de processos no Unix também são strings (e strings que não podem conter \0, ainda por cima), o que significa que não temos como passar diretamente nossos valores estruturados para outros processos. Uma abordagem alternativa seria fazer como Tcl: representar tudo como strings, definir certos formatos de string para armazenamento de dados estruturados (e.g., keyed lists, ou XML if you're feeling crazy), e prover funções para interpretar e manipular tais strings. Isso permitiria passar dados "estruturados" para subprocessos, pois eles seriam apenas strings. Mas, seriously, guardar tudo como string e parsear/procurar dentro da string para obter um elemento de uma lista/dicionário? Gerar uma string nova toda vez que se altera um elemento? Tá certo que seria possível mitigar um pouco esses problemas usando alguma representação interna mágica para strings, mas sei lá. Por ora eu prefiro ter dados estruturados normais.5 Além disso, blocos têm que ser dados especiais de qualquer forma, para carregar informação de escopo.

So, tipos de dados.

Strings e números

Uma string em lash é uma seqüência de bytes; internamente, o shell não está preocupado com a interpretação desses bytes (como caracteres codificados em UTF-8, por exemplo). No geral, o ambiente Unix como um todo não está preocupado com o conceito de codificação; nada exige que nomes de arquivo sejam strings UTF-8 válidas, por exemplo, e o resultado de um globbing deveria ser representável por strings do shell sem nenhum mistério. Arquivos/streams também não tem nenhuma codificação inerente, e coisas como echo $str não deveriam ter que fazer nada de mágico para decidir como mandar o conteúdo da string para o arquivo. Interpretar os bytes de uma string como UTF-8 (ou outro encoding) é responsabilidade das funções que o shell provê para manipular strings.

Acho que em um shell não faz muito sentido ter um tipo numérico distinto. Em um shell, quando se escreve algo como my x = 01, espera-se que o 0 permaneça lá; quando se chama xargs -0, espera-se que o - não se perca, etc. Além disso, os argumentos que o script recebe da linha de comando são todos strings, e não me parece interessante ter que convertê-los manualmente para números antes de fazer operações aritméticas com eles. Ao invés disso, a interpretação de uma string como um número cabe aos operadores aritméticos. Por questão de eficiência, o resultado de uma operação aritmética pode ser armazenado internamente como um número (a idéia é evitar ter que converter o resultado para string e reconverter para número caso ele seja usado novamente em uma operação aritmética), mas isso não é observável pelo script.

Diferentemente do (ba)sh, o lash deverá suportar aritmética de ponto flutuante. Isso levanta a questão de como distinguir divisão inteira de divisão em ponto flutuante. Eu sou favorável a adotar / para divisão em ponto flutuante e // para divisão inteira, a la Python 3. Os demais operadores aritméticos produzem resultado em ponto flutuante se um dos argumentos for float, e inteiro caso contrário. A representação em string de um número em ponto flutuante sempre inclui um ponto1 (a idéia é que se alguma coisa estiver produzindo resultados float indevidamente, isso não vai passar silenciosamente durante a execução (ou assim se espera)). Operações aritméticas sobre strings que não são números válidos produzem um erro de execução, i.e., nada de NaN propagation a la JavaScript ou interpretação implícita como 0 a la PHP. Na verdade nem o bash deixa esse tipo de coisa passar em silêncio... com algumas exceções: uma string vazia é tratada como um 0, e espaços em torno de um número são ignorados. Aqui fico na dúvida entre "strictness" e conveniência; talvez em um script seja uma boa aceitar esses dois casos.

Strings não são arrays, e (assim como em bash) não são indexáveis com a sintaxe normal de arrays. Haverá funções para obter substrings, mas ainda não pensei bem nos nomes e na sintaxe, e em como especificar o range de bytes/caracteres desejado (início e tamanho? início e fim? inclusivo ou exclusivo? Todas as opções, dependendo dos parâmetros?). Uma possibilidade seria:

Pode ser meio verboso, mas captura de substring parece ser uma coisa relativamente rara em bash, baseado em um grep na minha amostra extremamente significativa de meia dúzia de scripts que estavam à mão, então acho que a clareza e a flexibilidade compensam a verbosidade.

O tamanho da string pode ser obtido com as funções bytelen e charlen, dependendo do tipo de tamanho desejado. (Há ainda a situação em que se quer a largura impressa da string (combining characters não contam no comprimento, e caracteres chineses-et-al ocupam duas posições), bem como substrings baseadas na posição impressa dos caracteres, mas isso vai ficar para o futuro distante, possivelmente numa biblioteca.)

Funções que trabalham com delimitadores (e.g., split STRING DELIM) têm que aceitar delimitadores de tamanho arbitrário, pelo simples fato de que elas têm que funcionar com delimitadores em UTF-8 e ao mesmo tempo se manterem agnósticas quanto à codificação. (Por outro lado, isso assume que a codificação tem a mesma propriedade do UTF-8, de que é possível identificar o começo de um caractere inambiguamente a partir de um ponto arbitrário na stream, o que basicamente só é verdade no UTF-8 e em encodings em que 1 byte = 1 caractere. Meh.)

Arrays

Arrays são seqüências de valores quaisquer. A sintaxe literal para arrays é (valor1 ... valorN). (Os parênteses são herdados da sintaxe de inicialização de variáveis-array do bash. Além disso, colchetes e chaves já têm outros usos. Isso a princípio conflita com a sintaxe do (ba)sh para rodar um comando em um subprocesso4 (( comandos )), mas eu já não pretendia ter essa sintaxe em lash to begin with. Uma função poderia prover essa funcionalidade (e.g., subproc { comandos }).)

Arrays são indexados com a sintaxe $var[expr]. Assim como em bash, expr é avaliado como uma expressão aritmética, sem necessidade de escrever $var[$((expr))]. Diferentemente de bash, chaves não são exigidas, i.e., não é necessário escrever ${var[expr]}. Por um lado isso é mais limpo, mas por outro pode conflitar com o uso de [] como wildcard, e.g., my prefix = /dev/tty; echo $prefix[1-8]. Acho que isso não chega a ser um grande problema, pois isso gera um erro de execução ($prefix não é um array), e portanto é fácil de detectar e corrigir (para ${prefix}[1-8]; dá até para incluir essa informação na mensagem de erro).

Assim como em bash, o array tem que estar em uma variável para ser indexado ($[função][expr] não seria interpretado como uma indexação do resultado de função, a princípio (ou seria?)), mas nada impede que haja uma função index ARRAY N, com a qual se poderia escrever $[index $[função] N].

A sintaxe de atribuição funciona com arrays também (var[i] = 42). Isso implica que atribuição tem que ter tratamento sintático especial, para que coisas como var[i*i] = 42 não causem globbing.

Como fica o caso de arrays multidimensionais (i.e., arrays que contêm outros arrays)? $var[i][j] é uma sintaxe válida? Se sim, não tem por que não aceitar $[função][expr] também, acho.

É possível atribuir a uma posição que ainda não existe (a la Perl), ou isso é um erro (a la Python)? Se a "label" do índice é importante (e não apenas a ordem), não seria o caso de usar um dicionário anyway? Eu consigo pensar em duas situações em que se poderia querer especificar um índice não-existente explicitamente:

  1. Adicionar um elemento no fim do array. Mas para esse caso poderia haver uma função push (ou append, porque aí também podemos ter uma prepend para adicionar no começo; ou poderia haver uma função mais geral insert, para inserir um elemento entre dois quaisquer, ou no início/fim), ou uma sintaxe a la PHP (var[] = 42).
  2. Inicializar um vetor/matriz com alguma fórmula matemática, e.g.:
    my array = ()
    range 0 -toin 10 {|i|
        array[i] = $(( i * i ))
    }
    

    Parece um caso de uso razoável, mas de qualquer forma ele falha com arrays multidimensionais ($array[i][j] = 42 é um erro porque $array[i] não é um array, a menos que seja inicializado primeiro). Pode-se suprir esse caso com uma função make_matrix que recebe o tamanho das dimensões e retorna um vetor inicializado.

Ou podemos permitir atribuição out-of-bounds (e preencher qualquer elemento entre a última posição preenchida e a posição atribuída com a string vazia) e era isso. Não sei (o plano inicial é não permitir).

Outra função básica de manipulação de arrays é each, que recebe um array e um bloco e chama o bloco com cada elemento do array. Também pode haver uma map, que produz um novo array com cada resultado retornado pelo bloco, e uma versão destrutiva de map (chamada map!, talvez2).

A função len retorna o número de elementos do array. Não sei se há necessidade de uma sintaxe especial para isso (e.g., $#var).

$@var "splices" o array, produzindo um argumento ("word" na terminologia do (ba)sh) para cada elemento do array, i.e.:

my array = (1 2 3)
foo $array         # chama foo com um argumento (o array)
foo $@array        # chama foo com três argumentos (1, 2 e 3)

Dicionários

Um dicionário é um mapeamento de strings para valores. (Por que só strings? Talvez faça sentido permitir valores quaisquer como chave.) A sintaxe literal para dicionários é %(chave1=valor1 chave2=valor2 ...) (o % é para sugerir uma vaga relação com hash-tables em Perl), com espaços opcionais em torno do =, o que fica meio estranho sem delimitadores entre os pares chave = valor, mas pode-se usar quebras de linha se desejado:

my person = %(
    name = Hildur
    age = 18
    country = Iceland
)

[Note to self: Em coisas como %(foo=(1 2 3)), assim como em my foo=(1 2 3), foo=(1 2 3) não é uma "palavra" normal do shell, porque é parte string, parte array, i.e., tanto dicionários literais quanto declaração de variável exigem tratamento especial pelo parser (a menos que haja um tipo de dados "associação" ao qual coisas da forma A=B possam ser mapeadas).]

Elementos de um dicionário são acessados com a sintaxe $var{chave}. Não se usa colchetes como em arrays porque a expressão entre colchetes sofre avaliação aritmética, que não é o que queremos em um dicionário. (Será que foi uma boa idéia fazer avaliação aritmética automática after all?) Isso é outro elemento de sintaxe (além dos blocos) que conflita com a sintaxe de brace expansion do bash (foo{1,2,3}). Não sei se isso é um ponto a favor da mudança da sintaxe de acesso a dicionário ou do brace expansion. Outra possibilidade seria usar colchetes, assim como arrays (e aí eles perdem a propriedade de avaliação aritmética, o que pode tornar o acesso a array meio inconveniente), ou talvez $var<chave>, mas isso conflita com a sintaxe de redirecionamento. (Lembrando que isso poderia ser um redirecionamento se $var contivesse um file descriptor. Nesse caso o > posterior seria um erro de sintaxe, então só a interpretação como acesso a dicionário seria válida, mas eu só descubro isso quando chego no >; além disso a chave não poderia ter um espaço não-escapado. Fora que é uma sintaxe totalmente não-usual para acesso a dicionário (as chaves pelo menos têm precedente em Perl).)

Se my dict = %(a=1 b=2 c=3), qual o resultado de $@dict?

Haveria uma porção de funções para iterar sobre dicionários: each-key; each-value; each-entry, que reberia um dicionário e um bloco de dois argumentos e o chamaria com a chave e o valor de cada entrada no dicionário; ou, havendo o tipo associação, chamaria o bloco com cada associação. Alternativamente, havendo o pipeline de objetos, poderia haver uma função keys que produz todas as chaves, e aí escreveríamos keys $dict |> each {|key| ... } (ou qualquer que seja a sintaxe do pipe de objetos), e da mesma forma para os valores (e associações, em as havendo).

Será que é uma boa ter um tipo dicionário distinto de array, ou o melhor é unificar os dois a la PHP, JavaScript, etc.? Acho que eu prefiro ter dois tipos separados, mas há de se pensar melhor.

Interações entre valores estruturados e strings

Em (ba)sh, diferentemente das linguagens de programação em geral, uma variável pode aparecer como parte de uma "palavra" maior, e.g., foo$bar; o conteúdo da string é concatenado na palavra e era isso. Mas e se $bar não for uma string? Pode-se produzir uma versão serializada do valor (o que provavelmente é mais útil), ou gerar um erro.

Coisas como foo$@bar (onde my bar = (1 2 3)) poderiam expandir para foo1 foo2 foo3, como o brace expansion do bash. O problema é que $@ assume que o array está em uma variável. Daria para expandir arrays literais também3, e,g., foo(1 2 3) geraria foo1 foo2 foo3, e aí seria possível eliminar o uso de chaves para brace expansion. O problema é que by far o meu uso mais freqüente de brace expansion na linha de comando é com a string vazia, e.g., mv file{,~} ao invés de mv file file~, e na nova sintaxe isso seria mv file("" ~) (na verdade o ~ teria que ser escapado para não sofrer tilde expansion...). Talvez dê para sobreviver.

^D

Por hoje ficamos por aqui. Como sempre, tudo o que foi apresentado são só os planos e idéias atuais, tudo pode ser mudado, e comentários e sugestões são muito bem-vindos (mas provavelmente só vou ver/responder comentários depois do fim-de-semana).

_____

1 Ou talvez um e+42 da vida (talvez só como formato de entrada válido, mesmo que as operações do shell sempre produzam resultados em notação decimal).

2 (update) Ou adicionar uma opção -overwrite à função map (que parece uma coisa mais shell-like); ou ainda, adicionar opções -collect e -overwrite à each e nem ter uma map separada.

3 (update) Note' to self: Isso também é uma string misturada com um array, então o my x=(1 2 3) não é mais um caso especial para o parser (ou pelo menos para o "reader", porque ainda teria uma interpretação diferente do caso foo(1 2 3)).

4 (update) Na verdade não conflita, porque um array não faz sentido como primeira coisa na linha de comando (ou faz?).

5 (update) Parafraseando um grande sábio, "If you want Tcl, you know where to find it." (Dito isso, eu vejo mérito na abordagem "everything is a string".)

5 comentários

Blueprints for a shell, parte 2: Variáveis, definições e escopo

2015-03-13 00:11 -0300. Tags: comp, prog, pldesign, shell, lash

Este post é parte de uma série sobre o lash, um shell que eu estou ideando.

Um pouco de contexto

Em (ba)sh todas as variáveis são globais (inclusive as "locais", que são globais com escopo dinâmico). Independentemente das variáveis do shell, todo processo no Unix possui um conjunto de variáveis de ambiente (environment variables). Os shells tendem a unificar variável do shell e de ambiente de alguma forma. A maneira como isso é feito em (ba)sh é tratar todas as variáveis uniformemente como "do shell" e marcar certas variáveis como "exported": essas variáveis são passadas como variáveis de ambiente para os processos chamados pelo shell. Além disso, o bash possui um comando local, que faz com que os valores atribuídos às variáveis passadas ao local a partir desse ponto só durem até a função onde o local foi chamado retornar, i.e., o local permite "shadowar" uma variável durante a execução de uma função. Funções chamadas pela função que declarou a variável "local" também vêem o novo valor, e nada impede "localizar" uma variável de ambiente (que continua sendo uma variável de ambiente).

Nessa situação, determinar a que variável o código está se referindo ao dizer $x é uma questão bastante simples: só existe uma variável x no programa inteiro. Evitar conflitos de nomes é basicamente problema do programador.

Se isso já é um problema em bash, em um shell com lambdas isso seria um disastre, pois um bloco de código pode ser chamado dentro de uma função diferente da que o definiu, e quem escreve o bloco não necessariamente tem como saber (nem deveria ter que saber) os nomes das variáveis usadas nesse outro ponto do programa. Assim, lash adota escopo léxico, como qualquer linguagem sã, o que significa que pode haver múltiplas variáveis com o mesmo nome em um programa. Isso também implica que nós vamos ter que conciliar escopo léxico com variáveis de ambiente.

So, variáveis em lash

O comando my introduz variáveis léxicas, cujo escopo é o bloco onde o my se encontra. A sintaxe básica é:

my nome = valor

Eu estou meio na dúvida quanto ao uso de espaços em torno do =. Em bash, atribuição de variável não permite espaços. Não havendo espaços, seria possível definir múltiplas variáveis no mesmo comando:

my x=1 y=2 z=3

Com espaços, para a coisa continuar legível, acho que seria necessário introduzir um delimitador entre as atribuições, mas isso não é tão simples em um shell, porque em:

 my x=1, y=2, z=3

a vírgula poderia ser parte da string que se está atribuindo. Uma alternativa é permitir declarar uma única variável com espaços, ou múltiplas variáveis sem espaços. A sintaxe não é ambígua, de qualquer forma.

Pergunta: uma definição com my x=1 afeta referências a x no mesmo bloco que apareçam antes do my? Por exemplo, em:

my x = 1
while {true} {
    echo $x
    my x = 2
    echo $x
}

que x é visto pelo primeiro echo quando o while executar pela segunda vez? Ou, de maneira mais convoluta:

my x = 1
my block = {
    my f = { echo $x; }
    my x = 2
    $f
}

imprime o valor de qual x? Se o desejado for o 1, então a implementação de variable lookup tem que tomar o cuidado de não simplesmente pegar o primeiro x subindo na hierarquia de ambientes (a princípio o bloco interno procuraria a variável x primeiro no ambiente do próprio bloco, depois no bloco em que o bloco se encontra, depois fora dos blocos). Por outro lado, essa semântica em que a referência a uma variável nunca muda, independente de declarações posteriores, permitiria resolver tudo estaticamente, o que pode deixar o lookup com uma performance melhor. Outra questão é: esse tipo de coisa acontece na prática? Eu fico seriamente tentado a dizer que é indefinido nesses casos qual das duas variáveis é acessada. Provavelmente alguém vai querer comer meu fígado por introduzir comportamento indefinido em um shell, mas eu não estou propondo nada da natureza de comportamento indefinido em C, em que o programa pode fazer qualquer coisa, incluindo roubar seu dinheiro e fugir do país; certamente uma das duas variáveis é acessada, sem nenhum efeito inesperado. A idéia é apenas manter em aberto a possibilidade de diferentes implementações de lookup de variáveis. Se você acha que isso é uma má idéia, por favor se manifeste.

Atribuição

Estou na dúvida se atribuição vai usar uma keyword do tipo set, ou se só o sinal de igual vai ser suficiente. Parece concebível que alguém invente um comando que recebe = como argumento, então:

foo = 42

poderia ser uma chamada a foo. Esse problema poderia ser evitado exigindo set foo = 42, ou proibindo os espaços em volta do = (que é o que o (ba)sh faz), mas o espaço me parece bem desejável quando o valor atribuido é uma expressão maior com chamadas a funções e what-not, ou quando o lado esquerdo é um array[índice]. Por outro lado, não lembro de nenhum comando que recebe = como primeiro argumento, então talvez tratar um = não escapado/quoted na segunda posição como algo especial e dispensar o set não seja problema. Será?

Também há de se considerar a possibilidade de introduzir outros operadores de atribuição, como +=, e nesse caso, se haverá operadores separados para strings, números e arrays ou se um só basta. (Em bash, += appenda strings e arrays; olhando o lado direito da atribuição dá para saber qual é o caso. Para incrementar variáveis numéricas, é necessário estar em "modo de expressão aritmética", i.e., dentro de ((...)), $((...)), índice de array, etc.)

O que acontece ao se atribuir um valor a uma variável não declarada? Acho que isso seria no mínimo um warning, talvez um erro. Acessar uma variável não-definida também, mas seria bom ter alguma coisa equivalente ao ${var:-default}, i.e., "usa o valor de $var, ou a string default caso var não esteja definida (ou seja vazia, se o : estiver presente)". Eu tinha pensado em ter uma função or valor1 valor2, que devolve valor1 se ele for um valor diferente da string vazia (ou um valor nulo especial? nós teremos um?), ou valor2 caso contrário. O problema é que $[or $var default] vai emitir um warning se $var não estiver definida. Talvez pudesse haver uma sintaxe especial $?var que devolve o valor da variável ou vazio caso ela não exista, sem emitir um warning, e então o equivalente do ${var:-default} seria $[or $?var default]. Meio verboso, mas não parece ruim (eu acho).

Variáveis globais

Nós teremos um sistema de módulos (cujos detalhes eu ainda não pensei direito e que será assunto de um post futuro), e concebivelmente um módulo poderá querer tornar algumas variáveis visíveis a outros módulos. Possibilidades:

Separar variáveis públicas das demais parece uma boa, mas não sei se não é "só uma coisa a mais".

Funções

Funções e variáveis vivem em namespaces separados em (ba)sh, e a princípio isso deve ser mantido em lash. Em (ba)sh, todas as definições de função possuem escopo global (na verdade tudo tem escopo global em (ba)sh). Como já comentado anteriormente, embora possa parecer "óbvio" mudar isso em lash e tornar as definições de função léxicas, assim como as variáveis, código como:

if {some-condition} {
    def foo {
        ...
    }
}

em que se espera que a definição de foo resultante seja global, é comum em arquivos de configuração e afins. Possibilidades:

  1. def define funções globais, i.e., no escopo do módulo em que a definição foi feita. (No escopo léxico, ou no escopo dinâmico? Se um bloco que contém um def é passado como argumento e chamado em uma função definida em outro módulo, em que módulo o def tem efeito? Bom, a julgar pelo if, no módulo em que o def se encontra, i.e., no escopo léxico.) Não há definições locais de função e era isso.
  2. def define funções globais, mas é possível escrever algo como my def foo { ... } para definir uma função local. Pode ser uma boa, só não sei se vale a pena o esforço. Também teria algum efeito no lookup de funções/comandos que precisa ser melhor considerado.
  3. def define funções no escopo léxico local. Bagunça com o caso do def dentro de um if, mas isso poderia ser contornado permitindo algo como public def foo { ... } dentro do if. (Mas quem disse que eu queria exportar do módulo? Também poderia ser usada uma keyword diferente (e.g., global), que torna global mas não exporta do módulo.)

No momento eu estou inclinado à alternativa (1), mas aceito contra-argumentos.

Funções definidas em um módulo são visíveis a partir de outros módulos por default, ou é necessário dizer public def foo { ... } para exportar uma função? (Lembrando que a gente nem decidiu ainda se vai ter uma keyword public ou não na linguagem...)

Variáveis de ambiente

O escopo de uma variável de ambiente a princípio é o processo inteiro. (É possível conceber que cada módulo pudesse ter sua própria idéia de ambiente, mas acho que nunca antes na história desse país uma linguagem tratou variáveis de ambiente assim.) Em um shell, espera-se acessar variáveis de ambiente com a mesma sintaxe das variáveis comuns (acho inventar uma sintaxe nova para dizer $HOME não vai ser uma proposta popular). Outra peculiaridade das variáveis de ambiente é que seus valores só podem ser strings. Seria possível serializar outros valores para permitir passá-los como variáveis de ambiente para subprocessos, mas só o lash reconheceria essas variáveis como valores especiais, e seria necessário indicar de alguma maneira reliable que a variável contém um valor especial, e não uma string que parece muito com um valor especial. Depois do causo do ano passado com o Shellshock, eu estou meio receoso de permitir coisas que não sejam strings em variáveis de ambiente.

Em bash uma conseqüência não muito agradável de o shell misturar as variáveis de ambiente com as comuns é que é possível um script começar a usar uma variável feliz da vida sem saber que havia uma variável de ambiente com o mesmo nome. Isso é agravado pelo fato de que em bash uma variável inexistente pode ser usada sem warning nem erro (a menos que set -u esteja ativo), então um script pode ser escrito assumindo que uma dada variável está vazia e inadvertidamente herdar do ambiente uma variável com conteúdo. Mesmo que esse não seja o caso e o script inicialize suas variáveis antes de usar, ele ainda pode estar inadvertidamente alterando uma variável de ambiente, que será herdada por subprocessos.

Em lash a situação a princípio é menos problemática porque toda variável tem que ser declarada antes de usar, e um my sobrepõe uma variável de ambiente de mesmo nome. Em geral, se eu esquecer de declarar a variável, o shell emitirá um erro, então um script que roda sem erros para mim pelo menos está imune a variáveis de ambiente inesperadas presentes nos sistemas dos outros, mas eu ainda posso acabar esquecendo o my sem gerar erro se der o acaso de eu usar um nome de variável que é uma variável de ambiente presente no meu sistema. Soluções:

  1. Exigir que toda variável de ambiente usada seja explicitamente importada antes do uso. Acho que isso não seria uma opção muito popular. Talvez não fosse tão ruim se algumas variáveis mais tradicionais fossem importadas por default (e.g., HOME, USER), mas isso me parece super-arbitrário.
  2. Permitir o acesso a variáveis de ambiente como qualquer outra variável, mas permitir atribuição apenas com um comando especial (e.g., setenv HOME = /). Acho que isso pega como erro a grande maioria das capturas indevidas de variáveis de ambiente. Fica o caso de se o programador erra o nome da variável de ambiente (uma nova variável seria criada, ao invés de emitir um erro). Evitar esse problema acho que traria mais inconveniente do que vantagem.
  3. Não fazer nada. Na real isso mal é uma opção, já que o setenv tem que existir de qualquer forma para criar variáveis de ambiente novas, e uma vez que ele exista não tem por que não aplicar a solução (2).

So (2) it is, aparentemente.

Escopo dinâmico

E quando eu quero escopo dinâmico, after all? Pode-se argumentar que ninguém em sã consciência quer escopo dinâmico, mas, por exemplo, se formos implementar o tal pipeline de objetos, precisamos de um meio de redirecionar o canal de saída de um comando para o canal de entrada de outro, e uma maneira de fazer isso é ter os canais de entrada e saída como variáveis dinâmicas e shadowá-las para fazer o redirecionamento; é como normalmente se redireciona *standard-output* e companhia em Common Lisp, e (current-output-port) et al. nos Schemes que suportam "fluid variables" (que são variáveis dinâmicas com outro nome).

Se formos ter variáveis dinâmicas, para evitar o caos manifesto, parece uma boa exigir que elas sejam previamente declaradas como tal (i.e., não é possível "localizar" a la bash uma variável previamente declarada com my). Também há o problema de como implementar o escopo dinâmico. Na situação em que só há uma thread, a operação de shadowar uma variável pode ser implementada simplesmente salvando o valor antigo, atribuindo o valor novo, e depois restaurando o valor antigo. Quando há múltiplas threads, entretanto, deseja-se que um shadow dentro de uma thread não afete as outras. E guess what? O nosso pipeline de objetos exige que cada parte do pipeline rode simultaneamente (ou pelo menos cooperativamente), dentro do mesmo processo, e o que cada uma vê como canal de entrada e de saída é diferente, então essa implementação "ingênua" de shadowing não nos serve.

Eu tenho um certo receio de que, a menos que as variáveis dinâmicas sejam identificáveis estaticamente, a presença delas bagunce / afete a performance do lookup de todas as variáveis. Quando a definição da variável dinâmica está lexicamente visível é fácil distingui-las, mas quando elas vêm de outro módulo, isso pode ser complicado. Uma solução é simplesmente usar uma sintaxe diferente para acessar variáveis dinâmicas, e.g., earmuffs: $*output_channel*. Essa sintaxe tem a vantagem de ser imediatamente familiar ao grande contingente de programadores de Common Lisp (right?), e a desvantagem da potencial confusão com o * que faz globbing (e.g.:

dynamic *prefix* = foo
touch foo1 foo2 foo3
echo $*prefix**

), mas outra sintaxe que distinguisse variáveis dinâmicas de variáveis comuns poderia ser escolhida.

Acho que por hoje deu

Reiterando, sempre que eu digo que alguma coisa em lash "é" de tal e tal jeito, eu só quero dizer que esse é o plano atual, mas estou aberto a sugestões. Feedback é sempre bem-vindo.

8 comentários

Main menu

Posts recentes

Tags

comp (87) life (35) prog (34) unix (30) random (26) lang (23) mundane (21) about (19) mind (16) web (13) img (13) pldesign (12) ramble (11) rant (11) privacy (8) bash (7) esperanto (7) lash (7) academia (6) home (6) shell (6) freedom (5) conlang (5) misc (5) copyright (4) mestrado (4) book (4) worldly (4) lisp (4) music (4) film (3) kbd (3) security (3) wrong (3) latex (3) editor (3) politics (3) cook (2) android (2) poem (2) php (2) treta (2) comic (2) c (2) physics (2) network (2) pointless (1) audio (1) philosophy (1) perl (1) emacs (1) kindle (1)

Elsewhere

Quod vide


Copyright © 2012-2015 Vítor Bujés Ubatuba De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.