如何使用 Haskell Parsec 阻止不一致的浮点输入?

How to block inconsistent floating point inputs using Haskell Parsec?

我被指派使用 Haskell 解析器组合器(即通过导入 Parsec.Text)制作一个相当简单的计算器解析器。该解析器需要对整数和浮点输入进行操作。有一个必不可少的代码可以介绍一下我要找的东西:

import Text.Parsec hiding(digit)
import Data.Functor

type Parser a = Parsec String () a

digit :: Parser Char
digit = oneOf ['0'..'9']

number :: Parser Integer
number = read <$> many1 digit

fp_char :: Parser String 
fp_char = many1 digit

fp_number :: Parser Double
fp_number = read <$> parser where
    parser = (++) <$> fp_char <*> (option "" $ (:) <$> char '.' <*> fp_char)   

addition :: Parser Integer
addition = do
    lhv <- number
    spaces
    char '+'
    spaces
    rhv <- number
    return $ lhv + rhv

fp_addition :: Parser Double
addition = do
    lhv <- fp_number
    spaces
    char '+'
    spaces
    rhv <- fp_number
    return $ lhv + rhv

所以,我遇到了一个案例:如何像这里的实例一样阻止 fp 输入:

"123h.578" "600.w57"

两者都被认为是错误触发器,但在上面的代码中,结果是接受了一部分错误输入:事实上,解析器一旦出现错误字符就会像字母一样丢弃,后面的字符也会被删除。因此,问题似乎是相同或相似的输入仍然是有效的输入数据,即使它是部分输入。我的解决方案与这样的功能有关:

isValidInput :: Parser String -> Bool
isValidInput (x:xs) = if x `elem` ['0'..'9'] then isValidInput xs else False

由于 isValidInput 导致的类型不当,它无法正常工作。更重要的是,我想念如何使用这个辅助代码将它添加到核心计算函数中,比如这里给出的fp_addition。

作为一般规则,Parsec 解析器应该 编写为使用输入流的有效前缀并在它们无法识别的第一个字符处停止。

因此,字符串 "123h.578" 应该被 fp_numbernumber 成功解析,因为前缀 "123" 是这些解析器可接受的输入。因此,您现有的解析器 fp_numbernumber 具有正确的行为:

> parseTest fp_number "123h.578"
123.0      -- this is correct
> parseTest number "123h.578"
123        -- this is also correct

检查这个无效的 h 字符应该在更高级别进行。通常,您将拥有一个顶级解析器,它尝试解析整个流并使用 eof 解析器来确保没有无法解析的字符。例如,解析器:

fp_expression :: Parser Double
fp_expression =
  (try fp_addition <|> fp_number) <* eof

将解析这些:

> parseTest fp_expression "123+0.578"
123.578
> parseTest fp_expression "123.578"
123.578

同时拒绝你的其他例子:

> parseTest fp_expression "123h.578"
parse error at (line 1, column 4):
unexpected 'h'
expecting "." or end of input
> parseTest fp_expression "600.w57"
parse error at (line 1, column 5):
unexpected "w"

您不需要显式 isValidInput 函数。如果您要定义一个,它可能会由 运行 像 fp_expression 这样的解析器来实现,以查看它是否成功解析了整个流。

此外,您可能会发现学习 this tutorial 很有帮助。通读有关 "Very simple expression parsing" 的章节将对您学习编写此类解析器的远非显而易见的基础知识有很大帮助。