Refatorando MFBIL - interpretador de BF
Continuando o projeto de fazer um interpretador brainfuck em GO
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