首页 > 解决方案 > 如何将 Megaparsec 与 Text.Read (派生的 Read 实例)结合使用

问题描述

我想在 megaparsec 模块中使用 Read 的派生实例。如何在 'Parser a' 中使用 'Text.Read.read' 或 'Text.Read.readEither' ?

它不需要快速,但易于维护和扩展。megaparsec 模块用于通过 CLI 测试我的应用程序,因此必须解析许多不同的数据类型。

它应按以下方式工作:

import Text.Megaparsec

readableDatatype :: Read a => Parser a
readableDatatype = 
  -- This is wrong, but describes how it shall work
  -- liftA read chunkToTokens

expr' :: Parser UserControlExpr
expr' = timeExpr
  <|> timeEventExpr
  <|> digiInExpr
  <|> quitExpr

digiInExpr :: Parser UserControlExpr
digiInExpr = do
  cmdword "digiIn"
  inElement <- (readableDatatype  :: Parser TI_I)
  return $ UserDigiIn inElement

我必须写什么,以便三个函数类型检查,尤其是readableDataype

标签: haskell

解决方案


您可以为此使用getInput :: MonadParsec e s m => m sand 。并且只需获取和设置解析器正在处理的输入流并获取一个字符串并返回一个可能的解析列表以及输入的剩余未使用部分。我们还需要告诉解析器输入中的新偏移量,否则错误位置是错误的。我们可以使用and来做到这一点。setInput :: MonadParsec e s m => s -> m ()reads :: Read a => String -> [(a, String)]getInputsetInputreadsgetOffsetsetOffset

-- For equality constraint (~)
{-# LANGUAGE TypeFamilies #-}

import Text.Megaparsec
import Text.Read       (reads)

readableDatatype :: (Read a, MonadParsec e s m, s ~ String) => m a
readableDatatype = do
  input  <- getInput
  offset <- getOffset
  choice $ 
    (\(a, input') -> a <$ setInput input'
                       <* setOffset (offset + length input - length input'))
    <$> reads input

如果您的输入不是其他String内容,则必须在 that 和StringaftergetInput和 before之间进行转换setInput

以下是关于性能问题,因此与您的问题并不真正相关,但它可能具有教育意义,并且可能对可能需要具有良好性能的解决方案的其他人有用。

String在解析过程中一直在和其他类型之间转换整个输入是较大输入的一个相当大的性能瓶颈。此外length,在这里计算新的偏移量也不是很高效。

为了解决这两个问题,需要某种方法能够知道 Read-parser 实际消耗了多少输入,这样我们就可以从原始输入中删除该部分,而不必将整个未消耗的部分转换回来到原始输入类型。但是Read班级没有那个。可以尝试逐步解析输入的更长前缀,这在使用完成的解析Read比整个输入的长度短的情况下可能更快。您还可以使用unsafePerformIO写入IORef读取解析器实际强制执行多少输入,这将是最快但不是那么漂亮的解决方案。

我在这里实现了后者。随意使用它,但请注意它没有经过很好的测试。然而,它确实解决了上述方法的所有问题。


推荐阅读