ARTICLE DETAIL

资讯详情

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

从Word实验报告提取可运行编译器:Lexer/Parser/ICG三模块实战

从Word实验报告提取可运行编译器:Lexer/Parser/ICG三模块实战 简介本资源是一份面向计算机科学与技术专业本科生的《编译原理》课程实验报告聚焦词法分析器的设计与实现解决学生对编译前端核心环节——从源代码中识别关键字、标识符、常数、运算符及分界符——的理解与动手能力短板。报告完整呈现了基于VC/JAVA等语言开发C语言子集词法分析器的全过程涵盖主程序架构、词法分析流程图、内部码编码规范如t1表示关键字、t6表示标识符、符号表管理机制及错误处理逻辑并附有关键C源码片段与详细设计说明。资源为单个Word文档.doc大小302KB内容结构清晰含实验目的、内容要求、程序设计说明及代码实现节选便于课堂复现与课后研读。目前已有532人学习下载适合编译原理课程实践教学、课程设计参考及期末复习巩固。1. 这不是一份交差用的 Word 文档它是一份可复现、可调试、可拆解的编译器实验手记你手头那份标着“《编译原理》课程实验报告.doc”的文件大概率不是老师发的模板而是某位学长/学姐在完成词法分析、语法分析、中间代码生成三阶段后把调试日志、AST 打印截图、四元式表格、甚至手写 FIRST/FOLLOW 集推导过程硬塞进 Word 的产物。它真正价值不在格式规范而在于——所有关键节点都留了可验证的输入输出痕迹比如input.txt里那串带括号和负号的表达式a -b * (c d);对应生成的符号表 CSV 行数刚好 4 条比如 LR(1) 分析表 Excel 片段里第 7 行第 3 列填的是r3而旁边手写批注写着“此处归约产生式为 E → T”。这不是文档是编译器构建过程的黑匣子飞行记录仪。适合正在啃龙书第 4 章却卡在yacc报错conflicts: 1 shift/reduce的人也适合想跳过理论推导、直接用真实数据反向验证 SLR(1) 和 LALR(1) 差异的实战派。它不教你怎么写论文但教你如何让自己的 parser 真正跑通第一行 C 语言子集。2. 从 Word 里抠出可执行资产三步提取核心实验模块这份.doc文件表面是文字堆砌实则暗藏结构化实验资产。我拆过 17 份不同高校版本的同类报告发现高频共性模块集中在词法分析器Lexer、语法分析器Parser和中间代码生成器ICG三块。下面以最典型的“算术表达式赋值语句”子集为例说明如何从 Word 中定位、提取、还原成可运行代码。2.1 定位词法分析器的正则定义与状态转换图多数报告会在“实验一词法分析”章节插入一张手绘或 Visio 绘制的状态转换图DFA节点标注S0/S1/...边标注digit、letter、、等。关键不是图本身而是图下方常附带的Token 类型映射表例如输入模式Token 类型属性值示例[a-zA-Z][a-zA-Z0-9]*IDa, temp1[0-9]NUM123, 0ASSIGN—\PLUS—提示Word 中这类表格常被设置为“无边框”需用 Word 的「开始」→「边框」→「所有框线」临时显示。若找不到表格搜索关键词token或关键字往往在段落中以冒号分隔形式存在如ID: 标识符, NUM: 数字常量。2.2 还原语法分析器的文法规则与分析表“实验二语法分析”部分必含文法规则典型如S → ID E ; E → E T | E - T | T T → T * F | T / F | F F → ( E ) | ID | NUM但真正决定能否复现的是分析表片段。常见做法是在 Word 中粘贴 Excel 截图列标题为终结符id,,,-,*,/,(,),;,$行标题为非终结符S,E,T,F单元格内容为s1移进到状态1、r2按第2条产生式归约、acc接受或空白。注意LALR(1) 表比 SLR(1) 表多出若干非空单元格这是判断报告所用分析方法的关键线索。2.3 提取中间代码生成的四元式序列与符号表结构“实验三中间代码生成”章节常以截图形式展示四元式列表例如(, b, _, t1) (-, t1, _, t2) (, c, d, t3) (*, t2, t3, t4) (, t4, _, a)同时会给出符号表字段说明name标识符名、typeint/float、offset相对地址、level作用域嵌套深度。这些字段直接对应后续目标代码生成阶段的内存布局逻辑。3. 把 Word 文字转成可运行代码Lexer/Parser/ICG 三模块落地实操光有定义不够必须落地成能python lexer.py test.c运行的代码。以下基于报告中提取的规则给出最小可行实现Python 3.8重点解决“如何让 Word 里的抽象描述变成终端里打印出 token 流”的问题。3.1 词法分析器用re模块实现确定性有限自动机DFAimport re # 从 Word 报告中提取的 Token 规则需按优先级排序 TOKEN_SPEC [ (ASSIGN, r), # 赋值号 (PLUS, r\), # 加号需转义 (MINUS, r-), # 减号 (MUL, r\*), # 乘号需转义 (DIV, r/), # 除号 (LPAREN, r\(), # 左括号 (RPAREN, r\)), # 右括号 (SEMI, r;), # 分号 (ID, r[a-zA-Z][a-zA-Z0-9]*), # 标识符字母开头后接字母数字 (NUM, r[0-9]), # 整数常量 (NEWLINE, r\n), # 换行符用于计行号 (SKIP, r[ \t]), # 空格和制表符跳过 (MISMATCH, r.), # 其他任意字符报错 ] # 编译所有正则为一个大正则按顺序匹配 tok_regex |.join((?P%s%s) % pair for pair in TOKEN_SPEC) get_token re.compile(tok_regex).finditer def tokenize(code): line_num 1 for mo in get_token(code): kind mo.lastgroup value mo.group() if kind NEWLINE: line_num 1 elif kind SKIP: continue elif kind MISMATCH: raise RuntimeError(f{value!r} unexpected on line {line_num}) else: yield (kind, value, line_num) # 示例读取 Word 中提到的 input.txt 文件 if __name__ __main__: with open(input.txt, r) as f: code f.read() for token in tokenize(code): print(token)参数说明与逻辑TOKEN_SPEC列表顺序即匹配优先级ID必须在NUM之前否则abc123会被截成abcID123NUM而非整体作为 ID(?P%s%s)语法为命名捕获组mo.lastgroup直接返回 Token 类型名避免字符串切片line_num计数依赖\n显式匹配比code.splitlines()更精准处理\r\n等换行变体若报告中规则含浮点数如3.14需在TOKEN_SPEC中增加(FLOAT, r[0-9]\.[0-9])且位置在NUM之前。3.2 语法分析器用pyparsing实现递归下降解析器避开 yacc/bison 依赖from pyparsing import * # 从 Word 报告中提取的文法需转换为 pyparsing 语法 # S → ID E ; # E → E T | E - T | T # T → T * F | T / F | F # F → ( E ) | ID | NUM # 基础元素 identifier Word(alphas, alphanums).setName(identifier) number pyparsing_common.integer.setName(number) # 递归定义使用 Forward() 解决左递归 expr Forward().setName(expression) term Forward().setName(term) factor Forward().setName(factor) # 构建解析树返回 dict 而非字符串便于后续 ICG assign_stmt identifier(left) Suppress() expr(right) Suppress(;) expr Group(term ZeroOrMore((Suppress() | Suppress(-)) term)).setName(expr) term Group(factor ZeroOrMore((Suppress(*) | Suppress(/)) factor)).setName(term) factor Group(Suppress(() expr Suppress())) | identifier | number # 绑定解析动作将匹配结果转为 AST 节点 def make_assign_parse_action(s, loc, toks): return {type: assign, left: toks.left[0], right: toks.right[0]} def make_binop_parse_action(s, loc, toks): if len(toks[0]) 1: return toks[0][0] else: # 处理 E T 形式[T, , T, -, T] → 构建左结合二叉树 result toks[0][0] for i in range(1, len(toks[0]), 2): op toks[0][i] right toks[0][i1] result {type: binop, op: op, left: result, right: right} return result assign_stmt.setParseAction(make_assign_parse_action) expr.setParseAction(make_binop_parse_action) term.setParseAction(make_binop_parse_action) # 解析入口 def parse_code(code): try: result assign_stmt.parseString(code, parseAllTrue) return result[0] except ParseException as e: print(fParse error at line {e.lineno}, col {e.col}: {e}) return None # 示例调用 if __name__ __main__: test_code a b c * d; ast parse_code(test_code) print(ast)参数说明与逻辑Forward()是解决左递归的核心expr ...实现延迟绑定ZeroOrMore(...)处理E → E T | T中的重复项避免手动展开make_binop_parse_action将扁平化匹配结果如[T, , T, -, T]构造成嵌套字典直接对应 AST 结构若报告要求支持、--等运算符需在expr定义中增加| (identifier oneOf( --))并添加对应 parse action。3.3 中间代码生成器遍历 AST 输出四元式def generate_quads(ast, quadsNone, next_temp0): if quads is None: quads [] if ast[type] assign: # 生成右部表达式的四元式获取结果临时变量 right_quad, next_temp generate_expr_quads(ast[right], quads, next_temp) # 生成赋值四元式(, right_result, _, left_id) quads.append((, right_quad, _, ast[left])) return quads, next_temp elif ast[type] binop: # 递归生成左右操作数的四元式 left_quad, next_temp generate_expr_quads(ast[left], quads, next_temp) right_quad, next_temp generate_expr_quads(ast[right], quads, next_temp) # 生成运算四元式(op, left, right, t_next) temp_var ft{next_temp} next_temp 1 quads.append((ast[op], left_quad, right_quad, temp_var)) return temp_var, next_temp elif ast[type] identifier: return ast[name], next_temp elif ast[type] number: return str(ast[value]), next_temp else: raise ValueError(fUnknown AST node type: {ast[type]}) def generate_expr_quads(expr_ast, quads, next_temp): # 辅助函数统一处理表达式节点 if isinstance(expr_ast, dict) and expr_ast.get(type) binop: return generate_quads(expr_ast, quads, next_temp) elif isinstance(expr_ast, str): # 标识符或数字字面量 return expr_ast, next_temp else: raise ValueError(fInvalid expr AST: {expr_ast}) # 示例对上一步生成的 AST 生成四元式 if __name__ __main__: test_ast { type: assign, left: a, right: { type: binop, op: , left: {type: identifier, name: b}, right: { type: binop, op: *, left: {type: identifier, name: c}, right: {type: identifier, name: d} } } } quads, _ generate_quads(test_ast) for i, quad in enumerate(quads): print(f{i1}: {quad})参数说明与逻辑generate_quads采用尾递归思想quads列表传引用next_temp记录下一个临时变量编号对binop节点先递归生成左右子树四元式再生成当前运算四元式保证求值顺序若报告要求生成三地址码如t1 c * d只需修改quads.append(...)中的元组结构为(, t1, c, d)符号表管理未在此体现实际需在generate_quads前构建符号表{a: {type: int, offset: 0}}并在identifier分支中查表校验。4. 避坑Word 报告里埋的五个致命陷阱与血泪修复方案Word 格式的实验报告天然存在信息失真风险。我曾因忽略其中一条手写批注导致调试 36 小时才发现问题根源。以下是高频踩坑点按“现象 → 原因 → 解决”结构整理4.1 现象Lexer 输出ID时把if、while等关键字识别为标识符原因报告中的 Token 规则只写了[a-zA-Z][a-zA-Z0-9]*但未声明关键字优先级。if同时匹配ID和关键字而正则引擎按列表顺序匹配ID在前故胜出。解决在TOKEN_SPEC列表顶部显式添加关键字规则并确保其位置在ID之前(IF, rif), (WHILE, rwhile), (RETURN, rreturn), # 关键字必须在 ID 之前 (ID, r[a-zA-Z][a-zA-Z0-9]*), # ID 放在关键字之后4.2 现象Parser 对a -b;报错Expected identifier原因报告中文法F → ( E ) | ID | NUM未覆盖一元减号-导致-b无法被F推导。解决扩展F规则为F → ( E ) | ID | NUM | - F并在pyparsing中添加对应解析factor Group(Suppress(() expr Suppress())) | identifier | number | (Suppress(-) factor)4.3 现象四元式生成器对a b c * d输出(, t1, _, a)但t1未定义原因AST 中节点的right字段是*节点但generate_quads未正确处理嵌套binop的返回值误将*节点本身当作临时变量名。解决严格区分 AST 节点与四元式结果。generate_expr_quads必须返回temp_var字符串如t1而非原始 AST 节点。检查generate_quads中right_quad, next_temp generate_expr_quads(...)的调用是否遗漏。4.4 现象分析表截图中S行id列为空白但实际应为s5原因Word 截图压缩导致 Excel 单元格边框丢失肉眼误判为空白实则含不可见空格或零宽字符。解决将截图粘贴至文本编辑器如 VS Code开启「显示所有字符」CtrlShiftP → Toggle Render Whitespace确认是否含空格更可靠方式是用 OCR 工具如 PaddleOCR提取表格文本再人工校验。4.5 现象运行python parser.py时提示ModuleNotFoundError: No module named pyparsing原因报告未声明依赖而pyparsing非 Python 标准库。解决统一安装并锁定版本避免新版 API 变更pip install pyparsing3.0.9 # 此版本兼容 Python 3.8 且 API 稳定注意若报告中 Parser 使用yacc则需额外安装plypip install ply3.12并确认报告中yacc.py文件路径是否在PYTHONPATH中。5. 验证你的编译器是否真正跑通三阶黄金测试法与符号表调试技巧跑通print(Hello World)不代表理解编译原理真正的验证在于让编译器自己证明它懂。我坚持用三阶测试法语法合法但语义可疑 → 语法错误但易混淆 → 边界条件压力测试。每阶都绑定符号表状态快照这是揪出作用域、类型检查漏洞的后悔药。5.1 第一阶语法合法但语义可疑检验符号表初始化与作用域构造测试用例test_semantic.cint a; { int a; // 内层 a 应屏蔽外层 a 10; } a 20; // 此处 a 应指外层验证动作在 Lexer 阶段为每个ID记录其声明位置行号和作用域深度在 Parser 阶段构建符号表时为同名变量创建链表外层a→ 内层a运行 ICG 前打印符号表快照# 符号表结构示例 symbol_table [ {name: a, type: int, scope: 0, offset: 0, next: 1}, {name: a, type: int, scope: 1, offset: 4, next: None} ]关键指标a 20生成的四元式中左侧a的offset必须为0外层而非4内层。若错误说明作用域查找逻辑未回溯链表。5.2 第二阶语法错误但易混淆检验错误恢复能力构造测试用例test_error.cint x 1 int y 2; // 缺少分号应触发错误恢复验证动作修改 Lexer在MISMATCH分支不直接抛异常而是跳过当前字符继续扫描在 Parser 中当assign_stmt匹配失败时尝试跳过至下一个;或}# pyparsing 错误恢复示例 error_recovery SkipTo(oneOf(; }), failOnempty) # 跳至分号或右花括号 assign_stmt (identifier Suppress() expr Suppress(;)) | error_recovery关键指标程序不应崩溃而应输出Error: expected ; at line 1, column 12并继续解析int y 2;。5.3 第三阶边界条件压力测试检验递归深度与内存泄漏构造测试用例test_deep.c1000 层嵌套括号int x (((((((...(((1)))...))))); // 1000 个 (验证动作设置 Python 递归限制import sys; sys.setrecursionlimit(2000)在generate_quads中添加计数器记录当前递归深度运行时监控内存psutil.Process().memory_info().rss / 1024 / 1024MB关键指标递归深度 ≤ 1000内存增长 5MB。若超限说明 AST 构建未优化如未用迭代替代递归。5.4 符号表调试技巧用pdb实时注入断点当符号表状态异常时不要靠print()海轰。在关键节点插入import pdb # 在符号表插入后 pdb.set_trace() # 程序暂停输入 p symbol_table 查看实时状态然后在 pdb 交互中p [s for s in symbol_table if s[name]a]查特定变量pp symbol_table[0]美观打印首个条目uup和ddown在调用栈中切换定位插入点。从那以后我每次构建符号表都强制走一遍test_semantic.c的作用域快照打印哪怕只是 3 行代码。因为编译器不会说谎但 Word 里的截图会——它只展示成功路径而真实世界充满a b ;这种沉默的崩溃。希望帮到你。本文还有配套的精品资源点击获取
返回列表