Continuando o projeto de fazer um interpretador brainfuck em GO

Gopher

O tempo passa

Em um texto publicado há quase cinco anos, deixei o seguinte compromisso firmado, em relação ao interpretador:

O Futuro

Está quase no ponto em que essa série deixará de ser sobre GO e o mais importante será a linguagem BF. Algumas coisas que pretendo resolver logo:

  • A leitura do código fonte ser por arquivo, e não pelo stdin.
  • Implementar a entrada de dados com o comando ,.
  • Opção, em linha de comando, para tamanho da memória e tempo máximo de execução, ou quantidade máxima de instruções executadas (minimizar os efeitos de loops infinitos).
  • Um jeito visual de apresentar a execução dos programas, com apresentação dos valores em memória.
  • Testes, muitos testes.

E não é que abandonei isso? Mas chegaram minhas férias, tempo de diversões, entre elas, programar coisas fora do contexto profissional, eminentemente “crudeiro”.

Um código feito há cinco anos… Um estudo em que não estava evoluindo, mas que agora por motivos profissionais, que é dominar a linguagem Go, vai nem que seja na marra!

O que foi feito

Como um bom backlog, ignorei ele e fiz o que deu na telha:

  • o código-fonte em BF é passado por parâmetro, em linha de comando
  • foi implementada a entrada de dados, mas ao invés de digitar conforme esperado, passa-se como parâmetro do programa uma string com as entradas que se pretende usar
  • alguns testes foram escritos
  • realizada uma bela duma refatoração, criando um módulo e deixando o main apenas com o tratamento dos parâmetros, uso do módulo, e apresentação do resultado

Importante, a versão 1.0.0, está em:

https://github.com/paulohrpinheiro/mfbil/tree/1.0.0

O módulo principal

package main

import ( “fmt” “os”

bf “github.com/paulohrpinheiro/mfbil/bf” )

func main() { argsCount := len(os.Args) if argsCount < 2 { panic(“Please, provide a BF program as argument, and optionally, a string of input data”) }

inputData := "" if argsCount == 3 { inputData = os.Args[2] }

vm := bf.New(os.Args[1], 3000, inputData) err := vm.Run() if err != nil { panic(err) }

fmt.Println(string(vm.Output)) }

Como se pode ver, o “salsichão” de código transforma-se no apenas necessário para processar os parâmetros passados, fazer as chamadas adequadas, e imprimir o resultado. Aquele “3000” agride os meus sentimentos.

O módulo BF

O coração do módulo está no que chamo de sua “máquina virtual”:

type VirtualMachine struct { source []rune memory []rune pos int instruction int Output []rune inputData []rune }

Com essa estrutura, as funções associadas tem “em mãos” os dados necessários para executar o programa BF.

Em source tem-se o código-fonte que se deseja executar. Já memory é a memória em que o programa executado opera. A variável pos aponta para a posição corrente na memória. Para o controle do fluxo, usa-se instruction como ponteiro para o comando sendo executado. As impressões (comando .) estão em Output. E, finalmente, inputData contém os dados que serão usados pelo comando de leitura(,).

Deve-se, primeiro, criar um ponteiro para a estrutura usando-se a função New() do pacote BF:

func New(s string, m int, i string) *VirtualMachine { return &VirtualMachine{ source: []rune(s), memory: make([]rune, m), inputData: []rune(i), } }

Para então executar o programa com a função Run(), que trabalhará com essa estrutura.

Testes

Além de testes para cada operador, há alguns testes coletados Internet afora. Mais testes existem, e devem em breve serem incorporados.

Usa-se tabela de testes e o pacote testify, para asserções:

func TestOperatorNext(t *testing.T) { tests := []testsReturnInt{ {source: “>”, want: 1}, {source: “»»”, want: 4}, {source: “»”, want: 2}, {source: “»»>”, want: 0}, }

for _, c := range tests { vm := New(c.source, 5, “”) err := vm.Run() assert.Nil(t, err) assert.Equal(t, c.want, vm.pos) } }

Planos para os desenvolvimentos posteriores

O que está ululando para ser resolvido:

  • configurar o tamanho da memória através de parâmetro (atualmente usando 3000 pinado no código)
  • configurar o tempo máximo de execução (dá-lhe o context do Go)
  • implementar mais testes, especialmente esses aqui
  • uso de algum módulo para tratamento de parâmetros usando opções