我对可以管理多种类型的解析器有问题,例如用于算术表达式的解析器。目前,我的解析器仅接受输入整数:
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
。解析器的结果类型应基于输入为Float
或Int
(即,如果两个输入均为Float
,则为Int
,否则为Int
)。 / p>
我的问题是:管理此问题的最佳方法是什么?一种简单的方法是将所有输入都管理为Float
,以便将整数值转换为浮点值。但我不喜欢这种解决方案:最终结果将始终为Float
。
我应该创建两个不同的解析器,即一个用于Float
,一个用于Int
吗?但是两个解析器几乎相同。
有更好的解决方案吗?
答案 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"
例如,您的mulOp
和addOp
实现可以在其实现中的某个位置调用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
或其他。