"try" 回溯多远?

How far does "try" back track?

所以...我弄乱了 CSV 格式的记录:

23,95489,0,20,9888

由于语言设置,浮点数使用逗号作为分隔符...在逗号分隔值文件中...

问题是该文件没有为每个浮点数设置合适的格式。有些根本没有点,点后面的数字也各不相同。

我的想法是构建一个 MegaParsec 解析器,它会尝试读取所有可能的浮点格式,继续前进,如果发现错误则返回。

例如上面的例子:

  1. 阅读 23,95489 -> 好
  2. 阅读 0,20 -> 好(到目前为止)
  3. 读取 9888 -> 错误(因为列的值太高(由 guard 检查))
  4. (回溯到 2。)读取 0 -> 再次好
  5. 阅读 20,9888 -> 好
  6. 完成

我已经将其实现为(此处为伪代码):

floatP = try pointyFloatP <|> unpointyFloatP

lineP = (,,) <$> floatP <* comma <*> floatP <* comma <*> floatP <* comma

我的问题是 try 显然只适用于 'current' 浮动。没有回溯到以前的位置。这是正确的吗?

如果是这样...我将如何实施进一步的回溯?

How far does “try” back track?

如果 p 解析成功,解析器 try p 消耗与 p 一样多的输入,否则它根本不消耗任何输入。因此,如果您从回溯的角度来看它,它会回溯到您调用它时所在的位置。

My problem is that apparently the try only works in the 'current' float. There is no backtracking to previous positions. Is this correct?

是,try不"unconsume"输入。它所做的只是在不消耗任何输入的情况下从您提供的解析器中的故障中恢复。它不会撤消您之前应用的任何解析器的影响,也不会影响您在 try p 成功后应用的后续解析器。

And if so ... how would I go about implementing further back tracking?

基本上你想要的是不仅要知道 pointyFloatP 是否在当前输入上成功,还要知道你的 lineP 的其余部分在成功 pointyFloatP 之后是否会成功 - 如果您不想回到申请 pointyFloatP 之前。所以基本上你想要 try 中的整个剩余行的解析器,而不仅仅是 float 解析器。

要实现这一点,您可以让 floatP 将剩余行的解析器作为参数,如下所示:

floatP restP = try (pointyFloatP <*> restP) <|> unpointyFloatP <*> restP

请注意,这种回溯不会非常有效(但我假设您知道这样做)。

更新: 为更复杂的行包含自定义单子解析器。

使用 List Monad 进行简单解析

列表 monad 的回溯 "parser" 比 Megaparsec 更好。例如,要解析单元格:

row :: [String]
row = ["23", "95489", "0", "20", "9888"]

满足特定界限(例如,小于 30)的三列值,您可以生成所有可能的解析:

