Часть I
Часть II
Для начала напишем интерпретатор на высокоуровневом языке, например, на Паскале.
Массив data_arr будет представлять память данных, строка str_arr будет содержать команды.
Напишем программу, выводящую символ, ascii-код которого соответствует количеству + (поэтому нам нужны будут только команды + и .)
bf-код +++++++++++++++++++++++++++++++++. выдаст ! (ascii-код символа ! равен 33 ).
Программу можно проверить в online ide ideone.com
Вот здесь можно выполнить отладку bf-кода в пошаговом режиме.
Далее, заменим цикл for оператором goto и добавим команды
В конце будем выводить массив data_arr
Код выдаст 1 2 3 0 0 0 0 0 0 0
Код выдаст 1 2 2 0 0 0 0 0 0 0
ideone.com
Далее, добавим [ и ]
Добавим ещё одну переменную i_stor.
Если текущий элемент прошёл проверку на [, то проверяем текущий элемент массива data_arr на ноль, и, если элемент больше нуля, загружаем в i_stor значение из переменной i.
При обработке закрывающей скобки ], если data_arr не ноль, в переменную i из переменной i_stor загружаем адрес открывающей скобки [
Далее переходим к команде i:=i+1;
Если до этого в i было загружено значение из i_stor ( при проверке ] ), то после джампа мы окажемся за [ ( в противном случае мы окажемся за ] )
Код переносит число 5 в соседнюю ячейку 0 5 0 0 0 0 0 0 0 0
ideone.com
Код HelloWorld выглядит так ideone.com
Чтобы организовать цикл (loop), необходимо поместить в регистр CX количество тактов цикла и поставить метку, на которую будет сделан переход по завершении такта (по команде loop).
Создадим массив команд str_arr, поместим туда
Создадим массив данных data_arr, (для наглядности) поместим туда 1,1,1,1,1,1,1,1,1,1
В цикле сравниваем текущий символ с символом и, если символы равны, увеличиваем значение в текущей ячейке на 1.
Ассемблирование (трансляция) выполняется командой tasm.exe bf1.asm
Линковка выполняется командой tlink.exe bf1.obj
После выполнения программы в отладчике TurboDebagger видно, что начиная с адреса 0130 расположены команды
Далее идет массив данных, в котором мы изменили первый элемент, далее идет переменная i, которая после выполнения цикла стала равна 0Ah.
Добавим команды
Для того, чтобы вывести одиночный символ с помощью функции 02h прерывания int 21h, необходимо (перед вызовом прерывания) поместить код символа в регистр DL.
Напишем программу целиком
Цикл работает так:
если текущий элемент строки str_arr не то перепрыгиваем на метку next: (иначе выполняем )
если текущий элемент строки str_arr не то перепрыгиваем на метку next1:
если текущий элемент строки str_arr не то перепрыгиваем на метку next2:
если текущий элемент строки str_arr не то перепрыгиваем на метку next3:
если текущий элемент строки str_arr не то перепрыгиваем на метку next4:
После метки next4: увеличиваем индекс строки str_arr и прыгаем в начало цикла — на метку prev:
Далее, добавим [ и ]
Добавим переменную i_stor.
Если текущий элемент прошёл проверку на [, то проверяем текущий элемент массива data_arr на ноль, и, если элемент равен нулю, перепрыгиваем дальше (на следующую метку), в противном случае загружаем в i_stor значение из переменной i.
При обработке закрывающей скобки ], если data_arr не ноль, то в переменную i из переменной i_stor загружаем адрес открывающей скобки [
Проверим код
Добавим функцию ввода строки 3fh прерывания 21h
Этот интерпретатор может обрабатывать небольшие bf-программы (например):
Для больших программ нужно больше итераций/циклов.
Ссылка на github с листингами программ.
Часть II
Для начала напишем интерпретатор на высокоуровневом языке, например, на Паскале.
Массив data_arr будет представлять память данных, строка str_arr будет содержать команды.
Напишем программу, выводящую символ, ascii-код которого соответствует количеству + (поэтому нам нужны будут только команды + и .)
var
data_arr:array[1..10] of integer; // массив данных
str_arr: string; // команды
i, j: integer; // индексы строки и массива
begin
j:=1; // нумерация элементов массива (внезапно) начинается с единицы
readln(str_arr); //считываем строку
for i:=1 to length(str_arr) do begin // в цикле обрабатываем строку
if (str_arr[i]='+') then data_arr[j]:= data_arr[j]+1;
if (str_arr[i]='.') then write(chr(data_arr[j]));
end;
end.
bf-код +++++++++++++++++++++++++++++++++. выдаст ! (ascii-код символа ! равен 33 ).
Программу можно проверить в online ide ideone.com
Вот здесь можно выполнить отладку bf-кода в пошаговом режиме.
Далее, заменим цикл for оператором goto и добавим команды
В конце будем выводить массив data_arr
LABEL prev,next;
var
data_arr:array[1..10] of integer; // массив данных
str_arr: string; // команды
i,j,k: integer; // индексы строки и массива
begin
i:=1;
j:=1;
readln(str_arr); //считываем строку
prev:
if i>length(str_arr) then goto next;
if (str_arr[i]='+') then data_arr[j]:= data_arr[j]+1;
if (str_arr[i]='-') then data_arr[j]:= data_arr[j]-1;
if (str_arr[i]='>') then j:=j+1;
if (str_arr[i]='<') then j:=j-1;
if (str_arr[i]='.') then write(chr(data_arr[j]));
i:=i+1;
goto prev;
next:
for k:=1 to 10 do begin
write(data_arr[k]);
write(' ');
end;
end.
Код выдаст 1 2 3 0 0 0 0 0 0 0
Код выдаст 1 2 2 0 0 0 0 0 0 0
ideone.com
Далее, добавим [ и ]
Добавим ещё одну переменную i_stor.
Если текущий элемент прошёл проверку на [, то проверяем текущий элемент массива data_arr на ноль, и, если элемент больше нуля, загружаем в i_stor значение из переменной i.
При обработке закрывающей скобки ], если data_arr не ноль, в переменную i из переменной i_stor загружаем адрес открывающей скобки [
Далее переходим к команде i:=i+1;
Если до этого в i было загружено значение из i_stor ( при проверке ] ), то после джампа мы окажемся за [ ( в противном случае мы окажемся за ] )
LABEL prev,next;
var
data_arr:array[1..10] of integer; // массив данных
str_arr: string; // команды
i,j,k: integer; // индексы строки и массива
i_stor: integer;
begin
j:=1;
i:=1;
readln(str_arr); //считываем строку
prev:
if i>length(str_arr) then goto next;
if (str_arr[i]='+') then data_arr[j]:= data_arr[j]+1;
if (str_arr[i]='-') then data_arr[j]:= data_arr[j]-1;
if (str_arr[i]='>') then j:=j+1;
if (str_arr[i]='<') then j:=j-1;
if (str_arr[i]='.') then write(chr(data_arr[j]));
if (str_arr[i]='[') then
begin
if data_arr[j]>0 then i_stor:=i;
end;
if (str_arr[i]=']') then
begin
if data_arr[j]>0 then
begin
i:=i_stor;
end;
end;
i:=i+1;
goto prev;
next:
for k:=1 to 10 do begin
write(data_arr[k]);
write(' ');
end;
end.
Код переносит число 5 в соседнюю ячейку 0 5 0 0 0 0 0 0 0 0
ideone.com
Код HelloWorld выглядит так ideone.com
Перейдем к ассемблеру
Чтобы организовать цикл (loop), необходимо поместить в регистр CX количество тактов цикла и поставить метку, на которую будет сделан переход по завершении такта (по команде loop).
mov CX, 28h ; кол-во тактов цикла
prev: ; метка цикла
; выполняем
; операции
; внутри цикла
loop prev ; возвращаемся на метку prev
Создадим массив команд str_arr, поместим туда
Создадим массив данных data_arr, (для наглядности) поместим туда 1,1,1,1,1,1,1,1,1,1
В цикле сравниваем текущий символ с символом и, если символы равны, увеличиваем значение в текущей ячейке на 1.
text segment ; bf1.asm
assume cs:text, ds:data, ss:stk
begin:
;Подготовим все необходимое
mov AX,data ; настраиваем сегмент данных
mov DS,AX
mov DL, str_arr ; загружаем в DL 1ую команду
mov CX, 0Ah ; 10 тактов
prev:
cmp DL, 2Bh ; ячейка содержит +
jne next ; нет, переходим на метку next
mov BL, 00h ; загружаем в BL индекс
inc data_arr[BX] ; да, увеличиваем значение в ячейке на 1
next:
inc i ; переходим на следующий символ массива команд
mov BL, i
mov DL, str_arr [BX]
loop prev
mov AX, 4c00h ; завершение программы
int 21h
text ends
data segment
str_arr DB 2Bh,2Bh,2Bh,'$' ; код +++
data_arr DB 1,1,1,1,1,1,1,1,1,1,'$' ; данные
i DB 0 ;индекс элемента массива команд
data ends
stk segment stack
db 100h dup (0) ; резервируем 256 ячеек
stk ends
end begin
Ассемблирование (трансляция) выполняется командой tasm.exe bf1.asm
Линковка выполняется командой tlink.exe bf1.obj
После выполнения программы в отладчике TurboDebagger видно, что начиная с адреса 0130 расположены команды
Далее идет массив данных, в котором мы изменили первый элемент, далее идет переменная i, которая после выполнения цикла стала равна 0Ah.
Добавим команды
Для того, чтобы вывести одиночный символ с помощью функции 02h прерывания int 21h, необходимо (перед вызовом прерывания) поместить код символа в регистр DL.
mov AH,2
mov DL, код символа
int 21h
Напишем программу целиком
text segment ; bf2.asm
assume cs:text,ds:data, ss:stk
begin:
;Подготовим все необходимое
mov AX,data ; настраиваем сегмент данных
mov DS,AX
mov DL, str_arr ; загружаем в DL 1ую команду
mov CX, 0Ah ; 10 тактов
prev:
cmp DL, 2Bh ; ячейка содержит +
jne next ; нет, переходим на метку next
mov BL, j ; загружаем в BL индекс данных
inc data_arr[BX] ; да, увеличиваем значение в ячейке на 1
next:
cmp DL, 2Dh ; ячейка содержит -
jne next1 ; нет, переходим на метку next1
mov BL, j
dec data_arr[BX]
next1:
cmp DL, 3Eh ; ячейка содержит >
jne next2 ; нет, переходим на метку next2
inc j ; да, переходим на сдедующий элемент массива data_arr
next2:
cmp DL, 3Ch ; ячейка содержит <
jne next3 ; нет, переходим на метку next3
dec j ; да, переходим на предыдущий элемент массива data_arr
next3:
cmp DL, 2Eh ; ячейка содержит .
jne next4 ; нет, переходим на метку next4
mov AH,2 ; да, выводим содержимое ячейки
mov BL, j
mov DL, data_arr[BX]
int 21h
next4:
inc i ; переходим на следующий символ массива команд
mov BL, i
mov DL, str_arr [BX]
loop prev
mov AX, 4c00h ; завершение программы
int 21h
text ends
data segment
str_arr DB 2Bh,3Eh,2Bh,2Bh,'$' ; код +>++
data_arr DB 0,0,0,0,0,0,0,0,0,0,'$' ; данные
i DB 0, '$' ;индекс элемента массива команд
j DB 0, '$' ;индекс элемента массива данных
data ends
stk segment stack
db 100h dup (0) ; резервируем 256 ячеек
stk ends
end begin
Цикл работает так:
если текущий элемент строки str_arr не то перепрыгиваем на метку next: (иначе выполняем )
если текущий элемент строки str_arr не то перепрыгиваем на метку next1:
если текущий элемент строки str_arr не то перепрыгиваем на метку next2:
если текущий элемент строки str_arr не то перепрыгиваем на метку next3:
если текущий элемент строки str_arr не то перепрыгиваем на метку next4:
После метки next4: увеличиваем индекс строки str_arr и прыгаем в начало цикла — на метку prev:
Далее, добавим [ и ]
Добавим переменную i_stor.
Если текущий элемент прошёл проверку на [, то проверяем текущий элемент массива data_arr на ноль, и, если элемент равен нулю, перепрыгиваем дальше (на следующую метку), в противном случае загружаем в i_stor значение из переменной i.
next4:
cmp DL, 5Bh ; ячейка содержит [
jne next5 ; нет, переходим на метку next5
mov BL, j
mov DL, data_arr[BX]
cmp DL, 00 ; да, проверяем текущий элемент data_arr на ноль
jz next5 ; если ноль, прыгаем дальше
mov DL, i ; иначе загружаем
mov i_stor, Dl ; в i_stor значение переменной i
next5:
При обработке закрывающей скобки ], если data_arr не ноль, то в переменную i из переменной i_stor загружаем адрес открывающей скобки [
next5:
cmp DL, 5Dh ; ячейка содержит ]
jne next6 ; нет, переходим на метку next6
mov BL, j
mov DL, data_arr[BX]
cmp DL, 00 ; да, проверяем текущий элемент data_arr на ноль
jz next6 ; если ноль, прыгаем дальше
mov DL, i_stor ; иначе загружаем
mov i, Dl ; в i_stor значение переменной i
next6:
Проверим код
text segment ; bf4.asm
assume cs:text, ds:data, ss:stk
begin:
;Подготовим все необходимое
mov AX,data ; настраиваем сегмент данных
mov DS,AX
mov DL, str_arr ; загружаем в DL 1ую команду
mov CX, 50h ; 80 тактов
prev:
cmp DL, 2Bh ; ячейка содержит +
jne next ; нет, переходим на метку next
mov BL, j ; загружаем в BL индекс данных
inc data_arr[BX] ; да, увеличиваем значение в ячейке на 1
next:
cmp DL, 2Dh ; ячейка содержит -
jne next1 ; нет, переходим на метку next1
mov BL, j
dec data_arr[BX] ;BX, но не Bl
next1:
cmp DL, 3Eh ; ячейка содержит >
jne next2 ; нет, переходим на метку next2
inc j ; да, переходим на сдедующий элемент массива data_arr
next2:
cmp DL, 3Ch ; ячейка содержит <
jne next3 ; нет, переходим на метку next3
dec j ; да, переходим на предыдущий элемент массива data_arr
next3:
cmp DL, 2Eh ; ячейка содержит .
jne next4 ; нет, переходим на метку next4
mov AH,2 ; да, выводим содержимое ячейки
mov BL, j
mov DL, data_arr[BX]
int 21h
next4:
cmp DL, 5Bh ; ячейка содержит [
jne next5 ; нет, переходим на метку next5
mov BL, j
mov DL, data_arr[BX]
cmp DL, 00 ; да, проверяем текущий элемент data_arr на ноль
jz next5 ; если ноль, прыгаем дальше
mov DL, i ; иначе загружаем
mov i_stor, Dl ; в i_stor значение переменной i
next5:
cmp DL, 5Dh ; ячейка содержит ]
jne next6 ; нет, переходим на метку next6
mov BL, j
mov DL, data_arr[BX]
cmp DL, 00 ; да, проверяем текущий элемент data_arr на ноль
jz next6 ; если ноль, прыгаем дальше
mov DL, i_stor ; иначе загружаем
mov i, Dl ; в i_stor значение переменной i
next6:
inc i ; переходим к следующей команде
mov BL, i
mov DL, str_arr[BX]
loop prev ; прыгаем на метку prev:
mov AX, 4c00h ; завершение программы
int 21h
text ends
data segment
str_arr DB 2Bh,2Bh,2Bh,2Bh,5Bh, 3Eh,2Bh,3Ch,2Dh ,5Dh, '$' ; код ++++[>+<-]
data_arr DB 0,0,0,0,0,0,0,0,0,0,'$' ; данные
i DB 0,'$' ;индекс элемента массива команд
j DB 0,'$' ;индекс элемента массива данных
i_stor DB 0,'$'
data ends
stk segment stack
db 100h dup (0) ; резервируем 256 ячеек
stk ends
end begin
Добавим функцию ввода строки 3fh прерывания 21h
text segment ;bf5.asm
assume cs:text,ds:data, ss: stk
begin:
;Подготовим все необходимое
mov AX,data ; настраиваем сегмент данных
mov DS,AX
;;;
mov ah, 3fh ; функция ввода
mov cx, 10h ; 16 символов
mov dx,OFFSET str_arr
int 21h
;;;
mov DL, str_arr ; загружаем в DL 1ую команду
mov CX, 100h ; 256 тактов
prev:
cmp DL, 2Bh ; ячейка содержит +
jne next ; нет, переходим на метку next
mov BL, j ; загружаем в BL индекс данных
inc data_arr[BX] ; да, увеличиваем значение в ячейке на 1
next:
cmp DL, 2Dh ; ячейка содержит -
jne next1 ; нет, переходим на метку next1
mov BL, j
dec data_arr[BX] ;BX, но не Bl
next1:
cmp DL, 3Eh ; ячейка содержит >
jne next2 ; нет, переходим на метку next2
inc j ; да, переходим на следующий элемент массива data_arr
next2:
cmp DL, 3Ch ; ячейка содержит <
jne next3 ; нет, переходим на метку next3
dec j ; да, переходим на предыдущий элемент массива data_arr
next3:
cmp DL, 2Eh ; ячейка содержит .
jne next4 ; нет, переходим на метку next4
mov AH,2 ; да, выводим содержимое ячейки
mov BL, j
mov DL, data_arr[BX]
int 21h
next4:
cmp DL, 5Bh ; ячейка содержит [
jne next5 ; нет, переходим на метку next5
mov BL, j
mov DL, data_arr[BX]
cmp DL, 00 ; да, проверяем текущий элемент data_arr на ноль
jz next5 ; если ноль, прыгаем дальше
mov DL, i ; иначе загружаем
mov i_stor, Dl ; в i_stor значение переменной i
next5:
cmp DL, 5Dh ; ячейка содержит ]
jne next6 ; нет, переходим на метку next6
mov BL, j
mov DL, data_arr[BX]
cmp DL, 00 ; да, проверяем текущий элемент data_arr на ноль
jz next6 ; если ноль, прыгаем дальше
mov DL, i_stor ; иначе загружаем
mov i, Dl ; в i_stor значение переменной i
; здесь должен быть переход на метку prev:
next6:
inc i ; переходим к следующей команде
mov BL, i
mov DL, str_arr[BX]
loop prev ; прыгаем на метку prev:
MOV AH,2 ; переходим на новую строку
MOV DL,0Ah
INT 21h
mov AX, 4c00h ; завершение программы
int 21h
text ends
data segment
str_arr DB 10h DUP('$') ; буфер на 16 символов
data_arr DB 0,0,0,0,0,0,0,0,0,0,'$' ; данные
i DB 0,'$' ;индекс элемента массива команд
j DB 0,'$' ;индекс элемента массива данных
i_stor DB 0,'$'
data ends
stk segment para stack
db 100h dup (0) ; резервируем 256 ячеек
stk ends
end begin
Этот интерпретатор может обрабатывать небольшие bf-программы (например):
Для больших программ нужно больше итераций/циклов.
Ссылка на github с листингами программ.
Комментарии (6)
kloppspb
04.10.2018 03:01Ассемблирование (трансляция) выполняется командой tasm.exe bf1.asm
Линковка выполняется командой tlink.exe bf1.obj
Какой милый привет из 90-х :)
alex_231
04.10.2018 07:10+1Сразу хочу сказать, что с TASM я не знаком (мне как-то ближе NES-asm/SNES-asm), но и тут я немного понимаю.
Ваш код от «prev» до «loop prev» будет выполнять все проверки каждый цикл независимо от входных данных, этого можно избежать поставив перед каждым «next_» (кроме «next» и «next6» естественно) безусловный переход к «next6».
Самый простой способ выходить из цикла по окончанию строки можно сделать, если внести небольшие изменения:
... next5: cmp DL, 5Dh ; ячейка содержит ] jne next7 ; нет, переходим на метку next7 ... ... INT 21h next7 mov AX, 4c00h ...
Таким образом, если в строке команд встречается «левый» символ не являющийся командой выполнение программы прекратится.
И не совсем понятно, для чего вам цикл loop, если заранее неизвестно количество тактов, не проще реализовать цикл безусловным переходом с выходом из цикла по условию?
demsp Автор
04.10.2018 07:23Спасибо.
Самый простой способ выходить из цикла по окончанию строки
Такой способ не работает, потому что после ] надо переходить на [ (а не выходить из цикла).
И не совсем понятно, для чего вам цикл loop
Если заменить loop на jmp и выходить по условию (в модели .386)
prev: mov DL, str_arr cmp DL, 24h ; символ $ je exit_cycle ... jmp prev exit_cycle: mov AX, 4c00h
то получим бесконечный цикл.
alex_231
04.10.2018 07:45Такой способ не работает, потому что после ] надо переходить на [
Почему после проверки последнего возможного кода команды нужно возвращаться к предыдущему?
Моя правка нацелена на то, что программа проверяет наличие валидного кода команды в строке, если в текущей позиции строки отсутствует такой код, то выполнение прекращается.
Как это работает:
программа проверяет является ли код в ячейке командой, сравнивая его со всеми известными кодами команд поочередно, и если на все проверки мы получаем отрицательный ответ, то это значит, что строка команд закончилась, если же хотя бы одна проверка дает положительный результат, то отрабатывается соответствующая команде последовательность действий, увеличивается номер ячейки и цикл повторяется заново.
Подобным образом реализованы многие процедуры чтения строковых данных в играх NES/SNES, только там проверяются не сами строковые символы, а управляющие коды, ответственные за какое-либо поведение, и в большинстве случаев присутствует код окончания строки.
Про замену loop на jmp: мы получим бесконечный цикл только если условие будет невыполнимым (в строке будет отсутствовать символ $), если поставить на выход из цикла условие на окончание строки, то он должен прекратится (хотя в модели .386 я не специалист и не могу утверждать наверняка, но по логике должно сработать).
Error1024
Ээээ? Что-то я не понял окончания статьи :c