拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

自制编程语言源码包详解:从calc到Diksam的编译器成长路线

自制编程语言源码包详解:从calc到Diksam的编译器成长路线

简介:一份围绕《自制编程语言》整理的中文学习资料,以PDF文档形式打包,面向希望从零设计并实现编程语言的开发者、在校学生及编译技术爱好者。资料从语言的设计原则(语法、语义、可读性)入手,逐步延伸到 yacc/lex、bison/flex、MinGW 等工具链的配置与使用,完整覆盖词法分析、语法解析、代码生成到解释器/编译器落地,也涉及运行时环境与内存管理等实现要点。全篇以 Crowbar 和 Diksam 两个自制语言项目为主线,从简易计算器、mycalc 示例一路推进到带 GC(垃圾回收)的脚本引擎,包含 Crowbar 0.1 至 0.4、Diksam 0.1 至 0.4 的迭代演示,并给出各版本在 Linux 与 Windows 双平台下的编译运行思路,便于按章节边读边练。压缩包仅1个PDF文件,大小2.38MB,体积小巧;当前已有869人学习浏览,适合想通过具体项目理解编译原理的入门与进阶读者。

1. 自制编程语言的源码包:从 calc 到 Diksam 这条成长线怎么用

自学自制编程语言,最容易死在词法分析和语法分析这一步:龙书里的 LR 分析表还没看完,人已经没耐心了。这套《自制编程语言》相关资料走的是反方向——直接摊开一套从零长起来的源码,从 100 行不到的 calc 计算器,到 yacc/lex 生成的 mycalc,再到带 GC、数组、对象的解释器 crowbar,最后是编译成字节码的 Diksam。它解决的不是“编译原理怎么考”,而是“一门语言到底怎么实现”这个具体问题。适合谁:想弄懂 yacc/lex 生成器产物的人,想在 Windows/Linux 上把一个完整语言跑起来的人,以及手头正缺一份能拆开读的解释器工程的人。这篇笔记按目录结构、环境搭建、常见报错、验证方法四件事把它拆开讲。

2. 目录与工具链:从 yacc/lex 生成器看 mycalc 和 crowbar 的依赖关系

2.1 六个源码包的演进路线:calc、mycalc、llparser、crowbar、Diksam 各看什么

这套包不是“一个最终成品”,而是作者把学习过程本身打包进来了。你按目录顺序读,等于重新走一遍编译器的成长路线。我先把目录整理成一张对应表,后面再逐个说:

目录核心机制适合谁读
calc最简计算器,不依赖 yacc/lex第一次接触“语言雏形”的人
mycalc / mycalc_exyacc/lex 生成词法与语法分析器想弄清生成器产物的人
llparser / llparser_ex手写递归下降解析器想摆脱生成器、理解本质的人
crowbar_book_0_1 ~ 0_4完整脚本解释器,含 GC、数组、对象、正则想读完整解释器执行模型的人
diksam_book_0_1 ~ 0_4文本编译器 + 字节码 VM想读编译器完整流程的人

calc 是最小可运行语言,只有表达式求值,没有语法生成器,纯手写,适合当天下午跑通。mycalc 引入了 yacc/lex,这是第一道分水岭:你得理解.y文件描述语法、.l文件描述词法,然后由生成器帮你产出 C 代码。llparser 系列又往前走一步,作者把语法分析器手写出来,说明在生成器之外你还能用递归下降自己实现解析。到了 crowbar,才是真正意义上的脚本语言解释器,它有自己的执行循环、变量表、函数调用栈,还会做字符串和正则匹配。Diksam 则把编译期和运行期彻底分开,先编译成字节码,再用虚拟机执行,这已经是产品级架构的雏形。

2.2 生成器选型:为什么是 bison/flex,以及先 bison 后 flex 的硬顺序

yacc 和 lex 是 Unix 上古时代的经典组合,bison 和 flex 是它们的 GNU 实现,功能兼容但更活跃。这套源码里 Makefile 调用的就是 bison 和 flex,如果你机器上只装了老 yacc/lex,命令参数和产物文件名都会有差异。以 crowbar_book_0_1 为例,生成关系是这样的:

# 语法文件 crowbar.y 交给 bison crowbar.y -> bison --yacc -dv crowbar.y -> y.tab.c + y.tab.h # 词法文件 crowbar.l 交给 flex crowbar.l -> flex crowbar.l -> lex.yy.c # 生成产物和手写源码一起编译 y.tab.c + lex.yy.c + main.c + interface.c + execute.c ... -> crowbar

这里有个硬性顺序:必须先跑 bison,再跑 flex。原因是crowbar.l里#include "y.tab.h",而 y.tab.h 是 bison 生成的;如果不先执行 bison,flex 生成的 lex.yy.c 引用头文件时会直接报错。--yacc参数让 bison 兼容传统 yacc 行为,-d表示生成头文件,-v生成语法分析状态机的报告文件 y.output。这三个参数组合在一起,既保证行为一致,又方便你翻 y.output 看 LALR 状态迁移。作者在 Makefile 里把这些写成目标依赖,就是希望你别手动一条条敲。

2.3 oniguruma 依赖:crowbar 的正则支持是外挂库,版本锁定 5.9.4

crowbar 的源码里支持正则表达式字面量,比如/abc/这种写法,但正则匹配引擎不是自己实现的,而是链接 Oniguruma 这个正则库。源码包里不直接带 oniguruma 的编译产物,需要你单独下载onig-5.9.4.tar.gz并编译安装。版本号被锁定在 5.9.4,不是随便选一个新版就能保证行为一致,因为旧版本导出符号和头文件布局在后续版本里变过。我一般建议按作者指定的版本来,能少踩很多兼容坑。当年这个库托管在 geocities.jp 上,那个站点已经关停,现在找原版需要用镜像,这也是为什么很多人在复现这套源码时会卡在依赖这一步。后续第 4 章我会专门讲它在 Linux 和 Windows 下分别怎么装。

3. Windows 构建实战:MinGW、GnuWin32、Cygwin 三选一怎么配

3.1 先装 MinGW 工具链:gcc、mingw32-make、bison、flex 四件套

在 Windows 上编译这套源码,最主流的组合是 MinGW + GnuWin32 的 bison/flex。MinGW 提供 gcc 和 make,GnuWin32 提供 bison、flex、m4。装完第一件事不是急着 make,而是确认四个命令都在 PATH 里:

:: 在 cmd 里逐个检查,缺哪个补哪个 where gcc where mingw32-make where bison where flex :: 临时把工具目录加进 PATH,当前窗口生效 set PATH=D:\MinGW\bin;D:\GnuWin32\bin;%PATH%

MinGW 安装时通过mingw-get-setup.exe拉组件,我在 Windows 上一般勾 mingw32-base,里面带着 gcc 核心和 mingw32-make。make 命令在 MinGW 里默认叫mingw32-make.exe,不叫 make;为了省事,很多人会把它复制一份改名为make.exe,放进 MinGW 的 bin 目录。GnuWin32 的 bison 和 flex 安装包建议选 “Complete package, except sources”,只缺源码,二进制的 exe 和辅助 DLL 都会装好。装完后where bison能看到路径,才算合格。

3.2 构建 crowbar_book_0_1:从 make 到 crowbar.exe 的完整输出

工具链就绪后,进到源码目录执行构建,我习惯用全路径调用 make,避免 PATH 被别的环境变量污染:

cd C:\selflang\crowbar_book_0_1 D:\MinGW\bin\mingw32-make.exe

如果一切正常,你会看到 bison 先跑起来,生成 y.tab.c 和 y.tab.h,紧接着 flex 生成 lex.yy.c,然后 gcc 挨个编译 main.c、interface.c、execute.c、eval.c、string.c 等文件。Makefile 里还带-Wall -Wswitch-enum -ansi -pedantic这些严格告警选项,说明作者对自己代码的整洁度有要求。最终目录下出现 crowbar.exe,构建完成。验证运行很简单:

crowbar.exe test\test.crb

这一步容易翻车在换行符上,如果 test.crb 是从 Linux 环境拷过来的,Windows 下反而可能因为 LF 换行被词法分析器误解;反之 Linux 读 Windows 的 CRLF 也会报 0x0d 错误,这部分我放在第 5 章统一说。另外,构建过程中如果提示cd ./memory; gmake.exe失败,先检查 memory 子目录是否完整存在,再确认你用的 make 能正确处理子目录递归调用。打包下载的源码偶尔会漏子目录,这是第一个要排查的地方。

3.3 三种 Windows 环境的取舍:MinGW、GnuWin32、Cygwin 别混用

Windows 下能跑这套源码的环境不止一个,但各有脾气,我整理成一张对比表:

环境方案生成 exe 是否依赖 DLLbison/flex 来源适合做的事
MinGW + GnuWin32不需要,原生 Win32 exeGnuWin32 的 bison/flex产出可独立分发的 exe
Cygwin依赖 cygwin1.dllCygwin 包管理器里的 bison/m4/make模拟 Linux 的 configure 全流程
WSL 或原生 Linux依赖 Linux 运行库apt 安装 bison/flex直接走 make.sh,最省心

三者混用是最常见的坑。比如你 MinGW 和 Cygwin 都装了,在 cmd 里执行 make 时,系统可能先命中 Cygwin 的 make,导致链接时去找 Cygwin 的库,最后生成一个需要 cygwin1.dll 才能跑的 exe。我一般一套环境只留一个 make:用 MinGW 就只用mingw32-make.exe,用 Cygwin 就在 Cygwin Terminal 里跑,别在系统 PATH 里让两套工具打架。

