在使用像Parsec这样的parsing器组合器库时,是否应该使用词法分析器?

在parsing器组合库(如Haskell的Parsec)中编写parsing器时,通常有两个select:

  • 编写一个词法分析器,将您的Stringinput分成标记,然后在[Token]上执行parsing
  • 直接在String上写分析器组合器

第一种方法通常似乎是有道理的,因为许多parsinginput可以被理解为由空格分隔的令牌。

在其他地方,我曾经看到人们build议不要使用标记(或者扫描或者打点 ,有些人称之为),简单地被引用为主要原因。

勒索与不做之间的一般权衡是什么?

最重要的区别是,lexing会翻译你的input域

这是一个很好的结果

  • 你不必再考虑空白了。 在直接(非lexing)分析器中,必须在允许空格的地方撒上spaceparsing器,这很容易被遗忘,而且如果空格必须分离所有的记号,则会使代码混乱。

  • 你可以一个一个地考虑你的input,这对人类来说很容易。

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

  • 你不能在String上使用普通的parsing器 – 例如,用一个库parsing一个数字函数parseFloat :: Parsec String s Float (在一个stringinputstream上操作),你必须做一些事情,如takeNextToken :: TokenParser Stringexecute parseFloatparsing器,检查parsing结果(通常是Either ErrorMessage a )。 编写和限制可组合性是很麻烦的。

  • 你已经调整了所有的错误信息。 如果您的parsing器的令牌在第20个令牌失败,在inputstring中的位置? 您必须手动将错误位置映射回inputstring,这是乏味的(在Parsec中,这意味着要调整所有的SourcePos值)。

  • 错误报告通常更糟。 运行string "hello" *> space *> float在错误的input如"hello4"会精确地告诉你在hello之后会有空白,而词法分析器会声称已经find了"invalid token"

  • 实际上,许多人认为是primefaces单位 ,并被词法分离的东西实际上对于词法分析器来说是非常“太难”的。 举例来说,string文字 – 突然之间, "hello world"不再是两个"hello and world"标记(当然,如果引号没有被转义,就像\" ) – 这对parsing器来说是非常自然的,复杂的规则和一个词法分析器的特殊情况。

  • 您不能重新使用标记上的parsing器。 如果你定义了如何parsing一个String的双String ,那么导出它,世界其他地方可以使用它; 他们无法首先运行您的(专用)标记器。

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

总而言之,我会build议在大多数情况下编写无词法分析器。

最后,词法分析器只是一个“哑巴”分析器 – 如果你需要一个分析器,把它们合并成一个。


*从计算理论来看,我们知道所有正规语言都是上下文无关的语言 , 词法分析器通常是规则的,分析器上下文无关或甚至是上下文敏感的(像Parsec这样的一元分析器可以表示上下文敏感性)。