
简介这是暨南大学编译原理课程在Engintime CP Lab平台上的实验报告面向计算机科学与技术专业本科生及编译原理学习者聚焦“从正则表达式到NFA”和“使用Lex自动生成扫描程序”两项核心设计性实验。报告按本科实验报告规范编排完整呈现了实验环境搭建、项目从CodeCode.net平台领取与本地克隆、CP Lab主窗口布局和工具栏使用等操作过程并深入分析了主要源文件main.c定义程序入口与NFA片段构造RegexpToPost.c实现re2post后缀表达式转换NFAFragmentStack.c提供NFA片段栈操作。对关键数据结构和函数进行了说明同时记录了项目生成、语法错误定位和演示模式调试的步骤有助于理解正则表达式到NFA的转换算法和Lex词法分析器自动生成原理。资源压缩包内含1个doc文件总大小1.75MB便于打印和查阅。已有1471人学习下载适合需要完成同类实验、准备编译原理实验报告或复习编译器前端知识的读者能直接借鉴其报告结构与代码分析思路。1. 编译原理 CP lab 实验报告别把时间耗在排版上先想清楚这几个问题如果你正在准备编译原理的 CP lab 实验报告八成已经对着词法分析、语法分析、符号表这些名词头疼过了。这份报告不是简单记录“我写了哪几个函数”而是老师判断你有没有真正理解编译器前端工作方式的唯一凭证。我见过太多同学花三天调通一个递归下降解析器最后却因为报告结构混乱被扣分——实验本身满分报告却成了黑匣子。这篇笔记我会把 CP lab 从实验设计到报告撰写的完整路径拆开告诉你每一步该做什么、参数怎么调、坑在哪里帮你把劳动成果转化成一份能站得住脚的文档。2. 读懂 CP lab 的评分点实验报告到底在考察什么2.1 从实验指导书反推验收标准绝大多数编译原理实验的 CP lab 都会给出一份实验指导书里面列了“实验目的”“实验内容”“验收要求”。但很多同学只看内容忽略目的和验收标准。我一般拿到指导书第一件事是先把“验收要求”里的动词圈出来是“实现××算法”还是“设计××数据结构”还是“分析××过程”。这些动词直接决定了报告里必须出现什么。如果你的指导书写着“理解词法分析器的构造原理”那报告里至少要有状态转换图或正则表达式到 NFA 的转换过程如果写“掌握自顶向下的语法分析方法”那递归下降的 FIRST/FOLLOW 集计算过程就不能少。反过来如果验收只看程序是否能跑通那报告再华丽也没用。所以要学会从指导书反推把每个验收点对应到报告的一节这样写出来的东西天然满足评分表。另一个容易忽略的是评分表中“过程分”与“结果分”的权重。有些学校要求现场演示有些只收电子版报告。如果是现场演示报告里要多放中间过程的截图和测试用例方便答辩时快速定位如果只交文档那就要把设计思路写透彻因为老师没有机会问你问题只能从文字判断你是否理解了。2.2 报告里必须出现的四类内容我批过不少实验报告发现高分报告通常稳定包含四类内容。第一是问题定义你做的这个实验到底要实现什么输入是什么、输出是什么边界条件怎么约定。很多同学省略这一步直接贴代码这是最亏的——因为老师需要确认你理解实验目标。第二是设计决策你选择用什么方法实现为什么不用其他方法。比如词法分析器用状态机还是直接用正则表达式库语法分析用递归下降还是 LR。不需要长篇大论三五行讲清理由就行但这部分最能体现思考深度。第三是核心实现的说明不是贴程序清单而是把关键数据结构和算法讲明白。比如 token 结构体包含哪些字段、符号表用什么组织、错误恢复策略是什么。配一小段核心代码加注释比贴十个文件都管用。第四是测试与验证你用了哪些测试用例覆盖了哪些分支有没有边界情况。这一部分是最容易凑数的也是最容易被识破的。如果你只测了一个“int a 1;”就敢说“程序正确”老师一眼就能看出来。3. 从词法到语法CP lab 核心实验的设计顺序3.1 词法分析器token 定义与状态机实现词法分析是编译前端的第一关。CP lab 里最常见的做法是要求你识别标识符、关键字、数字、运算符、分隔符等并输出 token 流。我建议先别急着写代码先定义好 token 的类型枚举和属性值结构。下面是一个典型的 C 语言风格定义typedef enum { TOKEN_IDENT, // 标识符 TOKEN_NUMBER, // 整型/浮点数字 TOKEN_KEYWORD, // 关键字 TOKEN_OPERATOR, // 运算符 TOKEN_DELIMITER, // 分隔符 TOKEN_EOF // 结束标志 } TokenType; typedef struct { TokenType type; char lexeme[64]; // 原始词素 int line; // 行号便于报错 int col; // 列号 union { int intVal; double floatVal; } attr; // 属性值符号表需要的部分信息 } Token;这段代码的逻辑不用多解释关键是三个地方line 和 col 字段很多人会漏掉但它们是后续语法分析报错和实验报告里测试用例展示的重要依据union 里面放属性值是为了给符号表提供类型信息lexeme 数组的大小要根据实验要求定如果支持长标识符建议改成动态分配或固定 256。写完数据结构后状态机的实现有两种常见路线。一是手工写状态转移适合关键字和标识符的区分二是用正则表达式做模式匹配实现快但不容易讲清楚内部机制。如果你希望报告有深度我建议至少把标识符和数字的状态图画出来哪怕只画一个简化版。状态图放在报告里比对着一堆代码讲“用 switch 判断字符”要直观得多。3.2 语法分析递归下降与 LL(1) 的选择语法分析是 CP lab 的重头戏也是区分报告层次的地方。很多学校指定要用递归下降法因为它写起来直观而且对文法本身的要求容易满足。但递归下降有一个前提文法必须无左递归、无回溯。所以实验第一步不是写 parser而是先把文法改写成 LL(1) 形式。这里举一个常见的表达式文法例子。假设你要支持加法、减法、乘法、除法和括号原始文法可能是E - E T | E - T | T T - T * F | T / F | F F - ( E ) | num这个文法有左递归不能直接用。改写后的 LL(1) 文法为E - T E E - T E | - T E | ε T - F T T - * F T | / F T | ε F - ( E ) | num改写完以后每个非终结符对应一个函数代码结构和文法一一对应。递归下降解析器里最常见的错误是忘了处理 ε 产生式导致读不到预期字符时直接报错。我在写这类代码时习惯在每个函数开头先打印“进入某非终结符”的日志调试完再注释掉这个日志在报告里也可以作为调试过程的证据。如果你实验要求用 LL(1) 分析表而不是递归下降那就要先求 FIRST 和 FOLLOW 集合。这里有个血泪经验FOLLOW 集合中一定不要漏掉 EOF 标记。很多同学算 FOLLOW(E) 时只算了右括号忘了结束符结果分析表少了一条接受状态程序跑到最后就崩了。计算完两个集合后可以用一个二维数组表示分析表行是非终结符列是终结符单元格里是对应的产生式或错误标记。3.3 符号表与语义动作让实验报告有深度大多数 CP lab 会要求做符号表管理哪怕不做完整的语义分析。符号表的作用是记录标识符的类型、作用域、声明位置等信息。最简单的实现是链表或哈希表但要注意作用域的处理——如果实验不要求块级作用域可以用一张全局表如果要求支持多个作用域我建议用栈结构每进入一个复合语句压入一层。下面是符号表条目的一种设计typedef struct Symbol { char name[64]; // 变量名 int type; // 类型编码int/float/... int scopeLevel; // 所属作用域层级 int lineDecl; // 声明所在行 struct Symbol *next; // 哈希链或作用域链 } Symbol;这里的 scopeLevel 是实验报告里的一个亮点。你可以在报告里写清楚符号表按作用域分层查找时从当前层向上逐层找变量遮蔽shadowing问题就自然解决了。配合一个简单的测试用例比如在同一函数里声明两次同名变量展示查表结果是内层的那个这比写一千字解释“什么是符号表”更有说服力。语义动作通常指在语法分析的同时执行一些属性计算或类型检查。如果实验要求做类型检查你可以在递归下降的parseAssignStmt里判断赋值左右两边的类型是否一致。这类语义动作代码不需要多复杂但要在报告里说明它挂在哪个语法分析步骤上。4. 把实验过程写成可复现的报告结构、图表与关键代码4.1 报告结构问题描述、设计、实现、测试一份完整的 CP lab 实验报告我习惯按五个部分组织顺序固定实验内容与要求、总体设计、详细实现、测试与结果、分析总结。不要自作聪明换顺序评分老师每天看几十份反常结构只会让人找不着重点。“实验内容与要求”不是抄指导书而是用自己的话概括然后列出你实现的完整功能点。这里有个技巧把你实现的功能做成清单例如“支持关键字 15 个”“支持 - * / % 及括号”“错误信息包含行号和预期字符”。这样老师一眼看到边界范围比一大段描述清楚得多。“总体设计”放框图。不用画多好看PowerPoint 画个层次图就行输入源码 → 词法分析器 → token 流 → 语法分析器 → 抽象语法树/语法树 → 符号表。这个图是报告里最值钱的部分能证明你的系统有模块化意识。“详细实现”按模块写每个模块的标题就是函数名或数据结构名。别按文件顺序写按数据流顺序写先 token 结构再词法分析再符号表再语法分析。每个小节里放一到两段关键代码然后逐段解释设计意图。4.2 实验截图与测试用例怎么证明你的程序是对的测试部分是报告里水分最大的地方也是我重点看的地方。判断一份报告是否用心只看三点测试用例是否覆盖正常、边界、异常三种情况是否给出输入和对应的输出是否对输出做了断言而不是只贴一张运行截图。推荐的做法是做一个表格列名是“编号、测试输入、预期输出、实际输出、说明”。比如编号 T01输入int a 10;预期输出token: int 关键字等说明“验证关键字识别”。这个表格放在报告里比三张截图有用得多。还有一点很容易被忽略错误处理的测试。比如输入a ;你的程序应该给出什么错误信息、在哪一行报错。如果你能在报告里展示这类反例说明你考虑了健壮性这在评分时很加分。不要只展示正确路径那会让老师怀疑你是不是把错误分支写死了。4.3 代码片段怎么贴才不显得像拼凑一个常见的翻车现场是报告里贴了十几个文件每个文件几百行但没有任何注释和说明。这不但不能证明你写了代码反而暴露你没有理解自己实现。正确做法是只贴核心代码而且要经过裁剪。我一般会遵守几条原则第一每段代码不超过 50 行超过就把非关键部分用注释替换第二代码中的关键逻辑一定要有中文注释注释不是“定义变量”而是“查符号表若未命中则报未声明错误”第三代码前后要有段落解释这段代码被谁调用、在整体流程中的位置。比如展示递归下降解析的expr()函数你只需要贴出函数前几行说明如何调用term()并处理加减号即可不需要把整套 800 行 parser 都放进来。报告是给人看的不是给编译器看的。5. CP lab 实验报告的避坑指南这些错我见过太多5.1 现象报告写了 30 页但答辩一问三不知原因把大部分篇幅花在代码清单和环境配置上没有自己复述过设计逻辑。解决方法是写完后试着不看报告口头讲一遍“你的词法分析器遇到一个数字怎么处理、遇到非法字符怎么处理”如果讲不通就说明你还没吃透代码。在报告“分析总结”部分主动写下自己实现时遇到的三个难点和解决办法这样答辩时老师问到你你至少有话可说。5.2 现象测试用例只有“hello world”原因不知道哪些分支需要覆盖以为程序不崩溃就是正确。解决方法是根据词法 token 类型和语法产生式列出等价类。比如运算符至少覆盖单字符和双字符和如果是两个 token要分别测数字要覆盖整数、浮点、连续多个数字、前导零的情况。把这些列成测试用例表并注明每项的覆盖目标老师就无法质疑你的验证完备性。5.3 现象代码和报告对不上贴的代码根本不在工程里原因先把代码写完最后再抄代码结果抄混了版本。解决方法是每完成一个功能立刻把对应的代码片段和运行结果放进报告草稿。我一般会维护一份“报告素材清单”把每个模块的代码文件路径、测试用例、截图名记录下来最后写报告时直接取。千万别在提交前一晚熬夜从 IDE 里复制。5.4 现象把实验报告写成源码注释整页只有“创建哈希表”“分配内存”原因误以为贴满代码就是工作量。但源代码注释是给人读代码用的实验报告讲的是抽象层次更高的设计决策。解决方法是每个模块要先写“这个模块负责做什么、输入输出是什么”再放一段代码作为例证。记住一个原则报告里的每段代码都必须回答一个“为什么”而注释只需要回答“是什么”。6. 让报告加分的几个细节从“做完”到“做好”最后分享几个让报告从合格变成优秀的小技巧。第一在总体设计里加一个“异常处理流程”的小图画出词法错误、语法错误、符号表未声明错误分别在哪里报、在哪里恢复。大部分同学的报告只有正常流程加了这个图就显得你想得比别人多。第二在“详细实现”里穿插“对比与选型”内容。比如你写的符号表用了哈希表而不是顺序链表就写一句“哈希表查找 O(1)链表 O(n)虽然实验数据量小看不出差别但实习项目里我会选哈希表”。这种话很能体现你思考过而不是只完成作业。第三写一个“已知限制与改进方向”段落。这是我最喜欢看的内容。比如你可以写“当前语法分析未处理注释嵌套改进方向是进入注释状态后维护一个计数器”。承认缺点是自信的表现也比写“程序无 bug”这种话可信得多。我自己带实验课时从来不看报告的长短只看有没有这些思考痕迹。这些年一个感受是编译原理实验报告是少数几个“做得好不如写得好”的东西但不是靠文字吹出来的好而是靠你在每个设计选择上多问一句“为什么”堆出来的好。希望这份笔记帮你在动手之前先理清头绪少走我当年走过的弯路。本文还有配套的精品资源点击获取