我正在尝试解析sgf文件(描述go游戏的文件)。在这些文件中有表单的键值对(在sgf规范中,它们被称为属性id和属性值,但我使用键和值,希望人们在阅读标题时知道我在说什么):
key[value]
或
key[value1][value2]...[valuen]
也就是说,可能有一个或多个值。问题是值的类型取决于键。因此,例如,如果键是B
(用于在go中玩黑石)。该值应该是由两个字母描述的坐标,例如:B[ab]
。也可能是密钥是AB
(用于添加一些黑色宝石,用于设置棋盘),然后该值是一个坐标列表,如下所示:AB[ab][cd][fg]
。也可能是密钥是C
(用于评论)。然后,该值只是一个字符串C[this is a comment]
。
当然这可以通过类型
来描述type Property = (String, [String])
但我认为拥有像
这样的东西会更好data Property = B Coordinate | AB [Coordinate] | C String ...
或者也许其他类型更好地利用类型系统,并且不需要我一直转换为字符串和从字符串转换。
问题是,我需要一个解析器,根据键类型返回不同的值类型,但我认为这会导致类型问题,因为解析器只能返回一种类型的值。
你会如何解析这样的事情?
答案 0 :(得分:6)
这实际上是一个简单的选择,不需要monadic解析。我将使用应用程序界面来证明这一点。
为每个属性id 和构建一个解析器,其属性值有点像这样:
black = B <$> (char 'C' *> coordinate)
white = W <$> (char 'W' *> coordinate)
addBlack = AB <$> (string "AB" *> many1 coordinate)
(假设你已经构建了一个coordinate
解析器来吃括号并返回Coordinate
类型的东西。)
其中每一个都有类型Parser Property
(使用您的第二个更好的结构化数据类型),所以现在我们只需让解析器在它们之间进行选择。如果属性ID都具有不同的首字母,那么当您使用解析器输入错误的id时,它将在不消耗输入的情况下失败,这对于选择运算符来说是理想的:
myparser = black <|> white <|> addBlack
但我怀疑有一个AW
id用于添加白色宝石,所以我们需要警告它们使用try重叠,当解析器失败时会回溯:
mybetterparser = black <|> white <|> (try addBlack <|> try addWhite)
我已经将解析器与一个共同的开头放在一起,并尝试使用它们回到开头。