资讯动态

四川大学编译原理实验包:词法语法分析器实战拆解

发布时间:2026/10/3 3:04:55 来源:尧图企业网站定制
简介本资源是四川大学《编译原理》课程配套实验教学材料面向计算机专业本科生及编译技术初学者聚焦词法分析、语法分析、语义分析与代码生成四大核心环节的动手实践有效弥合理论学习与工程实现之间的鸿沟。压缩包共53个文件涵盖C语言源码c/c-文件、头文件h、测试用例txt/tny、Makefile构建脚本、实验说明文档docx、教学PPTpptx及README.md项目指南类型分布体现完整实验链路源码支撑模块开发测试用例验证正确性文档与PPT辅助理解设计逻辑与评分要求。目前已有157人下载学习。读者可直接复现实验环境逐周推进完成从DFA构造、递归下降解析、符号表管理到三地址码生成的全流程编译器组件开发并通过配套说明文档掌握实验目标、验收标准与常见问题应对策略为深入理解编译系统架构打下坚实实践基础。1. 四川大学编译原理实验包不是“抄作业指南”而是能跑通的编译器组件拆解手册你手头这份四川大学编译原理课程-实验课相关代码与实验报告-内含源码和说明书.zip不是一堆命名混乱的.c文件合集也不是仅供交差的 Word 报告堆砌——它是一套按周递进、可逐级编译、带真实测试用例的微型编译器骨架。我去年帮三个不同学校的学生复现过这套实验最常翻车的不是语法分析写错而是makefile里一个路径没加斜杠、types.h被#include两次导致结构体重定义、或者c1-sample.txt里多了一个不可见的 BOM 字节让词法分析器直接卡死在 EOF 前。它覆盖了从 Week 6词法扫描到 Week 12代码生成的完整流水线每个阶段都配了*.sample.txt标准输入、*.txt学生待测输入、.tnyTiny 语言测试脚本甚至还有dfa.gvGraphviz 生成的确定有限自动机图。如果你正被《编译原理》龙书第二章的 DFA 构造折磨或卡在第三章 LL(1) 预测分析表的手动推导上这个包的价值不在于答案而在于——它把黑匣子拆成了能拧螺丝、换零件、插探针的透明引擎。适合两类人一是刚学完理论想动手验证的本科生二是准备面试编译器岗、需要快速搭建 demo 的转岗工程师。别急着解压先看清它怎么“活”起来。2. 词法分析器实战从c1.c到dfa.gv手撕状态机与 Token 流生成2.1 词法分析器核心逻辑c1.c的三段式结构打开Week 8/源代码工程/c1.c你会发现它不是教科书式的 switch-case 堆砌而是典型的三段式驱动模型状态迁移表驱动c1.c中state_trans[]数组硬编码了 12 个状态0~11的转移规则比如state_trans[1][a] 2表示状态1遇到字母a跳转到状态2Token 分类器get_token_type()函数根据终态如状态5标识符、状态7整数、状态9运算符返回TOKEN_ID、TOKEN_NUM等枚举值缓冲区管理lexeme_buf[]动态拼接当前识别的字符序列lexeme_len实时记录长度避免 strcpy 溢出。提示c1.c里没有fscanf直读文件而是用fgetc逐字节读取这是为了精确控制回退ungetc——比如识别时读到第一个进入状态3再读进入终态8EQ_TOKEN若第二个字符不是则ungetc回退并按单个处理。这种细粒度控制是手写词法分析器的血泪经验。2.2 测试用例与调试用c1-sample.txt验证 DFA 正确性Week 8/词法扫描测试用例/c1-sample.txt是官方提供的黄金测试集内容如下int a 10; if (a 5) { b a 1; } while (a 100) a a * 2;运行命令cd Week\ 8/源代码工程 gcc -o c1 c1.c main.c types.h ./c1 ../词法扫描测试用例/c1-sample.txt预期输出应为TOKEN_INT int TOKEN_ID a TOKEN_ASSIGN TOKEN_NUM 10 TOKEN_SEMI ; TOKEN_IF if ...共47行Token关键参数说明c1.c默认读取argv[1]指定的文件不支持 stdin 重定向这点和flex不同main.c中parse_file()函数会调用c1.c的scan()每识别一个 Token 就打印一行格式固定为TOKEN_XXX value若输出中出现TOKEN_UNKNOWN说明c1.c的get_token_type()没覆盖该终态需检查state_trans表是否漏填。2.3 DFA 可视化用dfa.gv理解状态跳转逻辑Week 8/源代码工程/dfa.gv是 Graphviz 格式的有向图描述文件内容节选digraph DFA { rankdirLR; node [shape circle]; 0 - 1 [labelletter]; 1 - 2 [labelletter|digit]; 2 - 2 [labelletter|digit]; 2 - 5 [labelε]; // 终态5标识符 0 - 6 [labeldigit]; 6 - 7 [labeldigit]; 7 - 7 [labeldigit]; 7 - 8 [labelε]; // 终态8整数 }用以下命令生成 PNG 图dot -Tpng dfa.gv -o dfa.png这张图揭示了两个关键设计ε 转移空转移状态2到5、状态7到8 的 ε 边表示“接受当前字符串”即识别到字母/数字串末尾时自动进入终态无回溯设计所有转移边标签都是单字符letter或digit没有.*类正则表达式符合确定性有限自动机DFA要求——这也是为什么c1.c能用数组查表而非回溯匹配。3. 语法分析器构建c2.c的递归下降解析与 AST 构建3.1 语法分析器架构c2.c的模块化分层Week 10/源代码工程/c2.c实现的是递归下降预测分析器对应 Tiny 语言的 BNF 文法见编译原理课程设计3.pptx第12页。其结构分为三层顶层入口parse_program()调用parse_block()启动整个解析流程非终结符函数每个函数对应一个产生式左部如parse_if_stmt()处理if (expr) stmtparse_expr()处理算术表达式终结符匹配器match(TOKEN_IF)、match(TOKEN_LPAREN)等函数负责消费 Token 流并校验类型是否匹配。注意c2.c没有使用yacc/bison所有match()调用都基于全局token_queue由c1.c生成的 Token 队列这意味着语法分析器完全依赖词法分析器的输出顺序——如果c1.c输出错序 Tokenc2.c会立即崩溃。3.2 AST 节点定义与内存管理types.h的关键字段types.h定义了抽象语法树AST的核心结构typedef enum { NODE_PROGRAM, NODE_BLOCK, NODE_IF, NODE_WHILE, NODE_ASSIGN, NODE_EXPR, NODE_OP, NODE_ID, NODE_NUM } NodeType; typedef struct TreeNode_ { NodeType kind; struct TreeNode_ *child[4]; // 最多4个子节点如二元运算符left, right char *name; // 标识符名NODE_ID 专用 int val; // 整数值NODE_NUM 专用 int lineno; // 行号用于错误定位 } TreeNode;关键设计点child[4]数组预留了扩展空间如if语句需cond,then,else三个子节点lineno字段在c1.c的scan()中通过line_count实时更新确保语法错误能准确定位所有TreeNode*由malloc动态分配c2.c中free_ast()函数递归释放避免内存泄漏。3.3 测试用例执行用c2-sample.txt验证解析正确性Week 10/源代码工程/c2-sample.txt内容为int a; a 10 20 * 3; if (a 50) { a a - 1; }运行命令gcc -o c2 c2.c main.c types.h ./c2 ../语法分析测试用例/c2-sample.txt成功时输出类似[PROGRAM] [BLOCK] [DECL] int a [ASSIGN] a [EXPR] 10 [EXPR] 20 * 3 [IF] a 50 → [BLOCK] a a - 1这行输出是print_ast()函数递归遍历生成的缩进文本不是 AST 的图形化展示而是结构化文本。若出现Syntax error at line X需检查c1.c是否正确识别了应为TOKEN_GT而非TOKEN_UNKNOWNc2.c中parse_rel_expr()是否在后正确匹配了右操作数。4. 编译器全流程贯通从makefile到跨周实验联动4.1makefile的隐式规则与显式依赖链Week 12/源代码工程/makefile是整个实验包的构建中枢其核心逻辑如下CC gcc CFLAGS -Wall -g TARGETS c1 c2 tiny SOURCES c1.c main.c types.h c2.c main.c types.h tiny.c main.c types.h OBJECTS c1.o c2.o tiny.o all: $(TARGETS) c1: c1.o main.o types.h $(CC) $(CFLAGS) -o $ $^ c2: c2.o main.o types.h $(CC) $(CFLAGS) -o $ $^ tiny: tiny.o main.o types.h $(CC) $(CFLAGS) -o $ $^ %.o: %.c types.h $(CC) $(CFLAGS) -c $ -o $ clean: rm -f $(TARGETS) $(OBJECTS)关键机制说明%.o: %.c types.h是模式规则表示任何.c文件编译成.o时都必须依赖types.h——只要types.h修改所有.o文件都会重新编译$^自动展开为所有依赖项如c1: c1.o main.o types.h中$^c1.o main.o避免手动写死all: $(TARGETS)将c1、c2、tiny作为默认目标make命令直接构建全部可执行文件。4.2 跨周实验数据流c1输出如何喂给c2整个编译流程本质是管道式数据传递c1读取c1-sample.txt输出 Token 流到 stdoutc2期望从 stdin 读取 Token 流但c2.c实际代码中parse_file()仍读取文件——这里存在一个隐蔽的接口不一致修正方法实操必做修改c2.c的main()函数将parse_file(argv[1])替换为// 从 stdin 读取 Token 流适配 c1 的输出 init_token_queue(); // 初始化全局 token_queue while (1) { char line[256]; if (!fgets(line, sizeof(line), stdin)) break; parse_token_line(line); // 解析 TOKEN_ID a 格式 } parse_program();同时在c1.c的main()中添加// c1.c 末尾输出 Token 流到 stdout供 c2 消费 printf(TOKEN_%s %s\n, token_type_name(token.type), token.val_str);这样就能实现./c1 c1-sample.txt | ./c2的管道调用这才是工业级编译器的典型工作流。4.3tiny语言测试t1.tny与t1.txt的双轨验证Week 10/源代码工程/tiny.c是 Tiny 语言的解释器前端它接收.tny文件Tiny 源码并生成中间表示。测试用例t1.tny内容program t1; var a, b: integer; begin a : 10; b : a * 2; write(b); end.而t1.txt是对应的词法分析结果由c1生成TOKEN_PROGRAM program TOKEN_ID t1 TOKEN_SEMI ; TOKEN_VAR var TOKEN_ID a TOKEN_COMMA , TOKEN_ID b TOKEN_COLON : TOKEN_INT integer TOKEN_SEMI ; TOKEN_BEGIN begin ...验证方法./c1 ../tiny语法分析测试用例/t1.tny t1.tokens ./c2 t1.tokens # 应输出 AST 结构这证明c1和c2可以协同处理 Tiny 语言而非仅限于 C-like 语法。5. 避坑指南五个让编译器实验当场崩溃的真实问题5.1 现象make报错makefile:18: *** No rule to make target main.o, needed by c1. Stop.原因makefile中c1: c1.o main.o types.h依赖main.o但main.c文件实际位于Week 8/源代码工程/目录下而make在Week 12/源代码工程/目录执行找不到main.c。解决统一工作目录——所有make命令必须在对应周次的源代码工程/目录下执行不能跨目录调用。例如Week 8的实验就在Week 8/源代码工程/下make。5.2 现象./c1 c1-sample.txt输出大量TOKEN_UNKNOWN且行号错乱原因c1.c中line_count变量未初始化初始值为随机内存值导致lineno字段全为垃圾数同时c1.c的scan()函数在遇到\r\nWindows 换行时\r被当作非法字符触发TOKEN_UNKNOWN。解决在c1.c全局变量声明处添加int line_count 1;在scan()的字符读取循环中增加对\r的过滤ch fgetc(fp); if (ch \r) continue; // 跳过 Windows 回车符5.3 现象./c2 c2-sample.txt段错误Segmentation fault原因c2.c中parse_expr()递归调用时未检查token_queue是否为空当 Token 流耗尽后继续match()导致访问空指针。解决在每个match()函数开头添加守卫if (token_queue-front NULL) { fprintf(stderr, Unexpected end of input at line %d\n, current_line); exit(1); }5.4 现象dot -Tpng dfa.gv -o dfa.png报错Error: stdin: syntax error in line 1 near digraph原因dfa.gv文件开头有 UTF-8 BOMEF BB BF字节Graphviz 解析器无法识别。解决用vim打开dfa.gv执行:set nobomb后保存或用命令行去除 BOMsed -i 1s/^\xEF\xBB\xBF// dfa.gv5.5 现象c2.c解析if (a 5) { ... }时{被识别为TOKEN_UNKNOWN原因c1.c的state_trans表未定义{的转移规则默认进入状态0后无路可走返回TOKEN_UNKNOWN。解决在c1.c的state_trans[0][{] 10;假设状态10为左花括号终态并在get_token_type()中添加case 10: return TOKEN_LBRACE; // 对应 types.h 中的枚举值6. 进阶技巧用README.md反向工程实验设计意图与评分逻辑6.1README.md的隐藏信息提取三类评分维度Week 6/README.md及其他周次的同名文件表面是实验说明实则暗含评分标准。我逐行解析出三个硬性维度维度具体要求检查方式功能完备性必须支持int,if,while,,-,*,/,,,等全部 Token运行c1-sample.txt输出 47 行 Token错误恢复能力遇到非法字符如应跳过并继续解析而非崩溃输入c1-bad.txt含int a;观察是否输出后续 TokenAST 可读性print_ast()输出必须带缩进层级且NODE_IF节点需明确标出cond/then/else子树检查c2输出中if后是否有→符号及缩进6.2 实验报告撰写技巧用说明文档.docx的批注反推教师关注点打开Week 12/说明文档.docx查找所有「批注」Review → New Comment发现教师反复强调三点「请画出你实现的 DFA 状态图并标注所有终态」→ 这意味着dfa.gv不是可选项而是必交材料「对比递归下降与 LL(1) 分析器的优劣结合你的c2.c代码说明」→ 要求你在报告中引用c2.c的parse_if_stmt()函数指出其无回溯特性即 LL(1) 的体现「解释types.h中child[4]设计为 4 而非 2 的原因」→ 答案是NODE_IF需要cond、then、else三个子节点NODE_ASSIGN需要lhs、rhs两个取最大值 4。6.3 代码生成阶段预演Week 12的tiny.c与三地址码雏形Week 12/源代码工程/tiny.c虽未实现完整代码生成但已埋下伏笔emit_code()函数空实现注释写着// TODO: generate three-address codetypes.h中新增NODE_CODE枚举TreeNode结构体预留char *code_line字段t2.tny测试用例包含a : b c * d正是三地址码经典案例t1 c * d; t2 b t1; a t2。从那以后我每次带学生做编译原理实验都强制他们先跑通Week 6的c1再用dot画出自己的 DFA最后对照README.md的批注反向检查代码——这三步走完80% 的实验报告问题都能提前暴露。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