假设我有不同的解析器p1, ..., pk
。我想定义一个函数pk :: Parser ([t1], ..., [tk])
,其中pi :: Parser ti
。
这将解析与p 1 ... p k 中的任何一个匹配的字符串集合(一个接一个),并将它们分隔在相应的列表中。为简单起见,假设所有字符串都不匹配两个解析器。
我设法做到了,但我真的很难找到一种优雅的方式(最好使用许多或任何其他内置的parsec解析器)。
答案 0 :(得分: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
。
这要求所有解析器都具有相同的结果类型,因此您需要为每个 p 将每个结果包装在Cons <$> p
的数据类型中。然后,您可以从每个列表中解包构造函数。不可否认,这是相当丑陋的,但要用元组进行异构处理甚至需要更加丑陋的类型级别的黑客攻击。
对于您的特定用例,可能有一个更简单的解决方案,但这是我能想到的最简单的方法。
答案 1 :(得分:5)
第一步是将每个解析器转换为大类型的解析器:
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']
对于大小为5的元组,有Monoid
个实例;除此之外,您可以使用嵌套元组或更合适的数据结构。
答案 2 :(得分:5)
遗憾的是,如果没有类型级别的欺骗,你无法完全做到这一点。然而,根据Daniel Wagner的建议,可以使用多态组合器和一些预先存在的递归实例以DIY风格构建一种方法。我将提供一个简单的例子进行演示。
首先,一些简单的解析器用于测试:
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
失败,则填写空列表并使用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)))
。