Комментарии 31
А в чём смысл статьи? В качестве обучающего материала для написания интерпретаторов статья не годится, потому что в основном языки программирования имеют более сложную структуру, и для интерпретации придётся писать лексический анализатор и синтаксический анализатор, что в статье не рассматривается. В качестве хорошего обучающего материала могу предложить книгу "Build your own lisp". Есть так же её вариации на отличных от си языках
ПОЕСНИТЕЛЬНАЯ БРИГАДА:
Смысл статьи - максимально быстро и незапарно получить полные права.
Ну типа поделился человек опытом
Да, только написать интерпретатор bf - это на уровне решенения линейного уравнения, т.е. настолько просто, что писать об 100500 раз не стоит. Плюс, как вы могли заметить, в программе автора нашли несколько значительных недочётов. Ещё сам автор, как бы, написал, что статью он писал не для того, чтобы чем-то интересным поделиться, а чтобы получить инвайт.
char command[4096];
int i = 1;
do {
printf("BF> ");
gets(command);
Ух. Писали интерпретатор bf, а написали учебную программу на переполнение стэка.
Почему бы Вам не сделать интерпретатор чуть более корректным?
30000 - откуда число? Больше быть не может? И если его всё же используете, то лучше бы контролировать границы;
char * plus = "+"; . Э-э-э может лучше так?: char plus = '+'; ... if (buf[i] == plus) ...
Загонять весь файл в стэк? Это очень сурово. Попробуйте открыть файл размером ну хотя бы 1 гигабайт. Подозреваю, у вас всё рухнет;
команду интерпретатора не ввести более 4096 байт? А если ввод перенаправить из файла? (1 гигабайт)
Отрицательный индекс в ячейки быть не может?
Число 30000 взято из всех стандартных интерпретаторов bf.
char plus = "+" ; работать не будет, потому что тип данных "" это указатель.
Для примера попробуйте скомпилировать и запустить такой код
#include <studio.h>
int main()
{
char test = "i";
printf("%c\n", test);
return 0;
}
Он выведет только перевод строки, т.к в переменную test записан указатель на символ "i".
Ваш напор похвален.
"Число 30000" Вы можете взять любое. Только желательно контролировать, что в результате скачки по ячейкам вы за него не выйдите. Здесь это никто, кроме вас, контролировать не будет.
Про char plus ... - там ставятся одинарные кавычки. И тип данных сразу станет char.
А в целом: Удачи, язык чистый-C - это хорошо.
switch (buf[i]) {
case cmd_plus:
stack[indexx]++;
break;
case cmd_minus:
stack[indexx]--;
break;
...
}
Или вообще олдскульно:
#define CMD_PLUS '+'
#define CMD_MINUS '-'
По-моему char buf[size] в "методе" main не скомпилируется, так как size не известно на момент компиляции
Брейнфак - это язык для очень упрощённой машины Тьюринга. А МТ, как известно, работает на ленте, бесконечной в обе стороны.
Поэтому следующая итерация (после исправления всех ошибок и недочётов, которые вам тут сказали) - это поддержка этой самой бесконечной ленты.
Самый тупой способ на голом Си, - это, конечно, просто реаллокация. Выделили новый, бОльший блок памяти, скопировали в него старый (со смещением вправо, если двигались влево и выбежали в отрицательные индексы) и вуаля.
Более затейливо - сделать страничную организацию... Ну, в общем, простор для творчества, особенно, на Си.
---
Следующий пункт для улучшения - это интерпретатор.
Каждой скобке соответствует своя парная скобка. Как минимум, логично вместо тупого поиска вперёд-назад по исходнику завести табличку быстрого перехода к паре.
Серии однотипных действий (+/-, </>) также можно хранить в виде групповой операции +n, >n, где n - целое число (положительное или отрицательное по итогу серии).
Итого, у нас получается следующий промежуточный код:
'+' n -- инкремент ячейки
'>' n -- инкремент адреса
'[' n -- условный переход на n команд (промежуточных!) - если текущая ячейка нулевая и вперёд, или если ненулевая и назад
'.' n -- вывод текущей ячейки n раз
',' n -- ввод в текущую ячейку n раз (что означает проглатывание n-1 символов)
То есть, пишем транслятор исходника в промежуточный код. Назвать это компиляцией как-то ещё слишком гордо.
---
Следующая задача - существенно более творческая. Это распознавание и обработка идиом языка.
Например, обнуление ячейки: "[-]"
Или вывод нуль-терминированной строки "[.>]"
Или даже просто вывод n символов подряд ".>.>.>"
Можно расширить промежуточный код специальными командами для этих идиом.
---
Ну и дальше, насколько фантазии хватит.
Этот интерпретатор - очень упрощённый пример, поэтому я не стал заморачиваться, хотя можно было транслировать код bf в код C или ассемблера, а потом компилировать, но зто уже был бы компилятор а не интерпретатор.
перевод с одного языка на другой это транспилятор
А никто и не говорит делать трансляцию во что-то сохраняемое - си, ассемблер или объектный код для прямого запуска.
Интерпретатор вполне может транслировать во внутреннее представление, а не заниматься синтаксическим разбором каждой команды каждый раз!
Хорошо у брейнфака каждая элементарная команда - это один символ, и поэтому разбор может быть вырожденным. Но вот уже структурная команда цикла требует чуть более сложного действия. Формально, грамматика-то контекстно-свободная, а не регулярная.
Так почему бы не отделить фазу разбора от фазы исполнения?
В общем, что хочу сказать. Плюсик за смелость, минусик за незрелость :) Стремительно исправляйте и улучшайте, пока народ не заклевал.
Чтобы интерпретатор работал надо подключить определённые библиотеки
С каких это пор стандартные заголовки стали назваться "библиотеками"?
char * plus = "+";
Язык С конечно же не требует const
здесь, но это не причина не придерживаться элементарных правил константной корректности.
memset(stack, 0, 30000);
Обнуление агрегата при помощи memset
в 2022 году? Да еще и с явным указанием размера через магическую константу??? И это при том, что верный термин "инициализация" вам таки знаком. Может быть стоило действительно воспользоваться инициализацией
char stack[30000] = { 0 };
gets(command);
O_o. Лолшто???
for (int x = 0; x < sizeof(command); x++) {
Интересно заметить, что в этом месте автор догадался использовать sizeof
, пусть даже и с лишними скобками. Почему же тогда в memset
выше была использована магическая константа?
int bf_shell()
Зачем эта функция возвращает int
? И, пока не наступила эра C23, все таки пожалуйста
int bf_shell(void)
long size = ftell(file); fseek(file, 0L, SEEK_SET);
char buf[size];
Чтение целого файла в локальный VLA - весьма рискованная идея.
Чем плох memset?
Только тем, что незачем преумножать сущности без необходимости. Зачем притягивать сюда за уши библиотечную функцию memset
, когда ядро языка уже предоставляет вам встроенные возможности для выполнения инициализации?
Точно так же можно вызывать какую-нибудь библиотечную функцию для того, чтобы увеличить переменную на 1, вместо классического ++i
. Чем это плохо? Да, вроде, ничем... Но зачем?!
И, еще раз, в глаза бросается именно то, что вы сами использовали термин "инициализация", но при этом почему-то не воспользовались предоставляемыми вам языком С возможностями инициализации.
И почему возврат int чем-то может не устраивать?
"Не устраивает" не сам возврат int
, а тот факт, что функция очевидно ничего осмысленного не возвращает в этом int
. Зачем тогда было делать ее int
, если можно было сделать ее void
? Зачем взваливать на плечи читателя кода вопрос о том, что же это за возвращаемое значение, только для того, чтобы он в результате выяснил, что это возвращаемое значение никакого смысла не несет?
Перед чтением файла программа узнает размер файла и создаёт массив который соответствует размеру файла и только потом файл считается в массив.
Так о том и речь. Вы создаете автоматический VLA, размер которого равен размеру файла. Размер файла запросто может оказаться слишком большим для автоматического объекта. Не создавайте потенциально большие объекты "на стеке". Это добром не кончится.
Я в студенческие годы написал транслятор BF в C, это несложно, плюс, после оптимизации gcc даже терпимо работало. Потом поверх этого писал C-подобный язык, транслируемый в BF (да-да, транслируемый затем в С и компилируемый с помощью gcc). В общем, развлекался как умел.
А вот я как-то написал компилятор Brain**** в EXE.
Правда это был скорее транспилятор так как он переводил Brain**** в C а потом компилировал с помощью GCC...
Brainfuck и почему это просто?