资讯考证相关

MFC实现LALR(1)分析表构造:编译原理实验GUI工具

2026/10/12 11:55:42 安证通 考证咨询 特种作业
MFC实现LALR(1)分析表构造:编译原理实验GUI工具
简介本资源是一款基于MFC开发的LALR(1)分析表自动构造程序面向编译原理课程学习者、计算机专业本科生及课程设计实践者旨在解决LR分析理论中LALR(1)项目集规范族与分析表手工推导繁琐、易错的痛点。压缩包共52个文件含6个头文件.h、4个C源码.cpp、1个可执行程序.exe、1份Word设计报告、1份Markdown运行说明及配套资源文件如图标、资源脚本、工程配置等完整覆盖从理论实现CLOSURE、GO、FIRST集合计算到可视化界面交互的全流程。资源包大小为63.55MB结构清晰工程文件.sln/.vcxproj与调试产物分离合理便于编译复现与代码研读。已有315人学习下载提供教材例题P.115例5.13的完整输入输出验证附带详细设计逻辑与实现注释是理解LALR(1)算法机制、完成编译原理课程设计的高实用性参考实现。1. 这不是个“点开就跑”的玩具程序它真能把《编译原理》课本第115页的LALR(1)推导过程变成可点击、可调试、可导出表格的MFC界面——专为课程设计卡在Go(I,X)和CLOSURE(I)实现环节的同学准备你是不是刚啃完王生原《编译原理第3版》第三章对着P.115例5.13那个带ε产生式的文法G手算LR(1)项目集规范族时在CLOSURE(I)反复迭代后漏掉一个(a, b) lookahead是不是在合并同心集时发现两个I₃和I₆的core相同但lookahead集合没正确并集导致后续ACTION表出现冲突却查不出哪步错了这个基于MFC实现的LALR(1)分析表自动构造程序就是把教材里抽象的算法步骤——CLOSURE、GO、FIRST计算、同心集合并、ACTION/GOTO表填充——全部落地成有状态反馈、可单步追踪、带输入验证的Windows桌面程序。它不替代你理解原理但能立刻验证你手算的每一步是否正确它不是Java或Python脚本而是用原生VC MFC写的完整VS工程含.sln/.vcxproj编译后直接双击.exe就能跑适合广州大学、哈工大、北航等高校编译原理实验课要求提交可运行GUI程序的场景更关键的是它附带一份结构清晰的Word设计报告含算法流程图、核心函数伪代码、例5.13完整输出截图和一份逐行解释的运行说明.md连“为什么要在AutoConstruct.cpp里重载CMapStringToPtr的HashKey”这种细节都写了——这不是交差用的代码包是帮你把编译原理从黑匣子变成可触摸逻辑链的实操拐杖。2. 从文法输入到分析表生成MFC界面如何把教材算法一步步映射成可交互控件与后台逻辑2.1 文法输入与语法检查为什么必须用自定义编辑框而非普通CEdit教材P.115例5.13的文法是S → S S → aAB | bBA A → aAb | ε B → bBa | ε但学生常输错格式漏空格、用全角符号、产生式右部写成aAB|bBA无空格、ε写成e或0。MFC程序没用标准CEdit控件而是在LALR(1) AutoConstructAnalyzeTableDlg.h中声明了CEdit m_wndGrammarEdit并在OnInitDialog()里调用m_wndGrammarEdit.ModifyStyle(0, ES_MULTILINE | ES_AUTOVSCROLL)启用多行更重要的是——它在OnBnClickedBtnParseGrammar()中做了三重校验// AutoConstruct.cpp 第47行起 bool CAutoConstruct::ParseGrammar(const CString strGrammar) { // Step 1: 按换行分割过滤空行和注释行以//开头 CStringArray lines; strGrammar.Replace(_T(\r\n), _T(\n)); strGrammar.Tokenize(_T(\n), lines); // Step 2: 检查每行是否符合 非终结符 → 右部 格式且→为ASCII减号大于号 for (int i 0; i lines.GetSize(); i) { CString line lines[i].Trim(); if (line.IsEmpty() || line.Left(2) _T(//)) continue; int posArrow line.Find(_T(-)); // 必须是ASCII -拒绝全角→或— if (posArrow -1) { AfxMessageBox(_T(第) CString(std::to_wstring(i1).c_str()) _T(行缺少-符号请检查是否为英文减号大于号)); return false; } // Step 3: 提取左部非终结符必须是单个大写字母右部用空格分割后每个符号只能是大小写字母/ε/|/ε CString left line.Left(posArrow).Trim(); if (left.GetLength() ! 1 || !iswupper(left[0])) { AfxMessageBox(_T(左部必须是单个大写字母第) CString(std::to_wstring(i1).c_str()) _T(行错误)); return false; } } return true; }提示这段代码强制要求→必须是ASCII-因为MFC默认ANSI编码下全角符号会解析失败iswupper()判断宽字符大写避免用isupper()在Unicode环境下误判。如果你的文法含中文注释必须用//开头否则会被当作非法产生式。2.2 FIRST集合构造为什么教材P.78的递归算法在这里要加记忆化缓存教材P.78给出的FIRST(X)定义是若X是终结符FIRST(X) {X}若X→ε是产生式ε∈FIRST(X)若X→Y₁Y₂…YₖY₁Y₂…Yᵢ₋₁ ⇒* ε则FIRST(X)包含FIRST(Yᵢ)中除ε外的所有符号若所有Yⱼ⇒*ε则ε∈FIRST(X)但直接递归实现会导致指数级重复计算。程序在AutoConstruct.h中定义了CMapStringToString m_mapFirstCache并在CAutoConstruct::ComputeFirstSet(CString strSymbol)中实现记忆化// AutoConstruct.cpp 第189行 CString CAutoConstruct::ComputeFirstSet(CString strSymbol) { if (m_mapFirstCache.Lookup(strSymbol, m_strTemp)) { return m_strTemp; // 命中缓存直接返回 } CString strResult; // 处理终结符直接返回自身 if (IsTerminal(strSymbol)) { strResult strSymbol; m_mapFirstCache[strSymbol] strResult; return strResult; } // 处理非终结符遍历所有以strSymbol为左部的产生式 POSITION pos m_mapProductions.Lookup(strSymbol, m_arrRightParts); if (pos NULL) { m_mapFirstCache[strSymbol] _T(); // 无产生式FIRST为空 return _T(); } // 关键先标记当前符号正在计算中防止循环依赖如A→B, B→A m_setComputing.Add(strSymbol); for (int i 0; i m_arrRightParts.GetSize(); i) { CStringArray arrRight m_arrRightParts[i]; bool bAllNullable true; for (int j 0; j arrRight.GetSize(); j) { CString strRightSym arrRight[j]; if (strRightSym _T(ε)) { strResult _T(ε); break; } else if (IsTerminal(strRightSym)) { strResult strRightSym _T( ); bAllNullable false; break; } else { // 递归计算FIRST(strRightSym)但先检查是否在计算中防环 if (m_setComputing.Lookup(strRightSym)) { // 发现环按教材惯例忽略该路径继续下一个产生式 bAllNullable false; break; } CString strFirstOfRight ComputeFirstSet(strRightSym); // 合并FIRST去掉ε除非所有右侧符号都可空 if (strFirstOfRight.Find(_T(ε)) ! -1) { strFirstOfRight.Replace(_T(ε), _T()); } strResult strFirstOfRight _T( ); if (strFirstOfRight.Find(_T(ε)) -1) { bAllNullable false; } } } if (bAllNullable) { strResult _T(ε ); } } // 去重并排序便于对比教材结果 strResult RemoveDuplicatesAndSort(strResult); m_mapFirstCache[strSymbol] strResult; m_setComputing.RemoveKey(strSymbol); return strResult; }参数说明m_setComputing是CMapStringToPtr模拟的set用于检测左递归环RemoveDuplicatesAndSort()对空格分隔的符号串去重并按ASCII升序排列如a b a→a b确保每次输出顺序一致方便与教材手算结果比对。2.3 LR(1)项目集规范族构造CLOSURE(I)和GO(I,X)如何用MFC容器高效实现教材P.115要求对初始项目[S→·S, #]做CLOSURE再对每个项目集I和每个文法符号X计算GO(I,X)。程序用CArrayCString, CString存储每个项目集每个项目格式为[S-a·AB, {a,b}]用CMapStringToPtr映射项目集ID到项目集内容// AutoConstruct.cpp 第322行CLOSURE实现 void CAutoConstruct::Closure(CArrayCString, CString arrItems) { bool bChanged true; while (bChanged) { bChanged false; int nOldSize arrItems.GetSize(); for (int i 0; i nOldSize; i) { CString strItem arrItems[i]; int posDot strItem.Find(_T(·)); if (posDot -1) continue; // 提取·后的第一个符号X CString strAfterDot strItem.Mid(posDot 1); int posComma strAfterDot.Find(_T(,)); if (posComma -1) continue; CString strX strAfterDot.Left(posComma).Trim(); // 若X是非终结符需添加其所有产生式[X→·α, FIRST(βa)]其中βa是原项目的lookahead if (!IsTerminal(strX) strX ! _T(ε)) { CString strLookahead strAfterDot.Mid(posComma 1).Trim(); strLookahead strLookahead.Mid(1, strLookahead.GetLength() - 2); // 去掉{ } // 获取X的所有产生式右部 CStringArray arrProductions; if (m_mapProductions.Lookup(strX, arrProductions)) { for (int j 0; j arrProductions.GetSize(); j) { CString strProd arrProductions[j]; // 构造新项目[X→·α, FIRST(βa)] CString strNewItem _T([) strX _T(-·) strProd _T(, {) ComputeFirstSetForLookahead(strProd, strLookahead) _T(}]); if (!arrItems.Contains(strNewItem)) { arrItems.Add(strNewItem); bChanged true; } } } } } } } // GO(I,X)实现第385行 CArrayCString, CString CAutoConstruct::Go(const CArrayCString, CString arrI, const CString strX) { CArrayCString, CString arrJ; for (int i 0; i arrI.GetSize(); i) { CString strItem arrI[i]; int posDot strItem.Find(_T(·)); if (posDot -1) continue; int posNext posDot 1; if (posNext strItem.GetLength()) continue; // 检查·后是否紧跟strX CString strAfterDot strItem.Mid(posDot 1); int posComma strAfterDot.Find(_T(,)); if (posComma -1) continue; CString strNextSym strAfterDot.Left(posComma).Trim(); if (strNextSym strX) { // 移动·[A→α·Xβ, a] → [A→αX·β, a] CString strBeforeDot strItem.Left(posDot); CString strAfterDotPart strAfterDot.Mid(strX.GetLength() 1); // 跳过X和空格 int posCommaNew strAfterDotPart.Find(_T(,)); CString strNewDotPos _T([) strBeforeDot strX _T(·) strAfterDotPart.Left(posCommaNew) strAfterDotPart.Mid(posCommaNew) _T(]); arrJ.Add(strNewDotPos); } } Closure(arrJ); // 对GO结果做闭包 return arrJ; }关键点ComputeFirstSetForLookahead()专门处理FIRST(βa)——当项目是[A→α·Xβ, a]时lookahead是FIRST(βa)需先计算β的FIRST再与a并集。程序用CArrayCString而非std::vector是因为MFC工程默认使用ATL/MFC容器避免混用STL引发链接问题。3. LALR(1)项目集合并与分析表生成同心集识别、冲突检测与表格渲染的MFC实现细节3.1 同心集识别为什么不能只比对core字符串而要深度解析项目结构LR(1)项目集I和J是同心集当且仅当它们的core即去掉lookahead的项目集合相同。但简单用CString.CompareNoCase(core1, core2)会出错——因为[S-·aAB, {#}]和[S-·aAB, {a,b}]的core字符串都是[S-·aAB]但若未标准化·位置和空格[S-·aAB]和[S-·aAB ]末尾空格会被判为不同。程序在CAutoConstruct::GetCoreFromItem()中做标准化// AutoConstruct.cpp 第452行 CString CAutoConstruct::GetCoreFromItem(const CString strItem) { // 提取[]内内容去掉逗号后部分 int posStart strItem.Find(_T([)); int posEnd strItem.Find(_T(])); if (posStart -1 || posEnd -1) return _T(); CString strContent strItem.Mid(posStart 1, posEnd - posStart - 1); int posComma strContent.Find(_T(,)); if (posComma -1) return strContent; CString strCore strContent.Left(posComma).Trim(); // 标准化·前后不留空格→左右各一个空格消除所有多余空格 strCore.Replace(_T( · ), _T(·)); // ·前后空格 strCore.Replace(_T(-), _T( - )); // →左右空格 strCore.Replace(_T( ), _T( )); // 多空格变单空格 return strCore; }然后用CMapStringToPtr以标准化core为key存储所有具有该core的项目集ID// AutoConstruct.cpp 第498行 void CAutoConstruct::MergeLalrItems() { CMapStringToPtr mapCoreToIds; for (int i 0; i m_arrLr1Items.GetSize(); i) { CString strCore GetCoreFromItem(m_arrLr1Items[i][0]); // 取每个项目集的第一个项目代表core if (!mapCoreToIds.Lookup(strCore, m_pVoid)) { CArrayint, int* pArrIds new CArrayint, int; pArrIds-Add(i); mapCoreToIds[strCore] pArrIds; } else { CArrayint, int* pArrIds (CArrayint, int*)m_pVoid; pArrIds-Add(i); } } // 合并对每个core将所有对应项目集的lookahead并集 POSITION pos mapCoreToIds.GetStartPosition(); while (pos) { CString strCore; CArrayint, int* pArrIds; mapCoreToIds.GetNextAssoc(pos, strCore, (void*)pArrIds); CArrayCString, CString arrMergedItems; for (int j 0; j pArrIds-GetSize(); j) { int nId (*pArrIds)[j]; // 合并所有项目相同core的项目lookahead取并集 for (int k 0; k m_arrLr1Items[nId].GetSize(); k) { CString strItem m_arrLr1Items[nId][k]; CString strCoreOnly GetCoreFromItem(strItem); CString strLookahead ExtractLookahead(strItem); // 提取{a,b}部分 // 在arrMergedItems中查找相同core的项目合并lookahead bool bFound false; for (int l 0; l arrMergedItems.GetSize(); l) { if (GetCoreFromItem(arrMergedItems[l]) strCoreOnly) { CString strExistingLookahead ExtractLookahead(arrMergedItems[l]); strLookahead UnionLookaheadSets(strExistingLookahead, strLookahead); arrMergedItems[l] _T([) strCoreOnly _T(, {) strLookahead _T(}]); bFound true; break; } } if (!bFound) { arrMergedItems.Add(_T([) strCoreOnly _T(, {) strLookahead _T(}])); } } } m_arrLalrItems.Add(arrMergedItems); } }参数说明UnionLookaheadSets()将{a,b}和{b,c}合并为{a,b,c}并自动排序去重ExtractLookahead()用正则思路提取{}内内容避免字符串索引越界。3.2 ACTION/GOTO表生成如何把二维表渲染成MFC ListCtrl并标出移进/归约冲突生成的ACTION表是m_mapAction[CString][CString]行项目集ID列终结符GOTO表是m_mapGoto[CString][CString]行项目集ID列非终结符。渲染到CListCtrl前需转换为行列索引// LALR(1) AutoConstructAnalyzeTableDlg.cpp 第621行 void CLALRAutoConstructAnalyzeTableDlg::PopulateTableCtrl() { // 清空原有列 m_listTable.DeleteAllItems(); m_listTable.DeleteColumn(0); // 添加首列项目集ID m_listTable.InsertColumn(0, _T(State), LVCFMT_LEFT, 60); // 添加终结符列ACTION表列 for (int i 0; i m_arrTerminals.GetSize(); i) { m_listTable.InsertColumn(i 1, m_arrTerminals[i], LVCFMT_CENTER, 80); } // 添加非终结符列GOTO表列 for (int i 0; i m_arrNonterminals.GetSize(); i) { m_listTable.InsertColumn(m_arrTerminals.GetSize() 1 i, m_arrNonterminals[i], LVCFMT_CENTER, 80); } // 填充数据行 for (int i 0; i m_arrLalrItems.GetSize(); i) { int nItem m_listTable.InsertItem(i, CString(_T(I)) CString(std::to_wstring(i).c_str())); for (int j 0; j m_arrTerminals.GetSize(); j) { CString strAction _T(); if (m_mapAction.Lookup(CString(_T(I)) std::to_wstring(i), m_strTemp)) { CMapStringToString mapActions *(CMapStringToString*)m_strTemp; if (mapActions.Lookup(m_arrTerminals[j], strAction)) { // 标记冲突s/r表示移进-归约r/r表示归约-归约 if (strAction.Find(_T(s)) ! -1 strAction.Find(_T(r)) ! -1) { strAction _T(s/r); // 红色高亮 m_listTable.SetItemText(nItem, j 1, strAction); m_listTable.SetItemData(nItem, j 1, 1); // 1冲突 } else if (strAction.Find(_T(r)) ! -1 strAction.Find(_T(r)) ! strAction.ReverseFind(_T(r))) { strAction _T(r/r); m_listTable.SetItemText(nItem, j 1, strAction); m_listTable.SetItemData(nItem, j 1, 1); } else { m_listTable.SetItemText(nItem, j 1, strAction); } } } } // GOTO列同理... } }注意SetItemData()存储1表示该单元格有冲突后续可通过OnCustomDraw()回调给s/r单元格设红色背景但原工程未实现此UI增强需自行扩展。3.3 冲突检测与报告为什么教材例5.13在LALR(1)下无冲突但你的文法可能有程序在CAutoConstruct::BuildActionTable()末尾调用CheckConflicts()// AutoConstruct.cpp 第725行 void CAutoConstruct::CheckConflicts() { m_strConflictReport _T(); for (POSITION pos m_mapAction.GetStartPosition(); pos;) { CString strState; CMapStringToString* pMapActions; m_mapAction.GetNextAssoc(pos, strState, (void*)pMapActions); for (POSITION pos2 pMapActions-GetStartPosition(); pos2;) { CString strTerminal; CString strAction; pMapActions-GetNextAssoc(pos2, strTerminal, strAction); // 检查同一stateterminal是否有多个action if (strAction.Find(_T(/)) ! -1) { // s1/r2格式 m_strConflictReport strState _T( ) strTerminal _T( 存在移进-归约冲突) strAction _T(\n); } // 检查是否同时存在s和r未用/分隔的旧格式 if (strAction.Find(_T(s)) ! -1 strAction.Find(_T(r)) ! -1) { m_strConflictReport strState _T( ) strTerminal _T( 存在移进-归约冲突\n); } } } if (!m_strConflictReport.IsEmpty()) { AfxMessageBox(_T(检测到LALR(1)冲突\n) m_strConflictReport); } }血泪经验很多同学输入文法时把S→aSb | ε写成S-aSb|ε无空格导致程序解析出S-aSb|ε作为单个右部符号|被当终结符从而在ACTION表中产生大量非法转移。务必按教材格式S - a S b | ε符号间留空格。4. 避坑编译、运行与调试中五个真实踩过的坑每个都让我的Debug时间翻倍4.1 编译报错“无法解析的外部符号”VC版本与MFC库不匹配的玄学问题现象在VS2019中加载.sln后编译报错LNK2001: unresolved external symbol public: __thiscall CListCtrl::CListCtrl(void)或类似CMapStringToPtr构造函数未定义。原因原工程是用VS2010或VS2013创建的.vcxproj中PlatformToolsetv100/PlatformToolsetVS2010或v120VS2013而你的VS2019默认用v142。MFC库二进制不兼容导致链接器找不到旧版MFC类的实现。解决右键项目→属性→配置属性→常规→平台工具集改为与原工程匹配的版本查看.vcxproj文件中的PlatformToolset值。若无对应版本需安装旧版Visual Studio Build Tools或手动修改.vcxprojPlatformToolsetv142/PlatformToolset !-- VS2019 -- !-- 并确保 -- UseOfMfcStatic/UseOfMfc !-- 或 Dynamic --然后在属性→配置属性→常规→使用MFC选“在共享DLL中使用MFC”或“在静态库中使用MFC”保持与原工程一致。4.2 程序启动后立即崩溃资源文件缺失或路径硬编码的翻车现场现象双击LALR(1) AutoConstructAnalyzeTable.exe弹窗显示“应用程序无法正常启动(0xc000007b)”或直接闪退无报错。原因.exe依赖msvcp140.dll等VC运行时但更常见的是资源文件路径问题。原工程中图标、对话框模板存在res/目录但.rc文件里写的是IDI_ICON1 ICON res\\icon.ico。若你把整个文件夹移到其他盘符相对路径失效或res/目录被误删LALR(1) AutoConstructAnalyzeTable.rc引用了res\\LALR(1) AutoConstructAnalyzeTable.ico。解决确认res/目录存在且包含LALR(1) AutoConstructAnalyzeTable.ico、LALR(1) AutoConstructAnalyzeTable.rc2等文件在VS中右键资源视图→添加资源→图标重新导入或修改.rc文件中所有res\\xxx为xxx并将图标文件复制到exe同目录。4.3 输入文法后点击“构造分析表”无响应主线程被阻塞的GUI冻结现象输入文法点击按钮界面卡死CPU占用100%10分钟后才出结果或崩溃。原因LALR(1)构造算法复杂度高对含左递归或ε产生式多的文法项目集数量呈指数增长。原程序未做进度提示OnBnClickedBtnConstructTable()中m_autoConstruct.BuildLalrTable()在主线程同步执行阻塞消息循环。解决在OnBnClickedBtnConstructTable()开头添加// 显示等待光标 HCURSOR hOldCursor SetCursor(LoadCursor(NULL, IDC_WAIT)); // 执行耗时操作 m_autoConstruct.BuildLalrTable(); // 恢复光标 SetCursor(hOldCursor);更优解是用AfxBeginThread()启后台线程但需处理线程安全如用CCriticalSection保护m_autoConstruct成员对课程设计而言加光标提示已足够。4.4 输出的ACTION表全是空FIRST集合计算返回空串的静默失败现象文法输入正确CLOSURE和GO计算日志显示正常但最终ACTION表所有单元格为空。原因ComputeFirstSet()中IsTerminal(strSymbol)判断失误。教材中终结符是小写字母、运算符等但程序用if (strSymbol.GetLength() 1 iswlower(strSymbol[0]))判断若你输入终结符id多字符IsTerminal(id)返回false导致FIRST计算跳过后续GO(I,X)因X非终结符不触发转移。解决修改IsTerminal()逻辑在AutoConstruct.h中维护一个终结符集合// 初始化时 m_setTerminals.Add(_T(a)); m_setTerminals.Add(_T(b)); m_setTerminals.Add(_T(#)); m_setTerminals.Add(_T(ε)); // ε是特殊终结符 // 判断时 bool CAutoConstruct::IsTerminal(const CString strSym) { return m_setTerminals.Lookup(strSym) ! NULL; }并在文法解析时自动将所有不在产生式左部的符号加入m_setTerminals。4.5 导出表格为Excel失败COM接口未初始化的权限陷阱现象点击“导出Excel”按钮弹窗报错“无法创建Excel.Application对象”或0x800401F0错误。原因MFC程序调用Excel COM需要CoInitialize(NULL)初始化COM库且Excel必须已安装。原工程可能未调用CoInitialize或调用位置错误应在OnInitDialog()中而非按钮事件里。解决在CLALRAutoConstructAnalyzeTableDlg::OnInitDialog()开头添加CoInitialize(NULL); // 必须在使用COM前调用 // ... 其他初始化并在CLALRAutoConstructAnalyzeTableDlg::~CLALRAutoConstructAnalyzeTableDlg()中添加CoUninitialize(); // 匹配释放同时确认本机安装了Microsoft Excel否则COM创建失败。5. 进阶技巧用调试器单步追踪CLOSURE(I)的每一次迭代把教材算法变成可视化的思维导图5.1 在VS中设置断点并观察项目集演化从I₀到I₁的每一步都可回溯教材P.115例5.13的初始项目是[S→·S, #]CLOSURE后应得[S→·S, #],[S→·aAB, #],[S→·bBA, #],[A→·aAb, {a,b}],[A→·ε, {a,b}],[B→·bBa, {a,b}],[B→·ε, {a,b}]。要验证程序是否正确不必靠猜——直接在CAutoConstruct::Closure()入口设断点在AutoConstruct.cpp第322行void CAutoConstruct::Closure(...)函数第一行右键→断点→插入断点运行程序输入例5.13文法点击“构造分析表”当断点命中打开“调试”→“窗口”→“局部变量”展开arrItems看到初始只有[S-·S, {#}]按F11单步进入观察for循环中每次arrItems.Add()后arrItems.GetSize()的变化关键在while(bChanged)循环内打开“调试”→“窗口”→“监视”添加表达式arrItems[i]i为当前索引实时查看每个新添加的项目字符串。你会发现第一次迭代添加[S→·aAB, #]和[S→·bBA, #]第二次迭代因aAB中a是终结符不再扩展但A是非终结符于是添加[A→·aAb, {a,b}]和[A→·ε, {a,b}]……每一行代码都在执行教材定义的CLOSURE规则。这种单步追踪比手算快十倍且绝不会漏掉FIRST(βa)中的a。5.2 修改源码验证算法变体把LALR(1)临时改成SLR(1)来理解冲突根源LALR(1)和SLR(1)的核心区别在于lookahead计算SLR(1)用FOLLOW(A)代替FIRST(βa)。想验证为何例5.13在SLR(1)下有冲突而在LALR(1)下没有只需两行修改在AutoConstruct.cpp中找到ComputeFirstSetForLookahead()函数约第510行将其内容替换为// SLR(1)版本直接返回FOLLOW(X) CString CAutoConstruct::ComputeFirstSetForLookahead(const CString strRight, const CString strLookahead) { // 忽略strRight直接返回FOLLOW(strLeftSymbol) CString strLeft GetLeftSymbolFromProduction(strRight); // 需自行实现提取产生式左部 return ComputeFollowSet(strLeft); // 调用已有的FOLLOW计算函数 }确保ComputeFollowSet()已实现教材P.82算法重新编译输入同一文法对比ACTION表中冲突项数量。你会发现SLR(1)的FOLLOW(S){#}比LALR(1)的FIRST(AB#){a,b}范围大得多导致更多归约动作填入同一格从而产生冲突。这比读十页理论更直观地理解“lookahead精度决定分析能力”。5.3 用输出日志反向定位BUG当CLOSURE结果不对时如何快速缩小范围程序在AutoConstruct.cpp中预留了日志开关// 第315行附近 #ifdef _DEBUG TRACE(_T(CLOSURE start with %d items\n), arrItems.GetSize()); #endif但默认未启用TRACE输出。要激活它在VS中项目属性→配置属性→C/C→预处理器→预处理器定义添加_DEBUG;TRACE_ENABLED在AutoConstruct.cpp顶部添加#include atltrace.h #pragma comment(lib, atlthunk.lib)在Closure()函数中每添加一个新项目后加#ifdef TRACE p a hrefhttps://download.csdn.net/download/sheziqiong/85706453 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
本文仅供参考,具体政策以官方公告为准 返回资讯列表 →
延伸阅读

