Brainfuck для самых маленьких. Транслятор из Brainfuck в C++

в 9:34, , рубрики: Brainfuck, Песочница, метки:
++++++++[->++++>+++++++++>+++++++++++++<<<]>>.>[->+>+<<]>---.>++++..+++.<<<<.>.>>----.+.>+++.<<<<+.

Или, говоря по человечески, здравствуй Хабр!

Сегодня я хотел бы предложить сообществу инструмент, который может помочь понять брейнфак тем кто с ним не знаком, сосчитать за Вас количество плюсов и минусов(например для меня это неиссякаемый источник кучи ошибок), ну и упростить понимание простых программ на брейнфаке вроде неоптимизированного HelloWorld'a.

Данная статья в первую очередь ориентирована на людей, которые только начинают открывать для себя этот замечательный язык. Если вы один из них — добро пожаловать под хабракат.

Итак, приступим.

Brainfuck — эзотерический язык, предназначенный для разминки мозга. Он содержит в себе всего 8 команд, но при этом является полным по Тьюрингу, следовательно написать на нем Вы можете что угодно.

Знакомство с ним будет вестись на примере простенького транслятора на язык С++.

Пункт первый. Среда исполнения.

Программа на брейнфаке похожа на машину Тьюринга. Мы так же имеем бесконечную(или конечную, в зависимости от реализации) ленту ячеек, каждая из которых имеет размер в 1 байт. И по этой ленте передвигается взад-вперед один указатель.
Следовательно если мы хотим что бы выходной поток нашего транслятора мог компилироваться и исполняться, нам придется в первую очередь позаботиться о такой ленте.

Я решил реализовать её на базе массива, т.к. это был самый простой путь, хотя вариант с двусвязным списком максимально приблизил бы к бесконечности ячеек. Соответственно указатель является индексом текущего элемента.

//outFile - выходной .cpp файл
outFile<<"#include <iostream>"<<endl;	 
outFile<<"int main(){"<<endl;	                 
outFile<<"char mas[256]={0}; int i=0;"<<endl;  

Размер массива я взял 256, этого достаточно для большинства программ. При надобности легко изменить на любое другое значение.

Пункт второй. Операторы языка.

Brainfuck простой язык! Brainfuck просто замечательный язык для начинающих, ведь он содержит всего 8 команд, каждая длиной в 1 символ, а значит Вы можете выучить его синтаксис за 15 минут! К сожалению это же является и его основной бедой, писать используя столь скудный запас очень и очень трудно.
Позвольте представить, ваши будущие друзья:
"+" — увеличение значения ячейки на 1.
"-" — уменьшение ячейки на 1.
"." — вывод содержимого ячейки на экран в соответствии с таблицей ASCII.
"," — считывание ввода пользователя в соответствии с таблицей ASCII.
"<" — сдвиг указателя на одну ячейку влево.
">" — сдвиг указателя на одну ячейку вправо.
"[" — начало цикла, который выполняется до тех пор, пока в текущей ячейке значение отличное от 0.
"]" — конец цикла.

Добавим их всех в наш транслятор:

switch (inputChar)
	{
	case '+': add();
		break;
	case '-': sub();
		break;
	case ',':  scan();
		break;
	case '.':	print();
		break;
	case '[': whileOpen();
		break;
	case ']': whileClose();
		break;
	case '<': prev();
		break;
	case '>': next();
		break;
	}

Прежде чем переходить к детальному рассмотрению функций, вынужден признаться, что я слегка обманул Вас в вышестоящем коде. Нетрудно догадаться что функция add(); должна реализовывать что то вроде mas[i]++;, но я обещал подсчитать за вас количество подряд идущих плюсов(да и минусов тоже). Обещание придется сдержать и поэтому функция add() будет заменена на другой кусок кода.

А теперь все по порядку:

Арифметические операции

Для реализации подсчета будем использовать переменные inc и dec

case '+': inc++; 
		break;
	case '-': dec++;
		break;

Для вставки результата вставим следующий код над конструкцией switch

	if (inc && inputChar!='+') {op(inc,'+');inc=0;}
	if (dec && inputChar!='-') {op(dec,'-');dec=0;}

И реализуем функцию op(int,char);

void op(int n, char c)
{	
	elem();
	outFile<<c<<"="<<n<<";";
	outFile<<"t//";
	for (int i=0; i<n; i++) outFile<<c; 
	outFile<<endl;
}

Где elem(); подставляет в выходной файл конструкцию mas[i] (вынесено на всякий случай, если реализация массивом не удовлетворит каким либо нуждам).

Остальные команды:

void next()
{
	outFile<<"i++;";
	outFile<<"t//t >"<<endl;
}

void prev()
{
	outFile<<"i--;";
	outFile<<"t//t<"<<endl;
}

void scan()
{
	outFile<<"std::cin>>";
	elem();
	outFile<<";";
	outFile<<"t//t,";
	outFile<<endl;
}
void print()
{
	outFile<<"std::cout<<";
	elem();
	outFile<<";";
	outFile<<"t//t.";
	outFile<<endl;
}
void whileOpen(	)
	
