用 C++ 从零写一台海龟绘图脚本语言解释器——词法分析、语法分析、抽象语法树、解释执行、海龟画布,一条流水线全打通。你写的程序,能让海龟在屏幕上画出正方形、六边形、螺旋,甚至一棵分形树。
* * * *
* ** ** *
*** ***
* ** ** *
* * * *
* * * *
***** * * *****
** * * **
*** ***
* *
* *
* *
* *
*选了例子的源码,点「运行」——海龟真的会走起来、画出图形。这就是这台解释器的核心魅力:写下指令,立刻看见效果。
说明:这里的画布是同一套解释器语义的前端复刻(按屏幕像素缩放),方便在网页里即时看效果;真实程序是命令行程序,输出是终端字符画——见下面「跑起来」。
从「一段文字」到「一幅画」,源码经过 5 个阶段。每一段都是可单独实现的模块,这正是本项目适合当 C++ 实战练习的原因。
把源码字符串逐字符切成带类型的 Token。
main.cpp / token.hpp / lexer.cpp递归下降,把 Token 拼成抽象语法树。
ast.hpp / parser.cpp程序的「结构」——循环、条件、过程调用各就各位。
ast.hpp(11 个结点结构)遍历 AST、查符号表、递归求值,驱动海龟。
interpreter.cpp海龟按指令前进转弯,Bresenham 把线画到画布。
turtle.cpp / canvas.cpp核心思想:程序 = 给海龟下指令。命令之间用空格或换行分隔,没有分号;花括号 { } 表示一段。
| 语法 | 含义 | 写法 | 示例 |
|---|---|---|---|
fd / bk | 前进 / 后退 | fd 距离 | fd 100 |
lt / rt | 左转 / 右转 | rt 角度 | rt 90 |
pu / pd | 抬笔 / 落笔 | pu | pd |
let | 存一个数到名字 | let 名 = 表达式 | let side = 30 |
repeat | 重复一段指令 | repeat 次数 { ... } | repeat 4 { ... } |
if / else | 条件分支 | if 条件 { ... } | if len > 4 { ... } |
to | 定义过程(子程序) | to 名 参数 { ... } | to branch len { ... } |
# | 注释 | # 说明 | # 一行注释 |
| 约定 | 说明 | 举例 | 注意 |
|---|---|---|---|
| 初始状态 | 海龟在画布中心、面朝上,笔是落下的 | home 即回到此状态 | 不是朝右、也不在角落 |
| 转向 | rt 顺时针、lt 逆时针;角度累加不回绕 | rt 360 / n | 画正 n 边形就用这个角 |
| 数字 | 只有一种数值:小数(浮点) | 360 / 6 → 60 | 没有整数类型,也没有取模 % |
| 真值 | 条件判断「非 0 即真」;比较运算返回 1 / 0 | if len > 4 { … } | 比较符 == != < > <= >= |
| 表达式 | + - * / 与括号,优先级同数学 | (i + 1) * 0.4 | 括号可以直接写 |
| 变量 | let 名 = 表达式;同名再写一次就是改值 | let i = i + 1 | 过程里新声明的是局部变量 |
| 过程 | to 名 参数 { … } 定义;调用按形参个数取参 | branch len * 0.72 | 过程体用 { } 收尾,支持递归 |
| 颜色 | color "名字";字符画布用字母表示 | color "red" → R | red / green / blue / yellow / cyan / magenta |
| 圆 | circle r 用 36 段折线近似 | circle 30 | 不是真圆,是正 36 边形 |
| 暂不支持 | 数组、字符串变量、函数返回值、布尔类型 | — | 字符串只在 color 里出现 |
从最简单的正方形,到用「过程 + 递归」画出的分形树——每个示例都验证解释器的一项核心能力。
MiniTurtle 是一个命令行程序:编译一次,两种玩法——跑内置的 4 个示例,或者跑你自己写的 .mt 程序。真实输出是终端字符画(就是首屏那种),不依赖任何图形库。
**
**
* *
* * **
* * ****
* * *** *
* *** *
*** * *
**** * *
*** * * *
** * *
*** * * *
**** * *
*** * *
* *** *
* * *** *
* * ****
* * **
* *
**
**
*.cpp + 7 个头文件)| 文件 | 职责 | 对应阶段 | 一句话 |
|---|---|---|---|
token.hpp | Token 的定义(类型 + 文本 + 数值 + 行号) | 阶段 2 | 「单词」长什么样 |
lexer.cpp | 词法分析:源码 → Token 序列(跳空白/注释、认数字/命令/标识符) | 阶段 2 | 把字切成词 |
ast.hpp | 抽象语法树结点(11 个结构) | 阶段 4 | 程序的结构 |
parser.cpp | 递归下降解析:Token → AST,含表达式优先级与过程参数个数表 | 阶段 4 | 把词拼成句 |
interpreter.cpp | 遍历 AST 执行:作用域栈、变量、循环、条件、过程调用 | 阶段 5–8 | 让程序跑起来 |
turtle.cpp | 海龟:位置 / 朝向 / 笔状态;把一次前进算成一条线段 | 阶段 5 | 海龟怎么走 |
canvas.cpp | 字符画布:Bresenham 画线 + 渲染到终端 | 阶段 1、5 | 怎么落到画布 |
main.cpp | 入口:演示模式(4 个示例)与文件模式(minturtle xxx.mt) | 阶段 0 | 从哪开始跑 |
其余 5 个头文件(lexer.hpp、parser.hpp、interpreter.hpp、turtle.hpp、canvas.hpp)只放类与函数声明——C++ 的「声明与实现分离」,编译命令里只需列出 6 个 .cpp。
把这台解释器完整带走:可以直接编译运行的源码工程 + 配套文档。
.cpp + 7 .hpp)+ star.mt + 编译运行说明登录并开通「OJ / C++」会员后,即可下载完整源码包与配套文档。
这几个 bug 不是编的,是开发这台解释器时真实踩到、又亲手修掉的——每个都是一堂扎实的编译原理课。
结构体继承了带虚析构的基类后,不再是可以花括号初始化的聚合类型,编译直接报错。
修复 → 给需要传参的结点补上构造函数(其余用默认构造 + 字段赋值)解析器在等 end 关键字,但代码用 } 收尾,解析器静默吞掉分隔符一路越界到数组尽头。
{ } 收尾
pd branch 12 里,pd 误把 branch 当成了自己的参数。
每个阶段都有清晰可见的成果物——不断看到东西跑出来,正是这个项目适合当 C++ 学习实战的原因。
搭出能编译运行的工程;先有"能画图的画布"。
→ 终端画出字符线源码切成 Token;递归下降算出 2+3*4=14。
→ 打印 Token / 算出表达式Token 拼成 AST;程序真正跑起来驱动海龟。
→ 画出第一个正方形语言会存储、重复、分支决策,画出可配置图形。
→ 多边形 / 螺旋递归在视觉上开花,画出经典分形。
→ 科赫雪花 / 分形树报错定位到行;字符画布可换成彩色窗口。
→ 更健壮、更好看| 单元 | 主题 | 对应开发阶段 | 拿到什么 |
|---|---|---|---|
| 单元 0 | 会用:认识语言 + 搭好环境 | 阶段 0 | 跑通 4 个示例、画出自己的五角星 |
| 单元 1 | 词法分析:源码 → Token | 阶段 2 | 能打印程序的 Token 序列 |
| 单元 2 | 语法分析 + 表达式 + AST | 阶段 3–4 | 解析器跑通、能算表达式 |
| 单元 3 | 解释执行 + 海龟绘图 + 画布 | 阶段 1、5 | 第一次画出正方形 |
| 单元 4 | 变量 / 循环 / 条件 | 阶段 6–7 | 可配置的多边形、螺旋 |
| 单元 5 | 过程与递归:分形(高光时刻) | 阶段 8 | 递归分形树 / 科赫雪花 |
| 单元 6 | 错误处理 + 创造扩展 | 阶段 9 | 报错定位 + 自创图形 / 新命令 |
四级目标:会用(读懂并写出程序)→ 懂原理(说清「源码怎么变成一幅画」)→ 能实现(独立写出 lexer / parser / interpreter)→ 能创造(扩展语言、创作复杂作品)。本项目的核心目标落在「能实现」。