Paulo Henrique Rodrigues Pinheiro

Um blog sobre programação para programadores!


Refatorando MFBIL - interpretador de BF

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:

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: