3

我刚刚开始学习解析,我在 Haskell 中编写了这个简单的解析器(使用 parsec)来读取 JSON 并为其构建一个简单的树。我正在使用RFC 4627中的语法。

但是,当我尝试解析 string 时{"x":1 },我得到了输出:

解析错误(第 1 行,第 8 列):
意外的“}”
期待空白字符或“,”

只有当我在右大括号 (]) 或 mustachio (}) 之前有空格时,才会出现这种情况。

我做错了什么?如果我在结束符号之前避免空格,它就可以完美地工作。

4

2 回答 2

6

Parsec 不会自动进行倒带和回溯。当您编写sepBy member valueSeparator时,valueSeparator会消耗空格,因此解析器将解析您的值,如下所示:

{"x":1 }
[------- object
%        beginObject
 [-]     name
    %    nameSeparator
     %   jvalue
      [- valueSeparator
       X In valueSeparator: unexpected "}"

Legend:
[--]     full match
%        full char match
[--      incomplete match
X        incomplete char match

valueSeparator失败时,Parsec 不会返回并尝试不同的解析组合,因为一个字符已经在valueSeparator.

您有两种选择来解决您的问题:

  1. 由于空格在 JSON 中是微不足道的,因此始终在重要标记之后使用空格,而不是以前。所以,atok应该只在 char 之后消耗空格,所以它的定义是tok c = char c *> ws( (*>)from Control.Applicative); 将相同的规则应用于所有其他解析器。由于以这种方式输入“错误的解析器”后您将永远不会消耗空白空间,因此您最终不必回溯。
  2. 在 Parsec 中使用回溯,方法是try在解析器前面添加可能消耗多个字符的解析器,如果它们失败,应该回退它们的输入。

编辑:更新 ASCII 图形更有意义。

于 2012-01-02T07:22:40.303 回答
1

一个通用的解决方案是让所有解析器跳过尾随空格。查看Parsec 文档中的lexeme(in ParsecToken) 以获得一种巧妙的方法来执行此操作,或者自己制作一个简单的版本:

 lexeme parser = do result <- parser
                    spaces
                    return result

然后在所有标记上使用此函数(如数字文字)。这样,您只需要担心表达式开头的空格。

有关和朋友的更多信息,请查看Parsec 文档ParsecToken的“词法分析”部分。

仅在令牌之后跳过空格是有意义的,除非在开始时您可以手动跳过它。即使您最终不使用该ParsecToken模块,您也应该采用这种方法。

似乎您已经拥有tok了类似于我的行为,只是它在两边都lexeme消耗了空格。将其更改为仅在令牌之后使用空格,并手动忽略输入开头的空格。那应该(理想情况下:))解决问题。

于 2012-01-02T07:15:54.147 回答