在Haskell中解析Parsec

时间:2014-09-01 08:19:54

标签: haskell parsec

我有2个解析器:

nexpr::Parser (Expr Double)
sexpr::Parser (Expr String)

如果它不起作用,我如何构建一个尝试一个然后另一个的解析器?我无法弄清楚要归还什么。必须有一个聪明的方法来做到这一点。

感谢。

编辑:

添加更多信息...

我正在学习Haskel,所以我开始:

data Expr a where
    N::Double -> Expr Double
    S::String -> Expr String
    Add::Expr Double -> Expr Double -> Expr Double
    Cat::Expr String -> Expr String -> Expr String

然后我读到了关于F代数(here)的内容,因此我将其更改为:

data ExprF :: (* -> *) -> * -> * where
    N::Double -> ExprF r Double
    S::String -> ExprF r String
    Add::r Double -> r Double -> ExprF r Double
    Cat::r String -> r String -> ExprF r String

type Expr = HFix ExprF

所以我的解析:

Parser (Expr Double)

实际上是:

Parser (ExprF HFix Double)

也许我咬得比我能咬的还要多......

2 个答案:

答案 0 :(得分:6)

如评论中所述,您可以拥有这样的解析器

nOrSexpr :: Parser (Either (Expr Double) (Expr String))
nOrSexpr = (Left <$> nexpr) <|> (Right <$> sexpr)

但是,我认为您遇到这种困难的原因是因为您没有将您的解析树表示为单一类型,这是更常见的事情。像这样:

data Expr = 
      ExprDouble Double 
    | ExprInt Int 
    | ExprString String

通过这种方式,您可以为每种类型的Parser Expr类型的表达式提供解析器。这与使用Either相同,但更灵活,更易于维护。所以你可能有

doubleParser :: Parser Expr
doubleParser = ...

intParser :: Parser Expr
intParser = ...

stringParser :: Parser Expr
stringParser = ...

exprParser :: Parser Expr
exprParser = intParser <|> doubleParser <|> stringParser

请注意,解析器的顺序很重要,如果需要回溯,则可以使用Parsec的try函数。

因此,例如,如果您想现在有一个求和表达式,可以添加到数据类型

data Expr = 
      ExprDouble Double 
    | ExprInt Int 
    | ExprString String
    | ExprSum Expr Expr

并制作解析器

sumParser :: Parser Expr
sumParser = do
    a <- exprParser
    string " + "
    b <- exprParser
    return $ ExprSum a b

更新

好吧,如果你刚刚开始使用Haskell,我会把你的帽子直接带到GADTs。我一直在阅读您链接的文章,并在第一段中立即注意到这一点:

  

评审团仍然不清楚GADT提供的额外类型安全是否值得与他们合作带来的额外不便。

我认为有三点值得一试。第一个是简单地说,我会先尝试更简单的做事方式,了解它是如何工作的,以及为什么你可能想要增加更多的类型安全性,然后再尝试更复杂的理论类型。这个评论可能没有用,所以可以随意忽略它!

其次,更重要的是,你的代表......

data ExprF :: (* -> *) -> * -> * where
    N :: Double -> ExprF r Double
    S :: String -> ExprF r String
    Add :: r Double -> r Double -> ExprF r Double
    Cat :: r String -> r String -> ExprF r String

...专门设计为不允许生成错误的表达式。与我的形成鲜明对比,例如ExprSum (ExprDouble 5.0) (ExprString "test")。所以你真正想问的问题是当解析器试图解析像"5.0 + \"test\""这样的东西时应该发生什么?你想要它只是不解析,或者你是否希望它返回一个很好的消息,说这个表达式是错误的类型?出于这个原因,编译器通常设计为多个阶段。第一遍将输入转换为抽象语法树(AST),并进一步传递带有类型判断的树。然后可以将这个带注释的AST转换为您真正想要的语义表示。

所以在你的情况下,我会推荐两个阶段。首先,解析为像我一样的愚蠢表达,这将给你正确的树形状,但允许不良类型的表达。像

data ExprAST = 
      ExprASTDouble Double 
    | ExprASTInt Int 
    | ExprASTString String
    | ExprASTAdd Expr Expr

然后有另一个功能来检查ExprAST。像

这样的东西
 typecheck :: ExprAST -> Maybe (ExprF HFix a)

(您也可以使用Either并返回类型检查的GADT或错误字符串,说明问题所在。)这里的另一个问题是您不知道a是什么静态。另一个答案通过使用类型标记和存在包装来解决这个问题,您可能会发现它是最好的方法。我觉得在你的GADT中有一个顶级表达式可能更简单,所有表达式都必须存在,因此整个解析将始终具有相同的类型。最后通常只有一种程序类型。

我的第三点也是最后一点与此相关

  

评审团仍然不清楚GADT提供的额外类型安全是否值得与他们合作带来的额外不便。

类型安全性越高,通常你需要做的工作就越多。你提到你是Haskell的新手,但这次冒险已经把我们带到了它能够做到的边缘。解析表达式的类型不能仅依赖于Haskell函数中的输入字符串,因为它不允许依赖类型。如果你想沿着这条路走下去,我可能会建议你看一下名为Idris的语言。可以找到in this video的一个很好的介绍,在其中构建一个类型安全的printf。

答案 1 :(得分:2)

所描述的问题似乎是使用Parsec解析为GADT表示,为此最简单的解决方案可能会解析为单型表示,然后有一个(可能是部分的)类型检查阶段来生成良好类型的GADT,如果可以的话。单型表示可以是GADT术语的存在性包装,带有用于重新统一GADT索引的类型标记。

编辑:一个简单的例子

让我们为type-tags和一个存在包装器定义一个类型:

data Type :: * -> * where
  TDouble :: Type Double
  TString :: Type String

data Judgement f = forall ix. Judgement (f ix) (Type ix)

使用原始帖子中给出的示例GADT,我们只对最外层的产品有问题,我们需要解析为单型,因为我们不能静态地知道我们将在运行时获得哪种表达式类型:

pExpr :: Parser (Judgement Expr)
pExpr =  Judgement <$> pDblExpr <*> pure TDouble
     <|> Judgement <$> pStrExpr <*> pure TString

我们可以编写一个类型检查阶段来生成GADT或失败,具体取决于类型断言是否成功:

typecheck :: Judgement Expr -> Type ix  -> Maybe (Expr ix)
typecheck (Judgement e TDouble) TDouble = Just e
typecheck (Judgement e TString) TString = Just e
typecheck _ _ = Nothing