首页 / 资讯中心 / 文章详情

从编译原理到可运行解释器:自制编程语言源码与Windows工具链实践

从编译原理到可运行解释器:自制编程语言源码与Windows工具链实践 ★ FEATURED ARTICLE
简介这份《自制编程语言》配套资料面向希望理解语言底层运行机制的开发者与计算机专业学生围绕语言设计、实现与使用三条主线展开帮助读者打通从语法规则到可执行程序的完整认知链路。压缩包内仅有1个PDF文件体积约2.38MB属于轻量级阅读材料适合集中通读也便于在动手实践时随时对照查阅。内容覆盖词法分析与语法分析的基本思路、编译器与解释器的分工、Crowbar 与 Diksam 两个示例语言的版本演进及垃圾回收改造并穿插 yacc/lex、bison/flex、MinGW 等工具链的安装与配置说明同时对比 Linux 与 Windows 下 make.sh、make.bat 的构建差异点出跨平台编译中常见的环境问题。读者可据此建立从语言规范、解析器生成到运行时管理的知识框架为后续自行实现小型语言积累可复用的参考。目前已有870人学习下载。1. 从一堆编译不过的 ZIP 说起这套自制编程语言资料真正解决什么问题自制编程语言这个话题卡住大多数人的从来不是语法理论而是我把书上代码敲完了make 报了一屏错然后就没有然后了。这套相关资料的价值恰好在这里它不是一份理论讲义而是一组带版本号的、能跑起来的最小实现快照——crowbar 从 book_0_1 到 book_0_4Diksam 同样四个版本外加 calc、mycalc、llparser 三类前置练习每个目录里都自带 Makefile、make.sh、make.bat。你不需要先想清楚我要设计一门什么样的语言先把 0_1 编出来、跑通 test.crb理解一个解释器从 .l/.y 到可执行文件的最短路径比读十篇综述都管用。它适合两类人一类是写过业务代码、想知道for循环到底怎么被翻译成字节码或直接求值的工程师另一类是被龙书劝退过、想找一份能 debug 的小体量源码的在校生。不适合谁也想说清楚——如果你要的是 LLVM 那套工业级后端、或者想直接拿现成框架生成一门生产语言这套资料会让你失望它连代码优化都基本没碰。它就是教学用但真能跑的定位而恰恰是这个定位让它成为少数你能在一周内读完主循环源码的编译器项目。2. 拆包先看骨架crowbar 与 Diksam 两条演进线的分工拿到压缩包解压后第一件事不是急着 make而是把目录树看清楚。这一步偷懒后面所有的报错你都会归错因——很多人以为是环境问题其实是跑错了目录进了_ex或者进了只有源码没有 Makefile 的中间态目录。2.1 目录结构与版本递进book_0_1 到 book_0_4 各加了什么解压出来的顶层大致是这样的结构我按你会先碰哪个排序目录定位依赖建议顺序calc极简表达式求值器yacc/lex第 1 个看mycalc带变量与赋值的小计算器yacc/lex第 2 个看mycalc_exmycalc 的习题解答同上卡住时对照llparser手写 LL(1) 递归下降版本纯 C无生成器第 3 个看llparser_exllparser 的习题解答同上对照用crowbar_book_0_1 ~ 0_4动态类型脚本语言四个快照bison/flex主线diksam_book_0_1 ~ 0_4静态类型语言四个快照bison/flex、oniguruma进阶版本递进这件事别去背0_3 加了 GC、0_4 加了类这种二手结论最可靠的办法是直接 diff 两个相邻目录。这份资料的好处是每个快照都是完整可编译的独立工程不是 patch所以 diff 出来的每一条都是真金白银的语言特性增量# 在解压后的根目录执行只看新增的源文件和被改动的函数 diff -ru crowbar_book_0_2 crowbar_book_0_3 | head -200 # 只想看新增了哪些 .c/.h跳过庞大的上下文 diff -rq crowbar_book_0_2 crowbar_book_0_3第一条命令的-r是递归-u是统一格式可读性最好head -200是为了不让终端被刷爆——两个快照之间的差异动辄几千行先看前 200 行基本能判断这次改的是词法、语法还是运行时。第二条-q只输出哪些文件不同适合先建立全局印象。我一般会先跑第二条看到新增了execute.c里一大坨、或者多出一个heap.c就知道这一版动的是执行器和内存管理然后带着这个预判去读源码效率比盲读高一个量级。crowbar 这条线的设计取向是动态类型 树遍历解释变量不需要声明类型值在运行时带着类型标签走执行器直接对着抽象语法树递归求值没有中间字节码。这个选择对学习者极其友好——你不需要先理解栈式虚拟机的调度就能看到eval()如何对着一棵Expression树算出一个Value。代价是慢但这个项目本来就不追求快。Diksam 则反过来静态类型、需要声明、要做类型检查代码量明显更大配套还要链 oniguruma 做正则字面量-lonig这个链接参数就是它带来的。2.2 calc、mycalc、llparser三个前置练习各自练的是什么很多人跳过这三个目录直接冲 crowbar然后在%start、%token、移进归约冲突里卡三天。这三个练习不是热身运动它们各自对应一个独立的认知台阶。calc 只做一件事把12*3这样的表达式串变成结果。它小到你可以把.y文件一行一行读完理解%%分隔的三段结构——声明段放 token 和优先级规则段写expr: expr term用户代码段放main和yyerror。这里最关键的是优先级和结合性的写法/* calc 里 %left / %right 的典型写法顺序即优先级越靠后越高 */ %left - %left * / %right UMINUS %% expr: expr expr { $$ $1 $3; } | expr * expr { $$ $1 * $3; } | - expr %prec UMINUS { $$ -$2; } | ( expr ) { $$ $2; } | NUMBER { $$ $1; } ;%left表示左结合1-2-3算成(1-2)-3%right右结合书写顺序决定优先级——写在后面的优先级更高所以*在之后乘法先算。%prec UMINUS是给一元负号强行指定优先级不然-1*2会被解析成-(1*2)虽然结果一样但语义树不对遇到-23就露馅了。这段代码之后你应该能回答一个问题为什么12*3不需要括号也正确因为移进/归约冲突被优先级表裁决掉了。mycalc 在 calc 基础上加了变量、赋值和符号表。这一步加进来的东西就是 crowbar 里SymbolTable、Variable那些结构的雏形。看 mycalc 时重点盯两处赋值语句的语法规则怎么写IDENT expr以及符号表用什么数据结构存通常是线性数组或简单哈希因为变量少。mycalc_ex 是配套习题解答卡住的时候对照但别一上来就抄——解答里有些写法是为了练习题服务的未必是最优工程实践。llparser 是最容易被低估的一个。它把同样的表达式求值用手写递归下降重写了一遍不用 yacc/lex。手写解析器的好处是没有生成器的黑盒你能清清楚楚看到当前读到的 token 是什么、往前进一个、失败就回溯。这份资料把它和 mycalc 并排放目的就是让你对比两条路线生成器省事但报错信息晦涩、调试靠-v生成的output文件手写可控但工作量大、左递归要手动消除。llparser_ex 同样是解答版。学过这一圈再进 crowbar你对crowbar.y里的文法规则就不会有陌生感看到%%就知道该找什么。2.3 为什么这套实现用 C89 而不是 C 或脚本语言打开 Makefile 你会看到一串编译参数-Wall -Wswitch-enum -ansi -pedantic -DDEBUG。-ansi和-pedantic组合起来就是强制 C89 标准并禁止任何扩展-Wswitch-enum要求 switch 覆盖枚举的所有取值漏一个就警告-DDEBUG打开调试分支。这套参数不是随手写的它保证了代码在任何符合标准的 C 编译器上行为一致——这在教学场景里非常重要因为读者用的可能是 MinGW、Cygwin、clang、gcc 各种组合。用 C 而不是 C核心原因是让读者看得见内存。crowbar 的对象、字符串、符号表都是手动 malloc/freeDiksam 还要自己做 GC这些在 C 里会被智能指针和容器藏起来。教学项目最怕的就是关键逻辑藏在标准库后面。代价是代码里到处是malloc之后的判空和free之前的置 NULL读起来啰嗦但每一个内存动作都摊在明面上。再一个现实原因这本书写作年代较早配套代码对编译器的要求停留在有 gcc 就行。这直接导致了后面 Windows 用户的一大半痛苦——MinGW 装好了但没 bisonbison 装了但路径带空格路径没问题了但 make 的名字叫 mingw32-make。这些坑我们在第 3、5 章细说。3. 把 bison/flex 拉进 WindowsMinGW 与 Cygwin 的取舍Linux 用户到这一步基本可以跳过——apt install bison flex gcc make之后直接 make 就完事。Windows 用户才是这套资料真正的门槛所在而门槛不在代码在工具链的组装方式。这一章把两条可行路线摊开讲包括各自的代价。3.1 MinGW 路线安装、gmake 命名与 mingw32-baseMinGW 提供的是Windows 上的 gcc编译产物是原生 exe不依赖任何额外的 DLL 运行时除了系统自带的 msvcrt。这对分发和调试都友好。安装流程大致是下载安装器在 Installation Directory 里选一个不含空格的路径比如D:\MinGW组件列表里至少勾上mingw32-base含 gcc、binutils、mingw32-make和mingw32-gcc-g有些示例会用到然后 Apply Changes 等它下载完。装完之后D:\MinGW\bin要进 PATH。这一步的验证方式# 打开一个新的 cmd 窗口必须新开PATH 才会生效 gcc --version mingw32-make --version注意第二个命令的名字。MinGW 把 make 改名成mingw32-make.exe是为了避免和别的 make 冲突但这份资料的 Makefile 里写的是gmake。所以你要么在D:\MinGW\bin里复制一份改名要么每次手敲全名# 方式一复制一份改名为 gmake.exe推荐一劳永逸 copy D:\MinGW\bin\mingw32-make.exe D:\MinGW\bin\gmake.exe # 方式二不改名直接调用 cd /d D:\work\crowbar_book_0_1 D:\MinGW\bin\mingw32-make.execd /d里的/d是允许跨盘符切换很多人从 C 盘切到 D 盘时忘了这个参数结果还在原目录里跑 make报 No targets specified and no makefile found——这个报错 90% 不是 Makefile 的问题是你站错地方了。MinGW 自带的组件里没有 bison 和 flex。这两个要单独从 GnuWin32 那套包里下各自有独立的安装器选 Complete package, except sources 就行。装完之后同样要把它们的 bin 目录加进 PATH。这里埋着本套资料里出现频率最高的一个坑第 5 章第一条专门讲。3.2 Cygwin 路线能跑通但你要接受 cygwin.dllCygwin 走的是另一条路它在 Windows 上模拟了一套 POSIX 环境configure、make、bash、m4全都是原生可用的装 bison/flex 只需要在安装器里勾选Devel分类下的make、gcc以及Interpreters分类下的m4bison 依赖 m4 做宏展开这个依赖关系很多人不知道装完 bison 不装 m4 就会遇到一连串 m4 打不开文件的报错。Cygwin 的优势是省心./configure make这套流程原样可用不需要你操心 make 叫什么名字。代价有两个一是编译出来的 exe 依赖cygwin.dll把这个 exe 拷到没装 Cygwin 的机器上就跑不起来二是路径语义不同Cygwin 里的/usr/local映射到 Windows 目录树上某个位置make install经常装到你意想不到的地方。如果你的目标只是把 crowbar 编出来看看效果Cygwin 更快。如果你想把这套代码当长期读物、反复改反复编我建议 MinGW——原生 exe、调试器衔接顺畅、没有 DLL 依赖的干扰。两条路线不要混用。用 Cygwin 的 gcc 编译、再用 MinGW 的 ranlib 处理产物或者反过来会得到一些非常诡异的报错比如找不到-lmsvcp60或者 .a 文件明明在却链接不上。第 5 章会讲这个具体症状。3.3 oniguruma 5.9.4 的编译ranlib 与 make install 的路径陷阱Diksam 这条线要链 oniguruma正则表达式库版本是 5.9.4。Linux 下三步走完# Linux 下的标准三步注意最后一步可能要 sudo wget http://example.invalid/onig-5.9.4.tar.gz # 实际地址见资料内附链接 tar -xvf onig-5.9.4.tar.gz cd onig-5.9.4 ./configure make make installWindows 下就麻烦得多因为./configure是 shell 脚本得在 bash 里跑但你又希望它用 MinGW 的 gcc 编译出原生库。混合环境的典型做法是用 Cygwin 的 bash 执行./configure让它生成 Makefile配置时指定CC为 MinGW 的 gcc。这样能跑通大半但会在两个地方翻车。第一处是make install。Cygwin 的 shell 把/usr/local/lib解释成 Cygwin 根目录下的路径而 MinGW 的ranlib.exe把它解释成D:\MinGW\...或者干脆不认于是出现这种报错libtool: install: ranlib /usr/local/lib/libonig.a D:\MinGW\bin\ranlib.exe: /usr/local/lib/libonig.a: No such file Makefile:415: recipe for target install-libLTLIBRARIES failed第二处是.libs目录下的静态库没有被正确索引。绕过办法是不走make install手动把库建好索引再拷到 MinGW 的 lib 目录# 在 onig-5.9.4 目录下用 bash 手动完成安装的最后一步 cd .libs ranlib libonig.a # 生成符号索引缺这一步链接时会报 undefined reference cp libonig.a /d/MinGW/lib/ # 拷到 MinGW 的库搜索路径 cp ../oniguruma.h /d/MinGW/include/ # 头文件同理ranlib的作用是给静态库生成符号表armap没有它gcc -lonig能找到文件但找不到符号报一堆 undefined reference而你会以为是函数名写错了。cp的目标路径按你自己的 MinGW 安装位置改路径里带空格会再次触发问题这也是为什么前面强调装到D:\MinGW这种干净路径。做完之后验证一下写个两行的测试程序链一下-lonig能过就说明库装对了。别跳过这个验证直接去编 Diksam否则后面出错你分不清是库的问题还是代码的问题。4. 第一次 make从 .y/.l 到可执行文件的完整链路环境齐了进目录敲 make但在此之前你得知道 make 到底会替你做什么。这份资料的 Makefile 不复杂看懂依赖顺序报错时你才知道是哪一环断了。4.1 编译链路bison 生成什么、flex 生成什么一个用 bison/flex 写的解释器构建过程是两段式的先生成 C 源码再编译 C 源码。Makefile 里体现为两组规则。第一组是生成器规则# crowbar 的 Makefile 片段节选实际行号会因版本不同略有差异 y.tab.c y.tab.h: crowbar.y bison --yacc -dv crowbar.y lex.yy.c: crowbar.l flex crowbar.lbison --yacc -dv crowbar.y里--yacc让输出文件名用 yacc 的传统命名y.tab.c/y.tab.h而不是 bison 默认的crowbar.tab.c这样源码里的#include y.tab.h才找得到-d是生成头文件里面是 token 的宏定义-v是生成y.output记录所有状态和冲突调试文法时全靠它。flex crowbar.l生成lex.yy.c里面是yylex()函数。第二组是编译规则把生成的 C 和各手写模块逐个编成 .o# 典型的批量编译规则$ 是第一个依赖$ 是目标 %.o: %.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG $ -o $那串参数值得逐个说清楚-c只编译不链接-g带调试信息后面 gdb 要用-Wall开常用警告-Wswitch-enum要求 switch 覆盖枚举全部取值写解释器时这个警告能提前抓出漏掉的分支-ansi -pedantic锁死 C89-DDEBUG定义 DEBUG 宏代码里#ifdef DEBUG的断言和打印才会生效。最后一组是链接把所有 .o 合成可执行文件。如果这一版依赖 oniguruma链接行会多一个-lonig。4.2 一次完整的构建实录假设你在D:\work\crowbar_book_0_1MinGW 装在D:\MinGW# 1. 进目录注意 /d 跨盘符 cd /d D:\work\crowbar_book_0_1 # 2. 先看看有没有 Makefile别在空目录里瞎敲 dir Makefile # 3. 执行构建用你复制出来的 gmake或者全名 gmake # 4. 如果失败先清理再重来避免半成品 .o 干扰判断 gmake clean gmake成功的输出长这样顺序大致固定先 bison、再 flex、然后一串 gcc -c、最后链接bison --yacc -dv crowbar.y flex crowbar.l gcc -c -g lex.yy.c gcc -c -g y.tab.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG main.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG interface.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG create.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG execute.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG eval.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG string.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG string_pool.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG util.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG native.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG error.c gcc -c -g -Wall -Wswitch-enum -ansi -pedantic -DDEBUG error_message.c gcc -o crowbar *.o这份输出本身就是一张地图create.c建 ASTexecute.c跑语句eval.c算表达式string.c和string_pool.c管字符串后者是字符串驻留池用来让字符串比较变成指针比较native.c挂内置函数error.c和error_message.c管报错。你后面读源码时按这张地图跳文件比从 main.c 顺序读快得多。gmake clean这一步别省。中途失败过一次的话目录里会留下半截 .o 和过期的y.tab.h下次 make 看时间戳以为不用重编于是你对着同一份旧产物反复怀疑人生。清理重来是成本最低的排除法。4.3 用 test.crb 做第一次运行验证编出 exe 只是第一步能跑通示例脚本才算环境真的没问题# 运行自带测试脚本 ./crowbar test/test.crb # 或者按 Windows 的习惯 crowbar.exe test\test.crb如果输出正常恭喜工具链这关过了。如果报错注意报错信息的形态这套实现里error.c会打印行号和错误类型比如第 5 行非法字符0x0d。看到 0x0d 别怀疑代码那是换行符问题第 5 章专门讲。看到语法错误但行号是对的多半是.y文件没重新生成回到上一步gmake clean。跑通之后建议立刻做一件事随便改一下test/test.crb加两行你自己写的小程序——一个变量赋值、一个 if、一个循环。这一步的目的是确认改脚本不用重新编译这个事实也就是确认你手上的是解释器而不是编译器。很多人跑完示例就以为完事了其实还没真正理解这套东西的运行模型。5. 排错手册这几类报错几乎每个 Windows 用户都会撞上这一章的内容全部来自实际构建过程里反复出现的失败模式。每条按现象 → 原因 → 解决写你可以当查询表用。5.1 bison 装在带空格的路径里m4 集体打不开文件现象很唬人一次刷出五六行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 m4: cannot open (x86)\GnuWin32\Bison/share/bison/m4sugar/m4sugar.m4: No such file or directory原因bison 在执行时要把自己的 m4 宏文件路径作为参数传给 m4而这个路径是拼接出来的字符串没有加引号。装在C:\Program Files (x86)\GnuWin32下时路径里的空格把参数切成了三段m4 分别去找D:\Program、Files、(x86)\...自然全找不到。这是老版本 GnuWin32 打包方式的固有缺陷不是你操作错了。解决把 bison 重装到不含空格的路径比如D:\GnuWin32然后更新 PATH。如果不想重装可以设置环境变量BISON_PKGDATADIR指向真实的 share/bison 目录让它绕开自动拼接# 临时生效在当前 cmd 窗口里设置 set BISON_PKGDATADIRD:\GnuWin32\share\bison # 永久生效需要管理员权限的 cmd setx BISON_PKGDATADIR D:\GnuWin32\share\bison /Msetx写进的是系统环境变量/M表示机器级不加就是当前用户级。改完必须新开窗口验证。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原因有三种得按顺序排除。第一种bison 那一步失败了比如上一条的 m4 问题Makefile 继续往下走flex 生成的lex.yy.c里#include y.tab.h就找不到头文件。第二种你在 mac 或 Linux 上手动敲了cc lex.yy.c -o example -ll跳过了 bison 这一步——生成器不会自动运行得先bison -d xxx.y。第三种-d参数漏了bison 只生成了y.tab.c没生成头文件。解决先单独跑生成器看它自己报什么错# 单独验证 bison 是否能正常工作 bison --yacc -dv crowbar.y ls y.tab.c y.tab.h # 两个文件都得在 # 再单独验证 flex flex crowbar.l ls lex.yy.c这两条能过问题就在 Makefile 的依赖顺序或者清理不彻底gmake clean重来。过不了问题在工具链回到上一条。5.3 换行符、编码与 0x0d 报错现象脚本明明看着没问题运行时报第 5 行非法字符0x0d或者更隐蔽——不报错但字符串比较永远不相等。原因Windows 编辑器保存文件时用 CRLF\r\n而这套词法分析器的空白处理只认\n和空格\r被当成非法字符。另一个来源是文件编码源码里如果混进了 Shift-JIS 或 GBK 的中文注释在某些编译器下会连带影响行尾字节的解析。解决转成 LF。Linux 下一条命令# 批量转换先备份 dos2unix test/test.crbWindows 下没有 dos2unix 的话用 Git Bash 自带的或者用编辑器手动切VS Code 右下角点 CRLF 改成 LF。这一步的教训是这套代码的所有源文件和测试脚本都应该是 LF包括.c、.h、.y、.l、.crb。我见过有人在.y文件里混了 CRLFbison 报的错指向一个完全无关的行号排查了两小时。提示下载压缩包解压后先全量检查一遍换行符比出事之后再回头找便宜得多。5.4 2.5 节配套版本里 lexicalanalyzer.c 的未初始化指针现象llparser 或某一版词法分析器跑起来直接崩或者读到奇怪的行。定位到lexicalanalyzer.c里这段static char *st_line; /* ... */ while (fgets(st_line, LINE_BUF_SIZE, stdin) ! NULL) { parse_line(); }原因st_line声明成指针但从未分配内存fgets往一个野指针写数据属于典型的未定义行为。在某些平台上是直接段错误在某些平台上看起来能跑那只是运气。解决改成栈上数组或者先 malloc/* 方案一栈上数组最简单注意 LINE_BUF_SIZE 别开太大 */ static char st_line[LINE_BUF_SIZE]; while (fgets(st_line, LINE_BUF_SIZE, stdin) ! NULL) { parse_line(); } /* 方案二如果确实需要指针语义动态分配并检查返回值 */ static char *st_line NULL; if (st_line NULL) { st_line malloc(LINE_BUF_SIZE); if (st_line NULL) { fprintf(stderr, out of memory\n); exit(1); } }改完记得确认parse_line()里没有对st_line做sizeof运算——数组和指针在这一点上行为不同这是改这类 bug 时最容易引入的次生问题。资料包里这个位置是有勘误的遇到崩溃先来这里对一眼。5.5 -lmsvcp60 链接失败与两套工具链混用现象/usr/bin/ld: cannot find -lmsvcp60原因msvcp60是旧版微软 C 运行库Linux 上根本没有这个 .soCygwin 里也未必有。出现这个报错基本可以断定Makefile 是别人在别的环境下配好的或者你混用了两套工具链——比如用 Cygwin 的 gcc 去编一个原本为 MinGW 准备的工程链接参数里还留着 Windows 特有的库。解决先确认你用的是哪套编译器which gcc看路径。然后打开 Makefile 找-lmsvcp60删掉——这套代码是纯 C89不需要 C 运行库。如果删掉之后出现新的 undefined reference那才是真正缺的库多半是 oniguruma回去看 3.3。混用工具链的另一个典型症状是.a文件明明在却链接不上或者ranlib报文件不存在。判断方法看你链接的 .a 是用哪套工具链的ar打包的索引工具就得用同一套。Cygwin 打的包用 Cygwin 的 ranlibMinGW 的用 MinGW 的。这种事没有捷径只能保持一套到底。6. 从跑通到读懂一套按周推进的源码阅读路线跑通之后真正的价值在于读。但直接打开crowbar_book_0_4从 main.c 开始读多半会在第三个文件放弃——0_4 是最终形态代码量最大、特性最全不适合当入口。我给一条自己走过、也推荐给别人的顺序按周切分每周的产出都是能说清楚一件事。周次目标目录要回答的问题建议动手改的地方第 1 周calc一个表达式串怎么变成结果加一个%取模运算符第 2 周mycalc变量存在哪、怎么查加一个print语句第 3 周llparser手写解析器和生成器差在哪去掉一层左递归看文法怎么改第 4 周crowbar_book_0_1从源码到 AST 再到值的完整路径加一个内置函数第 5 周crowbar_book_0_3字符串驻留池为什么能让比较变快打印池的命中率第 6 周diksam_book_0_2静态类型检查在哪一步做让某个类型错误报得更清楚这张表的关键不是周次是每周只回答一个问题。编译器项目最容易犯的错是想一次看懂全部结果每个文件都翻了两页。第 4 周那个改动最有价值——给 crowbar 加一个内置函数比如abs你会被迫走完在 native.c 里注册 → 在符号表里绑定 → 在调用时找到 → 参数怎么传 → 返回值怎么造这条完整链路走一遍胜过读十遍。验证你读懂了的办法不是能复述架构图而是能回答具体的实现问题。比如crowbar里字符串比较为什么可以直接比指针答案在string_pool.c——所有字符串进池时做了驻留相同内容的字符串只存一份所以指针相等等价于内容相等比较从 O(n) 降到 O(1)。再比如create.c和execute.c为什么要分开因为一个是把 token 流变成树前端一个是遍历树求值后端分开之后你可以只换前端或者只换后端。这类问题答不上来说明那个文件还没读透。再说一个具体的调试技巧。这套代码编译时带了-g所以可以直接上 gdb在eval()里下断点然后单步看一个表达式怎么被求值gdb ./crowbar (gdb) break eval (gdb) run test/test.crb (gdb) print *node # 看当前节点类型 (gdb) continueprint *node打出来的结构体里有个type字段那就是 AST 节点的类型标签。跟着它走一遍 if 语句的执行比看源码注释清楚得多。用 gdb 的时候记得-g不能少MinGW 编译出来的 exe 是支持 gdb 的这一点比 Cygwin 方案舒服。最后一个坑也是我踩得最狠的一次我当年图省事直接在 0_4 目录里改代码试新特性改崩了之后想退回 0_3 对照发现两个目录的 Makefile 和源码结构已经不一样了根本没法 diff。从那以后我每次都强制把每个版本目录单独复制一份出来改原始包永远保持只读改之前先diff -rq确认基线。这套资料的价值恰恰在版本递进你把它的版本线破坏了它就退化成一份普通的教学代码了。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站