Часть I
Часть 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)


  1. Error1024
    04.10.2018 02:57
    +1

    Может, надо сначала вычислить длину строки и по этому значению вычислять количество тактов цикла. Или просто выходить из цикла по достижению конца строки $ (но я пока не знаю как).

    Ээээ? Что-то я не понял окончания статьи :c


  1. kloppspb
    04.10.2018 03:01

    Ассемблирование (трансляция) выполняется командой tasm.exe bf1.asm
    Линковка выполняется командой tlink.exe bf1.obj

    Какой милый привет из 90-х :)


  1. 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, если заранее неизвестно количество тактов, не проще реализовать цикл безусловным переходом с выходом из цикла по условию?


  1. 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 
    

    то получим бесконечный цикл.


  1. alex_231
    04.10.2018 07:45

    Такой способ не работает, потому что после ] надо переходить на [

    Почему после проверки последнего возможного кода команды нужно возвращаться к предыдущему?
    Моя правка нацелена на то, что программа проверяет наличие валидного кода команды в строке, если в текущей позиции строки отсутствует такой код, то выполнение прекращается.
    Как это работает:
    программа проверяет является ли код в ячейке командой, сравнивая его со всеми известными кодами команд поочередно, и если на все проверки мы получаем отрицательный ответ, то это значит, что строка команд закончилась, если же хотя бы одна проверка дает положительный результат, то отрабатывается соответствующая команде последовательность действий, увеличивается номер ячейки и цикл повторяется заново.
    Подобным образом реализованы многие процедуры чтения строковых данных в играх NES/SNES, только там проверяются не сами строковые символы, а управляющие коды, ответственные за какое-либо поведение, и в большинстве случаев присутствует код окончания строки.

    Про замену loop на jmp: мы получим бесконечный цикл только если условие будет невыполнимым (в строке будет отсутствовать символ $), если поставить на выход из цикла условие на окончание строки, то он должен прекратится (хотя в модели .386 я не специалист и не могу утверждать наверняка, но по логике должно сработать).


    1. demsp Автор
      04.10.2018 15:32

      Спасибо