首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
从0_1到0_4:自制编程语言分章源码的构建与排错
📅 2026/11/4 16:48:52
✍️ 爱科研究院
👁 阅读 3,247
简介《自制编程语言》相关资料1是一份面向编程语言爱好者、编译原理学习者及希望动手实现小型语言的开发者的PDF文档聚焦从语法设计到解释器、编译器落地的完整链路适合作为课程学习与项目实践的辅助材料。资源包仅含1个PDF文件约2.38MB便于单文件检索与阅读。内容以Crowbar、Diksam两个自制语言案例为主线串联词法分析与语法分析、yacc/lex与bison/flex的使用、MinGW工具链配置以及Linux与Windows下不同构建方式的对照并讨论编译器、解释器、运行时环境与资源管理等关键问题。已有870人学习读者可借此理解语言设计原则、实现工具选型与跨平台构建思路获得从语言规范到可运行实现的整体参照。1. 一份按章节切开的编译器源码为什么值得从 0_1 开始啃很多人对「自制编程语言」的第一反应是玄学词法、语法、语义、虚拟机、GC随便哪一环都能劝退。这套自制编程语言相关资料的价值恰恰在于它把这件事切碎了——crowbar_book_0_1到0_4、diksam_book_0_1到0_4每一份都对应书里的一章是一个能独立构建出可执行文件的完整快照旁边还配了mycalc、llparser这类前置练习。你不需要一次看懂一门语言只需要看懂从 0_1 到 0_2 多出来的那几百行。适合谁写过 C、知道指针和结构体、但没亲手撸过 parser 的人想把团队内部 DSL 做个原型、又不想一上来就上 LLVM 的人以及被编译原理教材的龙书吓退过、想找一条能跑通的路的人。不适合完全没写过 C 的读者光是 Makefile 和链接报错就够喝一壶。2. 解包先做什么八份分章源码的两条主线与一次构建压缩包里最容易被忽略的是「版本粒度」。它不是给你一份最终代码让你读而是把同一门语言在每个实现阶段的形态都留了一份这意味你可以拿diff当阅读器用第 6 章会展开。先把目录摸清楚再决定工具链最后跑通最简的那一份顺序不要反。2.1 目录与产物对照先知道每份代码能跑出什么解包后按目录名扫一遍大致是这么几类东西目录内容定位构建入口产物calc最小计算器练手用直接gcc单文件可执行程序mycalc/mycalc_exyacc/lex 版计算器含扩展Makefile词法语法分析器llparser/llparser_ex手写 LL 解析器不用生成器Makefile递归下降解析器crowbar_book_0_1~0_4解释器 Crowbar 的分章快照make.sh/make.batcrowbar/crowbar.exediksam_book_0_1~0_4静态类型语言 Diksam 的分章快照make.sh/make.batdiksam/diksam.exe这里有个非常具体的结构差异很多人第一次找不到构建入口就是栽在这Diksam 的 0_1 和 0_2源文件放在compiler目录下到了 0_3 和 0_4构建入口挪到了main目录。也就是说你在 0_4 里对着compiler目录敲make会得到一个「没有目标」的报错。同一件事在 Crowbar 各章里没有发生入口一直在章节目录根上别把两套的习惯混着用。mycalc和mycalc_ex是 yacc/lex 的入门线llparser和llparser_ex是手写解析器的入门线这两条线的价值在于它们足够小——一个.l加一个.y几十行就能生成出能算12*3的东西。先在这两条线上把「生成器怎么和 gcc 串起来」这件事搞明白再去看 Crowbar 的十几个.c文件心理压力会小很多。2.2 工具链选型MinGW 还是 Cygwin先把这件事定死Windows 下要跑这套代码绕不开这个选择而且它不是「装哪个都行」会直接影响你后面分发和排错的成本。MinGW 的产物是原生 PE 可执行文件只依赖系统自带的 C 运行时拷到另一台没装编译环境的机器上也能双击跑。Cygwin 提供的是类 UNIX 环境但它编出来的 exe 依赖cygwin.dll一旦这个 DLL 不在搜索路径上程序连启动都启动不了报的还是一条看不出所以然的错。我的选择逻辑很简单只想要能跑起来的 exe用 MinGW需要./configure make那套完整 POSIX 工具链比如第 4 章编正则库用 Cygwin。两者可以共存装在同一个盘符下不同目录就行别装进带空格的路径这个坑在第 5 章会细说。MinGW 这边要额外准备两个东西bison和flex的 Windows 版本。装的时候有个细节——安装包只选「完整包不含源码」装完把bin目录加进系统PATH。Cygwin 这边对应的做法是在安装器里勾上bison、flex、make、m4这几个包注意m4在Interpreters分类里、make在Devel分类里默认是不勾的。还有一个特别容易漏的动作MinGW 自带的构建工具叫mingw32-make.exe不是make.exe。章节目录里的脚本按make或gmake写死了命令名所以你需要在MinGW\bin下复制一份改名成gmake.exe或make.exe这是后面所有构建能跑通的前提。2.3 第一次构建把 crowbar_book_0_1 跑到能读 test.crb选 0_1 是有理由的它是全链路最短的一份词法、语法、表达式求值都有了变量和控制流还没加出错时你能定位到具体是哪个.c出的问题。Linux、macOS 或 Cygwin 环境下# 进入第一章目录 cd crowbar_book_0_1 # 脚本会根据平台生成对应的 Makefile sh make.sh # 生成完之后再 make顺序不能反 make # 用一个自带脚本跑一遍 ./crowbar test/test.crbWindows MinGW 环境下:: 切到章节目录路径不要带空格和中文 cd C:\dev\crowbar_book_0_1 :: 先跑批处理生成 Makefile make.bat :: 用改名后的 gmake 构建等价于 mingw32-make gmake :: 直接跑 crowbar.exe test\test.crbmake.sh和make.bat干的其实是同一件事把当前平台的编译器和链接参数写进 Makefile。所以它们不是构建命令是配置命令跑完必须再执行一次make。我第一次用的时候以为make.bat跑完就结束了结果目录里躺着一个 Makefile 我却不知道。构建过程里你会看到一长串 gcc 命令行参数是这样的gcc -c -g \ -Wall -Wswitch-enum -ansi -pedantic -DDEBUG \ main.c interface.c create.c execute.c eval.c \ string.c string_pool.c util.c native.c error.c-g保留调试符号后面用 gdb 单步跟解释器执行流程时必须有。-Wall -Wswitch-enum打开全部常规告警并强制switch把枚举值列全——这条对写解释器特别有用你加一个 Token 类型忘了在处理函数里补分支编译器会当场告诉你。-ansi -pedantic锁死标准 C避免用到编译器私有扩展这样一份代码在两个平台上行为一致。-DDEBUG打开代码里的断言和调试打印等你要做性能对比时把这条去掉再编一遍。提示如果你只看到gmake: crowbar is up to date.而没有任何编译输出说明上一次构建已经成功可执行文件就在当前目录直接跑就行不用删了重来。跑通这一步的意义在于你手上有了一个能读.crb脚本、能算表达式、能报语法错误的程序。接下来所有改动都建立在这个基线之上一旦后面某章编不过你至少知道基线是好的问题出在增量上。3. bison 与 flex 的接线.l/.y 生成顺序与 Makefile 三处改动这套资料里最密集的报错都集中在生成器这一层。原因不复杂bison和flex是两个独立进程它们生成的中间文件互相依赖而 Makefile 里的依赖关系一旦写反gcc 就会在你毫无防备的时候告诉你找不到头文件。3.1 从 .y 到 y.tab.c为什么 .l 不能先编译Crowbar 的构建链路是这样crowbar.y经 bison 生成y.tab.c和y.tab.hcrowbar.l经 flex 生成lex.yy.c而crowbar.l里有一行#include y.tab.h因为词法分析器返回的 token 常量是语法文件里定义的。这就意味着y.tab.h必须先于lex.yy.c的编译存在。如果 Makefile 把lex.yy.c和y.tab.c放在同一层并列依赖里make 有可能先编词法器此时y.tab.h还不存在gcc 直接抛出mycalc.l:3:10: fatal error: y.tab.h file not found #include y.tab.h这条报错在mycalc里一模一样地复现因为mycalc.l第 3 行也是这句 include。修法不是去改.l文件而是改依赖# y.tab.h 由 bison 从语法文件生成-d 参数就是让它额外吐出头文件 y.tab.h: crowbar.y bison --yacc -dv crowbar.y # 词法器的编译显式依赖头文件make 就会先做上面那一步 lex.yy.c: crowbar.l y.tab.h flex crowbar.lbison的三个参数含义要清楚--yacc让输出文件名沿用 yacc 的y.tab.c约定代码里 include 的是这个名字改不了-d生成头文件-v生成.output报告把冲突的移进/归约项全列出来调试语法歧义时这份报告比编译器报错有用得多。3.2 bison 在 Windows 上的两个暗坑PATH 与 m4生成器装好了不代表能用。Windows 上最常见的两条报错一条是找不到命令一条是找到了但内部炸了。第一条process_begin: CreateProcess(NULL, bison --yacc -dv crowbar.y, ...) failed. make (e2): 系统找不到指定的文件。 Makefile:30: recipe for target y.tab.h failede2就是「文件不存在」。make 调bison用的是系统PATH而你在某个终端里临时set PATH只对那个窗口有效换个 CMD 就失效。解决方式是把 bison 的bin目录永久加进系统环境变量然后关掉所有终端重开再验证。别在原来的窗口里反复set那是自己骗自己。第二条更隐蔽。bison 内部要靠m4做宏展开而 m4 被调用时会带上自己的数据文件路径。如果你把 bison 装进了默认的Program Files (x86)目录路径里的空格会把参数切断于是你看到m4: cannot open Files: No such file or directory m4: cannot open (x86)\GnuWin32\Bison/share/bison: No such file or directory m4: cannot open D:\Program: No such file or directory四条报错拼起来正好是完整路径被空格剁成的四段。这不是编码问题也不是权限问题就是路径带空格。解决方式是重装到C:\GnuWin32这种无空格目录或者手工把share\bison目录复制到无空格路径并设置对应的环境变量。我一般直接在装机第一步就把所有工具链放在C:\toolchain\下省得后面反复踩。3.3 一份能直接抄的最小 Makefile把上面两条揉起来mycalc这种规模的项目用这样一份 Makefile 就够了Crowbar 各章的 Makefile 是它的放大版# 目标可执行文件名 TARGET mycalc # 中间目标文件注意是 y.tab.o 和 lex.yy.o OBJS y.tab.o lex.yy.o main.o $(TARGET): $(OBJS) gcc -o $(TARGET) $(OBJS) -lm # 语法文件生成 C 源和头文件-d 不能省 y.tab.c y.tab.h: mycalc.y bison --yacc -dv mycalc.y # 词法文件的编译依赖头文件这一行是顺序保证的关键 lex.yy.c: mycalc.l y.tab.h flex mycalc.l # 两个 .c 都依赖生成结果显式写出来避免并行 make 时抢跑 y.tab.o: y.tab.c gcc -c -g -Wall y.tab.c lex.yy.o: lex.yy.c y.tab.h gcc -c -g -Wall lex.yy.c main.o: main.c y.tab.h gcc -c -g -Wall main.c clean: rm -f $(TARGET) $(OBJS) y.tab.c y.tab.h lex.yy.c几个容易写错的地方值得单独说。第一y.tab.c和y.tab.h写在同一条规则的目标位置意思是「跑一次 bison 同时产出这两个」make 会理解为任一缺失都要重跑。第二lex.yy.o的依赖里同时列了lex.yy.c和y.tab.h这是给make -j并行构建上保险不加的话单线程能过、多线程偶尔失败属于典型的玄学故障。第三clean必须把生成出来的y.tab.c、lex.yy.c一起删掉只删.o会让你在下一次构建时用上过期的生成结果改完.y却不生效能查半天。注意如果 make 报No targets specified and no makefile found先看目录里是不是只有make.sh和make.bat而没有Makefile。这两个脚本是生成 Makefile 的不是 Makefile 本身得先跑一次。4. 把 oniguruma 编进 Crowbar正则库在 Windows 与 Linux 的两条路到后面几章Crowbar 需要字符串模式匹配能力常见的做法是挂一个成熟的正则库上去而不是自己手写状态机——自己写的那套在边界条件上早晚翻车。资料里用的是 oniguruma 5.9.4 这个版本两个平台各有一条编译路径坑完全不同。4.1 为什么要单独编一个库而不是把源码拖进工程把第三方源码直接拷进项目目录看起来省事实际后患无穷源文件命名冲突、编译告警混在自己代码里、升级时无法区分改动。正规做法是把它编成静态库libonig.a自己的工程只 include 头文件、链接.a。静态库而不是动态库的理由也很实际你编出来的crowbar.exe要能拷到别的机器上直接跑动态库.dll一旦不在搜索路径就是启动失败。静态链接会把需要的代码打进 exe体积大一点但省掉一整类部署问题。Linux 和 macOS 下这一步是标准三部曲# 下载后解包 tar -xvf onig-5.9.4.tar.gz cd onig-5.9.4 # 生成 Makefile这一步会检查编译器、头文件、库函数 ./configure # 编译-j 后面跟核数可以并行加速 make -j4 # 装到系统目录需要写权限 sudo make installconfigure那一长串输出里你只需要盯三件事checking for alloca... yes、checking for working memcmp... yes、checking for prototypes... yes这三个都是yes就说明基础环境没问题。最后一行是config.status: creating Makefile出现了就说明配置成功。4.2 Windows 下走 bash还要补一步 ranlibWindows 上没有configure脚本能直接用的 shell得借 Cygwin 或者 MinGW 自带的 bash。把压缩包放到盘符根目录比如C:\然后# 在 bash 里操作注意路径是 /cygdrive/c/ 这种形式 cd /cygdrive/c tar -xvf onig-5.9.4.tar.gz cd onig-5.9.4 ./configure makemake一般能过卡住的多半是make install。因为install要往/usr/local/lib写东西而 Windows 上这个路径的映射关系跟 Linux 不一样MinGW 的ranlib处理这个路径时会直接报D:\MinGW\bin\ranlib.exe: /usr/local/lib/libonig.a: No such file Makefile:415: recipe for target install-libLTLIBRARIES failed意思是「我编译出来了但我在目标位置找不到它」。绕过去的办法是跳过make install手工做它本该做的两件事——给静态库建索引然后拷到该去的地方# 进到 .libs 目录make 把真正的产物放在这里外面那个 .a 只是壳 cd .libs # 给静态库建立符号索引缺这一步链接时会报 undefined reference ranlib libonig.a # 拷到 MinGW 的库目录链接时 -lonig 才能找到 cp libonig.a /d/MinGW/lib/ # 头文件同理拷到 include 下 cp ../oniguruma.h /d/MinGW/include/ranlib这一步特别容易被当成可选项跳过。它的作用是把.a里的符号表建好缺了它编译阶段一切正常链接阶段才开始报一堆undefined reference to onig_new之类而且报错位置跟你写的代码毫无关系能查到你怀疑人生。4.3 用十行代码验证库是不是真的能用装完别急着回去改 Crowbar先单独验证一遍。写一个只调版本号的最小程序/* check_onig.c —— 只验证头文件和库能被正确找到和链接 */ #include stdio.h #include oniguruma.h int main(void) { /* 初始化正则引擎不调用的话后续 API 行为未定义 */ int r onig_init(); if (r ! ONIG_NORMAL) { printf(init failed: %d\n, r); return 1; } /* 直接打印库版本能打出来就说明静态库真的链上了 */ printf(oniguruma version: %s\n, onig_version()); onig_end(); return 0; }编译命令和预期输出# -I 指定头文件目录-L 指定库目录-lonig 是链接静态库 gcc check_onig.c -o check_onig -I/usr/local/include -L/usr/local/lib -lonig ./check_onig # 期望输出形如oniguruma version: 5.9.4Windows 下把-I和-L换成D:/MinGW/include、D:/MinGW/lib的形式。如果这一步能过说明头文件位置对、库文件位置对、符号索引完整后面 Crowbar 链接失败就只可能是它自己的 Makefile 里少写了-lonig排查范围一下子缩小到一行。5. 排查清单五个高频翻车点从 m4 路径空格到 0x0d这套资料的时间跨度不小社区里积累的报错帖基本都是环境问题而不是代码问题。下面五条是我和周围人踩过、并且能稳定复现的按「现象 → 原因 → 解决」写清楚遇到时直接对号入座。5.1 m4 报一堆路径碎片现象执行 bison 时冒出四五条m4: cannot open ...每条路径都是残缺的比如Files、(x86)\GnuWin32\Bison/share/bison。原因bison 调用 m4 时把数据文件路径作为参数传过去而路径里含空格参数被 shell 层面切成了多段每一段都被当成独立路径去 open。解决把 bison 重装到无空格路径C:\GnuWin32这种或者把share\bison整个目录复制到无空格位置再把 bison 的查找路径指过去。改完必须开新终端旧窗口里的环境变量还是旧的。5.2 y.tab.h 找不到但 bison 明明跑过了现象mycalc.l:3:19: error: y.tab.h: No such file or directory或者gcc: y.tab.c: No such file or directory。原因Makefile 里lex.yy.c和y.tab.c是并列依赖make 挑了词法器先编此时 bison 还没跑另外有一种情况是上一步 bison 其实失败了比如 5.1 那个 m4 问题但 make 继续往下走了。解决在 Makefile 里给lex.yy.c显式加上y.tab.h依赖给y.tab.o加上y.tab.c依赖。同时往回翻构建日志确认 bison 那一步真的没有报错——很多「找不到文件」的根因是上一条命令其实失败了。5.3 CreateProcess failede2现象process_begin: CreateProcess(NULL, bison --yacc -dv crowbar.y, ...) failed. make (e2): 系统找不到指定的文件。原因bison不在当前进程的PATH里。注意是「当前进程」你在别的窗口设过不算数还有一种更隐蔽的情况装的是 Cygwin 版 bison但构建用的是 MinGW 的 make两边环境隔离互不可见。解决统一工具链来源——要么全用 MinGW 版要么全在 Cygwin 终端里操作。在 CMD 里用where bison确认能找到找不到就永久加进系统PATH并重开终端。5.4 程序能跑但第一行就报 0x0d 错误现象在 Windows 上编好跑./crowbar test/test.crb报错信息里带着类似5: 非法字符(0x0d)的字样位置固定在行尾。原因.crb脚本文件是 CRLF 换行而词法分析器的空白处理只认\n\r被当成非法字符。这不是 bug是文件编码问题Linux 下编出来的同名二进制读同一个文件就没事。解决转换换行符。Linux 侧的dos2unix test/test.crb最省事没有这个命令就用sed -i s/\r$// test/test.crb。如果你在 Windows 上编但想读同一份脚本也可以在词法规则里把\r加进空白字符集合但那属于改代码不如统一脚本格式来得干净。5.5 链接报 -lmsvcp60或者运行时报缺 cygwin.dll现象Linux 下构建 0_4 时链接器报cannot find -lmsvcp60或者 Windows 下双击 exe 弹出「找不到 cygwin.dll」。原因前者是 Makefile 里残留了 Windows 专用的 C 运行库链接项Linux 上根本没有这个库后者是用 Cygwin 工具链编出来的产物依赖cygwin.dll换台机器就找不到。解决-lmsvcp60直接从 Makefile 的LDFLAGS里删掉这份代码是纯 C不需要它。cygwin.dll的问题要么把 dll 一起拷过去要么换 MinGW 重新编一遍——长期看换 MinGW 更省心产物是原生 exe没有运行期依赖。6. 拿 diff 当阅读器两章之间到底加了什么这套资料最值钱的用法不是「读完一章读下一章」而是把相邻两章当成两个提交来对比。书里的叙述是线性的但代码的增量是非线性的——某一章可能只加了三十行却引入了整个对象管理机制你顺着文字读完全感觉不到这个重量。我的做法是先看目录级差异再钻到文件级# -r 递归 -q 只报差异不打印内容先看哪些文件被动过 diff -rq crowbar_book_0_1 crowbar_book_0_2 # 定位到具体文件后看这个文件的逐行改动 diff -u crowbar_book_0_1/eval.c crowbar_book_0_2/eval.c | less # 只想看新增了哪些文件不看内容差异 diff -rq crowbar_book_0_1 crowbar_book_0_2 | grep Only in第一条命令的输出会告诉你哪几个.c被改了、哪几个是新增的。通常是execute.c和eval.c改动最大新增文件则往往对应一个新模块——比如某一章开始出现 GC 相关的东西你在Only in里会看到新的源文件那就是这一章真正的主题。第二条命令配less是因为改动动辄几百行直接在终端刷过去等于没看。我一般会先按}跳段落看每个函数的边界找到「多出来一整个函数」的位置那基本就是本章的核心。函数签名比函数体更值得先看——参数里多出来一个Context *或者Object *往往比函数体里那几十行更能说明设计变化。有一点必须提醒别对着 diff 死磕看不懂的改动先跑一遍。我在对比 0_2 和 0_3 时就吃过这个亏盯着新增的对象管理代码看了半小时没想明白为什么这么设计后来把两章各自编出来、跑同一个脚本、用 gdb 跟了一遍内存分配路径五分钟就清楚了——原来差异不在代码逻辑在对象的生命周期被推迟了。从那以后我每次对比分章源码都强制走一遍「先编、再跑、后读 diff」的顺序读代码的效率比纯看文本高一个数量级。这套东西本质上是半成品仓库每一章都是一个能跑的检查点。你要是能把它当成 git 历史来读收获会比顺着书本翻一遍大得多。希望帮到你。本文还有配套的精品资源点击获取
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/11/4 16:48:52
ISO/IEC 42001草案解读:AIMS与AI风险评估、影响评估
2026/11/4 16:48:52
分布式系统监控中的观测者效应与TCP惊群问题
2026/11/4 16:43:52
Apifox全生命周期API管理工具安装与配置指南
2026/11/4 18:39:18
主键与索引的物理本质:从SQL约束到B+树存储
2026/11/4 18:39:18
算法竞赛实战:IOI赛制题目解析与策略优化
2026/11/4 18:39:18
零成本开源工作流:图像视频音频排版全方案
2026/11/4 18:39:18
Jev 入门第一课:TypeSafe AI 与 System One Model 核心概念解析
2026/11/4 18:39:18
AnyLogic人群仿真在建筑设计中的核心应用与技术实现
2026/11/4 18:34:17
PCDN技术解析:家庭宽带变IDC的利与弊
2026/11/4 0:02:45
AI辅助技术栈评估:提升研发决策效率60%
2026/11/4 0:02:45
从生成Token到直接决策:Jev如何重塑AI Agent工程实践
2026/11/4 0:02:45
32G统一内存跑大模型:内存账、TPS实测与端云分流
2026/11/4 4:08:30
面向复杂光照与角度的宠物面部识别鲁棒性增强策略
2026/11/4 4:08:26
DRV8818PWPR与R7FA8M1AHECBD工业步进驱动实战指南
2026/11/4 4:08:23
STM32与DRV8818双极步进电机驱动方案:从选型到调试全流程解析
2026/11/4 4:08:30
面向复杂光照与角度的宠物面部识别鲁棒性增强策略
2026/11/4 4:08:26
DRV8818PWPR与R7FA8M1AHECBD工业步进驱动实战指南
2026/11/4 4:08:23
STM32与DRV8818双极步进电机驱动方案:从选型到调试全流程解析