首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C语言的Basic Brainfuck解释器

问C语言的Basic Brainfuck解释器
EN

Code Review用户
提问于 2016-07-11 01:00:26
回答 1查看 4.8K关注 0票数 5

我用C. 这里托管的最新版本写了一个小型的Brainfuck翻译。

我发现很难测试我的解释器,因为用Brainfuck编写一个程序有点困难(或者至少非常乏味),而且我在网上找到的许多片段要么没有输出,要么非常神秘,所以我不知道是否一切正常。

不过,我已经在上面测试了一些程序,我测试的程序也很有效。

任何批评/建议都会很好。

代码语言:javascript
复制
#include <stdio.h>

#define MEMSIZE 30000

int memory[MEMSIZE];
int instructions[MEMSIZE];

int memptr = 0;
int insptr = 0;

int isInstruction(char);
void runInstruction(char);

int main(int argc, char *argv[]) {
    // Open input file
    FILE *file = fopen(argv[1], "r");
    if (file == 0){
        printf("Error: Could not open source file\n");
        return 0;
    }

    // Read all instructions from input
    char c;
    int i = 0;
    int balance = 0; // Keeps track of `[` and `]` balance
    while ((c = fgetc(file)) != EOF && i < MEMSIZE){
        if(isInstruction(c)){
            instructions[i] = c;
            i++;
            if (c == '[')
                balance++;
            if (c == ']')
                balance--;
        }
    }

    // Close input file
    fclose(file);

    // Error if amount of instructions is more than permitted
    if (c != EOF && i >= MEMSIZE){
        printf("Error: cannot read more than 30000 instructions\n");
        return 0;
    }

    // Error if `[` and `]` aren't balanced
    if (balance != 0){
        printf("Error: `[` and `]` were not balanced\n");
        return 0;
    }

    // Run the program
    while(insptr < MEMSIZE && instructions[insptr] != 0){
        runInstruction(instructions[insptr]);
        insptr++;
    }
}

int isInstruction(char c) {
    if(c == '+' || c == '-' || c == '>' || c == '<' || c == '[' || c == ']' ||
       c == '.' || c == ','){
        return 1;
    } else {
        return 0;
    }
}

void runInstruction(char c) {
    switch(c){
    case '+':
        memory[memptr]++;
        break;
    case '-':
        memory[memptr]--;
        break;
    case '>':
        if (memptr < MEMSIZE - 1)
            memptr++;
        else
            memptr = 0;
        break;
    case '<':
        if (memptr > 0)
            memptr--;
        else
            memptr = MEMSIZE - 1;
        break;
    case '[':
        if (memory[memptr] == 0)
            // seek `]` forward
            while(instructions[insptr] != ']'){
                insptr++;
            }
        break;
    case ']':
        if (memory[memptr] != 0)
            // seek `[` backward
            while(instructions[insptr] != '['){
                insptr--;
            }
        break;
    case '.':
        putchar(memory[memptr]);
        break;
    case ',':
        memory[memptr] = getchar();
        break;
    default:
        printf("Error: Encountered unknown instruction `%c`\n",c);
    }
}
EN

回答 1

Code Review用户

发布于 2016-07-11 09:50:15

Bugs

您的程序不适用于嵌套循环。如果你有

代码语言:javascript
复制
+   jump into the loop
[   this is the first loop
  [ this is the second loop                                 (1)
  - decrease current value to get out of loop
  ]
  + we increment our current value to get back to the start
]   whoops, we go to (1)

您需要记住遇到的[的位置。一堆东西会派上用场。

此外,余额计算不正确。程序][被解析,因为balance == 0,但它不应该。

写入错误到stderr

如果要打印错误,请使用fprintf(stderr,…)而不是printf。

用else

显示独占分支

如果两个if彼此独占,请确保使用else if:

代码语言:javascript
复制
if (c == '[')
    balance++;
else if (c == ']')
    balance--;


// add the balance check, see bugs above
if (balance < 0){
    ...
}

如果可能的话,

避免使用全局变量测试

接下来,尽可能避免全局变量。这将帮助您测试您的功能。例如,如果将Brainfuck数据分组到一个结构中,则可以检查它是否正确地解析代码:

代码语言:javascript
复制
typedef struct brainfuck_data {
    int * memory;
    int * instructions;
    size_t memsize;

    int memptr;
    int insptr;
} bf_data_t;

int brainfuck_data_init(bf_data_t * data, size_t memsize) {
    data.memory = calloc(memsize, sizeof(int));
    if(!data.memory) {
        return -1;
    }
    data.instructions = calloc(memsize, sizeof(int));
    if(!data.instructions) {
        free(data.memory);
        return -2;
    }
    data.memsize = memsize;
    data.memptr  = 0;
    data.insptr  = 0;
}

// and so on for brainfuck_data_deinit

现在,如果您有brainfuck_data_file(bf_data_t * data, FILE * fp)或brainfuck_data_parse(bf_data_t *, const char*),您可以检查解析器是否正确地忽略了非brainfuck令牌:

代码语言:javascript
复制
brainfuck_data_parse(&bf1, "[,.]");
brainfuck_data_parse(&bf2, "a simple echo program: [,.]");

int instructionsDiffer = 0;
for(i = 0; i < bf1.memsize && i < bf2.memsize && …; ++i){
   if(bf1.instructions[i] != bf2.instructions[i]) {
     instructionsDiffer = 1;
     break;
   }
   if(bf1.instructions[i] == 0)
     break;
}

非全局状态使得测试程序更加容易。

请注意,上面代码中的神奇数字-1和-2应该记录在某个地方,并替换为define。

代码语言:javascript
复制
#define BF_E_ALLOC_MEMORY -1
#define BF_E_ALLOC_INSTR  -2
#define BF_E_OK            0

拆分功能

目前,解析是在main中完成的。把它分成它自己的功能。上面给出了一个启示。

将其分解为一小组值

目前,您的所有指令都保留为字符。相反,您可以使用enum:

代码语言:javascript
复制
typedef enum {
   BF_INC,        BF_DEC,
   BF_GET,        BF_PUT
   BF_NEXT,       BF_PREV
   BF_LOOP_START, BF_LOOP_END       
} brainfuck_instruction_t;

这样,如果你用

代码语言:javascript
复制
typedef struct brainfuck_data {
    brainfuck_instruction_t * instructions;
    ...
} bf_data_t;

你已经知道只有有效的指示,除非有人事先篡改了它们。好的副作用是,您可以将几乎所有类似Brainfuck的语言解析成同一组指令,并仍然运行它们。看这个问与答为灵感。

结论

用非平凡的循环测试程序,将其功能分解为更易于测试的小函数,通过使用enums和更少的全局变量使代码更容易出错。

对于使用循环的程序,请尝试计算n_th Fibonacci数(但是对于小_n,因为BF中的加法需要很长时间)。

票数 8
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/134489

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档