Esta é uma versão arquivada/estática do antigo Blog do André. Isso significa que todo o conteúdo aqui presente não irá ser atualizado, e pode conter erros. Algumas funcionalidades poderão não estar disponíveis nesta versão arquivada.
07
Dez 2009

Linguagens esotéricas: Brainfuck

Tutorial actualizado e melhorado em https://andresilva.me/notes/tutoriais/tutorial-de-brainfuck

No mundo da programação existem as mais variadas linguagens. Uma boas, outras más, e outras de utilidade duvidosa. Hoje explorei um pouco do mundo das linguagens esotéricas. E o que são linguagens esotéricas? Muito resumidamente, são linguagens difíceis de compreender, e têm por base o objectivo de decifrar o que fazem. São por isso linguagens apenas para divertir e passar um bom bocado.

Vou falar em particular numa que aprendi, o Brainfuck (também conhecido por Brainf*ck, ou simplesmente por BF, devido ao seu nome polémico; ao longo do artigo vou referir-me à linguagem de acordo com o seu nome original, com isto espero não ofender ninguém, pois não é essa a intenção ;) ). O Brainfuck é uma linguagem cujo objectivo é “torrar cérebros” a ler o código. Mas, vão ver que depois de aprenderem toda a lógica de Brainfuck, ainda vão conseguir criar umas coisas engraçadas de forma fácil! Podem depois lançar o desafio para amigos, colegas, interessados na programação, etc.

Antes de continuar a avançar “em seco”, começo já por deixar um exemplo de um típico “Hello World”:

++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++
..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.

Parece confuso, certo? No final do tutorial, vamos ver em detalhe o que isto faz. Para avançar com as explicações, é preciso que estejam familiarizados com códigos ASCII (recomendo que tenham esta página aberta enquanto analisam os exemplos!) e noções básicas de apontadores (ou vectores, também serve para o efeito). Então vamos lá!

Como funciona o Brainfuck?

No Brainfuck, como noutras linguagens, temos variáveis. As variáveis são, na verdade, um vector, em que cada posição corresponde a uma variável. Não há por isso nomes ou etiquetas que correspondam a uma variável, mas sim uma posição numérica, 0, 1, 2, etc. Cada posição do vector irá guardar um valor inteiro, que faz correspondência com um caractere da tabela ASCII (estão a ver agora a necessidade de ter a tabela ASCII rapidamente acessível? :D ). Talvez ainda perguntem qual o limite desta memória: depende do interpretador que usarem para correr o código, mas normalmente é um número que não levanta preocupações.

Sobre o código que é escrito, apenas utilizamos alguns operadores, como vamos ver em baixo.

Operadores em Brainfuck

Em Brainfuck, temos ao total apenas 8 operadores. Tipicamente, os interpretadores de Brainfuck ignoram tudo o que não sejam estes 8 operadores, por isso tudo o resto pode ser considerado como comentários, ou como se não existisse:

Operador Equivalente em C Explicação
> ++p (incr. endereço) Muda para a posição seguinte do vector.
< −−p (decr. endereço) Muda para a posição anterior do vector
+ ++(*p) Incrementa o valor do vector na posição corrente
- −−(*p) Decrementa o valor do vector na posição corrente
. putchar(p) Imprime no ecrã o caractere ASCII com o valor do vector na posição corrente
, *p = getchar(p) Lê um caractere de teclado* para a posição corrente do vector
[ while(*p) { Inicio de um ciclo while, com o valor da posição actual do vector como guarda/condição
] } Fim de ciclo

* Não tem necessariamente de ser do teclado. Alguns interpretadores requerem que este input seja previamente passado como argumento ao programa, por isso depende sempre do interpretador/compilador que estiverem a utilizar.

Manipulação básica

Em Brainfuck, temos uma memória com estado, isto é, a cada operação, o estado da memória altera-se de acordo com a acção, e todas as alterações são reflectidas em operações futuras. A intenção é colocar valores em cada posição da memória/vector com um valor inteiro. Vamos ver um exemplo prático:

++++++++++
++++++++++
++++++++++
++++++++++
++++++++++
++++++++++
+++++
.

Experimentem ist… Ops! Ainda não falamos de compiladores e interpretadores! Para falar verdade, nem precisamos de nos esforçar muito. No meu caso, optei por utilizar um interpretador online, feito em Javascript (sim, é Javascript. Para os que gostam de olhar para o código fonte, dêem uma olhada e vejam quão simples é!): Kit’s Javascript Brainfuck Interpreter. Copiem o código do exemplo que vos dei e experimentem. Vão obter como Output a letra A maiúscula. Após a decepção de ter tanto código apenas para obter aquilo, continuemos! :D