更多相关内容

相关资讯、最新动态、本周本月更新,都在这里。

阜新高压电工证在哪考?3步搞定避坑,附郑州报考干货

阜新高压电工证在哪考?3步搞定避坑,附郑州报考干货

阜新高压电工证在哪考?3步搞定避坑,附郑州报考干货 手里没证,面试直接挂?急着上岗等米下锅,却被“特种作业操作证”卡住脖子,这急火攻心的滋味谁懂?别慌,作为在应急管理系统摸爬滚打十年的老法师,我见过太多人因为不懂流程,钱花了、时间耽误了,证还没拿到手。 今天这篇 阜新高压电工证在哪考…

查看 →
低压电工证考试视力卡住?这条良心建议能帮你过

低压电工证考试视力卡住?这条良心建议能帮你过

低压电工证考试视力卡住?这条良心建议能帮你过 你的电工证是不是快过期了,或者刚去报名却被体检单上的视力要求劝退?别慌,很多老电工都踩过这个坑,以为看不清就不行,其实只要方法对, 良心建议 是:别只盯着裸眼视力,重点在于矫正视力和辨色力。…

查看 →
阆中电工证复审在哪里?多少钱?老电工手把手教你避坑不花冤枉钱

阆中电工证复审在哪里?多少钱?老电工手把手教你避坑不花冤枉钱

