用C++实现一个Brainfuck解释器
> Brainfuck是一种极小化的计算机语言,只含有8种运算符,由于fuck在英语中是脏话,这种语言有时被称为brainf*ck或brainf***,甚至被简称为BF。正如它的名字所暗示,brainfuck程序很难读懂,尽管如此,brainfuck却是图灵完备的,也就是说它能够完成所有可计算的任务。
- - -
# 简介
以下摘自[维基百科](https://zh.wikipedia.org/wiki/Brainfuck)。
Müller的目标是创建一种简单的、可以用最小的编译器来实现的、符合图灵完全思想的编程语言。这种语言由八种运算符构成,为Amiga机器编写的编译器(第二版)只有240个字节大小。
这种语言基于一个简单的机器模型,除了指令,这个机器还包括:一个以字节为单位、被初始化为零的数组、一个指向该数组的指针(初始时指向数组的第一个字节)、以及用于输入输出的两个字节流。
下面是这八种状态的描述,其中每个状态由一个字符标识:(博客园的markdown居然不支持表格,我实在是...)
如果把这些指令翻译成C语言就是下面这样的:
示例程序
所以,Brainfuck的程序就是长这样的:
,.
这段代码的意思是,从键盘读取一个字符并输出到屏幕,>++++++++[<-->-]<-.
这也是我自己写的第一个BrainFuck程序啊哈哈,功能是,从键盘读取一个大写字母,然后转化成对应的数字,#比如A就输出0,B输出1。
代码解释
解释下,>++++++++[<-->-]<-.
首先,读取一个大写字母放到[0]里,然后把指针指向[1],接下来把[1]里的值增加8次,也就是变成8。
然后进入循环,指针左移一位,指向[0],把[0]的值减两次,又右移一位回到[1],把[1]的值减少1。因为一开始[1]被赋值为8,所以循环一共能执行8次,因此[0]里的值会被减少8 * 2 = 16,最后[1]变成了0,循环不再执行,这时候指针指向的是[1],将其左移一位指向[0],然后将[0]的值减1,所以[0]的值一共减少了16 + 1 = 17此,这刚好是‘A’的ACSII码与'0'的ACSII码的差值
是不是很好玩?
不如写个解释器更好玩。
解释器
#include <iostream>
#include <stack>
#include <cstdio>
#include <map>
using namespace std;
const int SIZE = 300000;
bool is_instruction(char);
int main(void)
{
while(1)
{
char instruction[SIZE];
char ch;
int count = 0;
while((ch = getchar()) != EOF) //读取指令,忽略空格回车等非命令字符
if(is_instruction(ch))
instruction[count ++] = ch;
instruction[count] = '\0';
stack<int> left_bracket_stack;
map<int,int> another_bracket_at;
bool instruction_ok = true;
for(int i = 0;instruction[i] != '\0';i ++) //检查代码是否有误,同时匹配括号
{
if(instruction[i] == '[')
left_bracket_stack.push(i);
else if(instruction[i] == ']')
{
if(left_bracket_stack.empty())
{
instruction_ok = false;
break;
}
int left_barcket_index = left_bracket_stack.top();;
left_bracket_stack.pop();
another_bracket_at[i] = left_barcket_index;
another_bracket_at[left_barcket_index] = i;
}
}
if(!left_bracket_stack.empty())
instruction_ok = false;
if(!instruction_ok) //如果括号不匹配则输出错误
{
puts("代码有误");
continue;
}
cout << endl << "***** BEGIN *****" << endl << endl;
int i = 0;
char box[SIZE] = {0};
char * cur = box;
while(instruction[i] != '\0')
{
if(instruction[i] == '>')
cur ++;
else if(instruction[i] == '<')
{
cur --;
if(cur < box) //如果操作会导致数组越界就报错
{
puts("代码有误");
break;
}
}
else if(instruction[i] == '+')
++ (*cur);
else if(instruction[i] == '-')
-- (*cur);
else if(instruction[i] == '.')
putchar(*cur);
else if(instruction[i] == ',')
*cur = getchar();
else if(instruction[i] == '[')
{
if(*cur == 0)
i = another_bracket_at[i];
}
else if(instruction[i] == ']')
if(*cur)
i = another_bracket_at[i];
i ++;
}
cout << endl << endl << "***** DONE *****" << endl << endl;
}
return 0;
}
bool is_instruction(char ch)
{
if(ch == '>' || ch == '<' || ch == '+' || ch == '-' || ch == '.' || ch == ',' || ch == '[' || ch == ']')
return true;
return false;
}
代码应该很好懂,逐个字符读入,同时忽略非命令的字符,处理到文件末尾为止。稍微复杂点的就是用到了map
和stack
来记录每一个括号对应的另外一半的位置。首先每遇到一个[
就压进栈里,然后遇到]
就从栈顶取出一个[
来和它配对,这时候用map
来记录他们的位置。
测试程序
输出"Hello World!" :
++++++++++[>+++++++>++++++++++>+++>+<<<<-]
>++.>+.+++++++..+++.>++.<<+++++++++++++++.
>.+++.------.--------.>+.>.
输出字符'A' :
++++++ [ > ++++++++++ < - ] > +++++ .
把小写字母转换成大写,按回车结束:
,----------[----------------------.,----------]
提示:在控制台输入完指令后按回车,然后按ctrl+z可以模拟EOF
效果
如有BUG欢迎指出~
用C++实现一个Brainfuck解释器的更多相关文章
- Licp - 一个玩具解释器的实现
纸上得来终觉浅,绝知此事要躬行. 最近看了 SICP,其第四章讲述了一个简单的 Scheme 解释器的实现.粗看了一遍后决定自己用 C 语言实现一个残疾的 Scheme 解释器,想来这样的学习效果应该 ...
- 打包一个python解释器
利用python的exec语句,可以很方便地动态执行python语句.如果一个python代码打包为了exe,其原先的代码就很难更改了.一个好的解决方法就是import相应的库,然后把主程序段放到一个 ...
- 前端与编译原理——用JS写一个JS解释器
说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...
- brainfuck 解释器
#include <cstdio>#include <cmath>#include <cstring>#include <ctime>#include ...
- pwnable.kr之brainf*ck
pwnable.kr之brainf*ck 今天又是被难倒的一天Orz,个人感觉pwnable.kr上的题都比较剑走偏锋,仔细做过去,一定会有很大的收获. 不多说了,今天看的是第二关的第一道题:brai ...
- [开源项目]Shell4Win,一个在Windows下执行shell命令的解释器
背景 顺利拿到心目中的理想offer之后,心里的负担一下减轻了很多,希望利用还没毕业之前这段难得的悠闲时间做一点有意义的事情.于是希望能做一个长久以来都想做的开源项目,就是题中提到的Windows下的 ...
- shell脚本就是由Shell命令组成的执行文件,将一些命令整合到一个文件中,进行处理业务逻辑,脚本不用编译即可运行。它通过解释器解释运行,所以速度相对来说比较慢。
shell脚本?在说什么是shell脚本之前,先说说什么是shell. shell是外壳的意思,就是操作系统的外壳.我们可以通过shell命令来操作和控制操作系统,比如Linux中的Shell命令就包 ...
- 以鶸ice为例,手撸一个解释器(一)明确目标
代码地址 # HelloWorld.ice print("hello, world") 前言(废话) 其实从开始学习编译原理到现在已经有快半年的时间了,但是其间常常不能坚持看下去龙 ...
- 一门能让你五分钟学会的语言-Brainfuck
看到标题,不出意外的话,你肯定开始骂我了:**标题党,什么编程语言五分钟就能学会? 其实我本来也是不相信的,但是学过了才知道这是真的. 1.Brainfuck 看到这个小标题,不要误会,我没有骂人. ...
随机推荐
- SQL Server下ADO.NET 怎么获取数据库SQL语句INSERT,UPDATE,DELETE了多少行数据
ADO.NET 在发送SQL语句到SQL Server数据库后,怎么知道真正INSERT,UPDATE,DELETE了多少行数据呢? 使用SQL Server内置的全局变量@@ROWCOUNT即可,@ ...
- 转: ASP.NET MVC 多语言配置
步骤1:打开VS2015新建测试项目. 步骤2:创建资源文件 App_GlobalResources下. Resource1.resx Resource1.zh-cn.resx 步骤3 ...
- 从外部导入django模块
import os import sys sys.path.append("D:\\pyweb\\sf"); # 项目位置(不是app) os.environ.setdefault ...
- /etc/resolv.conf服务器客户端DNS重要配置文件
DNS客户端配置文件:etc/resolv.conf /etc/resolv.conf文件相当于windows如下图: 当然/etc/resolv.conf文件为辅助配置DNS文件,其实在网卡里也可以 ...
- PHP 8中数据类型
PHP 一共支持八种数据类型 4种标量数据类型 boolean布尔型 只有两个值 true 和 flase integer整形 包括正整数和负整数,无小数位 float/double 浮点 ...
- Window10 Linux子系统挂载磁盘
默认情况下, Linux子系统将当前winodws磁盘的盘全部挂载到/mnt/<disk_label>, 但一些新增的盘就需要手动做下了.. 官方参考文档 挂载磁盘 -- DrvFs 挂载 ...
- Linux系统优化之设置swappiness值提高MySQL查询性能
对MySQL来说,操作系统层面的优化也可以值得考虑一下:就是swappiness. swappiness的大小主要对如何使用swap分区有着密切的联系. 来看一下: [root@chaofeng ~] ...
- java将Excel文件上传并解析为List数组
前端 //导入excel文件 layui.use('upload', function() { var upload =layui.upload; //指定允许上传的文件类型 var uploadIn ...
- 3675: [Apio2014]序列分割
Description 小H最近迷上了一个分隔序列的游戏.在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列.为了得到k+1个子序列,小H需要重复k次以下的步骤: 1.小H首 ...
- PAT B1013 数素数 (20 分)
令 Pi 表示第 i 个素数.现任给两个正整数 M≤N≤104,请输出 PM 到 PN 的所有素数. 输入格式: 输入在一行中给出 M 和 N,其间以空格分隔. 输出格式: 输 ...