I would like to define type level safe C language AST. So far, I come up with with something like this:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE RankNTypes #-}
data Defn
= Func String [Block]
type Block = forall a. BlockItem a
type Stmt = BlockItem StmtType
data BlockItemKind = StmtType
data BlockItem :: BlockItemKind -> * where
Var :: String -> Expr -> BlockItem a
SideEff :: Expr -> BlockItem StmtType
Return :: Expr -> BlockItem StmtType
data Expr
= Lit Int
The key point is the BlockItem data type. In C standard, there are two very similar structures – block and statements. Block is basically just list of statements that can also contain variable declaration. In this code, I'm trying to declare statement as subset of block items constructors (as SideEff and Return).
However, this code does not work as expected. Consider following code which is printing this AST:
showDefn :: Defn -> String
showDefn (Func name block) = "func " ++ name ++ " . " ++ show (showBlock <$> block)
showBlock :: Block -> String
showBlock (Var name e) = name ++ " = " ++ showExpr e ++ ";"
showBlock e = showStmt e
showStmt :: Stmt -> String
showStmt (SideEff e) = showExpr e ++ ";"
showStmt (Return e) = "return " ++ showExpr e ++ ";"
showExpr :: Expr -> String
showExpr (Lit x) = show x
When I'm evaluating showDefn with [Block], everything works well. This code however not work when I'm using Stmt type:
main :: IO ()
main = do
-- FOLLOWING WORK
print $ showDefn (Func "foo" [Var "a" (Lit 2), Var "b" (Lit 3)])
-- FOLLOWING DOES NOT WORK
print $ showDefn (Func "foo" [Return (Lit 0)])
print $ showDefn (Func "foo" [Var "a" (Lit 1), SideEff (Lit 2)])
You can run the code here.
Where is the problem? I'm not even sure, if this is the right design.