我们有一种类似 Markdown 的标记语言,以前使用正则表达式作为解析器,但这非常难于维护。因此,我们使用 EBNF 语法和 mxTextTools/SimpleParse 实现了自己的解决方案。但对于某些可能包含对方的标记,我们遇到了麻烦,我们找不到正确的处理方法。
以下是我们语法的一部分:
newline := "\r\n"/"\n"/"\r"
indent := ("\r\n"/"\n"/"\r"), [ \t]
number := [0-9]+
whitespace := [ \t]+
symbol_mark := [*_>#`%]
symbol_mark_noa := [_>#`%]
symbol_mark_nou := [*>#`%]
symbol_mark_nop := [*_>#`]
punctuation := [(),.!?]
noaccent_code := -(newline / '`')+
accent_code := -(newline / '``')+
symbol := -(whitespace / newline)
text := -newline+
safe_text := -(newline / whitespace / [*_>#`] / '%%' / punctuation)+/whitespace
link := 'http' / 'ftp', 's'?, '://', (-[ \t\r\n<>`^'"*,.!?]/([,.?],?-[ \t\r\n<>`^'"*]))+
strikedout := -[ \t\r\n*_>#^]+
ctrlw := '^W'+
ctrlh := '^H'+
strikeout := (strikedout, (whitespace, strikedout)*, ctrlw) / (strikedout, ctrlh)
strong := ('**', (inline_nostrong/symbol), (inline_safe_nostrong/symbol_mark_noa)* , '**') / ('__' , (inline_nostrong/symbol), (inline_safe_nostrong/symbol_mark_nou)*, '__')
emphasis := ('*',?-'*', (inline_noast/symbol), (inline_safe_noast/symbol_mark_noa)*, '*') / ('_',?-'_', (inline_nound/symbol), (inline_safe_nound/symbol_mark_nou)*, '_')
inline_code := ('`' , noaccent_code , '`') / ('``' , accent_code , '``')
inline_spoiler := ('%%', (inline_nospoiler/symbol), (inline_safe_nop/symbol_mark_nop)*, '%%')
inline := (inline_code / inline_spoiler / strikeout / strong / emphasis / link)
inline_nostrong := (?-('**'/'__'),(inline_code / reference / signature / inline_spoiler / strikeout / emphasis / link))
inline_nospoiler := (?-'%%',(inline_code / emphasis / strikeout / emphasis / link))
inline_noast := (?-'*',(inline_code / inline_spoiler / strikeout / strong / link))
inline_nound := (?-'_',(inline_code / inline_spoiler / strikeout / strong / link))
inline_safe := (inline_code / inline_spoiler / strikeout / strong / emphasis / link / safe_text / punctuation)+
inline_safe_nostrong := (?-('**'/'__'),(inline_code / inline_spoiler / strikeout / emphasis / link / safe_text / punctuation))+
inline_safe_noast := (?-'*',(inline_code / inline_spoiler / strikeout / strong / link / safe_text / punctuation))+
inline_safe_nound := (?-'_',(inline_code / inline_spoiler / strikeout / strong / link / safe_text / punctuation))+
inline_safe_nop := (?-'%%',(inline_code / emphasis / strikeout / strong / link / safe_text / punctuation))+
inline_full := (inline_code / inline_spoiler / strikeout / strong / emphasis / link / safe_text / punctuation / symbol_mark / text)+
line := newline, ?-[ \t], inline_full?
sub_cite := whitespace?, ?-reference, '>'
cite := newline, whitespace?, '>', sub_cite*, inline_full?
code := newline, [ \t], [ \t], [ \t], [ \t], text
block_cite := cite+
block_code := code+
all := (block_cite / block_code / line / code)+
我们的问题是,spoiler、strong 和 emphasis 标记可以任意顺序包含彼此。而且,我们之后可能还会需要更多这样的内联标记。
我目前的方法是针对每种组合创建一个单独的标记(如 inline_noast、inline_nostrong 等),但很明显,随着标记元素数量的增加,这种组合的数量会增长得非常快。
我们的第二个问题是,strong/emphasis 中的这些前瞻在某些语法错误的标记情况下(如 ._.......____.___),表现非常差。解析几 KB 这样的随机文本需要花费几分钟。
我们的语法有问题,还是我们应该使用其他类型的解析器来完成此任务?
2. 解决方案
# 这些是标记(定义为正则表达式)
stg_marker = Token(r'**')
emp_marker = Token(r'*') # 标记是最长匹配项,所以如果可能,优先使用 strong
spo_marker = Token(r'%%')
....
# 语法规则组合标记
contents = Delayed() # 这将在稍后定义,并允许我们递归
strong = stg_marker + contents + stg_marker
emphasis = emp_marker + contents + emp_marker
spoiler = spo_marker + contents + spo_marker
other_stuff = .....
contents += strong | emphasis | spoiler | other_stuff # 这会递归定义 contents
您可以看到,我希望 contents 如何匹配 strong、emphasis 等的嵌套使用。
您的最终解决方案还需要完成更多工作,并且效率可能会成为任何纯 Python 解析器中的问题(有一些解析器是用 C 实现的,但可以从 Pytho