如何用Ply(Lex/Yacc)仅解析C++宏调用并忽略其他代码?
实现仅解析C++宏调用的Ply解析器
1. 词法分析器(Lexer)调整
核心思路是优先识别需要处理的宏、字符串、注释,其余所有内容标记为可忽略token,避免触发语法错误。
import ply.lex as lex # 定义token列表 tokens = [ 'MACRO', 'LPAREN', 'RPAREN', 'COMMA', 'STRING', 'IGNORED' ] # 忽略空格、制表符、换行 t_ignore = ' \t\n' # 匹配C++单行注释 def t_COMMENT(t): r'//.*' pass # 直接忽略 # 匹配C++多行注释 def t_MULTILINE_COMMENT(t): r'/\*(.|\n)*?\*/' pass # 直接忽略 # 匹配字符串常量(避免把字符串内的大写下划线误判为宏) def t_STRING(t): r'"([^"\\]|\\.)*"' pass # 字符串内容无需处理,直接忽略 # 匹配宏名:仅大写字母和下划线开头,后续只能是大写字母和下划线 def t_MACRO(t): r'[A-Z_][A-Z_]*' return t # 返回宏token供解析器处理 # 匹配括号和逗号 t_LPAREN = r'\(' t_RPAREN = r'\)' t_COMMA = r',' # 匹配所有其他字符,标记为可忽略 def t_IGNORED(t): r'.' return t # 错误处理:跳过非法字符,继续解析 def t_error(t): t.skip(1) # 构建lexer lexer = lex.lex()
2. 语法分析器(Parser)调整
语法规则设计为允许任意内容存在,仅捕获宏调用并提取参数,其余内容直接忽略,不会抛出语法错误。
import ply.yacc as yacc # 存储捕获到的宏调用,格式:(宏名, [参数列表]) macro_calls = [] # 顶层规则:整个程序由任意数量的语句组成 def p_program(p): '''program : statement*''' pass # 语句可以是宏调用,也可以是忽略的内容 def p_statement(p): '''statement : macro_call | ignored''' pass # 匹配宏调用:宏名 + 括号 + 参数列表 def p_macro_call(p): '''macro_call : MACRO LPAREN argument_list RPAREN''' macro_name = p[1] arguments = p[3] if p[3] else [] macro_calls.append( (macro_name, arguments) ) print(f"捕获宏调用:{macro_name},参数:{arguments}") # 处理参数列表:空列表或多个参数 def p_argument_list(p): '''argument_list : argument | argument_list COMMA argument''' if len(p) == 2: p[0] = [p[1]] else: p[0] = p[1] + [p[3]] # 处理单个参数:简化处理,若需支持嵌套括号可扩展此规则 def p_argument(p): '''argument : IGNORED | MACRO | STRING''' p[0] = p[1] # 处理空参数列表 def p_argument_list_empty(p): '''argument_list : ''' p[0] = [] # 忽略任意数量的IGNORED token def p_ignored(p): '''ignored : IGNORED+''' pass # 错误处理:跳过错误token,继续解析 def p_error(p): if p: parser.errok() else: pass # 构建parser parser = yacc.yacc()
3. 使用示例
# 测试代码 test_code = ''' #include <iostream> using namespace std; #define LOG_INFO(msg) cout << msg << endl #define ADD(a, b) (a + b) int main() { int x = 5; float y = 3.14; LOG_INFO("Hello World"); int result = ADD(x, y); // 注释里的MACRO_TEST不会被识别 /* 多行注释里的MACRO_IGNORE也不会被识别 */ return 0; } ''' # 解析代码 parser.parse(test_code) # 输出捕获到的宏调用 print("\n最终捕获的宏调用:") for macro in macro_calls: print(f"{macro[0]}: {macro[1]}")
关键说明
- 优先级处理:词法分析器中注释、字符串的匹配优先级高于宏,避免误识别注释或字符串内的内容。
- 错误抑制:通过
IGNOREDtoken和p_error函数,让解析器跳过所有非宏调用内容,不会因int、float等关键字或变量抛出语法错误。 - 参数扩展:若需支持参数内的嵌套括号(如
MACRO(func(a,b), c)),需扩展p_argument规则,添加括号嵌套匹配逻辑,避免把括号内的逗号当成参数分隔符。
内容的提问来源于stack exchange,提问作者Ricky
相关产品推荐
相关产品推荐

