Parsing Parsec解析和分离不同的结构

Parsing Parsec解析和分离不同的结构,parsing,haskell,parsec,Parsing,Haskell,Parsec,假设我有不同的解析器p1,…,pk。我想定义一个函数pk::Parser([t1],…,[tk])wherepi::Parser ti 这将解析与p1…pk中的任何一个匹配的字符串集合(一个接一个),并在相应的列表中分隔它们。为简单起见,假设所有字符串都不匹配两个解析器 我成功地做到了这一点,但我真的很难找到一种优雅的方法(最好使用许多或任何其他内置的parsec解析器)。将解析器表示为一个列表使这变得容易。使用: choice :: [Parser a] -> Parser a many

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

这将解析与p1…pk中的任何一个匹配的字符串集合(一个接一个),并在相应的列表中分隔它们。为简单起见,假设所有字符串都不匹配两个解析器


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

将解析器表示为一个列表使这变得容易。使用:

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':[(k,解析器a)]->解析器[(k,a)]
组合器“=多个。选择结合
其中combine=map(\(k,p)->(,)kp)
然后,我们可以将其转换回列表形式:

combineParsers :: [Parser a] -> Parser [[a]]
combineParsers ps = map snd . sortBy fst <$> combineParsers' (zip [0..] ps)
combineParsers::[Parser a]->Parser[[a]]
组合器ps=映射snd。按fst组合器排序(邮政编码[0..]ps)
您可以通过编写
combineParsers'::[(k,Parser a)]->解析器(Map k[a])
来提高效率,例如使用
combine=Map$\(k,p)->fmap(\a->Map.insertWith(++)k[a])p

这要求所有解析器都具有相同的结果类型,因此您需要将它们的每个结果封装在一个数据类型中,每个p都使用
Cons p
。然后可以从每个列表中展开构造函数。诚然,这是相当丑陋的,但要用元组进行异构操作,需要更丑陋的类型类攻击


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

第一步是将每个解析器转换为大型解析器:

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
实例;除此之外,您还可以使用嵌套元组或更合适的数据结构。

不幸的是,如果没有类型级别的技巧,您无法完全通用地实现这一点。然而,可以按照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
当然,这相当于
()
,我只是创建了一个新类型来消除歧义,以防我们真的想要解析
()
。接下来,将解析器链接在一起的组合器:

infixr 3 ?|>
(?|>) :: (Monoid b) => Parser a -> Parser b -> Parser ([a], b)
p1 ?|> p2 = ((,) <$> ((:[]) <$> p1) <*> pure mempty)
        <|> ((,) <$> pure [] <*> p2)
将解析器与新的组合器结合起来很简单,结果类型也很简单

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

成功解析为
右([123,8],([True,False],([“acdf”,“qq”],Nil))

@Rotsor:Oops,修复了!谢谢。我还在考虑一个优雅的解决方案,避免用新型包装。事实上,我当前的解决方案与您建议的类似(只是它是硬编码的或实例化为我的类型..您将其抽象化了)。@aelguindy:那么,k是什么?如果超过,比方说,3,你可能在做一些不符合传统的事情。关于你处境的具体细节会有所帮助。下面是一个示例:解析源代码中不同类型的定义。使用一个
Defn
类型来表示所有定义是有意义的,并且在这些定义的构造函数中封装不同类型的定义。。老实说,我也不是很满意。我现在不想做一些类型重构:-)。无论如何,我仍然对解决方案很好奇,不管它是否有用。我认为这是最接近优雅的解决方案,幸运的是,我的元组有5个元素:-)。谢谢我认为这是非常优雅的,尽管我仍然不理解(?|>)的定义。我觉得这个问题有点太复杂了。。但还是很优雅!为什么不使用
()
而不是
Nil
?它已经有了一个
Monoid
实例。实际上,我想我现在完全理解它了!好主意@主要是消除歧义。当我只将某个值用作哨兵值或占位符值,而不是简单地使用泛型类型时,我想说清楚。事实上,我可能也想替换
(,)
,以明确这实际上并不是在解析任何看起来像元组的东西。
parseOne :: Parser ([Int], ([Bool], ([String], Nil)))
parseOne = parseNumber ?|> parseBool ?|> parseName ?|> parseNil
parseMulti :: Parser ([Int], ([Bool], ([String], Nil)))
parseMulti = mconcat <$> many1 (parseOne <* newline)
runTest = parseTest parseMulti testInput

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