使用PEG解析器进行BBCode解析:pegjs还是...什么?

7
我有一个将bbcode转换为html的转换器,它响应文本区域中的更改事件。目前,这是使用一系列正则表达式完成的,并且存在许多病态案例。我一直想在这个语法上磨刀,但不想涉及到琐碎的工作。但是...最近我意识到pegjs,它似乎是PEG解析器生成的一个相当完整的实现。我已经指定了大部分语法,但现在我想知道这是否是使用完整解析器的适当用法。
我的具体问题是:
  1. 由于我的应用程序依赖于将我能够转换为HTML的内容进行翻译,并将其余部分作为原始文本保留,因此使用一个可以在语法错误上失败的解析器来实现bbcode是否合理?例如:[url=/foo/bar]点击我![/url] 当输入关闭标记的结束括号时,肯定会成功。但是用户在此期间会看到什么呢?使用正则表达式,我只需忽略不匹配的内容,并将其视为普通文本进行预览。使用正式语法,我不知道是否可能,因为我依赖于从解析树创建HTML,而解析失败的内容是什么?

  2. 我不清楚转换应该在哪里进行。在基于正式lex/yacc的解析器中,我将具有标头文件和表示节点类型的符号。在pegjs中,我得到带有节点文本的嵌套数组。我可以将翻译后的代码作为pegjs生成的解析器的操作发出,但似乎将解析器和发射器组合在一起是一种代码异味。然而,如果我调用PEG.parse.parse(),我会得到像这样的东西:

[
       [
          "[",
          "img",
          "",
          [
             "/",
             "f",
             "o",
             "o",
             "/",
             "b",
             "a",
             "r"
          ],
          "",
          "]"
       ],
       [
          "[/",
          "img",
          "]"
       ]
    ]

给定类似以下的语法:

document
   = (open_tag / close_tag / new_line / text)*

open_tag
   = ("[" tag_name "="? tag_data? tag_attributes? "]")


close_tag
   = ("[/" tag_name "]") 

text
   = non_tag+

non_tag
   = [\n\[\]]

new_line
   = ("\r\n" / "\n")

我当然缩写了语法,但你能够理解我的意思。因此,如果你注意到,数组中没有上下文信息告诉我我有什么样的节点,我只能再次进行字符串比较,即使解析器已经完成了这个过程。我希望可以定义回调和使用操作来在解析期间运行它们,但是关于如何做到这一点,网络上几乎没有什么信息。
我是不是在错误的方向上努力?我应该退回到正则表达式扫描并忘记解析吗?
谢谢

Steve,你的问题非常有趣(+1),我也想在扩展中做同样的事情:解析文本区域中的BBCode(不幸的是这仍然是论坛使用的格式),并使用PEG.js或其他任何东西除了正则表达式从输入的文本创建“实时”预览。你是否成功创建了BBCode解析器的语法?你能否通过GitHub或其他方式分享你的解决方案?那会帮助我很多。非常感谢您的帮助! - Sk8erPeter
我使用了patorjk的bbcode解析器。它非常好用,如果你有特殊的标签需求,也可以进行调整。 - Steve Ross
谢谢,我已经看过这个库了,但它使用正则表达式,而我想避免使用正则表达式,因为理论上,使用正则表达式解析BBCode在某些情况下可能会出现错误(»»链接),例如当它们嵌套在彼此中时等。这就是为什么我想使用解析表达式语法形式来完成它。所以你没有尝试对你开始的语法进行改进吗? :) 你能分享一下它的基础吗? :) - Sk8erPeter
3个回答

4

第一个问题(关于不完整文本的语法):

您可以添加

incomplete_tag = ("[" tag_name "="? tag_data? tag_attributes?)
//                         the closing bracket is omitted ---^

open_tag后并改变document以在结尾处包含一个不完整的标签。诀窍在于提供解析器需要的所有产生式来进行始终的解析,但有效的产生式首先出现。你随后可以在实时预览过程中忽略incomplete_tag
第二个问题(如何包含操作):
在表达式之后编写所谓的操作。一个操作是被大括号包括的JavaScript代码,并且允许在pegjs表达式之后添加,即也可以在产生式中间添加!
实际上,像{ return result.join("") }这样的操作几乎总是必要的,因为pegjs会分割成单个字符。也可以返回复杂的嵌套数组。因此,我通常在语法开头的pegjs初始化器中编写帮助程序函数,以保持操作小。如果您仔细选择函数名称,则操作是自我记录的。
有关示例,请参见PEG for Python style indentation。免责声明:这是我的答案。

3

关于您的第一个问题,我必须说实现实时预览很困难。您指出的问题是正确的,因为解析器无法理解输入是“正在进行中”的状态。Peg.js会告诉您错误所在的位置,因此也许您可以获取该信息并向前移动几个单词再次进行解析,或者如果缺少结束标记,请尝试在末尾添加它。

您的第二个问题则较为简单,但是语法可能不太好看。基本上,您需要在每个规则上放置回调函数,例如:

text
   = text:non_tag+ {
     // we captured the text in an array and can manipulate it now
     return text.join("");
   }

目前,您必须在语法中内联编写这些回调函数。我现在正在工作中处理很多这样的事情,所以我可能会向peg.js发起pullrequest来解决这个问题。但我不确定何时有时间做这件事。


1
尝试使用类似这样的替换规则。你已经在正确的轨道上了,只需要告诉它如何组装结果即可。
文本 = result:non_tag+ { return result.join(''); }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接