两个AST之间的简单编译器,暴露相同的语言类型和操作

时间:2014-04-16 22:43:34

标签: haskell

我正在寻找一种优雅的方法来解决下面的问题。欢迎所有选项,特别是类型类和GADT: - )

场景是这样的:存在一种带有类型(字符串和句点)和操作(+, - ,++和拆分)的小语言。语言有两种语法,每种语法都有自己的解析器。我想编写一个可以从语​​言X到语言Y,或从Y到X的编译器。从一个编译到另一个是一个直接映射到表达式列表中的一个:

xToY :: ExpX -> ExpY
yToX :: ExpY -> ExpX

..然后是show [ExpY][ExpX]。下面是这两个编译器函数的简单实现,使用构造函数上的常规数据定义和模式匹配:

{-# LANGUAGE LambdaCase #-}

module Compiler where

data ExpX = StringX String | IntX Int | ArithOpX ArithExpX | StringOpX StringExpX deriving (Show)
data ArithExpX = EAddX ExpX ExpX | EMinusX ExpX ExpX deriving (Show)
data StringExpX = EAppendX ExpX ExpX | ESplitX ExpX ExpX deriving (Show)

data ExpY = StringY String | IntY Int | ArithOpY ArithExpY | StringOpY StringExpY deriving (Show)
data ArithExpY = EAddY ExpY ExpY | EMinusY ExpY ExpY deriving (Show)
data StringExpY = EAppendY ExpY ExpY | ESplitY ExpY ExpY deriving (Show)

xToY :: ExpX -> ExpY
xToY =
    \case
    StringX s -> StringY s
    IntX i -> IntY i
    ArithOpX (EAddX a b) -> ArithOpY (EAddY (xToY a) (xToY b))
    ArithOpX (EMinusX a b) -> ArithOpY (EMinusY (xToY a) (xToY b))
    StringOpX (EAppendX a b) -> StringOpY (EAppendY (xToY a) (xToY b))
    StringOpX (ESplitX a b) -> StringOpY (ESplitY (xToY a) (xToY b))

yToX :: ExpY -> ExpX
yToX =
    \case
    StringY s -> StringX s
    IntY i -> IntX i
    ArithOpY (EAddY a b) -> ArithOpX (EAddX (yToX a) (yToX b))
    ArithOpY (EMinusY a b) -> ArithOpX (EMinusX (yToX a) (yToX b))
    StringOpY (EAppendY a b) -> StringOpX (EAppendX (yToX a) (yToX b))
    StringOpY (ESplitY a b) -> StringOpX (ESplitX (yToX a) (yToX b))

测试noddy编译器:

*Compiler> xToY (ArithOpX (EAddX (IntX 2) (IntX 5)))
ArithOpY (EAddY (IntY 2) (IntY 5))
*Compiler> yToX (StringOpY (ESplitY (StringY "foo") (StringY "bar")))
StringOpX (ESplitX (StringX "foo") (StringX "bar"))

所以它有效。不幸的是,代码重复很多,并且模式显然正在出现。我想采用Haskell更优雅的功能来实现xToYyToX给出的相同结果。特别是,我正在寻找一种在构造函数之间定义二元性的方法,例如StringX s被编译为StringY s,而StringY s被编译回StringX s。当然,有一种很好的表达方式吗?此外,案例匹配右侧的嵌套xToYyToX调用看起来很脏,例如ArithOpX (EAddX (yToX a) (yToX b))。必须有更好的方法吗?

1 个答案:

答案 0 :(得分:3)

尝试使用以下单一类型ExpX替换ExpYExp tt是一个由某种类型替换的标记,用于将其标记为特定用途:

data Exp t = String String | Int Int | ArithOp (ArithExp t) | StringOp (StringExp t) deriving (Show)
data ArithExp t = EAdd (Exp t) (Exp t) | EMinus (Exp t) (Exp t) deriving (Show)
data StringExp t = EAppend (Exp t) (Exp t) | ESplit (Exp t) (Exp t) deriving (Show)

data ForX = ForX
data ForY = ForY

然后使用Exp ForX代替ExpXExp ForY代替ExpY,无论您何时关心差异。

然后,您可以编写适用于forall个标记的函数。例如,我们可以使用单个函数xToY替换yToXretag

retag:: Exp t1 -> Exp t2
retag =
    \case
    String s -> String s
    Int i -> Int i
    ArithOp (EAdd a b) -> ArithOp (EAdd (retag a) (retag b))
    ArithOp (EMinus a b) -> ArithOp (EMinus (retag a) (retag b))
    StringOp (EAppend a b) -> StringOp (EAppend (retag a) (retag b))
    StringOp (ESplit a b) -> StringOp (ESplit (retag a) (retag b))

此类型t是"幻像类型"的示例。 A"幻象类型"是一种永远不会出现在任何构造函数中的类型。