在使用像Parsec这样的解析器组合库时,我应该使用词法分析器吗?

时间:2013-03-05 05:02:53

标签: haskell lexer parsec

在像Haskell的Parsec这样的解析器组合库中编写解析器时,通常有两种选择:

  • 编写词法分析器,将String输入拆分为令牌,然后在[Token]上执行解析
  • 直接在String
  • 上编写解析器组合子

第一种方法通常似乎有意义,因为许多解析输入可以被理解为由空格分隔的标记。

在其他地方,我看到人们建议不要进行标记化(或扫描 lexing ,有些人如何称呼它),并且引用简单性作为主要原因。< / p>

lexing和不做之间的一般权衡是什么?

1 个答案:

答案 0 :(得分:54)

最重要的区别是lexing将翻译您的输入域

漂亮的结果是

  • 您不必再考虑空格了。在直接(非lexing)解析器中,你必须在允许空格的所有地方撒上space解析器,这很容易被遗忘,如果空格必须分开 all

  • 您可以逐个考虑您的输入,这对人类来说很容易。

但是,如果你执行lexing,你会得到问题

  • 您不能再使用String上的常用解析器 - 例如要使用库函数parseFloat :: Parsec String s Float解析数字(对字符串输入流进行操作),您必须执行类似takeNextToken :: TokenParser Stringexecute parseFloat解析器的操作,检查解析结果(通常是Either ErrorMessage a)。编写和限制可组合性很麻烦。

  • 您已调整所有错误消息。如果令牌上的解析器在第20个令牌处失败,那么输入字符串中的那个是什么?您必须手动将错误位置映射回输入字符串,这很繁琐(在Parsec中这意味着调整所有SourcePos值)。

  • 错误报告通常更糟糕。在错误的输入上运行string "hello" *> space *> float,例如"hello4"会在hello之后准确地告诉您预期的空格丢失,而词法分析者只会声称找到了"invalid token"。< / p>

  • 人们期望原子单位并被词法分子分开的许多东西实际上很难&#34;太难了#34;让词法分子识别。以字符串文字为例 - 突然"hello world"不再是两个标记"helloworld"了(但,当然,如果引号未被转义,例如{ {1}}) - 虽然这对于解析器来说非常自然,但它对于词法分析器来说意味着复杂的规则和特殊情况。

  • 您无法在令牌上重复使用解析器。如果您定义如何解析\"中的双精度数,请将其导出,世界其他地方可以使用它;他们不能先运行你的(专用)标记器。

  • 你被困在了。当您开发要解析的语言时,使用词法分析器可能会导致您做出早期决策,修复您之后可能想要更改的内容。例如,假设您定义了一种包含一些String标记的语言。在某些时候,你想引入负面文字(Float-3.4) - 这可能是不可能的,因为词法分析器将空格解释为标记分隔符。使用仅解析器方法,您可以更灵活,更轻松地更改语言。这并不奇怪,因为解析器是一个更复杂的工具,它固有地编码规则

总而言之,我建议在大多数情况下编写无lexer解析器。

最后,词法分析器只是一个&#34; dumbed-down&#34; *解析器 - 如果你还需要一个解析器,将它们组合成一个。


*从计算理论来看,我们知道all regular languages are also context-free languages;词法分析器通常是常规的,解析器无上下文甚至是上下文敏感(像Parsec这样的monadic解析器可以表达上下文敏感性)。