Segundo a tabela de oepradores que já foi apresentada, o operador + (mais) serve para incrementar numa unidade o valor da posição actual do vector (inicializado internamente a zero). As 6 primeiras linhas têm 10 operadores + e a penúltima tem mais 5 operações. Sabem com que valor vamos ficar? Exactamente 6×10 + 5 = 65, que como alguns já devem ter adivinhado, trata-se do código ASCII para o caractere ‘A’ maiúsculo. Por fim, a última linha contém o operador . (ponto), que nada mais faz que ir buscar o valor actual e imprime o caractere correspondente! Já parece mais simples, certo? xD

Porém, até aqui ainda só usamos uma posição do vector, ou seja, só usamos uma variável, na qual guardamos o valor. Vamos ver agora como usar outras posições de memória, e guardar lá valores.

Trabalhar com várias posições do vector

Vamos então alterar o nosso pequeno exemplo e introduzir novos operadores:

++++++++++
++++++++++
++++++++++
++++++++++
++++++++++
++++++++++
++++++++++
++++++.
>
++++++++++
++++++++++
++++++++++
++++++++++
++++++++++
++++++++++
++++++++++
+++++++++.
<.

Corram agora este exemplo no Kit’s Brainfuck Interpreter. Deve-vos aparecer a típica expressão “LOL”, igualmente em maiúsculas. Ufa! Tanto código só para aparecer aquilo? É semelhante ao exemplo anterior, com umas diferenças. Como se lembram, a memória do Brainfuck é um vector, logo tem mais que uma posição. O que o operador > (maior) faz é mudar para a posição à direita, ou intuitivamente, a posição seguinte. Devo lembrar que todas as posições são inicializadas a zero internamente. Então o que está a acontecer?

Nas primeiras 8 linhas, é colocado 1ª posição do vector o valor 76, que corresponde ao L maiúsculo em ASCII. De seguida imprimimos para o ecrã, com o operador . (ponto), tal e qual como anteriormente. Na 9ª linha, encontramos o operador >, que vai trocar para a posição seguinte do vector. Então significa que temos agora duas posições conhecidas no vector: a primeira, com o valor 76, e agora esta 2ª posição para a qual acabamos de mudar, inicializada a 0. Fresquinha e fofinha :D

Vamos então criar a letra O, cujo código ASCII é 79. Tal como para o L, vamos colocar nesta nova posição do array o valor 79 correspondente e imprimimos. Tal como anteriormente, chamamos tantos operadores + quanto necessários para obter o valor 79. Resta-nos a última letra L. Podíamos colocar novamente 76 operadores de incremento numa nova posição do vector, mas lembram-se de que na primeira posição ainda temos lá o nosso código 76 (letra L)? Então, tudo o que temos de fazer é ir lá buscá-lo: voltamos à posição 0 do vector, e imprimimos o L. É tudo o que faz a última linha de código.

Para tentar simplificar, fica uma tabela de resumo com linhas importantes onde ocorrem mudanças de valores:

Linha p[0] (1ª posição) p[1] (2ª posição) Output
(inicio programa) 0 0
1 10 0
2 20 0
8 76 0 L
10 76 10 L
11 76 20 L
17 76 79 LO
18 76 79 LOL

Como viram, o valor da primeira posição manteve-se e foi aproveitado para mostrar novamente no ecrã a letra L maiúscula, sem esforço adicional. Como nota, qualquer programa começa sempre na primeira posição, e guarda sempre a posição actual ao longo da execução, conforme vai sendo alterada.

Compreender os ciclos

Já oiço alguém a implicar que estão demasiados operadores  no código e que existem muitas linhas repetidas! Eu também sou dessa opinião. O Brainfuck também tem ciclos, semelhantes ao while: vamos tirar partido deles para escrever (um pouco) menos linhas de código. Como sabemos, no ciclo while, temos uma condição, que enquanto foi verdadeira, o ciclo continua. No Brainfuck, apenas consideramos valores inteiros. Por isso, necessitamos uma posição do vector que vai fazer de condição de paragem do ciclo. Para ser mais específico, vamos ter de inicializar uma posição do nosso vector, com o número de iterações que queremos que o nosso ciclo faça, e ir decrementando. A nossa condição de paragem é quando este valor chegar a zero: aí, o ciclo deixa de executar, logo interessa-nos colocar exactamente o número de vezes que queremos que o ciclo repita, e ir subtraindo até chegar a 0. Vamos voltar ao exemplo da letra A, agora com ciclos:

++++++
[
>+++++++++++
<-
]
>-
.

Já parece mais pequeno! xD Mas continua apenas a escrever um A maiúsculo. Analisando: a primeira linha inicializa a primeira posição do vector com 6. Hã, mas não era 65 que queríamos? Sim. Mas não vai ser esta posição que vamos imprimir. Na verdade, esta posição vai ser a condição de paragem do nosso ciclo. Vamos então analisar o corpo do ciclo (lembram-se para que servem os parêntesis rectos? ;) ) para ver que acção se repete. Na 3ª linha, já dentro do ciclo, trocamos para uma posição seguinte no vector e incrementamos 11 unidades. Na 4ª linha, regressamos à posição inicial do vector, onde está o nosso contador de ciclo, e decrementamos (operador – (menos)) uma unidade. Agora, como a condição de paragem ainda não está satisfeita (6 – 1 = 5 é diferente de 0), regressamos à 3ª linha, e repetimos a execução.

Vamos continuar o ciclo. E o que é que ele faz? Exactamente o mesmo! (é o que se pode esperar de um ciclo :D ) Muda para a 2ª posição de memória, incrementa mais 11 unidades (totalizando agora o valor 22 para a 2ª posição), e retorna à primeira posição, retirando-lhe uma unidade… E repete, e repete. Até a primeira posição conter o valor zero. Vejamos um quadro resumo com as iterações que o ciclo faz:

Nº Iterações ciclo p[0] (1ª posição) p[1] (2ª posição)
Antes do ciclo 6 0
Após 1ª 5 11
Após 2ª 4 22
Após 3ª 3 33
Após 4ª 2 44
Após 5ª 1 55
Após 6ª 0 66

No final da execução do ciclo, ficamos com a 1ª posição a 0 e a 2ª posição com 66 unidades. Porquê? O nosso ciclo repetiu a operação de incrementar 11 valores na 2ª posição 6 vezes ao todo, certo? Tão simples como 6×11 = 66. E se bem se lembram, queremos atingir o valor 65 para escrever uma A no ecrã. Ah, agora já só falta decrementar uma unidade na 2ª posição e imprimir! É isso mesmo que a penúltima e última linha fazem: muda novamente para a 2ª posição de memória (isto é necessário, porque a última condição do ciclo mudou para a 1ª posição, a fim de decrementar o “contador”) e decrementa 1 unidade, totalizando 65, e imprimimos para o ecrã. Os ciclos dão jeito, certo? xD

Para os mais distraídos, há um pormenor importante que pelo menos alguns já devem ter reparado: nós com os ciclos obtemos sempre aproximações dos números ASCII que pretendemos, com vista a diminuir o código produzido (sobretudo para evitar o desgaste prematuro da tecla + ) :D . Neste caso, usamos a aproximação de 66, para escrevermos o 65 (po outro lado, podiamos incrementar 5 unidades 13 vezes, ou vice-versa, e ter o valo 65, mas fiz de propósito neste exemplo, porque nem sempre é possível obter um valor certo). Assim, no fim de cada ciclo, basta mudar para a posição do vector e incrementar/decrementar  para obter o valor pretendido, se for necessário.

Finalmente, o Hello World! desvendado

Estamos então munidos das armas necessárias para atacar e perceber o Hello World! (que ironicamente, deveria ser a primeira coisa a ser percebida…): operações de incremento/decremento, mudar a posição do vector e ciclos! Vamos ver o programa de novo:

++++++++++
[
  >+++++++
  >++++++++++
  >+++
  <<<-
]
>++.                    # Escrever H
>+.                     # Escrever e
+++++++..               # Escrever l (2 vezes)
+++.                    # Escrever o
>++.                    # Escrever um espaço
<<+++++++++++++++.      # Escrever W
>.                      # Escrever novamente o
+++.                    # Escrever um r
------.                 # Escrever um l
--------.               # Escrever um d
>+.                     # Escrever um !

Uma explicação de texto fica muito maçuda para ler os conceitos que já foram transmitidos, por isso deixo aqui uma tabela resumo com as linhas mais importantes do código:

Linha p[0] (1ª pos) p[1] (2ª pos) p[2] (3ª pos) p[3] (4ª pos) Output
8 0 72 * 100 30 H
9 0 72 101 * 30 He
10 0 72 108 * 30 Hell
11 0 72 113 * 30 Hello
12 0 72 113 32 * Hello **
13 0 87 * 113 32 Hello W
14 0 87 113 * 32 Hello Wo
15 0 87 116 * 32 Hello Wor
16 0 87 108 * 32 Hello Worl
17 0 87 100 * 32 Hello World
18 0 87 100 33 * Hello World!

* Posição actual do vector após execução da linha

** Palavra Hello, com um espaço, “Hello ” (espaço -> caractere 32 na tabela ASCII)

Alternando as posições de memória, e aproveitando de forma inteligente os valores que já dispomos, conseguimos minimizar os programa em Brainfuck de modo a parecerem mais simples.

