简介《自制编程语言》学习资料以PDF文档形式提供压缩包内仅1个文件整体大小2.38MB便于离线阅读和随身查阅。这份材料围绕编程语言的设计、实现与使用展开既讲清了语法、语义等设计层面应关注的表达能力、运行效率和可维护性也介绍了借助词法分析工具与语法分析工具生成自定义语言解析器的常见路线。内容以自制语言实例为线索从基础计算器逐步过渡到具备完整词法、语法处理能力的脚本语言展示了在Windows环境下配置编译器工具集、以及在不同操作系统上编译运行的实用方法。同时整理了语言实现中的运行时环境、资源管理、编码风格与命名惯例等细节能够帮助读者建立从零构建编程语言的整体认知。目前已有870人学习下载适合编程语言爱好者、编译原理初学者以及希望动手实践解释器或编译器的开发者作为阅读《自制编程语言》一书时的配套参考资料。1. 自制编程语言从入门到折腾这份资料能帮你少走一半弯路自制编程语言听起来像硬核课题真的动手后你会发现卡住你的往往不是语法树和语义分析而是环境装不上、make 报错、文件换行符不一致这类杂事。这套资料把自制编程语言相关的源码版本和构建笔记收在了一起从计算器 calc到 yacc/lex 示例 mycalc到手写解析器 llparser再到完整的脚本语言 crowbar 0.1~0.4 和编译型语言 Diksam 0.1~0.4每个版本都带 Linux 的 make.sh 和 Windows 的构建说明。它适合三类人学过编译原理但没见过完整实现的在校生、想给业务写 DSL 的工程师、以及买了书却卡在环境搭建上的读者。资料里还收集了大量真实用户报错与解法这些翻车现场比源码本身更能帮你排雷。2. 资源全景从 calc 到 Diksam四层递进的学习路径2.1 目录结构每个子项目对应哪一阶段能力先别急着 make把这套资源的目录结构摸清楚比什么都重要。资料里排了一条明确的难度递进线我建议按顺序走而不是随手点开一个文件夹就开始编译。整份资料的骨架如下目录名定位覆盖知识点calc极简计算器示例词法分析的最初形态适合热身mycalc / mycalc_exyacc/lex 版计算器.y 文法文件与 .l 词法文件的配合llparser / llparser_ex手写自顶向下解析器LL 递归下降不依赖生成器crowbar_book_0_1 ~ 0_4脚本语言解释器语法树求值、GC0.2 起、正则支持0.4 起diksam_book_0_1 ~ 0_4编译型语言字节码生成、虚拟机、静态作用域每个子项目都对应一个明确的阶段目标。calc 是热身让你理解字符串怎么变成 token 序列mycalc 是第一次接触 yacc/lex你会发现文法写起来比手写解析器快得多但生成出来的代码像黑匣子出问题时很难下手。llparser 则完全反过来纯手写递归下降逼你把每一个终结符和非终结符之间的调用关系看清楚——LL 这个名字的含义就是从左到右扫描、推导时始终展开最左侧的非终结符。到了 crowbar才算进入真正的语言实现词法、语法、语义、求值、内建函数、字符串池、垃圾回收一个不少而且每一代版本都在补前一代的短板。2.2 选型逻辑为什么先用 yacc/lex再手写递归下降很多人一上来就问到底用 yacc/lex 还是手写——这两种路线在这套资料里都有而且顺序是刻意安排的先用生成器再手写。原因很简单yacc/lexGNU 环境对应 bison/flex能让你在半天内把一门小语言跑起来先建立语法驱动的直觉但生成器产出的 y.tab.c 不是给人读的一旦在文法动作里加了复杂的语义处理调试就变成体力活。这时候再去看 llparser 的手写版本你会突然理解左递归为什么要消除、为什么要向前看一个 token、为什么某些冲突只能靠改写文法而不是调优先级。以 mycalc 为例手把手过一遍构建顺序# mycalc 的构建顺序先 bison 后 flex最后统一编译 bison --yacc -dv mycalc.y # 生成 y.tab.c / y.tab.h / y.output flex mycalc.l # 生成 lex.yy.c gcc -o mycalc y.tab.c lex.yy.c -lfl这里的顺序是硬约束flex 生成的 lex.yy.c 里通常会#include y.tab.h所以 bison 必须先跑。--yacc让 bison 以兼容旧 yacc 的方式工作输出文件名保持 y.tab.c 和 y.tab.h-d表示生成头文件-v额外输出 y.output里面是完整的 LR 状态机排查 shift/reduce 冲突时非常有用。最后的-lfl链接 flex 的库提供 yylex 的默认辅助实现。如果 bison 的命令少了-d后面 gcc 阶段就会报y.tab.h: No such file or directory——这是整套资料里被问过最多的问题之一第 5 章还会专门展开。2.3 构建脚本分工make.sh 与 make.bat 各自管什么从 crowbar 开始每个版本目录里都放了构建入口Linux 下是 make.shWindows 下是 make.bat。但这两个脚本并不等价Windows 的 make.bat 本质上是把 make 命令包装一层真正干活的是 Makefile而 Makefile 在 Windows 上不能用微软的 nmake必须用 GNU make 系工具也就是 MinGW 带的 mingw32-make.exe或者 Cygwin 里的 make。# crowbar_book_0_1 的 make.sh 内容大致如此 #!/bin/sh make # 调用系统 make读取当前目录下的 Makefile注意这里有个高频坑Makefile 内部某些子目录比如 memory的构建规则直接写的是gmake.exe而不是make。也就是说你在 Windows 的 cmd 里跑 mingw32-make.exe跑到一半它去找 gmake.exe找不到就报No targets specified and no makefile found或直接中断。常见做法是把 D:\MinGW\bin\mingw32-make.exe 复制一份改名为 gmake.exe再把这个目录加进 PATH两条腿就都站稳了。提示Makefile 里写死的 gmake.exe 是 Windows 构建最常见的隐形依赖先确认这个文件存在再跑 make。这套资源的版本演进本身就值得单独说一句。crowbar 0.1 是能跑基本运算和流程控制的解释器0.2 引入垃圾回收0.3 补数组等内建能力0.4 开始依赖 oniguruma 正则库。Diksam 走了另一条路0.1 和 0.2 只有编译器本体你需要先用系统工具链把编译器构建出来再拿编译器去处理 Diksam 程序到 0.3 和 0.4 才补上了 main 入口和虚拟机可以直接运行 .dkm 文件。这个先编译器、后运行时的演进顺序就是一门编译型语言最真实的诞生路径也是我推荐你把每一版都编译一遍的原因——版本与版本之间的 diff 就是最好的教材。源码里到处是 hoge、foobar 这类占位名在日语开发者语境里 hoge 相当于我们说的 foo看到别觉得奇怪。3. 环境搭建MinGW 与 Cygwin 两条路线怎么选3.1 MinGW 路线组件安装与 PATH 配置Windows 用户最常见的选择是 MinGW。它把 GCC 工具链搬到了 Windows 上不用像 Cygwin 那样带一个 POSIX 兼容层生成的 exe 也不依赖额外的 DLL。安装分四步走从 MinGW 官网下载 mingw-get-setup.exe运行后保持默认组件安装目录选一个不含空格的路径比如 D:\MinGW这是整份资料里最重要的一个决定打开 MinGW Installation Manager勾选 mingw32-base 和 mingw32-make标记后点 Installation → Apply把 D:\MinGW\bin 加进系统 PATH然后重新开一个终端。# 验证工具链是否就位 gcc --version mingw32-make --version两条命令能正常输出版本号说明 PATH 配置成功。这里要特别提醒MinGW 默认不提供 bison 和 flex这两个生成器需要单独从 GnuWin32 项目获取或者直接切到 Cygwin 方案。所以我的建议是只打算跑 calc、mycalc 和 llparser 的话MinGW 加 GnuWin32 够用一旦要碰 crowbar 和 Diksambison/flex 的版本依赖会让你重新考虑 Cygwin。注意MinGW 与 Cygwin 的 gcc 是两套独立工具链不要混着调用。同一个源文件用两边各编译一次没问题但链接阶段交叉使用会带来莫名其妙的符号错误。还有一点容易被忽略如果机器上之前装过其他版本的 GCC注意 PATH 顺序。cmd 里执行gcc --version显示的路径是哪一版make 调用的就是哪一版先后顺序会直接影响链接结果。我之前就遇到过系统里两个 gcc 并存crowbar 编译到一半链接器崩溃最后把旧版从 PATH 去掉才消停。3.2 Cygwin 路线bison、m4 与 make 的依赖链Cygwin 本质上是在 Windows 上模拟 Linux API 的环境所有 exe 都依赖 cygwin.dll换来的是和 Linux 几乎一致的命令行体验。安装时选 32 位或 64 位的 setup 程序一路 Install from Internet到选择软件包那一步需要手动勾选Interpreters 分类下的 bison、Devel 分类下的 make以及 bison 运行时依赖的 m4。# Cygwin 终端里验证依赖链 which bison m4 make bison --version为什么 bison 依赖 m4因为 bison 生成解析器时要调用 m4 做宏展开m4 不在 PATH 里bison 会直接报m4: cannot open ...。这个依赖链文档里写得很含蓄实际踩坑的人一抓一大把。我一般建议 Windows 用户直接选 Cygwin 路线因为 crowbar 0.4 的正则依赖 oniguruma用 Cygwin 的 bash 跑 configure 和 make比在 cmd 里折腾 MinGW 省心得多。对比项MinGWCygwinexe 运行时依赖无额外 DLL依赖 cygwin.dllbison/flex 获取需另装 GnuWin32包管理器直接选与 Linux 行为一致性一般高适合阶段mycalc / llparsercrowbar / DiksamCygwin 的 bash 环境里跑./configure几乎不会遇到路径问题因为整个根目录结构都是模拟好的。但代价是生成的 crowbar.exe 或 diksam.exe 运行时需要 cygwin.dll 在旁边换到没有 Cygwin 的机器上就起不来。如果只是学习用途这个缺点无所谓要是想长期保留一个可执行的解释器MinGW 版反而更适合分发。3.3 onigurumacrowbar 0.4 绕不开的正则库crowbar 0.4 新增了正则表达式支持底层用的是 oniguruma 5.9.4。Linux 上编译这个库是标准四连# Linux 下编译 oniguruma 5.9.4 tar -xvf onig-5.9.4.tar.gz cd onig-5.9.4 ./configure make make installconfigure 负责检测编译器特性和平台差异生成 Makefile、onig-config 和 config.hmake 把库文件编译出来make install 把头文件装到 /usr/local/include把库装到 /usr/local/lib。安装完成后确认链接器能发现它ldconfig -p | grep onig有输出就说明库已经注册。Windows MinGW 环境下直接执行 make install 十有八九会中途失败报错一般是ranlib: /usr/local/lib/libonig.a: No such file。这是因为 MinGW 的 shell 环境对路径的处理和 Linux 不一致安装脚本执行到一半就断了。我当时是进 Cygwin bash 把 configure 和 make 跑完然后手工处理 .libs/libonig.a复制到 MinGW 能找到的目录后 ranlib 一次再回去 make crowbar 才能链接通过。这一步确实有点玄学但只要抓住两个核心——库文件必须真实存在、归档索引必须 ranlib 过——就不会乱。顺带提醒一句oniguruma 的版本要和对上。资料锁的是 5.9.4后续大版本调整过内部 API直接拿新版本源码来顶替crowbar 的正则相关代码很可能编译不过。遇到这类报错先看版本再怀疑自己改错了代码。4. 构建与运行把 crowbar 和 Diksam 跑起来的完整步骤4.1 Linux 侧make.sh 执行与行尾符的坑Linux 下构建 crowbar 最顺利但藏着一个最隐蔽的坑。第一次跑cd crowbar_book_0_1 ./make.shmake.sh 调用 make 读取 Makefile依次编译 lex.yy.c、y.tab.c、main.c、interface.c、create.c、execute.c、eval.c、string.c、string_pool.c、util.c、native.c、error.c、error_message.c最后进入 memory 子目录构建内存管理模块。这一长串 .c 文件就是一棵完整的解释器骨架main 是入口interface 负责和外部环境交互create 构建语法树节点execute 执行语句eval 做表达式求值string_pool 管理字符串常量native 注册内建函数。文件职责lex.yy.c / y.tab.c词法与语法分析bison/flex 生成create.c语法树节点构建execute.c / eval.c语句执行与表达式求值memory/内存管理与 GC 基础留意 Makefile 里的编译参数里面带着-Wall -Wswitch-enum -ansi -pedantic -DDEBUG意思是打开全部告警、对 switch 语句做枚举完整性检查、按 ANSI C 严格模式编译并开启调试宏。这套参数本身就是在教你写可移植的 C 代码——如果你自己改源码尽量别破坏这些约束否则行为可能悄悄变化。编译没有任何输出到终端通常是好事Unix 工具链的哲学是没有消息就是好消息。接着用测试脚本验证./crowbar test/test.crb如果输出行 5: (0x0d)这种报错不要怀疑代码逻辑这是文件行尾符问题压缩包里的 .crb 文件是 Windows 的 CRLF 换行Linux 解释器把行尾的回车符\r0x0d当成了 token。解决办法是统一转成 LFdos2unix test/test.crb # 没有 dos2unix 时用 sed sed -i s/\r$// test/test.crb这条血泪经验让我养成一个习惯拿到压缩包先file test/test.crb确认行尾符而不是等解释器报错了再回头查。llparser 目录、Diksam 的 .dkm 测试文件都可能有同样问题一次转干净。4.2 Windows 侧mingw32-make 与 gmake 的区别Windows 下构建要分清三个 makenmake 是微软家的MinGW 自带 mingw32-make.exeCygwin 里的是 make。这套资源的 Makefile 按 GNU make 语法写的nmake 直接排除不要在它身上浪费时间。# cmd 下用 MinGW 构建 crowbar_book_0_1 cd C:\win_sjis\crowbar_book_0_1 D:\MinGW\bin\mingw32-make.exe如果报process_begin: CreateProcess(NULL, bison ...) failed是 bison 不在 PATH回第 3 章检查如果报No targets specified and no makefile found多半是当前目录不对或者 Makefile 被改名。最头疼的是 Makefile 内部调用 gmake.exe 的情况memory 子目录就这么干过# 给 mingw32-make 起个别名满足内部对 gmake.exe 的调用 copy D:\MinGW\bin\mingw32-make.exe D:\MinGW\bin\gmake.exe改名后重新执行多数子目录能一次过。如果 make 提示crowbar is up to date那是它误判了增量依赖把中间产物删掉重来del y.tab.c lex.yy.c *.o然后从头跑。我在 Windows 上的标准流程是先手工把 bison/flex 生成的中间文件跑一遍参考 2.2 的顺序确认生成没问题再让 make 接管这样能把生成器失败和make 依赖错误两类问题分开排查效率高很多。4.3 Diksam 的构建与运行先编译器后虚拟机Diksam 和 crowbar 的构建逻辑完全不同。diksam_book_0_1 和 0_2 里只有编译器没有直接运行的入口你需要先构建出编译器再用编译器处理 .dkm 源码。到 0_3 和 0_4 才补上 main 和虚拟机运行方式变成一条命令cd diksam_book_0_3 ./make.sh ./diksam test/test.dkm这里的 ./diksam 是虚拟机主程序它读入 .dkm 文件逐条解释执行字节码——注意Diksam 的编译产物不是机器码而是字节码由内置的 DVMDiksam Virtual Machine执行。这个编译前端 字节码虚拟机的双层结构比单纯解释器高了一个维度也是把编译器与运行时分离的典型范例。你可以在 test 目录里放自己写的 .dkm 文件比如定义一个函数、声明局部变量、做字符串拼接运行后观察输出是否符合预期这就是最直接的验证手段。跑通 0_3 之后建议回头把 0_1 和 0_2 的编译链路也走一遍因为早期版本的源码里有完整的前端实现词法、语法、语义检查和代码生成。等你把 0_1 的编译器构建出来再用它去编译 0_2 的源码就理解了什么叫逐步升级的构建策略。我第一次跑通 Diksam 时最大的感受是一个能编译自身语言特性的编译器和一门能跑测试脚本的解释器复杂度不在一个量级。5. 避坑手册五个高频报错与排查路径5.1 bison 命令找不到CreateProcess 失败与 PATH 检查现象Windows 下执行 mingw32-make立刻报process_begin: CreateProcess(NULL, bison --yacc -dv crowbar.y, ...) failed. make (e2): 系统找不到指定的文件。原因Makefile 规则里调用了 bison但系统 PATH 里没有这个可执行文件。注意报错发生在 CreateProcess 阶段说明 make 根本没找到程序而不是 bison 运行出错。解决装好 bison 后确认 PATH 里能看到它where bison没有输出就把 bison 所在目录加进 PATH然后新开终端。改完 PATH 必须重新开窗口旧终端不会刷新环境变量。我早期在这上面反复翻车后来固定用where bison flex m4 make一条命令把四个工具一次看清省了很多冤枉路。5.2 m4 路径带空格Program Files 引发的连锁反应现象bison 能跑但立刻报m4: cannot open Files: No such file or directory或者m4: cannot open D:\Program Files (x86)\...被截断成m4: cannot open D:\Program。原因GnuWin32 的 bison 被装进了带空格的路径bison 内部调用 m4 时参数按空格切分路径被截断。这个问题的根源是安装路径不是 bison 本身。解决重装 GnuWin32安装目录改成 C:\GnuWin32 这样的无空格路径已经在 Program Files 下的卸载重来。这个教训直接决定了第 3 章里我反复强调安装目录不要有空格——路径空格是 Windows 上 C 工具链一系列诡异报错的总根源从 bison 到 make 到链接器都会犯。5.3 y.tab.c 缺失生成顺序被打断现象执行 make 后 gcc 报gcc: y.tab.c: No such file or directory随后 flex 那边报mycalc.l:3:19: error: y.tab.h: No such file or directory。原因bison 和 flex 的中间产物还没生成gcc 就去找它们。正常顺序是 bison 先生成 y.tab.c 和 y.tab.hflex 再生成 lex.yy.c最后 gcc 一起编译。bison 一旦失败见 5.1后面全断。解决不要直接跑 make先把生成器环节手工执行一遍bison --yacc -dv crowbar.y flex crowbar.l ls -l y.tab.c y.tab.h lex.yy.c三个文件都在再回到 make。这套中间文件手工生成一遍的做法是排查一切 make 依赖问题的通用钥匙尤其适合你后来自己修改文法的场景——先确认生成器本身能过再谈别的。5.4 Linux 报错 0x0dCRLF 行尾符在咬人现象Linux 下 ./crowbar test/test.crb 报5: (0x0d)肉眼看不到异常字符但解释器在第 5 行附近报语法错误。原因压缩包里 Windows 编辑过的 .crb 文件带\r字符在 Linux 下被词法分析器当成 token。llparser 和 Diksam 测试文件也有同样隐患Mac 用户一样会遇到。解决统一换行符再跑dos2unix test/test.crb # 或者直接批量处理 find . -name *.crb -o -name *.dkm | xargs dos2unix转完再跑就干净。我拿到压缩包第一件事就是批量转一遍免得跑到一半发现下一个文件还是 CRLF。这个坑在 git 换行符转换配置不当的项目里也经常复发属于纯环境问题跟代码逻辑无关。5.5 链接期 -lmsvcp60跨平台库错位现象Linux 下编译某个版本时ld 报/usr/bin/ld: cannot find -lmsvcp60。原因Makefile 里写了 Windows 特有的运行时库 msvcp60链接参数被原样带到了 Linux 的 ld 上。常见于直接把 Windows 版源码拿到 Linux 下编译Makefile 没做平台判断。解决去掉这一项或按平台条件分支# Makefile 片段按平台决定链接库 ifeq ($(OS),Windows_NT) LIBS -lmsvcp60 else LIBS endif类似还有 oniguruma 的链接路径问题Linux 下确认-lonig能找到库找不到就回到 3.3 节重装一次。跨平台构建的本质是库和参数对不上排查时先make -n看实际执行的链接命令别靠猜。6. 进阶用法给 Diksam 搭回归测试验证每次语法改动crowbar 和 Diksam 都跑通之后真正的进阶不是再读一遍源码而是把改代码—验证—回归这件事变成机械流程。Diksam 和 crowbar 的分水岭在于作用域处理crowbar 是 AST 遍历解释器运行时边遍历边求值Diksam 在编译期就把变量绑定关系确定下来输出字节码交给 DVM 执行。也就是说你在 test 目录下写的每个 .dkm 程序都同时验证了词法、语法、语义分析和代码生成四条链路。我的做法是给 Diksam 搭一个极简回归脚本#!/bin/bash # regression.sh - 每次改完语法后批量回归 for f in test/*.dkm; do out$(./diksam $f 21) exp$(cat ${f%.dkm}.out 2/dev/null) if [ $out ! $exp ]; then echo FAIL: $f echo --- got ---; echo $out echo --- want ---; echo $exp else echo PASS: $f fi done脚本的逻辑很简单每个 .dkm 对应一个 .out 文件里面是该程序的期望输出运行结果做 diff只要不一致就打印失败。${f%.dkm}是 bash 的变量截断用来把后缀 .dkm 替换成 .out。如果期望输出还没有第一次运行后把实际输出重定向保存一份之后它就是基线。建议把正常输出的 .out 文件也纳入这套资料的管理范围这样每天跑一遍所有的行为变化都有据可查。这个脚本的威力在改文法时特别明显。比如你给 Diksam 加了一个新的内建函数或者调整了运算符优先级跑一遍回归受影响的老测试用例立刻告诉你哪里语义变了而不是等你手工敲命令试出来。从那以后我每次改完词法或文法都会强制走一遍回归脚本连 crowbar 的老测试用例也会一起重跑确认解释器和编译器两条线都没被改坏。这套资料从源码到踩坑记录都打包齐全下载后按第 2 章的目录顺序走一遍就能跑通希望这个回归习惯也能帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?