代码之家  ›  专栏  ›  技术社区  ›  Daniel Kluev

实现类标记语言的解析器

  •  8
  • Daniel Kluev  · 技术社区  · 14 年前

    我有一种类似markdown和SO使用的标记语言。

    遗留解析器是基于regex的,维护起来非常困难,所以我提出了自己的基于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)+
    

    我目前的解决方案只是为每个组合创建单独的标记(inline\u noast、inline\u nostrong等),但很明显,随着标记元素数量的增加,这种组合的数量增长太快。

    第二个问题是,在一些错误的标记(比如 __._.__*__.__...___._.____.__**___*** (许多随机放置的标记符号)。解析几kb这样的随机文本需要几分钟。

    是我的语法有问题还是我应该使用其他类型的解析器来完成这个任务?

    1 回复  |  直到 14 年前
        1
  •  6
  •   andrew cooke    14 年前

    如果一个事物包含另一个事物,那么通常你把它们当作单独的标记,然后把它们嵌套在语法中。雷普利( http://www.acooke.org/lepl PyParsing(可能是最流行的纯Python解析器)都允许递归地嵌套东西。

    因此,在Lepl中,您可以编写如下代码:

    # these are tokens (defined as regexps)
    stg_marker = Token(r'\*\*')
    emp_marker = Token(r'\*') # tokens are longest match, so strong is preferred if possible
    spo_marker = Token(r'%%')
    ....
    # grammar rules combine tokens
    contents = Delayed() # this will be defined later and lets us recurse
    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 # this defines contents recursively
    

    然后你就可以看到,我希望,内容将如何匹配强,强调等嵌套使用。

    对于您的最终解决方案,还有很多事情要做,效率可能是任何纯Python解析器的一个问题(有些解析器是用C实现的,但是可以从Python调用)。这些会更快,但可能更难使用;我不能推荐任何,因为我没有使用它们)。