阆中电工证复审在哪里?多少钱?老电工手把手教你避坑不花冤枉钱 别问我现在去考电工证会不会白交培训费,我只问你,你的证过期了吗?很多兄弟拿着快过期的证,心里慌得一批,怕去复审还得重新学理论,怕考不过又要再掏一次几千块,这钱花得肉疼。其实, 阆中电工证复审在哪里 跑, 多少钱…

查看 →
滁州电工证复训别踩坑:外省证转回看这3步拿电子证书

滁州电工证复训别踩坑:外省证转回看这3步拿电子证书

滁州电工证复训别踩坑:外省证转回看这3步拿电子证书 手里那张电工证是五年前在外省考的,现在人回了滁州,想知道这证还能不能用?很多人卡在这一步:之前考的证在外省能不能转过来?其实只要流程对, 电子证书 全国联网,根本不用折腾纸质本,但前提是你得完成滁州本地的复训登记。…

查看 →
ABAP ALV报表自定义按钮执行完后隐藏的两种实现方案与实战

ABAP ALV报表自定义按钮执行完后隐藏的两种实现方案与实战

做ABAP开发的,十个ALV报表里少说有七八个要放自定义按钮。业务顾问的需求往往很简单——“加一个按钮,点了执行一下,执行完了就别再给用户点了”。比如生成对账单、执行批量过账、触发一次数据同步,点过一次如果还能再点&#xff…

