Haskell 演化数据结构

Haskell 演化数据结构,haskell,Haskell,我正在尝试用Haskell为类C语言编写编译器。编译器通过转换AST进行编译。第一步解析输入以创建AST,与符号表结为一体,以允许在定义符号之前定位符号,而无需前向引用 AST包含关于类型和表达式的信息,它们之间可以有连接;e、 g.sizeof(T)是一个依赖于类型T的表达式,T[e]是一个依赖于常量表达式e的数组类型 类型和表达式由Haskell数据类型表示,如下所示: data Type = TypeInt Id | TypePointer Id Type -- tar

我正在尝试用Haskell为类C语言编写编译器。编译器通过转换AST进行编译。第一步解析输入以创建AST,与符号表结为一体,以允许在定义符号之前定位符号,而无需前向引用

AST包含关于类型和表达式的信息,它们之间可以有连接;e、 g.
sizeof(T)
是一个依赖于类型
T
的表达式,
T[e]
是一个依赖于常量表达式
e
的数组类型

类型和表达式由Haskell数据类型表示,如下所示:

data Type = TypeInt Id
          | TypePointer Id Type -- target type
          | TypeArray Id Type Expr -- elt type, elt count
          | TypeStruct Id [(String, Type)] -- [(field name, field type)]
          | TypeOf Id Expr
          | TypeDef Id Type

data Expr = ExprInt Int -- literal int
          | ExprVar Var -- variable
          | ExprSizeof Type
          | ExprUnop Unop Expr
          | ExprBinop Binop Expr Expr
          | ExprField Bool Expr String -- Bool gives true=>s.field, false=>p->field
其中
Unop
包括地址(
&
)和解除引用(
*
)等运算符,
Binop
包括加号(
+
)和时间(
*
)等运算符

请注意,每个类型都分配了唯一的
Id
。这用于构造类型依赖关系图,以检测导致无限类型的循环。一旦我们确定类型图中没有循环,就可以安全地对其应用递归函数,而不必进入无限循环

下一步是确定每种类型的大小,为结构字段指定偏移量,并用指针算法替换
ExprField
s。这样,我们可以确定表达式的类型,并从类型图中删除
ExprSizeof
s、
ExprField
s、
TypeDef
s和
TypeOf
s,因此我们的类型和表达式已经进化,现在看起来更像这样:

data Type' = TypeInt Id
           | TypePointer Id Type'
           | TypeArray Id Type' Int -- constant expression has been eval'd
           | TypeStruct Id [(String, Int, Type')] -- field offset has been determined

data Expr' = ExprInt Type' Int
           | ExprVar Type' Var
           | ExprUnop Type' Unop Expr'
           | ExprBinop Type' Binop Expr' Expr'
请注意,我们已经删除了一些数据构造函数,并稍微更改了一些其他构造函数。特别是,
Type'
不再包含任何
Expr'
,并且每个
Expr'
都已确定其
类型'

因此,最后一个问题是:创建两个几乎相同的数据类型集,还是尝试将它们统一为一个数据类型更好

保留两个单独的数据类型可以明确地表明某些构造函数不能再出现。但是,执行常数折叠以计算常数表达式的函数将具有以下类型:

foldConstants :: Expr -> Either String Expr'
但这意味着我们以后不能用
Expr'
s执行常量折叠(想象一下,某个过程操纵
Expr'
,并希望折叠任何出现的常量表达式)。我们需要另一种实施方式:

foldConstants' :: Expr' -> Either String Expr'
另一方面,保留单个类型可以解决常量折叠问题,但会阻止类型检查器强制执行静态不变量


此外,在第一次过程中,我们在未知字段(如字段偏移量、数组大小和表达式类型)中放入了什么?我们可以用
未定义的
,或
错误“*hole*”
,来填补漏洞,但这感觉就像是一场即将发生的灾难(比如你甚至无法检查的
NULL
指针)。我们可以将未知字段更改为
Maybe
s,并使用
Nothing
堵住漏洞(比如我们可以检查的
NULL
指针),但是,在随后的过程中,必须不断地从
中提取值,这将是一件令人恼火的事情,因为每个值都有不同的优缺点

我个人会使用单一的数据类型“树”,并为需要区分的内容添加单独的数据构造函数。即:

data Type
  = ...
  | TypeArray Id Type Expr
  | TypeResolvedArray Id Type Int
  | ...
这样做的好处是,正如您所说,您可以在同一棵树上多次运行同一阶段,但推理要比这更深:假设您正在实现一个生成更多AST的语法元素(像)包括或C++模板的一种处理,它可以依赖于常数ExpRs,比如你的代码> TypeArray < /Cord>,因此你不能在第一次迭代中对其进行评估。使用统一数据类型方法,您只需在现有树中插入新的AST,不仅可以直接在该树上运行与以前相同的阶段,还可以免费获得缓存,即如果新的AST使用
sizeof(typeof(myarr))引用数组
或其他,您不必再次确定
myarr
的常量大小,因为它的类型已经是上一个解析阶段的
TypeResolvedArray

当您完成所有编译阶段后,您可以使用不同的表示法,现在是解释代码(或其他内容)的时候了;然后您可以确定,不再需要对AST进行任何更改,更精简的表示法可能是一个好主意


顺便说一句,数组大小应该使用而不是。在C中,使用
int
s索引数组是一个常见的错误,而C指针实际上是无符号的。请不要在您的语言中也犯这个错误,除非您真的想支持负大小的数组。

希望有更多经验的人会有一个更精致、经过战斗考验和准备好的答案,但这是我的尝试

您可以用GADT以相对较低的成本吃一部分馅饼:

{-# LANGUAGE GADTs #-}

data P0 -- phase zero
data P1 -- phase one

data Type p where
     TypeInt     :: Id -> Type p
     TypePointer :: Id -> Type p -> Type p             -- target type
     TypeArray   :: Id -> Type p -> Expr p -> Type p   -- elt type, elt count
     TypeStruct  :: Id -> [(String, Type p)] -> Type p -- [(field name, field type)]
     TypeOf      :: Id -> Expr P0 -> Type P0
     TypeDef     :: Id -> Type P0 -> Type P0

data Expr p where
     ExprInt     :: Int -> Expr p                        -- literal int
     ExprVar     :: Var -> Expr p                        -- variable
     ExprSizeof  :: Type P0 -> Expr P0
     ExprUnop    :: Unop -> Expr p -> Expr p
     ExprBinop   :: Binop -> Expr p -> Expr p -> Expr p
     ExprField   :: Bool -> Expr P0 -> String -> Expr P0 -- Bool gives true=>s.field, false=>p->field
我们改变的是:

  • 数据类型现在使用GADT语法。这意味着构造函数是使用其类型签名声明的。
    data Foo=Bar Int Char
    变成
    data Foo,其中Bar::Int->Char->Foo
    (除了语法之外,两者完全等效)

  • 我们在
    type
    Expr
    中都添加了一个类型变量。这是一个所谓的幻影类型变量:没有存储
    p
    类型的实际数据,它仅用于在类型系统中强制执行不变量

  • 我们声明了虚拟类型来表示转换前后的阶段:阶段0和阶段1。(在一个包含多个阶段的更复杂系统中)
    -- a couple of helper types
    -- here I take advantage of the fact that of the things only present in one phase,
    -- they're always present in P1 and not P0, and not vice versa
    data MaybeP p a where
         NothingP :: MaybeP P0 a
         JustP    :: a -> MaybeP P1 a
    
    data EitherP p a b where
         LeftP  :: a -> EitherP P0 a b
         RightP :: b -> EitherP P1 a b
    
    data Type p where
         TypeInt     :: Id -> Type p
         TypePointer :: Id -> Type p -> Type p
         TypeArray   :: Id -> Type p -> EitherP p (Expr p) Int -> Type p
         TypeStruct  :: Id -> [(String, MaybeP p Int, Type p)] -> Type p
         TypeOf      :: Id -> Expr P0 -> Type P0
         TypeDef     :: Id -> Type P0 -> Type P0
    
    -- for brevity
    type MaybeType p = MaybeP p (Type p)
    
    data Expr p where
         ExprInt     :: MaybeType p -> Int -> Expr p
         ExprVar     :: MaybeType p -> Var -> Expr p
         ExprSizeof  :: Type P0 -> Expr P0
         ExprUnop    :: MaybeType p -> Unop -> Expr p -> Expr p
         ExprBinop   :: MaybeType p -> Binop -> Expr p -> Expr p -> Expr p
         ExprField   :: Bool -> Expr P0 -> String -> Expr P0
    
    data Proxy a = Proxy
    
    class Phase p where
        type MaybeP  p a
        type EitherP p a b
        maybeP  :: Proxy p -> MaybeP p a -> Maybe a
        eitherP :: Proxy p -> EitherP p a b -> Either a b
        phase   :: Proxy p
        phase = Proxy
    
    instance Phase P0 where
        type MaybeP  P0 a   = ()
        type EitherP P0 a b = a
        maybeP  _ _ = Nothing
        eitherP _ a = Left a
    
    instance Phase P1 where
        type MaybeP  P1 a   = a
        type EitherP P1 a b = b
        maybeP  _ a = Just  a
        eitherP _ a = Right a
    
    asdf :: MaybeP p a -> MaybeP p a
    asdf NothingP  = NothingP
    asdf (JustP a) = JustP a
    
    maybeP  :: Proxy p -> (p ~ P0 => r) -> (p ~ P1 => a -> r) -> MaybeP p a -> r
    eitherP :: Proxy p -> (p ~ P0 => a -> r) -> (p ~ P1 => b -> r) -> EitherP p a b -> r
    
    asdf :: Phase p => MaybeP p a -> MaybeP p a
    asdf a = maybeP phase () id a
    
    data.hs:116:29:
     Could not deduce (MaybeP p a ~ MaybeP p0 a0)
     from the context (Phase p)
       bound by the type signature for
                  asdf :: Phase p => MaybeP p a -> MaybeP p a
       at data.hs:116:1-29
     NB: `MaybeP' is a type function, and may not be injective
     In the fourth argument of `maybeP', namely `a'
     In the expression: maybeP phase () id a
     In an equation for `asdf': asdf a = maybeP phase () id a