O livro "The Pragmatic Programmer" (se ainda não o leu, pare de ler este artigo e faça-o agora!) diz que todos os anos devemos aprender uma nova linguagem de programação.
Embora alguns possam argumentar que é demasiado esforço, todos podemos concordar que pode ser uma boa ideia. Escolher uma nova língua para aprender não é assim tão fácil. Não queremos dedicar o nosso tempo a algo que talvez nunca venhamos a utilizar na prática, pois não? Mas talvez, por vezes, devêssemos abrir uma exceção e aprender algo só pelo prazer de o fazer? Gostaria de vos apresentar a linguagem Brainfuck. É uma linguagem que se aprende em poucos minutos, por isso não há problema em investir demasiado tempo em vão. Além disso, posso prometer que a resolução de qualquer problema com o Brainfuck vai estimular o teu cérebro (todas as merdas são apenas um bónus ;)). Vamos começar! De acordo com a Wikipédia:
Brainfuck é um linguagem de programação esotérica criada em 1993 por Urban Müller. A linguagem é constituída apenas por oito comandos simples e um ponteiro de instruções. Embora seja totalmente Turing-completa, não se destina a uma utilização prática, mas a desafiar e divertir os programadores.
Visão geral da língua
Imagine uma fita infinitamente longa composta de células, cada uma inicializada em 0. Há também um ponteiro de dados móvel que aponta inicialmente para a primeira célula. Existem também dois fluxos de bytes para entrada e saída. As instruções são executadas sequencialmente, uma a uma. A máquina pára após a execução da última instrução.
Comando
O que é que faz?
>
mover o ponteiro de dados para a célula seguinte à direita
<
mover o ponteiro de dados para a célula seguinte à esquerda
+
valor de incremento da célula atual
–
diminuir o valor da célula atual
.
indica o byte de uma célula atualmente apontada em ASCII código
,
lê um byte de stdin e armazena o seu valor na célula atual
[
se a célula atual for 0, saltar para a célula correspondente ]
]
saltar para o correspondente [
Todos os caracteres que não sejam ><+-.,[] são ignorados.
Vejamos um exemplo simples:
,+.
Será interpretado da seguinte forma:
ler um byte e armazená-lo na célula atual (cell0)
valor de incremento da célula atual (cell0 = cell0 + 1)
escrever o conteúdo da célula atual na saída
Como resultado, será lido um carácter da entrada e será impresso o carácter seguinte da tabela ASCII.
Interpretador / Compilador
Antes de escrevermos alguns programas úteis (?) em Brainfuck, precisamos de um interpretador ou um compilador. AFAIK, não existe um oficial, mas isso não é um problema. Há dezenas de não-oficiais na Internet. Eu posso recomendar estes dois:
O "Hello World!" deve ser o primeiro programa que escrevemos quando estamos a aprender uma nova língua. No entanto, escrevê-lo em Brainfuck é um pouco mais difícil do que noutras línguas. Temos de começar com algo mais fácil... Vamos escrever um programa que imprima uma única letra "H" no ecrã (tão excitante :D):
Como é que funciona? Define o valor da célula atual para 72 (realizando incrementos de 72) e imprime-o no ecrã utilizando "." (H tem o código 72 em ASCII). Agora já sabe o que devemos fazer para imprimir "Hello World!" no ecrã, mas antes disso, vamos fazer uma pequena refacção. Escrever todos estes '+' requer demasiada digitação e contagem. Podemos torná-lo mais curto usando [ e ] para o looping. Para definir o valor para 72, podemos, por exemplo, fazer um ciclo que aumenta o valor 7 vezes por 10. Desta forma obtemos 70. Se adicionarmos 2, obtemos 72. O resultado é o seguinte:
++++++++++ # set cell0 to 10
[ # repetir até que cell0 seja 0
- # diminuir cell0
> # mover o ponteiro de dados para a direita (cell1)
+++++++ # aumenta a célula1 em 7
# mover ponteiro de dados para a direita (cell1)
++ # aumentar em 2
. # imprimir o resultado
Incluí comentários para tornar claro como tudo funciona. O mesmo programa sem comentários:
++++++++++[->+++++++++.
Não é lindo? 🙂
Olá mundo!
Voltando ao nosso programa "Olá Mundo!". Poderíamos definir o valor da primeira célula como 72 (H) e imprimi-lo, definir o valor da segunda célula como 101 (e) e imprimi-lo, definir o valor da terceira célula como 108 e imprimi-lo e assim por diante. Aqui está a implementação deste algoritmo:
Sim, apenas 1120 bytes para imprimir "Hello World!"... Mas podemos fazer melhor! Em vez de utilizarmos uma nova célula para cada carácter, utilizemos apenas uma. Para imprimir a letra "e" (101) podemos reutilizar o valor da célula0 (72). Podemos aumentá-lo em uma unidade 29 vezes (101 - 72). E o resultado é o seguinte:
Fizemos grandes progressos. 377 bytes em vez de 1120 bytes. No entanto, ainda há espaço para melhorias. Vou dar-vos algumas ideias:
utilizar 3 (ou mais) células para caracteres que estão mais próximos uns dos outros em ASCII (letras minúsculas, letras maiúsculas, espaço e exclamação)
utilizar loops em vez de repetições + e -
Eis a versão da Wikipédia que utiliza estas ideias:
Tem apenas 106 bytes e imprime uma nova linha no final! É espetacular.
Inverter uma cadeia
Agora estamos prontos para escrever algo mais desafiante. Vamos escrever um programa que leia uma linha do input e a imprima na ordem inversa. O primeiro problema é ler os caracteres e parar no carácter da nova linha. Lembra-te, não há pausa, se ou outras declarações semelhantes. Temos de utilizar [ e ]. Vamos começar com um programa que lê todos os caracteres da entrada e os coloca em células sucessivas:
,[>,]
Começa com a leitura do primeiro carácter e continua até ao último , resultados da operação 0. No entanto, este ciclo será infinito na implementação que devolve algo diferente de O para EOF (a linguagem não especifica este comportamento). Então, como podemos parar no carácter de nova linha? Aqui está o truque:
+[++++++++++>,----------]
Começamos com a célula0 definida como 1 para garantir que o nosso ciclo é executado pelo menos uma vez. Num ciclo, aumentamos o valor da célula atual em 10, movemos o ponteiro de dados para a célula seguinte, lemos um carácter e diminuímos o seu valor em 10. Desta forma, se for lido um carácter de nova linha (10 em ASCII), o programa parará na próxima iteração, caso contrário o seu valor será restaurado adicionando 10.
Após este passo, as nossas células terão o seguinte aspeto:
11 C1 C2 C3 0* 0 0
Cn é o n-ésimo carácter da entrada, e * é a posição atual do ponteiro de dados. Agora temos de começar a mover o ponteiro de dados para a esquerda e imprimir todas as células até atingirmos o valor 11. Aqui está a minha abordagem à tarefa:
Quando me deparei com o Brainfuck, uma linguagem de programação esotérica, inicialmente não a considerei nada mais do que um truque ou uma piada. Esta linguagem peculiar e, como muitos podem argumentar, extremamente difícil, pareceu-me algo destinado apenas a divertir-me. Mas com o tempo, a minha perceção do Brainfuck mudou drasticamente.
A natureza enigmática do Brainfuck desafia-o, levando-o a alargar a sua perspetiva sobre linguagens de programação. Esta linguagem esotérica permite-lhe apreciar a beleza e a necessidade das linguagens de alto nível a que estamos habituados. Ela traz à tona a importância das abstrações, convenções de nomenclatura adequadas e um layout de memória organizado no reino das linguagens de programação. Isso é algo que o Brainfuck, com seu design minimalista que consiste em apenas oito comandos simples, não oferece.
Brainfuck é uma linguagem Turing completa que enfatiza ainda mais a importância de ter um código-fonte claro e coerente. Apesar de ser reconhecida como uma das linguagens esotéricas mais desafiadoras para escrever programas, ela ironicamente brilha como a favorita dos iniciantes para qualquer um que queira criar um compilador Brainfuck ou um interpretador Brainfuck próprio. A razão para isso é a simplicidade de seu conjunto de comandos e o fato de que não requer análise complexa.
Criar um programa Brainfuck é único em dois aspectos. Em primeiro lugar, tens de te adaptar à utilização de um único ponteiro de memória, o que te obriga a pensar de forma diferente sobre o teu código fonte. E, em segundo lugar, tens a "opção zero", que é a capacidade de repor a célula de memória a zero, uma caraterística que não é comum noutras linguagens de programação formais.
Em termos de aprendizagem, o Brainfuck é mais do que aparenta. Com tempo suficiente e a mentalidade certa, é possível escrever o mesmo programa de várias maneiras usando diferentes códigos Brainfuck. A última metade desta jornada é sobre ser inventivo e encontrar maneiras novas e criativas de utilizar os seus seis símbolos.
Os interpretadores do Brainfuck, embora minimalistas, dão-lhe uma compreensão profunda de como o código é executado, o que o programa imprime e a mecânica subjacente de uma linguagem completa de Turing. No final, Brainfuck não é apenas mais uma linguagem de programação esotérica. É uma dimensão totalmente nova, uma visão diferente de como vemos, entendemos e escrevemos programas.