Интересно, всего пару дней назад я писал интерпретатор brainf * ck на Java.
Одна из проблем, с которыми я столкнулся, заключалась в том, что объяснение команд на официальной странице было недостаточным и не упоминало часть о вложенных циклах. Страница Википедии на Brainf * ck содержит подраздел Команды , который описывает правильное поведение.
В основном, чтобы подвести итог проблемы, на официальной странице говорится, что когда инструкция - [
, а текущая ячейка памяти - 0
, затем перейдите к следующему ]
. Правильное поведение - перейти к , соответствующему ]
, а не к следующему.
Одним из способов достижения этого поведения является отслеживание уровня вложенности. Я реализовал это, имея счетчик, который отслеживал уровень вложенности.
Следующее является частью основного цикла интерпретатора:
do {
if (inst[pc] == '>') { ... }
else if (inst[pc] == '<') { ... }
else if (inst[pc] == '+') { ... }
else if (inst[pc] == '-') { ... }
else if (inst[pc] == '.') { ... }
else if (inst[pc] == ',') { ... }
else if (inst[pc] == '[') {
if (memory[p] == 0) {
int nesting = 0;
while (true) {
++pc;
if (inst[pc] == '[') {
++nesting;
continue;
} else if (nesting > 0 && inst[pc] == ']') {
--nesting;
continue;
} else if (inst[pc] == ']' && nesting == 0) {
break;
}
}
}
}
else if (inst[pc] == ']') {
if (memory[p] != 0) {
int nesting = 0;
while (true) {
--pc;
if (inst[pc] == ']') {
++nesting;
continue;
} else if (nesting > 0 && inst[pc] == '[') {
--nesting;
continue;
} else if (inst[pc] == '[' && nesting == 0) {
break;
}
}
}
}
} while (++pc < inst.length);
Вот легенда для имен переменных:
memory
- ячейки памяти для данных.
p
- указатель на текущую ячейку памяти.
inst
- массив, содержащий инструкции.
pc
- программный счетчик; указывает на текущую инструкцию.
nesting
- уровень вложенности токовой петли. nesting
из 0
означает, что текущее местоположение не находится во вложенном цикле.
Обычно при открытии цикла [
проверяется текущая ячейка памяти, чтобы узнать, является ли значение 0
. В этом случае вводится цикл while
для перехода к соответствующему ]
.
Способ обработки вложенности следующий:
Если при поиске соответствующего закрытия цикла ]
встречается [
, то переменная nesting
увеличивается на 1
, чтобы указать, что мы вошли во вложенный цикл.
Если встречается ]
и:
а. Если переменная nesting
больше 0
, то переменная nesting
уменьшается на 1
, чтобы указать, что мы оставили вложенный цикл.
б. Если переменная nesting
равна 0
, то мы знаем, что был обнаружен конец цикла, поэтому поиск конца цикла в цикле while
завершается выполнением оператора break
.
Теперь следующая часть должна обработать закрытие цикла на ]
. Подобно открытию цикла, он будет использовать счетчик nesting
, чтобы определить текущий уровень вложенности цикла, и попытаться найти соответствующее открытие цикла [
.
Этот метод, возможно, не самый элегантный способ сделать что-то, но кажется, что он дружественный к ресурсам, потому что ему требуется только одна дополнительная переменная для использования в качестве счетчика для текущего уровня вложенности.
(Конечно, «дружественный к ресурсам» игнорирует тот факт, что этот интерпретатор был написан на Java - я просто хотел написать какой-то быстрый код, и Java оказалась именно тем, в котором я его написал.)