4. Linux 构建实战:make.sh 编译 crowbar 和 oniguruma 的 configure 细节

4.1 Linux 上一条路走通:装 bison/flex,跑 make.sh,读 test.crb

Linux 下比 Windows 省心不是一点点。Debian/Ubuntu 系先把依赖装齐,再去编译:

# Debian/Ubuntu 系安装编译依赖 sudo apt install -y gcc make bison flex # 进入 crowbar 源码目录,执行作者提供的构建脚本 cd crowbar_book_0_1 ./make.sh

make.sh 本质是封装了 bison、flex、gcc 的完整调用序列,避免不同机器上 Makefile 缺目标导致意外。脚本内部会先执行 bison 生成 y.tab.c 和 y.tab.h,再执行 flex 生成 lex.yy.c,随后按依赖顺序编译各模块。编译完成后,用自带测试用例跑一遍:

# 运行解释器,执行 test 目录下的脚本文件 ./crowbar test/test.crb

如果在编译时遇到 bison 版本过新产生的告警,通常不影响产物;真正影响的是 test.crb 里的换行符,后面第 5 章会专门讲。

4.2 oniguruma 的 configure 流程:Linux 三步安装,Windows 要手动复制库文件

oniguruma 是整个依赖链里最需要耐心的部分。它在 Linux 下走的是标准 autotools 流程:

# 解压后进入源码目录 tar -xvf onig-5.9.4.tar.gz cd onig-5.9.4 # configure 会检查 alloca、memcmp、可变长度原型等底层能力 ./configure make sudo make install

configure 脚本会生成 config.h 和 Makefile,并根据当前环境决定启用哪些底层实现。make install默认把头文件和静态库装到/usr/local/lib和/usr/local/include,crowbar 链接时用-lonig去找。Windows 下如果走 Cygwin 做同样操作,make install很容易在.deps/euc_jp.Plo上失败,或者在 MinGW 里遇到ranlib '/usr/local/lib/libonig.a': No such file。这类问题的原因大多是 libtool 中途没生成最终归档文件,解决方式也直接:进入.libs目录手动执行一次ranlib libonig.a,再把libonig.a复制到/usr/local/lib,之后接着跑make install就能绕过。

4.3 源码里的 hoge 和 foobar:日文占位符不是 bug,是习惯

读这套源码的人,注意力很容易被测试脚本里的 hoge、foobar 这种名字带走。我最初也以为是作者随手乱写,后来查了资料才知道,hoge 在日文编程社区里是通用的占位符,地位相当于英文世界的 foo/bar。日本程序员写示例变量名时,第一选择往往就是 hoge,它没有任何业务含义,就是“随便一个变量”。所以你在 test.crb 里看到print("hoge")或者变量名 v1、n1 混着出现,不用怀疑源码有问题,这是作者的习惯。真正值得研究的是表达式怎么解析、变量表怎么管理,而不是名字本身。

5. 避坑:五条最高频编译报错的现象、原因和解决记录

5.1 bison 不在 PATH:process_begin 报错把整个 make 停在这一步

现象:在 Windows 下执行mingw32-make,立刻输出类似这样的错误后中止:

bison --yacc -dv crowbar.y process_begin: CreateProcess(NULL, bison --yacc -dv crowbar.y, ...) failed. make (e=2): 系统找不到指定的文件。

原因:make 在执行 Makefile 里的 bison 规则时,在 PATH 里找不到 bison.exe。GnuWin32 没装,或者装了但 bin 目录没加进 PATH,都会引发这个结果。

解决:先where bison确认;如果确实缺失,把 GnuWin32 的 bin 目录加进 PATH 后重跑。若安装目录在D:\GnuWin32\bin,命令是set PATH=D:\GnuWin32\bin;%PATH%。改完环境变量后要重新打开 cmd 再执行 make,否则仍会沿用旧环境。

5.2 GnuWin32 装进带空格路径:m4 把 Program Files 拆成两个参数

现象:bison 本身能启动,但紧接着 m4 报一串离奇错误:

m4: cannot open `Files': No such file or directory m4: cannot open `(x86)\GnuWin32\Bison/share/bison': No such file or directory

原因:GnuWin32 被默认安装到了类似D:\Program Files (x86)\GnuWin32的路径。bison 调用 m4 加载宏文件时,带空格的完整路径被拆成了多个参数,m4 拿Files当文件名去找。

解决:卸载后重新安装到无空格路径,比如D:\GnuWin32。这是最省事的方案,优先级高过手动加引号改配置。以后再遇到 m4 打不开文件,第一反应就查安装路径里有没有空格和中文字符。

5.3 y.tab.h 没生成:flex 找不到头文件,gcc 跟着翻车

