如何将Megaparsec与Text.Read(派生的Read实例)组合

时间:2019-01-27 02:49:02

标签: haskell

我想在megaparsec模块中使用Read的派生实例。 如何在“解析器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

2 个答案:

答案 0 :(得分:0)

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

-- 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,则必须在String之后和getInput之前在该输入和setInput之间进行转换。

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

在解析过程中,始终在String和其他类型之间转换整个输入,这对于较大的输入来说是一个很大的性能瓶颈。此外,在这里使用length来计算新的偏移量也不是很好。

要解决这两个问题,需要某种方法才能知道读取解析器实际消耗了多少输入,以便我们可以从原始输入中删除该部分,而不必转换整个输入未使用的零件恢复为原始输入类型。但是Read类没有。可以尝试解析输入的更长前缀,这在使用Read进行的解析比整个输入的长度短的情况下可能会更快。您还可以使用unsafePerformIO来向IORef写入读取解析器实际上强制了多少输入,这将是最快但不是那么好的解决方案。

我实现了后者here。随时使用它,但要注意,它不是经过很好的测试。但是,它确实解决了上述方法的所有问题。

答案 1 :(得分:0)

做到了。谢谢!同时,我通过将构造函数定义为字符串并解析它们(不使用read)来“保守”地解决了该问题。这样做的好处是,您得到了megaparsec令人印象深刻的错误消息,告诉您缺少哪些符号。

read的示例:

1:8:
  |
1 | digiIn TI_I_Signal1 DirA Dectivated
  |        ^
unknown parse error

(“停用”中仅缺少“ a”)

带有手写解析器的示例,用于数据类型:

1:19:
  |
1 | digiIn TI_I_Signal1 Dectivated
  |                     ^^^^^^^^
unexpected "Dectivat"
expecting "active", "inactive", '0', or '1'

我想我会在将来的数据类型中使用您的代码块。 非常感谢你!