查看 →
考电工证两天能拿证吗?附官方报名入口避坑指南

考电工证两天能拿证吗?附官方报名入口避坑指南

考电工证两天能拿证吗?附官方报名入口避坑指南 最怕什么?花了钱去培训,结果考试没考过,钱打了水漂,时间也浪费了。很多南阳的朋友在咨询时,第一句话就是:“听说考电工证只要两天,是不是交钱就能拿证?”…

查看 →
泰安电工证怎样规划复审?网上查询防过期指南

泰安电工证怎样规划复审?网上查询防过期指南

泰安电工证怎样规划复审?网上查询防过期指南 手里那张电工操作证突然显示“过期”或者“临期”,心里是不是咯噔一下?别慌,这是很多持证电工最头疼的时刻:不知道复审要提前多久,更不知道去哪里确认自己的状态。很多人第一反应是打电话问朋友,但最稳妥的办法其实是 网上查询…

查看 →
庆阳市安监局电工证到底值不值得考 3天搞定考试

庆阳市安监局电工证到底值不值得考 3天搞定考试

庆阳市安监局电工证到底值不值得考 3天搞定考试 工地太忙,根本没时间复习考试,这大概是绝大多数一线电工兄弟最真实的写照。手里干着活,脑子想着证,想考吧,怕考不过白花钱;不考吧,又担心哪天项目查下来,没证就是黑工,工资都拿不稳。很多人都在纠结,这【庆阳市安监局电工证】到底 值不值得考…

