如何在Ply Lex Yacc中让 仅在指定规则中作为Token,其余情况忽略?
我之前用Ply处理过类似的换行规则需求,刚好能给你一套可行的实现方案。核心思路是利用Ply的**lexer状态(states)**来区分两种场景:需要换行作为语法结束符的def定义场景,以及其他忽略换行只统计行数的场景。
步骤1:定义Lexer状态
首先在解析器类里声明一个专属的def_mode状态,用来隔离def定义的语境:
from ply import lex, yacc class DefinitionParser: def __init__(self): self.lexer = lex.lex(module=self) self.parser = yacc.yacc(module=self) # 定义排他性状态:进入该状态后仅处理该状态下的Token规则 states = ( ('def_mode', 'exclusive'), )
步骤2:处理普通模式下的Token
在默认的INITIAL状态(普通场景)下,换行仅统计行数但不返回Token;遇到def关键字时切换到def模式:
# 普通模式:识别def关键字,切换到def模式并返回DEF Token def t_INITIAL_def(self, t): r'def' t.lexer.begin('def_mode') t.type = 'DEF' return t # 普通模式:换行仅更新行号,不返回Token(相当于忽略) def t_INITIAL_NEWLINE(self, t): r'\n+' t.lexer.lineno += len(t.value) # 普通模式:忽略空格和制表符 def t_ignore_WHITESPACE(self, t): r'[ \t]+' pass # 普通模式:处理其他通用标识符(按需调整) def t_INITIAL_ID(self, t): r'[a-zA-Z_][a-zA-Z0-9_]*' t.type = 'ID' return t
步骤3:处理def模式下的Token
在def_mode状态下,我们需要确保定义必须以换行结尾,同时拦截非法输入:
# def模式:处理定义的名称标识符 def t_def_mode_ID(self, t): r'[a-zA-Z_][a-zA-Z0-9_]*' t.type = 'ID' return t # def模式:换行作为定义结束符,返回Token并切回普通模式 def t_def_mode_NEWLINE(self, t): r'\n+' t.lexer.lineno += len(t.value) t.lexer.begin('INITIAL') t.type = 'NEWLINE' return t # def模式:如果未换行就遇到新的def,直接抛出语法错误 def t_def_mode_def(self, t): r'def' print(f"语法错误:定义必须以换行结尾,行号 {t.lexer.lineno}") t.lexer.skip(1) # def模式:忽略空格 def t_def_mode_ignore_WHITESPACE(self, t): r'[ \t]+' pass # 通用错误处理 def t_error(self, t): print(f"非法字符 '{t.value[0]}',行号 {t.lexer.lineno}") t.lexer.skip(1)
步骤4:编写语法规则
最后在语法规则里明确def定义的结构,必须以NEWLINE结尾:
# 顶层规则:支持多个定义或空输入 def p_top_level(p): '''top_level : definition top_level | empty''' pass def p_empty(p): 'empty :' pass # 定义规则:严格要求 DEF + 名称 + NEWLINE def p_definition(p): 'definition : DEF ID NEWLINE' print(f"成功解析定义:{p[2]}")
测试验证
现在测试你的示例场景:
- 合法输入:
会输出三个成功解析的定义。def1 def2 def3 - 非法输入:
会在遇到第二个def1 def2 def3def时抛出“定义必须以换行结尾”的错误,完全符合你的需求。
这种方式的好处是逻辑清晰,用状态隔离了两种换行处理场景,而且扩展性强——如果以后需要其他需要换行的语法,只需要新增对应的状态即可。
内容的提问来源于stack exchange,提问作者ibi0tux




