Browse Source

feat: 添加了菜单操作

添加了命令行菜单和相应的菜单操作
修复了拼写错误
更新README.md文件
SongZihuan 3 years ago
parent
commit
34678bb66a
7 changed files with 436 additions and 102 deletions
  1. 4 3
      CMakeLists.txt
  2. 62 16
      README.md
  3. 25 3
      include/brainfuck.h
  4. 210 72
      main.c
  5. 4 1
      src/CMakeLists.txt
  6. 10 0
      src/_brainfuck.h
  7. 121 7
      src/brainfuck.c

+ 4 - 3
CMakeLists.txt

@@ -5,12 +5,13 @@ PROJECT(BrainFuck C)
 ADD_DEFINITIONS(-DBF_VERSION="1.0.0 Version")
 ADD_DEFINITIONS(-DBF_VERSION_INFO="first version of BrainFuckPro")
 
-SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin)
-SET(LIBRARY_OUTPUT_PATH lib)  # 设置输出路
+SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
+SET(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib)  # 设置输出路
 SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH})
 SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH})
 
 SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")  # 设定rpath
+SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
 
 INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include)
 
@@ -18,4 +19,4 @@ ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/src)
 ADD_EXECUTABLE(BrainFuck main.c)
 TARGET_LINK_LIBRARIES(BrainFuck BrainFuck_Lib)
 
-INSTALL(TARGETS BrainFuck BrainFuck_Lib)
+INSTALL(TARGETS BrainFuck)

+ 62 - 16
README.md

@@ -2,9 +2,9 @@
 使用C语言实现的BrainFuck解释器,并且扩展了部分语法。
 ## 关于BrainFuck
 Brainfuck,是一种极小化的程序语言,它是由Urban Müller在1993年创造的。这种语言有时被简称为BF。
-## 命令参数
+## 命令参数
 ```
-使用: BrainFuck[参数] 文件..  
+使用: BrainFuck [参数] 文件..  
 参数:  
 -e --eval              从一个字符串中运行代码  
 -v --version           显示版本信息  
@@ -13,7 +13,7 @@ Brainfuck,是一种极小化的程序语言,它是由Urban Müller在1993年
 ## 支持的语法
 ```
 使用:指令[指令[指令...]]
-指令
+指令
 >       读取头向后移动1格
 <       读取头向前移动1格
 +       读取头所指格子的数据增加1
@@ -33,17 +33,36 @@ Brainfuck,是一种极小化的程序语言,它是由Urban Müller在1993年
 ]       循环结束位置
 ?       读取头回到初始位置
 ```
-
-## 命令行模式
+##命令行模式
 执行完成指定文件后,BrainFuckPro进入命令行交互模式。使用者可以通过stdin即时写入指令操控读取头。
-## 关于API
+命令行菜单支持的操作(同时为步进模式菜单)
+```
+v       显示版本号
+h       显示帮助
+m       显示菜单
+w       显示详细信息
+p       显示纸带信息
+r       显示读取头信息
+s       开启/关闭步进模式 (默认关闭)
+i       开启/关闭详细信息模式 (默认关闭)
+c       清空屏幕 (调用系统clear指令)
+q       退出
+e       退出菜单
+```
+##关于API
 BrainFuckPro为C语言提供了相应的API。若需要使用,请链接库`BrainFuck_LIB`和包含头文件`brainfuck.h`。  
 ```c
-char *bf_getVersionInfo();  // 获取版本和版本信息字符串
-char *bf_getVersion();  // 获取版本
+#define bf_setEnvMode(env, name, mode) (bf_setEnv##name##Mode((env), (mode)))
+// bf_setEnvMode用于设置enc的mode
+// 目前env的mode支持step(步进模式)和information(执行时显示信息)
+// mode: 0-关闭 1-打开 -1-若打开则关闭,否则打开 其他值-无实际操作
+// 返回值为对应mode的实际值
+
+char *bf_getVersionInfo(void);  // 获取版本和版本信息字符串
+char *bf_getVersion(void);  // 获取版本
 
 // bf_env指针通常可以使用void *来代替
-bf_env *bf_setEnv();  // 设置一个运行环境(返回一个指针)
+bf_env *bf_setEnv(void);  // 设置一个运行环境(返回一个指针)
 void bf_freeEnv(bf_env *env);  // 释放一个环境
 void bf_initEnv(bf_env *env);  // 复位读取头到初始位置
 
@@ -54,15 +73,42 @@ bf_code bf_parserBrainFuck_Str(const char *str); // 读取字符串并生成代
 void bf_printBrainFuck(bf_code code); // 打印代码
 int bf_runBrainFuck(bf_code code, bf_env *env); // 在指定环境中执行代码 (返回0表示无异常)
 void bf_printError(char *info, bf_env *env);  // 打印错误信息, 若无错误则不执行
+void bf_printPaperTape(bf_env *env);  // 打印纸带
+void bf_printHead(bf_env *env);  // 打印读取头信息
+void bf_printEnv(bf_env *env);  // 打印env信息
+void bf_printEnvWithMode(bf_env *env);  // 打印env信息和env的mode信息
+bf_STEP_FUNC bf_setEnvStepFunc(bf_env *env, bf_STEP_FUNC step_func);  // 设置步进函数, 每次不仅时当用户输入m时则回调该函数(step_func)
 ```
 `bf_env`即图灵机的纸带,`bf_code`即读取头的指令。二者本质上均为指针,且在brainfuck.h中无具体实现,使用者可以使用`void *`来代替。
-## 构建
-使用cmake+make来构建本项目。
-cmake中可以设置`CMAKE_INSTALL_PREFIX`指定文件安装路径。
-可以使用`make`构建项目,使用`make install`安装项目。
-## 声明
-### 版权声明
-版权所有 © 2021 [SuperHuan](https://github.com/SuperH-0630) 保留所有权利。  
+##构建
+使用cmake+make来构建本项目。  
+创建目录
+```shell
+$ mkdir build
+$ cd build
+```
+执行cmake
+```shell
+$ cmake ..
+```
+若在windows平台上使用Mingw编译,则
+```shell
+$ cmake .. -G "MinGW Makefiles"
+```
+使用make编译
+```shell
+$ make
+```
+安装程序
+```shell
+$ make install
+```
+可以在cmake时指定安装目录。  
+安装步骤不是必须的,你可以直接运行编译后的二进制文件。
+可执行文件会安装在bin目录,动态库则会安装在lib目录(或bin目录),头文件则安装在include目录。
+##声明
+###版权声明
+版权所有 (c) 2021 [SuperHuan](https://github.com/SuperH-0630) 保留所有权利。
 BrainFuckPro编程语言C解释器由SuperHuan开发,技术归属SuperHuan。  
 本授权协议适用于BrainFuckPro编程语言C解释器,SuperHuan拥有对本授权协议最终解释权和修改权。  
 ### 免责声明

+ 25 - 3
include/brainfuck.h

@@ -1,5 +1,6 @@
 #ifndef BRAINFUCK_BRAINFUCK_H
 #define BRAINFUCK_BRAINFUCK_H
+#include <stdbool.h>
 
 #ifndef TYPEDEF_bf_code
 typedef struct bf_code *bf_code;
@@ -11,11 +12,27 @@ typedef struct bf_env bf_env;
 #define TYPEDEF_bf_env
 #endif
 
-char *bf_getVersionInfo();  // 获取版本和版本信息字符串
-char *bf_getVersion();  // 获取版本
+#ifndef TYPEDEF_bf_STEP_FUNC
+#define TYPEDEF_bf_STEP_FUNC
+typedef int (*bf_STEP_FUNC)(bf_env *env);
+#endif
+
+#define bf_setEnvModeFunc(name) bool bf_setEnv##name##Mode(bf_env *env, int mode)
+bf_setEnvModeFunc(step);
+bf_setEnvModeFunc(information);
+#undef bf_setEnvModeFunc
+
+#define bf_setEnvMode(env, name, mode) (bf_setEnv##name##Mode((env), (mode)))
+// bf_setEnvMode用于设置enc的mode
+// 目前env的mode支持step(步进模式)和information(执行时显示信息)
+// mode: 0-关闭 1-打开 -1-若打开则关闭,否则打开 其他值-无实际操作
+// 返回值为对应mode的实际值
+
+char *bf_getVersionInfo(void);  // 获取版本和版本信息字符串
+char *bf_getVersion(void);  // 获取版本
 
 // bf_env指针通常可以使用void *来代替
-bf_env *bf_setEnv();  // 设置一个运行环境(返回一个指针)
+bf_env *bf_setEnv(void);  // 设置一个运行环境(返回一个指针)
 void bf_freeEnv(bf_env *env);  // 释放一个环境
 void bf_initEnv(bf_env *env);  // 复位读取头到初始位置
 
@@ -26,5 +43,10 @@ bf_code bf_parserBrainFuck_Str(const char *str); // 读取字符串并生成代
 void bf_printBrainFuck(bf_code code); // 打印代码
 int bf_runBrainFuck(bf_code code, bf_env *env); // 在指定环境中执行代码 (返回0表示无异常)
 void bf_printError(char *info, bf_env *env);  // 打印错误信息, 若无错误则不执行
+void bf_printPaperTape(bf_env *env);  // 打印纸带
+void bf_printHead(bf_env *env);  // 打印读取头信息
+void bf_printEnv(bf_env *env);  // 打印env信息
+void bf_printEnvWithMode(bf_env *env);  // 打印env信息和env的mode信息
+bf_STEP_FUNC bf_setEnvStepFunc(bf_env *env, bf_STEP_FUNC step_func);  // 设置步进函数, 每次不仅时当用户输入m时则回调该函数(step_func)
 
 #endif //BRAINFUCK_BRAINFUCK_H

+ 210 - 72
main.c

@@ -2,32 +2,140 @@
 #include <stdlib.h>
 #include <getopt.h>
 #include <string.h>
+#include <stdbool.h>
 
 #include "brainfuck.h"
 #define COMMAND_LINE_STR_SIZE (20)
 
+static bf_env *global_env;
 static struct option long_options[] = {
         {"version", no_argument, 0, 'v'},
         {"help", no_argument, 0, 'h'},
         {"eval", required_argument, 0, 'e'},
         {0, 0, 0, 0}
 };
+char *program_name;
 
-void printUsage(char *name) {
-    printf("Usage: %s[options] file..\n", name);
+void printUsage(void);
+void printVersion(void);
+void printMenu(void);
+int stepModeFunc(bf_env *env);
+int runFile(int argc, char **argv, bf_env *env);
+int runCommandLine_(bf_env *env);
+int runCommandLine(bf_env *env);
+void free_env_at_exit(void);
+int clInformation(int ch, bf_env *env);
+
+int main(int argc, char **argv){
+    int option_index = 0;
+    int status;
+    program_name = *argv;
+    global_env = bf_setEnv();
+    atexit(free_env_at_exit);
+    bf_setEnvStepFunc(global_env, stepModeFunc);
+
+    while (1) {
+        option_index = 0;
+        int c = getopt_long (argc, argv, "vhe:", long_options, &option_index);
+        if (c == -1)
+            break;
+
+        switch (c) {
+            case 0:
+                break;
+            case 'h':
+                printUsage();
+                goto end;
+            case 'v':
+                printVersion();
+                goto end;
+            case 'e': {
+                bf_code code;
+                code = bf_parserBrainFuck_Str((char *) optarg);
+                bf_runBrainFuck(code, global_env);
+                bf_printError("eval error", global_env);
+                bf_freeBrainFuck(code);
+                break;
+            }
+            default:
+            case '?':
+                printUsage();
+                return 1;
+        }
+    }
+
+    status = runFile(argc, argv, global_env);
+    if (status != 0)
+        return status;
+
+    status = runCommandLine(global_env);
+    if (status == 1) {
+        printf("stdin error\n");
+        return status;
+    }
+
+    printf("BrainFuckPro: bye~\n");
+    return 0;
+
+    end:  // 从-v和-h中退出
+    if (argc > 2) {
+        printf("Too many argument\n");
+        return 1;
+    }
+    return 0;
+}
+
+
+void printUsage(void) {
+    printf("Usage: %s[options] file..\n", program_name);
     printf("Options: \n");
     printf(" -e --eval\t\tRun code in a string\n");
     printf(" -v --version\t\tshow version\n");
     printf(" -h --help\t\tshow help\n\n");
 
-    printf("\nFor more information, please see: \n");
-    printf("github.com/SuperH-0630/BranchFuckPro\n");
+    printf("CommandLine Options: \n");
+    printf(" v show version\n");
+    printf(" h show help\n");
+    printf(" q quit\n");
+    printf(" m show CommandLine Menu\n");
+    printf("CommandLine Menu: \n");
+    printMenu();
+
+    printf("\n");
+    printf("Step Options: \n");
+    printf(" n continue\n");
+    printf(" m show step menu\n");
+    printf("Step Menu: \n");
+    printMenu();
+
+    printf("\n");
+    printf(" For more information, please see: \n");
+    printf(" github.com/SuperH-0630/BrainFuckPro\n");
 }
 
-void printVersion() {
+
+void printVersion(void) {
     printf("%s", bf_getVersionInfo());
 }
 
+
+void printMenu(void) {
+    printf("+---+----------------------------+\n");
+    printf("+ v + show version               +\n");
+    printf("+ h + show help                  +\n");
+    printf("+ m + show menu                  +\n");
+    printf("+ w + print env information      +\n");
+    printf("+ p + print paper tape           +\n");
+    printf("+ r + print read head            +\n");
+    printf("+ s + run in step model          +\n");
+    printf("+ i + run with information       +\n");
+    printf("+ c + clear screen(May not work) +\n");
+    printf("+ q + quit                       +\n");
+    printf("+ e + exit the menu              +\n");
+    printf("+---+----------------------------+\n");
+}
+
+
 int runFile(int argc, char **argv, bf_env *env) {
     while (optind < argc) {
         FILE *file = fopen(argv[optind], "r");
@@ -50,95 +158,125 @@ int runFile(int argc, char **argv, bf_env *env) {
     return 0;
 }
 
-int runCommandLine_(bf_env *env) {
-    int size = COMMAND_LINE_STR_SIZE;
-    int status;
-    char *str = calloc(size + 1, sizeof(char ));
-    fgets(str, COMMAND_LINE_STR_SIZE + 1, stdin);
-    while (!strchr(str, '\n') && !feof(stdin) && !ferror(stdin)) {
-        char *new = calloc(size + COMMAND_LINE_STR_SIZE + 1, sizeof(char ));
-        strcpy(new, str);
-        fgets(new + size, COMMAND_LINE_STR_SIZE + 1, stdin);
-        free(str);
-        str = new;
-        size += COMMAND_LINE_STR_SIZE;
+
+int stepModeFunc(bf_env *env) {
+    return clInformation('m', env);
+}
+
+
+int clInformation(int ch, bf_env *env) {
+    int return_ = 0;
+    switch (ch) {
+        case 'v':
+            printVersion();
+            break;
+        case 'h':
+            printUsage();
+            break;
+        case 'm':
+            printMenu();
+            printf("Enter the operation:");
+
+            int del_ch;
+            while ((del_ch = getc(stdin) != '\n') && del_ch != EOF)
+                continue;
+
+            ch = getc(stdin);
+            return_ = clInformation(ch, env);
+            goto NOT_CLEAR;  // 不用清除stdin
+        case 'q':  // 退出菜单
+            break;
+        case 'w':
+            bf_printEnvWithMode(env);
+            break;
+        case 'p':
+            bf_printPaperTape(env);
+            printf("\n");
+            break;
+        case 'r':
+            bf_printHead(env);
+            printf("\n");
+            break;
+        case 's':
+            if (bf_setEnvMode(env, step, -1))
+                printf("step mode on\n");
+            else
+                printf("step mode off\n");
+            break;
+        case 'i':
+            if (bf_setEnvMode(env, information, -1))
+                printf("information mode on\n");
+            else
+                printf("information mode off\n");
+            break;
+        case 'c':
+            system("clear");  // 清空
+            break;
+        case 'e':  // 退出菜单
+            break;
+        default:
+            printf("Unsupported menu option\n");
+            break;
     }
 
-    bf_code code;
-    code = bf_parserBrainFuck_Str(str);
-    status = bf_runBrainFuck(code, env);
-    bf_printError("command line error", env);
-    bf_freeBrainFuck(code);
-    free(str);
-    return status;
+    int del_ch;
+    while ((del_ch = getc(stdin) != '\n') && del_ch != EOF)
+        continue;
+
+NOT_CLEAR:
+    return return_;
 }
 
+
 int runCommandLine(bf_env *env) {
     int ch;
-    unsigned int count = 0;
 
-    printf("BranchFuck %s (" __DATE__ ", " __TIME__ ")\n", bf_getVersion());
-    printf("Welcome to ues BranchFuck CommandLine (Type 'q' to quit)\n");
+    printf("BrainFuck %s (" __DATE__ ", " __TIME__ ")\n", bf_getVersion());
+    printf("Welcome to ues BrainFuck CommandLine (Type 'q' to quit)\n");
+    printf("Type v(version), h(help) and m(menu) for more information\n");
 
-    while (1) {
+    for (unsigned count = 0; true;count++) {
         if (feof(stdin) || ferror(stdin))
             return 1;
         printf("[%d] >", count);
         ch = getc(stdin);
         if (ch == 'q')
             return 0;
-        else
+        else if (ch == 'v' || ch == 'h' || ch == 'm') {
+            if(clInformation(ch, env) == 1)
+                return 0;  // quit
+            continue;
+        } else
             ungetc(ch, stdin);
         runCommandLine_(env);
-        count++;
     }
 }
 
-int main(int argc, char **argv){
-    int option_index = 0;
-    int status;
-    bf_env *env = bf_setEnv();
-
-    while (1) {
-        option_index = 0;
-        int c = getopt_long (argc, argv, "vhe:", long_options, &option_index);
-        if (c == -1)
-            break;
 
-        switch (c) {
-            case 0:
-                break;
-            case 'h':
-                printUsage(argv[0]);
-                return 0;
-            case 'v':
-                printVersion();
-                return 0;
-            case 'e': {
-                bf_code code;
-                code = bf_parserBrainFuck_Str((char *) optarg);
-                bf_runBrainFuck(code, env);
-                bf_printError("eval error", env);
-                bf_freeBrainFuck(code);
-                break;
-            }
-            default:
-            case '?':
-                printUsage(argv[0]);
-                return 1;
-        }
+int runCommandLine_(bf_env *env) {
+    int size = COMMAND_LINE_STR_SIZE;
+    int status;
+    char *str = calloc(size + 1, sizeof(char ));
+    fgets(str, COMMAND_LINE_STR_SIZE + 1, stdin);
+    while (!strchr(str, '\n') && !feof(stdin) && !ferror(stdin)) {
+        char *new = calloc(size + COMMAND_LINE_STR_SIZE + 1, sizeof(char ));
+        strcpy(new, str);
+        fgets(new + size, COMMAND_LINE_STR_SIZE + 1, stdin);
+        free(str);
+        str = new;
+        size += COMMAND_LINE_STR_SIZE;
     }
 
-    status = runFile(argc, argv, env);
-    if (status != 0)
-        return status;
+    bf_code code;
+    code = bf_parserBrainFuck_Str(str);
+    status = bf_runBrainFuck(code, env);
+    bf_printError("command line error", env);
+    bf_freeBrainFuck(code);
+    free(str);
+    return status;
+}
 
-    status = runCommandLine(env);
-    if (status == 1) {
-        printf("stdin error\n");
-        return status;
-    }
 
-    printf("BranchFuckPro: bye~\n");
-    return 0;
+void free_env_at_exit(void) {
+    bf_freeEnv(global_env);
 }

+ 4 - 1
src/CMakeLists.txt

@@ -2,4 +2,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.19)
 SET(CMAKE_C_STANDARD 11)
 PROJECT(BrainFuck_Lib C)
 
-ADD_LIBRARY(BrainFuck_Lib SHARED brainfuck.c)
+ADD_LIBRARY(BrainFuck_Lib SHARED brainfuck.c)
+SET_TARGET_PROPERTIES(BrainFuck_Lib PROPERTIES PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/brainfuck.h)
+INSTALL(TARGETS BrainFuck_Lib
+        PUBLIC_HEADER DESTINATION include)

+ 10 - 0
src/_brainfuck.h

@@ -1,5 +1,6 @@
 #ifndef BRAINFUCK__BRAINFUCK_H
 #define BRAINFUCK__BRAINFUCK_H
+#include <stdbool.h>
 
 #define TABLE_SIZE (20)
 #define FILE_READ_SIZE (20)
@@ -19,6 +20,11 @@ typedef struct bf_code *bf_code;
 #define TYPEDEF_bf_code
 #endif
 
+#ifndef TYPEDEF_bf_STEP_FUNC
+#define TYPEDEF_bf_STEP_FUNC
+typedef int (*bf_STEP_FUNC)(bf_env *env);
+#endif
+
 struct bf_code {
     bf_byte byte;
 };
@@ -39,6 +45,10 @@ struct bf_env {
     struct bf_item *item;
     struct bf_pitem pitem;
     char *error_info;  // 错误信息 (字符串常量)
+
+    bool step_mode;
+    bool information_mode;
+    bf_STEP_FUNC step_func;  // 步进处理函数
 };
 
 #endif //BRAINFUCK__BRAINFUCK_H

+ 121 - 7
src/brainfuck.c

@@ -12,15 +12,23 @@
 #define BF_VERSION_INFO "This is a magical version because no one knows how magical he is."
 #endif
 
-char *bf_getVersionInfo() {
+static bf_code make_bf_code(void);
+static void free_bf_code(bf_code code);
+static int runBrainFuck(bf_byte *byte, bf_env *env);
+static int runBrainFuck_(bf_byte *byte, bf_env *env);
+static void freeItem(bf_item *item);
+static bf_item *makeItem(void);
+static void addItem(bf_item *global);
+
+char *bf_getVersionInfo(void) {
     return BF_VERSION "\n" BF_VERSION_INFO "\n";
 }
 
-char *bf_getVersion() {
+char *bf_getVersion(void) {
     return BF_VERSION;
 }
 
-static bf_code make_bf_code() {
+static bf_code make_bf_code(void) {
     bf_code code = calloc(1, sizeof(struct bf_code));
     return code;
 }
@@ -108,7 +116,7 @@ void bf_printBrainFuck(bf_code code) {
     printf("%s", code->byte);
 }
 
-static bf_item *makeItem() {
+static bf_item *makeItem(void) {
     bf_item *item = calloc(1, sizeof(bf_item));
     return item;
 }
@@ -122,13 +130,15 @@ static void freeItem(bf_item *item) {
     }
 }
 
-bf_env *bf_setEnv() {
+bf_env *bf_setEnv(void) {
     bf_env *env = calloc(1, sizeof(bf_env));
     env->item = makeItem();
     env->pitem.item = env->item;
     env->pitem.index = 0;
     env->pitem.base = env->item;
     env->error_info = NULL;
+    env->step_mode = false;
+    env->information_mode = false;
     return env;
 }
 
@@ -151,7 +161,6 @@ static void addItem(bf_item *global) {
     global->next->prev = global;
 }
 
-static int runBrainFuck(bf_byte *byte, bf_env *env);
 int bf_runBrainFuck(bf_code code, bf_env *env) {
     bf_byte byte = code->byte;
     int status = runBrainFuck(&byte, env);
@@ -164,7 +173,6 @@ int bf_runBrainFuck(bf_code code, bf_env *env) {
     }
 }
 
-static int runBrainFuck_(bf_byte *byte, bf_env *env);
 static int runBrainFuck(bf_byte *byte, bf_env *env) {
     while (**byte != '\0') {
         int status = runBrainFuck_(byte, env);
@@ -176,6 +184,12 @@ static int runBrainFuck(bf_byte *byte, bf_env *env) {
 
 static int runBrainFuck_(bf_byte *byte, bf_env *env) {
     bf_pitem *pitem = &(env->pitem);
+    char *b = *byte;
+    if (env->information_mode) {
+        printf("Execute instructions: %c[%p]\n", *b, b);
+        bf_printEnv(env);
+    }
+
     switch (**byte) {
         case '>': {
             int num = 1;
@@ -333,6 +347,32 @@ static int runBrainFuck_(bf_byte *byte, bf_env *env) {
             return -1;
     }
 
+    if (env->information_mode) {
+        printf("\nEnd execute instructions: %c[%p]\n", *b, b);
+        bf_printHead(env);
+        printf("\n\n");
+    }
+
+    if (env->step_mode && env->step_func != NULL) {  // 步进模式
+        int ch;
+        while (1) {
+            printf("(Type n or [enter] to continue. Type m to show menu.)\nEnter:");
+            ch = getc(stdin);
+            if (ch == 'n' || ch == '\n' || ch == EOF) { // 继续
+                printf("continue...\n");
+                break;
+            } else if (ch == 'm') {  // 进入菜单
+                if (env->step_func(env) != 0) {  // 调用函数 (stdin在该函数中清空)
+                    env->error_info = "step mode func error";
+                    return -1;
+                }
+            }
+        }
+
+        while ((ch = getc(stdin)) != '\n' && ch != EOF)  // 清除stdin
+            continue;
+    }
+
     (*byte)++;  // 读取下一个指令
     return 0;
 }
@@ -341,3 +381,77 @@ void bf_printError(char *info, bf_env *env) {
     if (env->error_info != NULL)
         printf("%s : %s\n", info, env->error_info);
 }
+
+void bf_printPaperTape(bf_env *env) {
+    unsigned count = 0;
+    bf_item *item = env->item;
+    while (true) {
+        for (int i = 0; i < TABLE_SIZE; i++) {
+            printf("[(%d)%d", count, item->data[i]);
+            if (isprint(item->data[i]))
+                printf("'%c'", item->data[i]);
+            if (i == env->pitem.index && item == env->pitem.item)
+                printf("(head)");
+            if (i == 0 && item == env->pitem.base)
+                printf("(base)");
+            printf("]");
+            if (i != TABLE_SIZE - 1)  // 不是最后一个
+                printf("-");
+            count++;
+        }
+        item = item->next;
+        if (item == NULL)
+            break;
+        else
+            printf("-");  // 换行接着输出
+    }
+}
+
+void bf_printHead(bf_env *env) {
+    printf("head data: %d", env->pitem.item->data[env->pitem.index]);
+    if (isprint(env->pitem.item->data[env->pitem.index]))
+        printf("'%c'", env->pitem.item->data[env->pitem.index]);
+}
+
+void bf_printEnv(bf_env *env) {
+    bf_printPaperTape(env);
+    printf("\n");
+    bf_printHead(env);
+    printf("\n");
+}
+
+void bf_printEnvWithMode(bf_env *env) {
+    bf_printPaperTape(env);
+    printf("\n");
+    bf_printHead(env);
+    printf("\n");
+
+    if (env->step_mode)  // 不修改实际值
+        printf("step mode on\n");
+    else
+        printf("step mode off\n");
+
+    if (env->information_mode)  // 不修改实际值
+        printf("information mode on\n");
+    else
+        printf("information mode off\n");
+}
+
+bf_STEP_FUNC bf_setEnvStepFunc(bf_env *env, bf_STEP_FUNC step_func) {
+    bf_STEP_FUNC bak = env->step_func;
+    env->step_func = step_func;
+    return bak;
+}
+
+#define bf_setEnvModeFunc(name) bool bf_setEnv##name##Mode(bf_env *env, int mode) { \
+switch (mode) { \
+case 1:env->name##_mode = true;break; \
+case 0:env->name##_mode = false;break; \
+case -1:env->name##_mode = !env->name##_mode;break; \
+default:break; \
+} \
+return env->name##_mode; \
+}
+
+bf_setEnvModeFunc(step)
+bf_setEnvModeFunc(information)