Inference on most expressions. HM based.

Still have to figure out how to infer type of lambda variables, as well
as how function application on polymorphic should work
This commit is contained in:
sebastianselander 2023-02-13 12:17:49 +01:00
parent a1e9624d5e
commit 73dc2e4b6a
12 changed files with 347 additions and 310 deletions

View file

@ -1,20 +1,28 @@
Program. Program ::= [Bind];
EId. Exp3 ::= Ident;
EInt. Exp3 ::= Integer;
ELet. Exp3 ::= "let" [Bind] "in" Exp;
EApp. Exp2 ::= Exp2 Exp3;
EAdd. Exp1 ::= Exp1 "+" Exp2;
EAbs. Exp ::= "\\" Ident "." Exp;
Bind. Bind ::= Ident [Ident] "=" Exp;
EAnn. Exp5 ::= Exp5 ":" Type ;
EId. Exp4 ::= Ident;
EConst. Exp4 ::= Const;
EApp. Exp3 ::= Exp3 Exp4;
ELet. Exp2 ::= "let" Bind "in" Exp;
EAdd. Exp1 ::= Exp1 "+" Exp2;
EAbs. Exp ::= "\\" Ident "." Exp;
CInt. Const ::= Integer ;
CStr. Const ::= String ;
TMono. Type1 ::= Ident ;
TPoly. Type1 ::= Ident ;
TFun. Type ::= Type1 "->" Type ;
separator Bind ";";
separator Ident " ";
coercions Exp 3;
coercions Type 1 ;
coercions Exp 5;
comment "--";
comment "{-" "-}";

View file

@ -1,7 +1,7 @@
.PHONY : sdist clean
language : src/Grammar/Test
cabal install --installdir=.
cabal install --installdir=. --overwrite-policy=always
src/Grammar/Test.hs src/Grammar/Lex.x src/Grammar/Par.y : Grammar.cf
bnfc -o src -d $<

View file

@ -17,7 +17,7 @@ extra-source-files:
common warnings
ghc-options: -Wall
ghc-options: -W
executable language
import: warnings
@ -30,7 +30,12 @@ executable language
Grammar.Par
Grammar.Print
Grammar.Skel
LambdaLifter
Grammar.ErrM
-- LambdaLifter
TypeChecker.TypeChecker
TypeChecker.TypeCheckerIr
-- Renamer.Renamer
-- Renamer.RenamerIr
-- Interpreter
hs-source-dirs: src
@ -40,7 +45,7 @@ executable language
, mtl
, containers
, either
, array
, extra
, array
default-language: GHC2021

View file

