VioletTec 发表于 2020-11-21 20:01

使用汇编(Assembly)语言实现BrainFuck解释器

本帖最后由 VioletTec 于 2020-11-21 20:40 编辑

> 在看完 《汇编语言(第三版)》——王爽著 并完成大部分习题之后,我想要实现一些东西巩固基础。于是想到了Brainfuck解释器,我在百度上尝试搜索`BrainFuck解释器` `汇编` 之类的关键词,只有一篇博客在写相关内容( <http://blog.sina.com.cn/s/blog_6adc92d701017sb3.html> )(当然了,我没有他写的优雅...行数比他多了好多,但是比较通俗易懂),但是使用谷歌搜索`BrainFuck Interpreter` `Assembly` 的关键词,出来的GitHub仓库却有不少,可以看出国人相关文章相对较少,于是我准备利用我学习的16位8086汇编基础来实现一个简单的BrainFuck解释器。
文原创博客地址:<https://blog.mcplugin.cn/p/690>

## # 0x0001 代码

```assembly
;AUTHOR: VioletTec
;Date: 2020.11.20 ~ 2020.11.21
assume cs:code,ds:data,ss:stack_data
data segment
more_than_msg db 'this is >',0dH,0aH,'$';共12个字节,0~0BH (0dH和0aH是换行)
start_running_msg db 0dH,0aH,'BrainFuck Interpreter Running ! ',0dH,0aH,'$'
exit_msg db 0dH,0aH,'bye~$'
data ends
stack_data segment
    db 512 dup (0);用于临时存储数据的栈
stack_data ends
code segment
bf_stack db 256 dup (0);定义256个字节,用于BF的栈
code_stack db 512 dup (0);定义512个字节,存放BF代码
code_len dw 0
code_point dw 0
stack_point dw 0
get_input: ;返回:al:ASCII码 ah:扫描码
    mov ah,0
    int 16H
    ret
print_screen:;ds:dx输出,$停止
    push ax
    mov ah,9H;输出DL的字符到显示器,$终止
    int 21H
    pop ax
    ret
print_single_word:;DL作为参数,打印DL
    mov ah,2H
    int 21H
    ret
get_code:;获取输入的代码
    push si
    push dx
    call get_input
    mov si,code_len
    mov dl,al
    mov code_stack,dl;把输入的代码放到代码栈里
    inc code_len;代码长度+1
    call print_single_word
    cmp dl,0dH;如果是回车
    pop dx
    pop si
    jne get_code;如果不是回车
    je start_run;如果是回车,则开始运行
start_run:
    dec code_len
    jmp start_get_code1;开始运行
more_than1:;如果是>符号,则stack指针+1
    inc stack_point
    jmp start_continue
less_than1:;如果是<符号,则stack指针-1
    dec stack_point
    jmp start_continue
inc_one1:;加号,则stack_point上的数据+1
    push si
    mov si,stack_point
    inc bf_stack
    pop si
    jmp start_continue
dec_one1:;减号,则stack_point上的数据-1
    push si
    mov si,stack_point
    dec bf_stack
    pop si
    jmp start_continue
;;==============检测到 [   查找 ]=================
find_right_bracket:;在当前stack_point所指向的bf_stack地址查找 ] (向右查找→),返回查到的字符串所在的指针位置
;返回值在dx中保存
    push si;存储si
    mov si,code_point
    push si;存储当前code_point
    push ax
    push bx
    mov bx,0H;bx用来计数
    mov cx,code_len
    sub cx,code_point;cx存放剩下的代码长度
find_right_s:
    mov al,code_stack
    cmp al,'['
    je find_right_find_left;如果查到了 [
    cmp al,']'
    pop ax
    je find_right_find_right
after_find_right:
    cmp bx,0
    je finded_right_bracket
    inc si;如果没查到继续+1再查
    loop find_right_s
    ;查找失败
    mov dx,0000H
    jmp after_find_right
find_right_find_left:;查找 [ 成功
    inc bx
    mov dx,si;此处si为查找到的字符的指针地址
find_right_find_right:;查找 ] 成功
    dec bx;bx+1
    jmp after_find_right
finded_right_bracket:;如果bx=0,找到了]则停止(finally)
    mov dx,si;当前si存放的是 ] 的位置
    pop bx
    pop ax;恢复之前的ax
    pop si
    mov code_point,si;恢复之前的code指针
    pop si;恢复之前的si
    ;现在的dx就是 ]所在位置 所在的code_point
    ret
left_bracket1:;检测到左中括号
    push si
    push ax
    mov si,stack_point
    mov al,bf_stack
    cmp al,0H;
    pop ax
    pop si
    jne left_bracket1_not_zero;如果不是0
    je left_bracket1_is_zero;如果是0
left_bracket1_not_zero:;如果当前指针下数据不为0,则继续执行
    jmp start_continue;返回
left_bracket1_is_zero:;如果当前指针下数据为0,则跳转到最近的一个右括号,则结束循环
    push dx
    call find_right_bracket;查找 ]
    ;dx为返回的stack位置(失败为0000H)
    mov code_point,dx
    pop dx
    jmp start_continue;返回
;;==============检测到 ] 查找 [ =======================
find_left_bracket:;查找 [
;返回值在dx中保存
    push si;存储si
    mov si,code_point
    push si;存储当前code_point
    push ax
    push bx
    mov bx,0H;bx用来计数
    mov cx,code_point;只需要循环code指针个即可
find_left_s:;循环体
    mov al,code_stack
    cmp al,'['
    je find_left_find_left;如果查到了 [
    cmp al,']'
    je find_left_find_right
after_find_left:
    cmp bx,0
    je finded_left_bracket
    dec si;如果没查到继续-1再查
    loop find_left_s
    ;查找失败
    mov dx,0000H
    jmp left_bracket1
find_left_find_left:;查找 [ 成功
    dec bx;bx-1
    mov dx,si;此处si为查找到的字符的指针地址
    jmp after_find_left
find_left_find_right:;查找 ] 成功
    inc bx;bx+1
    jmp after_find_left
finded_left_bracket:;如果bx=0,找到了[则停止(finally)
    mov dx,si;当前si存放的是 ] 的位置
    pop bx
    pop ax;恢复之前的ax
    pop si
    mov code_point,si;恢复之前的code指针
    pop si;恢复之前的si
    ;现在的dx就是 [ 所在的code_point
    ret
right_bracket1:;检测到 ]准备 查找 [
    push si
    push ax
    mov si,stack_point;存储代码指针
    mov al,bf_stack
    cmp al,0H;
    pop ax
    pop si
    jne right_bracket1_not_zero;如果不是0
    je right_bracket1_is_zero;如果是0
right_bracket1_is_zero:;如果当前指针下数据为0,则继续执行
    jmp start_continue;返回
right_bracket1_not_zero:;如果当前指针下数据不为0,则跳转到最近的一个左[括号,继续循环
    push dx
    call find_left_bracket;查找 [
    ;dx为返回的stack位置(失败为0000H)
    mov code_point,dx
    pop dx
    jmp start_continue;返回
;;======================================
comma_print1:;如果是,逗号,则打印
    push ax
    push dx
    push si
    mov si,stack_point
    mov dl,bf_stack;打印单个字符(使用ASCII码)
    call print_single_word;dl是参数,打印
    pop si
    pop dx
    pop ax
    jmp start_continue
exit:
    push ds
    push dx
    push ax
    mov ax,data
    mov ds,ax
    lea dx,exit_msg
    call print_screen
    pop ax
    pop dx
    pop ds
    ret
more_than:
    jmp more_than1
less_than:
    jmp less_than1
inc_one:
    jmp inc_one1
dec_one:
    jmp dec_one1
comma_print:
    jmp comma_print1
left_bracket:
    jmp left_bracket1
right_bracket:
    jmp right_bracket1
start:
    ;初始栈顶
    mov si,stack_data
    mov ss,si
    mov sp,14cH;512+1为栈底
    call get_code;获取代码,放到code_stack里
start_get_code1:
    ;此时code_stack里装的是代码,code_point在code_stack起始位置
    push ds
    push dx
    mov ax,data
    mov ds,ax
    lea dx,start_running_msg;打印开始解释BF的提示语
    call print_screen
    pop dx
    pop ds
run:
    mov ax,code_point
    mov bx,code_len
    cmp ax,bx
    je stop;如果code_point超出长度,则执行结束
    mov si,code_point
    mov al,code_stack;读取代码
    cmp al,'>'
    je more_than
    cmp al,'<'
    je less_than
    cmp al,'+'
    je inc_one
    cmp al,'-'
    je dec_one
    cmp al,','
    je comma_print
    cmp al,'['
    je left_bracket
    cmp al,']'
    je right_bracket
start_continue:
    inc code_point;代码指针+1
    jmp run;继续循环
stop:
    call exit
    mov ax,4c00H
    int 21H
code ends
end start
```




------

## # 0x0002 使用方法&配图

在你的DosBox里运行就行了,需要手动输入代码(不支持退格键,请仔细输入哦),优化等我有空吧。

使用我的《如何使用BrainF**k实现乘法计算?》这篇文章中的乘法算法进行测试,计算 2*3
https://blog.mcplugin.cn/p/595



(6对应的字符为♠)

------

## # 0x0003结语

新学汇编,只会基础,所以BrainFuck代码只能手动输入(对于'.'输入功能的实现也是在咕咕咕中,虽然功能很简单,但是比较懒也没办法...),(主要是懒得去做读取文件(dog)),没有优化,就这样吧,有空再改(也许就是明天)(咕咕咕。

garfieldlong 发表于 2020-11-22 08:10

楼主加油,我看好你呦
页: [1]
查看完整版本: 使用汇编(Assembly)语言实现BrainFuck解释器