Go Brainf * ck переводчик

Недавно написал мозговой интерпретатор в Go.

Вот репо на Github Brainfuck

Области, требующие работы

Разбор скобок [] все еще глючит и, как известно, не работает при работе с вложенными циклами, т.е. [[]] любые подсказки о том, как подойти к проблеме, будут очень признательны.

Посмотрев на это репо. Он создает компилятор, а затем парсер для вызова кодов операций. Распространен ли этот тип дизайна при написании интерпретаторов? И следует ли мне использовать этот подход к дизайну в моем интерпретаторе?

Набор тестов очень простой и требует больше тестов, например, я использую scanner.Scan() чтобы получить ввод от пользователя, но как мне написать для этого тестовый пример? Далее есть -file который захватывает содержимое файла при указании пути к файлу, но я не знаю, как его проверить.

Общая структура моего кода пахнет плохим кодом, как я могу ее улучшить?

brainfuck.go

package main

import (
    "bufio"
    "flag"
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

const memorySize = 30000
const cellLimit = 256

// incrementPointer
func incrementPointer(mem []int, ptr *int) error {
    if *ptr >= len(mem)-1 {
        return fmt.Errorf("memory error: %d", ptr)
    }
    *ptr++

    return nil
}

// decrementPointer
func decrementPointer(mem []int, ptr *int) error {
    if *ptr < 0 {
        return fmt.Errorf("memory error: %d", ptr)
    }
    *ptr--

    return nil
}

// incrementByte
func incrementByte(mem []int, ptr *int) {
    if mem[*ptr] == cellLimit {
        mem[*ptr] = 0
    }
    mem[*ptr]++
}

// decrementByte
func decrementByte(mem []int, ptr *int) {
    if mem[*ptr] == 0 {
        mem[*ptr] = cellLimit
    }
    mem[*ptr]--
}

// outputByte
func outputByte(mem []int, ptr *int) string {
    return fmt.Sprintf("%c", mem[*ptr])
}

// storeByte
func storeByte(mem []int, ptr *int) {
    var s string
    fmt.Print("Waiting for input: ")
    fmt.Scanln(&s)
    mem[*ptr] = int([]byte(s)[0])
}

func main() {

    var memory = [...]int{memorySize: 0}
    var pointer int

    var result string

    var leftBracket int
    var rightBracket int

    filename := flag.String("file", "", "choose a brainfuck file.")
    flag.Parse()

    file, err := ioutil.ReadFile(*filename)
    if err != nil && *filename != "" {
        fmt.Print(err)
        os.Exit(1)
    }
    tfile := strings.TrimSuffix(string(file), "n")

    scanner := bufio.NewScanner(os.Stdin)

    if tfile == "" {
        scanner.Scan()
    }

    text := scanner.Text()

    var input string

    if text != "" {
        input = strings.TrimSuffix(text, "n")
    } else if tfile != "" {
        input = tfile
    } else {
        fmt.Print("nothing to parse.")
        os.Exit(1)
    }

    for i := 0; i < len(input); i++ {

        switch input[i] {
        case '>':
            incrementPointer(memory[:], &pointer)
        case '<':
            decrementPointer(memory[:], &pointer)
        case '+':
            incrementByte(memory[:], &pointer)
        case '-':
            decrementByte(memory[:], &pointer)
        case '.':
            result += outputByte(memory[:], &pointer)
        case ',':
            storeByte(memory[:], &pointer)
        case '[':
            leftBracket = i
        case ']':
            rightBracket = i
            i = leftBracket
            if memory[pointer] == 0 {
                i = rightBracket
            }
        default:
            continue
        }
    }

    fmt.Printf("%sn", result)
}

brainfuck_test.go

package main

import (
    "testing"
    // "fmt"
)

func TestIncrementPointer(t *testing.T) {

    mem := make([]int, 4)
    ptr := 0

    incrementPointer(mem, &ptr)

    if ptr != 1 {
        t.Errorf("incrementPointer was incorrect, got %d instead", ptr)
    }

}

func TestDecrementPointer(t *testing.T) {

    mem := make([]int, 4)
    ptr := 1

    decrementPointer(mem, &ptr)

    if ptr != 0 {
        t.Errorf("decrementPointer was incorrect, got %d instead", ptr)
    }

}

func TestIncrementByte(t *testing.T) {
    mem := make([]int, 4)
    ptr := 0

    want := 1

    incrementByte(mem, &ptr)

    if mem[ptr] != want {
        t.Errorf("incrementByte was incorrect, got %d instead", ptr)
    }
}

func TestDecrementByte(t *testing.T) {
    mem := make([]int, 4)
    ptr := 0

    want := 255

    decrementByte(mem[:], &ptr)
    
    if mem[ptr] != want {
        t.Errorf("decrementByte was incorrect, got %d instead", ptr)
    }
}

Пример

$./brainfuck *ENTER*
>++++++++[<+++++++++>-]<.>++++[<+++++++>-]<+.+++++++..+++.>>++++++[<+++++++>-]<++.------------.>++++++[<+++++++++>-]<+.<.+++.------.--------.>>>++++[<++++++++>-]<+
$ Hello, World!

1 ответ
1

Состав

Я ожидал найти что-то, что просто запускает код Brainfuck, например метод или функцию, а не встроить это в основную функцию. Точно так же получение кода из стандартного ввода или из файла можно переместить в отдельную функцию. Было бы легче понять эти вещи в разлуке. Также, что касается этого разделения, избавьтесь от привычки объявлять переменные (memory, pointer и т. д.) далеко от того места, где они фактически используются.

Я ожидал, что эти функции также вернут error, чтобы соответствовать соглашениям Go. Это также будет означать, что вы прерываете выполнение, когда вы получаете переполнение / потеря значимости для указателя или другие фатальные ошибки. Если вы решите поместить это в класс, его состоянием будет память, указатель и стек (я думаю, вам понадобится) в скобках.

Я не уверен, как далеко вы хотите зайти, но, возможно, вы захотите расширить тесты на функции с побочными эффектами, то есть на ввод и вывод. В этом случае было бы полезно, если бы вы определили интерфейс ввода-вывода, который можно подключить к интерпретатору. Это позволило бы создать макет для тестов.

Примечания и ошибки

  • Операции увеличения и уменьшения байта прерываются, они все равно увеличивают байт при переполнении после его обнуления. Рассмотрите возможность использования явного int8 или же uint8 (не уверен, что там есть спецификация Brainfuck). Кроме того, в Go должна быть функция по модулю, которую вы могли бы использовать, чтобы избежать этих проблем.
  • Есть какой-то «мертвый» код:
    • result используется, но бесполезно. Разделение вещей, вероятно, показало бы это.
    • Закомментировал import fmt в тестовом файле.
    • Завершающая пустая строка в функциях.
    • Комментарии для функций, которые повторяют только имя функции.
  • Изолированная правая скобка в настоящее время игнорируется. Я не уверен, что это задумано.
  • Недействительные символы во вводе игнорируются. Я тоже не уверен, что это задумано.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *