Flipper Zero 上的 Brainfuck 解释器与编辑器:FlipperZeroBrainfuck 源码解析与实战指南
2026/9/14 6:47:52 网站建设 项目流程

Flipper Zero 上的 Brainfuck 解释器与编辑器:FlipperZeroBrainfuck 源码解析与实战指南

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

FlipperZeroBrainfuck 是一个运行在 Flipper Zero 上的 Brainfuck 语言解释器与代码编辑器,它允许用户在设备上直接编写、保存并运行.b格式的 Brainfuck 脚本,支持文本输入(,指令)与文本输出(.指令)。本文将以 关联文档 为主体,结合本仓库中的完整源码,讲解该应用的场景架构、虚拟按键编辑器布局、解释器底层实现、缓冲区边界与已知限制,帮助你理解其工作原理并快速上手使用。

项目定位与源码布局

FlipperZeroBrainfuck 位于本仓库 Applications/Official/source-OLDER/xMasterX/brainfuck 目录下,属于旧版官方应用源码归档(source-OLDER)中的外部应用(EXTERNAL,即 FAP 插件)。源码由一个非常精简的 C 工程构成:

  • brainfuck.c:应用入口与资源分配/释放;
  • brainfuck_i.h:核心数据结构与全部缓冲区常量定义;
  • worker.c:Brainfuck 解释器本体(8 条指令的执行逻辑);
  • views/bf_dev_env.c:设备上的代码编辑环境(虚拟键盘视图);
  • scenes/:基于场景管理器(Scene Manager)的 6 个界面场景;
  • application.fam:FAP 应用构建元数据。

值得注意的细节来自 brainfuck.c 顶部的注释:作者坦言该应用是"复制 picopass 应用骨架、掏空内部后用其躯壳搭建"出来的,这解释了为什么代码中出现了brainfuck_alloc()brainfuck_free()等与经典 Flipper 应用模板一致的资源管理模式。

应用元数据与构建方式

application.fam 完整声明了应用的构建信息:

字段说明
appidBrainfuck应用唯一标识
apptypeFlipperAppType.EXTERNAL外部插件(编译为 .fap 文件)
entry_pointbrainfuck_app入口函数,见 brainfuck.c
requiresstorage,gui依赖 SD 卡存储与 GUI 子系统
stack_size8 * 1024(8 KB)任务栈大小
fap_iconbfico.png应用图标
fap_categoryMisc_Extra在应用菜单中的分类
fap_icon_assetsicons附带图标资源目录

由于它是 EXTERNAL 类型应用,你可以像构建其他 FAP 插件一样,使用 Flipper 固件的 fbt/ufbt 构建链将其编译为.fap插件文件,再拷贝到 Flipper Zero 的 SD 卡上运行。

应用启动与整体架构

入口函数brainfuck_app()的流程(brainfuck.c)如下:

  1. 调用brainfuck_alloc()分配BFApp结构体,初始化 ViewDispatcher 与 SceneManager,注册全部视图(Submenu、Popup、TextInput、TextBox、开发环境视图);
  2. 打开RECORD_GUIRECORD_NOTIFICATION系统服务记录;
  3. 打开RECORD_STORAGE,调用storage_simply_mkdir(storage, "/ext/brainfuck")确保/ext/brainfuck目录存在——这是所有脚本文件的存放目录;
  4. 切换到起始场景并进入view_dispatcher_run()主循环。

BFApp结构体(brainfuck_i.h)中与解释执行直接相关的成员是:

int dataSize; char dataBuffer[BF_INST_BUFFER_SIZE]; // 指令缓冲区,2048 字节 char inputBuffer[BF_INPUT_BUFFER_SIZE]; // 输入缓冲区,64 字节

应用的全部界面由 brainfuck_scene_config.h 声明的 6 个场景驱动,其跳转关系与职责如下:

场景文件名职责
Startbrainfuck_scene_start.c主菜单:New / Open / About
FileCreatebrainfuck_scene_file_create.c输入新脚本名并创建文件
FileSelectbrainfuck_scene_file_select.c通过系统文件浏览器选择.b脚本
DevEnvbrainfuck_scene_dev.c开发环境:编辑、输入、运行、保存
ExecEnvbrainfuck_scene_exec.c执行结果展示(TextBox 视图)
SetInputbrainfuck_scene_set_input.c编辑,指令的输入缓冲区

文件系统约定:/ext/brainfuck 与 .b 脚本

脚本统一存放在 SD 卡/ext/brainfuck目录下,扩展名为.b

  • 新建脚本(brainfuck_scene_file_create.c):进入 TextInput 场景,输入脚本名后拼接为/ext/brainfuck/<name>.b,先删除旧文件再以FSAM_WRITE + FSOM_CREATE_ALWAYS写入一个空字节,随后直接进入开发环境;
  • 打开脚本(brainfuck_scene_file_select.c):使用系统DialogsApp文件浏览器,browser_options限定扩展名.b、基础路径为/ext/brainfuck且不隐藏扩展名;
  • 加载与清洗(views/bf_dev_env.c):进入开发环境时清空指令缓冲区、以只读方式打开文件读入缓冲区,然后用strchr(bfChars, ...)把所有非 Brainfuck 指令字符(换行、注释等)统一替换为下划线_,再定位缓冲区末尾作为编辑起点。这意味着注释与换行会被静默清洗掉,加载后缓冲区中只剩 8 个有效指令字符与下划线占位。

开发环境(Dev Environment):12 键虚拟键盘

开发环境是 views/bf_dev_env.c 实现的自绘视图,屏幕顶部用 3 行 × 24 字符的文本框显示当前代码(超出 72 字符时只显示末尾 72 字符,即dbOffset偏移逻辑),下方是两组虚拟按键:

第一排:8 个指令按键(屏幕坐标 x = 1, 17, 33, 49, 65, 81, 97, 113,y = 36)

按键序号字符含义
0+当前单元格加 1
1-当前单元格减 1
2<数据指针左移
3>数据指针右移
4[循环开始
5]循环结束
6.输出当前单元格字节
7,从输入缓冲区读一个字节

第二排:4 个功能按键(使用 icons 目录下的图标绘制,y = 52)

按键序号功能触发行为
8Backspace删除缓冲区最后一个字符并置 0(bf_dev_env.c)
9Input切换到 SetInput 场景编辑输入缓冲区
10Run启动RUNNING...倒计时后执行解释器
11Save删除旧文件并以FSAM_WRITE + FSOM_CREATE_ALWAYS写回当前缓冲区

按键间的上下左右跳转由buttonMappings[12]表驱动(bf_dev_env.c),结构为{up, down, left, right}。从源码可归纳出导航规律:4 个功能键的上下方向分别映射到其正上方的 2 个指令键(如 Backspace 上下对应+/-,Input 上下对应</>,Run 上下对应[/],Save 上下对应./,),左右方向则在同一排内循环移动。所有指令按键在dataSize < BF_INST_BUFFER_SIZE时向dataBuffer尾部追加对应字符,实现连续输入。

解释器核心实现:worker.c 逐指令拆解

解释器主体在 worker.c,采用全局状态机,运行状态status三态:0空闲、1运行中、2失败(worker.c)。数据模型如下:

  • 指令流inst指向app->dataBufferinstCount为指令数,instPtr为当前指令指针;
  • 数据栈bfStackuint8_t数组(一字节单元格,符合标准 Brainfuck 语义),初始容量BF_STACK_INITIAL_SIZE = 128字节,stackPtr为当前指针;
  • 输出缓冲区wOutput容量BF_OUTPUT_SIZE = 512字节;
  • 输入缓冲区wInput指向app->inputBuffer,最多 64 字节。

主循环beginWorker()(worker.c)从instPtr = 0开始,以指令流中的0x00作为程序结束标记,对 8 个有效字符执行 switch 分派,其余字符(如下划线)直接忽略。每执行一条指令instPtr++并做一次指针合法性校验。

各指令的执行逻辑与边界行为:

指令函数行为与边界
>rShift()stackPtr++;越界检查失败置status=2;当指针超过当前容量时以reallocBF_STACK_STEP_SIZE = 32字节扩容并清零新区域,同时更新stackSizeReal记录实际用到的最大深度
<lShift()stackPtr--stackPtr < 0时置status=2
+/-inc()/dec()当前单元格自增/自减,uint8_t天然支持 0xFF→0x00 的循环回绕
.print()将当前单元格字节写入输出缓冲区;wOutputPtr > BF_OUTPUT_SIZE - 1时回绕到 0(输出环形覆盖)
,input()从输入缓冲区取字节写入当前单元格;遇到0x00或指针到达 64 时输入指针回绕到 0(worker.c)
[loop()当前单元格为 0 时向后跳过到匹配的];不为 0 则继续顺序执行(进入循环体)
]endLoop()当前单元格不为 0 时向前回跳到匹配的[;为 0 则跳出循环

循环匹配(worker.c)通过计数嵌套括号实现:loop()向后扫描时遇[计数加 1、遇]计数减 1,直到计数归零即为匹配位置;endLoop()同理反向扫描。循环跳转与指令指针越界都会触发status = 2的失败终止。

所有操作统一经过runOpCount计数(可通过getOpCount()获取执行步数),且整个解释过程是同步执行的——从 bf_dev_env.c 的绘制回调可以看到,按 Run 后先绘制 3 帧RUNNING...,随后直接调用initWorker()+beginWorker(),把workerGetOutput()写入 TextBox 并切换到执行结果场景。正因为同步阻塞式执行,长时间运行的程序会卡住整个 UI——这正是 README 强调"程序必须终止"的根本原因。

关键常量汇总

brainfuck_i.h 定义了全部关键尺寸,是理解该应用能力边界的基础:

含义
BF_INST_BUFFER_SIZE2048指令缓冲区容量(单脚本最大字节数)
BF_OUTPUT_SIZE512输出缓冲区容量,超出后循环覆盖
BF_STACK_INITIAL_SIZE128数据栈初始容量(字节)
BF_STACK_STEP_SIZE32数据栈每次扩容步长
BF_INPUT_BUFFER_SIZE64,指令的输入缓冲区容量

已知限制与使用注意事项

原文档明确列出了该应用的三大限制,结合源码可进一步确认其成因:

  1. 程序必须终止,否则冻结在 "RUNNING":由于解释器在 UI 绘制回调中同步执行(见上文),一个死循环程序会无限占用主循环,界面停留在RUNNING...状态且无法响应按键。README 明确警告 "Major limitation is that programs MUST terminate, or it will freeze at RUNNING"。
  2. 对死循环无防护loop()/endLoop()没有任何循环次数上限或超时机制,[/]不匹配的语法错误同样不会在运行前被检测。
  3. 对语法错误无防护:虽然 views/bf_dev_env.c 会在加载文件时把非法字符替换为下划线,但括号配对、指针越界(<移出栈底、>扩容失败)等逻辑性错误只有在运行期才会通过status = 2暴露——越界会直接失败退出,而括号不匹配或死循环则可能造成冻结。

实操建议:编写脚本时保持括号成对、避免无界循环;若程序需要较长运行时间,请先在 PC 端模拟器验证逻辑后再上机。

实战:在设备上编写并运行第一个脚本

下面以一个在标准 Brainfuck 中广泛使用的经典示例("Hello World!",106 字节,远小于 2048 字节的指令缓冲区上限)演示完整流程:

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

操作步骤:

  1. 启动应用,在主菜单选择New,输入脚本名(如hello),应用会创建/ext/brainfuck/hello.b并进入开发环境;
  2. 在虚拟键盘上依次录入上述指令:先录入+>[]等指令键完成代码输入,期间可用 Backspace 删除误输入;
  3. Input键进入Edit input buffer界面,为,指令准备输入数据(本例无输入需求可留空);
  4. Run键,屏幕短暂显示RUNNING...后切换到执行结果视图,.,>等指令的输出会在 TextBox 中显示 "Hello World!";
  5. 如需保存代码,按Save键,屏幕会显示SAVED提示,代码写入/ext/brainfuck/hello.b

一个更简单的可验证示例是输出大写字母H(ASCII 72 = 9 × 8),利用嵌套循环将第二个单元格累加到 72 后输出:

+++++++++[>++++++++<-]>.

其执行轨迹为:先让单元格 0 从 0 累加到 9;进入循环后每轮把单元格 1 加 8 并把单元格 0 减 1;循环 9 轮后单元格 1 = 72,指针右移后用.输出字符H。你可以在 worker.c 中逐步对照验证这一过程。

相关资源索引

  • 应用自述文档:README.md
  • 入口与资源管理:brainfuck.c
  • 常量与数据结构定义:brainfuck_i.h
  • 解释器实现:worker.c
  • 编辑器视图实现:views/bf_dev_env.c
  • 场景注册表:scenes/brainfuck_scene_config.h
  • 构建元数据:application.fam

【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询