Недавно написал мозговой интерпретатор в 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 ответ
Состав
Я ожидал найти что-то, что просто запускает код Brainfuck, например метод или функцию, а не встроить это в основную функцию. Точно так же получение кода из стандартного ввода или из файла можно переместить в отдельную функцию. Было бы легче понять эти вещи в разлуке. Также, что касается этого разделения, избавьтесь от привычки объявлять переменные (memory, pointer и т. д.) далеко от того места, где они фактически используются.
Я ожидал, что эти функции также вернут error, чтобы соответствовать соглашениям Go. Это также будет означать, что вы прерываете выполнение, когда вы получаете переполнение / потеря значимости для указателя или другие фатальные ошибки. Если вы решите поместить это в класс, его состоянием будет память, указатель и стек (я думаю, вам понадобится) в скобках.
Я не уверен, как далеко вы хотите зайти, но, возможно, вы захотите расширить тесты на функции с побочными эффектами, то есть на ввод и вывод. В этом случае было бы полезно, если бы вы определили интерфейс ввода-вывода, который можно подключить к интерпретатору. Это позволило бы создать макет для тестов.
Примечания и ошибки
- Операции увеличения и уменьшения байта прерываются, они все равно увеличивают байт при переполнении после его обнуления. Рассмотрите возможность использования явного
int8или жеuint8(не уверен, что там есть спецификация Brainfuck). Кроме того, в Go должна быть функция по модулю, которую вы могли бы использовать, чтобы избежать этих проблем. - Есть какой-то «мертвый» код:
resultиспользуется, но бесполезно. Разделение вещей, вероятно, показало бы это.- Закомментировал
import fmtв тестовом файле. - Завершающая пустая строка в функциях.
- Комментарии для функций, которые повторяют только имя функции.
- Изолированная правая скобка в настоящее время игнорируется. Я не уверен, что это задумано.
- Недействительные символы во вводе игнорируются. Я тоже не уверен, что это задумано.