{
	outFile<<"while(";
	elem();
	outFile<<"){";	
	outFile<<"t//t]"<<endl;
}

void whileClose()
{
	outFile<<"}";	
	outFile<<"t//t["<<endl;
}

В каждой функции в конце вставляется комментарий, содержащий исходную команду.

Внесем наш switch в цикл, наведем красоты(вроде вставки в результирующий код return'a) и получим готовый транслятор.

// test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <fstream>
using namespace std;

ifstream inFile;
ofstream outFile;

void elem()
{
	outFile<<"mas[i]";
}
void next()
{
	outFile<<"i++;";
	outFile<<"t//t >"<<endl;
}
void prev()
{
	outFile<<"i--;";
	outFile<<"t//t<"<<endl;
}
void op(int n, char c)
{	
	elem();
	outFile<<c<<"="<<n<<";";
	outFile<<"t//";
	for (int i=0; i<n; i++) outFile<<c; 
	outFile<<endl;
}


void scan()
{
	outFile<<"std::cin>>";
	elem();
	outFile<<";";
	outFile<<"t//t,";
	outFile<<endl;
}
void print()
{
	outFile<<"std::cout<<";
	elem();
	outFile<<";";
	outFile<<"t//t.";
	outFile<<endl;
}
void whileOpen(	)
	
{
	outFile<<"while(";
	elem();
	outFile<<"){";	
	outFile<<"t//t]"<<endl;
}

void whileClose()
{
	outFile<<"}";	
	outFile<<"t//t["<<endl;
}


void main() {
setlocale (LC_ALL,"RUS");
bool komment = 0;
outFile.open("out.cpp");
inFile.open("in.bf");
outFile<<"#include <iostream>"<<endl;	
outFile<<"int main(){"<<endl;	
outFile<<"char mas[256]={0}; int i=0;"<<endl;	
char inputChar;
int dec=0;
int inc =0;
while (inFile>>inputChar)
{
	if (inc && inputChar!='+') {op(inc,'+');inc=0;}
	if (dec && inputChar!='-') {op(dec,'-');dec=0;}
	switch (inputChar)
	{
	case '+': inc++; if(komment) {outFile<<"*/"<<endl; komment =0;}
		break;
	case '-': dec++;if(komment) {outFile<<"*/"<<endl; komment =0;}
		break;
	case ',':  if(komment) {outFile<<"*/"<<endl; komment =0;}scan();
		break;
	case '.':	if(komment) {outFile<<"*/"<<endl; komment =0;}print();
		break;
	case '[':  if(komment) {outFile<<"*/"<<endl; komment =0;}whileOpen();
		break;
	case ']':  if(komment) {outFile<<"*/"<<endl; komment =0;}whileClose();
		break;
	case '<':  if(komment) {outFile<<"*/"<<endl; komment =0;}prev();
		break;
	case '>': if(komment) {outFile<<"*/"<<endl; komment =0;} next();
		break;
	default: if(!komment) {outFile<<"/*"<<endl; komment =1;}
			 outFile<<inputChar;
	}
}
if(komment) {outFile<<"*/"<<endl; komment =0;}
outFile<<"return 0;}"<<endl;	
return;
}

Внимательный читатель заметит кучу плохо смотрящегося кода в котором фигурирует переменная komment.
Это я на скорою руку пытался прикрутить перенос комментариев из исходного кода в результирующий. И на первый взгляд даже получилось.

 Hello World+++++++++++++++++++++++++++++++++++++++++++++
 +++++++++++++++++++++++++++.+++++++++++++++++
 ++++++++++++.+++++++..+++.-------------------
 ---------------------------------------------
 ---------------.komment+++++++++++++++++++++++++++++
 ++++++++++++++++++++++++++.++++++++++++++++++
 ++++++.+++.------.--------.------------------
 ---------------------------------------------
 ----.-----------------------.

#include <iostream>
int main(){
char mas[256]={0}; int i=0;
/*
HelloWorld*/
mas[i]+=72;	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
std::cout<<mas[i];	//	.
mas[i]+=29;	//+++++++++++++++++++++++++++++
std::cout<<mas[i];	//	.
mas[i]+=7;	//+++++++
std::cout<<mas[i];	//	.
std::cout<<mas[i];	//	.
mas[i]+=3;	//+++
std::cout<<mas[i];	//	.
mas[i]-=79;	//-------------------------------------------------------------------------------
std::cout<<mas[i];	//	.
/*
komment*/
mas[i]+=55;	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++
std::cout<<mas[i];	//	.
mas[i]+=24;	//++++++++++++++++++++++++
std::cout<<mas[i];	//	.
mas[i]+=3;	//+++
std::cout<<mas[i];	//	.
mas[i]-=6;	//------
std::cout<<mas[i];	//	.
mas[i]-=8;	//--------
std::cout<<mas[i];	//	.
mas[i]-=67;	//-------------------------------------------------------------------
std::cout<<mas[i];	//	.
mas[i]-=23;	//-----------------------
std::cout<<mas[i];	//	.
return 0;}

На этом я завершу статью. И если после её прочтения хоть кто-то вобьет в поисковой строке слово brainfuck, то она была написана не зря.

Автор: WYCOR

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js