在看完 《汇编语言(第三版)》——王爽著 并完成大部分习题之后,我想要实现一些东西巩固基础。于是想到了Brainfuck解释器,我在百度上尝试搜索
BrainFuck解释器
汇编
之类的关键词,只有一篇博客在写相关内容(http://blog.sina.com.cn/s/blog_6adc92d701017sb3.html)(当然了,我没有他写的优雅...行数比他多了好多,但是比较通俗易懂),但是使用谷歌搜索BrainFuck Interpreter
Assembly
的关键词,出来的GitHub仓库却有不少,可以看出国人相关文章相对较少,于是我准备利用我学习的16位8086汇编基础,在MS-DOS提供的实模式下来实现一个简单的BrainFuck解释器。
代码整体思路根据我的Java版的BrainFuck解释器来的。
Java版解释器GitHub地址:https://github.com/KeKe12030/BrainFuckInterpreter
# 0x0001 代码
注:由于我博客的代码编辑器不支持汇编语言展示,所以一下内容显现格式为ECL代码格式,但是不影响阅读与运行。
;AUTHOR: VioletTec (QQ:3352772828) ;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[si],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[si] pop si jmp start_continue dec_one1:;减号,则stack_point上的数据-1 push si mov si,stack_point dec bf_stack[si] 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[si] 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[si] 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[si] 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[si] 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[si];打印单个字符(使用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[si];读取代码 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里运行就行了,需要手动输入BrainFuck代码(不支持退格键,请仔细输入),优化等我有空吧。
使用我的《如何使用BrainF**k实现乘法计算?》这篇文章中的乘法算法进行测试,计算 2*3
https://blog.mcplugin.cn/p/595
# 0x0003结语
新学汇编,只会基础,所以BrainFuck代码只能手动输入(对于'.'输入功能的实现也是在咕咕咕中,虽然功能很简单,但是比较懒也没办法...),(主要是懒得去做读取文件(dog)),没有优化,就这样吧,有空再改(也许就是明天)(咕咕咕。