@ -1,28 +0,0 @@
{-# LANGUAGE TypeFamilies, PatternSynonyms, StandaloneDeriving #-}
module Abs where
import Data.String
data Program a = Program [Bind a]
data Bind a = Bind Ident [Ident] (Exp a)
newtype Ident = Ident String
deriving (Eq, Ord, Show, Data.String.IsString)
data Exp a = EId (IdFamily a) Ident
| EInt (IntFamily a) Integer
| EAdd (AddFamily a) (Exp a) (Exp a)
| EApp (AppFamily a) (Exp a) (Exp a)
| EAbs (AbsFamily a) Ident (Exp a)
| ELet (LetFamily a) [Bind a] (Exp a)
| EExp (ExpFamily a) (Exp a)
type family IdFamily a
type family IntFamily a
type family AddFamily a
type family AppFamily a
type family AbsFamily a
type family LetFamily a
type family ExpFamily a

View file

@ -1,253 +0,0 @@
{-# LANGUAGE LambdaCase, OverloadedStrings, TypeFamilies, PatternSynonyms #-}
module LambdaLifter (lambdaLift, freeVars, abstract, rename, collectScs) where
import Data.List (mapAccumL)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.Set (Set, (\\))
import qualified Data.Set as Set
import Data.Tuple.Extra (uncurry3)
import Grammar.Abs
import Prelude hiding (exp)
import qualified Abs as A
import Data.Void
-- | Lift lambdas and let expression into supercombinators.
lambdaLift :: Program -> Program
lambdaLift = collectScs . rename . abstract . freeVars
-- | Annotate free variables
freeVars :: Program -> AnnProgram
freeVars (Program ds) = [ (n, xs, freeVarsExp (Set.fromList xs) e)
| Bind n xs e <- ds
]
freeVarsExp :: Set Ident -> Exp -> AnnExp
freeVarsExp lv = \case
EId n | Set.member n lv -> (Set.singleton n, AId n)
| otherwise -> (mempty, AId n)
EInt i -> (mempty, AInt i)
EApp e1 e2 -> (Set.union (freeVarsOf e1') (freeVarsOf e2'), AApp e1' e2')
where e1' = freeVarsExp lv e1
e2' = freeVarsExp lv e2
EAdd e1 e2 -> (Set.union (freeVarsOf e1') (freeVarsOf e2'), AAdd e1' e2')
where e1' = freeVarsExp lv e1
e2' = freeVarsExp lv e2
EAbs n e -> (Set.delete n $ freeVarsOf e', AAbs n e')
where e' = freeVarsExp (Set.insert n lv) e
ELet bs e -> (Set.union bsFree eFree, ALet bs' e')
where
bsFree = freeInValues \\ nsSet
eFree = freeVarsOf e' \\ nsSet
bs' = zipWith3 ABind ns xs es'
e' = freeVarsExp e_lv e
(ns, xs, es) = fromBinders bs
nsSet = Set.fromList ns
e_lv = Set.union lv nsSet
es' = map (freeVarsExp e_lv) es
freeInValues = foldr1 Set.union (map freeVarsOf es')
freeVarsOf :: AnnExp -> Set Ident
freeVarsOf = fst
fromBinders :: [Bind] -> ([Ident], [[Ident]], [Exp])
fromBinders bs = unzip3 [ (n, xs, e) | Bind n xs e <- bs ]
-- AST annotated with free variables
type AnnProgram = [(Ident, [Ident], AnnExp)]
type AnnExp = (Set Ident, AnnExp')
data ABind = ABind Ident [Ident] AnnExp deriving Show
data AnnExp' = AId Ident
| AInt Integer
| AApp (Set Ident, AnnExp') (Set Ident, AnnExp')
| AAdd (Set Ident, AnnExp') (Set Ident, AnnExp')
| AAbs Ident (Set Ident, AnnExp')
| ALet [ABind] (Set Ident, AnnExp')
deriving Show
-- | Lift lambdas to let expression of the form @let sc = \x -> rhs@
abstract :: AnnProgram -> Program
abstract prog = Program $ map f prog
where
f :: (Ident, [Ident], AnnExp) -> Bind
f (name, pars, rhs@(_, e)) =
case e of
AAbs par body -> Bind name (snoc par pars) $ abstractExp body
_ -> Bind name pars $ abstractExp rhs
abstractExp :: AnnExp -> Exp
abstractExp (free, exp) = case exp of
AId n -> EId n
AInt i -> EInt i
AApp e1 e2 -> EApp (abstractExp e1) (abstractExp e2)
AAdd e1 e2 -> EAdd (abstractExp e1) (abstractExp e2)
ALet bs e -> ELet [Bind n xs (abstractExp e1) | ABind n xs e1 <- bs ] $ abstractExp e
AAbs n e -> foldl EApp sc (map EId fvList)
where
fvList = Set.toList free
bind = Bind "sc" [] e'
e' = foldr EAbs (abstractExp e) (fvList ++ [n])
sc = ELet [bind] (EId (Ident "sc"))
snoc :: a -> [a] -> [a]
snoc x xs = xs ++ [x]
-- | Rename all supercombinators and variables
rename :: Program -> Program
rename (Program ds) = Program $ map (uncurry3 Bind) tuples
where
tuples = snd (mapAccumL renameSc 0 ds)
renameSc i (Bind n xs e) = (i2, (n, xs', e'))
where
(i1, xs', env) = newNames i xs
(i2, e') = renameExp env i1 e
renameExp :: Map Ident Ident -> Int -> Exp -> (Int, Exp)
renameExp env i = \case
EId n -> (i, EId . fromMaybe n $ Map.lookup n env)
EInt i1 -> (i, EInt i1)
EApp e1 e2 -> (i2, EApp e1' e2')
where
(i1, e1') = renameExp env i e1
(i2, e2') = renameExp env i1 e2
EAdd e1 e2 -> (i2, EAdd e1' e2')
where
(i1, e1') = renameExp env i e1
(i2, e2') = renameExp env i1 e2
ELet bs e -> (i3, ELet (zipWith3 Bind ns' xs es') e')
where
(i1, e') = renameExp e_env i e
(ns, xs, es) = fromBinders bs
(i2, ns', env') = newNames i1 ns
e_env = Map.union env' env
(i3, es') = mapAccumL (renameExp e_env) i2 es
EAbs n e -> (i2, EAbs (head ns) e')
where
(i1, ns, env') = newNames i [n]
(i2, e') = renameExp (Map.union env' env ) i1 e
newNames :: Int -> [Ident] -> (Int, [Ident], Map Ident Ident)
newNames i old_names = (i', new_names, env)
where
(i', new_names) = getNames i old_names
env = Map.fromList $ zip old_names new_names
getName :: Int -> Ident -> (Int, Ident)
getName i (Ident s) = (i + 1, makeName s i)
getNames :: Int -> [Ident] -> (Int, [Ident])
getNames i ns = (i + length ss, zipWith makeName ss [i..])
where
ss = map (\(Ident s) -> s) ns
makeName :: String -> Int -> Ident
makeName prefix i = Ident (prefix ++ "_" ++ show i)
-- | Collects supercombinators by lifting appropriate let expressions
collectScs :: Program -> Program
collectScs (Program ds) = Program $ concatMap collectOneSc ds
where
collectOneSc (Bind name args rhs) = Bind name args rhs' : scs
where (scs, rhs') = collectScsExp rhs
collectScsExp :: Exp -> ([Bind], Exp)
collectScsExp = \case
EId n -> ([], EId n)
EInt i -> ([], EInt i)
EApp e1 e2 -> (scs1 ++ scs2, EApp e1' e2')
where
(scs1, e1') = collectScsExp e1
(scs2, e2') = collectScsExp e2
EAdd e1 e2 -> (scs1 ++ scs2, EAdd e1' e2')
where
(scs1, e1') = collectScsExp e1
(scs2, e2') = collectScsExp e2
EAbs x e -> (scs, EAbs x e')
where
(scs, e') = collectScsExp e
ELet bs e -> (rhss_scs ++ e_scs ++ local_scs, mkEAbs non_scs' e')
where
(rhss_scs, bs') = mapAccumL collectScs_d [] bs
scs' = [ Bind n xs rhs | Bind n xs rhs <- bs', isEAbs rhs]
non_scs' = [ Bind n xs rhs | Bind n xs rhs <- bs', not $ isEAbs rhs]
local_scs = [ Bind n (xs ++ [x]) e1 | Bind n xs (EAbs x e1) <- scs']
(e_scs, e') = collectScsExp e
collectScs_d scs (Bind n xs rhs) = (scs ++ rhs_scs1, Bind n xs rhs')
where
(rhs_scs1, rhs') = collectScsExp rhs
isEAbs :: Exp -> Bool
isEAbs = \case
EAbs {} -> True
_ -> False
mkEAbs :: [Bind] -> Exp -> Exp
mkEAbs [] e = e
mkEAbs bs e = ELet bs e
{----------- BOILERPLATE -----------}
data LL
type instance A.IdFamily LL = ()
type instance A.IntFamily LL = ()
type instance A.AddFamily LL = (Set Ident, Set Ident)
type instance A.AppFamily LL = (Set Ident, Set Ident)
type instance A.AbsFamily LL = Set Ident
type instance A.LetFamily LL = Set Ident
type instance A.ExpFamily LL = Void
pattern LLId ident = A.EId () ident
pattern LLInt int = A.EInt () int
pattern LLAdd s1 s2 e1 e2 = A.EAdd (s1,s2) e1 e2
pattern LLApp s1 s2 e1 e2 = A.EApp (s1,s2) e1 e2
pattern LLAbs s i e = A.EAbs s i e
pattern LLLet s binds e = A.ELet s binds e
pattern LLExp v e = A.EExp v e
{-
data AnnExp' = AId Ident
| AInt Integer
| AApp (Set Ident, AnnExp') (Set Ident, AnnExp')
| AAdd (Set Ident, AnnExp') (Set Ident, AnnExp')
| AAbs Ident (Set Ident, AnnExp')
| ALet [ABind] (Set Ident, AnnExp')
deriving Show
-}

View file

@ -3,7 +3,6 @@ module Main where
import Grammar.Par (myLexer, pProgram)
import Grammar.Print (printTree)
import LambdaLifter (lambdaLift)
import System.Environment (getArgs)
import System.Exit (exitFailure, exitSuccess)
@ -17,17 +16,4 @@ main = getArgs >>= \case
putStrLn "SYNTAX ERROR"
putStrLn err
exitFailure
Right prg -> do
putStrLn "-- Parse"
putStrLn $ printTree prg
-- putStrLn "\n-- Abstract"
-- putStrLn . printTree $ (abstract . freeVars) prg
-- putStrLn "\n-- Rename"
-- putStrLn . printTree $ (rename . abstract . freeVars) prg
putStrLn "\n-- Lamda lifter"
putStrLn . printTree $ lambdaLift prg
putStrLn ""
exitSuccess
Right prg -> putStrLn "NO SYNTAX ERROR"

90
src/Renamer/Renamer.hs Normal file
View file

@ -0,0 +1,90 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}
module Renamer.Renamer (rename) where
import Control.Applicative (Applicative (liftA2))
import Control.Monad.Except (MonadError (throwError))
import Control.Monad.RWS (MonadState, gets, modify)
import Control.Monad.State (StateT, evalStateT)
import Data.Set (Set)
import qualified Data.Set as Set
import Grammar.Abs
import Grammar.ErrM (Err)
import Grammar.Print (printTree)
import qualified Renamer.RenamerIr as R
data Cxt = Cxt
{ uniques :: [(Ident, R.Unique)]
, nextUnique :: R.Unique
, sig :: Set Ident
}
initCxt :: Cxt
initCxt = Cxt
{ uniques = []
, nextUnique = R.Unique 0
, sig = mempty
}
newtype Rn a = Rn { runRn :: StateT Cxt Err a }
deriving (Functor, Applicative, Monad, MonadState Cxt, MonadError String)
rename :: Program -> Err R.Program
rename p = evalStateT (runRn $ renameProgram p) initCxt
renameProgram :: Program -> Rn R.Program
renameProgram (Program ds (Main e)) = do
ds' <- mapM renameDef ds
e' <- renameExp e
pure $ R.Program ds' (R.Main e')
renameDef :: Def -> Rn R.Def
renameDef = \case
DExp x t _ xs e -> do
newSig x
xs' <- mapM newUnique xs
e' <- renameExp e
let e'' = foldr ($) e' . zipWith R.EAbs xs' $ fromTree t
pure . R.DBind $ R.Bind x t e''
renameExp :: Exp -> Rn R.Exp
renameExp = \case
EId x -> R.EId <$> findBind x
EInt i -> pure $ R.EInt i
EApp e e1 -> liftA2 R.EApp (renameExp e) $ renameExp e1
EAdd e e1 -> liftA2 R.EAdd (renameExp e) $ renameExp e1
EAbs x t e -> do
x' <- newUnique x
e' <- renameExp e
pure $ R.EAbs x' t e'
findBind :: Ident -> Rn R.Name
findBind x = lookupUnique x >>= \case
Just u -> pure $ R.Nu u
Nothing -> gets (Set.member x . sig) >>= \case
False -> throwError ("Unbound variable " ++ printTree x)
True -> pure $ R.Ni x
newUnique :: Ident -> Rn R.Unique
newUnique x = do
u <- gets nextUnique
modify $ \env -> env { nextUnique = succ u
, uniques = (x, u) : env.uniques
}
pure u
newSig :: Ident -> Rn ()
newSig x = modify $ \cxt -> cxt { sig = Set.insert x cxt.sig}
lookupUnique :: Ident -> Rn (Maybe R.Unique)
lookupUnique x = lookup x <$> gets uniques
fromTree :: Type -> [Type]
fromTree = fromTree' []
fromTree' :: [Type] -> Type -> [Type]
fromTree' acc = \case
TFun t t1 -> acc ++ [t] ++ fromTree t1
other -> other : acc

84
src/Renamer/RenamerIr.hs Normal file
View file

@ -0,0 +1,84 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
module Renamer.RenamerIr where
import Grammar.Abs (Ident, Type (..))
import Grammar.Print
data Program = Program [Def] Main
deriving (Eq, Ord, Show, Read)
newtype Main = Main Exp
deriving (Eq, Ord, Show, Read)
newtype Def = DBind Bind
deriving (Eq, Ord, Show, Read)
data Name = Nu Unique | Ni Ident deriving (Ord, Show, Eq, Read)
newtype Unique = Unique Int deriving (Enum, Eq, Read, Ord)
instance Show Unique where show (Unique i) = "x" ++ show i
data Exp
= EId Name
| EInt Integer
| EApp Exp Exp
| EAdd Exp Exp
| EAbs Unique Type Exp
deriving (Eq, Ord, Show, Read)
data Bind = Bind Ident Type Exp
deriving (Eq, Ord, Show, Read)
instance Print Program where
prt i = \case
Program defs main -> prPrec i 0 (concatD [prt 0 defs, prt 0 main])
instance Print Def where
prt i (DBind b) = prPrec i 0 $ concatD [prt 0 b, doc (showString ";")]
instance Print Bind where
prt i = \case
Bind x t e -> prPrec i 0 $ concatD
[ prt 0 x
, doc (showString ":")
, prt 0 t
, doc (showString "=")
, prt 0 e]
instance Print [Def] where
prt _ [] = concatD []
prt _ (x:xs) = concatD [prt 0 x, prt 0 xs]
instance Print Main where
prt i = \case
Main exp -> prPrec i 0 $ concatD
[ doc (showString "main")
, doc (showString "=")
, prt 0 exp
, doc (showString ";")
]
instance Print Exp where
prt i = \case
EId u -> prPrec i 3 (concatD [prt 0 u])
EInt n -> prPrec i 3 (concatD [prt 0 n])
EApp e e1 -> prPrec i 2 (concatD [prt 2 e, prt 3 e1])
EAdd e e1 -> prPrec i 1 (concatD [prt 1 e, doc (showString "+"), prt 2 e1])
EAbs u t e -> prPrec i 0 (concatD [doc (showString "\\"), prt 0 u, doc (showString ":"), prt 0 t, doc (showString "."), prt 0 e])
instance Print Name where
prt _ = \case
Ni i -> prt 0 i
Nu u -> prt 0 u
instance Print Unique where
prt _ = doc . showString . show

View file

View file

@ -0,0 +1,122 @@
{-# LANGUAGE LambdaCase #-}
module TypeChecker.TypeChecker where
import Control.Monad (when)
import Control.Monad.Except (throwError)
import Control.Monad.Reader (ReaderT)
import qualified Control.Monad.Reader as R
import Control.Monad.Writer (WriterT)
import qualified Control.Monad.Writer as W
import Data.Map (Map)
import qualified Data.Map as M
import Data.Set (Set)
import qualified Data.Set as S
import qualified Grammar.Abs as Old
import Grammar.ErrM (Err)
import TypeChecker.TypeCheckerIr
data Ctx = Ctx
{ env :: [Map Ident Type]
, sig :: Map Ident Bind
, typs :: Set Ident
}
type Check a = WriterT String (ReaderT Ctx Err) a
inferExp :: Old.Exp -> Check Type
inferExp = \case
Old.EAnn e t -> do
infT <- inferExp e
when (t /= infT) (throwError $ show (AnnotatedMismatch (show e) (show t) (show infT)))
return infT
Old.EConst c -> case c of
(CInt i) -> return (TMono $ Old.Ident "Int")
(CStr s) -> return (TMono $ Old.Ident "String")
Old.EId i -> lookupEnv i
Old.EAdd e1 e2 -> do
t1 <- inferExp e1
t2 <- inferExp e2
case (t1, t2) of
(TMono (Old.Ident "Int"), TMono (Old.Ident "Int")) -> return t1
_ -> throwError $ show (NotNumber (show t1))
return t1
-- This is wrong currently. (a -> b) should be able to take String
Old.EApp e1 e2 -> do
inferExp e1 >>= \case
TFun mono@(TMono i) t2 -> do
t <- inferExp e2
when (t /= mono) (throwError $ show $ TypeMismatch (show t) (show mono))
return t
-- Not entirely correct. Should sometimes be able to provide mono types where poly expected.
-- i.e id : a -> a; id "string"
TFun poly@(TPoly f) t2 -> do
t <- inferExp e2
when (t /= poly) (throwError $ show (TypeMismatch (show t) (show poly)))
return t
t -> throwError $ show (NotFunction "Expected a function, but got:" (show t))
Old.EAbs i e -> undefined
Old.ELet b e -> undefined
-- Aux
lookupEnv :: Ident -> Check Type
lookupEnv i =
R.asks env >>= \case
[] -> throwError $ show (UnboundVar "Variable not found" (show i))
xs -> lookupEnv' i xs
where
lookupEnv' :: Ident -> [Map Ident Type] -> Check Type
lookupEnv' i [] = throwError $ show (UnboundVar "Variable not found" (show i))
lookupEnv' i (x : xs) = case M.lookup i x of
Just t -> return t
Nothing -> lookupEnv' i xs
lookupSig :: Ident -> Check Bind
lookupSig b =
R.asks sig >>= \m -> case M.lookup b m of
Nothing -> undefined
Just b -> return b
insertEnv :: Ident -> Type -> Ctx -> Ctx
insertEnv i t c =
case env c of
[] -> Ctx{env = [M.insert i t mempty]}
(x : xs) -> Ctx{env = M.insert i t x : xs}
data Error
= TypeMismatch String String
| NotNumber String
| FunctionTypeMismatch String String String
| NotFunction String String
| UnboundVar String String
| AnnotatedMismatch String String String
| Default String
showErr :: Error -> String
showErr = \case
TypeMismatch expected found -> unwords ["Expected type:", show expected, "but got", show found]
NotNumber mess -> "Expected a number, but got: " <> mess
NotFunction mess func -> mess <> ": " <> func
FunctionTypeMismatch func expected found -> unwords ["Function:", show func, "expected:", show expected, "but got:", show found]
UnboundVar mess var -> mess <> ": " <> var
AnnotatedMismatch expression expected found ->
unwords
[ "Expression"
, expression
, "expected type"
, expected
, "but was inferred as type"
, found
]
Default mess -> mess
instance Show Error where
show = showErr

View file

@ -0,0 +1,22 @@
{-# LANGUAGE LambdaCase #-}
module TypeChecker.TypeCheckerIr
( Program(..)
, Bind(..)
, Ident
, Type(..)
, Const(..)
, Exp(..)
)
where
import Grammar.Abs (Program(..), Bind(..), Ident, Type(..), Const(..))
data Exp
= EAnn Exp Type
| EId Ident Type
| EConst Const Type
| EApp Exp Exp Type
| EAdd Exp Exp Type
| EAbs Ident Exp Type
deriving (Eq, Ord, Show, Read)

1
test_program Normal file
View file

@ -0,0 +1 @@
main = \x. x + (3 : Int)