 预测分析表构造实战:从 5 条文法到 3 步代码实现)
LL(1)预测分析表构造实战从文法规则到代码实现1. LL(1)分析器的核心机制LL(1)分析器作为编译原理中的关键组件其核心在于预测分析表的高效构造。这种自顶向下的分析方法之所以能在实际编译器中广泛应用关键在于它通过三个决定性要素实现了线性时间的分析效率单向扫描严格从左(L)向右读取输入流推导策略始终采用最左(L)推导方式预测能力仅需查看一个(1)前看符号即可确定产生式选择预测分析表的本质是一个二维矩阵其行对应文法中的非终结符列对应终结符包括特殊的结束符$。每个单元格M[A,a]指示当非终结符A面临输入符号a时应采用的产生式。# 预测分析表数据结构示例 analysis_table { E: {i: T E\, (: T E\}, E\: {: T E\, ): ε, $: ε}, T: {i: F T\, (: F T\}, T\: {*: * F T\, : ε, ): ε, $: ε}, F: {i: i, (: ( E )} }2. FIRST与FOLLOW集计算原理2.1 FIRST集算法实现FIRST(α)定义为可从α推导出的所有串的首终结符集合。其计算规则遵循以下递推关系若X是终结符FIRST(X) {X}若X→ε是产生式则ε ∈ FIRST(X)若X→Y₁Y₂...Yₖ则FIRST(X) ∪ FIRST(Y₁)若Y₁可推导出ε则继续考虑Y₂依此类推def compute_first(grammar): first defaultdict(set) changed True while changed: changed False for nt in grammar: for production in grammar[nt]: # 处理产生式右部 for symbol in production.split(): if symbol in grammar: # 非终结符 before len(first[nt]) first[nt].update(first[symbol] - {ε}) if ε not in first[symbol]: break after len(first[nt]) if before ! after: changed True else: # 终结符 before len(first[nt]) first[nt].add(symbol) after len(first[nt]) if before ! after: changed True break else: # 所有符号都可推出ε first[nt].add(ε) return first2.2 FOLLOW集计算要点FOLLOW(A)包含所有可能紧跟在A后面的终结符计算时需注意对开始符号S将$加入FOLLOW(S)若存在产生式B→αAβ则FOLLOW(A) ∪ (FIRST(β)-{ε})若β可推出ε或存在B→αA则FOLLOW(A) ∪ FOLLOW(B)def compute_follow(grammar, first, start_symbol): follow defaultdict(set) follow[start_symbol].add($) changed True while changed: changed False for nt in grammar: for production in grammar[nt]: symbols production.split() for i, symbol in enumerate(symbols): if symbol not in grammar: continue # 处理非末尾情况 if i len(symbols)-1: next_sym symbols[i1] if next_sym in grammar: before len(follow[symbol]) follow[symbol].update(first[next_sym] - {ε}) if ε in first[next_sym]: follow[symbol].update(follow[nt]) after len(follow[symbol]) else: before len(follow[symbol]) follow[symbol].add(next_sym) after len(follow[symbol]) if before ! after: changed True # 处理末尾情况 else: before len(follow[symbol]) follow[symbol].update(follow[nt]) after len(follow[symbol]) if before ! after: changed True return follow3. 预测分析表构造算法基于FIRST和FOLLOW集可按以下规则填充预测分析表对每个产生式A→α对每个终结符a∈FIRST(α)将A→α加入M[A,a]若ε∈FIRST(α)则对每个b∈FOLLOW(A)将A→α加入M[A,b]def build_parsing_table(grammar, first, follow): table defaultdict(dict) for nt in grammar: for production in grammar[nt]: first_alpha compute_production_first(production, first) for terminal in first_alpha - {ε}: table[nt][terminal] production if ε in first_alpha: for terminal in follow[nt]: table[nt][terminal] production return table def compute_production_first(production, first): result set() for symbol in production.split(): if symbol in first: result.update(first[symbol] - {ε}) if ε not in first[symbol]: break else: result.add(symbol) break else: result.add(ε) return result4. 工程实现关键点4.1 文法预处理模块实际工程中需要处理文法的左递归和左公因子def eliminate_left_recursion(grammar): new_grammar defaultdict(list) non_terminals list(grammar.keys()) for i, A in enumerate(non_terminals): # 处理间接左递归 for j in range(i): B non_terminals[j] new_productions [] for production in grammar[A]: if production.startswith(B): for b_prod in grammar[B]: new_productions.append(b_prod production[len(B):]) else: new_productions.append(production) grammar[A] new_productions # 处理直接左递归 left_recursive [] non_left_recursive [] for production in grammar[A]: if production.startswith(A): left_recursive.append(production[len(A):]) else: non_left_recursive.append(production) if left_recursive: new_nt A new_grammar[A] [prod new_nt for prod in non_left_recursive] new_grammar[new_nt] [prod new_nt for prod in left_recursive] [ε] else: new_grammar[A] grammar[A] return new_grammar4.2 分析器核心逻辑class LL1Parser: def __init__(self, grammar, start_symbol): self.grammar grammar self.start start_symbol self.first compute_first(grammar) self.follow compute_follow(grammar, self.first, start_symbol) self.table build_parsing_table(grammar, self.first, self.follow) def parse(self, input_str): stack [$, self.start] input_tokens input_str.split() [$] ptr 0 while stack: top stack[-1] current_token input_tokens[ptr] if top current_token $: return True elif top current_token: stack.pop() ptr 1 elif top in self.grammar: if current_token in self.table[top]: production self.table[top][current_token] stack.pop() if production ! ε: stack.extend(reversed(production.split())) else: raise SyntaxError(fUnexpected token {current_token}) else: raise SyntaxError(fUnexpected token {current_token}) return False5. 典型文法处理案例考虑以下表达式文法E → T E E → T E | ε T → F T T → * F T | ε F → ( E ) | i通过我们的实现可以得到FIRST集:E: {(, i} E: {, ε} T: {(, i} T: {*, ε} F: {(, i}FOLLOW集:E: {), $} E: {), $} T: {, ), $} T: {, ), $} F: {*, , ), $}预测分析表片段:非终结符i*()$EE→TEE→TEEE→TEE→εE→εTT→FTT→FTTT→εT→*FTT→εT→εFF→iF→(E)6. 错误处理与优化策略在实际编译器实现中LL(1)分析器还需要考虑错误恢复机制当遇到分析表空白项时可采用同步记号集策略内存优化对大型文法可采用稀疏矩阵存储分析表性能分析通过计时器监控各阶段耗时优化关键路径def error_recovery(self, stack, token): # 同步记号集策略 sync_tokens self.follow[stack[-1]] if stack[-1] in self.grammar else set() if token in sync_tokens: stack.pop() # 弹出栈顶非终结符 else: # 尝试跳过当前token print(fError: Skipping unexpected token {token}) return True # 指示已跳过 return False通过系统性地实现这些组件开发者可以构建出高效可靠的LL(1)语法分析器为编译器前端提供坚实的语法分析基础。