解析器组合器:在解析器中处理空白而没有过多的回溯

时间:2013-09-13 17:54:05

标签: parsing parser-combinators

我正在从一个单独的词法分析器和解析器转移到一个对字符数组进行操作的组合解析器。一个问题是如何正确处理空格。

问题

采用由一系列字符'a'和'b'组成的语言。输入中允许使用空格,但不影响程序的含义。

我目前解析这种语言的方法是:

var token = function(p) {
    return attempt(next(
         parse.many(whitespace),
         p));
};

var a = token(char('a'));
var b = token(char('b'));

var prog = many(either(a, b));

这有效,但需要不必要的回溯。对于诸如' _ __ ba_alb'之类的程序(空格在帖子中被删除所以让_是空格),在匹配'b'时,空格被解析两次,首先是{{1}当a失败时,再次a。如果消耗了任何空格,只需删除b就无效,因为attempt永远不会达到either

尝试

我的第一个问题是将b调用移到token之外:

either

这样可行,但现在var a = char('a'); var b = char('b'); var prog = many(token(either(a, b))); 无法轻易重复使用。在构建解析器库时,这似乎需要两次定义解析器:一个实际使用“a”或“b”并且可以在其他解析器中使用的版本,以及一个正确处理空格的版本。它还要求解析器定义通过明确了解每个解析器如何操作以及如何处理空白来解决它。

问题

预期的行为是在令牌之前可以消耗任意数量的空白。如果解析令牌失败,它会回溯到令牌的开头而不是空白的开头。

如果不预处理输入以生成令牌流,如何表达?这有什么好的,现实世界的代码示例吗?我找到的最接近的是Higher-Order Functions for Parsing,但这似乎并没有解决我特别关注的问题。

1 个答案:

答案 0 :(得分:0)

我在我构建的a JSON parser中解决了这个问题。这是我的解决方案的关键部分,其中我遵循了“两次编写解析器”的方法:

  1. 为每个令牌定义基本解析器 - 数字,字符串等

  2. 定义一个token组合子 - 它接受一个基本的令牌解析器并输出一个空白的解析器。咀嚼应该在之后发生,这样只会解析一次空格,如你所说:

    function token(parser) {
        // run the parser, then munch whitespace
    }
    
  3. 使用token组合子生成咀嚼令牌解析器

  4. 使用3.中的解析器为其余语言构建解析器

  5. 我有两个相似版本的解析器没有问题,因为每个版本实际上是不同的。这有点烦人,但成本很低。您可以查看the full code here