查看 →
焊工应急局特种工多久拿证?避坑指南

焊工应急局特种工多久拿证?避坑指南

焊工应急局特种工多久拿证?避坑指南 想考焊工应急局特种工,最怕就是不知道去哪报名,怕被中介坑得血本无归。很多兄弟在微信上问:到底多久拿证?能不能加急?这里必须把丑话说在前头: 正规渠道,从报名到拿证,标准流程通常需要 25-35 天,任何承诺“3天拿证”、“内部通道”的,全是骗子。…

查看 →
建机电工证怎么考?3步搞定郑州报考避坑指南

建机电工证怎么考?3步搞定郑州报考避坑指南

建机电工证怎么考?3步搞定郑州报考避坑指南 手里那张特种作业操作证是不是快到期了?看着有效期临近,心里慌得一批,却完全搞不清复审流程,怕错过了时间窗口直接作废。这种“证在手、心发慌”的日子,咱们干工程的都经历过。别急,今天这份 郑州报考避坑指南 ,专门拆解 建机电工证怎么考…

查看 →
相关服务

看完文章,下一步可以直接办

报考、备考、复审相关的服务入口,都在这里。

考试批次时间

近期各工种批次安排与报名截止提醒。

查看详情 →

报考条件查询

年龄、学历、体检条件逐项对照。

查看详情 →

材料免费预审

报名材料逐项核对,缺什么当场补齐。

查看详情 →

复审流程

复审时间、材料与流程一次说清。

查看详情 →
报名流程

从咨询到拿证,就四步

每一步都有明确产出,每一步都有人盯着。

01

意向沟通

说清岗位与目标,顾问推荐对应工种与报考方向。

02

材料预审

身份证、学历、体检逐项核对,缺什么当场补齐。

03

批次报名

锁定最近考试批次,考务信息逐一确认。

04

培训考试

题库辅导加实操要点,考完节点逐一跟进拿证。

免费咨询

想报考特种作业证?找顾问聊一聊

根据你的工作经历推荐工种,确认批次与材料,30 秒登记当天回访。