使用Parsec编写一个Read实例

时间:2013-01-25 14:23:54

标签: parsing haskell parsec

使用Parsec,我能够相对轻松地编写String -> Maybe MyType类型的函数。我现在想基于此为我的类型创建一个Read实例;但是,我不明白readsPrec是如何工作的,或者它应该做什么。

我现在最好的猜测是readsPrec用于从头开始构建递归解析器以遍历字符串,在Haskell中构建所需的数据类型。但是,我已经拥有一个非常强大的解析器,它为我做了那件事。那么如何告诉readsPrec使用我的解析器?什么是“运算符优先级”参数,它在我的上下文中有什么用?

如果有帮助,我创建了minimal example on Github。它包含一个类型,一个解析器和一个空白的Read实例,并且很好地反映了我被卡住的地方。

(背景:真正的解析器用于Scheme。)

1 个答案:

答案 0 :(得分:2)

  

但是,我已经有了一个非常强大的解析器,它为我做了那件事。

它实际上并不那么健壮,你的解析器有多余的括号问题,它不会解析

((1) (2))
例如

,它会在一些格式错误的输入上抛出异常,因为

singleP = Single . read <$> many digit

可以使用read "" :: Int

除此之外,优先级参数用于确定在某些地方是否需要括号,例如:如果你有

infixr 6 :+:

data a :+: b = a :+: b

data C = C Int

data D = D C

C 12作为(:+:)的参数,你不需要使用括号,因为应用程序的优先级高于(:+:),但是你需要括号{ {1}}作为C 12的参数。

所以你通常会有像

这样的东西
D

其中readsPrec p = needsParens (p >= precedenceLevel) someParser 解析输入中的值而不括括号,someParser在括号之间解析needsParens True thing,而thing解析needsParens False thing 可选括在括号中[你应该总是接受比必要更多的括号,thing应该解析为((((((1))))))]。

由于Int解析器用于在读取列表,元组等时将部分输入解析为值的一部分,因此它们不仅必须返回已解析的值,还必须返回输入的其余部分。

有了这个,将readsPrec p解析器转换为parsec解析器的简单方法就是

readsPrec

如果你正在使用GHC,最好使用withRemaining :: Parser a -> Parser (a, String) withRemaining p = (,) <$> p <*> getInput parsecToReadsPrec :: Parser a -> Int -> ReadS a parsecToReadsPrec parsecParser prec input = case parse (withremaining $ needsParens (prec >= threshold) parsecParser) "" input of Left _ -> [] Right result -> [result] 解析器(使用ReadPrec / ReadP构建)而不是Text.ParserCombinators.ReadP[rec]解析器,而是定义parsec readPrec