簡體   English   中英

兩個AST之間的簡單編譯器,公開相同的語言類型和操作

[英]A simple compiler between two ASTs exposing identical language types and operations

我正在尋找一種解決以下問題的優雅方法。 歡迎所有選項,尤其是類型類和GADT :-)

場景是這樣的:存在一種具有類型(字符串和整數)和操作(+,-,++和split)的小語言。 該語言有兩種語法,每種都有自己的解析器。 我想編寫一個編譯器,它可以從語言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 sStringY s被編譯回到StringX s 當然有表達這個的好方法嗎? 此外,案例匹配右側的嵌套xToYyToX調用看起來ArithOpX (EAddX (yToX a) (yToX b)) ,例如ArithOpX (EAddX (yToX a) (yToX b)) 一定會有更好的辦法?

嘗試使用以下單一類型Exp t替換ExpXExpY t是被某種類型的標記替換的標記,用於特定目的:

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代替ExpX ,用Exp ForY代替ExpY

然后,您可以編寫工作職能forall標簽。 例如,我們可以更換xToYyToX與單一功能的retag

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是“幻影類型”的示例。 “幻像類型”是一種永遠不會出現在任何構造函數中的類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM