具有多种返回类型的解析器

时间:2019-12-23 17:58:11

标签: parsing haskell

我对可以管理多种类型的解析器有问题,例如用于算术表达式的解析器。目前,我的解析器仅接受输入整数:

aexpr :: Parser Int
aexpr = aterm `chainl` addOp

aterm :: Parser Int
aterm = afactor `chainl` mulOp

afactor :: Parser Int
afactor = parenP '(' aexpr ')'
          <|>
          do
            s <- sign 
            a <- aexpr 
            return (s * a)
          <|>  
          token int_const

这对于两个整数非常有效,但是我正在介绍浮点类型,因此算术表达式的解析器可以在输入中包含两个Int,两个Float或两个{{1}的混合类型}和Int。解析器的结果类型应基于输入为FloatInt(即,如果两个输入均为Float,则为Int,否则为Int)。 / p>

我的问题是:管理此问题的最佳方法是什么?一种简单的方法是将所有输入都管理为Float,以便将整数值转换为浮点值。但我不喜欢这种解决方案:最终结果将始终为Float

我应该创建两个不同的解析器,即一个用于Float,一个用于Int吗?但是两个解析器几乎相同。

有更好的解决方案吗?

1 个答案:

答案 0 :(得分:4)

在设计DSL时,很常见的是定义一个新的数据类型来表示所有不同形状的值可以具有:

data Value
    = I Int
    | D Double
    deriving (Eq, Ord, Read, Show)

您可能要实现一些辅助功能,例如:

binOp :: (forall a. Num a => a -> a -> a) -> Value -> Value -> Parser Value
binOp f (I a) (I b) = pure (I (f a b))
binOp f (D a) (D b) = pure (D (f a b))
binOp _ _ _ = fail "oof, tried to mix Ints and Doubles"

例如,您的mulOpaddOp实现可以在其实现中的某个位置调用binOp (*)binOp (+)

但是我会考虑另一种方法,而不是直接使用此类帮助器。我会建议引入一个中间表示形式,对于该中间表示形式,“总是成功”进行解析,然后添加一个单独的类型检查阶段,在该阶段中,您可能会抛出关于混合int和double的错误……或者进行适当的强制转换,或者您的DSL希望在那里发生任何事情。所以:

data BinOp = Plus | Times deriving (Bounded, Enum, Eq, Ord, Read, Show)
data Type = Int | Double deriving (Bounded, Enum, Eq, Ord, Read, Show)
data DSL
    = ILit Int
    | DLit Double
    | BinOp BinOp DSL DSL
    deriving (Eq, Ord, Read, Show)

然后您可以用类似类型的东西写东西

typeCheck :: DSL -> These [TypeError] Type -- check for mixing
unsafeEval :: DSL -> Value -- implementation uses incomplete patterns and assumes no mixing

eval :: DSL -> Either [TypeError] Value
eval t = case typeCheck t of
    That _ -> Right (unsafeEval t)
    These [] _ -> Right (unsafeEval t)
    These es _ -> Left es
    This es -> Left es

或其他。