使用parsec

时间:2018-04-10 16:46:18

标签: parsing haskell parsec

我试图弄清楚如何以最好的方式解析Haskell中的sum-datatype。这是我尝试的摘录

type Value = Int

data Operator = ADD | SUB | MUL | DIV | SQR deriving (Show)

toOperator :: String -> Maybe Operator
toOperator "ADD" = Just ADD
toOperator "SUB" = Just SUB
toOperator "MUL" = Just MUL
toOperator "DIV" = Just DIV
toOperator "SQR" = Just SQR
toOperator _     = Nothing

parseOperator :: ParsecT String u Identity () Operator
parseOperator = do
    s <- choice $ map (try . string) ["ADD", "SUB", "MUL", "DIV", "SQR"]
    case toOperator s of
        Just x  -> return x
        Nothing -> fail "Could not parse that operator."

此代码执行我想要的但有一个明显的问题:它检查数据两次。进入choice $ map (try . string) ["ADD", "SUB", "MUL", "DIV", "SQR"]行,进入toOperator一次。

我想要的是,如果字符串出现在列表中,则希望将字符串解析为Operator,否则将失败。但我无法弄清楚如何以“干净”的方式做到这一点。

3 个答案:

答案 0 :(得分:3)

如果让toOperator直接参与Parsec解析过程,而不是让它成为单独发生的步骤,那就更简单了,因为“这个是否是一个有效的运算符”可以为解析过程提供反馈

对于这种特殊情况,你正在解析的东西是零字段枚举,其构造函数名称与你正在解析的字符串完全匹配,已经发布了几个好的快捷方式,向你展示如何简洁地解析这些构造函数。在这个答案中,我将展示一个替代方法,它更容易适应“匹配几个案例之一”的一般情况,并处理更高级的东西,如“三个构造函数中的一个有一个Int参数但其他人没有。“

operator :: StringParser Operator
operator = string "ADD" *> pure ADD
       <|> string "DIV" *> pure DIV 
       <|> string "MUL" *> pure MUL
       <|> try (string "SUB") *> pure SUB 
       <|> string "SQR" *> pure SQR

现在假设您有一个额外的构造函数VAR,它接受​​一个String参数。可以很容易地将该构造函数的支持添加到此解析器中:

operator :: StringParser Operator
operator = ...
       <|> string "VAR" *> (VAR <$> var)

var :: StringParser String
var = spaces *> anyChar `manyTill` space

答案 1 :(得分:2)

您有几种方法可以避免此类重复。

首先,如果您尝试解析的输入中显示的名称与Operator的构造函数完全匹配(在您的示例中似乎就是这种情况),则可以完全避免使用toOperator通过导出Read的{​​{1}}实例,并使用Operator。然后代码将沿着

read

您必须小心在此处列出与parseOperator :: ParsecT String u Identity () Operator parseOperator = do s <- choice $ map (try . string) ["ADD", "SUB", "MUL", "DIV", "SQR"] pure $ read s 构造函数相同的名称,并根据需要更新它们。

其次,您可以通过定义列表(或OperatorData.Map)来自己构建映射,然后使用它来指定可接受的输入并找到相应的运算符构造函数:

HashMap

注意operators :: [(String, Operator)] operators = [("ADD", ADD), ("SUB", SUB), ("MUL", MUL), ("DIV", DIV), ("SQR", SQR)] parseOperator :: ParsecT String u Identity () Operator parseOperator = do s <- choice $ map (try . string . fst) operators case lookup s operators of Just x -> return x Nothing -> fail "Could not parse that operator." 对于定义良好的解析器并不是必需的:根据定义,解析的结果将在case列表中。而且,缺点是你必须保持operators和构造函数列表同步。

第三个,也许是最甜蜜的一个是通过一些额外的类型类自动生成运算符列表:operatorsBounded,它们组合在一起,允许枚举所有构造函数类似于你的类型,以及哪些ghc会愉快地为你的Enum派生。然后Operator定义看起来像

operators

答案 2 :(得分:1)

您只需要toOperator的倒数来映射解析器; read是一个简单的(如果不是强大的)示例。

>>> data Operator = ADD | SUB | MUL | DIV | SQR deriving (Show, Read)
>>> parse (read <$> string "ADD") "" "ADD" :: Either ParseError Operator
Right ADD