ARTICLE DETAIL

资讯详情

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

吉大编译原理实验代码:可调试可延展的编译器工程脚手架

吉大编译原理实验代码:可调试可延展的编译器工程脚手架 简介本资源是吉林大学编译原理课程配套的完整实践资料包面向计算机专业本科生及编译技术初学者聚焦编译器设计全流程实操训练有效解决理论抽象、缺乏工程落地参考的常见学习痛点。压缩包共27个文件涵盖5个Word实验报告与模板含5517-0611小组四次阶段性报告、4个Java源码文件Compiler.java、DoToken.java等核心模块及9个class字节码文件支撑从词法分析、语法解析到中间代码生成的完整实验链另含SNL语言介绍文档、实验要求PPT、《编译程序的设计与实现》电子书稿PDF及RAR源码包形成“理论—规范—代码—验证”闭环学习体系。资源大小4.86MB结构清晰、即下即用。已有1207人学习下载读者可直接复现教学级编译器架构、对照实验报告撰写范式、逐行研读Eclipse工程下的Java实现细节并基于TestData.txt等样例完成测试验证。1. 吉林大学编译原理设计代码实验报告.zip不是“交作业包”而是可复现、可调试、可延展的编译器工程脚手架你下载到的这个.zip文件表面看是“吉大某届学生交上去的实验材料”但实际拆开后会发现它是一套完整覆盖词法分析→语法分析→中间代码生成→目标代码优化全链路的、带真实输入输出验证的编译器前端工程。它不依赖 IDE 图形界面所有模块用 Python主 C部分优化器实现核心数据结构如符号表、抽象语法树 AST、四元式序列全部显式建模每阶段输出都可人工比对——这意味着你不仅能“看懂报告里写的流程”还能在命令行里python lexer.py test.c看到 token 流逐行打印能python parser.py test.c | dot -Tpng -o ast.png直接生成 AST 可视化图甚至能把生成的三地址码喂进自研的寄存器分配器跑出汇编片段。适合两类人一是刚学完龙书第二章、卡在“LL(1) 表怎么填”上的本科生需要一个可打断点、可改文法、可替换分析算法的沙盒二是准备课程设计或毕设选题的高年级学生它提供了从.c子集到 x86-64 汇编的最小可行路径且所有实验报告中的“设计思路”“测试用例”“错误分析”都对应着代码里的TODO注释和test/目录下的.in/.out文件。这不是模板是活的编译器骨架。2. 从 ZIP 解压到可运行环境准备、目录结构与最小验证命令2.1 解压后第一眼该看什么三个关键目录与两个隐藏约束解压吉林大学编译原理设计代码实验报告.zip后你会看到如下主结构├── code/ # 所有源码Python 主 少量 C │ ├── lexer/ # 词法分析器正则驱动支持中文标识符 │ ├── parser/ # 语法分析器递归下降 LL(1) 表驱动双模式 │ ├── ir/ # 中间表示三地址码生成含基本块划分 │ ├── optimizer/ # 优化模块常量传播 复写传播C 实现 │ └── backend/ # 后端x86-64 ATT 汇编生成含栈帧管理 ├── test/ # 测试用例集覆盖全部实验要求 │ ├── valid/ # 合法程序.c 后缀含注释说明考点 │ └── invalid/ # 非法程序用于测试错误恢复能力 ├── doc/ # 实验报告 PDF Word含手绘 DFA/NFA 转换图 └── run.sh # 一键运行全流程的 shell 脚本Linux/macOS两个必须提前确认的约束Python 版本锁定为 3.8–3.10lexer 使用re.Pattern的fullmatch()方法3.4但 parser 依赖ast.literal_eval()对dict的安全解析3.8 引入strict参数控制3.11 因ast模块内部重构导致NodeVisitor子类行为微变已知在ir/generator.py第 127 行触发AttributeError: Call object has no attribute argsC 编译器需支持-stdc99optimizer 目录下regalloc.c使用了restrict关键字C99 标准GCC 4.8 / Clang 3.1 均兼容但 MinGW-w64 默认启用-stdgnu11需手动加-stdc99。提示不要直接双击run.sh先执行bash run.sh --dry-run查看环境检测结果。它会检查python3 --version、gcc --version、dot -VGraphviz 用于 AST 可视化是否就位并输出缺失项提示。2.2 用 3 行命令跑通第一个测试词法分析器的最小闭环我们以test/valid/hello.c为例验证词法分析器能否正确切分cd code/lexer python lexer.py ../../test/valid/hello.c预期输出截取前 10 行LINE 1: KEYWORD int LINE 1: IDENTIFIER main LINE 1: SYMBOL ( LINE 1: SYMBOL ) LINE 1: SYMBOL { LINE 2: KEYWORD printf LINE 2: SYMBOL ( LINE 2: STRING_LITERAL Hello, World!\\n LINE 2: SYMBOL ) LINE 2: SYMBOL ;这行命令背后发生了什么lexer.py加载token_rules.py中预定义的 12 条正则规则按优先级排序对输入文件逐行扫描每个匹配成功 token 会调用Token(pos, type, value)构造函数其中pos是(行号, 列号)元组type是枚举值KEYWORD/IDENTIFIER/NUMBER等value是原始字符串输出格式强制对齐LINE {n}: {TYPE} {value}方便人工核对——这是吉大实验报告评分标准第 3 条明确要求的“输出可读性”。注意若出现SyntaxError: invalid escape sequence \n说明你的hello.c文件里\n是 Windows 风格\r\n而 lexer 的正则r([^\\]|\\.)*未处理\r。解决方案用dos2unix ../../test/valid/hello.c转换或修改token_rules.py第 42 行正则为r([^\r\n\\]|\\.)*。2.3 语法分析器的两种启动方式递归下降 vs LL(1) 表驱动parser 目录下提供两种分析器实现对应实验报告中“方案对比”章节recursive_descent.py纯 Python 实现parse_program()→parse_function_def()→parse_statement_list()逐层递归适合理解语法规则如何映射到代码ll1_parser.py基于预计算的parsing_table.json运行该文件由gen_table.py读取grammar.txt自动生成grammar.txt是吉大指定的 23 条产生式。启动 LL(1) 分析器的命令cd code/parser python ll1_parser.py ../../test/valid/simple_if.c --verbose--verbose参数会输出每一步的栈顶符号、输入符号、应用的产生式例如STACK: [$] INPUT: [if, (, a, , 1, ), {, ...] ACTION: shift STACK: [if, (, E, )] INPUT: [{, a, , 2, ;, }] ACTION: reduce E - ID OP NUM关键参数说明--table-path指定parsing_table.json路径默认同目录--grammar-path指定grammar.txt路径修改文法后必须重跑python gen_table.py--no-ast跳过 AST 构建仅验证分析过程节省内存适合大文件压力测试。提示gen_table.py会检查 FIRST/FOLLOW 集是否冲突若输出Conflict in row if col ID说明文法存在左递归或公共前缀——此时需按实验报告附录 B 的“消除左递归”步骤手动改写grammar.txt而非强行忽略警告。3. 实验报告与代码的强耦合设计如何把 PDF 里的“设计思路”变成可调试的断点3.1 报告中的“符号表设计”对应代码里的哪几行实验报告第 4.2 节《符号表实现》描述“采用哈希表 链地址法每个作用域独立哈希桶嵌套作用域通过 parent 指针链接”。这直接映射到code/ir/symbol_table.pyclass SymbolTable: def __init__(self, parentNone): self.symbols {} # key: name, value: SymbolEntry self.parent parent # parent scope (None for global) self.level 0 if not parent else parent.level 1 def insert(self, name, entry): # 若当前作用域已存在同名符号报错实验报告要求“重复定义检查” if name in self.symbols: raise SemanticError(fRedeclaration of {name} at line {entry.line}) self.symbols[name] entry def lookup(self, name): # 从当前作用域向上查找报告图 4-3 的“作用域链搜索” scope self while scope: if name in scope.symbols: return scope.symbols[name] scope scope.parent return None调试技巧在insert()方法第一行加import pdb; pdb.set_trace()然后运行python code/ir/ir_generator.py ../../test/valid/scope_test.c当遇到int a; { int a; }时pdb 会停在重复插入处p self.level可验证嵌套层级是否为 0→1。3.2 “三地址码生成规则”如何被编码成 AST Visitor报告第 5.1 节列出 7 类表达式翻译规则如E → E1 E2对应t E1.code || E2.code || t str(temp_count) E1.place E2.place。这在code/ir/ir_generator.py中体现为IRGenerator类继承ast.NodeVisitordef visit_BinOp(self, node): # 生成左子表达式代码 left_code self.visit(node.left) # 生成右子表达式代码 right_code self.visit(node.right) # 分配临时变量 temp self.new_temp() # 生成三地址码temp left.place op right.place op_map {ast.Add: , ast.Sub: -, ast.Mult: *} op_str op_map.get(type(node.op), ?) code f{temp} {self.visit(node.left)} {op_str} {self.visit(node.right)} # 合并所有代码段 return left_code right_code code \n注意self.visit(node.left)的双重含义当node.left是Num节点时visit_Num()返回字面值123当node.left是BinOp节点时递归调用visit_BinOp()最终返回包含多行三地址码的字符串所有visit_*方法返回值类型统一为str确保操作符始终拼接字符串——这是吉大代码区别于其他开源编译器的关键设计避免 AST 节点混杂str和list类型。3.3 报告“错误处理机制”在代码中的三处落地报告第 6 章强调“语法错误需定位到具体行列语义错误需给出变量名”。代码中对应词法错误lexer.py第 89 行raise LexicalError(fUnrecognized character {char} at line {line}, column {col})语法错误ll1_parser.py第 156 行raise ParseError(fUnexpected token {token.type} at line {token.line}, expected {expected})语义错误ir_generator.py第 221 行raise SemanticError(fUse of undefined variable {name} at line {node.lineno})。统一错误基类CompilerError定义在code/utils/error.pyclass CompilerError(Exception): def __init__(self, message, lineNone, columnNone): super().__init__(message) self.line line self.column column # 所有错误实例自动记录发生位置供报告生成器提取 self.context self._get_context(line, column) def _get_context(self, line, column): # 从原始源码中提取出错行及前后 1 行用于报告“错误现场截图” if not hasattr(self, _source_lines): with open(self._source_file, r) as f: self._source_lines f.readlines() start max(0, line - 2) end min(len(self._source_lines), line 1) return .join(self._source_lines[start:end])提示实验报告“错误分析”章节要求截图展示error.context因此run.sh会自动捕获异常并保存error_context.log无需手动复制粘贴。4. 避坑指南吉大编译原理代码里最常踩的 4 个深坑4.1 现象python parser/ll1_parser.py test.c报KeyError: ID但test.c明明有int main()原因parsing_table.json未更新或grammar.txt中ID终结符未声明。LL(1) 表生成器gen_table.py仅扫描grammar.txt中-右侧的单词若ID出现在右侧但未在FIRST集计算前显式声明为终结符会导致ID不在表头。解决打开grammar.txt在文件顶部添加一行TERMINALS: ID, NUM, PLUS, MINUS, ...所有终结符用逗号分隔再运行python gen_table.py。4.2 现象python ir/ir_generator.py test.c生成的三地址码里t1 t2 t3顺序混乱甚至出现t1 t1 1原因AST 遍历顺序错误。吉大代码要求左子树先于右子树生成代码保证操作数计算顺序但visit_BinOp中self.visit(node.left)和self.visit(node.right)调用顺序被误写反。解决检查ir_generator.py中所有visit_*方法确保left总在right之前调用特别注意visit_Assign中self.visit(node.targets[0])左值不能放在self.visit(node.value)右值之后。4.3 现象code/backend/asm_generator.py输出的汇编里mov %rax, -8(%rbp)地址偏移为负数但实际运行 segmentation fault原因栈帧布局未对齐。x86-64 ABI 要求栈指针rsp在call指令前必须 16 字节对齐而吉大代码默认按 8 字节分配局部变量offset - 8导致后续push指令破坏对齐。解决修改asm_generator.py第 63 行self.stack_offset - 8为self.stack_offset - 16并在generate_prologue()中插入andq $-16, %rsp对齐指令。4.4 现象test/invalid/undeclared.c运行时无错误提示但报告要求“未声明变量必须报错”原因语义分析阶段未启用。ir_generator.py默认只做语法树遍历SymbolTable插入/查找逻辑被注释掉或visit_Name方法未调用self.symtab.lookup(node.id)。解决确认ir_generator.py第 188 行def visit_Name(self, node):内有if not self.symtab.lookup(node.id): raise SemanticError(...)且__init__中self.symtab SymbolTable()已初始化。5. 进阶把吉大代码改造成支持函数调用的完整编译器5.1 函数调用的三步扩展从语法、语义到代码生成吉大原始代码仅支持int main(){...}单函数要添加int add(int a, int b){return ab;}支持需同步修改三处第一步扩展语法修改grammar.txt增加产生式FuncDef → TYPE ID ( ParamList ) Block ParamList → ε | ParamDecl { , ParamDecl } ParamDecl → TYPE ID然后重跑python gen_table.py更新 LL(1) 表。第二步增强符号表在symbol_table.py中为SymbolEntry添加is_function属性并修改lookup()def lookup(self, name, is_functionFalse): scope self while scope: if name in scope.symbols: entry scope.symbols[name] if is_function and not entry.is_function: continue # 跳过非函数符号 return entry scope scope.parent return None第三步生成调用指令在asm_generator.py中新增visit_Calldef visit_Call(self, node): # 1. 生成实参计算代码从右到左符合 x86-64 ABI args_code for arg in reversed(node.args): args_code self.visit(arg) # 2. 将实参压栈x86-64 使用寄存器传参但吉大简化版用栈 for i, arg in enumerate(node.args): args_code fmovq {self.visit(arg)}, %rax\n args_code fmovq %rax, -{8*(i1)}(%rbp)\n # 3. 生成 call 指令 args_code fcall {node.func.id}\n return args_code5.2 验证函数调用是否生效用这个测试用例创建test/valid/call_test.cint add(int a, int b) { return a b; } int main() { int x add(3, 4); return x; }运行python code/ir/ir_generator.py test/valid/call_test.c应输出t1 3 t2 4 t3 t1 t2 t4 t3再运行python code/backend/asm_generator.py test/valid/call_test.c检查汇编中是否有call add和ret指令。5.3 一个血泪经验别碰optimizer/regalloc.c的寄存器分配算法吉大代码中regalloc.c实现的是图着色寄存器分配但它硬编码了 8 个通用寄存器%rax–%rdi而现代 Linux 内核禁止用户程序直接使用%rbp作为帧指针导致生成的汇编在gcc -O0下能跑-O2下崩溃。我曾花 17 小时 debug 发现问题根源是regalloc.c第 88 行regs[6] %rbp;—— 解决方案不是改寄存器列表而是彻底禁用该模块在run.sh中注释掉gcc -o optimizer.so optimizer/regalloc.c行改用backend直接生成栈式代码。编译原理课设的目标是理解流程不是写出工业级优化器。希望帮到你。本文还有配套的精品资源点击获取
返回列表