使用Parsec,我能够相对轻松地编写String -> Maybe MyType
类型的函数。我现在想基于此为我的类型创建一个Read
实例;但是,我不明白readsPrec
是如何工作的,或者它应该做什么。
我现在最好的猜测是readsPrec
用于从头开始构建递归解析器以遍历字符串,在Haskell中构建所需的数据类型。但是,我已经拥有一个非常强大的解析器,它为我做了那件事。那么如何告诉readsPrec
使用我的解析器?什么是“运算符优先级”参数,它在我的上下文中有什么用?
如果有帮助,我创建了minimal example on Github。它包含一个类型,一个解析器和一个空白的Read实例,并且很好地反映了我被卡住的地方。
(背景:真正的解析器用于Scheme。)
答案 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
。