使用 Parsec,我可以相对轻松地编写 String -> Maybe MyType
类型的函数。我现在想基于此为我的类型创建一个 Read 实例;但是,我不明白 readsPrec
是如何工作的或者它应该做什么。
我现在最好的猜测是,readsPrec
用于从头开始构建递归解析器来遍历字符串,在 Haskell 中构建所需的数据类型。然而,我已经有了一个非常强大的解析器,它可以为我做这件事。那么我如何告诉 readsPrec 使用我的解析器呢?它采用的“运算符优先级”参数是什么?它在我的上下文中有何用处?
如果有帮助,我创建了一个 minimal example on Github 。它包含一个类型、一个解析器和一个空白 Read 实例,并且很好地反射(reflect)了我陷入困境的地方。
(背景:真正的解析器是针对Scheme的。)
最佳答案
However, I already have a very robust parser who does that very thing for me.
它实际上不是那么健壮,你的解析器有多余括号的问题,它不会解析
((1) (2))
例如,它会在某些格式错误的输入上引发异常,因为
singleP = Single . read <$> many digit
可以使用读取“”::Int
。
顺便说一句,优先级参数用于确定在某些地方是否需要括号,例如如果你有
infixr 6 :+:
data a :+: b = a :+: b
data C = C Int
data D = D C
您不需要将 C 12
括起来作为 (:+:)
的参数,因为 application 的优先级高于 (:+:)
,但您需要将 C 12
括起来作为 D
的参数。
所以你通常会有类似的东西
readsPrec p = needsParens (p >= precedenceLevel) someParser
其中 someParser
解析输入中的值而不用括号括起来,而 needsParens True thing
解析括号之间的 thing
,而 needParens False thing
解析括在括号中的事物
可选 [您应该始终接受比必要的更多的括号,((((((1)) ))))
应该可以很好地解析为 Int
]。
由于 readsPrec p
解析器用于在读取列表、元组等时将部分输入解析为值的一部分,因此它们不仅必须返回解析后的值,还必须返回剩余部分输入的。
这样,将 parsec
解析器转换为 readsPrec
解析器的简单方法就是
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]
如果您使用的是 GHC,则最好使用 ReadPrec/ReadP
解析器(使用 Text.ParserCombinators.ReadP[rec]
构建)而不是parsec
解析器并定义 readPrec
而不是 readsPrec
。
关于parsing - 使用 Parsec 写入 Read 实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14523728/