【发布时间】:2020-09-11 12:29:06
【问题描述】:
我有一个 DSL,以及一个用 Haskell 和 Parsec 包编写的解析器。现在我想弃用 DSL 的特定语言功能。在下一个版本中,我希望解析器同时接受新语法和旧语法,但我希望解析器产生弃用消息。我找不到如何做到这一点。这可能吗?如果可以,怎么做?
【问题讨论】:
我有一个 DSL,以及一个用 Haskell 和 Parsec 包编写的解析器。现在我想弃用 DSL 的特定语言功能。在下一个版本中,我希望解析器同时接受新语法和旧语法,但我希望解析器产生弃用消息。我找不到如何做到这一点。这可能吗?如果可以,怎么做?
【问题讨论】:
与其在解析期间发出消息,不如在解析结束时返回额外信息:是否遇到过时的语法。
ParsecT 类型接受用户在解析期间设置的状态类型参数:
ParsecT s um a 是一个解析器,流类型为 s,用户状态类型为 u,底层 monad m,返回类型为 a。 Parsec 在用户态是严格的。
可以使用putState 和modifyState 设置用户状态。可以使用getState获取。
大多数 parsec 组合子在用户状态上是多态的。您自己的 DSL 的大多数组合器也应该是。但是语法中不推荐使用的部分的解析器应该在你的用户状态中设置一个“标志”。
类似这样的:
import Text.Parsec
import Text.Parsec.Char
import Data.Functor.Identity
type Parser = ParsecT [Char] Bool Identity -- using a Bool state
myParser :: Parser Char
myParser =
try (do char 'a'
putState True
char 'b')
<|>
try (do char 'a'
char 'c')
main :: IO ()
main = do
print $ runParser ((,) <$> myParser <*> getState) False "" "ab"
print $ runParser ((,) <$> myParser <*> getState) False "" "ac"
-- results:
-- Right ('b',True)
-- Right ('c',False)
当然,不是简单的布尔标志,而是将更多信息放入状态中。
请注意,如果子解析器回溯,则由子解析器设置的状态将被“遗忘”。这是符合我们目的的正确行为:否则,我们会得到由最终丢弃的分支触发的“误报”。
parsec 的常见替代品是megaparsec。后者不允许用户定义状态in the parser type itself,但可以使用StateT 转换器在ParsecT 类型上进行模拟。
【讨论】:
WriterT 变体比完全狂野的StateT 更合适。以后无法取消弃用。