3

假设我有不同的解析器。我想定义一个函数where .p1, ..., pkpk :: Parser ([t1], ..., [tk])pi :: Parser ti

这将解析与 p 1 ...p k中的任何一个匹配的字符串集合(一个接一个),并将它们分隔在相应的列表中。为简单起见,假设没有一个字符串与两个解析器匹配。

我设法做到了,但我真的很难找到一种优雅的方式(最好使用许多或任何其他内置解析器解析器)。

4

3 回答 3

6

第一步是将每个解析器变成大类型的解析器:

p1 :: Parser t1
p2 :: Parser t2
p3 :: Parser t3
p1 = undefined
p2 = undefined
p3 = undefined

p1', p2', p3' :: Parser ([t1], [t2], [t3])
p1' = fmap (\x -> ([x], [], [])) p1
p2' = fmap (\x -> ([], [x], [])) p2
p3' = fmap (\x -> ([], [], [x])) p3

现在,我们反复从这些最后的解析器中进行选择,并在最后连接结果:

parser :: Parser ([t1], [t2], [t3])
parser = fmap mconcat . many . choice $ [p1', p2', p3']

Monoid大小为 5 的元组的实例;除此之外,您可以使用嵌套元组或更合适的数据结构。

于 2012-01-06T14:38:45.147 回答
5

将解析器表示为列表使这很容易。使用:

choice :: [Parser a] -> Parser a
many :: Parser a -> Parser [a]

我们可以写:

combineParsers :: [Parser a] -> Parser [a]
combineParsers = many . choice

这不太正确,因为它将它们全部捆绑到一个列表中。让我们将每个解析器与一个唯一标识符相关联:

combineParsers' :: [(k, Parser a)] -> Parser [(k, a)]
combineParsers' = many . choice . combine
  where combine = map (\(k,p) -> (,) k <$> p)

然后我们可以将其转换回列表形式:

combineParsers :: [Parser a] -> Parser [[a]]
combineParsers ps = map snd . sortBy fst <$> combineParsers' (zip [0..] ps)

您也许可以通过combineParsers' :: [(k, Parser a)] -> Parser (Map k [a])使用例如编写来提高效率combine = map $ \(k,p) -> fmap (\a -> Map.insertWith (++) k [a]) p

这要求所有解析器都具有相同的结果类型,因此您需要将它们的每个结果包装在一个数据类型中Cons <$> p,每个p都有。然后,您可以从每个列表中解开构造函数。诚然,这是相当丑陋的,但要对元组进行异构处理,甚至需要更丑陋的类型类 hack。

对于您的特定用例,可能有一个更简单的解决方案,但这是我能想到的最简单的通用方法。

于 2012-01-06T13:33:04.273 回答
5

不幸的是,如果没有类型级别的技巧,你就不能完全通用地做到这一点。但是,可以使用多态组合器和一些预先存在的递归实例,以 DIY 风格构建符合 Daniel Wagner 建议的方法。我将举一个简单的例子来演示。

首先,使用一些简单的解析器进行测试:

type Parser = Parsec String ()

parseNumber :: Parser Int
parseNumber = read <$> many1 digit

parseBool :: Parser Bool
parseBool = string "yes" *> return True
        <|> string "no" *> return False

parseName :: Parser String
parseName = many1 letter

接下来我们创建一个“基本案例”类型来标记可能性的结束,并给它一个在没有输入时总是成功的解析器。

data Nil = Nil deriving (Eq, Ord, Read, Show, Enum, Bounded)
instance Monoid Nil where 
    mempty = Nil
    mappend _ _ = Nil

parseNil :: Parser Nil
parseNil = return Nil

当然,这相当于()—— 我只是创建一个新类型来消除歧义,以防我们真正想要解析(). 接下来,将解析器链接在一起的组合器:

infixr 3 ?|>
(?|>) :: (Monoid b) => Parser a -> Parser b -> Parser ([a], b)
p1 ?|> p2 = ((,) <$> ((:[]) <$> p1) <*> pure mempty)
        <|> ((,) <$> pure [] <*> p2)

这里发生的事情是p1 ?|> p2尝试p1,如果成功,则将其包装在一个单例列表中并填充mempty元组的第二个元素。如果p1失败,则 if 填充一个空列表并p2用于第二个元素。

parseOne :: Parser ([Int], ([Bool], ([String], Nil)))
parseOne = parseNumber ?|> parseBool ?|> parseName ?|> parseNil

将解析器与新的组合器结合起来很简单,而且结果类型非常不言自明。

parseMulti :: Parser ([Int], ([Bool], ([String], Nil)))
parseMulti = mconcat <$> many1 (parseOne <* newline)

我们依赖Monoid元组的递归实例和列表的常用实例来组合多行。现在,为了证明它有效,定义一个快速测试:

runTest = parseTest parseMulti testInput

testInput = unlines [ "yes", "123", "acdf", "8", "no", "qq" ]

成功解析为Right ([123,8],([True,False],(["acdf","qq"],Nil))).

于 2012-01-06T15:14:23.557 回答