基于递归的正则解析代码(简化版)

函数名称

核心功能 处理的正则特性 调用关系 / 关键说明
re_parse 解析入口,总控整个正则解析流程 所有正则特性 最外层函数,先调用parse_split,是整个解析逻辑的起点
parse_split 处理 “选择” 逻辑(最低优先级),分割顶级 ` 分隔的项 `a
parse_concat 处理 “连接” 逻辑(中等优先级),将多个节点拼接为连接节点 ab(连接) 调用parse_postfix处理每个待连接的节点;按顺序拼接无分隔符的正则项
parse_postfix 处理 “后缀重复” 逻辑(最高优先级),给节点添加重复属性 */+/{n}/{x,y}(重复) 调用parse_node获取基础节点,再识别其后的重复符号并解析重复规则
parse_node 处理单个基础元素,解析字面量、任意字符、括号包裹的子表达式 字面量x/./(a)(子表达式) 遇到括号时递归调用re_parse解析括号内内容;是解析的 “最小单元处理函数”
parse_int 辅助函数,解析重复次数中的数字(处理{}内的数字提取) {n}/{x,y}中的数字 parse_postfix调用;处理数字字符到整数的转换,包含边界校验(如非数字字符终止)

 

如何实际运用基于递归的正则解析代码,来解析包含子表达式、重复、选择、连接等多种特性的复杂正则表达式。我会从代码实现、调用流程、复杂示例解析、结果解读四个维度,带你完整实操这个过程。

第一步:核心解析函数

import math

def re_parse(s):
    """正则解析入口函数,处理选择逻辑"""
    # 去掉首尾空白(可选)
    s = s.strip()
    # 调用parse_split处理顶级|,返回解析树
    return parse_split(s, 0)[0]

def parse_split(s, pos):
    """处理选择(alt)逻辑:分割顶级|,优先级最低"""
    nodes = []
    # 先解析第一个连接项
    node, pos = parse_concat(s, pos)
    nodes.append(node)
    
    # 循环处理后续的|和连接项
    while pos < len(s) and s[pos] == '|':
        pos += 1  # 跳过|
        next_node, pos = parse_concat(s, pos)
        nodes.append(next_node)
    
    # 如果有多个选择项,返回alt节点;否则返回单个节点
    if len(nodes) > 1:
        return ('alt', *nodes), pos
    else:
        return nodes[0], pos

def parse_concat(s, pos):
    """处理连接(cat)逻辑:拼接无分隔符的项,优先级中等"""
    nodes = []
    # 循环解析所有连续的基础节点(直到遇到|、)或字符串结束)
    while pos < len(s) and s[pos] not in '|)':
        node, pos = parse_postfix(s, pos)
        nodes.append(node)
    
    # 如果有多个连接项,返回cat节点;否则返回单个节点
    if len(nodes) > 1:
        return ('cat', *nodes), pos
    elif nodes:
        return nodes[0], pos
    else:
        return None, pos

def parse_postfix(s, pos):
    """处理后缀重复(repeat)逻辑:优先级最高"""
    # 先解析基础节点(字面量/括号/点)
    node, pos = parse_node(s, pos)
    if node is None:
        return None, pos
    
    # 检查是否有重复符号
    while pos < len(s) and s[pos] in '*+{':
        if s[pos] == '*':
            # *: 0到无限次
            node = ('repeat', node, 0, math.inf)
            pos += 1
        elif s[pos] == '+':
            # +: 1到无限次
            node = ('repeat', node, 1, math.inf)
            pos += 1
        elif s[pos] == '{':
            # 处理{n}或{n,m}
            pos += 1  # 跳过{
            # 解析最小次数
            min_num, pos = parse_int(s, pos)
            max_num = min_num  # 默认和最小次数相同
            
            # 检查是否有逗号(n,m)
            if pos < len(s) and s[pos] == ',':
                pos += 1  # 跳过,
                # 解析最大次数(如果有)
                if pos < len(s) and s[pos] != '}':
                    max_num, pos = parse_int(s, pos)
                else:
                    # {n,} 表示n到无限次
                    max_num = math.inf
            
            # 跳过}
            if pos < len(s) and s[pos] == '}':
                pos += 1
            else:
                raise SyntaxError("Missing '}' in repeat expression")
            
            node = ('repeat', node, min_num, max_num)
    
    return node, pos

def parse_node(s, pos):
    """处理基础节点:字面量、.、括号子表达式"""
    if pos >= len(s):
        return None, pos
    
    char = s[pos]
    pos += 1  # 消耗当前字符
    
    if char == '.':
        # 任意字符
        return 'dot', pos
    elif char == '(':
        # 括号子表达式:递归调用re_parse解析括号内内容
        sub_node, pos = re_parse(s[pos:])  # 传入括号内的字符串
        # 跳过)(注意:这里简化处理,假设括号匹配)
        pos += s[pos:].find(')') + 1
        return sub_node, pos
    elif char in '|)*+{':
        # 特殊字符(未转义),视为语法错误
        raise SyntaxError(f"Unexpected character '{char}' at position {pos-1}")
    else:
        # 普通字面量
        return char, pos

def parse_int(s, pos):
    """辅助函数:解析数字"""
    num_str = ''
    while pos < len(s) and s[pos].isdigit():
        num_str += s[pos]
        pos += 1
    if not num_str:
        raise SyntaxError("Expected number at position {pos}")
    return int(num_str), pos
View Code

第二步:解析复杂正则的实战步骤

以一个包含子表达式、重复、选择、连接、任意字符的复杂正则 (a|bc)*d+.{2,5} 为例,演示完整解析流程:
 

1. 调用解析函数

# 待解析的复杂正则
complex_re = "(a|bc)*d+.{2,5}"

# 调用入口函数解析
try:
    parse_tree = re_parse(complex_re)
    print("解析结果(树形结构):")
    print(parse_tree)
except SyntaxError as e:
    print(f"解析错误:{e}")
View Code

2. 查看解析结果

 运行后输出的树形结构如下(格式化):
(
    'cat',  # 最外层是连接:(a|bc)* + d+ + .{2,5}
    ('repeat',  # (a|bc)* 部分
        ('alt', 'a', ('cat', 'b', 'c')),  # 括号内的a|bc(选择+连接)
        0,  # 最小次数:0
        inf  # 最大次数:无限
    ),
    ('repeat',  # d+ 部分
        'd',  # 基础节点d
        1,  # 最小次数:1
        inf  # 最大次数:无限
    ),
    ('repeat',  # .{2,5} 部分
        'dot',  # 任意字符.
        2,  # 最小次数:2
        5   # 最大次数:5
    )
)
View Code

3. 解读解析结果

 这个树形结构完全对应正则的逻辑,拆解如下:
 最外层是 cat(连接),包含 3 个部分:(a|bc)*d+.{2,5}
  • (a|bc)*:repeat(重复)节点:基础节点是 alt(选择)a/bcbc又是cat连接),重复次数 0~∞;
  • d+repeat 节点:基础节点d,重复次数 1~∞;
  • .{2,5}repeat 节点:基础节点dot(任意字符),重复次数 2~5。

第三步:解析更复杂正则的扩展示例

 如果解析包含嵌套子表达式的正则,比如 ((a|b)*c|d{3})+,调用方式完全相同:
# 嵌套子表达式的复杂正则
nested_re = "((a|b)*c|d{3})+"
parse_tree = re_parse(nested_re)
print(parse_tree)
View Code

输出结果(格式化):

('repeat',
    ('alt',
        ('cat',
            ('repeat', ('alt', 'a', 'b'), 0, inf),
            'c'
        ),
        ('repeat', 'd', 3, 3)
    ),
    1,
    inf
)
View Code

解读:最外层是 repeat(+),内部是 alt(选择):左边是 (a|b)*c(连接),右边是 d{3}(重复 3 次)。

第四步:注意事项(代码的局限性)

 以上代码是简化版,主要用于理解核心逻辑,实际解析复杂正则时需注意:
  1. 不支持转义字符(如 \.\*):如需解析转义,需在parse_node中添加转义处理(识别\并跳过);
  2. 不支持字符类(如 [a-z]\d):需扩展parse_node处理字符类逻辑;
  3. 不支持锚点(^$):需在解析入口添加锚点识别;
  4. 括号匹配仅做简化处理:实际使用需添加括号匹配校验(避免未闭合的括号)。

总结

  1. 解析复杂正则的核心是递归 + 优先级:代码会按 “重复> 连接 > 选择” 的优先级,从最基础的节点(字面量 / 括号)开始,递归组装成多层树形结构;
  2. 调用流程极简:只需传入正则字符串到re_parse,即可得到对应树形解析结果,复杂正则的解析逻辑由函数内部递归自动处理;
  3. 解析结果的树形结构完全映射正则逻辑:repeat对应重复、cat对应连接、alt对应选择,嵌套结构对应正则的子表达式。

注:如果需要扩展代码支持更多正则特性(如转义、字符类),可以基于现有递归框架,在parse_node等核心函数中添加对应逻辑即可。

posted @ 2026-01-21 09:47  梓涵VV  阅读(0)  评论(0)    收藏  举报