Receber input de dados

Já vimos até agora 7 dos 8 operadores de Brainfuck. Falta-nos apenas falar do operador responsável pela leitura de dados, o operador , (vírgula). De forma bastante simples, este operador lê um único caractere, obtém o seu valor inteiro equivalente, segundo a tabela ASCII, e armazena-o na posição do vector corrente. O seu funcionamento depende da implementação de cada compilador/interpretador: os caracteres podem ser lidos de ficheiros, teclado, por passagem de argumentos ao programa, etc. Não vou avançar muito mais que isto, pelo que me fico por um exemplo muito simples:

,-.+.+.

Na sua essência, este programa recebe um caractere como argumento um caractere, e mostra o caractere anterior, o próprio, e o caractere seguinte, segundo a tabela ASCII. Por exemplo, se introduzirem na caixa Input do Kit’s JavaScript Brainfuck Interpreter (antes de correr o programa) a letra D, o programa irá produzir como Ouput o resultado ‘CDE’.

Desafio

Porque nem sempre apenas a ler teoria se aprende, é sempre bom treinar a componente prática. Deixo-vos 2 desafios para treinar:

  1. Fazer um programa que imprima o alfabeto de letras minúsculas de A até Z, faça uma quebra de linha, e imprima os números de 9 até 0 (ou seja, ordem inversa de 0-9)
  2. Dados dois caracteres como argumento, o programa deve dizer quantos caracteres de distância faz de um a outro. Por exemplo, se tivermos as letras A e C, o resultado deve ser 2; se tivermos os caracteres D e O o resultado deve ser 11. (Não esquecer que o Brainfuck apenas imprime caracteres da tabela ASCII e não valores, dica útil para apresentar o resultado). Restrições: a distância não deve ser maior que 9 caracteres.

Postem os vossos resultados nos comentários, dúvidas, etc. Deixo também soluções que apenas são exemplos, e poderão não ser as mais optimizadas. Download das soluções.

Mais recursos

Para finalizar este tutorial de Brainfuck, sugiro-vos alguns endereços web onde podem explorar mais sobre a linguagem, das quais recolhi muita informação para criar o artigo:

Kit’s JavaScript Brainfuck Interpreter v0.1

Javascript Brainfuck Interpreter / Debugger

brainfuck – Wikipédia, a enciclopédia livre

http://esolangs.org/wiki/Brainfuck

http://99-bottles-of-beer.net/language-brainfuck-101.html

Brainfuck – Wikipedia, the free encyclopedia

The Brainfuck Programming Language

Afinal o Brainfuck não é assim tão difícil, pois não? Críticas construtivas, correcções, sugestões e dúvidas são bem-vindas! Venham elas! xD

Gostou deste artigo?

Facebook Twitter Google Plus Delicious

5 Comentários

 

  • Gravatar de Ivanoel

    Ivanoel

    30/12/2009 @ 23:58

    Eu tive conhecimento da linguagem à umas horas ao ver um compilador online da mesma…. Tenciono aprendê-la pois é mesmo o cumulo da programação :D

    Acredito que agora saibas a tabela ASCII de cor e salteado não?!

  • Gravatar de Ivanoel

    Ivanoel

    31/12/2009 @ 00:00

    Sorry .. esqueci-me de referir o tal compilador online que te pode ser útil :D

    http://www.ideone.com/

    Cumps

  • Gravatar de André

    André

    31/12/2009 @ 10:34

    Olá Ivanoel, bem vindo ao meu cantinho. ;)

    Brainfuck é uma linguagem fácil de aprender, por também ser bastante reduzida em termos de operadores. Ainda dá para fazer umas brincadeiras engraçadas :D

    Quanto ao saber a tabela ASCII de cor, nem por sombras, mas olha que estimula mesmo, e até já sei algumas posições de cor :D

    Obrigado pela dica do compilador, muito útil mesmo. Fica então a dica para os demais! ;)

    Já agora, se seguires este tutorial e tiveres alguma dúvida, tens os links de recursos no fim do tutorial, ou deixa aqui um comentário, que te irei responder assim que me for possível :D

    Cumps.

  • Gravatar de Jose Carlos

    Jose Carlos

    23/06/2010 @ 00:28

    Estou procurando pessoas para fazerem alguns software esotéricos, sobre Tarô, Runas, Astrologia, Numerologia, e outros ,
    entrar em contato

  • Gravatar de André

    André

    23/06/2010 @ 21:55

    Olá José, bem-vindo. talvez este site não seja o mais indicado para o efeito, mas sugiro o Portugal-a-programar, onde há bastantes programadores disposto a alinhar em projectos, talvez encontre lá um ;)

    http://www.portugal-a-programar.org

    Cumps.