{-# OPTIONS_GHC -Wall #-}

import Control.Monad
import Control.Applicative

rowResults :: [String] -> [[Double]]
rowResults = cols 3
  where cols :: Int -> [String] -> [[Double]]

        cols 0 [] = pure []   -- good, finished on time
        cols 0 _  = empty     -- bad, didn't use all the data

        -- otherwise, parse exactly @n@ columns from cells @xs@
        cols n xs = do
          -- form @d@ from one or two cells
          (d, ys) <- num1 xs <|> num2 xs
          -- only accept @d < 30@
          guard $ d < 30
          ds <- cols (n-1) ys
          return $ d : ds

        -- read number from a single cell
        num1 (x:xs) | ok1 x = pure (read x, xs)
        num1 _ = empty

        -- read number from two cells
        num2 (x:y:zs) | ok1 x && ok2 y = pure (read (x ++ "." ++ y), zs)
        num2 _ = empty

        -- first cell: "0" is okay, but otherwise can't start with "0"
        ok1 "0" = True
        ok1 (c:_) | c /= '0' = True
        ok1 _ = False

        -- second cell: can't end with "0" (or *be* "0")
        ok2 xs = last xs /= '0'

上面的基于列表的解析器试图通过假设如果 "xxx,yyy" 是一个数字,"xxx" 不会以零开头(除非它只是“0”)来减少歧义,并且"yyy" 不会以零结尾(或者就此而言,是单个“0”)。如果这不对,请适当修改 ok1ok2

应用于row,这给出了单一明确的解析:

> rowResults row
[[23.95489,0.0,20.9888]]

应用于不明确的行,它给出了所有解析:

> rowResults ["0", "12", "5", "0", "8601"]
[[0.0,12.5,0.8601],[0.0,12.5,0.8601],[0.12,5.0,0.8601]]

无论如何,我建议使用标准 CSV 解析器将您的文件解析为 String 单元格矩阵,如下所示:

dat :: [[String]]
dat = [ ["23", "95489", "0", "20", "9888"]
      , ["0", "12", "5", "0", "8601"]
      , ["23", "2611", "2", "233", "14", "422"]
      ]

然后使用上面的rowResults得到不明确行的行号:

> map fst . filter ((>1) . snd) . zip [1..] . map (length . rowResults) $ dat
[2]
>

或无法解析:

> map fst . filter ((==0) . snd) . zip [1..] . map (length . rowResults) $ dat
[]
>

假设没有无法解析的行,您可以重新生成一个可能的固定文件,即使某些行不明确,但只需为每一行抓取第一个成功解析的文件:

> putStr $ unlines . map (intercalate "," . map show . head . rowResults) $ dat
23.95489,0.0,20.9888
0.0,12.5,0.8601
23.2611,2.233,14.422
>

使用基于 List Monad 的自定义 Monad 进行更复杂的解析

对于更复杂的解析,例如,如果您想解析一行:

type Stream = [String]
row0 :: Stream
row0 = ["Apple", "15", "1", "5016", "2", "5", "3", "1801", "11/13/2018", "X101"]

混合使用字符串和数字,编写一个基于列表 monad 的 monadic 解析器实际上并不难,它可以生成所有可能的解析。

关键思想是将解析器定义为一个函数,该函数接受一个流并生成一个可能的解析列表,每个可能的解析表示为成功的对象的 元组 从流的开头开始解析,与流的其余部分配对。包装在一个新类型中,我们的并行解析器看起来像:

newtype PParser a = PParser (Stream -> [(a, Stream)]) deriving (Functor)

请注意与 Text.ParserCombinators.ReadP 中类型 ReadS 的相似性,从技术上讲,它也是一个 "all possible parses" 解析器(尽管您通常只期望从 reads调用):

type ReadS a = String -> [(a, String)]

无论如何,我们可以像这样为 PParser 定义一个 Monad 实例:

instance Applicative PParser where
  pure x = PParser (\s -> [(x, s)])
  (<*>) = ap
instance Monad PParser where
  PParser p >>= f = PParser $ \s1 -> do  -- in list monad
    (x, s2) <- p s1
    let PParser q = f x
    (y, s3) <- q s2
    return (y, s3)

这里没有什么太棘手的:pure x returns 一个可能的解析,即结果 x 具有未更改的流 s,而 p >>= f应用第一个解析器 p 生成一个可能的解析列表,在列表 monad 中一个一个地获取它们以计算要使用的下一个解析器 q (按照通常的 monadic 操作,可以取决于第一次解析的结果),并生成可能返回的最终解析列表。

AlternativeMonadPlus 实例非常简单——它们只是从列表 monad 中移除空虚和交替:

instance Alternative PParser where
  empty = PParser (const empty)
  PParser p <|> PParser q = PParser $ \s -> p s <|> q s
instance MonadPlus PParser where

对于 运行 我们的解析器,我们有:

parse :: PParser a -> Stream -> [a]
parse (PParser p) s = map fst (p s)

现在我们可以介绍原语了:

-- read a token as-is
token :: PParser String
token = PParser $ \s -> case s of
  (x:xs) -> pure (x, xs)
  _ -> empty

-- require an end of stream
eof :: PParser ()
eof = PParser $ \s -> case s of
  [] -> pure ((), s)
  _ -> empty

和组合器:

-- combinator to convert a String to any readable type
convert :: (Read a) => PParser String -> PParser a
convert (PParser p) = PParser $ \s1 -> do
  (x, s2) <- p s1     -- for each possible String
  (y, "") <- reads x  -- get each possible full read
                      -- (normally only one)
  return (y, s2)

和我们 CSV 行中各种 "terms" 的解析器:

-- read a string from a single cell
str :: PParser String
str = token

-- read an integer (any size) from a single cell
int :: PParser Int
int = convert (mfilter ok1 token)

-- read a double from one or two cells
dbl :: PParser Double
dbl = dbl1 <|> dbl2
  where dbl1 = convert (mfilter ok1 token)
        dbl2 = convert $ do
          t1 <- mfilter ok1 token
          t2 <- mfilter ok2 token
          return $ t1 ++ "." ++ t2

-- read a double that's < 30
dbl30 :: PParser Double
dbl30 = do
  x <- dbl
  guard $ x < 30
  return x

-- rules for first cell of numbers:
-- "0" is okay, but otherwise can't start with "0"
ok1 :: String -> Bool
ok1 "0" = True
ok1 (c:_) | c /= '0' = True
ok1 _ = False

-- rules for second cell of numbers:
-- can't be "0" or end in "0"
ok2 :: String -> Bool
ok2 xs = last xs /= '0'

然后,对于特定的行模式,我们可以像通常使用单子解析器一样编写行解析器:

-- a row
data Row = Row String Int Double Double Double
               Int String String deriving (Show)
rowResults :: PParser Row
rowResults = Row <$> str <*> int <*> dbl30 <*> dbl30 <*> dbl30
                 <*> int <*> str <*> str <* eof

并获取所有可能的解析:

> parse rowResults row0
[Row "Apple" 15 1.5016 2.0 5.3 1801 "11/13/2018" "X101"
,Row "Apple" 15 1.5016 2.5 3.0 1801 "11/13/2018" "X101"]
>

完整的程序是:

{-# LANGUAGE DeriveFunctor #-}
{-# OPTIONS_GHC -Wall #-}

import Control.Monad
import Control.Applicative

type Stream = [String]

newtype PParser a = PParser (Stream -> [(a, Stream)]) deriving (Functor)
instance Applicative PParser where
  pure x = PParser (\s -> [(x, s)])
  (<*>) = ap
instance Monad PParser where
  PParser p >>= f = PParser $ \s1 -> do  -- in list monad
    (x, s2) <- p s1
    let PParser q = f x
    (y, s3) <- q s2
    return (y, s3)
instance Alternative PParser where
  empty = PParser (const empty)
  PParser p <|> PParser q = PParser $ \s -> p s <|> q s
instance MonadPlus PParser where

parse :: PParser a -> Stream -> [a]
parse (PParser p) s = map fst (p s)

-- read a token as-is
token :: PParser String
token = PParser $ \s -> case s of
  (x:xs) -> pure (x, xs)
  _ -> empty

-- require an end of stream
eof :: PParser ()
eof = PParser $ \s -> case s of
  [] -> pure ((), s)
  _ -> empty

-- combinator to convert a String to any readable type
convert :: (Read a) => PParser String -> PParser a
convert (PParser p) = PParser $ \s1 -> do
  (x, s2) <- p s1     -- for each possible String
  (y, "") <- reads x  -- get each possible full read
                      -- (normally only one)
  return (y, s2)

-- read a string from a single cell
str :: PParser String
str = token

-- read an integer (any size) from a single cell
int :: PParser Int
int = convert (mfilter ok1 token)

-- read a double from one or two cells
dbl :: PParser Double
dbl = dbl1 <|> dbl2
  where dbl1 = convert (mfilter ok1 token)
        dbl2 = convert $ do
          t1 <- mfilter ok1 token
          t2 <- mfilter ok2 token
          return $ t1 ++ "." ++ t2

-- read a double that's < 30
dbl30 :: PParser Double
dbl30 = do
  x <- dbl
  guard $ x < 30
  return x

-- rules for first cell of numbers:
-- "0" is okay, but otherwise can't start with "0"
ok1 :: String -> Bool
ok1 "0" = True
ok1 (c:_) | c /= '0' = True
ok1 _ = False

-- rules for second cell of numbers:
-- can't be "0" or end in "0"
ok2 :: String -> Bool
ok2 xs = last xs /= '0'

-- a row
data Row = Row String Int Double Double Double
               Int String String deriving (Show)
rowResults :: PParser Row
rowResults = Row <$> str <*> int <*> dbl30 <*> dbl30 <*> dbl30
                 <*> int <*> str <*> str <* eof

row0 :: Stream
row0 = ["Apple", "15", "1", "5016", "2", "5", "3", "1801", "11/13/2018", "X101"]

main = print $ parse rowResults row0

现成的解决方案

我有点惊讶我找不到提供这种 "all possible parses" 解析器的现有解析器库。 Text.ParserCombinators.ReadP 中的内容采用了正确的方法,但它假设您正在解析来自 String 的字符,而不是来自其他流的任意标记(在我们的例子中,Strings 来自[String]).

也许其他人可以指出一个现成的解决方案,使您不必使用自己的解析器类型、实例和原语。