现象:编译 mycalc 或 crowbar 时,先出现:

mycalc.l:3:19: error: y.tab.h: No such file or directory gcc: y.tab.c: No such file or directory

原因:.y 语法文件和 .l 词法文件的生成顺序错了。y.tab.h 必须由 bison 先处理 .y 文件产生,flex 处理 .l 文件时才能通过#include "y.tab.h"拿到 token 定义。如果 Makefile 的依赖关系不完整,或者你手动执行时只跑了 flex 没跑 bison,就会翻车。

解决:手动按顺序执行 bison、flex、gcc:

bison --yacc -dv mycalc.y flex mycalc.l gcc -o mycalc.exe y.tab.c lex.yy.c

如果用了这条顺序仍然失败,回头检查 bison 是否成功生成了 y.tab.h,而不是直接怀疑编译器。

5.4 Windows 行尾进入 Linux:crowbar 报 0x0d 语法错误

现象:Linux 下编译成功后运行./crowbar test/test.crb,报错信息像这样:

5: 语法错误(0x0d)

原因:test.crb 文件是从 Windows 环境复制过来的,行尾是 CRLF。crowbar 的词法分析器把每行结尾的\r当成一个非法字符,于是在第 5 行附近报 0x0d 错误。这个问题和编译器无关,纯粹是文件格式。

解决:把 .crb 文件的换行符转成 Unix 风格:

# 单个文件转换 dos2unix test/test.crb # 或者目录内批量处理 find test -name '*.crb' -exec sed -i 's/\r$//' {} \;

我一般会养成习惯:从 Windows 解压源码包后,先全目录批量清理一遍行尾,再开始编译。这不仅针对 crowbar,任何跨平台源码包都适用。

5.5 链接期缺库:-lmsvcp60 和 libonig.a 的经典错位

现象:在 Linux 上编译 crowbar_book_0_4,链接器报:

/usr/bin/ld: cannot find -lmsvcp60

同时在 Windows 的 MinGW 环境里,编译 oniguruma 后执行make install又报:

ranlib '/usr/local/lib/libonig.a': No such file

原因:-lmsvcp60是 MSVC 的运行库参数,是源码里某个 Windows 专用 Makefile 或链接配置残留下来的;Linux 上根本没有这个库文件。而 MinGW 的make install失败,是因为 oniguruma 用 Cygwin 风格的 libtool 生成归档文件,路径和 MinGW 预期不一致,ranlib 找不到目标。

解决:Linux 上编辑对应的 Makefile,把-lmsvcp60从链接行去掉,然后重新编译。MinGW 环境则进入 onig-5.9.4 源码目录手动补齐:

cd .libs ranlib libonig.a cp libonig.a /usr/local/lib/ cp ../oniguruma.h /usr/local/include/

把静态库和头文件放到位后,再回到 crowbar 目录执行 make,链接就能找到 libonig 了。这个坑的教训是:旧日文源码包里的链接参数经常带有原作者 Windows 开发环境的残留,编译失败时先看清楚 ld 在找哪个库,再决定是删参数还是补库文件。

6. 拿到源码包先做验证:make clean 与最小化编译链路

6.1 用 make clean 强制走一遍完整生成链路

网上很多源码包为了省事,打包时直接塞入了编译产物,比如 y.tab.c、lex.yy.c 甚至现成的 exe。你拿到的代码里这些文件都在,运行起来看似正常,但一旦换个平台或改一行语法,整个体系就露馅。我的习惯是拿到任何语言实现源码包,第一件事先跑make clean,把生成物全清理掉,只留手写源文件,然后重新构建一遍。以 crowbar 为例:

cd crowbar_book_0_1 make clean make

如果make clean后重新 make 依然能顺利产出 crowbar,说明这个包确实是源码级完整,而不是靠预生成文件撑场面的半残包。这招对判断资源完整度很管用,因为很多下载包里 y.tab.c 还在,但原始的 crowbar.y 已经被删掉或损坏,直接 make 不会报错,改一下语法文件才露馅。

6.2 用 calc 目录做工具链探针

验证完整工具链,不需要一上来就挑战 crowbar。calc 是最简单的一层,先在这里确认 gcc 和 make 没问题。进去看一眼 Makefile 内容,确认没有额外依赖,直接执行构建,再手动输入表达式测试输出。这个小动作花两分钟,能把环境问题隔离在最小范围内。我也常用它判断当前机器的基础编译器是否正常,避免一上来就在复杂项目里排查“到底是我环境坏了还是源码坏了”。整体链路验证通过后,再进入 mycalc 接触 yacc/lex,最后才是 crowbar 和 Diksam。

从那以后我每次拿到这类源码包,都强制自己先make clean再全量重编,用最小例子探明工具链,再往深了改代码。这个习惯帮我把“环境问题和源码问题”分开,省下过不少白费功夫的排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表